quiche/recovery/congestion/
cubic.rs1use std::cmp;
35
36use std::time::Duration;
37use std::time::Instant;
38
39use super::rtt::RttStats;
40use super::Acked;
41use super::Sent;
42
43use super::reno;
44use super::Congestion;
45use super::CongestionControlOps;
46use crate::recovery::MINIMUM_WINDOW_PACKETS;
47
48pub(crate) static CUBIC: CongestionControlOps = CongestionControlOps {
49 on_init,
50 on_packet_sent,
51 on_packets_acked,
52 congestion_event,
53 checkpoint,
54 rollback,
55 has_custom_pacing,
56 #[cfg(feature = "qlog")]
57 state_str,
58 debug_fmt,
59};
60
61const BETA_CUBIC: f64 = 0.7;
65
66const C: f64 = 0.4;
67
68const ROLLBACK_THRESHOLD_PERCENT: usize = 20;
71
72const MIN_ROLLBACK_THRESHOLD: usize = 2;
74
75const ALPHA_AIMD: f64 = 3.0 * (1.0 - BETA_CUBIC) / (1.0 + BETA_CUBIC);
77
78#[derive(Debug, Default)]
83pub struct State {
84 k: f64,
85
86 w_max: f64,
87
88 w_est: f64,
89
90 alpha_aimd: f64,
91
92 last_sent_time: Option<Instant>,
94
95 cwnd_inc: usize,
97
98 prior: PriorState,
100}
101
102#[derive(Debug, Default)]
106struct PriorState {
107 congestion_window: usize,
108
109 ssthresh: usize,
110
111 w_max: f64,
112
113 k: f64,
114
115 epoch_start: Option<Instant>,
116
117 lost_count: usize,
118}
119
120impl State {
126 fn cubic_k(&self, cwnd: usize, max_datagram_size: usize) -> f64 {
128 let w_max = self.w_max / max_datagram_size as f64;
129 let cwnd = cwnd as f64 / max_datagram_size as f64;
130
131 libm::cbrt((w_max - cwnd) / C)
132 }
133
134 fn w_cubic(&self, t: Duration, max_datagram_size: usize) -> f64 {
136 let w_max = self.w_max / max_datagram_size as f64;
137
138 (C * (t.as_secs_f64() - self.k).powi(3) + w_max) *
139 max_datagram_size as f64
140 }
141
142 fn w_est_inc(
144 &self, acked: usize, cwnd: usize, max_datagram_size: usize,
145 ) -> f64 {
146 self.alpha_aimd * (acked as f64 / cwnd as f64) * max_datagram_size as f64
147 }
148}
149
150fn on_init(_r: &mut Congestion) {}
151
152fn on_packet_sent(
153 r: &mut Congestion, sent_bytes: usize, bytes_in_flight: usize, now: Instant,
154) {
155 let cubic = &mut r.cubic_state;
158
159 if let Some(last_sent_time) = cubic.last_sent_time {
160 if bytes_in_flight == 0 {
161 let delta = now - last_sent_time;
162
163 if let Some(recovery_start_time) = r.congestion_recovery_start_time {
166 if delta.as_nanos() > 0 {
167 r.congestion_recovery_start_time =
168 Some(recovery_start_time + delta);
169 }
170 }
171 }
172 }
173
174 cubic.last_sent_time = Some(now);
175
176 reno::on_packet_sent(r, sent_bytes, bytes_in_flight, now);
177}
178
179fn on_packets_acked(
180 r: &mut Congestion, bytes_in_flight: usize, packets: &mut Vec<Acked>,
181 now: Instant, rtt_stats: &RttStats,
182) {
183 for pkt in packets.drain(..) {
184 on_packet_acked(r, bytes_in_flight, &pkt, now, rtt_stats);
185 }
186}
187
188fn on_packet_acked(
189 r: &mut Congestion, bytes_in_flight: usize, packet: &Acked, now: Instant,
190 rtt_stats: &RttStats,
191) {
192 let in_congestion_recovery = r.in_congestion_recovery(packet.time_sent);
193
194 if in_congestion_recovery {
195 r.prr.on_packet_acked(
196 packet.size,
197 bytes_in_flight,
198 r.ssthresh.get(),
199 r.max_datagram_size,
200 );
201
202 return;
203 }
204
205 if r.app_limited {
206 return;
207 }
208
209 if r.congestion_recovery_start_time.is_some() {
216 let new_lost = r.lost_count - r.cubic_state.prior.lost_count;
217
218 let rollback_threshold = (r.congestion_window / r.max_datagram_size) *
219 ROLLBACK_THRESHOLD_PERCENT /
220 100;
221
222 let rollback_threshold = rollback_threshold.max(MIN_ROLLBACK_THRESHOLD);
223
224 if new_lost < rollback_threshold {
225 let did_rollback = rollback(r);
226 if did_rollback {
227 return;
228 }
229 }
230 }
231
232 if r.congestion_window < r.ssthresh.get() {
233 r.bytes_acked_sl += packet.size;
236
237 if r.bytes_acked_sl >= r.max_datagram_size {
238 if r.hystart.in_css() {
239 r.congestion_window +=
240 r.hystart.css_cwnd_inc(r.max_datagram_size);
241 } else {
242 r.congestion_window += r.max_datagram_size;
243 }
244
245 r.bytes_acked_sl -= r.max_datagram_size;
246 }
247
248 if r.hystart.on_packet_acked(packet, rtt_stats.latest_rtt, now) {
249 r.ssthresh.update(r.congestion_window, true);
251 }
252 } else {
253 let ca_start_time;
255
256 if r.hystart.in_css() {
258 ca_start_time = r.hystart.css_start_time().unwrap();
259
260 if r.cubic_state.w_max == 0.0 {
262 r.cubic_state.w_max = r.congestion_window as f64;
263 r.cubic_state.k = 0.0;
264
265 r.cubic_state.w_est = r.congestion_window as f64;
266 r.cubic_state.alpha_aimd = ALPHA_AIMD;
267 }
268 } else {
269 match r.congestion_recovery_start_time {
270 Some(t) => ca_start_time = t,
271 None => {
272 ca_start_time = now;
275 r.congestion_recovery_start_time = Some(now);
276
277 r.cubic_state.w_max = r.congestion_window as f64;
278 r.cubic_state.k = 0.0;
279
280 r.cubic_state.w_est = r.congestion_window as f64;
281 r.cubic_state.alpha_aimd = ALPHA_AIMD;
282 },
283 }
284 }
285
286 let t = now.saturating_duration_since(ca_start_time);
287
288 let target = r
290 .cubic_state
291 .w_cubic(t + *rtt_stats.min_rtt, r.max_datagram_size);
292
293 let target = f64::max(target, r.congestion_window as f64);
295 let target = f64::min(target, r.congestion_window as f64 * 1.5);
296
297 let w_est_inc = r.cubic_state.w_est_inc(
299 packet.size,
300 r.congestion_window,
301 r.max_datagram_size,
302 );
303 r.cubic_state.w_est += w_est_inc;
304
305 if r.cubic_state.w_est >= r.cubic_state.w_max {
306 r.cubic_state.alpha_aimd = 1.0;
307 }
308
309 let mut cubic_cwnd = r.congestion_window;
310
311 if r.cubic_state.w_cubic(t, r.max_datagram_size) < r.cubic_state.w_est {
312 cubic_cwnd = cmp::max(cubic_cwnd, r.cubic_state.w_est as usize);
314 } else {
315 let cubic_inc =
317 r.max_datagram_size * (target as usize - cubic_cwnd) / cubic_cwnd;
318
319 cubic_cwnd += cubic_inc;
320 }
321
322 r.cubic_state.cwnd_inc += cubic_cwnd - r.congestion_window;
324
325 if r.cubic_state.cwnd_inc >= r.max_datagram_size {
326 r.congestion_window += r.max_datagram_size;
327 r.cubic_state.cwnd_inc -= r.max_datagram_size;
328 }
329 }
330}
331
332fn congestion_event(
333 r: &mut Congestion, bytes_in_flight: usize, _lost_bytes: usize,
334 largest_lost_pkt: &Sent, now: Instant,
335) {
336 let time_sent = largest_lost_pkt.time_sent;
337 let in_congestion_recovery = r.in_congestion_recovery(time_sent);
338
339 if !in_congestion_recovery {
342 r.congestion_recovery_start_time = Some(now);
343
344 if (r.congestion_window as f64) < r.cubic_state.w_max {
346 r.cubic_state.w_max =
347 r.congestion_window as f64 * (1.0 + BETA_CUBIC) / 2.0;
348 } else {
349 r.cubic_state.w_max = r.congestion_window as f64;
350 }
351
352 let ssthresh = (r.congestion_window as f64 * BETA_CUBIC) as usize;
353 let ssthresh =
354 cmp::max(ssthresh, r.max_datagram_size * MINIMUM_WINDOW_PACKETS);
355 r.ssthresh.update(ssthresh, r.hystart.in_css());
356 r.congestion_window = ssthresh;
357
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 };
364
365 r.cubic_state.cwnd_inc =
366 (r.cubic_state.cwnd_inc as f64 * BETA_CUBIC) as usize;
367
368 r.cubic_state.w_est = r.congestion_window as f64;
369 r.cubic_state.alpha_aimd = ALPHA_AIMD;
370
371 if r.hystart.in_css() {
372 r.hystart.congestion_event();
373 }
374
375 r.prr.congestion_event(bytes_in_flight);
376 }
377}
378
379fn checkpoint(r: &mut Congestion) {
380 r.cubic_state.prior.congestion_window = r.congestion_window;
381 r.cubic_state.prior.ssthresh = r.ssthresh.get();
382 r.cubic_state.prior.w_max = r.cubic_state.w_max;
383 r.cubic_state.prior.k = r.cubic_state.k;
384 r.cubic_state.prior.epoch_start = r.congestion_recovery_start_time;
385 r.cubic_state.prior.lost_count = r.lost_count;
386}
387
388fn rollback(r: &mut Congestion) -> bool {
389 if r.cubic_state.prior.congestion_window < r.cubic_state.prior.ssthresh {
391 return false;
392 }
393
394 if r.congestion_window >= r.cubic_state.prior.congestion_window {
395 return false;
396 }
397
398 r.congestion_window = r.cubic_state.prior.congestion_window;
399 r.ssthresh.update(r.cubic_state.prior.ssthresh, false);
400 r.cubic_state.w_max = r.cubic_state.prior.w_max;
401 r.cubic_state.k = r.cubic_state.prior.k;
402 r.congestion_recovery_start_time = r.cubic_state.prior.epoch_start;
403
404 true
405}
406
407fn has_custom_pacing() -> bool {
408 false
409}
410
411#[cfg(feature = "qlog")]
412fn state_str(r: &Congestion, now: Instant) -> &'static str {
413 reno::state_str(r, now)
414}
415
416fn debug_fmt(r: &Congestion, f: &mut std::fmt::Formatter) -> std::fmt::Result {
417 write!(
418 f,
419 "cubic={{ k={} w_max={} }} ",
420 r.cubic_state.k, r.cubic_state.w_max
421 )
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 use crate::CongestionControlAlgorithm;
429
430 use crate::recovery::congestion::hystart;
431 use crate::recovery::congestion::recovery::LegacyRecovery;
432 use crate::recovery::congestion::test_sender::TestSender;
433 use crate::recovery::RecoveryOps;
434
435 fn test_sender() -> TestSender {
436 TestSender::new(CongestionControlAlgorithm::CUBIC, false)
437 }
438
439 fn hystart_test_sender() -> TestSender {
440 TestSender::new(CongestionControlAlgorithm::CUBIC, true)
441 }
442
443 #[test]
444 fn cubic_init() {
445 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
446 cfg.set_cc_algorithm(CongestionControlAlgorithm::CUBIC);
447
448 let r = LegacyRecovery::new(&cfg);
449
450 assert!(r.cwnd() > 0);
451 assert_eq!(r.bytes_in_flight(), 0);
452 }
453
454 #[test]
455 fn cubic_slow_start() {
456 let mut sender = test_sender();
457 let size = sender.max_datagram_size;
458
459 for _ in 0..sender.initial_congestion_window_packets {
461 sender.send_packet(size);
462 }
463
464 let cwnd_prev = sender.congestion_window;
465
466 sender.ack_n_packets(1, size);
467
468 assert_eq!(sender.congestion_window, cwnd_prev + size);
470 }
471
472 #[test]
473 fn cubic_slow_start_multi_acks() {
474 let mut sender = test_sender();
475 let size = sender.max_datagram_size;
476
477 for _ in 0..sender.initial_congestion_window_packets {
479 sender.send_packet(size);
480 }
481
482 let cwnd_prev = sender.congestion_window;
483
484 sender.ack_n_packets(3, size);
485
486 assert_eq!(sender.congestion_window, cwnd_prev + size * 3);
488 }
489
490 #[test]
491 fn cubic_congestion_event() {
492 let mut sender = test_sender();
493 let size = sender.max_datagram_size;
494
495 sender.send_packet(size);
496
497 let cwnd_prev = sender.congestion_window;
498
499 sender.lose_n_packets(1, size, None);
500
501 assert_eq!(
504 cwnd_prev as f64 * BETA_CUBIC,
505 sender.congestion_window as f64
506 );
507 }
508
509 #[test]
510 fn cubic_congestion_avoidance() {
511 let mut sender = test_sender();
512 let size = sender.max_datagram_size;
513
514 let prev_cwnd = sender.congestion_window;
515
516 for _ in 0..sender.initial_congestion_window_packets {
518 sender.send_packet(size);
519 }
520
521 sender.lose_n_packets(1, size, None);
523
524 let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;
526 assert_eq!(sender.congestion_window, cur_cwnd);
527
528 let rtt = Duration::from_millis(100);
530 sender.update_rtt(rtt);
531 sender.advance_time(rtt);
533
534 for _ in 0..5 {
537 sender.ack_n_packets(1, size);
538 sender.advance_time(rtt);
539 }
540
541 assert_eq!(sender.congestion_window, cur_cwnd + size);
542 }
543
544 #[test]
545 fn cubic_hystart_css_to_ss() {
546 let mut sender = hystart_test_sender();
547 let size = sender.max_datagram_size;
548
549 let n_rtt_sample = hystart::N_RTT_SAMPLE;
551
552 let rtt_1st = Duration::from_millis(50);
553
554 let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;
555 sender.hystart.start_round(next_rnd);
556 for _ in 0..n_rtt_sample {
558 sender.send_packet(size);
559 }
560 sender.update_app_limited(false);
561
562 sender.advance_time(rtt_1st);
564 sender.update_rtt(rtt_1st);
565 sender.ack_n_packets(n_rtt_sample, size);
566
567 assert!(sender.hystart.css_start_time().is_none());
569
570 let mut rtt_2nd = Duration::from_millis(100);
572
573 sender.advance_time(rtt_2nd);
574
575 let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;
576 sender.hystart.start_round(next_rnd);
577 for _ in 0..n_rtt_sample {
579 sender.send_packet(size);
580 }
581 sender.update_app_limited(false);
582
583 let mut cwnd_prev = sender.congestion_window();
586
587 for _ in 0..n_rtt_sample {
588 cwnd_prev = sender.congestion_window();
589 sender.update_rtt(rtt_2nd);
590 sender.ack_n_packets(1, size);
591 rtt_2nd += rtt_2nd.saturating_add(Duration::from_millis(4));
593 }
594
595 assert!(sender.hystart.css_start_time().is_some());
597 assert_eq!(sender.congestion_window(), cwnd_prev + size);
598
599 let rtt_3rd = Duration::from_millis(80);
602 sender.advance_time(rtt_3rd);
603 cwnd_prev = sender.congestion_window();
604
605 let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;
606 sender.hystart.start_round(next_rnd);
607 for _ in 0..n_rtt_sample {
609 sender.send_packet(size);
610 }
611 sender.update_app_limited(false);
612
613 sender.update_rtt(rtt_3rd);
616 sender.ack_n_packets(n_rtt_sample, size);
617
618 assert!(sender.hystart.css_start_time().is_none());
620 assert_eq!(
621 sender.congestion_window(),
622 cwnd_prev +
623 size / hystart::CSS_GROWTH_DIVISOR * hystart::N_RTT_SAMPLE
624 );
625 }
626
627 #[test]
628 fn cubic_hystart_css_to_ca() {
629 let mut sender = hystart_test_sender();
630 let size = sender.max_datagram_size;
631
632 let n_rtt_sample = hystart::N_RTT_SAMPLE;
634
635 let rtt_1st = Duration::from_millis(50);
636
637 let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;
638 sender.hystart.start_round(next_rnd);
639 for _ in 0..n_rtt_sample {
641 sender.send_packet(size);
642 }
643 sender.update_app_limited(false);
644
645 sender.advance_time(rtt_1st);
647 sender.update_rtt(rtt_1st);
648 sender.ack_n_packets(n_rtt_sample, size);
649
650 assert!(sender.hystart.css_start_time().is_none());
652
653 let mut rtt_2nd = Duration::from_millis(100);
655 sender.advance_time(rtt_2nd);
656 let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;
658 sender.hystart.start_round(next_rnd);
659 for _ in 0..n_rtt_sample {
660 sender.send_packet(size);
661 }
662 sender.update_app_limited(false);
663
664 let mut cwnd_prev = sender.congestion_window();
667
668 for _ in 0..n_rtt_sample {
669 cwnd_prev = sender.congestion_window();
670 sender.update_rtt(rtt_2nd);
671 sender.ack_n_packets(1, size);
672 rtt_2nd += rtt_2nd.saturating_add(Duration::from_millis(4));
674 }
675
676 assert!(sender.hystart.css_start_time().is_some());
678 assert_eq!(sender.congestion_window(), cwnd_prev + size);
679
680 let rtt_css = Duration::from_millis(100);
682 sender.advance_time(rtt_css);
683
684 for _ in 0..hystart::CSS_ROUNDS {
685 let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;
687 sender.hystart.start_round(next_rnd);
688 for _ in 0..n_rtt_sample {
689 sender.send_packet(size);
690 }
691 sender.update_app_limited(false);
692
693 sender.update_rtt(rtt_css);
695 sender.ack_n_packets(n_rtt_sample, size);
696 }
697 assert_eq!(sender.congestion_window(), sender.ssthresh.get());
699 }
700
701 #[test]
702 fn cubic_spurious_congestion_event() {
703 let mut sender = test_sender();
704 let size = sender.max_datagram_size;
705
706 let prev_cwnd = sender.congestion_window();
707
708 for _ in 0..sender.initial_congestion_window_packets {
710 sender.send_packet(size);
711 }
712 sender.lose_n_packets(1, size, None);
713
714 let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;
716 assert_eq!(sender.congestion_window(), cur_cwnd);
717
718 let rtt = Duration::from_millis(100);
720 sender.update_rtt(rtt);
721
722 let acked = Acked {
723 pkt_num: 0,
724 time_sent: sender.time + rtt,
726 size,
727 delivered: 0,
728 delivered_time: sender.time,
729 first_sent_time: sender.time,
730 is_app_limited: false,
731 rtt: Duration::ZERO,
732 };
733
734 sender.inject_ack(acked, sender.time + rtt + Duration::from_millis(5));
736
737 assert_eq!(sender.congestion_window(), cur_cwnd);
739
740 sender.advance_time(rtt);
741
742 let prev_cwnd = sender.congestion_window();
743
744 sender.lose_n_packets(1, size, Some(sender.time));
745
746 let cur_cwnd = (cur_cwnd as f64 * BETA_CUBIC) as usize;
748 assert_eq!(sender.congestion_window(), cur_cwnd);
749
750 sender.advance_time(rtt + Duration::from_millis(5));
751
752 let acked = Acked {
753 pkt_num: 0,
754 time_sent: sender.time + rtt,
756 size,
757 delivered: 0,
758 delivered_time: sender.time,
759 first_sent_time: sender.time,
760 is_app_limited: false,
761 rtt: Duration::ZERO,
762 };
763
764 sender.inject_ack(acked, sender.time + rtt + Duration::from_millis(5));
766
767 assert_eq!(sender.congestion_window(), prev_cwnd);
769 }
770
771 #[test]
772 fn cubic_fast_convergence() {
773 let mut sender = test_sender();
774 let size = sender.max_datagram_size;
775
776 let prev_cwnd = sender.congestion_window;
777
778 for _ in 0..sender.initial_congestion_window_packets {
780 sender.send_packet(size);
781 }
782
783 sender.lose_n_packets(1, size, None);
785
786 let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;
788 assert_eq!(sender.congestion_window, cur_cwnd);
789
790 let rtt = Duration::from_millis(100);
792 sender.update_rtt(rtt);
793 sender.advance_time(rtt);
795
796 for _ in 0..5 {
799 sender.ack_n_packets(1, size);
800 sender.advance_time(rtt);
801 }
802
803 assert_eq!(sender.congestion_window, cur_cwnd + size);
804
805 let prev_cwnd = sender.congestion_window;
806
807 sender.lose_n_packets(1, size, None);
811
812 let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;
814 assert_eq!(sender.congestion_window, cur_cwnd);
815
816 assert_eq!(
818 sender.cubic_state.w_max,
819 prev_cwnd as f64 * (1.0 + BETA_CUBIC) / 2.0
820 );
821 }
822}