1use std::{cmp::min, time::Duration};
2
3const INITIAL_WAIT_MS: u64 = 250;
4const MAX_WAIT_MS: u64 = 32_000;
5const EXP_BASE: u64 = 2;
6
7pub fn get_backoff_iter() -> Backoff {
21 Backoff::default()
22}
23
24pub fn iter_with_initial_wait_ms(initial_wait_ms: u64) -> Backoff {
27 debug_assert!(initial_wait_ms <= MAX_WAIT_MS);
30
31 Backoff {
32 initial_wait_ms,
33 ..Backoff::default()
34 }
35}
36
37pub struct Backoff {
43 pub initial_wait_ms: u64,
44 pub max_wait_ms: u64,
45 pub attempt: u32,
46}
47
48impl Backoff {
49 pub fn new(initial_wait_ms: u64, max_wait_ms: u64) -> Self {
51 debug_assert!(initial_wait_ms <= max_wait_ms);
52 Self {
53 initial_wait_ms,
54 max_wait_ms,
55 attempt: 0,
56 }
57 }
58
59 pub fn reset(&mut self) {
61 self.attempt = 0;
62 }
63
64 pub fn next_delay(&mut self) -> Duration {
66 let factor = EXP_BASE.saturating_pow(self.attempt);
67 let wait_ms = self.initial_wait_ms.saturating_mul(factor);
68 let bounded_wait_ms = min(wait_ms, self.max_wait_ms);
69 self.attempt = self.attempt.saturating_add(1);
70 Duration::from_millis(bounded_wait_ms)
71 }
72}
73
74impl Default for Backoff {
75 fn default() -> Self {
76 Self {
77 initial_wait_ms: INITIAL_WAIT_MS,
78 max_wait_ms: MAX_WAIT_MS,
79 attempt: 0,
80 }
81 }
82}
83
84impl Iterator for Backoff {
85 type Item = Duration;
86
87 #[inline]
88 fn next(&mut self) -> Option<Self::Item> {
89 Some(self.next_delay())
90 }
91}
92
93#[cfg(test)]
94mod test {
95 use super::*;
96
97 #[test]
98 fn no_integer_overflow() {
99 let mut backoff_durations = get_backoff_iter();
100 for _ in 0..200 {
101 backoff_durations.next();
102 }
103 }
104}