Skip to main content

Module output

Module output 

Source
Expand description

RunObserver implementations for the two normative presentation modes of EXEC-016.

Two distinct observer types live here. They share the EXEC-016 vocabulary (mode-specific surfacing of a task’s captured stdout and stderr) but compose differently with the scheduler:

  • LiveOutputObserver splits incoming byte chunks on \n and writes each complete line to the configured stdout / stderr sink prefixed with [project:task] . Multiple in-flight tasks interleave at line granularity; never within a line. Trailing partial bytes (a write with no terminating newline) are buffered until the next \n or until the task terminates, at which point they are flushed with an appended \n so the line-atomicity invariant is preserved.

  • BufferedOutputObserver accumulates incoming byte chunks per task into in-memory buffers and writes each task’s full stdout followed by its full stderr as two contiguous blocks when the task terminates. No tag prefix; bytes are verbatim. Output from different tasks never interleaves.

Both observers are generic over the sink types O: Write + Send (stdout) and E: Write + Send (stderr). Production code passes std::io::Stdout / std::io::Stderr (or their lock guards). Tests pass Vec<u8> (which implements Write) and call LiveOutputObserver::into_inner / BufferedOutputObserver::into_inner after the run to extract the captured bytes.

Interior mutability is a single std::sync::Mutex per observer guarding the sinks together with the per-task accumulator map. The kernel write syscall dominates lock-hold time; per-stream contention savings are theoretical. Holding a single mutex across the line-splitting and sink write is what enforces EXEC-016’s atomic-per-line-writes contract in live mode.

Both observers implement the cache-hit contract of EXEC-017 implicitly: crate::run_task::restore_from_hit feeds cached stdout / stderr bytes through the same RunObserver::on_stdout and RunObserver::on_stderr calls a fresh run uses. The observer cannot distinguish a hit from a fresh run; both surface byte-identically.

Cache contents themselves are untouched by either observer. EXEC-016’s “the captured bytes that enter the cache per CACHE-012 are the bytes the process wrote, EXACTLY, with no prefix and no transformation” is satisfied structurally: crate::run_task::run_fresh hashes and stores the raw byte buffers; the observer reads its own copy of the same bytes for presentation.

Structs§

BufferedOutputObserver
Buffered-mode RunObserver per EXEC-016.
LiveOutputObserver
Live-mode RunObserver per EXEC-016.