quiche/recovery/gcongestion/bbr2/
startup.rs1use std::time::Instant;
32
33use crate::recovery::gcongestion::bbr2::Params;
34use crate::recovery::RecoveryStats;
35use crate::recovery::StartupExit;
36use crate::recovery::StartupExitReason;
37
38use super::mode::Mode;
39use super::mode::ModeImpl;
40use super::network_model::BBRv2NetworkModel;
41use super::Acked;
42use super::BBRv2CongestionEvent;
43use super::Limits;
44use super::Lost;
45
46#[derive(Debug)]
47pub(super) struct Startup {
48 pub(super) model: BBRv2NetworkModel,
49}
50
51impl ModeImpl for Startup {
52 #[cfg(feature = "qlog")]
53 fn state_str(&self) -> &'static str {
54 "bbr_startup"
55 }
56
57 fn is_probing_for_bandwidth(&self) -> bool {
58 true
59 }
60
61 fn on_congestion_event(
62 mut self, _prior_in_flight: usize, event_time: std::time::Instant,
63 _acked_packets: &[Acked], _lost_packets: &[Lost],
64 congestion_event: &mut BBRv2CongestionEvent,
65 _target_bytes_inflight: usize, params: &Params,
66 recovery_stats: &mut RecoveryStats, cwnd: usize,
67 ) -> Mode {
68 if self.model.full_bandwidth_reached() {
69 return self.into_drain(event_time, Some(congestion_event), params);
70 }
71
72 if !congestion_event.end_of_round_trip {
73 return Mode::Startup(self);
74 }
75
76 let has_bandwidth_growth =
77 self.model.has_bandwidth_growth(congestion_event, params);
78 if self.model.full_bandwidth_reached() {
79 recovery_stats.set_startup_exit(StartupExit::new(
80 cwnd,
81 StartupExitReason::BandwidthPlateau,
82 ));
83 }
84
85 let check_persisten_queue =
86 params.max_startup_queue_rounds > 0 && !has_bandwidth_growth;
87 if check_persisten_queue {
88 self.model.check_persistent_queue(1.75, params);
92 if self.model.full_bandwidth_reached() {
93 recovery_stats.set_startup_exit(StartupExit::new(
94 cwnd,
95 StartupExitReason::PersistentQueue,
96 ));
97 }
98 }
99
100 let check_for_excessive_loss = !congestion_event.last_packet_send_state.is_app_limited &&
104 !has_bandwidth_growth &&
105 !self.model.full_bandwidth_reached();
107
108 if check_for_excessive_loss {
109 self.check_excessive_losses(congestion_event, params);
110
111 if self.model.full_bandwidth_reached() {
112 recovery_stats.set_startup_exit(StartupExit::new(
113 cwnd,
114 StartupExitReason::Loss,
115 ));
116 }
117 }
118
119 if self.model.full_bandwidth_reached() {
120 self.into_drain(event_time, Some(congestion_event), params)
121 } else {
122 Mode::Startup(self)
123 }
124 }
125
126 fn get_cwnd_limits(&self, _params: &Params) -> Limits<usize> {
127 Limits {
128 lo: 0,
129 hi: self.model.inflight_lo(),
130 }
131 }
132
133 fn on_exit_quiescence(
134 self, _now: Instant, _quiescence_start_time: Instant, _params: &Params,
135 ) -> Mode {
136 Mode::Startup(self)
137 }
138
139 fn enter(
140 &mut self, _now: Instant,
141 _congestion_event: Option<&BBRv2CongestionEvent>, _params: &Params,
142 ) {
143 unreachable!("Enter should never be called for startup")
144 }
145
146 fn leave(
147 &mut self, _now: Instant,
148 _congestion_event: Option<&BBRv2CongestionEvent>,
149 ) {
150 self.model.clear_bandwidth_lo();
152 }
153}
154
155impl Startup {
156 fn into_drain(
157 mut self, now: Instant, congestion_event: Option<&BBRv2CongestionEvent>,
158 params: &Params,
159 ) -> Mode {
160 self.leave(now, congestion_event);
161 let mut next_mode = Mode::drain(self.model);
162 next_mode.enter(now, congestion_event, params);
163 next_mode
164 }
165
166 fn check_excessive_losses(
167 &mut self, congestion_event: &mut BBRv2CongestionEvent, params: &Params,
168 ) {
169 if self.model.is_inflight_too_high(
171 congestion_event,
172 params.startup_full_loss_count,
173 params,
174 ) {
175 let mut new_inflight_hi = self.model.bdp0();
176
177 if params.startup_loss_exit_use_max_delivered_for_inflight_hi {
178 new_inflight_hi = new_inflight_hi
179 .max(self.model.max_bytes_delivered_in_round());
180 }
181
182 self.model.set_inflight_hi(new_inflight_hi);
183 self.model.set_full_bandwidth_reached();
184 }
185 }
186}