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: 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                Some(self.model.max_bandwidth()),
82                StartupExitReason::BandwidthPlateau,
83            ));
84        }
85
86        let check_persisten_queue =
87            params.max_startup_queue_rounds > 0 && !has_bandwidth_growth;
88        if check_persisten_queue {
89            // https://github.com/google/quiche/blob/27eca0257490df89d2bd2c2a8bcea15565e7831c/quiche/quic/core/congestion_control/bbr2_startup.cc#L60-L62
90            // 1.75 is less than the 2x CWND gain, but substantially more than
91            // 1.25x, the minimum bandwidth increase expected during
92            // STARTUP.
93            self.model.check_persistent_queue(1.75, params);
94            if self.model.full_bandwidth_reached() {
95                recovery_stats.set_startup_exit(StartupExit::new(
96                    cwnd,
97                    Some(self.model.max_bandwidth()),
98                    StartupExitReason::PersistentQueue,
99                ));
100            }
101        }
102
103        // TCP BBR always exits upon excessive losses. QUIC BBRv1 does not exit
104        // upon excessive losses, if enough bandwidth growth is observed or if the
105        // sample was app limited.
106        let check_for_excessive_loss = !congestion_event.last_packet_send_state.is_app_limited &&
107                !has_bandwidth_growth &&
108                // check for excessive loss only if not exiting for other reasons
109                !self.model.full_bandwidth_reached();
110
111        if check_for_excessive_loss {
112            self.check_excessive_losses(congestion_event, params);
113
114            if self.model.full_bandwidth_reached() {
115                recovery_stats.set_startup_exit(StartupExit::new(
116                    cwnd,
117                    Some(self.model.max_bandwidth()),
118                    StartupExitReason::Loss,
119                ));
120            }
121        }
122
123        if self.model.full_bandwidth_reached() {
124            self.into_drain(event_time, Some(congestion_event), params)
125        } else {
126            Mode::Startup(self)
127        }
128    }
129
130    fn get_cwnd_limits(&self, _params: &Params) -> Limits<usize> {
131        Limits {
132            lo: 0,
133            hi: self.model.inflight_lo(),
134        }
135    }
136
137    fn on_exit_quiescence(
138        self, _now: Instant, _quiescence_start_time: Instant, _params: &Params,
139    ) -> Mode {
140        Mode::Startup(self)
141    }
142
143    fn enter(
144        &mut self, _now: Instant,
145        _congestion_event: Option<&BBRv2CongestionEvent>, _params: &Params,
146    ) {
147        unreachable!("Enter should never be called for startup")
148    }
149
150    fn leave(
151        &mut self, _now: Instant,
152        _congestion_event: Option<&BBRv2CongestionEvent>,
153    ) {
154        // Clear bandwidth_lo if it's set during STARTUP.
155        self.model.clear_bandwidth_lo();
156    }
157}
158
159impl Startup {
160    fn into_drain(
161        mut self, now: Instant, congestion_event: Option<&BBRv2CongestionEvent>,
162        params: &Params,
163    ) -> Mode {
164        self.leave(now, congestion_event);
165        let mut next_mode = Mode::drain(self.model);
166        next_mode.enter(now, congestion_event, params);
167        next_mode
168    }
169
170    fn check_excessive_losses(
171        &mut self, congestion_event: &mut BBRv2CongestionEvent, params: &Params,
172    ) {
173        // At the end of a round trip. Check if loss is too high in this round.
174        if self.model.is_inflight_too_high(
175            congestion_event,
176            params.startup_full_loss_count,
177            params,
178        ) {
179            let mut new_inflight_hi = self.model.bdp0();
180
181            if params.startup_loss_exit_use_max_delivered_for_inflight_hi {
182                new_inflight_hi = new_inflight_hi
183                    .max(self.model.max_bytes_delivered_in_round());
184            }
185
186            self.model.set_inflight_hi(new_inflight_hi);
187            self.model.set_full_bandwidth_reached();
188        }
189    }
190}