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

65 lines
3.5 KiB
Markdown

# 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](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](crates/tty/tty-wire.md),
[tty-adapter.md](crates/tty/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).