- AxumFraming arms were exercised only indirectly via tungstenite
(shared generic pumps); drive the axum message types directly with
an in-process fake WebSocket (futures mpsc-backed Sink+Stream
stand-in for the split halves)
- text test: read pump maps a text message to the WriteMsg close
carrying 1002 + the text reason
- cap-trip test: an above-MAX_CHUNK_LEN header through the write pump
closes with 1011 naming the violation
Verification: scripts/verify.sh OK (345 passed), test-support suite
ok, clippy -D warnings clean, fmt clean
- WsTimeouts { idle, write } request extension mirrors ChannelsPolicy:
a deployment layers it on a WS route (bare-registry routes included)
to set the pump knobs per route
- precedence: extension present replaces the router state entirely;
absent falls back to SessionState (adapter-configured idle) and the
crate default write window — a Default impl never clobbers the
adapter-configured idle knob
- upgrade.rs module + handler docs now state the real defaults for
bare-registry routes (60 s idle + 60 s write, 64-session semaphore,
handler-private WsSessions) and the extension surface
- split_ws_to_bytes_idle_with_write exposes the WS-18 write window to
run_channels_session; acceptance test drives a bare-registry route
with a 150 ms extension idle window (1001 eviction observed)
Verification: scripts/verify.sh OK (343 passed), test-support suite
ok, clippy -D warnings clean, fmt clean
- DEFAULT_WS_WRITE_TIMEOUT (60 s, WS-01 knob family): one outbound WS
send that stays unsent past the window (peer stopped reading)
evicts the connection — the send path was slot-bounded but
time-unbounded
- bound is per send call: a slow-but-draining peer resets it with
every message emitted; only a fully stalled sink trips it
- on timeout the pump signals the stream error (InvalidData naming
the write stall) and ends WITHOUT a close frame — a clogged socket
cannot receive one and the close send would park on it
- test-support split helper with both knobs explicit backs the
scaled eviction test (clogged duplex, eviction well inside the
5 s slack)
Verification: scripts/verify.sh OK (343 passed), clippy -D warnings
clean, fmt clean
- WriteMsg::CloseWith now carries (code, reason); the write pump sent
the hardcoded "text messages not supported" string for the idle
(1001) and inbound-frame (1011) closes as well as the text (1002)
close
- close_reason module: canonical per-cause reason strings, shared by
the close frames and the stream-error diagnostics (they already
matched for idle/oversize; now single-sourced)
- reason asserts added to the idle-stall and forever-dribble 1001
tests (reason names the no-chunk-progress cause) and a new text
frame test asserts 1002 + the distinct text reason
Verification: scripts/verify.sh OK (342 passed), clippy -D warnings
clean, fmt clean
- byte-cap check moved ahead of poll_ready/try_send so the mux sees
the InvalidData stream error at the failing call instead of the
write pump emitting a mid-stream 1011 close (the chunk was already
committed to the wire path)
- pump-side over-cap arm replaced by a debug_assert (defense-in-depth
invariant; the error was unreachable for single-call writes once the
pre-send check exists)
- write_tx_mut renamed write_tx_ref with an updated doc contract
- WS-05's over-cap pump test replaced by a pre-send rejection test
(error surfaces at the first write, names the cap) + a cap-edge
test (exactly PENDING_BUFFER_CAP still flows)
Verification: scripts/verify.sh OK (341 passed), clippy -D warnings
clean, fmt clean
Decides the WS-13 legitimate-silence question as option (b): 60s of no
chunk progress is an intentional eviction line even for silent
subscriptions; no WS ping/pong keepalive is added because a keepalive
can only rescue app-silence by re-arming the deadline, which reopens
the dribble hole the knob exists to seal. Documented in the byte_adapter
module doc, on DEFAULT_WS_IDLE_TIMEOUT, on the unchanged
HttpAdapter::with_ws_idle_timeout knob, and in websocket.md (new
'Idle-read timeout' section, including the FWD-15 SSE-keepalive
layering note). Deployment posture for long-lived silent sessions:
with_ws_idle_timeout(None) + WsSessions abort + write-side caps.
The WS-01 idle timer reset on WS message arrival, so the forever-dribble
stall (declare a chunk, deliver its payload one byte per message) reset
the deadline forever while the demux stayed parked on the partial chunk;
conversely the reset-on-message rule was the only thing slow-but-alive
sessions survived on.
Semantics: the deadline now resets on demux progress — bytes actually
forwarded into read_tx that complete inbound chunks (the frames the
demux routes), tracked byte-for-byte in line with alkcall's parse walk
(8-byte header -> payload skip). Message arrival without a completed
chunk resets nothing, so the dribble hits the deadline; every completed
chunk (even one per message, slowly) re-arms the window.
Tests (tungstenite path): the forever-dribble eviction with 1001 despite
arriving messages; a productive-progress session that survives across
many windows; the knob-disabled (None) arm; the stall and text/cap arms
unchanged.
- Replace the axum/tungstenite pump paths' Notify-based read-EOF signal
with a retained tokio watch channel: a late subscriber (monitor
spawned after session setup, or pump EOF before the receiver is
taken) still observes EOF (WS-02).
- from_wss drop monitor: on EOF (or session close) fail all pendings
retryable, then keep sweeping the pending map every 1 s — calls
registered after the initial fail_all (the forgotten-session import
path) resolve instead of hanging (CON-02).
- Tests: drop-during-registration race variants (forget + held
session) and a post-EOF registration resolved via the sweep; the
existing no-hang test stays green.
cargo test (219), cargo test --features wss (231, 3x for flake check),
cargo clippy --all-targets -- -D warnings, cargo fmt --check
Replace the axum/tungstenite pump paths' Notify-based read-EOF signal
with a retained tokio watch channel so a late subscriber observes EOF
regardless of when it fired. Extend the from_wss drop monitor to sweep
the pending map (1 s interval) once EOF is observed, so calls
registered after the initial fail_all also resolve retryable instead
of hanging.
cargo test; cargo clippy --all-targets -- -D warnings (default +
all-features); cargo fmt --check
Ported the alknet-http overlay verification to the channels-over-WS
session (tests/ws_overlay_ops.rs, test-support feature, 8 tests):
- overlay mechanism: browser-registered ops land in the connection's
Layer 2 overlay (register_imported), exposed via overlay_env() —
no PeerIds (browsers are not peers); PeerRef::Specific to a browser
id routes to nothing (NOT_FOUND)
- hub→browser call through compose_root_env's attached overlay
- AccessControl on browser ops gates hub calls (scope match allows,
missing scope FORBIDDEN)
- overlay dies with the connection; no leak between connections;
in-flight calls to browser ops resolve on close
- wire-level: 10 interleaved concurrent calls across two WS sessions
— no cross-correlation, no deadlock; disconnect mid-call resolves
and a fresh session works (no listener wedge)
byte_adapter: read_eof Notify now gated to the wss feature (its only
consumer is from_wss) so a test-support-only build is warning-free.
Verified: cargo test (182 lib), --all-features (227 lib + 5 MCP + 8
overlay + 10 WS integration), clippy -D warnings (default,
test-support, all-features), fmt.