exponential_backoff/
lib.rs

1//! An exponential backoff generator with jitter. Serves as a building block to
2//! implement custom retry functions.
3//!
4//! # Why?
5//! When an network requests times out, often the best way to solve it is to try
6//! again. But trying again straight away might at best cause some network overhead,
7//! and at worst a full fledged DDOS. So we have to be responsible about it.
8//!
9//! A good explanation of retry strategies can be found on the [Stripe
10//! blog](https://stripe.com/blog/idempotency).
11//!
12//! # Usage
13//! Here we try and read a file from disk, and try again if it fails. A more
14//! realistic scenario would probably to perform an HTTP request, but the approach
15//! should be similar.
16//!
17//! ```rust
18//! # fn retry() -> std::io::Result<()> {
19//! use exponential_backoff::Backoff;
20//! use std::{fs, thread, time::Duration};
21//!
22//! let attempts = 3;
23//! let min = Duration::from_millis(100);
24//! let max = Duration::from_secs(10);
25//!
26//! for duration in Backoff::new(attempts, min, max) {
27//!     match fs::read_to_string("README.md") {
28//!         Ok(s) => {
29//!             println!("{}", s);
30//!             break;
31//!         }
32//!         Err(err) => match duration {
33//!             Some(duration) => thread::sleep(duration),
34//!             None => return Err(err),
35//!         }
36//!     }
37//! }
38//! # Ok(()) }
39//! ```
40
41mod into_iter;
42
43use std::time::Duration;
44
45pub use crate::into_iter::IntoIter;
46
47/// Exponential backoff type.
48#[derive(Debug, Clone)]
49pub struct Backoff {
50    max_attempts: u32,
51    min: Duration,
52    max: Duration,
53    jitter: f32,
54    factor: u32,
55}
56
57impl Backoff {
58    /// Create a new instance.
59    #[inline]
60    pub fn new(max_attempts: u32, min: Duration, max: impl Into<Option<Duration>>) -> Self {
61        Self {
62            max_attempts,
63            min,
64            max: max.into().unwrap_or(Duration::MAX),
65            jitter: 0.3,
66            factor: 2,
67        }
68    }
69
70    /// Set the min duration.
71    #[inline]
72    pub fn set_min(&mut self, min: Duration) {
73        self.min = min;
74    }
75
76    /// Set the max duration.
77    #[inline]
78    pub fn set_max(&mut self, max: Duration) {
79        self.max = max;
80    }
81
82    /// Set the amount of jitter per backoff.
83    ///
84    /// ## Panics
85    /// This method panics if a number smaller than `0` or larger than `1` is
86    /// provided.
87    #[inline]
88    pub fn set_jitter(&mut self, jitter: f32) {
89        assert!(
90            jitter > 0f32 && jitter < 1f32,
91            "<exponential-backoff>: jitter must be between 0 and 1."
92        );
93        self.jitter = jitter;
94    }
95
96    /// Set the growth factor for each iteration of the backoff.
97    #[inline]
98    pub fn set_factor(&mut self, factor: u32) {
99        self.factor = factor;
100    }
101
102    /// Create an iterator.
103    #[inline]
104    pub fn iter(&self) -> IntoIter {
105        IntoIter::new(self.clone())
106    }
107}
108
109impl<'b> IntoIterator for &'b Backoff {
110    type Item = Option<Duration>;
111    type IntoIter = IntoIter;
112
113    fn into_iter(self) -> Self::IntoIter {
114        Self::IntoIter::new(self.clone())
115    }
116}
117
118impl IntoIterator for Backoff {
119    type Item = Option<Duration>;
120    type IntoIter = IntoIter;
121
122    fn into_iter(self) -> Self::IntoIter {
123        Self::IntoIter::new(self)
124    }
125}