quiche/recovery/gcongestion/bbr2/
startup.rs

1// Copyright (c) 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Copyright (C) 2023, Cloudflare, Inc.
6// All rights reserved.
7//
8// Redistribution and use in source and binary forms, with or without
9// modification, are permitted provided that the following conditions are
10// met:
11//
12//     * Redistributions of source code must retain the above copyright notice,
13//       this list of conditions and the following disclaimer.
14//
15//     * Redistributions in binary form must reproduce the above copyright
16//       notice, this list of conditions and the following disclaimer in the
17//       documentation and/or other materials provided with the distribution.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
23// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31use 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            // 1.75 is less than the 2x CWND gain, but substantially more than
89            // 1.25x, the minimum bandwidth increase expected during
90            // STARTUP.
91            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        // TCP BBR always exits upon excessive losses. QUIC BBRv1 does not exit
101        // upon excessive losses, if enough bandwidth growth is observed or if the
102        // sample was app limited.
103        let check_for_excessive_loss = !congestion_event.last_packet_send_state.is_app_limited &&
104                !has_bandwidth_growth &&
105                // check for excessive loss only if not exiting for other reasons
106                !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        // Clear bandwidth_lo if it's set during STARTUP.
151        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        // At the end of a round trip. Check if loss is too high in this round.
170        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}