From 05def87600c4b3be4df97b9d71aa8ccdeddb60cd Mon Sep 17 00:00:00 2001 From: Vladimir Petrzhikovskii Date: Wed, 13 May 2026 14:00:38 +0200 Subject: [PATCH] congestion: avoid double-reducing CUBIC fast convergence Backport the CUBIC fast convergence fix to 0.11.x. Fast convergence adjusts W_max, but the loss reduction still applies to the old congestion window, not to the already-reduced W_max. Add a focused regression test for the 0.11.x field layout. --- quinn-proto/src/congestion/cubic.rs | 47 ++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/quinn-proto/src/congestion/cubic.rs b/quinn-proto/src/congestion/cubic.rs index 1df9ac4d7e..3630ef3c0a 100644 --- a/quinn-proto/src/congestion/cubic.rs +++ b/quinn-proto/src/congestion/cubic.rs @@ -178,20 +178,19 @@ impl Controller for Cubic { } self.recovery_start_time = Some(now); - - // Fast convergence - #[allow(clippy::branches_sharing_code)] - // https://github.com/rust-lang/rust-clippy/issues/7198 - if (self.window as f64) < self.cubic_state.w_max { - self.cubic_state.w_max = self.window as f64 * (1.0 + BETA_CUBIC) / 2.0; + let window = self.window as f64; + + // 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.cubic_state.w_max { + self.cubic_state.w_max = window * (1.0 + BETA_CUBIC) / 2.0; } else { - self.cubic_state.w_max = self.window as f64; + self.cubic_state.w_max = window; } - self.ssthresh = cmp::max( - (self.cubic_state.w_max * BETA_CUBIC) as u64, - self.minimum_window(), - ); + self.ssthresh = cmp::max((window * BETA_CUBIC) as u64, self.minimum_window()); self.window = self.ssthresh; self.cubic_state.k = self.cubic_state.cubic_k(self.current_mtu); @@ -264,3 +263,29 @@ 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.window = window; + cubic.ssthresh = window; + cubic.cubic_state.w_max = 12.0 * BASE_DATAGRAM_SIZE as f64; + + cubic.on_congestion_event(now, now + Duration::from_millis(1), false, 0); + + assert_eq!( + cubic.cubic_state.w_max, + window as f64 * (1.0 + BETA_CUBIC) / 2.0 + ); + assert_eq!(cubic.ssthresh, (window as f64 * BETA_CUBIC) as u64); + assert_eq!(cubic.window, cubic.ssthresh); + } +}