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).
3.5 KiB
OQ-45: Flow Control for High-Throughput stdout
-
Origin:
docs/research/alknet-tty/phase-0-findings.mdOQ-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:
- Client reads slowly → QUIC flow control. The server's
write_chunkwrites into a quinnSendStream, 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.) - Send awaits → drainer channel backpressures. The drainer task
writes chunks to the client one at a time. When
write_chunkawaits, the drainer stops pulling from the boundedwriter_rxchannel (depth 64 in the POC'ssession.rs). - 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. - 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). - Stdout channel fills → reader thread backpressures. The
backend's reader thread (
portable_ptymaster reader, or a pipedChild::stdout) doesblocking_send; when the channel is full, the std thread blocks and stops reading from the kernel. - 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'swrite()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 buildoutput) 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 newControlMessagevariant), 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. - Client reads slowly → QUIC flow control. The server's
-
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).