Repository navigation
feat(congestion): SCReAMv2 delay-based controller candidate - #7547
Conversation
Quest-Claim: a1ccdb52d74317cf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inish its quest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e trace Takes #7544's Bottleneck::timed, Link::grant and Bottleneck::trace verbatim in place of this branch's own grant and queue sampling, so the two candidates' harness changes merge cleanly in either order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. WalkthroughThe change adds SCReAMv2 as a public congestion controller with configuration options, controller callbacks, and unit tests. It adds simulation utilities and tests for cellular uplink conditions, plus ignored real-socket QUIC measurements. Documentation and test commands now include SCReAMv2. Merge Risk:
|
There was a problem hiding this comment.
ℹ️ No critical issues — two port-fidelity notes inline.
Reviewed changes
First pullfrog pass on the initial revision: a new SCReAMv2 delay-based congestion controller and the two harnesses that measure it.
rs/moq-congestion/src/scream.rs(new) — ports the queue-delay target path of draft-ietf-ccwg-rfc8298bis-screamv2-01:Scream/ScreamConfig, a reference window with per-acked-byte growth and loss/ECN/"virtual L4S" reduction, receive-timestamp queue delay over a 100s base, and probing.rs/moq-congestion/src/lib.rs— exportsScream,ScreamConfig(additive; nothing selects it yet).rs/moq-quic/src/tests/{mod,scream,util}.rs— sim harness pieces (Bottleneck::timed/trace,Link::grant) andtests::scream: the BBR3 comparison and probe-recovery merge-gate tests plus an#[ignore]d comparison table.rs/moq-tokio/tests/integration/{main,scream}.rs— nightly real-socket measurement through the moq-shaper presets.rs/justfile, the two READMEs, andquest/m2/quic-realtime/*— wire the nightly runs and mark the quest complete.
I verified the controller's constants and formulas against the draft (§4.2.1.4, §4.2.2) and Ericsson's ScreamV2Tx.cpp updateCwnd; the values all match the draft except the two notes below. A peer-input pass found no panic, overflow, out-of-bounds, or NaN path from untrusted receive timestamps or ACK/loss patterns.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: b724eb2
No actionable bugs found in this static review. Checked the delay baseline/quiet-spell aging, window growth and congestion reactions, pacing/probe integration, and the sim and socket measurement paths. The earlier quiet-spell finding recorded in the PR body is addressed by aging all elapsed windows and its focused test.
Direction: a separate, opt-in controller is a reasonable boundary for this bake-off, with no wire change or change to existing defaults. Reusing the pacer and controller interface keeps the production addition focused. Consolidating the duplicated measurement harness during the planned comparison remains preferable to maintaining divergent workloads. The documented bursty, application-limited estimate overshoot should remain part of that comparison rather than treating this estimate as measured delivery capacity.
Verification limits: source inspection only. I did not run tests, simulations, socket experiments, or reproduce the reported performance measurements.
(Written by OpenAI)
The increase's inflection scale is taken after the update's reduction, as the draft's comment says, rather than before as its pseudocode and the reference do: the reaction makes the window it began at the inflection, so growth right after a small backoff stays held. Taken before, the queue's p95 after follows_a_capacity_drop's drop rose from 58ms to 62ms, past the 60ms target. A unit test pins it. on_ce notes why CE-marked bytes still count toward the increase: Ce carries no count, and classic ECN's additive growth makes it a fraction of an MSS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 6df8231, compared with b724eb2, accounting for the base merge.
No new actionable bugs found. The added backoff_holds_growth_at_the_new_inflection test meaningfully distinguishes post-reduction scaling from scaling against the older inflection: the asserted 0.25 additive factor follows from the small backoff and updated ref_wnd_i. Production behavior is unchanged; the new comments record the intentional scaling order and classic-ECN approximation discussed in the existing threads (scaling, CE accounting). The earlier quiet-spell fix remains intact.
Direction remains reasonable for the scoped delay-controller candidate. Keeping these choices explicit and pinning the behavior is preferable to expanding the public congestion interface just for the deferred L4S work.
Static inspection only; I did not run the new test or reproduce the reported queue-delay comparison.
(Written by OpenAI)
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Incremental pass on the commits since the prior pullfrog review (b724eb2): the author's documentation of, and regression test for, the two port-fidelity notes pullfrog raised. No production behavior change.
rs/moq-congestion/src/scream.rs—scl()'s doc now states the post-reduction evaluation order (with the draft-comment citation and the measured queue-p95 impact), andon_cedocuments why CE-marked bytes still count toward the increase until the L4S path adds a count toCe.rs/moq-congestion/src/scream.rs— addedbackoff_holds_growth_at_the_new_inflection, which pins the 0.25 additive scale after a small delay backoff and fails under the pre-reduction order.
Both prior pullfrog threads were addressed by this delta with evidence and are resolved; the new test confirms the pinned behavior.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Main (#7377) moved the sim's Bottleneck and Link into moq_quic::sim. Port this branch's Bottleneck::timed, Bottleneck::trace, and Link::grant there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port SCReAMv2 to main's namespaced congestion API: `scream::Config` replaces `ScreamConfig`, `Factory` and `Metrics` replace `ControllerFactory` and `ControllerMetrics`, and `into_any` is gone. Floor its window at two datagrams of the path MTU, as #7650 does for every controller, and add it to that test. The tests follow main's sealed rustls client config and default-INFO `subscribe`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d pacer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: b75b60e, compared with 6df8231 and their respective bases.
No new actionable bugs found in the two-MTU floor, probe-hook and API adaptations. The prior quiet-spell fix remains present; the earlier CE accounting and scaling-order discussions are documented design deviations. Reusing the shared simulation helpers is sensible. Static review only; I did not run tests, simulations or benchmarks.
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 34f40a8, compared with b75b60e.
The corrected-pacer results and omitted media-queue feedback are useful context for the controller decision. No new actionable bugs found in this documentation update. I did not reproduce the measurements or run tests.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Incremental pass on the commits since the prior pullfrog review (837643a): two origin/main merges and the branch's adaptation to them.
rs/moq-congestion/src/{lib,scream}.rs— adapted to main's congestion-config namespacing (ScreamConfig→scream::Config,ControllerFactory→Factory,ControllerMetrics→Metrics, droppedinto_any) and added Scream to thewindow_holds_two_datagrams_at_large_mtufactory sweep.rs/moq-congestion/src/scream.rs—window()and the pacer's initial window now floor at two datagrams of the path MTU, matching main's#7650floor for the other controllers.rs/moq-quic/src/tests/scream.rs,rs/moq-tokio/tests/integration/scream.rs— renamed to the namespaced API; the recorder forwards the newpaces_probe/on_probe_targettrait methods.- READMEs,
rs/justfile,quest/m2/quic-realtime/— SCReAMv2 added to the controller lists and marked landed; the nightly commands are unchanged.
I confirmed against the merged main that the rename is complete (no stale ScreamConfig / ControllerFactory / ControllerMetrics left in code) and that cargo test -p moq-congestion --lib passes (365 tests, including window_holds_two_datagrams_at_large_mtu). The new floor only binds above ~2250-byte MTU, so it does not move the 1200-byte-MTU merge-gate measurements. Both prior pullfrog threads are resolved.
ℹ️ The PR description's Impact line still names the pre-merge API
After the #7258 merge the added API is moq_congestion::Scream and moq_congestion::scream::Config behind a Factory, not moq_congestion::ScreamConfig / ControllerFactory. The Impact section is what the release notes and migration guidance are drawn from, so it is worth restating with the namespaced names.
Technical details
# Stale public API names in the PR description
## Affected sites
- PR #7547 body, "Impact" section — names `moq_congestion::ScreamConfig` and `ControllerFactory`
## Required outcome
- The Impact bullet names `moq_congestion::Scream` and `moq_congestion::scream::Config`, with the `Factory` trait, matching the code after the `#7258` merge.deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Problem
The realtime uplink epic runs a bake-off between delay-based controllers fed by receive timestamps. This is the SCReAMv2 candidate (
quest/m2/quic-realtime/scream.md): no Rust implementation existed.Approach
moq_congestion::Scream/scream::Configport the queue-delay target path of draft-ietf-ccwg-rfc8298bis-screamv2-01: a reference window that grows per acked byte (multiplicative after a quiet spell) and shrinks on loss, classic ECN-CE, and "virtual L4S" as the averaged queue delay passes half the target. The window isref_wndtimes the 1.5 to 3x overhead; pacing is 1.5x the target rate;bandwidth_estimateis the target rate. The L4S path and the competing-flow target raise are left out, per the quest.probes()is true andprobe_inflightis the window while the averaged queue delay is under a quarter of the target and no reaction happened in the last RTT.tests::scream(sim) runs an encoder that follows the estimate (30 fps, 2s GOP, 5x keyframes) through sim replicas of the three cellular presets. The merge gate checks two things on one seed: SCReAMv2's queue p50/p95 beat BBR3's on cellular-step, and a stopped constant-rate rung recovers only with a probe target. The nightlycomparetable is injust rs quic-sim.screamintegration tests run the real moq-shaper presets over sockets, includingnear-zeroas the noise floor. They are nightly, wired intojust rs quic-sim.BottleneckandLinkintomoq_quic::sim(hidden, behindcfg(test)or thesimfeature). This PR addsBottleneck::timed,Bottleneck::trace, andLink::grantthere, the same pieces moq-dev/moq#7544 adds to the same file, line for line, so whichever lands second merges them cleanly.pub mod screamwithscream::Config, built throughFactory, reportingMetrics, nointo_any), and moq-dev/moq#7650's floor: its window is at least two datagrams of the path MTU, and it joins that floor's test inmoq-congestion.Measurements
Sim, mean of 4 seeds, adaptive media, rerun on
34f40a8300with main's corrected pacer (moq-dev/moq#7606). Queue is per packet at the bottleneck past 3s. Utilization is goodput over capacity per phase (before, during, after the drop). Fall is when the estimate first sits at or under the dropped rate. Drain is when the queue first returns under 30ms. Rise is when the estimate reaches 80% of the restored rate.Real sockets through moq-shaper, default config, one run each on
34f40a8300. The host was busy (loadavg 17 to 20, against 1.5 to 5 for the earlier runs), which widens every number here. Queue is the forward delay above the run's minimum, so it includes the preset's jitter.Stopped rung (sim, 4 seeds): a 500 kbit/s rung on a 1 Mbit/s path that opens to 4 Mbit/s at 15s, probe target 2.5 Mbit/s.
Findings:
pacer.rs, every SCReAMv2 row reproduced the earlier table exactly (step 853ms, deep 300ms frame p95). The old pacer let keyframes (5x a delta frame) overshoot SCReAMv2's pacing of 1.5x the target rate; now they wait in the sender. The draft's media-queue terms (4.4), left out here, are what would answer that backlog. Recorded in the epic for the bake-off.reduce_jittershrinks the window overhead under keyframe jitter and cuts the queue p95 by 2 to 4x, at another 1 to 15% of throughput, though its frame p95 is worse on deep (2244 vs 1361ms).Choices made without asking
Listed for review. The Plan did not settle these.
qdelay_target60ms,REDUCE_JITTERoff) and measured the variants instead. The bake-off can choose.scl_tis taken after the update's reduction, which is what the draft's own comment says ("ref_wnd_i is updated before this code"). A reaction makes the window it began at the new inflection, so growth right after a small backoff stays at 0.25. Taking it before, against an older inflection the window had grown past, raisedfollows_a_capacity_drop's post-drop queue p95 from 57.8 to 61.8ms, past the 60ms target.backoff_holds_growth_at_the_new_inflectionpins this.Cecarries no marked count, and adding one would be a semver-breaking field. On the classic ECN path, growth after a reaction is additive, at most one MSS per round trip, so the marked bytes are worth a fraction of an MSS against the 20% cut. The L4S path would need the count.Impact
moq_congestion::Screamandmoq_congestion::scream::Config(qdelay_target,reduce_jitter), aFactory. No wire change; nothing selects it yet.Realtimewiring is the epic's job.Alternatives
tests::realtimeharness now. That file is not onmainyet (feat(congestion): BBR3 delay bound from the forward delay's trend #7544 is queued), and its bulk upload is not the workload SCReAMv2 is built for (see the comment on that PR).tests::screamdrives an adaptive encoder instead. The epic's comparison step can fold both into one harness.Follow-ups
Review
34f40a8300: Codex, gpt-6.1-sol, medium effort (quest review --base origin/main, new session), round 2: no findings. Round 1 (headb75b60eb46, the merge of origin/main at8121cf68dfplus the API port): no findings. The only change between the rounds is the epic README note on the frame tail.b724eb2were both declined with evidence, documented in the code, and resolved: CE bytes in the increase andsclorder. Both are listed under the choices above. CodeRabbit had no actionable comments; its docstring-coverage pre-merge warning is a generic threshold, not a finding.6df823161d, no findings; round 2 onb724eb2, no findings; round 1 on9640ad3, one finding (the base delay's history aging across a quiet spell), fixed and covered bybase_delay_ages_across_a_quiet_spell.34f40a8300innix develop:just check origin/mainpassed (11452 tests),quest check --base origin/mainok, the probe acceptance suite (tests::probe, ignored included, 14 passed),tests::scream(gated andcompare), and moq-tokio's real-socketscreamruns (4 passed). The tables above are from these runs.sim.rspieces line for line, so the merge after it lands should touch onlytests/mod.rs's module list and the epic README.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code