Skip to main content

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