quinn_proto/congestion/
cubic.rs1use std::any::Any;
2use std::cmp;
3use std::sync::Arc;
4
5use super::{BASE_DATAGRAM_SIZE, Controller, ControllerFactory};
6use crate::connection::RttEstimator;
7use crate::{Duration, Instant};
8
9const BETA_CUBIC: f64 = 0.7;
13
14const C: f64 = 0.4;
15
16#[derive(Debug, Default, Clone)]
21pub(super) struct State {
22 k: f64,
23
24 w_max: f64,
25
26 cwnd_inc: u64,
28}
29
30impl State {
36 fn cubic_k(&self, max_datagram_size: u64) -> f64 {
38 let w_max = self.w_max / max_datagram_size as f64;
39 (w_max * (1.0 - BETA_CUBIC) / C).cbrt()
40 }
41
42 fn w_cubic(&self, t: Duration, max_datagram_size: u64) -> f64 {
44 let w_max = self.w_max / max_datagram_size as f64;
45
46 (C * (t.as_secs_f64() - self.k).powi(3) + w_max) * max_datagram_size as f64
47 }
48
49 fn w_est(&self, t: Duration, rtt: Duration, max_datagram_size: u64) -> f64 {
52 let w_max = self.w_max / max_datagram_size as f64;
53 (w_max * BETA_CUBIC
54 + 3.0 * (1.0 - BETA_CUBIC) / (1.0 + BETA_CUBIC) * t.as_secs_f64() / rtt.as_secs_f64())
55 * max_datagram_size as f64
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct Cubic {
62 config: Arc<CubicConfig>,
63 window: u64,
65 ssthresh: u64,
68 recovery_start_time: Option<Instant>,
71 cubic_state: State,
72 current_mtu: u64,
73}
74
75impl Cubic {
76 pub fn new(config: Arc<CubicConfig>, _now: Instant, current_mtu: u16) -> Self {
78 Self {
79 window: config.initial_window,
80 ssthresh: u64::MAX,
81 recovery_start_time: None,
82 config,
83 cubic_state: Default::default(),
84 current_mtu: current_mtu as u64,
85 }
86 }
87
88 fn minimum_window(&self) -> u64 {
89 2 * self.current_mtu
90 }
91}
92
93impl Controller for Cubic {
94 fn on_ack(
95 &mut self,
96 now: Instant,
97 sent: Instant,
98 bytes: u64,
99 app_limited: bool,
100 rtt: &RttEstimator,
101 ) {
102 if app_limited
103 || self
104 .recovery_start_time
105 .map(|recovery_start_time| sent <= recovery_start_time)
106 .unwrap_or(false)
107 {
108 return;
109 }
110
111 if self.window < self.ssthresh {
112 self.window += bytes;
114 } else {
115 let ca_start_time;
117
118 match self.recovery_start_time {
119 Some(t) => ca_start_time = t,
120 None => {
121 ca_start_time = now;
124 self.recovery_start_time = Some(now);
125
126 self.cubic_state.w_max = self.window as f64;
127 self.cubic_state.k = 0.0;
128 }
129 }
130
131 let t = now - ca_start_time;
132
133 let w_cubic = self.cubic_state.w_cubic(t + rtt.get(), self.current_mtu);
135
136 let w_est = self.cubic_state.w_est(t, rtt.get(), self.current_mtu);
138
139 let mut cubic_cwnd = self.window;
140
141 if w_cubic < w_est {
142 cubic_cwnd = cmp::max(cubic_cwnd, w_est as u64);
144 } else if cubic_cwnd < w_cubic as u64 {
145 let cubic_inc =
147 (w_cubic - cubic_cwnd as f64) / cubic_cwnd as f64 * self.current_mtu as f64;
148
149 cubic_cwnd += cubic_inc as u64;
150 }
151
152 self.cubic_state.cwnd_inc += cubic_cwnd - self.window;
154
155 if self.cubic_state.cwnd_inc >= self.current_mtu {
159 self.window += self.current_mtu;
160 self.cubic_state.cwnd_inc = 0;
161 }
162 }
163 }
164
165 fn on_congestion_event(
166 &mut self,
167 now: Instant,
168 sent: Instant,
169 is_persistent_congestion: bool,
170 _lost_bytes: u64,
171 ) {
172 if self
173 .recovery_start_time
174 .map(|recovery_start_time| sent <= recovery_start_time)
175 .unwrap_or(false)
176 {
177 return;
178 }
179
180 self.recovery_start_time = Some(now);
181
182 if (self.window as f64) < self.cubic_state.w_max {
184 self.cubic_state.w_max = self.window as f64 * (1.0 + BETA_CUBIC) / 2.0;
185 } else {
186 self.cubic_state.w_max = self.window as f64;
187 }
188
189 self.ssthresh = cmp::max(
190 (self.cubic_state.w_max * BETA_CUBIC) as u64,
191 self.minimum_window(),
192 );
193 self.window = self.ssthresh;
194 self.cubic_state.k = self.cubic_state.cubic_k(self.current_mtu);
195
196 self.cubic_state.cwnd_inc = (self.cubic_state.cwnd_inc as f64 * BETA_CUBIC) as u64;
197
198 if is_persistent_congestion {
199 self.recovery_start_time = None;
200 self.cubic_state.w_max = self.window as f64;
201
202 self.ssthresh = cmp::max(
204 (self.window as f64 * BETA_CUBIC) as u64,
205 self.minimum_window(),
206 );
207
208 self.cubic_state.cwnd_inc = 0;
209
210 self.window = self.minimum_window();
211 }
212 }
213
214 fn on_mtu_update(&mut self, new_mtu: u16) {
215 self.current_mtu = new_mtu as u64;
216 self.window = self.window.max(self.minimum_window());
217 }
218
219 fn window(&self) -> u64 {
220 self.window
221 }
222
223 fn clone_box(&self) -> Box<dyn Controller> {
224 Box::new(self.clone())
225 }
226
227 fn initial_window(&self) -> u64 {
228 self.config.initial_window
229 }
230
231 fn into_any(self: Box<Self>) -> Box<dyn Any> {
232 self
233 }
234}
235
236#[derive(Debug, Clone)]
238pub struct CubicConfig {
239 initial_window: u64,
240}
241
242impl CubicConfig {
243 pub fn initial_window(&mut self, value: u64) -> &mut Self {
247 self.initial_window = value;
248 self
249 }
250}
251
252impl Default for CubicConfig {
253 fn default() -> Self {
254 Self {
255 initial_window: 14720.clamp(2 * BASE_DATAGRAM_SIZE, 10 * BASE_DATAGRAM_SIZE),
256 }
257 }
258}
259
260impl ControllerFactory for CubicConfig {
261 fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller> {
262 Box::new(Cubic::new(self, now, current_mtu))
263 }
264}