Skip to main content

pb_mapper_core/
timeout.rs

1use std::time::Duration;
2
3#[derive(Debug, Clone, Copy)]
4pub struct RetryBackoff {
5    failures: u32,
6    min_delay: Duration,
7    max_delay: Duration,
8}
9
10impl Default for RetryBackoff {
11    fn default() -> Self {
12        Self::new(Duration::from_millis(100), Duration::from_secs(1))
13    }
14}
15
16impl RetryBackoff {
17    pub fn new(min_delay: Duration, max_delay: Duration) -> Self {
18        assert!(!min_delay.is_zero());
19        assert!(max_delay >= min_delay);
20        Self {
21            failures: 0,
22            min_delay,
23            max_delay,
24        }
25    }
26
27    pub fn failures(&self) -> u32 {
28        self.failures
29    }
30
31    pub fn reset(&mut self) {
32        self.failures = 0;
33    }
34
35    pub fn next_delay(&mut self) -> Duration {
36        let multiplier = 1_u32.checked_shl(self.failures.min(10)).unwrap_or(1);
37        self.failures = self.failures.saturating_add(1);
38        self.min_delay
39            .saturating_mul(multiplier)
40            .min(self.max_delay)
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn retry_backoff_caps_and_resets_without_exhausting() {
50        let mut backoff = RetryBackoff::new(Duration::from_millis(100), Duration::from_secs(1));
51
52        assert_eq!(backoff.next_delay(), Duration::from_millis(100));
53        assert_eq!(backoff.next_delay(), Duration::from_millis(200));
54        assert_eq!(backoff.next_delay(), Duration::from_millis(400));
55        assert_eq!(backoff.next_delay(), Duration::from_millis(800));
56        assert_eq!(backoff.next_delay(), Duration::from_secs(1));
57        for _ in 0..32 {
58            assert_eq!(backoff.next_delay(), Duration::from_secs(1));
59        }
60
61        assert!(backoff.failures() > 0);
62        backoff.reset();
63        assert_eq!(backoff.failures(), 0);
64        assert_eq!(backoff.next_delay(), Duration::from_millis(100));
65    }
66}