blob: 8b091c416a195b9fdcbe12410a9c7ee20939a7c7 [file] [log] [blame]
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +01001// Copyright (C) 2019, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright notice,
9// this list of conditions and the following disclaimer.
10//
11// * Redistributions in binary form must reproduce the above copyright
12// notice, this list of conditions and the following disclaimer in the
13// documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27//! CUBIC Congestion Control
28//!
Joel Galenson96d408b2021-06-08 17:53:00 -070029//! This implementation is based on the following draft:
30//! <https://tools.ietf.org/html/draft-ietf-tcpm-rfc8312bis-02>
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +010031//!
32//! Note that Slow Start can use HyStart++ when enabled.
33
34use std::cmp;
35
36use std::time::Duration;
37use std::time::Instant;
38
39use crate::packet;
40use crate::recovery;
41use crate::recovery::reno;
42
43use crate::recovery::Acked;
44use crate::recovery::CongestionControlOps;
45use crate::recovery::Recovery;
46
47pub static CUBIC: CongestionControlOps = CongestionControlOps {
48 on_packet_sent,
49 on_packet_acked,
50 congestion_event,
51 collapse_cwnd,
Joel Galenson96d408b2021-06-08 17:53:00 -070052 checkpoint,
53 rollback,
54 has_custom_pacing,
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +010055};
56
57/// CUBIC Constants.
58///
59/// These are recommended value in RFC8312.
60const BETA_CUBIC: f64 = 0.7;
61
62const C: f64 = 0.4;
63
Joel Galenson96d408b2021-06-08 17:53:00 -070064/// The packet count threshold to restore to the prior state if the
65/// lost packet count since the last checkpoint is less than the threshold.
66const RESTORE_COUNT_THRESHOLD: usize = 10;
67
68/// Default value of alpha_aimd in the beginning of congestion avoidance.
69const ALPHA_AIMD: f64 = 3.0 * (1.0 - BETA_CUBIC) / (1.0 + BETA_CUBIC);
70
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +010071/// CUBIC State Variables.
72///
73/// We need to keep those variables across the connection.
Joel Galenson96d408b2021-06-08 17:53:00 -070074/// k, w_max, w_est are described in the RFC.
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +010075#[derive(Debug, Default)]
76pub struct State {
77 k: f64,
78
79 w_max: f64,
80
Joel Galenson96d408b2021-06-08 17:53:00 -070081 w_est: f64,
82
83 alpha_aimd: f64,
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +010084
85 // Used in CUBIC fix (see on_packet_sent())
86 last_sent_time: Option<Instant>,
87
88 // Store cwnd increment during congestion avoidance.
89 cwnd_inc: usize,
Joel Galenson96d408b2021-06-08 17:53:00 -070090
91 // CUBIC state checkpoint preceding the last congestion event.
92 prior: PriorState,
93}
94
95/// Stores the CUBIC state from before the last congestion event.
96///
97/// <https://tools.ietf.org/id/draft-ietf-tcpm-rfc8312bis-00.html#section-4.9>
98#[derive(Debug, Default)]
99struct PriorState {
100 congestion_window: usize,
101
102 ssthresh: usize,
103
104 w_max: f64,
105
106 w_last_max: f64,
107
108 k: f64,
109
110 epoch_start: Option<Instant>,
111
112 lost_count: usize,
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100113}
114
115/// CUBIC Functions.
116///
117/// Note that these calculations are based on a count of cwnd as bytes,
118/// not packets.
119/// Unit of t (duration) and RTT are based on seconds (f64).
120impl State {
Joel Galenson96d408b2021-06-08 17:53:00 -0700121 // K = cubic_root ((w_max - cwnd) / C) (Eq. 2)
122 fn cubic_k(&self, cwnd: usize, max_datagram_size: usize) -> f64 {
123 let w_max = self.w_max / max_datagram_size as f64;
124 let cwnd = cwnd as f64 / max_datagram_size as f64;
125
126 libm::cbrt((w_max - cwnd) / C)
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100127 }
128
Joel Galenson96d408b2021-06-08 17:53:00 -0700129 // W_cubic(t) = C * (t - K)^3 + w_max (Eq. 1)
130 fn w_cubic(&self, t: Duration, max_datagram_size: usize) -> f64 {
131 let w_max = self.w_max / max_datagram_size as f64;
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100132
133 (C * (t.as_secs_f64() - self.k).powi(3) + w_max) *
Joel Galenson96d408b2021-06-08 17:53:00 -0700134 max_datagram_size as f64
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100135 }
136
Joel Galenson96d408b2021-06-08 17:53:00 -0700137 // W_est = W_est + alpha_aimd * (segments_acked / cwnd) (Eq. 4)
138 fn w_est_inc(
139 &self, acked: usize, cwnd: usize, max_datagram_size: usize,
140 ) -> f64 {
141 self.alpha_aimd * (acked as f64 / cwnd as f64) * max_datagram_size as f64
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100142 }
143}
144
145fn collapse_cwnd(r: &mut Recovery) {
146 let cubic = &mut r.cubic_state;
147
148 r.congestion_recovery_start_time = None;
149
Joel Galenson96d408b2021-06-08 17:53:00 -0700150 cubic.w_max = r.congestion_window as f64;
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100151
152 // 4.7 Timeout - reduce ssthresh based on BETA_CUBIC
153 r.ssthresh = (r.congestion_window as f64 * BETA_CUBIC) as usize;
Joel Galenson96d408b2021-06-08 17:53:00 -0700154 r.ssthresh = cmp::max(
155 r.ssthresh,
156 r.max_datagram_size * recovery::MINIMUM_WINDOW_PACKETS,
157 );
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100158
159 cubic.cwnd_inc = 0;
160
161 reno::collapse_cwnd(r);
162}
163
164fn on_packet_sent(r: &mut Recovery, sent_bytes: usize, now: Instant) {
165 // See https://github.com/torvalds/linux/commit/30927520dbae297182990bb21d08762bcc35ce1d
166 // First transmit when no packets in flight
167 let cubic = &mut r.cubic_state;
168
169 if let Some(last_sent_time) = cubic.last_sent_time {
170 if r.bytes_in_flight == 0 {
171 let delta = now - last_sent_time;
172
173 // We were application limited (idle) for a while.
174 // Shift epoch start to keep cwnd growth to cubic curve.
175 if let Some(recovery_start_time) = r.congestion_recovery_start_time {
176 if delta.as_nanos() > 0 {
177 r.congestion_recovery_start_time =
178 Some(recovery_start_time + delta);
179 }
180 }
181 }
182 }
183
184 cubic.last_sent_time = Some(now);
185
186 reno::on_packet_sent(r, sent_bytes, now);
187}
188
189fn on_packet_acked(
190 r: &mut Recovery, packet: &Acked, epoch: packet::Epoch, now: Instant,
191) {
192 let in_congestion_recovery = r.in_congestion_recovery(packet.time_sent);
193
194 r.bytes_in_flight = r.bytes_in_flight.saturating_sub(packet.size);
195
196 if in_congestion_recovery {
197 return;
198 }
199
200 if r.app_limited {
201 return;
202 }
203
Joel Galenson96d408b2021-06-08 17:53:00 -0700204 // Detecting spurious congestion events.
205 // <https://tools.ietf.org/id/draft-ietf-tcpm-rfc8312bis-00.html#section-4.9>
206 //
207 // When the recovery episode ends with recovering
208 // a few packets (less than RESTORE_COUNT_THRESHOLD), it's considered
209 // as spurious and restore to the previous state.
210 if r.congestion_recovery_start_time.is_some() {
211 let new_lost = r.lost_count - r.cubic_state.prior.lost_count;
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100212
Joel Galenson96d408b2021-06-08 17:53:00 -0700213 if r.congestion_window < r.cubic_state.prior.congestion_window &&
214 new_lost < RESTORE_COUNT_THRESHOLD
215 {
216 rollback(r);
217 return;
218 }
219 }
220
221 if r.congestion_window < r.ssthresh {
222 // In Slow slart, bytes_acked_sl is used for counting
223 // acknowledged bytes.
224 r.bytes_acked_sl += packet.size;
225
226 if r.bytes_acked_sl >= r.max_datagram_size {
227 r.congestion_window += r.max_datagram_size;
228 r.bytes_acked_sl -= r.max_datagram_size;
229 }
230
231 if r.hystart.enabled() &&
232 epoch == packet::EPOCH_APPLICATION &&
233 r.hystart.try_enter_lss(
234 packet,
235 r.latest_rtt,
236 r.congestion_window,
237 now,
238 r.max_datagram_size,
239 )
240 {
241 r.ssthresh = r.congestion_window;
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100242 }
243 } else {
244 // Congestion avoidance.
245 let ca_start_time;
246
247 // In LSS, use lss_start_time instead of congestion_recovery_start_time.
Joel Galenson96d408b2021-06-08 17:53:00 -0700248 if r.hystart.in_lss(epoch) {
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100249 ca_start_time = r.hystart.lss_start_time().unwrap();
250
251 // Reset w_max and k when LSS started.
252 if r.cubic_state.w_max == 0.0 {
253 r.cubic_state.w_max = r.congestion_window as f64;
254 r.cubic_state.k = 0.0;
Joel Galenson96d408b2021-06-08 17:53:00 -0700255
256 r.cubic_state.w_est = r.congestion_window as f64;
257 r.cubic_state.alpha_aimd = ALPHA_AIMD;
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100258 }
259 } else {
260 match r.congestion_recovery_start_time {
261 Some(t) => ca_start_time = t,
262 None => {
263 // When we come here without congestion_event() triggered,
264 // initialize congestion_recovery_start_time, w_max and k.
265 ca_start_time = now;
266 r.congestion_recovery_start_time = Some(now);
267
268 r.cubic_state.w_max = r.congestion_window as f64;
269 r.cubic_state.k = 0.0;
Joel Galenson96d408b2021-06-08 17:53:00 -0700270
271 r.cubic_state.w_est = r.congestion_window as f64;
272 r.cubic_state.alpha_aimd = ALPHA_AIMD;
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100273 },
274 }
275 }
276
277 let t = now - ca_start_time;
278
Joel Galenson96d408b2021-06-08 17:53:00 -0700279 // target = w_cubic(t + rtt)
280 let target = r.cubic_state.w_cubic(t + r.min_rtt, r.max_datagram_size);
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100281
Joel Galenson96d408b2021-06-08 17:53:00 -0700282 // Clipping target to [cwnd, 1.5 x cwnd]
283 let target = f64::max(target, r.congestion_window as f64);
284 let target = f64::min(target, r.congestion_window as f64 * 1.5);
285
286 // Update w_est.
287 let w_est_inc = r.cubic_state.w_est_inc(
288 packet.size,
289 r.congestion_window,
290 r.max_datagram_size,
291 );
292 r.cubic_state.w_est += w_est_inc;
293
294 if r.cubic_state.w_est >= r.cubic_state.w_max {
295 r.cubic_state.alpha_aimd = 1.0;
296 }
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100297
298 let mut cubic_cwnd = r.congestion_window;
299
Joel Galenson96d408b2021-06-08 17:53:00 -0700300 if r.cubic_state.w_cubic(t, r.max_datagram_size) < r.cubic_state.w_est {
301 // AIMD friendly region (W_cubic(t) < W_est)
302 cubic_cwnd = cmp::max(cubic_cwnd, r.cubic_state.w_est as usize);
303 } else {
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100304 // Concave region or convex region use same increment.
Joel Galenson96d408b2021-06-08 17:53:00 -0700305 let cubic_inc =
306 r.max_datagram_size * (target as usize - cubic_cwnd) / cubic_cwnd;
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100307
Joel Galenson96d408b2021-06-08 17:53:00 -0700308 cubic_cwnd += cubic_inc;
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100309 }
310
311 // When in Limited Slow Start, take the max of CA cwnd and
312 // LSS cwnd.
Joel Galenson96d408b2021-06-08 17:53:00 -0700313 if r.hystart.in_lss(epoch) {
314 let lss_cwnd_inc = r.hystart.lss_cwnd_inc(
315 packet.size,
316 r.congestion_window,
317 r.ssthresh,
318 );
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100319
Joel Galenson96d408b2021-06-08 17:53:00 -0700320 cubic_cwnd = cmp::max(cubic_cwnd, r.congestion_window + lss_cwnd_inc);
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100321 }
322
323 // Update the increment and increase cwnd by MSS.
324 r.cubic_state.cwnd_inc += cubic_cwnd - r.congestion_window;
325
Joel Galenson96d408b2021-06-08 17:53:00 -0700326 if r.cubic_state.cwnd_inc >= r.max_datagram_size {
327 r.congestion_window += r.max_datagram_size;
328 r.cubic_state.cwnd_inc -= r.max_datagram_size;
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100329 }
330 }
331}
332
333fn congestion_event(
334 r: &mut Recovery, time_sent: Instant, epoch: packet::Epoch, now: Instant,
335) {
336 let in_congestion_recovery = r.in_congestion_recovery(time_sent);
337
338 // Start a new congestion event if packet was sent after the
339 // start of the previous congestion recovery period.
340 if !in_congestion_recovery {
341 r.congestion_recovery_start_time = Some(now);
342
343 // Fast convergence
Joel Galenson96d408b2021-06-08 17:53:00 -0700344 if (r.congestion_window as f64) < r.cubic_state.w_max {
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100345 r.cubic_state.w_max =
Joel Galenson96d408b2021-06-08 17:53:00 -0700346 r.congestion_window as f64 * (1.0 + BETA_CUBIC) / 2.0;
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100347 } else {
Joel Galenson96d408b2021-06-08 17:53:00 -0700348 r.cubic_state.w_max = r.congestion_window as f64;
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100349 }
350
Joel Galenson96d408b2021-06-08 17:53:00 -0700351 r.ssthresh = (r.congestion_window as f64 * BETA_CUBIC) as usize;
352 r.ssthresh = cmp::max(
353 r.ssthresh,
354 r.max_datagram_size * recovery::MINIMUM_WINDOW_PACKETS,
355 );
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100356 r.congestion_window = r.ssthresh;
Joel Galenson96d408b2021-06-08 17:53:00 -0700357
358 r.cubic_state.k = if r.cubic_state.w_max < r.congestion_window as f64 {
359 0.0
360 } else {
361 r.cubic_state
362 .cubic_k(r.congestion_window, r.max_datagram_size)
363 };
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100364
365 r.cubic_state.cwnd_inc =
366 (r.cubic_state.cwnd_inc as f64 * BETA_CUBIC) as usize;
367
Joel Galenson96d408b2021-06-08 17:53:00 -0700368 r.cubic_state.w_est = r.congestion_window as f64;
369 r.cubic_state.alpha_aimd = ALPHA_AIMD;
370
371 if r.hystart.in_lss(epoch) {
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100372 r.hystart.congestion_event();
373 }
374 }
375}
376
Joel Galenson96d408b2021-06-08 17:53:00 -0700377fn checkpoint(r: &mut Recovery) {
378 r.cubic_state.prior.congestion_window = r.congestion_window;
379 r.cubic_state.prior.ssthresh = r.ssthresh;
380 r.cubic_state.prior.w_max = r.cubic_state.w_max;
381 r.cubic_state.prior.k = r.cubic_state.k;
382 r.cubic_state.prior.epoch_start = r.congestion_recovery_start_time;
383 r.cubic_state.prior.lost_count = r.lost_count;
384}
385
386fn rollback(r: &mut Recovery) {
387 r.congestion_window = r.cubic_state.prior.congestion_window;
388 r.ssthresh = r.cubic_state.prior.ssthresh;
389 r.cubic_state.w_max = r.cubic_state.prior.w_max;
390 r.cubic_state.k = r.cubic_state.prior.k;
391 r.congestion_recovery_start_time = r.cubic_state.prior.epoch_start;
392}
393
394fn has_custom_pacing() -> bool {
395 false
396}
397
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100398#[cfg(test)]
399mod tests {
400 use super::*;
401 use crate::recovery::hystart;
402
403 #[test]
404 fn cubic_init() {
405 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
406 cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::CUBIC);
407
408 let r = Recovery::new(&cfg);
409
410 assert!(r.cwnd() > 0);
411 assert_eq!(r.bytes_in_flight, 0);
412 }
413
414 #[test]
415 fn cubic_send() {
416 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
417 cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::CUBIC);
418
419 let mut r = Recovery::new(&cfg);
420
421 r.on_packet_sent_cc(1000, Instant::now());
422
423 assert_eq!(r.bytes_in_flight, 1000);
424 }
425
426 #[test]
427 fn cubic_slow_start() {
428 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
429 cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::CUBIC);
430
431 let mut r = Recovery::new(&cfg);
432 let now = Instant::now();
433
434 let p = recovery::Sent {
435 pkt_num: 0,
436 frames: vec![],
437 time_sent: now,
438 time_acked: None,
439 time_lost: None,
Joel Galenson96d408b2021-06-08 17:53:00 -0700440 size: r.max_datagram_size,
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100441 ack_eliciting: true,
442 in_flight: true,
443 delivered: 0,
444 delivered_time: now,
445 recent_delivered_packet_sent_time: now,
446 is_app_limited: false,
447 has_data: false,
448 };
449
Joel Galenson96d408b2021-06-08 17:53:00 -0700450 // Send initcwnd full MSS packets to become no longer app limited
451 for _ in 0..recovery::INITIAL_WINDOW_PACKETS {
452 r.on_packet_sent_cc(p.size, now);
453 }
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100454
455 let cwnd_prev = r.cwnd();
456
457 let acked = vec![Acked {
458 pkt_num: p.pkt_num,
459 time_sent: p.time_sent,
460 size: p.size,
461 }];
462
463 r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now);
464
465 // Check if cwnd increased by packet size (slow start)
466 assert_eq!(r.cwnd(), cwnd_prev + p.size);
467 }
468
469 #[test]
Joel Galenson96d408b2021-06-08 17:53:00 -0700470 fn cubic_slow_start_multi_acks() {
471 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
472 cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::CUBIC);
473
474 let mut r = Recovery::new(&cfg);
475 let now = Instant::now();
476
477 let p = recovery::Sent {
478 pkt_num: 0,
479 frames: vec![],
480 time_sent: now,
481 time_acked: None,
482 time_lost: None,
483 size: r.max_datagram_size,
484 ack_eliciting: true,
485 in_flight: true,
486 delivered: 0,
487 delivered_time: now,
488 recent_delivered_packet_sent_time: now,
489 is_app_limited: false,
490 has_data: false,
491 };
492
493 // Send initcwnd full MSS packets to become no longer app limited
494 for _ in 0..recovery::INITIAL_WINDOW_PACKETS {
495 r.on_packet_sent_cc(p.size, now);
496 }
497
498 let cwnd_prev = r.cwnd();
499
500 let acked = vec![
501 Acked {
502 pkt_num: p.pkt_num,
503 time_sent: p.time_sent,
504 size: p.size,
505 },
506 Acked {
507 pkt_num: p.pkt_num,
508 time_sent: p.time_sent,
509 size: p.size,
510 },
511 Acked {
512 pkt_num: p.pkt_num,
513 time_sent: p.time_sent,
514 size: p.size,
515 },
516 ];
517
518 r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now);
519
520 // Acked 3 packets.
521 assert_eq!(r.cwnd(), cwnd_prev + p.size * 3);
522 }
523
524 #[test]
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100525 fn cubic_congestion_event() {
526 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
527 cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::CUBIC);
528
529 let mut r = Recovery::new(&cfg);
530 let now = Instant::now();
531 let prev_cwnd = r.cwnd();
532
533 r.congestion_event(now, packet::EPOCH_APPLICATION, now);
534
535 // In CUBIC, after congestion event, cwnd will be reduced by (1 -
536 // CUBIC_BETA)
537 assert_eq!(prev_cwnd as f64 * BETA_CUBIC, r.cwnd() as f64);
538 }
539
540 #[test]
541 fn cubic_congestion_avoidance() {
542 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
543 cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::CUBIC);
544
545 let mut r = Recovery::new(&cfg);
Joel Galenson96d408b2021-06-08 17:53:00 -0700546 let mut now = Instant::now();
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100547 let prev_cwnd = r.cwnd();
548
Joel Galenson96d408b2021-06-08 17:53:00 -0700549 // Send initcwnd full MSS packets to become no longer app limited
550 for _ in 0..recovery::INITIAL_WINDOW_PACKETS {
551 r.on_packet_sent_cc(r.max_datagram_size, now);
552 }
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100553
554 // Trigger congestion event to update ssthresh
555 r.congestion_event(now, packet::EPOCH_APPLICATION, now);
556
557 // After congestion event, cwnd will be reduced.
558 let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;
559 assert_eq!(r.cwnd(), cur_cwnd);
560
Joel Galenson96d408b2021-06-08 17:53:00 -0700561 // Shift current time by 1 RTT.
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100562 let rtt = Duration::from_millis(100);
563
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100564 r.update_rtt(rtt, Duration::from_millis(0), now);
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100565
Joel Galenson96d408b2021-06-08 17:53:00 -0700566 // Exit from the recovery.
567 now += rtt;
568
569 // To avoid rollback
570 r.lost_count += RESTORE_COUNT_THRESHOLD;
571
572 // During Congestion Avoidance, it will take
573 // 5 ACKs to increase cwnd by 1 MSS.
574 for _ in 0..5 {
575 let acked = vec![Acked {
576 pkt_num: 0,
577 time_sent: now,
578 size: r.max_datagram_size,
579 }];
580
581 r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now);
582 now += rtt;
583 }
584
585 assert_eq!(r.cwnd(), cur_cwnd + r.max_datagram_size);
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100586 }
587
588 #[test]
589 fn cubic_collapse_cwnd_and_restart() {
590 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
591 cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::CUBIC);
592
593 let mut r = Recovery::new(&cfg);
594 let now = Instant::now();
595
596 // Fill up bytes_in_flight to avoid app_limited=true
597 r.on_packet_sent_cc(30000, now);
598
599 // Trigger congestion event to update ssthresh
600 r.congestion_event(now, packet::EPOCH_APPLICATION, now);
601
Joel Galenson96d408b2021-06-08 17:53:00 -0700602 // After persistent congestion, cwnd should be the minimum window
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100603 r.collapse_cwnd();
Joel Galenson96d408b2021-06-08 17:53:00 -0700604 assert_eq!(
605 r.cwnd(),
606 r.max_datagram_size * recovery::MINIMUM_WINDOW_PACKETS
607 );
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100608
609 let acked = vec![Acked {
610 pkt_num: 0,
611 // To exit from recovery
612 time_sent: now + Duration::from_millis(1),
Joel Galenson96d408b2021-06-08 17:53:00 -0700613 size: r.max_datagram_size,
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100614 }];
615
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100616 r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now);
617
Joel Galenson96d408b2021-06-08 17:53:00 -0700618 // Slow start again - cwnd will be increased by 1 MSS
619 assert_eq!(
620 r.cwnd(),
621 r.max_datagram_size * (recovery::MINIMUM_WINDOW_PACKETS + 1)
622 );
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100623 }
624
625 #[test]
626 fn cubic_hystart_limited_slow_start() {
627 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
628 cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::CUBIC);
629 cfg.enable_hystart(true);
630
631 let mut r = Recovery::new(&cfg);
632 let now = Instant::now();
633 let pkt_num = 0;
634 let epoch = packet::EPOCH_APPLICATION;
635
636 let p = recovery::Sent {
637 pkt_num: 0,
638 frames: vec![],
639 time_sent: now,
640 time_acked: None,
641 time_lost: None,
Joel Galenson96d408b2021-06-08 17:53:00 -0700642 size: r.max_datagram_size,
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100643 ack_eliciting: true,
644 in_flight: true,
645 delivered: 0,
646 delivered_time: now,
647 recent_delivered_packet_sent_time: now,
648 is_app_limited: false,
649 has_data: false,
650 };
651
652 // 1st round.
653 let n_rtt_sample = hystart::N_RTT_SAMPLE;
654 let pkts_1st_round = n_rtt_sample as u64;
655 r.hystart.start_round(pkt_num);
656
657 let rtt_1st = 50;
658
659 // Send 1st round packets.
660 for _ in 0..n_rtt_sample {
661 r.on_packet_sent_cc(p.size, now);
662 }
663
664 // Receving Acks.
665 let now = now + Duration::from_millis(rtt_1st);
666 for _ in 0..n_rtt_sample {
667 r.update_rtt(
668 Duration::from_millis(rtt_1st),
669 Duration::from_millis(0),
670 now,
671 );
672
673 let acked = vec![Acked {
674 pkt_num: p.pkt_num,
675 time_sent: p.time_sent,
676 size: p.size,
677 }];
678
679 r.on_packets_acked(acked, epoch, now);
680 }
681
682 // Not in LSS yet.
683 assert_eq!(r.hystart.lss_start_time().is_some(), false);
684
685 // 2nd round.
Joel Galenson96d408b2021-06-08 17:53:00 -0700686 r.hystart.start_round(pkts_1st_round * 2);
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100687
688 let mut rtt_2nd = 100;
689 let now = now + Duration::from_millis(rtt_2nd);
690
691 // Send 2nd round packets.
Joel Galenson96d408b2021-06-08 17:53:00 -0700692 for _ in 0..n_rtt_sample {
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100693 r.on_packet_sent_cc(p.size, now);
694 }
695
696 // Receving Acks.
697 // Last ack will cause to exit to LSS.
698 let mut cwnd_prev = r.cwnd();
699
Joel Galenson96d408b2021-06-08 17:53:00 -0700700 for _ in 0..n_rtt_sample {
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100701 cwnd_prev = r.cwnd();
702 r.update_rtt(
703 Duration::from_millis(rtt_2nd),
704 Duration::from_millis(0),
705 now,
706 );
707
708 let acked = vec![Acked {
709 pkt_num: p.pkt_num,
710 time_sent: p.time_sent,
711 size: p.size,
712 }];
713
714 r.on_packets_acked(acked, epoch, now);
715
716 // Keep increasing RTT so that hystart exits to LSS.
717 rtt_2nd += 4;
718 }
719
720 // Now we are in LSS.
721 assert_eq!(r.hystart.lss_start_time().is_some(), true);
Joel Galenson96d408b2021-06-08 17:53:00 -0700722 assert_eq!(r.cwnd(), cwnd_prev + r.max_datagram_size);
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100723
Joel Galenson96d408b2021-06-08 17:53:00 -0700724 // Send a full cwnd.
725 r.on_packet_sent_cc(r.cwnd(), now);
726
727 // Ack'ing 4 packets to increase cwnd by 1 MSS during LSS
728 cwnd_prev = r.cwnd();
729 for _ in 0..4 {
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100730 let acked = vec![Acked {
731 pkt_num: p.pkt_num,
732 time_sent: p.time_sent,
733 size: p.size,
734 }];
735 r.on_packets_acked(acked, epoch, now);
736 }
737
Joel Galenson96d408b2021-06-08 17:53:00 -0700738 // During LSS cwnd will be increased less than usual slow start.
739 assert_eq!(r.cwnd(), cwnd_prev + r.max_datagram_size);
740 }
741
742 #[test]
743 fn cubic_spurious_congestion_event() {
744 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
745 cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::CUBIC);
746
747 let mut r = Recovery::new(&cfg);
748 let now = Instant::now();
749 let prev_cwnd = r.cwnd();
750
751 // Send initcwnd full MSS packets to become no longer app limited
752 for _ in 0..recovery::INITIAL_WINDOW_PACKETS {
753 r.on_packet_sent_cc(r.max_datagram_size, now);
754 }
755
756 // Trigger congestion event to update ssthresh
757 r.congestion_event(now, packet::EPOCH_APPLICATION, now);
758
759 // After congestion event, cwnd will be reduced.
760 let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;
761 assert_eq!(r.cwnd(), cur_cwnd);
762
763 let rtt = Duration::from_millis(100);
764
765 let acked = vec![Acked {
766 pkt_num: 0,
767 // To exit from recovery
768 time_sent: now + rtt,
769 size: r.max_datagram_size,
770 }];
771
772 // Ack more than cwnd bytes with rtt=100ms
773 r.update_rtt(rtt, Duration::from_millis(0), now);
774
775 // Trigger detecting sprurious congestion event
776 r.on_packets_acked(
777 acked,
778 packet::EPOCH_APPLICATION,
779 now + rtt + Duration::from_millis(5),
780 );
781
782 // cwnd is restored to the previous one.
783 assert_eq!(r.cwnd(), prev_cwnd);
784 }
785
786 #[test]
787 fn cubic_fast_convergence() {
788 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
789 cfg.set_cc_algorithm(recovery::CongestionControlAlgorithm::CUBIC);
790
791 let mut r = Recovery::new(&cfg);
792 let mut now = Instant::now();
793 let prev_cwnd = r.cwnd();
794
795 // Send initcwnd full MSS packets to become no longer app limited
796 for _ in 0..recovery::INITIAL_WINDOW_PACKETS {
797 r.on_packet_sent_cc(r.max_datagram_size, now);
798 }
799
800 // Trigger congestion event to update ssthresh
801 r.congestion_event(now, packet::EPOCH_APPLICATION, now);
802
803 // After 1st congestion event, cwnd will be reduced.
804 let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;
805 assert_eq!(r.cwnd(), cur_cwnd);
806
807 // Shift current time by 1 RTT.
808 let rtt = Duration::from_millis(100);
809 r.update_rtt(rtt, Duration::from_millis(0), now);
810
811 // Exit from the recovery.
812 now += rtt;
813
814 // To avoid rollback
815 r.lost_count += RESTORE_COUNT_THRESHOLD;
816
817 // During Congestion Avoidance, it will take
818 // 5 ACKs to increase cwnd by 1 MSS.
819 for _ in 0..5 {
820 let acked = vec![Acked {
821 pkt_num: 0,
822 time_sent: now,
823 size: r.max_datagram_size,
824 }];
825
826 r.on_packets_acked(acked, packet::EPOCH_APPLICATION, now);
827 now += rtt;
828 }
829
830 assert_eq!(r.cwnd(), cur_cwnd + r.max_datagram_size);
831
832 let prev_cwnd = r.cwnd();
833
834 // Fast convergence: now there is 2nd congestion event and
835 // cwnd is not fully recovered to w_max, w_max will be
836 // further reduced.
837 r.congestion_event(now, packet::EPOCH_APPLICATION, now);
838
839 // After 2nd congestion event, cwnd will be reduced.
840 let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;
841 assert_eq!(r.cwnd(), cur_cwnd);
842
843 // w_max will be further reduced, not prev_cwnd
844 assert_eq!(
845 r.cubic_state.w_max,
846 prev_cwnd as f64 * (1.0 + BETA_CUBIC) / 2.0
847 );
Jeff Vander Stoep2bbaf7e2020-12-04 14:00:07 +0100848 }
849}