Repository navigation
Conversation
`WebSocketMonitor.receive()` sends a PING whenever `asyncio.wait()` returns without a completed task, even when the liveness deadline has not passed. On Windows the event loop's timer granularity makes a wait whose remaining budget is smaller than one tick return immediately; the loop then re-sends a probe and continues, so a peer that never answers sees dozens of PINGs at the same instant instead of one. The upstream test `tests/test_websocket_monitor.py::test_outbound_audio_does_not_hide_unanswered_ping` observes 67-113 PINGs there (it asserts 1) while it passes on Linux. Once the first probe is out, an empty wake can only be an early return. Keep waiting for the liveness deadline instead of probing again; an answered probe still resets the window through the receiver path. Add a deterministic regression test that makes one wait wake early. It fails on main (3 pings) and passes with the change, so the behavior is covered on every platform rather than only where the timer rounds. Co-Authored-By: Claude Code <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
WebSocketMonitor.receive()sends a PING wheneverasyncio.wait()returns without a completed task, even when the liveness deadline has not been reached:At that point the wait's remaining budget is
deadline - now. When that budget is smaller than the loop's timer resolution (observed on Windows, ~15.6 ms),asyncio.wait()can return immediately, so the loop probes again, wakes again, and repeats until the clock finally crosses the deadline. One unanswered probe produced 113 PINGs at the same timestamp before the liveness error:The existing test
tests/test_websocket_monitor.py::test_outbound_audio_does_not_hide_unanswered_pingcatches this on Windows (assert 113 == 1atassert pings == 1); it passes on Linux, where the timer is precise enough that the burst does not happen.Fix
Once the first probe is out, an empty wake can only be an early return. Keep waiting for the liveness deadline instead of probing again; a PONG (or any incoming frame) still resets the window through the receiver path, and the timeout still fires at
liveness_timeout.Tests
test_early_wake_does_not_resend_the_liveness_probesimulates one early wake deterministically: it fails onmain(three PINGs) and passes with the change, so the behavior is covered on every platform rather than only where the timer rounds.tests/test_websocket_monitor.py: 13 passed.tests/test_inference_stt_connection.py tests/test_inference_stt_fallback.py: 80 passed.ruff checkandruff format --check: clean.Note
Maintainer PR #8147 also edits
websocket.py(close/error handling) but leaves this probe logic untouched, so the two changes are compatible; happy to fold this into that PR instead if you prefer to batch.🤖 Generated with Claude Code