grafos_leasekit/
backoff.rs

1//! Deterministic exponential backoff.
2
3/// Exponential backoff with a configurable cap.
4///
5/// Each call to [`next_delay`](Backoff::next_delay) doubles the delay
6/// (starting from `initial_delay_secs`) up to `max_delay_secs`. Calling
7/// [`reset`](Backoff::reset) returns to the initial delay.
8///
9/// This is fully deterministic — no timers or randomness.
10#[derive(Debug, Clone)]
11pub struct Backoff {
12    initial_delay_secs: u64,
13    max_delay_secs: u64,
14    current_delay_secs: u64,
15}
16
17impl Backoff {
18    /// Create a new backoff starting at `initial_delay_secs`, capped at
19    /// `max_delay_secs`.
20    pub fn new(initial_delay_secs: u64, max_delay_secs: u64) -> Self {
21        let initial = initial_delay_secs.max(1);
22        Backoff {
23            initial_delay_secs: initial,
24            max_delay_secs: max_delay_secs.max(initial),
25            current_delay_secs: initial,
26        }
27    }
28
29    /// Return the next delay and advance the backoff.
30    pub fn next_delay(&mut self) -> u64 {
31        let delay = self.current_delay_secs;
32        self.current_delay_secs = self
33            .current_delay_secs
34            .saturating_mul(2)
35            .min(self.max_delay_secs);
36        delay
37    }
38
39    /// Reset backoff to the initial delay.
40    pub fn reset(&mut self) {
41        self.current_delay_secs = self.initial_delay_secs;
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn backoff_doubles_to_cap() {
51        let mut b = Backoff::new(1, 8);
52        assert_eq!(b.next_delay(), 1);
53        assert_eq!(b.next_delay(), 2);
54        assert_eq!(b.next_delay(), 4);
55        assert_eq!(b.next_delay(), 8);
56        assert_eq!(b.next_delay(), 8); // capped
57        assert_eq!(b.next_delay(), 8);
58    }
59
60    #[test]
61    fn backoff_reset() {
62        let mut b = Backoff::new(1, 16);
63        b.next_delay();
64        b.next_delay();
65        b.next_delay();
66        assert_eq!(b.next_delay(), 8);
67        b.reset();
68        assert_eq!(b.next_delay(), 1);
69    }
70
71    #[test]
72    fn backoff_min_initial_is_one() {
73        let b = Backoff::new(0, 10);
74        assert_eq!(b.initial_delay_secs, 1);
75    }
76
77    #[test]
78    fn backoff_max_at_least_initial() {
79        let b = Backoff::new(10, 3);
80        assert_eq!(b.max_delay_secs, 10);
81    }
82
83    #[test]
84    fn backoff_saturating_mul() {
85        let mut b = Backoff::new(1, u64::MAX);
86        // Step through many doublings; should never overflow
87        for _ in 0..100 {
88            let _ = b.next_delay();
89        }
90    }
91}