Skip to content

voice: a provider that stops answering is only noticed at the session deadline #150

Description

@L4XB

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

  1. 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.
  2. 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.
  3. 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

  • A provider that stops answering is detected in a bounded time that does
    not depend on the participant falling silent
  • The bound is justified against a legitimate long answer, not chosen to
    make a test pass
  • Audio handed to an unresponsive transport is bounded, or it is stated why
    the transport's own limits are sufficient
  • The participant is given the same defined end they get today

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:interviewsInterview, transcript, voice, and consent boundariesprivacyPrivacy boundaries and data minimizationtype:hardeningCorrectness, resilience, and adversarial-input hardening

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions