Skip to main content

lexe_std/
backoff.rs

1use std::{cmp::min, time::Duration};
2
3const INITIAL_WAIT_MS: u64 = 250;
4const MAX_WAIT_MS: u64 = 32_000;
5const EXP_BASE: u64 = 2;
6
7/// Get a iterator of [`Duration`]s which can be passed into e.g.
8/// `tokio::time::sleep` to observe time-based exponential backoff.
9///
10/// ```ignore
11/// # use lexe_common::backoff;
12/// # #[tokio::test(start_paused = true)]
13/// # async fn backoff_example() {
14/// let mut backoff_durations = backoff::get_backoff_iter();
15/// for _ in 0..10 {
16///     tokio::time::sleep(backoff_durations.next().unwrap()).await;
17/// }
18/// # }
19/// ```
20pub fn get_backoff_iter() -> Backoff {
21    Backoff::default()
22}
23
24/// Like [`get_backoff_iter`], but allows specifying the initial wait time in
25/// milliseconds.
26pub fn iter_with_initial_wait_ms(initial_wait_ms: u64) -> Backoff {
27    // The initial wait being greater than the maximum wait won't cause any
28    // problems, but the programmer probably didn't intend this.
29    debug_assert!(initial_wait_ms <= MAX_WAIT_MS);
30
31    Backoff {
32        initial_wait_ms,
33        ..Backoff::default()
34    }
35}
36
37/// Exponential backoff iterator yielding [`Duration`]s.
38///
39/// ```text
40/// delay_i := min(2^i * initial_wait_ms, max_wait_ms)
41/// ```
42pub struct Backoff {
43    pub initial_wait_ms: u64,
44    pub max_wait_ms: u64,
45    pub attempt: u32,
46}
47
48impl Backoff {
49    /// Create a new backoff with the given initial and max wait durations.
50    pub fn new(initial_wait_ms: u64, max_wait_ms: u64) -> Self {
51        debug_assert!(initial_wait_ms <= max_wait_ms);
52        Self {
53            initial_wait_ms,
54            max_wait_ms,
55            attempt: 0,
56        }
57    }
58
59    /// Reset the backoff to the initial wait delay.
60    pub fn reset(&mut self) {
61        self.attempt = 0;
62    }
63
64    /// Get the next delay duration according to the exponential backoff.
65    pub fn next_delay(&mut self) -> Duration {
66        let factor = EXP_BASE.saturating_pow(self.attempt);
67        let wait_ms = self.initial_wait_ms.saturating_mul(factor);
68        let bounded_wait_ms = min(wait_ms, self.max_wait_ms);
69        self.attempt = self.attempt.saturating_add(1);
70        Duration::from_millis(bounded_wait_ms)
71    }
72}
73
74impl Default for Backoff {
75    fn default() -> Self {
76        Self {
77            initial_wait_ms: INITIAL_WAIT_MS,
78            max_wait_ms: MAX_WAIT_MS,
79            attempt: 0,
80        }
81    }
82}
83
84impl Iterator for Backoff {
85    type Item = Duration;
86
87    #[inline]
88    fn next(&mut self) -> Option<Self::Item> {
89        Some(self.next_delay())
90    }
91}
92
93#[cfg(test)]
94mod test {
95    use super::*;
96
97    #[test]
98    fn no_integer_overflow() {
99        let mut backoff_durations = get_backoff_iter();
100        for _ in 0..200 {
101            backoff_durations.next();
102        }
103    }
104}