Skip to main content

atrg_stream/
backoff.rs

1//! Exponential backoff with a configurable cap for reconnection.
2
3use std::time::Duration;
4
5/// Exponential backoff starting at 1 second and capping at 60 seconds.
6pub struct Backoff {
7    current: Duration,
8    max: Duration,
9    base: Duration,
10}
11
12impl Backoff {
13    /// Create a new backoff starting at 1s, capping at 60s.
14    pub fn new() -> Self {
15        Self {
16            current: Duration::from_secs(1),
17            max: Duration::from_secs(60),
18            base: Duration::from_secs(1),
19        }
20    }
21
22    /// Get the next backoff duration and advance the state.
23    pub fn next_delay(&mut self) -> Duration {
24        let duration = self.current;
25        self.current = (self.current * 2).min(self.max);
26        duration
27    }
28
29    /// Reset the backoff to the initial value (call on successful connection).
30    pub fn reset(&mut self) {
31        self.current = self.base;
32    }
33
34    /// Get the current backoff duration in milliseconds.
35    pub fn current_ms(&self) -> u64 {
36        self.current.as_millis() as u64
37    }
38}
39
40impl Default for Backoff {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn backoff_doubles() {
52        let mut b = Backoff::new();
53        assert_eq!(b.next_delay(), Duration::from_secs(1));
54        assert_eq!(b.next_delay(), Duration::from_secs(2));
55        assert_eq!(b.next_delay(), Duration::from_secs(4));
56        assert_eq!(b.next_delay(), Duration::from_secs(8));
57    }
58
59    #[test]
60    fn backoff_caps_at_60s() {
61        let mut b = Backoff::new();
62        for _ in 0..20 {
63            b.next_delay();
64        }
65        assert_eq!(b.next_delay(), Duration::from_secs(60));
66    }
67
68    #[test]
69    fn backoff_resets() {
70        let mut b = Backoff::new();
71        b.next_delay();
72        b.next_delay();
73        b.reset();
74        assert_eq!(b.next_delay(), Duration::from_secs(1));
75    }
76
77    #[test]
78    fn current_ms_reflects_state() {
79        let mut b = Backoff::new();
80        assert_eq!(b.current_ms(), 1000);
81        b.next_delay();
82        assert_eq!(b.current_ms(), 2000);
83    }
84
85    #[test]
86    fn default_is_same_as_new() {
87        let b = Backoff::default();
88        assert_eq!(b.current_ms(), 1000);
89    }
90}