1use crate::polling_state::PollingState;
24use crate::retry_state::RetryState;
25use std::time::Duration;
26
27#[derive(thiserror::Error, Debug)]
29#[non_exhaustive]
30pub enum Error {
31 #[error("the scaling value ({0}) should be >= 1.0")]
32 InvalidScalingFactor(f64),
33 #[error("the initial delay ({0:?}) should be greater than zero")]
34 InvalidInitialDelay(Duration),
35 #[error(
36 "the maximum delay ({maximum:?}) should be greater than or equal to the initial delay ({initial:?})"
37 )]
38 EmptyRange {
39 maximum: Duration,
40 initial: Duration,
41 },
42}
43
44#[derive(Clone, Debug)]
46pub struct ExponentialBackoffBuilder {
47 initial_delay: Duration,
48 maximum_delay: Duration,
49 scaling: f64,
50}
51
52impl ExponentialBackoffBuilder {
53 pub fn new() -> Self {
69 Self {
70 initial_delay: Duration::from_secs(1),
71 maximum_delay: Duration::from_secs(60),
72 scaling: 2.0,
73 }
74 }
75
76 pub fn with_initial_delay<V: Into<Duration>>(mut self, v: V) -> Self {
78 self.initial_delay = v.into();
79 self
80 }
81
82 pub fn with_maximum_delay<V: Into<Duration>>(mut self, v: V) -> Self {
84 self.maximum_delay = v.into();
85 self
86 }
87
88 pub fn with_scaling<V: Into<f64>>(mut self, v: V) -> Self {
90 self.scaling = v.into();
91 self
92 }
93
94 pub fn build(self) -> Result<ExponentialBackoff, Error> {
115 if self.scaling < 1.0 {
116 return Err(Error::InvalidScalingFactor(self.scaling));
117 }
118 if self.initial_delay.is_zero() {
119 return Err(Error::InvalidInitialDelay(self.initial_delay));
120 }
121 if self.maximum_delay < self.initial_delay {
122 return Err(Error::EmptyRange {
123 maximum: self.maximum_delay,
124 initial: self.initial_delay,
125 });
126 }
127 Ok(ExponentialBackoff {
128 maximum_delay: self.maximum_delay,
129 scaling: self.scaling,
130 initial_delay: self.initial_delay,
131 })
132 }
133
134 pub fn clamp(self) -> ExponentialBackoff {
161 let scaling = self.scaling.clamp(1.0, 32.0);
162 let maximum_delay = self
163 .maximum_delay
164 .clamp(Duration::from_secs(1), Duration::from_secs(24 * 60 * 60));
165 let current_delay = self
166 .initial_delay
167 .clamp(Duration::from_millis(1), maximum_delay);
168 ExponentialBackoff {
169 initial_delay: current_delay,
170 maximum_delay,
171 scaling,
172 }
173 }
174}
175
176impl Default for ExponentialBackoffBuilder {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182#[derive(Debug)]
184pub struct ExponentialBackoff {
185 initial_delay: Duration,
186 maximum_delay: Duration,
187 scaling: f64,
188}
189
190impl ExponentialBackoff {
191 fn delay(&self, _loop_start: std::time::Instant, attempt_count: u32) -> Duration {
192 let exp = std::cmp::min(i32::MAX as u32, attempt_count) as i32;
193 let exp = exp.saturating_sub(1);
194 let scaling = self.scaling.powi(exp);
195 if scaling >= self.maximum_delay.div_duration_f64(self.initial_delay) {
196 self.maximum_delay
197 } else {
198 self.initial_delay.mul_f64(scaling)
202 }
203 }
204
205 fn delay_with_jitter(
206 &self,
207 state: &RetryState,
208 rng: &mut impl rand::Rng,
209 ) -> std::time::Duration {
210 let delay = self.delay(state.start, state.attempt_count);
211 rng.random_range(Duration::ZERO..=delay)
212 }
213}
214
215impl Default for ExponentialBackoff {
216 fn default() -> Self {
217 Self {
218 initial_delay: Duration::from_secs(1),
219 maximum_delay: Duration::from_secs(60),
220 scaling: 2.0,
221 }
222 }
223}
224
225impl crate::polling_backoff_policy::PollingBackoffPolicy for ExponentialBackoff {
226 fn wait_period(&self, state: &PollingState) -> std::time::Duration {
227 self.delay(state.start, state.attempt_count)
228 }
229}
230
231impl crate::backoff_policy::BackoffPolicy for ExponentialBackoff {
232 fn on_failure(&self, state: &RetryState) -> std::time::Duration {
233 self.delay_with_jitter(state, &mut rand::rng())
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::mock_rng::MockRng;
241
242 #[test]
243 fn exponential_build_errors() {
244 let b = ExponentialBackoffBuilder::new()
245 .with_initial_delay(Duration::ZERO)
246 .with_maximum_delay(Duration::from_secs(5))
247 .build();
248 assert!(matches!(b, Err(Error::InvalidInitialDelay(_))), "{b:?}");
249 let b = ExponentialBackoffBuilder::new()
250 .with_initial_delay(Duration::from_secs(10))
251 .with_maximum_delay(Duration::from_secs(5))
252 .build();
253 assert!(matches!(b, Err(Error::EmptyRange { .. })), "{b:?}");
254
255 let b = ExponentialBackoffBuilder::new()
256 .with_initial_delay(Duration::from_secs(1))
257 .with_maximum_delay(Duration::from_secs(60))
258 .with_scaling(-1.0)
259 .build();
260 assert!(
261 matches!(b, Err(Error::InvalidScalingFactor { .. })),
262 "{b:?}"
263 );
264
265 let b = ExponentialBackoffBuilder::new()
266 .with_initial_delay(Duration::from_secs(1))
267 .with_maximum_delay(Duration::from_secs(60))
268 .with_scaling(0.0)
269 .build();
270 assert!(
271 matches!(b, Err(Error::InvalidScalingFactor { .. })),
272 "{b:?}"
273 );
274
275 let b = ExponentialBackoffBuilder::new()
276 .with_initial_delay(Duration::ZERO)
277 .build();
278 assert!(matches!(b, Err(Error::InvalidInitialDelay { .. })), "{b:?}");
279 }
280
281 #[test]
282 fn exponential_build_limits() {
283 let r = ExponentialBackoffBuilder::new()
284 .with_initial_delay(Duration::from_secs(1))
285 .with_maximum_delay(Duration::MAX)
286 .build();
287 assert!(r.is_ok(), "{r:?}");
288
289 let r = ExponentialBackoffBuilder::new()
290 .with_initial_delay(Duration::from_nanos(1))
291 .with_maximum_delay(Duration::MAX)
292 .build();
293 assert!(r.is_ok(), "{r:?}");
294
295 let r = ExponentialBackoffBuilder::new()
296 .with_initial_delay(Duration::from_nanos(1))
297 .with_maximum_delay(Duration::MAX)
298 .with_scaling(1.0)
299 .build();
300 assert!(r.is_ok(), "{r:?}");
301 }
302
303 #[test]
304 fn exponential_builder_defaults() {
305 let r = ExponentialBackoffBuilder::new().build();
306 assert!(r.is_ok(), "{r:?}");
307 let r = ExponentialBackoffBuilder::default().build();
308 assert!(r.is_ok(), "{r:?}");
309 }
310
311 #[test_case::test_case(Duration::from_secs(1), Duration::MAX, 0.5; "scaling below range")]
312 #[test_case::test_case(Duration::from_secs(1), Duration::MAX, 1_000_000.0; "scaling over range"
313 )]
314 #[test_case::test_case(Duration::from_secs(1), Duration::MAX, 8.0; "max over range")]
315 #[test_case::test_case(Duration::from_secs(1), Duration::ZERO, 8.0; "max below range")]
316 #[test_case::test_case(Duration::from_secs(10), Duration::ZERO, 8.0; "init over range")]
317 #[test_case::test_case(Duration::ZERO, Duration::ZERO, 8.0; "init below range")]
318 fn exponential_clamp(init: Duration, max: Duration, scaling: f64) {
319 let b = ExponentialBackoffBuilder::new()
320 .with_initial_delay(init)
321 .with_maximum_delay(max)
322 .with_scaling(scaling)
323 .clamp();
324 assert_eq!(b.scaling.clamp(1.0, 32.0), b.scaling);
325 assert_eq!(
326 b.initial_delay
327 .clamp(Duration::from_millis(1), b.maximum_delay),
328 b.initial_delay
329 );
330 assert_eq!(
331 b.maximum_delay
332 .clamp(b.initial_delay, Duration::from_secs(24 * 60 * 60)),
333 b.maximum_delay
334 );
335 }
336
337 #[test]
338 fn exponential_full_jitter() {
339 let b = ExponentialBackoffBuilder::new()
340 .with_initial_delay(Duration::from_secs(10))
341 .with_maximum_delay(Duration::from_secs(10))
342 .build()
343 .expect("should succeed with the hard-coded test values");
344
345 let mut rng = MockRng::new(1);
346 assert_eq!(
347 b.delay_with_jitter(&RetryState::new(true).set_attempt_count(1_u32), &mut rng),
348 Duration::ZERO
349 );
350
351 let mut rng = MockRng::new(u64::MAX / 2);
352 assert_eq!(
353 b.delay_with_jitter(&RetryState::new(true).set_attempt_count(2_u32), &mut rng),
354 Duration::from_secs(5)
355 );
356
357 let mut rng = MockRng::new(u64::MAX);
358 assert_eq!(
359 b.delay_with_jitter(&RetryState::new(true).set_attempt_count(3_u32), &mut rng),
360 Duration::from_secs(10)
361 );
362 }
363
364 #[test]
365 fn exponential_scaling() {
366 let b = ExponentialBackoffBuilder::new()
367 .with_initial_delay(Duration::from_secs(1))
368 .with_maximum_delay(Duration::from_secs(4))
369 .with_scaling(2.0)
370 .build()
371 .expect("should succeed with the hard-coded test values");
372
373 let now = std::time::Instant::now();
374 assert_eq!(b.delay(now, 1), Duration::from_secs(1));
375 assert_eq!(b.delay(now, 2), Duration::from_secs(2));
376 assert_eq!(b.delay(now, 3), Duration::from_secs(4));
377 assert_eq!(b.delay(now, 4), Duration::from_secs(4));
378 }
379
380 #[test]
381 fn wait_period() {
382 use crate::polling_backoff_policy::PollingBackoffPolicy;
383 let b = ExponentialBackoffBuilder::new()
384 .with_initial_delay(Duration::from_secs(1))
385 .with_maximum_delay(Duration::from_secs(4))
386 .with_scaling(2.0)
387 .build()
388 .expect("should succeed with the hard-coded test values");
389
390 assert_eq!(
391 b.wait_period(&PollingState::default().set_attempt_count(1_u32)),
392 Duration::from_secs(1)
393 );
394 assert_eq!(
395 b.wait_period(&PollingState::default().set_attempt_count(2_u32)),
396 Duration::from_secs(2)
397 );
398 assert_eq!(
399 b.wait_period(&PollingState::default().set_attempt_count(3_u32)),
400 Duration::from_secs(4)
401 );
402 assert_eq!(
403 b.wait_period(&PollingState::default().set_attempt_count(4_u32)),
404 Duration::from_secs(4)
405 );
406 }
407
408 #[test]
409 fn exponential_scaling_jitter() {
410 let b = ExponentialBackoffBuilder::new()
411 .with_initial_delay(Duration::from_secs(1))
412 .with_maximum_delay(Duration::from_secs(4))
413 .with_scaling(2.0)
414 .build()
415 .expect("should succeed with the hard-coded test values");
416
417 let mut rng = MockRng::new(u64::MAX);
418 assert_eq!(
419 b.delay_with_jitter(&RetryState::new(true).set_attempt_count(1_u32), &mut rng),
420 Duration::from_secs(1)
421 );
422
423 let mut rng = MockRng::new(u64::MAX);
424 assert_eq!(
425 b.delay_with_jitter(&RetryState::new(true).set_attempt_count(2_u32), &mut rng),
426 Duration::from_secs(2)
427 );
428
429 let mut rng = MockRng::new(u64::MAX);
430 assert_eq!(
431 b.delay_with_jitter(&RetryState::new(true).set_attempt_count(3_u32), &mut rng),
432 Duration::from_secs(4)
433 );
434
435 let mut rng = MockRng::new(u64::MAX);
436 assert_eq!(
437 b.delay_with_jitter(&RetryState::new(true).set_attempt_count(4_u32), &mut rng),
438 Duration::from_secs(4)
439 );
440 }
441
442 #[test]
443 fn on_failure() {
444 use crate::backoff_policy::BackoffPolicy;
445 let b = ExponentialBackoffBuilder::new()
446 .with_initial_delay(Duration::from_secs(1))
447 .with_maximum_delay(Duration::from_secs(4))
448 .with_scaling(2.0)
449 .build()
450 .expect("should succeed with the hard-coded test values");
451
452 let d = b.on_failure(&RetryState::new(true).set_attempt_count(1_u32));
453 assert!(Duration::ZERO <= d && d <= Duration::from_secs(1), "{d:?}");
454 let d = b.on_failure(&RetryState::new(true).set_attempt_count(2_u32));
455 assert!(Duration::ZERO <= d && d <= Duration::from_secs(2), "{d:?}");
456 let d = b.on_failure(&RetryState::new(true).set_attempt_count(3_u32));
457 assert!(Duration::ZERO <= d && d <= Duration::from_secs(4), "{d:?}");
458 let d = b.on_failure(&RetryState::new(true).set_attempt_count(4_u32));
459 assert!(Duration::ZERO <= d && d <= Duration::from_secs(4), "{d:?}");
460 let d = b.on_failure(&RetryState::new(true).set_attempt_count(5_u32));
461 assert!(Duration::ZERO <= d && d <= Duration::from_secs(4), "{d:?}");
462 }
463
464 #[test]
465 fn default() {
466 let b = ExponentialBackoff::default();
467
468 let mut rng = MockRng::new(u64::MAX);
469 let next =
470 2 * b.delay_with_jitter(&RetryState::new(true).set_attempt_count(1_u32), &mut rng);
471
472 let mut rng = MockRng::new(u64::MAX);
473 assert_eq!(
474 b.delay_with_jitter(&RetryState::new(true).set_attempt_count(2_u32), &mut rng),
475 next
476 );
477 let next = 2 * next;
478
479 let mut rng = MockRng::new(u64::MAX);
480 assert_eq!(
481 b.delay_with_jitter(&RetryState::new(true).set_attempt_count(3_u32), &mut rng),
482 next
483 );
484 }
485}