tower_resilience_retry/
backoff.rs1use std::time::Duration;
2
3pub trait IntervalFunction: Send + Sync {
8 fn next_interval(&self, attempt: usize) -> Duration;
13}
14
15#[derive(Debug, Clone)]
17pub struct FixedInterval {
18 duration: Duration,
19}
20
21impl FixedInterval {
22 pub fn new(duration: Duration) -> Self {
24 Self { duration }
25 }
26}
27
28impl IntervalFunction for FixedInterval {
29 fn next_interval(&self, _attempt: usize) -> Duration {
30 self.duration
31 }
32}
33
34#[derive(Debug, Clone)]
36pub struct ExponentialBackoff {
37 initial_interval: Duration,
38 multiplier: f64,
39 max_interval: Option<Duration>,
40}
41
42impl ExponentialBackoff {
43 pub fn new(initial_interval: Duration) -> Self {
45 Self {
46 initial_interval,
47 multiplier: 2.0,
48 max_interval: None,
49 }
50 }
51
52 pub fn multiplier(mut self, multiplier: f64) -> Self {
54 self.multiplier = multiplier;
55 self
56 }
57
58 pub fn max_interval(mut self, max_interval: Duration) -> Self {
60 self.max_interval = Some(max_interval);
61 self
62 }
63}
64
65impl IntervalFunction for ExponentialBackoff {
66 fn next_interval(&self, attempt: usize) -> Duration {
67 let multiplier = self.multiplier.powi(attempt as i32);
68 let interval = self.initial_interval.mul_f64(multiplier);
69
70 if let Some(max) = self.max_interval {
71 interval.min(max)
72 } else {
73 interval
74 }
75 }
76}
77
78#[derive(Debug, Clone)]
80pub struct ExponentialRandomBackoff {
81 initial_interval: Duration,
82 multiplier: f64,
83 randomization_factor: f64,
84 max_interval: Option<Duration>,
85}
86
87impl ExponentialRandomBackoff {
88 pub fn new(initial_interval: Duration, randomization_factor: f64) -> Self {
95 Self {
96 initial_interval,
97 multiplier: 2.0,
98 randomization_factor: randomization_factor.clamp(0.0, 1.0),
99 max_interval: None,
100 }
101 }
102
103 pub fn multiplier(mut self, multiplier: f64) -> Self {
105 self.multiplier = multiplier;
106 self
107 }
108
109 pub fn max_interval(mut self, max_interval: Duration) -> Self {
111 self.max_interval = Some(max_interval);
112 self
113 }
114
115 fn randomize(&self, duration: Duration) -> Duration {
116 use rand::Rng;
117 let mut rng = rand::rng();
118 let delta = duration.as_secs_f64() * self.randomization_factor;
119 let min = duration.as_secs_f64() - delta;
120 let max = duration.as_secs_f64() + delta;
121 let randomized = rng.random_range(min..=max);
122 Duration::from_secs_f64(randomized.max(0.0))
123 }
124}
125
126impl IntervalFunction for ExponentialRandomBackoff {
127 fn next_interval(&self, attempt: usize) -> Duration {
128 let multiplier = self.multiplier.powi(attempt as i32);
129 let interval = self.initial_interval.mul_f64(multiplier);
130
131 let capped = if let Some(max) = self.max_interval {
132 interval.min(max)
133 } else {
134 interval
135 };
136
137 self.randomize(capped)
138 }
139}
140
141pub struct FnInterval<F> {
143 f: F,
144}
145
146impl<F> FnInterval<F>
147where
148 F: Fn(usize) -> Duration + Send + Sync,
149{
150 pub fn new(f: F) -> Self {
152 Self { f }
153 }
154}
155
156impl<F> IntervalFunction for FnInterval<F>
157where
158 F: Fn(usize) -> Duration + Send + Sync,
159{
160 fn next_interval(&self, attempt: usize) -> Duration {
161 (self.f)(attempt)
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn fixed_interval_returns_same_duration() {
171 let backoff = FixedInterval::new(Duration::from_secs(1));
172 assert_eq!(backoff.next_interval(0), Duration::from_secs(1));
173 assert_eq!(backoff.next_interval(1), Duration::from_secs(1));
174 assert_eq!(backoff.next_interval(10), Duration::from_secs(1));
175 }
176
177 #[test]
178 fn exponential_backoff_grows() {
179 let backoff = ExponentialBackoff::new(Duration::from_millis(100));
180 assert_eq!(backoff.next_interval(0), Duration::from_millis(100));
181 assert_eq!(backoff.next_interval(1), Duration::from_millis(200));
182 assert_eq!(backoff.next_interval(2), Duration::from_millis(400));
183 assert_eq!(backoff.next_interval(3), Duration::from_millis(800));
184 }
185
186 #[test]
187 fn exponential_backoff_custom_multiplier() {
188 let backoff = ExponentialBackoff::new(Duration::from_millis(100)).multiplier(3.0);
189 assert_eq!(backoff.next_interval(0), Duration::from_millis(100));
190 assert_eq!(backoff.next_interval(1), Duration::from_millis(300));
191 assert_eq!(backoff.next_interval(2), Duration::from_millis(900));
192 }
193
194 #[test]
195 fn exponential_backoff_respects_max() {
196 let backoff = ExponentialBackoff::new(Duration::from_millis(100))
197 .max_interval(Duration::from_millis(500));
198 assert_eq!(backoff.next_interval(0), Duration::from_millis(100));
199 assert_eq!(backoff.next_interval(1), Duration::from_millis(200));
200 assert_eq!(backoff.next_interval(2), Duration::from_millis(400));
201 assert_eq!(backoff.next_interval(3), Duration::from_millis(500)); assert_eq!(backoff.next_interval(4), Duration::from_millis(500)); }
204
205 #[test]
206 fn exponential_random_backoff_has_variance() {
207 let backoff = ExponentialRandomBackoff::new(Duration::from_millis(100), 0.5);
208
209 let mut intervals = Vec::new();
211 for _ in 0..10 {
212 intervals.push(backoff.next_interval(1));
213 }
214
215 let all_same = intervals.windows(2).all(|w| w[0] == w[1]);
217 assert!(!all_same, "Randomized intervals should vary");
218
219 for interval in intervals {
222 assert!(
223 interval >= Duration::from_millis(100) && interval <= Duration::from_millis(300),
224 "Interval {:?} outside expected range",
225 interval
226 );
227 }
228 }
229
230 #[test]
231 fn fn_interval_uses_custom_function() {
232 let backoff = FnInterval::new(|attempt| Duration::from_secs((attempt + 1) as u64));
233 assert_eq!(backoff.next_interval(0), Duration::from_secs(1));
234 assert_eq!(backoff.next_interval(1), Duration::from_secs(2));
235 assert_eq!(backoff.next_interval(2), Duration::from_secs(3));
236 }
237}