13.9. Streaming and memory mapping

A stream is a list too large or too long-lived to hold in memory at once, kept in a file and read or written in batches. Any pool can read a stream another pool is writing, and a finished stream file can be read at random without loading it. This section specifies the stream file format, its compression, how processes share an open stream, and what a reader may rely on while the stream is still being written.

Stream types. Three Morloc types name a stream, each a 16-byte slot in voidstar (schema codes F, I, O):

Type Use

IFile [a]

A finished file, read at random by element index.

IStream a

A stream read forward in batches. Several readers share one cursor, so together they consume each batch once.

OStream a

A stream written in batches.

The slot’s first byte says what follows in its last eight: 0 for a relative pointer to a path (length, then bytes), 1 for the index of an open stream in the run’s stream registry (below). A path can cross between runs; an index means something only inside the run that opened it.

File format. A stream file is a sequence of packets (Packet protocol):

Part Contents

Head

A stream packet (type 3) whose metadata holds the schema of the list, a followed by the element schema. Its length field is all ones.

Batches

Data packets, one per batch. Each payload is a self-contained voidstar array of that batch’s elements, its pointers relative to the start of the payload.

Footer

A footer packet (type 4) whose metadata holds a batch index (offset and element count of every batch), a final marker, and a status.

Tail

8 bytes: the footer’s length as a 32-bit integer, then the magic bytes reversed (07 07 f8 6d).

A reader finds the footer from the end of the file through the tail, so random access needs no scan. The status is closed for a stream written to the end, or paused for one a writer stopped and may resume. While a stream is open for writing, a temporary footer carrying only diagnostics is rewritten after every batch, so the file always ends in a valid tail.

Compression. Each batch is compressed separately with Zstandard, so a reader can decompress any batch without the others. A batch is split into independent frames of at most 16 MiB, listed in the batch’s metadata, so they can be decompressed in parallel straight into shared memory. The user’s compression level 0-9 maps to Zstandard levels 0, 1, 3, 6, 9, 12, 15, 19, 21 and 22, with long-distance matching from level 7.

Write-behind. A writer fills a buffer in shared memory (16 MiB by default). When compression is on and the buffer is full, it is sealed and handed to background threads for compression, and the writer continues into a fresh buffer. Compressed batches are appended to the file in the order they were sealed, so compression never reorders elements. The writer waits only when more than a fixed number of sealed batches are outstanding (8 by default), and every flush, close and end of call writes everything sealed so far.

Sharing an open stream. A stream’s identity lives in a registry in shared memory, created with the run’s first volume. Each slot records the file’s path, schema, write cursor, element count and batch index, and a generation number that changes when the slot is opened and when it is released. A stream index carries the generation it was made with, and every operation checks it, so an index to a closed stream can never touch the stream that later reuses its slot. Each process keeps its own descriptor and read-only mapping of the file. A writer holds an exclusive file lock while the stream is open.

Readers. An IFile maps the file and locates an element through the batch index. A batch stored uncompressed and 8-byte aligned is read where it lies in the mapping; any other batch is decompressed or copied into shared memory first. An IStream reads forward from the shared cursor. A reader that opens a stream still being written sees the batches written before it opened, up to the temporary footer; reading past the end yields an empty batch.

Rewriting a file. Opening an OStream on a file that already has data writes a new file and renames it into place, so a reader that still has the old file mapped keeps a consistent view. Where a rename is impossible (the directory is not writable, say) and nothing in the program is reading the file, it is truncated in place instead. The runtime never deletes a stream file; only a half-written temporary file is removed on failure.