1use crate::config::RetryBackoff;
4use std::time::Duration;
5
6#[derive(Debug, Clone)]
7pub struct Backoff {
8 policy: RetryBackoff,
9 attempt: u32,
10 next: Duration,
11}
12
13impl Backoff {
14 pub fn new(policy: &RetryBackoff) -> Self {
15 Backoff {
16 policy: policy.clone(),
17 attempt: 0,
18 next: policy.initial,
19 }
20 }
21
22 pub fn next_delay(&mut self) -> Option<Duration> {
24 if self.policy.max_attempts != 0 && self.attempt >= self.policy.max_attempts {
25 return None;
26 }
27 self.attempt += 1;
28 let d = self.next.min(self.policy.max);
29 let scaled = self.next.as_secs_f64() * self.policy.multiplier;
30 self.next = Duration::from_secs_f64(scaled.min(self.policy.max.as_secs_f64()));
31 Some(d)
32 }
33
34 pub fn attempt(&self) -> u32 {
35 self.attempt
36 }
37
38 pub fn reset(&mut self) {
40 self.attempt = 0;
41 self.next = self.policy.initial;
42 }
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn grows_and_caps() {
51 let p = RetryBackoff {
52 initial: Duration::from_secs(1),
53 max: Duration::from_secs(5),
54 multiplier: 2.0,
55 max_attempts: 0,
56 };
57 let mut b = Backoff::new(&p);
58 let seq: Vec<u64> = (0..5).map(|_| b.next_delay().unwrap().as_secs()).collect();
59 assert_eq!(seq, vec![1, 2, 4, 5, 5]);
60 b.reset();
61 assert_eq!(b.next_delay().unwrap().as_secs(), 1);
62 }
63
64 #[test]
65 fn max_attempts_exhausts() {
66 let p = RetryBackoff {
67 initial: Duration::from_millis(10),
68 max: Duration::from_millis(10),
69 multiplier: 1.0,
70 max_attempts: 2,
71 };
72 let mut b = Backoff::new(&p);
73 assert!(b.next_delay().is_some());
74 assert!(b.next_delay().is_some());
75 assert!(b.next_delay().is_none());
76 assert_eq!(b.attempt(), 2);
77 }
78}