Skip to main content

lexe_std/
backoff.rs

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