Repository navigation
feat(congestion): GCC candidate, GoogCC refreshed to WebRTC 556f7eec27 - #7546
Conversation
Quest-Claim: 8b0af89e7be69f35
Copied unchanged from kixelated/goog_cc at 3599b2a (WebRTC da2a3d6c4d, 2024-12-09), with its BSD LICENSE, PATENTS, and AUTHORS, so the following commits show the refresh and the trim as diffs. Not built yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paths move under crate::gcc, approx gives way to a local macro, and the two unused files (inter_arrival, is_newer) go. Its 265 tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…C, and drive it as a Controller Refreshes the vendored port from WebRTC da2a3d6c4d to 556f7eec27: the LossBasedBweV2 defaults and candidate tie-break, the send-side pacing factor of 1.1, ALR probing gated on transport feedback, allocation probes outside ALR, and the removal of the link capacity tracker, LossBasedBandwidthEstimation and GetExpectedBwePeriod, with their tests. Trims what QUIC has no source for: RTCP loss reports and REMB, route changes, the network state estimate, separate audio detection, RTP sequence numbers, ECN feedback, padding, and the disabled-by-default AcknowledgedBitrateEstimator. The ALR detector stamps send times rather than the wall clock. Gcc adapts it to moq_congestion::Controller: ACKs and losses become transport feedback with the peer's receive times, the window is the congestion window, the pacing rate and target rate become the metrics, and a port of WebRTC's BitrateProber paces each probe cluster and reports what it still needs as probe_inflight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
moq-quic checks a datagram's pace while the packet before it is still being built, asking for both. Under 2 Mbit/s a rate's burst budget is a single packet, so both left on one packet's credit and the second's cost saturated at zero: a rate-paced controller sent at twice its pacing rate. The pacer now carries the overshoot as debt, repaid before new credit counts, bounded to one burst so unpaced loss probes cost no more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tests - The network becomes available to GoogCC at the first ACK, and a probe cluster starts with a large enough sent packet, so the initial probes pace data rather than the handshake. - A probe cluster the sender runs out of data for restarts under a new id with its next packet, since only the transport's duplicates can pad it; ALR probing is on so an application-limited sender probes. - Receive times map through i128 and clamp within an hour of the local clock, so no peer value overflows GoogCC's units. - gcc::Config builds it, namespaced as the congestion API audit settled. - Unit tests over a simulated uplink on mocked time, and a nightly measurement of a stopped rung under native probing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This reverts commit b62e538.
…hmark it - burst() is the pacer's budget at the pacing rate, or one packet while a probe cluster runs: moq-quic checks a datagram's pace while the packet before it is still being built, and a longer batch sends a packet the budget never pays for, twice the rate under 2 Mbit/s. - The crate's license gains BSD-3-Clause for the vendored GoogCC; the READMEs list GCC. - A per-packet benchmark beside BBR3's, run by the nightly's benchmark_per_packet filter. - The quest keeps what remains: a stopped rung does not yet recover under GCC's native probing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Allows say why, the unused std::time conversions go, and clippy's mechanical fixes apply. The module docs no longer link a private item. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… received A packet whose receive time never arrived, as when the ACK carrying it was lost, was left out of GoogCC's feedback once the peer had reported times, while every loss was reported, so the loss-based estimate read too high a loss rate. It now reaches the feedback as received with an ambiguous receive time, upstream's flag for a time not to read, which the delay-based estimate and the RTT skip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GoogCC reads a probe at the slower of its send and receive rates, so the transport holding a cluster's duplicates to the session's probe target capped the reading. A controller now says it paces its own probe (`Controller::paces_probe`), and the transport fills its clusters at that pace, bounded by the cluster's size instead of the target. BBR3 is unchanged. GCC's `probe_inflight` also rounds a cluster's remaining bytes up to whole packets: a cluster short of one packet's bytes held the sender back and was left unfinished, and its stretched reading pulled the estimate down. `rises_toward_target_under_gcc` now ends at 5.0 Mbit/s against the 4 Mbit/s target on every seed, 7.9s after the path opens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settled by the maintainer on 2026-11-01: - GoogCC gets a maximum rate (1 Gbit/s when the transport sets none), so its probes are no longer capped at 5 Mbit/s, and the session's probe target reaches it through a new `Controller::on_probe_target` hook, fed to `OnMaxTotalAllocatedBitrate` through a port of `OnStreamsConfig`. GoogCC then caps its probes at twice the target. - `rises_to_target_under_gcc` asserts that the estimate reaches the target, at 4 and 8 Mbit/s, and reports the rise time rather than holding GCC to BBR3's 5s budget. Lifting the 5 Mbit/s cap exposed a crawling start: from 300 kbit/s the initial probes let 2 Mbit/s of media queue behind the pacer, and its drain triggered an overuse cut to 85% of a startup throughput. GoogCC now starts at 1 Mbit/s, so its initial probes go out at 3 and 6 Mbit/s. moq-quic reports the target on every change and to each controller it builds on migration. The GCC quest is complete, so it is deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e/gcc Ports GCC to the congestion API audit (#7258): Factory, Metrics, and no into_any. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ALR detector rounded each interval between sends to whole milliseconds, so packets under a millisecond apart never refilled its budget and a fast sender below 80% of the estimate never entered the application-limited region. Subtract the whole-millisecond send times, as upstream's integer times do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e/gcc Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 4 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: cfb42fd2b3e73bd70937434cd91ccbc77a5581d0.
No actionable bugs found in the inspected adapter, feedback estimators and native-probing integration. Keeping GCC opt-in for the comparison is appropriate. The provided probe hooks preserve other controllers' behavior and reuse transport-owned duplication rather than introducing a second sending path. The documented startup/probing tradeoffs need the planned real-socket and encoder-following comparison before choosing a default.
Verification: static inspection of integration paths, estimator logic and regression tests. Tests, benchmarks and fuzzing were not run; the full vendored port's equivalence to upstream and the reported performance measurements were not independently verified.
(Written by gpt-6.1-sol)
There was a problem hiding this comment.
ℹ️ No critical issues. Two minor findings inline, both in code nothing selects yet.
Reviewed changes
- Vendored GoogCC (
rs/moq-congestion/src/gcc/). goog_cc 0.1.4 imported, refreshed to WebRTC556f7eec27, trimmed to a QUIC sender (no RTCP/REMB, ECN, padding,AcknowledgedBitrateEstimator), keeping WebRTC's BSDLICENSE/PATENTS/AUTHORS; the crate license becomes(MIT OR Apache-2.0) AND BSD-3-Clause. - Adapter (
gcc/mod.rs,Gcc). ACKs become transport feedback with peer receive times mapped onto the local clock through a fixedreceive_basisand theRECEIVE_SKEWclamp, losses become lost packets, and a 25ms process timer drives GoogCC. Window is GoogCC's congestion window,pacing_rateits pace,bandwidth_estimateits target. A portedBitrateProberpaces each probe cluster and reportsprobe_inflight. ControllerAPI. Two additive provided methods,paces_probe() -> bool(default false) andon_probe_target(u64)(default no-op), plus thegcc::Configfactory andpub use gcc::Gcc.Packetgains a privateprobe_clusterthat fits its existing padding.moq-quicprobing.set_probe_targetforwards the target to the current, previous, and newly built path controllers, and the native probe skips its own target bucket when the controller paces its own probe.- Tests, bench, quests. Vendored unit tests, mocked-time adapter sim tests over a simulated uplink, a per-packet bench, nightly
tests::probe::rises_to_target_under_gcc, and the quest bookkeeping that deletesgcc.md.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…nterval departs from upstream A restarted probe cluster's id, 1<<30 plus its count, wrapped to the same 16-bit short id as GoogCC's cluster 16384 plus that count, so a packet could resolve to the wrong cluster. GoogCC's ids now wrap within the lower half of the short ids and restarts within the upper half. The ALR detector's comment claimed upstream rounds each send time; at 556f7eec27 upstream rounds each interval. Say the departure is deliberate. 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: 7cb1e4a68215e7dc683d127824377d68e5c76de5.
No new actionable bugs found in the changes since my previous review. The compact-ID split is a simple, appropriate fix for the independently reported restart/GoogCC collision: both packet tagging and history lookup use the disjoint ranges, with regression coverage for boundaries and wrap. The ALR documentation finding is also addressed by explicitly documenting the intentional upstream departure; that behavior is unchanged.
Excluded the base branch's shared-version planning changes. Verification was static inspection of the changed mapping, its callers and tests; tests were not run. The earlier opt-in direction and performance-verification limits still apply.
(Written by gpt-6.1-sol)
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
The two commits since the prior pullfrog review (cfb42fd) apply the fixes to both inline findings, with no behavior beyond them. I re-verified the id-splitting arithmetic and ran the affected tests (cargo test -p moq-congestion --lib, both new tests pass).
- Restart cluster ids kept off GoogCC's.
short_idnow maps GoogCC's ids into1..=32767and the prober's restart ids (BitrateProber::RESTART_ID_BASE, nowpub) into32768..=65535, so a restarted cluster never shares a short id with one of GoogCC's.gcc::tests::restart_ids_never_alias_googcc_idscovers both ranges and the wrap boundaries, andrate.rs'sprobe_clusterdocs describe the split instead of the old "far fewer than wrap" premise. - ALR comment corrected.
alr_detector.rsnow documentssend_time.ms() - last_send_time.ms()as a deliberate departure from upstream's rounded interval rather than claiming equivalence. Behavior unchanged.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Main's GCC candidate (#7546) added a gcc flag to the probe scenarios beside this branch's delay_bound flag; both become one Controller enum, and GCC's rise reuses opening(). The epic no longer says the delay bound landed first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Completes and deletes quest/m2/quic-realtime/gcc.md, and removes its links from the realtime uplink epic and the WebRTC epic.
Summary
libwebrtc's Google Congestion Control now runs as
moq_congestion::Gcc, driven by receive timestamps (draft-ietf-quic-receive-ts). It is the GCC candidate in the realtime uplink bake-off.rs/moq-congestion/src/gcc/with WebRTC's BSDLICENSE,PATENTSandAUTHORS. The first commit imports it verbatim, so later commits show the refresh and the trim as diffs.Timestamps.GetExpectedBwePeriod.AcknowledgedBitrateEstimatorthat only a disabled field trial selects. Each file's docs say what it lost. The port's ALR detector stamped the wall clock; it now uses send times, so tests run on mocked time.gcc/mod.rs).i128and clamped within an hour of it. Each loss becomes feedback of lost packets.pacing_rateis GoogCC's pacing rate andbandwidth_estimateis its target rate.BitrateProberpaces each probe cluster and reports what the cluster still needs, in whole packets, asprobe_inflight, so moq-quic's native probing fills it. The cluster id rides inPacket's padding:size_of::<Packet>()stays 40.Controller::paces_probe(defaultfalse, GCCtrue) tells moq-quic that the controller sizes and paces its own probe. moq-quic then fills it at the controller'spaceinstead of holding the duplicates to the session's probe target. GoogCC's clusters (15ms or 5 packets at twice the estimate) bound the overhead. BBR3 keeps the target cap.Controller::on_probe_target(default no-op) hands the controller the session's probe target. moq-quic reports it on everyset_probe_targetchange, to the previous path's controller while a migration validates, and to each controller it builds on migration. GCC feeds it toOnMaxTotalAllocatedBitratethrough a port of upstream'sOnStreamsConfig, so GoogCC probes toward the target and caps its probes at twice it.SendSideBandwidthEstimationalready used, soProbeControllerno longer caps every probe at 5 Mbit/s.gcc::alr_detector::test::sub_millisecond_sends_refill_the_budgetfor the review fix below.probes_aim_at_the_target, where a 4 Mbit/s target holds filled probes between the target and twice it on a link where they otherwise reach 18.7 Mbit/s; hostile receive times. The hostile-time test caught an overflow panic, now fixed.tests::probe::rises_to_target_under_gcc(renamed fromrises_toward_target_under_gcc), at 4 and 8 Mbit/s targets: asserts that every seed's estimate reaches the target and that the probe's own budgets hold, and reports the rise time.tests::probe::paced_probe_fills_past_the_target: a controller that paces its own probe gets itsprobe_inflightfilled at once, while one that does not is held to the target.gcc::tests::probe_inflight_rounds_up_to_a_packetcovers the whole-packet remainder.Measurements
These were measured before this round's 1 Mbit/s start and 1 Gbit/s maximum, so GCC's rows may shift; the shared comparison in the epic re-runs every candidate. They come from the sibling quest's harness,
tests::realtimein moq-dev/moq#7544: sim replicas ofcellular-step,cellular-burstyandcellular-deep, with the uplink dropping at 10s and restoring at 20s, and a bulk upload averaged over 8 seeds. I added a GCC candidate in a local copy. That file is not onmain, so this PR does not carry the row; whichever of the two PRs lands second adds its candidate. The local patch swapsDELAY_BOUNDfor:What the table shows:
Stopped rung under native probing, re-measured on this head. This is
rises_to_target_under_gcc, BBR3'srises_to_targetscenario on 8 seeds: constant 2 Mbit/s media, a 3 Mbit/s path that opens to 12 Mbit/s at 5s, and 20s runs.A stopped rung now recovers at both targets, so the quest's goal is met. The previous head reached 4 Mbit/s 7.9s after the path opened and never reached 8 Mbit/s. The rise follows GoogCC's schedule: once the target is above the estimate, GoogCC probes from the application-limited region five seconds after it last probed or entered that region, at twice its estimate but no more than twice the target, and the 8 Mbit/s target takes a second such probe. As decided, the test asserts only that the target is reached, not BBR3's 5s budget.
Start. Lifting the 5 Mbit/s cap exposed a crawling start, in the baseline as much as while probing: the estimate fell to 0.7 Mbit/s within a second and climbed back at 8% a second, with 264 of 450 frames late on every seed. A trace showed why. From 300 kbit/s the initial probes took several round trips to reach the path, while 2 Mbit/s of media queued behind the pacer. Draining that backlog, once a probe found the path, queued it at the bottleneck, and the delay-based estimate cut the rate to 85% of the throughput acknowledged so far, which was still the startup rate. The media then held GoogCC out of the application-limited region, so no probe followed. Under the 5 Mbit/s cap, the capped probe happened to land after the cut and restore the estimate.
I compared candidate retunes on the same scenario, sweeping the first path's rate from 2 to 4.8 Mbit/s:
further_probe_threshold0.875)The 1 Mbit/s start is the only retune that held across the sweep, and it is one constant. On a 2 Mbit/s path, equal to the media, the 300 kbit/s variants had 380 late frames probing or not; the 1 Mbit/s start has 1 in the baseline but 59 while probing (see Follow-ups).
Per-packet cost, from
gcc::bench, the median of three runs with an ACK per packet:Most of the cost is
RobustThroughputEstimatorscanning its window of up to 500 packets on every ACK. At 32 packets per ACK GCC costs about 85ns per packet.Public API and wire
Controller::paces_probe, a provided method returningfalse, andController::on_probe_target, a provided no-op. Existing controllers are unaffected, and the change is additive.moq_congestion::gccmodule, withgcc::Configas its factory, and a newmoq_congestion::Gcc. This follows the namespacing of moq-dev/moq#7258, which has landed; the merge ports GCC to itsFactory,Metrics, and the removal ofinto_any.CongestionControl::Realtimeis the epic's job once a candidate wins.(MIT OR Apache-2.0) AND BSD-3-Clausefor the vendored code. BSD-3-Clause is already ondeny.toml's allow list.Packetgains a private field that fits its existing padding.Choices made without asking
approxandtest-tracedev-dependencies (a local macro replacesapprox) and addedtracingtomoq-congestion.requests_alr_probing). This lets an application-limited sender probe, the mechanism by which native probing can raise the estimate.ambiguous_receive_timeso the delay-based estimate and the RTT skip it.burst()is the pacer's own budget, or one packet during a probe cluster. moq-quic checks a datagram's pace while the packet before it is still being built; under 2 Mbit/s the budget is one packet, so a batch of two left on one packet's credit, at twice the pacing rate. The shared pacer's fix is quest/m1/pacer-low-rate.md.DropFrame:true.bandwidth_estimateis the loss-based target, and the pushback's frame-drop ratio has no consumer.Controllermethod. moq-quic must know which controllers bound their own probe, since BBR3 relies on the target cap, so a providedpaces_probe() -> boolcarries that.gcc::Configstill has no settings.max_outgoing_bytes_per_secondstill replaces it.on_probe_targettakes none and GoogCC timestamps its probes.rises_to_target_under_gccruns 20s rather than 15s, so the 8 Mbit/s rise, 14s in, has margin.Timestamps, so the intervals add up to the elapsed time (sub_millisecond_sends_refill_the_budget).Packetstores 16 bits, so GoogCC's ids wrap within 1–32767 and the prober's restart ids within 32768–65535, and the two never alias.Decisions
Settled by the maintainer on 2026-11-01:
Controller::paces_probe).Controllerhook carrying the session's probe target, fed toOnMaxTotalAllocatedBitrate.bandwidth_estimate, as moq-cli and moq-ffi do, alongside bulk. The epic README's remaining work already runs both sender models in one harness.Controllerhook fed toOnMaxTotalAllocatedBitrate. Implemented asController::on_probe_target, a 1 Gbit/s maximum, and a 1 Mbit/s start.rises_to_target_under_gcc: 5.0s at 4 Mbit/s, 9.0s at 8 Mbit/s.Follow-ups
RobustThroughputEstimator's window scan, and the feedback vectors it sorts and copies) if GCC wins.near-zeronoise floor, once the shared comparison has a real-socket harness.WebRTC-CongestionWindow's 350ms queue for the uplink presets.PROBE_CLUSTER_TIMEOUT, if GCC wins.backs_off_unreachable_target); GoogCC's ALR probes keep their own cadence. Worth a GCC case besideunreachable_targetif GCC wins.Review
quest review --deep --base origin/main: Codex, gpt-6-astra, medium effort, in a new session for this round's design.alr_detector.rs:86), a behavior upstream shares since its own move ontoTimestamps. Fixed in d09d1dc by subtracting whole-millisecond send times, as upstream's integer times did before that move, and covered bysub_millisecond_sends_refill_the_budget, which fails on the old rounding.Online reviewers (Pullfrog) on cfb42fd, two threads, both addressed in 88dad74:
gcc/mod.rs:532: restart ids (1 << 30plus a count) aliased GoogCC's cluster 16384 plus that count in the 16-bit short id. Fixed: GoogCC's ids wrap within the lower half and restarts within the upper half, covered bygcc::tests::restart_ids_never_alias_googcc_ids; thePacket.probe_clusterdocs inrate.rssay so.gcc/alr_detector.rs:84: the comment claimed upstream equivalence, but upstream rounds the interval. Fixed the comment to state the deliberate departure. Behavior kept, since upstream's rounding never refills the budget for sends under half a millisecond apart.Local review skipped for 88dad74 under the simple-review rule (moq-dev/quest#364): a fix of a few dozen lines in private code, touching no public API, wire format, security code, or
unsafe. The head adds a conflict-free merge of origin/main that touches quests alone.Local checks on 88dad74:
cargo nextest run -p moq-congestion --lib(348 passed),cargo clippy -p moq-congestion --all-targets -D warnings,cargo fmt --check, andquest check.Earlier local checks on d09d1dc, after merging origin/main:
cargo nextest run -p moq-congestion -p moq-quic --lib(all passed),cargo clippy -p moq-congestion -p moq-quic --all-targets -D warnings,cargo fmt --check,cargo docwith-D warnings,quest check, and the probe suite in release with its nightly cases (cargo test --release -p moq-quic --lib tests::probe:: -- --include-ignored, 22 passed, BBR3's unchanged). The fulljust checkwas not run, to keep the host's nearly full disk clear.🤖 Generated with Claude Code
(Written by Claude Opus 5.5)