SessionChunkReader class final
Backward/forward byte-scan reader over one JSONL session file.
All reads are record-atomic: a window is grown until its oldest boundary sits on a record start, so a record straddling the initial read window is never truncated (issue #135 E1) — the window doubles until the whole record is inside, bounded only by the file length.
The parse cache (issue #1498) assumes the session JSONL is APPEND-ONLY:
entries survive observed growth and are dropped on any observed shrink
or mtime anomaly (see _noteParseCacheStamp). A writer that rewrites
earlier bytes in place while keeping size AND mtime is unobservable at
that granularity — the same signal the windowed storage itself relies
on — and would serve stale records.
Constructors
- SessionChunkReader({required FileSystem fs, required String path, int startWindowBytes = 128 << 10, SessionParseExecutor? parseExecutor, int parseCacheEntries = defaultParseCacheEntries, int parseCacheBytes = defaultParseCacheBytes})
-
Creates a reader over
path.startWindowBytesis the first backward read size; it doubles up to the file length as needed. The parse cache (parseCacheEntries×parseCacheBytes, issue #1498) remembers parsed lines across scans —0entries disables it.
Properties
- canReadRanges → bool
-
Whether the backing filesystem supports byte-range reads. Hosts without
it keep the whole-file load path.
no setter
- fs → FileSystem
-
The store backing path.
final
- hashCode → int
-
The hash code for this object.
no setterinherited
- locatePassCount → int
-
Blocks read by locateRecord this open (AC6 instrumentation: a
jump through the sparse offset map adds none).
no setter
- parseCacheBytes → int
-
Remembered raw-line bytes across all cached lines (issue #1498).
0means no byte budget (the entry cap still applies); combined with parseCacheEntries0the cache is fully disabled.final - parseCacheEntries → int
-
Remembered distinct lines (issue #1498).
final
- parseCacheEntryCount → int
-
no setter
- parseCacheHitCount → int
-
Lines the cache saved from decode+parse (issue #1498 instrumentation,
the locatePassCount precedent): a repeat scan must add misses, not
parse work.
no setter
- parseCacheMissCount → int
-
no setter
- parseExecutor → SessionParseExecutor?
-
Where record parsing runs —
nullkeeps the inline batched path (web); IO hosts inject the isolate executor (issue #199).final - path → String
-
final
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
- startWindowBytes → int
-
final
Methods
-
countRecords(
) → Future< int> - Streams the file counting newlines — no JSON decode (~1 s per 300 MB). Returns the number of RECORDS (lines minus the header line).
-
locateRecord(
String recordId) → Future< int?> -
Streaming id seek (issue #135 AC6): scans the file block-wise for
the line whose record id is
recordIdand returns its byte offset;nullwhen absent. Cost is one pass — callers jump through the sparse offset map instead whenever the record was read before (locatePassCount instruments the difference). -
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
readAround(
int byteOffset, {int maxRecords = defaultChunkRecords, int maxBytes = defaultChunkBytes}) → Future< SessionChunk> -
Reads a window centered on the record at
byteOffset(a jump target): the record itself, the newer records below it, and older records above it, bounded by the same dual cap. -
readBefore(
int anchorOffset, {int maxRecords = defaultChunkRecords, int maxBytes = defaultChunkBytes}) → Future< SessionChunk> -
Reads up to
maxRecordsrecords immediately aboveanchorOffset(the byte offset of a record start — typically a previous chunk's SessionChunk.firstOffset). -
readBlockBefore(
int anchorOffset, {int maxRecords = defaultChunkRecords, int maxBytes = defaultChunkBytes, bool shallowGiantCustoms = false}) → Future< SessionChunk> -
Boundary-walk block read (issue #503): ONE strip of up to
maxBytesimmediately aboveanchorOffset, trimmed to the newestmaxRecordsrecords. Unlike the capped readBefore — whose doubling window re-splits and re-decodes its whole buffer on every pass — every byte here is read, decoded and parsed exactly once, so a deep resume walk costs one linear pass over the span instead of ~5x the parse work (a 400 MB tail-after-compaction walked 10s+ through readBefore). -
readForward(
int fromOffset, {int? maxRecords, int? maxBytes}) → Future< SessionChunk> -
Reads records from the line boundary
fromOffsetto EOF. WithmaxRecords/maxBytes(the jump and page-down paths) at most that much is kept, scanned block-wise — the below-range of a deep-paged window is never read whole; without caps (the live-tail ingest path — an external CLI appended while the app held the window) everything is returned, so a burst of external appends is never silently dropped. The chunk's SessionChunk.limitOffset is the end of the last kept record, so a capped read resumes exactly after it. -
readHeader(
) → Future< SessionHeader> - Reads the session header (first line) only — the metadata an open needs without touching the record body. The prefix read doubles from 4 KiB so a normal open moves a few KiB, never the file.
-
readNewestSessionInfo(
) → Future< SessionInfoRecord?> -
The newest
session_inforecord of the file (null when it holds none) — the record form of readNewestSessionInfoName, so callers probing several segments can tell "no record" apart from an empty (name-clearing) one. -
readNewestSessionInfoName(
) → Future< String?> -
The newest
session_inforecord's name via two bounded probes (issue #369). The HEAD window is read first: a named-at-creation session keeps its session_info among the first records, so the 400 MB giant case resolves from the file start without any backward walk (AC2). The TAIL window's verdict wins whenever it sees a record — bytes after the head are newer, so a rename-at-end still beats the header name (AC5, newest within the cap) — and on a head miss it is the bounded-cap fallback AC1 names. The body between the windows is never read. Returns the name,nullwhen the probes see no session_info or the newest probed record clears the name (empty/whitespace — the caller's contract). Throws SessionException like readTail when the file is missing. -
readRecordsByIds(
Set< String> ids) → Future<Map< String, SessionRecord> > -
Hidden-range drill-in (issue #385 F4): one forward block scan
collecting the records whose ids appear in
ids— the records a HiddenRangeRecord covers. They sit OFF the kept branch (the compaction evicted them from the context), so the branch readers never surface them; this reads the raw file instead. Serves what exists: a range still open at the file tail resolves the records already written, honestly; ids absent from the file stay absent from the result. Cost is one pass, bounded like locateRecord. -
readTail(
{int maxRecords = defaultChunkRecords, int maxBytes = defaultChunkBytes}) → Future< SessionChunk> -
Reads up to the newest
maxRecordsrecords (bounded bymaxBytes). -
stat(
) → Future< ({int mtimeMs, int size})?> -
File facts for staleness guards;
nullwhen the file is gone. -
toString(
) → String -
A string representation of this object.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited