Split from #96 (its open item 2), which #97 resolves on the receive
side: a silent wire is now detected by the liveness probe. The send
side remains unmeasured.
The question
The pump awaits relay datagram sends inline (endpoint_impl.rs, the
relay_outbound drain loop) and answers server pings inline inside
recv_datagram (core/src/relay.rs). If a host's websocket send
can pend indefinitely against a backpressured wire — stalled TCP peer,
send buffer at its bound — the pump pins there, and no probe can run
(the liveness sweep lives in the same task).
Per host:
- Browser-profile hosts (
@polymorph/websocket): send resolves
after a bounded buffered-amount drain poll against an 8 MiB bound —
it pends while the buffer stays full. A wire stalled with a full
buffer therefore CAN pin a send await. In practice the liveness
probe bounds the damage window (the wire is retired ≤ 20s after it
goes quiet — but a pinned pump cannot run the sweep, so the two
interact badly exactly when both trigger).
- wasmtime host (
wasmtime-websocket): unmeasured. Whether its
send applies backpressure indefinitely against a stalled peer is
the original open item.
Note the liveness probe's own sends are exempt by construction: #97
detaches them (spawn_local) precisely so a backpressured wire cannot
pin the pump through the probe. The exposure is the datagram drain and
the inline pong reply.
What resolution looks like
- Measure both hosts against a peer that accepts, completes the
handshake, and stops reading (grow the send buffer to its bound).
- If a send can pend unboundedly: decide the shape — upstream drops
datagrams when its send path backs up (relay datagrams are
expendable; QUIC loss recovery owns retransmits), so a bounded send
(select against a short deadline, drop the loser — safe since
polyengine 0.5.1 / A23) that counts as datagram loss is the
parity-shaped fix. The inline pong reply needs the same treatment
or detachment.
- Gate: an exam stub that reads nothing after the handshake while the
endpoint transmits, with the pump required to stay live (exports
keep answering) and the wire eventually retired by the probe.
Split from #96 (its open item 2), which #97 resolves on the receive
side: a silent wire is now detected by the liveness probe. The send
side remains unmeasured.
The question
The pump awaits relay datagram sends inline (
endpoint_impl.rs, therelay_outbounddrain loop) and answers server pings inline insiderecv_datagram(core/src/relay.rs). If a host's websocketsendcan pend indefinitely against a backpressured wire — stalled TCP peer,
send buffer at its bound — the pump pins there, and no probe can run
(the liveness sweep lives in the same task).
Per host:
@polymorph/websocket):sendresolvesafter a bounded buffered-amount drain poll against an 8 MiB bound —
it pends while the buffer stays full. A wire stalled with a full
buffer therefore CAN pin a send await. In practice the liveness
probe bounds the damage window (the wire is retired ≤ 20s after it
goes quiet — but a pinned pump cannot run the sweep, so the two
interact badly exactly when both trigger).
wasmtime-websocket): unmeasured. Whether itssendapplies backpressure indefinitely against a stalled peer isthe original open item.
Note the liveness probe's own sends are exempt by construction: #97
detaches them (
spawn_local) precisely so a backpressured wire cannotpin the pump through the probe. The exposure is the datagram drain and
the inline pong reply.
What resolution looks like
handshake, and stops reading (grow the send buffer to its bound).
datagrams when its send path backs up (relay datagrams are
expendable; QUIC loss recovery owns retransmits), so a bounded send
(select against a short deadline, drop the loser — safe since
polyengine 0.5.1 / A23) that counts as datagram loss is the
parity-shaped fix. The inline pong reply needs the same treatment
or detachment.
endpoint transmits, with the pump required to stay live (exports
keep answering) and the wire eventually retired by the probe.