Repository navigation
fix(net): stop idle viewer sessions from waking every 100ms - #5382
Conversation
The Session handle held its own send-bandwidth consumer, so the sampler never idled, and each open PROBE stream ticked every 100ms reading the transport stats. Together an idle viewer cost its server ~40 driver polls and ~20 path-stats reads per second. The handle now keeps a weak reference, so only held consumers are demand. PROBE serving follows the supervisor's estimate instead of a timer: the supervisor samples on driver polls that already happen (at most once per 100ms) while a PROBE stream is open, and ProbeServe wakes on a change or its age deadline. Reporting rules (thresholds, decay, max age) are unchanged. An idle viewer now costs one max-age refresh per 10s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughRust sessions now carry optional estimate producers, and Lite PROBE reporting uses transport estimates to send reports when values change or reach an age deadline. Tests and benchmarks cover idle sampling, updated estimates, and session polling. The quest notes also describe planned demand-driven PROBE behavior for Rust and js/net. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review (Written by Claude Opus 5.5) |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed b363605 against base c5b1150. No actionable bugs found. Rechecked the base-aware delta against the earlier full inspection: the Rust implementation, tests and benchmark are unchanged; the new PR-specific edits clarify the Rust PROBE quest's branch and test path.
Direction looks sound: weak consumer demand and passive PROBE sampling remove both recurring idle timers without adding a backoff timer. The documented trade-off remains: ACK-only estimate changes can wait for subsequent driver work or the next max-age refresh.
Static review only; I did not execute tests or benchmarks.
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Merge summary: stops idle viewer sessions from waking every 100ms. The Changes while merging:
Verification: (Written by Claude Opus 5.5) |
fix(net): backport stop idle viewer sessions from waking every 100ms (#5382)
A `Tasks` wake from another thread-local's destructor at thread exit can run after the batch scope's own thread-local is destroyed; `with` then panicked inside a destructor and aborted. `try_with` delivers the wake unheld. Tighten `idle_session` to one driver poll per PROBE refresh now that #5382 is in, and bench the batch's own cost next to the plain sparse-wake pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
An idle viewer session still costs its server a fixed amount of work every 100ms. A server holding thousands of mostly idle viewer sessions spent more than a core this way. Each session woke its driver ~35 times per second and read the QUIC path stats ~18 times per second, while moving almost no bytes. Two timers caused it, and each wake re-polls the whole session driver:
Session::newstoredproducer.consume()in theSessionhandle, sobandwidth::Producer::is_used()was always true.Supervisor::poll_samplertherefore sampled every 100ms for the life of the session, even when nothing read the estimate.ProbeServeread the transport stats on a fixed 100ms tick for every open PROBE stream. Browser viewers keep one open for the whole connection. Most ticks then sent nothing because of the threshold and max-age logic.Fix
bandwidth::Weakinstead of a consumer, so only consumers a caller actually holds count as demand. A realsend_bandwidth()consumer still gets 100ms-fresh samples.(send rate, rtt)only when it changes.ProbeServewakes on that change, or when its own age deadline comes due. If nothing wakes the driver, the session is sending nothing, so the congestion controller has nothing new to report. The supervisor runs before the protocol in every driver poll, so the estimate PROBE reads is never more than 100ms old.Behavior change: a change in the congestion controller's estimate is reported when the session next does work, or at the 10s max age. This mostly matters for ACKs that land after writes stop. Before, a change was seen within 100ms.
Public API and wire impact
Session::send_bandwidth()mints a fresh consumer per call, as the clone did before.Session::stats()is unchanged.Reproduction (
rs/moq-net/tests/idle_session.rs)One idle viewer holds a PROBE stream for 60s of simulated time; the test counts what the server does.
mainEach driver poll is one wake, so the server-side wake rate drops from ~40/s to ~0.2/s. The only work left is one PROBE_MAX_AGE refresh every 10s.
busy_viewer_sees_the_estimate_movecovers the other side: a session the server is working for reports a moved estimate along with that work. It fails if passive sampling is removed.Benchmark (
cargo bench -p moq-net --bench session -- session_idle)New
session_idlegroup: N idle viewer sessions, each holding a PROBE stream, over the mock transport, on tokio's paused clock. The time is the CPU it takes to advance one second of protocol time, with both peers in one process.mainFollow-ups
Clock::advancewakes a due deadline throughkio::Tasks, which re-wakes the driver while it is already being polled. That doubles the driver work for every timer in every session. It is a separate fix; this PR leaves only one timer per 10s on an idle session.runProbe()unconditionally, and the RustSessionholds its ownrecv_bandwidthconsumer. Planned in this PR as two m1 quests,quest/m1/probe-on-demand-rs.mdandquest/m1/probe-on-demand-js.md.Decision paper trail
PROBE wake design:
PROBE on demand (quest-plan):
Session::recv_bandwidth()already isStats::estimated_recv_rate: ✅ remove it (breaking,dev) / keep it, documented(Written by Claude Opus 5.5)
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.