Merge branch 'wt/review-002-ws13-idle-progress'

This commit is contained in:
2026-08-30 19:57:04 +00:00
4 changed files with 516 additions and 44 deletions
+44 -1
View File
@@ -1,6 +1,6 @@
---
status: draft
last_updated: 2026-08-27
last_updated: 2026-08-30
---
# WebSocket — the Browser Bidirectional Path (Channels over WS)
@@ -176,6 +176,49 @@ The adapter is shared with the `from_wss` consumer path
([ADR-070](decisions/070-from-wss-consumer-adapter.md)) — one
implementation, both directions.
#### Idle-read timeout: progress semantics + the no-keepalive decision (WS-01, WS-13)
The WS pumps carry an **idle-read eviction knob**
(`HttpAdapter::with_ws_idle_timeout`, default
`DEFAULT_WS_IDLE_TIMEOUT` = 60 s, disable with `None`). Its semantics,
decided in review-002 (WS-13), are **progress-based, deliberately
strict**:
- The deadline resets on **demux progress** — bytes actually forwarded
into the byte stream that *complete* an inbound chunk (a full
8-byte header + its declared payload). WS message arrival resets
nothing.
- Therefore the dribble stall (declare a chunk, deliver its payload a
byte per message) hits the deadline and is evicted with a `1001`
(GoingAway) close — even though messages keep arriving — while a
peer delivering complete chunks, however slowly per message, re-arms
the window with each chunk and survives.
- **There is no WS ping/pong keepalive, on purpose.** A keepalive
rescues app-silence only by re-arming the deadline — and a pong is
indistinguishable from the dribble's almost-invisible arrivals, so
adding one would reopen the stall it exists to seal. The recorded
decision (module doc of `src/websocket/byte_adapter.rs`, option (b)
of the two WS-13 alternatives): **60 s of no chunk progress is an
intentional eviction line, even for a silent subscription.**
- A deployment running long-lived silent-but-alive sessions (quiet
subscriptions that outlast the window) disables the knob with
`with_ws_idle_timeout(None)` and leans on the remaining levers:
the session registry's forced-eviction
([`WsSessions::abort`](#connection-local-overlay)), the
inbound caps (WS-06), and the write-side caps (WS-04/05/06). This is
the same deployment posture as `from_wss`'s drop monitor
([ADR-070](decisions/070-from-wss-consumer-adapter.md)): the idle
knob bounds *demux parking*, not app liveness.
- Layered note (the WS-13/FWD-15 interaction recorded here): the
*HTTP* SSE path (`/subscribe`) sends server-side keep-alive comment
frames every 15 s — see
[http-server.md](http-server.md)) — because its idle enemy is
LB/proxy timeouts, and its keep-alive does not reset any
progress deadline, it cannot reopen the WS-13 hole. The two live at
different layers: SSE keep-alive fights transport fires; the WS idle
knob bounds demux parking. Both documented in the same pass per
review-002 Unit-2 sequencing.
### Dispatch: channel 0 = the shared `Dispatcher`, unchanged
Channel 0's session is the alknet design's native session, verbatim: