Problem
A provider that accepts the connection, answers setupComplete, and then stops
reading and stops answering is not detected while the participant is speaking.
The relay keeps forwarding audio to it until the session deadline.
Measured while building the transport tests for #139. Run at ten times real
time, with a local server that answered setup and then never called recv
again, the relay handed 8.5 MB across twenty-two seconds into a socket nobody
was draining, and send never blocked once. Loopback buffers are generous and
a real network would push back sooner — but the shape is the same, and so is the
participant's experience: an interviewer that stopped answering, for as long as
the session is allowed to last.
Why nothing catches it
watchdog has four timeouts and none of them applies to a speaking participant:
patience_ms ends an activity only after silence. process_audio refreshes
last_voice on every voiced frame, so someone who keeps talking keeps
resetting it.
generation_timeout_seconds applies only once phase == "generating", which
requires an activity to have ended first.
meter_timeout_seconds applies only in settling, which requires a
generation to have completed.
silence_timeout_seconds applies only while idle.
So the relay stays in input indefinitely. The only limit left is
min(LIVE_SESSION_MAX_MINUTES * 60, deadline).
Keepalive does not help either. _connect_provider sets
ping_interval=20, ping_timeout=20, but pongs are generated by the peer's
library below the application, so a provider whose application layer has stopped
still answers pings. The check proves the process is reachable, not that anyone
is listening.
What to consider
- A liveness expectation with a bound. The provider acknowledges activity
within some window — setupComplete already exists as the shape; something
equivalent during a long turn would give the watchdog something to time out
on.
- A cap on unacknowledged forwarded audio. If N seconds of a turn have been
sent with nothing at all received, end the session rather than continue. This
needs care: a long uninterrupted answer with no interim transcription is
legitimate, so the threshold has to be well above normal silence from the
provider.
- A write-side bound. Even before anything decides to end the session, the
relay could refuse to hand the transport more than a bounded amount of
unflushed audio, so the memory is capped regardless of what the watchdog
concludes.
Option 3 alone would bound the resource; only 1 or 2 shorten the participant's
wait.
What is already true
The session does end at the deadline, the participant is sent relayEnd, and
their socket is closed —
test_a_provider_that_stops_reading_is_only_noticed_at_the_session_deadline in
services/api/tests/test_voice_relay_transport.py asserts exactly that, with
the deadline shortened so it can. Ingress from the browser is separately bounded
by the token bucket and the queue, and that is asserted too. This issue is about
the gap between "eventually ends" and "notices".
Acceptance criteria
Problem
A provider that accepts the connection, answers
setupComplete, and then stopsreading and stops answering is not detected while the participant is speaking.
The relay keeps forwarding audio to it until the session deadline.
Measured while building the transport tests for #139. Run at ten times real
time, with a local server that answered setup and then never called
recvagain, the relay handed 8.5 MB across twenty-two seconds into a socket nobody
was draining, and
sendnever blocked once. Loopback buffers are generous anda real network would push back sooner — but the shape is the same, and so is the
participant's experience: an interviewer that stopped answering, for as long as
the session is allowed to last.
Why nothing catches it
watchdoghas four timeouts and none of them applies to a speaking participant:patience_msends an activity only after silence.process_audiorefresheslast_voiceon every voiced frame, so someone who keeps talking keepsresetting it.
generation_timeout_secondsapplies only oncephase == "generating", whichrequires an activity to have ended first.
meter_timeout_secondsapplies only insettling, which requires ageneration to have completed.
silence_timeout_secondsapplies only whileidle.So the relay stays in
inputindefinitely. The only limit left ismin(LIVE_SESSION_MAX_MINUTES * 60, deadline).Keepalive does not help either.
_connect_providersetsping_interval=20, ping_timeout=20, but pongs are generated by the peer'slibrary below the application, so a provider whose application layer has stopped
still answers pings. The check proves the process is reachable, not that anyone
is listening.
What to consider
within some window —
setupCompletealready exists as the shape; somethingequivalent during a long turn would give the watchdog something to time out
on.
sent with nothing at all received, end the session rather than continue. This
needs care: a long uninterrupted answer with no interim transcription is
legitimate, so the threshold has to be well above normal silence from the
provider.
relay could refuse to hand the transport more than a bounded amount of
unflushed audio, so the memory is capped regardless of what the watchdog
concludes.
Option 3 alone would bound the resource; only 1 or 2 shorten the participant's
wait.
What is already true
The session does end at the deadline, the participant is sent
relayEnd, andtheir socket is closed —
test_a_provider_that_stops_reading_is_only_noticed_at_the_session_deadlineinservices/api/tests/test_voice_relay_transport.pyasserts exactly that, withthe deadline shortened so it can. Ingress from the browser is separately bounded
by the token bucket and the queue, and that is asserted too. This issue is about
the gap between "eventually ends" and "notices".
Acceptance criteria
not depend on the participant falling silent
make a test pass
the transport's own limits are sufficient