Skip to main content

Module split

Module split 

Source
Expand description

One exec’s output as two byte channels, stdout and stderr.

§One drive, one cursor, two channels

The daemon publishes stdout and stderr into one SSE stream with a stream discriminator per output frame, sharing one offset space. That’s what makes the byte cursor work: a single cursor can’t be split into two without inventing an ordering between them that the wire never stated. So ExecHandle::split runs one ExecHandle::for_each_event_async drive, reconnects and all, and routes each output frame to its side’s channel. Order is kept within each side and isn’t recoverable between them; a caller that needs the interleaving reads ExecHandle::stream_with instead.

Both bindings’ process shapes (Node’s and Python’s Session.spawn) read these channels, so the routing, the gap attribution and the gap policy are written once, here.

§A gap is attributed to the next output frame’s stream

A gap frame carries no discriminator: the offset space is shared, so a gap is a hole in the combined stream and the daemon can’t say which side’s bytes were in it. The bytes that resumed after the hole came out of one side, and that side’s log is the one with a hole in it, so a gap is held until the next output frame and takes its stream. A gap the stream ends on has nothing after it, and is recorded with no stream rather than guessed at.

§The gap policy

GapPolicy::Error, the default, ends both channels with an WireKind::OutputGap error naming the range: both, because the shared offset space can’t say which side lost the bytes, and erroring one would leave the other looking complete when it may be the truncated one. An error can’t be ignored by the obvious consumer, a loop over one side’s chunks, and a swallowed gap would hand it a contiguous-looking stream that’s missing bytes. GapPolicy::Event records the gap in Split::gaps and keeps both channels open, for a caller that wants the surviving bytes more than the completeness guarantee (a log tail, a progress display).

StreamOptions::error_on_gap is ignored here and always off: a drive that ended on the gap would lose the range’s attribution and never fill Split::gaps. The gap arrives as an event and the policy is applied one layer up, in this module’s drive.

§One chunk per channel

Each channel holds one chunk, for the reason exec.rs gives: the daemon’s SSE body is the backpressure signal, and buffering here would defeat the cursor the drive reconnects at. Per channel rather than shared, so a slow stderr reader doesn’t hold chunks stdout has already been handed. A channel nobody reads still stalls the drive once it holds its one chunk, the same bound a pipe has, so a caller reads both sides concurrently or drops the one it doesn’t want. Dropping either receiver ends the drive, since both share it.

Structs§

GapLog
The gaps a split drive has recorded so far, shared with the drive.
OutputGap
One byte range the daemon couldn’t replay, under GapPolicy::Event.
Split
One exec’s output as two channels, and the drive that fills them.

Enums§

GapPolicy
What a split drive does when the daemon reports evicted output. See the module docs.

Type Aliases§

SplitItem
What a channel carries: a chunk of one side’s bytes, or the error that ended it.