Skip to main content

rustrade_integration/socket/
backoff.rs

1/// Calculates backoff duration between socket reconnection attempts.
2pub trait ReconnectBackoff {
3    /// Returns the backoff duration to wait before the next reconnection attempt.
4    fn reconnect_backoff(&mut self, reconnection_attempt: u32) -> std::time::Duration;
5}
6
7impl<F> ReconnectBackoff for F
8where
9    F: FnMut(u32) -> std::time::Duration,
10{
11    #[inline]
12    fn reconnect_backoff(&mut self, reconnection_attempt: u32) -> std::time::Duration {
13        self(reconnection_attempt)
14    }
15}
16
17/// Default exponential backoff strategy with a maximum delay cap.
18///
19/// Uses 2^n + 10ms formula, capped at 2^15ms.
20#[derive(Debug, Copy, Clone, PartialEq)]
21pub struct DefaultBackoff;
22
23impl ReconnectBackoff for DefaultBackoff {
24    fn reconnect_backoff(&mut self, reconnection_attempt: u32) -> std::time::Duration {
25        match reconnection_attempt {
26            0 => std::time::Duration::ZERO,
27            n => std::time::Duration::from_millis(2u64.pow(n.min(15)) + 10),
28        }
29    }
30}
31
32#[cfg(test)]
33#[allow(clippy::unwrap_used)] // Test code: panics on bad input are acceptable
34mod tests {
35    use super::*;
36    use std::time::Duration;
37
38    #[test]
39    fn test_default_backoff_zero() {
40        let mut backoff = DefaultBackoff;
41        assert_eq!(backoff.reconnect_backoff(0), Duration::ZERO);
42    }
43
44    #[test]
45    fn test_default_backoff_first_attempt() {
46        let mut backoff = DefaultBackoff;
47        assert_eq!(backoff.reconnect_backoff(1), Duration::from_millis(12));
48    }
49
50    #[test]
51    fn test_default_backoff_at_cap() {
52        let mut backoff = DefaultBackoff;
53        // 2^15 + 10 = 32768 + 10 = 32778
54        assert_eq!(backoff.reconnect_backoff(15), Duration::from_millis(32778));
55    }
56
57    #[test]
58    fn test_default_backoff_beyond_cap() {
59        let mut backoff = DefaultBackoff;
60        // n=16 should be capped at 2^15 + 10
61        assert_eq!(backoff.reconnect_backoff(16), Duration::from_millis(32778));
62    }
63
64    #[test]
65    fn test_default_backoff_u32_max() {
66        let mut backoff = DefaultBackoff;
67        assert_eq!(
68            backoff.reconnect_backoff(u32::MAX),
69            Duration::from_millis(32778)
70        );
71    }
72
73    #[test]
74    fn test_closure_backoff() {
75        let mut backoff = |n: u32| Duration::from_secs(n as u64);
76        assert_eq!(backoff.reconnect_backoff(5), Duration::from_secs(5));
77    }
78}