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