Files
alknet/docs/architecture/questions/045-flow-control-for-high-throughput-stdout.md
T
glm-5.2 a3cb44968e docs(adr): 093 — channels pure channel multiplexing (8-byte header, no stream_type)
Prune the channels spec to reflect the stream-unification resolution
(docs/research/stream-unification/findings.md): the channels wire format
goes from 9 bytes to 8 bytes, the channels layer no longer carries a
stream_type concept, into_sub_streams() is removed, and TTY always uses
its 5-byte format (carried transparently in the channels payload).

ADR-093 is the umbrella decision (the channels-layer consequence of
ADR-092's BiStream handler leaf): every channel is a BiStream, the
handler owns its sub-stream multiplexing, the channels layer routes by
channel_id only. Amends ADR-071 (8-byte header, no stream_type),
ADR-074 (into_sub_streams removed, accept_bi yields BiStream), reverses
ADR-077 (TTY always 5-byte), and the channels-facing clauses of
ADR-072/073/075/076/080/081. Adds ADR-092 forward-reference note
(into_sub_streams preservation subsequently reversed by ADR-093) and
the missing ADR-092 cross-reference on ADR-070.

Adds OQ-68 (add/strip API shape — built-in vs utility; the contract is
decided in ADR-093, the function surface is open; two-way door, low
priority, decision-ready when the channels crate's implementation
begins).

Rewrites the 7 channels spec docs (README, overview, channels-wire,
channels-connection, channels-adapter, channel-operations, channel-client)
to describe the post-amendment shape as current, with the 8-byte header,
the add/strip composition, single accept_bi accessor, BiStream per
channel, and TTY-always-5-byte.

Touch-up cross-references in hub README, client README, ADR-085, and
the OQ-45/47/65 question files (TTY-internal stream_type 3 →
STREAM_CTRL_IN; channels 9-byte → 8-byte).
2026-07-18 18:10:17 +00:00

3.5 KiB

OQ-45: Flow Control for High-Throughput stdout

  • Origin: docs/research/alknet-tty/phase-0-findings.md OQ-TTY-03; crates/tty/tty-wire.md (no windowing in the chunk format).

  • Status: resolved

  • Door type: Two-way

  • Priority: low

  • Resolution: No application-level windowing. QUIC's per-stream flow control is the backpressure mechanism — the chunk format carries no window. This is decided, not a default assumption.

    The backpressure chain is complete by construction — every link awaits its producer, so slowness at the client's read end propagates all the way back to the child process's stdout write without any unbounded buffering link:

    1. Client reads slowly → QUIC flow control. The server's write_chunk writes into a quinn SendStream, which respects per-stream flow control. When the client's receive window fills, the send awaits. (Standard, mature quinn behavior — not something alknet-tty inventes.)
    2. Send awaits → drainer channel backpressures. The drainer task writes chunks to the client one at a time. When write_chunk awaits, the drainer stops pulling from the bounded writer_rx channel (depth 64 in the POC's session.rs).
    3. Drainer channel fills → stdout pump backpressures. The stdout→client pump does writer_tx.send(chunk).await; when the drainer isn't pulling, the send awaits.
    4. Stdout pump awaits → backend stdout channel backpressures. The pump stops receiving from the backend's stdout channel (the local backend's mpsc::Receiver<Bytes>, also bounded at 64).
    5. Stdout channel fills → reader thread backpressures. The backend's reader thread (portable_pty master reader, or a piped Child::stdout) does blocking_send; when the channel is full, the std thread blocks and stops reading from the kernel.
    6. Reader thread stops → OS pipe/PTY buffer fills → process blocks. The kernel PTY buffer (or Stdio::piped() buffer, typically 64 KiB) fills, and the child process's write() to stdout blocks. The process is throttled.

    Every link in the chain awaits its producer. The chain is the standard composition of QUIC flow control + bounded tokio channels + OS pipe buffers — the same pattern the docker POC's logs subscription and the tty POC's PTY pump already use. A high-throughput stdout workload (e.g., cargo build output) throttles at the process when the client can't keep up; no unbounded buffer breaks the chain.

    The two-way-door reversal — a per-stream window-update control message on TTY's STREAM_CTRL_IN (stream_type 3) — is an additive extension to the control channel (a new ControlMessage variant), not a wire-format header change. It is not the expected path; it is noted in ADR-052's consequences as the cheap reversal if a flow-control problem ever surfaces that QUIC's defaults cannot handle (e.g., a pathological stream that needs sub-QUIC-window backpressure signaling). Tuning concerns (read buffer size, channel depth) are implementation-level, not architectural, and don't warrant an ADR.

  • Cross-references: ADR-052, tty-wire.md, tty-adapter.md, /workspace/alknet-tty-poc/src/session.rs (the three-pump driver — the bounded channels at every link), /workspace/alknet-tty-poc/src/local_pty.rs (the blocking→async bridge — the reader thread that backpressures into the kernel).