Skip to content

fix(proto): re-derive BBRv3's initial pacing rate from the first RTT … - #802

Open
richinsley wants to merge 1 commit into
n0-computer:mainfrom
richinsley:pr/bbr3-initial-pacing
Open

fix(proto): re-derive BBRv3's initial pacing rate from the first RTT …#802
richinsley wants to merge 1 commit into
n0-computer:mainfrom
richinsley:pr/bbr3-initial-pacing

Conversation

@richinsley

@richinsley richinsley commented Sep 11, 2026

Copy link
Copy Markdown

Description

At construction no SRTT exists, so Bbr3::new computes the initial pacing rate as startup_pacing_gain * InitialCwnd / 1 ms (~266 Mbps for a 12 kB window). Until startup exits, BBRSetPacingRateWithGain only ever raises the rate. A flow that stays application-limited never exits startup — its delivery-rate samples never plateau — so that placeholder stays for the life of the flow and the flow is effectively unpaced: every burst leaves at line rate.

This change re-derives the pacing rate from InitialCwnd / SRTT on the first acknowledgement that arrives with an RTT sample, as Linux BBR does (has_seen_rtt / bbr_init_pacing_rate_from_rtt) and as the draft's BBRInitPacingRate intends (InitialCwnd / (SRTT ? SRTT : 1ms), which is only ever evaluated once, before an SRTT exists). Startup's bandwidth-driven growth is unchanged; the rate simply starts from a value the path can carry.

  • Bbr3::has_seen_rtt, and the re-initialisation in on_ack (with set_send_quantum so the quantum matches the corrected rate).
  • RttEstimator::has_sample() so the configured initial RTT is not mistaken for a measurement.
  • Test initial_pacing_rate_follows_first_rtt_sample: after one packet acknowledged with a 40 ms RTT, pacing_rate is gain * 12000 / 0.040 (832 kB/s), not the 33 MB/s placeholder.

Fixes #800.

API Changes

RttEstimator::has_sample(&self) -> bool — additive, on a public type.

Notes & open questions

  • A flow that does fill the pipe is unaffected in practice: its measured bandwidth raises the rate within a few round trips either way.
  • Where I hit it: a second multipath path opened after the handshake and fed a paced media source, in a link-bonding experiment on netem-shaped links. The path reported a constant 266 Mbps pacing rate for whole sessions; with this change it paces at 5.5–7.6 Mbps on an 8 Mbps link, and burst loss into a 25-packet queue went from 28 % to under 3 %.
  • The same construction is in quinn's open BBRv3 PR (feat(bbr3) Implemented bbr3's latest version based on the RFC quinn-rs/quinn#2481); I will point it out there.
  • This came out of a Rust port of a bonding transport I am prototyping; I prepared the change with tooling assistance and have gone through the mechanism and the test myself.

Change checklist

  • Self-review.
  • Documentation updates following the style guide, if relevant.
  • Tests if relevant.
  • All API changes documented.
  • This PR was created by a human that thought critically about the
    proposed change and wrote an as clear and concise description as
    they could.
  • This PR isn't slop, and is carefully crafted to do have the
    intented effect.
  • cargo make passes locally.

…sample

At construction no SRTT exists, so `Bbr3::new` computes the initial pacing
rate as `startup_pacing_gain * InitialCwnd / 1 ms` (the draft's
placeholder when SRTT is unknown): about 266 Mbps for a 12 kB window.
Until startup exits, `BBRSetPacingRateWithGain` only ever raises the
rate. A flow that stays application-limited never exits startup (its
delivery-rate samples never show a plateau), so that placeholder stays
for the life of the flow and the flow is effectively unpaced: every burst
goes out at line rate.

This shows up on any secondary multipath path that is opened after the
handshake and fed a rate-limited source. Observed with a paced media
source over two links: the second path reported a constant 266 Mbps
pacing rate for entire sessions, and frame-sized bursts overflowed a
shallow queue with 28% loss.

Linux BBR handles this with `has_seen_rtt`: once the first RTT sample
exists, the pacing rate is re-initialised from `InitialCwnd / SRTT`
(`bbr_init_pacing_rate_from_rtt`) and grows from there. Do the same on
the first acknowledgement that arrives with an RTT sample; the estimator
gains `has_sample()` so the configured initial RTT is not mistaken for
one. Startup's bandwidth-driven growth is unchanged.

Test: after one packet acknowledged with a 40 ms RTT the pacing rate is
`gain * 12000 / 0.040` (832 kB/s) instead of the 33 MB/s placeholder.
@n0bot n0bot Bot added this to iroh Sep 11, 2026
@github-project-automation github-project-automation Bot moved this to 🚑 Needs Triage in iroh Sep 11, 2026
@matheus23

Copy link
Copy Markdown
Member

This PR was created by a human that thought critically about the proposed change and wrote an as clear and concise description as they could.

I highly doubt this is the case, given you took your agent's instructions and copied them into your PR description:

Everything below the line is the PR body. The fifth checkbox is left for you: tick it once you
have read the diff (git show HEAD in ~/projects/quic_research/noq-pr-pacing); it is one
boolean, one recomputation on the first acknowledged RTT, one getter, and the test.


I'm tempted to just close this PR. In any case, the PR description needs to be fixed otherwise we can't accept this change.

@richinsley

Copy link
Copy Markdown
Author

Sorry about that. The code was me and the review was me, but the PR text claude (I'm not particularly good at PR write ups).

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

Labels

None yet

Projects

Status: 🚑 Needs Triage

Development

Successfully merging this pull request may close these issues.

BBRv3: an application-limited flow is never paced (initial pacing rate stays at InitialCwnd / 1 ms)

2 participants