1use std::{iter::Iterator, time::Duration};
6
7use rand::Rng as _;
8
9#[derive(Debug, Clone)]
38pub struct ExponentialBackoff {
39 next: Duration,
40 factor: f64,
41 max_delay: Duration,
42 max_jitter: Duration,
43}
44
45impl ExponentialBackoff {
46 pub fn new(initial_delay: Duration, max_delay: Duration) -> ExponentialBackoff {
49 ExponentialBackoff {
50 next: initial_delay,
51 factor: 1.2,
52 max_delay,
53 max_jitter: initial_delay,
54 }
55 }
56
57 pub fn factor(mut self, factor: f64) -> ExponentialBackoff {
62 self.factor = factor;
63 self
64 }
65
66 pub fn max_jitter(mut self, max_jitter: Duration) -> ExponentialBackoff {
70 self.max_jitter = max_jitter;
71 self
72 }
73}
74
75impl Iterator for ExponentialBackoff {
76 type Item = Duration;
77
78 fn next(&mut self) -> Option<Duration> {
80 let current = self.next;
81
82 let jitter = if self.max_jitter.is_zero() {
83 Duration::ZERO
84 } else {
85 Duration::from_secs_f64(
86 rand::thread_rng().gen_range(0.0..self.max_jitter.as_secs_f64()),
87 )
88 };
89 self.next = current
90 .mul_f64(self.factor)
91 .min(self.max_delay)
92 .saturating_add(jitter);
93
94 Some(current)
95 }
96}
97
98#[test]
99fn test_exponential_backoff_default() {
100 let mut backoff = ExponentialBackoff::new(Duration::from_millis(50), Duration::from_secs(10));
101
102 let bounds = vec![
103 (Duration::from_millis(50), Duration::from_millis(100)),
104 (Duration::from_millis(60), Duration::from_millis(170)),
105 ];
106 for ((lower, upper), delay) in bounds.into_iter().zip(backoff.next()) {
107 assert!(delay >= lower && delay <= upper);
108 }
109}
110
111#[test]
112fn test_exponential_backoff_base_100_factor_2_no_jitter() {
113 let mut backoff = ExponentialBackoff::new(Duration::from_millis(100), Duration::from_secs(10))
114 .factor(2.0)
115 .max_jitter(Duration::ZERO);
116
117 assert_eq!(backoff.next(), Some(Duration::from_millis(100)));
118 assert_eq!(backoff.next(), Some(Duration::from_millis(200)));
119 assert_eq!(backoff.next(), Some(Duration::from_millis(400)));
120 assert_eq!(backoff.next(), Some(Duration::from_millis(800)));
121}
122
123#[test]
124fn test_exponential_backoff_max_delay() {
125 let mut backoff = ExponentialBackoff::new(Duration::from_millis(200), Duration::from_secs(1))
126 .factor(3.0)
127 .max_jitter(Duration::ZERO);
128
129 assert_eq!(backoff.next(), Some(Duration::from_millis(200)));
130 assert_eq!(backoff.next(), Some(Duration::from_millis(600)));
131
132 for _ in 0..10 {
133 assert_eq!(backoff.next(), Some(Duration::from_secs(1)));
134 }
135}