Skip to main content

rtc_interceptor/pacing/
pacer.rs

1//! The leaky bucket a pacer meters packets through.
2
3use std::time::{Duration, Instant};
4
5/// Smallest burst any pacer allows, in bits.
6///
7/// One maximum-sized packet: 1500 bytes, 12 000 bits. A burst below one packet would mean the
8/// common case never becomes affordable by waiting, so every packet would take the
9/// larger-than-burst path and the rate would stop being enforced at all.
10pub const MIN_BURST_BITS: f64 = 8.0 * 1500.0;
11
12/// A token bucket in bits, refilled from elapsed time.
13///
14/// Unlike upstream's `rate.Limiter`, nothing here reads a clock: the budget is a pure function of
15/// the instants handed in. That is what makes a release schedule reproducible in a test rather
16/// than merely eventually-correct — pion cannot assert its own schedule without a fake clock.
17#[derive(Debug, Clone)]
18pub struct Pacer {
19    /// Target rate in bits per second.
20    bitrate: f64,
21    /// The burst the caller chose, or `None` to derive it from the rate.
22    ///
23    /// Only the *intent* is stored, never the resulting burst: a burst chosen by the caller is a
24    /// property of the sender — how much it is willing to put on the wire at once — while a
25    /// derived one is a property of the rate and has to follow it. Keeping the computed value in
26    /// a field instead would lose which of the two it was the moment it was written, and
27    /// [`Pacer::set_target_bitrate`] could not tell whether it was allowed to replace it.
28    configured_burst_bits: Option<f64>,
29    /// Currently available budget, in bits.
30    budget_bits: f64,
31    /// When the budget was last brought up to date.
32    last_refill: Option<Instant>,
33}
34
35impl Pacer {
36    /// A bucket paced at `bits_per_second`, starting full.
37    ///
38    /// Starting full rather than empty lets a connection send immediately instead of waiting out
39    /// one burst's worth of accumulation, which is what upstream's limiter does too.
40    pub fn new(bits_per_second: f64) -> Self {
41        Self {
42            bitrate: bits_per_second.max(0.0),
43            configured_burst_bits: None,
44            budget_bits: Self::burst_for(bits_per_second),
45            last_refill: None,
46        }
47    }
48
49    /// Override the burst size, in bits.
50    ///
51    /// A burst set here is kept across rate changes. Deriving it from the rate is only a default;
52    /// once the caller has said how much it is willing to release at once, an estimator raising
53    /// the rate must not quietly widen that.
54    pub fn with_burst_bits(mut self, burst_bits: f64) -> Self {
55        self.configured_burst_bits = Some(burst_bits.max(MIN_BURST_BITS));
56        self.budget_bits = self.budget_bits.min(self.burst_bits());
57        self
58    }
59
60    /// The burst that goes with a rate: a tenth of a second's worth, floored at one packet.
61    fn burst_for(bits_per_second: f64) -> f64 {
62        (bits_per_second / 10.0).max(MIN_BURST_BITS)
63    }
64
65    /// The rate currently being paced at, in bits per second.
66    pub fn target_bitrate(&self) -> f64 {
67        self.bitrate
68    }
69
70    /// Change the rate.
71    ///
72    /// Synchronous and immediate, because this is what a bandwidth estimator drives: it computes
73    /// a new target and the very next release must respect it. Accumulated budget is kept but
74    /// clamped to the new burst, so lowering the rate cannot leave a large budget behind that
75    /// would let a burst out at the old rate.
76    ///
77    /// A burst set through [`Pacer::with_burst_bits`] survives this; only a burst that was derived
78    /// from the rate follows the rate.
79    pub fn set_target_bitrate(&mut self, bits_per_second: f64) {
80        self.bitrate = bits_per_second.max(0.0);
81        self.budget_bits = self.budget_bits.min(self.burst_bits());
82    }
83
84    /// Bring the budget up to `now`.
85    pub fn refill(&mut self, now: Instant) {
86        if let Some(last) = self.last_refill {
87            let elapsed = now.saturating_duration_since(last).as_secs_f64();
88            self.budget_bits = (self.budget_bits + elapsed * self.bitrate).min(self.burst_bits());
89        }
90        self.last_refill = Some(now);
91    }
92
93    /// Whether `bits` can be sent now.
94    pub fn can_afford(&self, bits: f64) -> bool {
95        self.budget_bits >= bits
96    }
97
98    /// Spend `bits` from the budget.
99    ///
100    /// The budget is allowed to go negative on a packet larger than a full burst; otherwise such
101    /// a packet could never be sent at all, and a stalled queue is worse than a momentary
102    /// overshoot.
103    pub fn consume(&mut self, bits: f64) {
104        self.budget_bits -= bits;
105    }
106
107    /// How long until `bits` becomes affordable.
108    ///
109    /// `Duration::ZERO` when it already is. At a zero rate nothing ever becomes affordable, which
110    /// the caller must treat as "no deadline" rather than waiting forever.
111    pub fn time_until_affordable(&self, bits: f64) -> Option<Duration> {
112        if self.budget_bits >= bits {
113            return Some(Duration::ZERO);
114        }
115        if self.bitrate <= 0.0 {
116            return None;
117        }
118        Some(Duration::from_secs_f64(
119            (bits - self.budget_bits) / self.bitrate,
120        ))
121    }
122
123    /// The instant `bits` becomes affordable, given the last refill.
124    pub fn affordable_at(&self, bits: f64) -> Option<Instant> {
125        let last = self.last_refill?;
126        self.time_until_affordable(bits).map(|wait| last + wait)
127    }
128
129    /// The available budget, in bits.
130    pub fn budget_bits(&self) -> f64 {
131        self.budget_bits
132    }
133
134    /// Whether `bits` may be released now.
135    ///
136    /// A packet larger than a full burst can never be *afforded* — the budget caps at the burst —
137    /// so it is released once the budget has refilled to that cap, which is as long as waiting can
138    /// possibly help. Gating on a full budget rather than releasing unconditionally is what keeps
139    /// a run of oversized packets paced: each drives the budget negative and the next must wait
140    /// for it to recover, so they leave at the target rate instead of all at once.
141    pub fn can_release(&self, bits: f64) -> bool {
142        let burst_bits = self.burst_bits();
143        if bits > burst_bits {
144            return self.budget_bits >= burst_bits;
145        }
146        self.budget_bits >= bits
147    }
148
149    /// The instant `bits` may be released, per [`Pacer::can_release`].
150    pub fn releasable_at(&self, bits: f64) -> Option<Instant> {
151        self.affordable_at(bits.min(self.burst_bits()))
152    }
153
154    /// The maximum the budget can accumulate to, in bits.
155    ///
156    /// Anything larger than this can never be afforded by waiting, however long the wait — the
157    /// budget caps here — which is why release is asked for through [`Pacer::can_release`] rather
158    /// than [`Pacer::can_afford`].
159    pub fn burst_bits(&self) -> f64 {
160        self.configured_burst_bits
161            .unwrap_or_else(|| Self::burst_for(self.bitrate))
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    /// 1 Mb/s, with a burst big enough to be interesting but small enough to exhaust.
170    fn pacer() -> Pacer {
171        Pacer::new(1_000_000.0).with_burst_bits(MIN_BURST_BITS)
172    }
173
174    #[test]
175    fn a_new_bucket_starts_full() {
176        let pacer = pacer();
177        assert_eq!(MIN_BURST_BITS, pacer.budget_bits());
178        assert!(pacer.can_afford(MIN_BURST_BITS));
179    }
180
181    #[test]
182    fn spending_reduces_the_budget() {
183        let mut pacer = pacer();
184        pacer.consume(1000.0);
185        assert_eq!(MIN_BURST_BITS - 1000.0, pacer.budget_bits());
186    }
187
188    /// The budget is a function of elapsed time, not of how often refill happens to be called.
189    #[test]
190    fn the_budget_refills_from_elapsed_time() {
191        let now = Instant::now();
192        let mut pacer = pacer();
193        pacer.refill(now);
194        pacer.consume(pacer.budget_bits());
195        assert_eq!(0.0, pacer.budget_bits());
196
197        // At 1 Mb/s, 10 ms is 10 000 bits.
198        pacer.refill(now + Duration::from_millis(10));
199        assert!(
200            (pacer.budget_bits() - 10_000.0).abs() < 1.0,
201            "{}",
202            pacer.budget_bits()
203        );
204    }
205
206    #[test]
207    fn refilling_in_steps_matches_refilling_at_once() {
208        let now = Instant::now();
209
210        let mut stepped = pacer();
211        stepped.refill(now);
212        stepped.consume(stepped.budget_bits());
213        for step in 1..=10 {
214            stepped.refill(now + Duration::from_millis(step));
215        }
216
217        let mut at_once = pacer();
218        at_once.refill(now);
219        at_once.consume(at_once.budget_bits());
220        at_once.refill(now + Duration::from_millis(10));
221
222        assert!((stepped.budget_bits() - at_once.budget_bits()).abs() < 1.0);
223    }
224
225    /// The bucket cannot fill beyond its burst, or an idle connection would accumulate an
226    /// unbounded allowance and release it all at once the moment it resumed.
227    #[test]
228    fn the_budget_is_capped_at_the_burst() {
229        let now = Instant::now();
230        let mut pacer = pacer();
231        pacer.refill(now);
232        pacer.refill(now + Duration::from_secs(60));
233
234        assert_eq!(MIN_BURST_BITS, pacer.budget_bits());
235    }
236
237    #[test]
238    fn the_time_until_affordable_is_zero_when_it_already_is() {
239        let pacer = pacer();
240        assert_eq!(Some(Duration::ZERO), pacer.time_until_affordable(100.0));
241    }
242
243    #[test]
244    fn the_time_until_affordable_scales_with_the_shortfall() {
245        let now = Instant::now();
246        let mut pacer = pacer();
247        pacer.refill(now);
248        pacer.consume(pacer.budget_bits());
249
250        // 10 000 bits short at 1 Mb/s is 10 ms.
251        let wait = pacer
252            .time_until_affordable(10_000.0)
253            .expect("a finite wait");
254        assert!((wait.as_secs_f64() - 0.010).abs() < 0.0005, "got {wait:?}");
255        assert_eq!(Some(now + wait), pacer.affordable_at(10_000.0));
256    }
257
258    /// At a zero rate nothing ever becomes affordable. Reporting a deadline anyway would have the
259    /// driver wake for a release that can never happen.
260    #[test]
261    fn nothing_becomes_affordable_at_a_zero_rate() {
262        let now = Instant::now();
263        let mut pacer = Pacer::new(0.0);
264        pacer.refill(now);
265        pacer.consume(pacer.budget_bits());
266
267        assert_eq!(None, pacer.time_until_affordable(1000.0));
268        assert_eq!(None, pacer.affordable_at(1000.0));
269    }
270
271    #[test]
272    fn changing_the_rate_changes_how_fast_the_budget_refills() {
273        let now = Instant::now();
274        // A derived burst, so the burst grows with the rate and does not cap what is asserted.
275        let mut pacer = Pacer::new(1_000_000.0);
276        pacer.refill(now);
277        pacer.consume(pacer.budget_bits());
278
279        pacer.set_target_bitrate(2_000_000.0);
280        pacer.refill(now + Duration::from_millis(10));
281
282        // Twice the rate, twice the budget for the same elapsed time.
283        assert!(
284            (pacer.budget_bits() - 20_000.0).abs() < 1.0,
285            "{}",
286            pacer.budget_bits()
287        );
288        assert_eq!(2_000_000.0, pacer.target_bitrate());
289    }
290
291    /// Lowering the rate must not leave a budget accumulated at the old one, or the first thing
292    /// after a rate cut would be a burst at the rate that was just abandoned.
293    #[test]
294    fn lowering_the_rate_clamps_the_budget_to_the_new_burst() {
295        let now = Instant::now();
296        let mut pacer = Pacer::new(100_000_000.0);
297        pacer.refill(now);
298        let before = pacer.budget_bits();
299
300        pacer.set_target_bitrate(1000.0);
301
302        assert!(pacer.budget_bits() < before);
303        assert_eq!(
304            MIN_BURST_BITS,
305            pacer.budget_bits(),
306            "clamped to the floor burst"
307        );
308    }
309
310    #[test]
311    fn a_negative_rate_is_treated_as_zero() {
312        let mut pacer = Pacer::new(-5.0);
313        assert_eq!(0.0, pacer.target_bitrate());
314        pacer.set_target_bitrate(-1.0);
315        assert_eq!(0.0, pacer.target_bitrate());
316    }
317
318    /// A burst the caller chose is not a function of the rate. Losing it on the first estimator
319    /// update would silently change how bursty the sender is, long after it was configured.
320    #[test]
321    fn a_configured_burst_survives_a_rate_change() {
322        let mut pacer = Pacer::new(1_000_000.0).with_burst_bits(MIN_BURST_BITS);
323        assert_eq!(MIN_BURST_BITS, pacer.burst_bits());
324
325        pacer.set_target_bitrate(100_000_000.0);
326
327        assert_eq!(
328            MIN_BURST_BITS,
329            pacer.burst_bits(),
330            "a rate change must not widen a burst the caller set"
331        );
332        assert_eq!(100_000_000.0, pacer.target_bitrate());
333    }
334
335    /// The derived default still tracks the rate — pinning is opt-in, not the new default.
336    #[test]
337    fn a_derived_burst_follows_the_rate() {
338        let mut pacer = Pacer::new(1_000_000.0);
339        assert_eq!(100_000.0, pacer.burst_bits());
340
341        pacer.set_target_bitrate(2_000_000.0);
342
343        assert_eq!(200_000.0, pacer.burst_bits());
344    }
345
346    /// A packet larger than the burst waits for a full budget rather than going unconditionally,
347    /// so a run of them leaves at the target rate instead of all at once.
348    #[test]
349    fn an_oversized_packet_waits_for_a_full_budget() {
350        let now = Instant::now();
351        let mut pacer = pacer();
352        pacer.refill(now);
353        let oversized = MIN_BURST_BITS * 2.0;
354
355        assert!(pacer.can_release(oversized), "a full budget releases it");
356        pacer.consume(oversized);
357        assert!(
358            !pacer.can_release(oversized),
359            "the next one waits for the debt to be repaid"
360        );
361
362        // The wait is the debt at the target rate, which is what paces a run of them.
363        let at = pacer.releasable_at(oversized).expect("a finite wait");
364        assert_eq!(
365            Some(Duration::from_secs_f64(oversized / 1_000_000.0)),
366            pacer.time_until_affordable(MIN_BURST_BITS)
367        );
368        pacer.refill(at);
369        assert!(pacer.can_release(oversized), "and then it goes");
370    }
371
372    /// A packet larger than a full burst must still get out. Refusing it would stall the queue
373    /// permanently behind a packet that can never become affordable.
374    #[test]
375    fn a_packet_larger_than_the_burst_can_still_be_sent() {
376        let now = Instant::now();
377        let mut pacer = pacer();
378        pacer.refill(now);
379
380        let oversized = MIN_BURST_BITS * 2.0;
381        assert!(!pacer.can_afford(oversized));
382
383        // Even after waiting, the budget caps at the burst — so the caller has to spend anyway.
384        pacer.refill(now + Duration::from_secs(10));
385        assert!(!pacer.can_afford(oversized));
386
387        pacer.consume(oversized);
388        assert!(
389            pacer.budget_bits() < 0.0,
390            "the overshoot is paid back over time"
391        );
392
393        pacer.refill(now + Duration::from_secs(20));
394        assert_eq!(
395            MIN_BURST_BITS,
396            pacer.budget_bits(),
397            "and recovers to the burst"
398        );
399    }
400}