fix(proto): congestion: avoid double-reducing CUBIC fast convergence

Quinn mistakenly applied the beta multiplier to the newly adjusted `W_max` instead of the current window.
This double-reduced the window to 59.5% instead of the intended 70%.
This fixes the math by snapshotting the original window for the multiplicative decrease.
This commit is contained in:
Vladimir Petrzhikovskii
2026-05-13 12:20:40 +02:00
committed by Diva Martínez
parent a6042549b4
commit 4cfbafa4cf
+32 -8
View File
@@ -199,18 +199,19 @@ impl Controller for Cubic {
}
self.state.recovery_start_time = Some(now);
let window = self.state.window as f64;
// Fast convergence
if (self.state.window as f64) < self.state.w_max {
self.state.w_max = self.state.window as f64 * (1.0 + BETA_CUBIC) / 2.0;
// Fast convergence lowers W_max first; the 0.7 loss reduction still
// applies to the old window, not to that already-reduced W_max.
// https://www.rfc-editor.org/rfc/rfc9438.html#section-4.7
// https://www.rfc-editor.org/rfc/rfc9438.html#section-4.6
if window < self.state.w_max {
self.state.w_max = window * (1.0 + BETA_CUBIC) / 2.0;
} else {
self.state.w_max = self.state.window as f64;
self.state.w_max = window;
}
self.state.ssthresh = cmp::max(
(self.state.w_max * BETA_CUBIC) as u64,
self.minimum_window(),
);
self.state.ssthresh = cmp::max((window * BETA_CUBIC) as u64, self.minimum_window());
self.state.window = self.state.ssthresh;
self.state.k = self.state.cubic_k(self.current_mtu);
@@ -300,3 +301,26 @@ impl ControllerFactory for CubicConfig {
Box::new(Cubic::new(self, now, current_mtu))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fast_convergence_reduces_w_max_without_double_reducing_window() {
let now = Instant::now();
let config = Arc::new(CubicConfig::default());
let mut cubic = Cubic::new(config, now, BASE_DATAGRAM_SIZE as u16);
let window = 8 * BASE_DATAGRAM_SIZE;
cubic.state.window = window;
cubic.state.ssthresh = window;
cubic.state.w_max = 12.0 * BASE_DATAGRAM_SIZE as f64;
cubic.on_congestion_event(now, now + Duration::from_millis(1), false, false, 0, 100);
assert_eq!(cubic.state.w_max, window as f64 * (1.0 + BETA_CUBIC) / 2.0);
assert_eq!(cubic.state.ssthresh, (window as f64 * BETA_CUBIC) as u64);
assert_eq!(cubic.state.window, cubic.state.ssthresh);
}
}