noq_proto/connection/
pacing.rs

1//! Pacing of packet transmissions.
2
3use crate::{Duration, Instant};
4
5use tracing::warn;
6
7/// A simple token-bucket pacer
8///
9/// The pacer's capacity is derived on a fraction of the congestion window
10/// which can be sent in regular intervals
11/// Once the bucket is empty, further transmission is blocked.
12/// The bucket refills at a rate slightly faster
13/// than one congestion window per RTT, as recommended in
14/// <https://tools.ietf.org/html/draft-ietf-quic-recovery-34#section-7.7>
15#[derive(Debug)]
16pub(super) struct Pacer {
17    capacity: u64,
18    last_window: u64,
19    last_mtu: u16,
20    tokens: u64,
21    max_bytes_per_second: Option<u64>,
22    prev: Instant,
23}
24
25impl Pacer {
26    /// Obtains a new [`Pacer`].
27    pub(super) fn new(
28        smoothed_rtt: Duration,
29        window: u64,
30        mtu: u16,
31        max_bytes_per_second: Option<u64>,
32        now: Instant,
33    ) -> Self {
34        let window = rate_limited_window(smoothed_rtt, window, max_bytes_per_second);
35        let capacity = optimal_capacity(smoothed_rtt, window, mtu);
36        Self {
37            capacity,
38            last_window: window,
39            last_mtu: mtu,
40            tokens: capacity,
41            max_bytes_per_second,
42            prev: now,
43        }
44    }
45
46    /// Obtains the `max_bytes_per_second` used when this [`Pacer`] was constructed.
47    pub(crate) fn max_bytes_per_second(&self) -> Option<u64> {
48        self.max_bytes_per_second
49    }
50
51    /// Record that a packet has been transmitted.
52    pub(super) fn on_transmit(&mut self, packet_length: u16) {
53        self.tokens = self.tokens.saturating_sub(packet_length.into())
54    }
55
56    /// Return how long we need to wait before sending `bytes_to_send`.
57    ///
58    /// If we can send a packet right away, this returns `None`. Otherwise, returns
59    /// `Some(d)`, where `d` is the duration after which this function should be called
60    /// again.
61    ///
62    /// The 5/4 ratio used here comes from the suggestion that N = 1.25 in the draft IETF
63    /// RFC for QUIC.
64    ///
65    /// `capacity` (bytes) and `pacing_rate` (bytes/s) are optional overrides supplied by
66    /// the congestion controller (e.g. BBRv3's `send_quantum` / `pacing_rate`). They take
67    /// precedence over the window-derived defaults, but are still subject to the static
68    /// `max_bytes_per_second` cap configured at construction time.
69    pub(super) fn delay(
70        &mut self,
71        smoothed_rtt: Duration,
72        bytes_to_send: u64,
73        mtu: u16,
74        window: u64,
75        now: Instant,
76        capacity: Option<u64>,
77        pacing_rate: Option<u64>,
78    ) -> Option<Duration> {
79        debug_assert_ne!(
80            window, 0,
81            "zero-sized congestion control window is nonsense"
82        );
83
84        let window = rate_limited_window(smoothed_rtt, window, self.max_bytes_per_second);
85        if window != self.last_window || mtu != self.last_mtu {
86            self.capacity = optimal_capacity(smoothed_rtt, window, mtu);
87
88            // Clamp the tokens
89            self.tokens = self.capacity.min(self.tokens);
90            self.last_window = window;
91            self.last_mtu = mtu;
92        }
93
94        if let Some(capacity) = capacity {
95            self.capacity = capacity;
96            self.tokens = self.capacity.min(self.tokens);
97        }
98
99        if let Some(pacing_rate) = pacing_rate
100            && bytes_to_send > self.capacity
101        {
102            // Pace at the controller-supplied rate; cap the static rate-limit through the
103            // `rate_limited_window` window above.
104            let capped_bytes_to_send = bytes_to_send.max(self.capacity);
105            let delay = Duration::from_secs_f64(capped_bytes_to_send as f64 / pacing_rate as f64);
106            return Some(delay);
107        }
108
109        // if we can already send a packet, there is no need for delay
110        if self.tokens >= bytes_to_send {
111            return None;
112        }
113
114        // we disable pacing for extremely large windows
115        if window > u64::from(u32::MAX) {
116            return None;
117        }
118
119        let window = window as u32;
120
121        let time_elapsed = now.checked_duration_since(self.prev).unwrap_or_else(|| {
122            warn!("received a timestamp early than a previous recorded time, ignoring");
123            Default::default()
124        });
125
126        if smoothed_rtt.as_nanos() == 0 {
127            return None;
128        }
129
130        let elapsed_rtts = time_elapsed.as_secs_f64() / smoothed_rtt.as_secs_f64();
131        let new_tokens = (window as f64 * 1.25 * elapsed_rtts).round() as u64;
132        self.tokens = self.tokens.saturating_add(new_tokens).min(self.capacity);
133
134        // In the unlikely event that we're getting polled faster than tokens are generated, ensure
135        // that `elapsed_rtts` can grow until we make progress.
136        if new_tokens > 0 {
137            self.prev = now;
138        }
139
140        // if we can already send a packet, there is no need for delay
141        if self.tokens >= bytes_to_send {
142            return None;
143        }
144
145        let unscaled_delay = smoothed_rtt
146            .checked_mul((bytes_to_send.max(self.capacity) - self.tokens) as _)
147            .unwrap_or(Duration::MAX)
148            / window;
149
150        // divisions come before multiplications to prevent overflow
151        // this is the time at which the pacing window becomes empty
152        Some((unscaled_delay / 5) * 4)
153    }
154}
155
156/// Calculates a pacer capacity for a certain window and RTT
157///
158/// The goal is to emit a burst (of size `capacity`) in timer intervals
159/// which compromise between
160/// - ideally distributing datagrams over time
161/// - constantly waking up the connection to produce additional datagrams
162///
163/// Too short burst intervals means we will never meet them since the timer
164/// accuracy in user-space is not high enough. If we miss the interval by more
165/// than 25%, we will lose that part of the congestion window since no additional
166/// tokens for the extra-elapsed time can be stored.
167///
168/// Too long burst intervals make pacing less effective.
169fn optimal_capacity(smoothed_rtt: Duration, window: u64, mtu: u16) -> u64 {
170    let rtt = smoothed_rtt.as_nanos().max(1);
171    let mtu = u64::from(mtu);
172
173    let target_capacity = ((window as u128 * TARGET_BURST_INTERVAL.as_nanos()) / rtt) as u64;
174    // Never restrict capacity below one MTU.
175    let max_capacity = Ord::max(
176        ((window as u128 * MAX_BURST_INTERVAL.as_nanos()) / rtt) as u64,
177        mtu,
178    );
179
180    // Batch the greater of `TARGET_BURST_INTERVAL` or `MIN_BURST_SIZE` worth of traffic at a
181    // time. To avoid inducing excessive latency, limit that result to at most `MAX_BURST_INTERVAL`
182    // worth of traffic.
183    Ord::min(
184        max_capacity,
185        target_capacity.clamp(MIN_BURST_SIZE * mtu, MAX_BURST_SIZE * mtu),
186    )
187}
188
189/// Clamps the window to limit the sending rate to `max_bytes_per_second`.
190///
191/// If `max_bytes_per_second` is `None`, the original window is returned.
192fn rate_limited_window(
193    smoothed_rtt: Duration,
194    window: u64,
195    max_bytes_per_second: Option<u64>,
196) -> u64 {
197    let Some(max_bytes_per_second) = max_bytes_per_second else {
198        return window;
199    };
200
201    let rate_window = max_bytes_per_second as f64 * smoothed_rtt.as_secs_f64();
202
203    // the pacer refills tokens at x1.25 speed, so we shrink the window to cancel out the speedup
204    // (otherwise the actual sending rate could be higher than `max_bytes_per_second`)
205    let adjusted_rate_window = (rate_window / 1.25).round();
206
207    Ord::min(window, Ord::max(adjusted_rate_window as u64, 1))
208}
209
210/// Period of traffic to batch together on a reasonably fast connection
211const TARGET_BURST_INTERVAL: Duration = Duration::from_millis(2);
212
213/// Maximum period of traffic to batch together on a slow connection
214///
215/// Takes precedence over [`MIN_BURST_SIZE`].
216const MAX_BURST_INTERVAL: Duration = Duration::from_millis(10);
217
218/// Minimum number of datagrams to batch together, so long as we won't have to wait for more than
219/// [`MAX_BURST_INTERVAL`]
220const MIN_BURST_SIZE: u64 = 10;
221
222/// Creating 256 packets took 1ms in a benchmark, so larger bursts don't make sense.
223const MAX_BURST_SIZE: u64 = 256;
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn does_not_panic_on_bad_instant() {
231        let old_instant = Instant::now();
232        let new_instant = old_instant + Duration::from_micros(15);
233        let rtt = Duration::from_micros(400);
234
235        assert!(
236            Pacer::new(rtt, 30000, 1500, None, new_instant)
237                .delay(
238                    Duration::from_micros(0),
239                    0,
240                    1500,
241                    1,
242                    old_instant,
243                    None,
244                    None
245                )
246                .is_none()
247        );
248        assert!(
249            Pacer::new(rtt, 30000, 1500, None, new_instant)
250                .delay(
251                    Duration::from_micros(0),
252                    1600,
253                    1500,
254                    1,
255                    old_instant,
256                    None,
257                    None
258                )
259                .is_none()
260        );
261        assert!(
262            Pacer::new(rtt, 30000, 1500, None, new_instant)
263                .delay(
264                    Duration::from_micros(0),
265                    1500,
266                    1500,
267                    3000,
268                    old_instant,
269                    None,
270                    None
271                )
272                .is_none()
273        );
274    }
275
276    #[test]
277    fn derives_initial_capacity() {
278        let window = 2_000_000;
279        let mtu = 1500;
280        let rtt = Duration::from_millis(50);
281        let now = Instant::now();
282
283        let pacer = Pacer::new(rtt, window, mtu, None, now);
284        assert_eq!(
285            pacer.capacity,
286            (window as u128 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64
287        );
288        assert_eq!(pacer.tokens, pacer.capacity);
289
290        let pacer = Pacer::new(Duration::from_millis(0), window, mtu, None, now);
291        assert_eq!(pacer.capacity, MAX_BURST_SIZE * mtu as u64);
292        assert_eq!(pacer.tokens, pacer.capacity);
293
294        let pacer = Pacer::new(rtt, 1, mtu, None, now);
295        assert_eq!(pacer.capacity, mtu as u64);
296        assert_eq!(pacer.tokens, pacer.capacity);
297    }
298
299    #[test]
300    fn adjusts_capacity() {
301        let window = 2_000_000;
302        let mtu = 1500;
303        let rtt = Duration::from_millis(50);
304        let now = Instant::now();
305
306        let mut pacer = Pacer::new(rtt, window, mtu, None, now);
307        assert_eq!(
308            pacer.capacity,
309            (window as u128 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64
310        );
311        assert_eq!(pacer.tokens, pacer.capacity);
312        let initial_tokens = pacer.tokens;
313
314        pacer.delay(rtt, mtu as u64, mtu, window * 2, now, None, None);
315        assert_eq!(
316            pacer.capacity,
317            (2 * window as u128 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64
318        );
319        assert_eq!(pacer.tokens, initial_tokens);
320
321        pacer.delay(rtt, mtu as u64, mtu, window / 2, now, None, None);
322        assert_eq!(
323            pacer.capacity,
324            (window as u128 / 2 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64
325        );
326        assert_eq!(pacer.tokens, initial_tokens / 2);
327
328        pacer.delay(rtt, mtu as u64, mtu * 2, window, now, None, None);
329        assert_eq!(
330            pacer.capacity,
331            (window as u128 * TARGET_BURST_INTERVAL.as_nanos() / rtt.as_nanos()) as u64
332        );
333
334        pacer.delay(rtt, mtu as u64, 20_000, window, now, None, None);
335        assert_eq!(pacer.capacity, 20_000_u64 * MIN_BURST_SIZE);
336    }
337
338    #[test]
339    fn computes_pause_correctly() {
340        let window = 2_000_000u64;
341        let mtu = 1000;
342        let rtt = Duration::from_millis(50);
343        let old_instant = Instant::now();
344
345        let mut pacer = Pacer::new(rtt, window, mtu, None, old_instant);
346        let packet_capacity = pacer.capacity / mtu as u64;
347
348        for _ in 0..packet_capacity {
349            assert_eq!(
350                pacer.delay(rtt, mtu as u64, mtu, window, old_instant, None, None),
351                None,
352                "When capacity is available packets should be sent immediately"
353            );
354
355            pacer.on_transmit(mtu);
356        }
357
358        let pace_duration = Duration::from_nanos((TARGET_BURST_INTERVAL.as_nanos() * 4 / 5) as u64);
359
360        let actual_delay = pacer
361            .delay(rtt, mtu as u64, mtu, window, old_instant, None, None)
362            .expect("Send must be delayed");
363
364        let diff = actual_delay.abs_diff(pace_duration);
365
366        // Allow up to 2ns difference due to rounding
367        assert!(
368            diff < Duration::from_nanos(2),
369            "expected ≈ {pace_duration:?}, got {actual_delay:?} (diff {diff:?})"
370        );
371        // Refill half of the tokens
372        assert_eq!(
373            pacer.delay(
374                rtt,
375                mtu as u64,
376                mtu,
377                window,
378                old_instant + pace_duration / 2,
379                None,
380                None,
381            ),
382            None
383        );
384        assert_eq!(pacer.tokens, pacer.capacity / 2);
385
386        for _ in 0..packet_capacity / 2 {
387            assert_eq!(
388                pacer.delay(rtt, mtu as u64, mtu, window, old_instant, None, None),
389                None,
390                "When capacity is available packets should be sent immediately"
391            );
392
393            pacer.on_transmit(mtu);
394        }
395
396        // Refill all capacity by waiting more than the expected duration
397        assert_eq!(
398            pacer.delay(
399                rtt,
400                mtu as u64,
401                mtu,
402                window,
403                old_instant + pace_duration * 3 / 2,
404                None,
405                None,
406            ),
407            None
408        );
409        assert_eq!(pacer.tokens, pacer.capacity);
410    }
411
412    #[test]
413    fn computes_pause_correctly_for_rate_limited() {
414        let window = 2_000_000u64;
415        let mtu = 1000;
416        let rtt = Duration::from_millis(50);
417        let old_instant = Instant::now();
418
419        let mut pacer = Pacer::new(rtt, window, mtu, Some(2_000), old_instant);
420        assert_eq!(
421            pacer.delay(rtt, 1_000, mtu, window, old_instant, None, None),
422            None,
423            "When capacity is available packets should be sent immediately"
424        );
425        pacer.on_transmit(mtu);
426
427        let actual_delay = pacer
428            .delay(rtt, 1_000, mtu, window, old_instant, None, None)
429            .expect("Send must be delayed");
430
431        let expected_delay = Duration::from_millis(500);
432        let diff = actual_delay.abs_diff(expected_delay);
433
434        // Allow up to 2ns difference due to rounding
435        assert!(
436            diff < Duration::from_nanos(2),
437            "expected ≈ {expected_delay:?}, got {actual_delay:?} (diff {diff:?})"
438        );
439
440        // Should be able to send after a while
441        let now = old_instant + expected_delay / 2;
442        assert_eq!(pacer.delay(rtt, 500, mtu, window, now, None, None), None);
443    }
444}