quiche/recovery/congestion/
reno.rs

1// 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//! Reno Congestion Control
28//!
29//! Note that Slow Start can use HyStart++ when enabled.
30
31use std::cmp;
32use std::time::Instant;
33
34use super::rtt::RttStats;
35use super::Acked;
36use super::Sent;
37
38use super::Congestion;
39use super::CongestionControlOps;
40use crate::recovery::LOSS_REDUCTION_FACTOR;
41use crate::recovery::MINIMUM_WINDOW_PACKETS;
42
43pub(crate) static RENO: CongestionControlOps = CongestionControlOps {
44    on_init,
45    on_packet_sent,
46    on_packets_acked,
47    congestion_event,
48    checkpoint,
49    rollback,
50    has_custom_pacing,
51    #[cfg(feature = "qlog")]
52    state_str,
53    debug_fmt,
54};
55
56pub fn on_init(_r: &mut Congestion) {}
57
58pub fn on_packet_sent(
59    _r: &mut Congestion, _sent_bytes: usize, _bytes_in_flight: usize,
60    _now: Instant,
61) {
62}
63
64fn on_packets_acked(
65    r: &mut Congestion, _bytes_in_flight: usize, packets: &mut Vec<Acked>,
66    now: Instant, rtt_stats: &RttStats,
67) {
68    for pkt in packets.drain(..) {
69        on_packet_acked(r, &pkt, now, rtt_stats);
70    }
71}
72
73fn on_packet_acked(
74    r: &mut Congestion, packet: &Acked, now: Instant, rtt_stats: &RttStats,
75) {
76    if r.in_congestion_recovery(packet.time_sent) {
77        return;
78    }
79
80    if r.app_limited {
81        return;
82    }
83
84    if r.congestion_window < r.ssthresh.get() {
85        // In Slow slart, bytes_acked_sl is used for counting
86        // acknowledged bytes.
87        r.bytes_acked_sl += packet.size;
88
89        if r.hystart.in_css() {
90            r.congestion_window += r.hystart.css_cwnd_inc(r.max_datagram_size);
91        } else {
92            r.congestion_window += r.max_datagram_size;
93        }
94
95        if r.hystart.on_packet_acked(packet, rtt_stats.latest_rtt, now) {
96            // Exit to congestion avoidance if CSS ends.
97            r.ssthresh.update(r.congestion_window, true);
98        }
99    } else {
100        // Congestion avoidance.
101        r.bytes_acked_ca += packet.size;
102
103        if r.bytes_acked_ca >= r.congestion_window {
104            r.bytes_acked_ca -= r.congestion_window;
105            r.congestion_window += r.max_datagram_size;
106        }
107    }
108}
109
110fn congestion_event(
111    r: &mut Congestion, _bytes_in_flight: usize, _lost_bytes: usize,
112    largest_lost_pkt: &Sent, now: Instant,
113) {
114    // Start a new congestion event if packet was sent after the
115    // start of the previous congestion recovery period.
116    let time_sent = largest_lost_pkt.time_sent;
117
118    if !r.in_congestion_recovery(time_sent) {
119        r.congestion_recovery_start_time = Some(now);
120
121        r.congestion_window =
122            (r.congestion_window as f64 * LOSS_REDUCTION_FACTOR) as usize;
123
124        r.congestion_window = cmp::max(
125            r.congestion_window,
126            r.max_datagram_size * MINIMUM_WINDOW_PACKETS,
127        );
128
129        r.bytes_acked_ca =
130            (r.congestion_window as f64 * LOSS_REDUCTION_FACTOR) as usize;
131
132        r.ssthresh.update(r.congestion_window, r.hystart.in_css());
133
134        if r.hystart.in_css() {
135            r.hystart.congestion_event();
136        }
137    }
138}
139
140fn checkpoint(_r: &mut Congestion) {}
141
142fn rollback(_r: &mut Congestion) -> bool {
143    true
144}
145
146fn has_custom_pacing() -> bool {
147    false
148}
149
150#[cfg(feature = "qlog")]
151pub fn state_str(r: &Congestion, now: Instant) -> &'static str {
152    if r.hystart.in_css() {
153        "conservative_slow_start"
154    } else if r.congestion_window < r.ssthresh.get() {
155        "slow_start"
156    } else if r.in_congestion_recovery(now) {
157        "recovery"
158    } else {
159        "congestion_avoidance"
160    }
161}
162
163fn debug_fmt(_r: &Congestion, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
164    Ok(())
165}
166
167#[cfg(test)]
168mod tests {
169    use crate::CongestionControlAlgorithm;
170
171    use super::*;
172
173    use crate::recovery::congestion::recovery::LegacyRecovery;
174    use crate::recovery::congestion::test_sender::TestSender;
175    use crate::recovery::RecoveryOps;
176
177    use std::time::Duration;
178
179    fn test_sender() -> TestSender {
180        TestSender::new(CongestionControlAlgorithm::Reno, false)
181    }
182
183    #[test]
184    fn reno_init() {
185        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
186        cfg.set_cc_algorithm(CongestionControlAlgorithm::Reno);
187
188        let r = LegacyRecovery::new(&cfg);
189
190        assert!(r.cwnd() > 0);
191        assert_eq!(r.bytes_in_flight(), 0);
192    }
193
194    #[test]
195    fn reno_slow_start() {
196        let mut sender = test_sender();
197        let size = sender.max_datagram_size;
198
199        // Send initcwnd full MSS packets to become no longer app limited
200        for _ in 0..sender.initial_congestion_window_packets {
201            sender.send_packet(size);
202        }
203
204        let cwnd_prev = sender.congestion_window;
205
206        sender.ack_n_packets(1, size);
207
208        // Check if cwnd increased by packet size (slow start).
209        assert_eq!(sender.congestion_window, cwnd_prev + size);
210    }
211
212    #[test]
213    fn reno_slow_start_multi_acks() {
214        let mut sender = test_sender();
215        let size = sender.max_datagram_size;
216
217        // Send initcwnd full MSS packets to become no longer app limited
218        for _ in 0..sender.initial_congestion_window_packets {
219            sender.send_packet(size);
220        }
221
222        let cwnd_prev = sender.congestion_window;
223
224        sender.ack_n_packets(3, size);
225
226        // Acked 3 packets.
227        assert_eq!(sender.congestion_window, cwnd_prev + size * 3);
228    }
229
230    #[test]
231    fn reno_congestion_event() {
232        let mut sender = test_sender();
233        let size = sender.max_datagram_size;
234
235        let prev_cwnd = sender.congestion_window;
236
237        sender.send_packet(size);
238        sender.lose_n_packets(1, size, None);
239
240        // In Reno, after congestion event, cwnd will be cut in half.
241        assert_eq!(prev_cwnd / 2, sender.congestion_window);
242    }
243
244    #[test]
245    fn reno_congestion_avoidance() {
246        let mut sender = test_sender();
247        let size = sender.max_datagram_size;
248
249        // Send initcwnd full MSS packets to become no longer app limited
250        for _ in 0..14 {
251            sender.send_packet(size);
252        }
253
254        let prev_cwnd = sender.congestion_window;
255
256        sender.lose_n_packets(1, size, None);
257
258        // After congestion event, cwnd will be reduced.
259        let cur_cwnd = (prev_cwnd as f64 * LOSS_REDUCTION_FACTOR) as usize;
260        assert_eq!(sender.congestion_window, cur_cwnd);
261
262        let rtt = Duration::from_millis(100);
263        sender.update_rtt(rtt);
264        sender.advance_time(2 * rtt);
265
266        sender.ack_n_packets(8, size);
267        // After acking more than cwnd, expect cwnd increased by MSS
268        assert_eq!(sender.congestion_window, cur_cwnd + size);
269    }
270}