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}
56impl Backoff {
57    /// Create a new instance of `Backoff`.
58    ///
59    /// # Examples
60    ///
61    /// With an explicit max duration:
62    ///
63    /// ```rust
64    /// use exponential_backoff::Backoff;
65    /// use std::time::Duration;
66    ///
67    /// let backoff = Backoff::new(3, Duration::from_millis(100), Duration::from_secs(10));
68    /// assert_eq!(backoff.max_attempts(), 3);
69    /// assert_eq!(backoff.min(), &Duration::from_millis(100));
70    /// assert_eq!(backoff.max(), &Duration::from_secs(10));
71    /// ```
72    ///
73    /// With no max duration (sets it to 584,942,417,355 years):
74    ///
75    /// ```rust
76    /// use exponential_backoff::Backoff;
77    /// use std::time::Duration;
78    ///
79    /// let backoff = Backoff::new(5, Duration::from_millis(50), None);
80    /// # assert_eq!(backoff.max_attempts(), 5);
81    /// # assert_eq!(backoff.min(), &Duration::from_millis(50));
82    /// assert_eq!(backoff.max(), &Duration::MAX);
83    /// ```
84    #[inline]
85    pub fn new(max_attempts: u32, min: Duration, max: impl Into<Option<Duration>>) -> Self {
86        Self {
87            max_attempts,
88            min,
89            max: max.into().unwrap_or(Duration::MAX),
90            jitter: 0.3,
91            factor: 2,
92        }
93    }
94
95    /// Get the min duration
96    ///
97    /// # Examples
98    ///
99    /// ```rust
100    /// use exponential_backoff::Backoff;
101    /// use std::time::Duration;
102    ///
103    /// let mut backoff = Backoff::default();
104    /// assert_eq!(backoff.min(), &Duration::from_millis(100));
105    /// ```
106    pub fn min(&self) -> &Duration {
107        &self.min
108    }
109
110    /// Set the min duration.
111    ///
112    /// # Examples
113    ///
114    /// ```rust
115    /// use exponential_backoff::Backoff;
116    /// use std::time::Duration;
117    ///
118    /// let mut backoff = Backoff::default();
119    /// backoff.set_min(Duration::from_millis(50));
120    /// assert_eq!(backoff.min(), &Duration::from_millis(50));
121    /// ```
122    #[inline]
123    pub fn set_min(&mut self, min: Duration) {
124        self.min = min;
125    }
126
127    /// Get the max duration
128    ///
129    /// # Examples
130    ///
131    /// ```rust
132    /// use exponential_backoff::Backoff;
133    /// use std::time::Duration;
134    ///
135    /// let mut backoff = Backoff::default();
136    /// assert_eq!(backoff.max(), &Duration::from_secs(10));
137    /// ```
138    pub fn max(&self) -> &Duration {
139        &self.max
140    }
141
142    /// Set the max duration.
143    ///
144    /// # Examples
145    ///
146    /// ```rust
147    /// use exponential_backoff::Backoff;
148    /// use std::time::Duration;
149    ///
150    /// let mut backoff = Backoff::default();
151    /// backoff.set_max(Duration::from_secs(30));
152    /// assert_eq!(backoff.max(), &Duration::from_secs(30));
153    /// ```
154    #[inline]
155    pub fn set_max(&mut self, max: Duration) {
156        self.max = max;
157    }
158
159    /// Get the maximum number of attempts
160    ///
161    /// # Examples
162    ///
163    /// ```rust
164    /// use exponential_backoff::Backoff;
165    ///
166    /// let mut backoff = Backoff::default();
167    /// assert_eq!(backoff.max_attempts(), 3);
168    /// ```
169    pub fn max_attempts(&self) -> u32 {
170        self.max_attempts
171    }
172
173    /// Set the maximum number of attempts.
174    ///
175    /// # Examples
176    ///
177    /// ```rust
178    /// use exponential_backoff::Backoff;
179    ///
180    /// let mut backoff = Backoff::default();
181    /// backoff.set_max_attempts(5);
182    /// assert_eq!(backoff.max_attempts(), 5);
183    /// ```
184    pub fn set_max_attempts(&mut self, max_attempts: u32) {
185        self.max_attempts = max_attempts;
186    }
187
188    /// Get the jitter factor
189    ///
190    /// # Examples
191    ///
192    /// ```rust
193    /// use exponential_backoff::Backoff;
194    ///
195    /// let mut backoff = Backoff::default();
196    /// assert_eq!(backoff.jitter(), 0.3);
197    /// ```
198    pub fn jitter(&self) -> f32 {
199        self.jitter
200    }
201
202    /// Set the amount of jitter per backoff.
203    ///
204    /// # Panics
205    ///
206    /// This method panics if a number smaller than `0` or larger than `1` is
207    /// provided.
208    ///
209    /// # Examples
210    ///
211    /// ```rust
212    /// use exponential_backoff::Backoff;
213    ///
214    /// let mut backoff = Backoff::default();
215    /// backoff.set_jitter(0.3);  // default value
216    /// backoff.set_jitter(0.0);  // min value
217    /// backoff.set_jitter(1.0);  // max value
218    /// ```
219    #[inline]
220    pub fn set_jitter(&mut self, jitter: f32) {
221        assert!(
222            jitter >= 0f32 && jitter <= 1f32,
223            "<exponential-backoff>: jitter must be between 0 and 1."
224        );
225        self.jitter = jitter;
226    }
227
228    /// Get the growth factor
229    ///
230    /// # Examples
231    ///
232    /// ```rust
233    /// use exponential_backoff::Backoff;
234    ///
235    /// let mut backoff = Backoff::default();
236    /// assert_eq!(backoff.factor(), 2);
237    /// ```
238    pub fn factor(&self) -> u32 {
239        self.factor
240    }
241
242    /// Set the growth factor for each iteration of the backoff.
243    ///
244    /// # Examples
245    ///
246    /// ```rust
247    /// use exponential_backoff::Backoff;
248    ///
249    /// let mut backoff = Backoff::default();
250    /// backoff.set_factor(3);
251    /// assert_eq!(backoff.factor(), 3);
252    /// ```
253    #[inline]
254    pub fn set_factor(&mut self, factor: u32) {
255        self.factor = factor;
256    }
257
258    /// Create an iterator.
259    #[inline]
260    pub fn iter(&self) -> IntoIter {
261        IntoIter::new(self.clone())
262    }
263}
264
265/// Implements the `IntoIterator` trait for borrowed `Backoff` instances.
266///
267/// # Examples
268///
269/// ```rust
270/// use exponential_backoff::Backoff;
271/// use std::time::Duration;
272///
273/// let backoff = Backoff::default();
274/// let mut count = 0;
275///
276/// for duration in &backoff {
277///     count += 1;
278///     if count > 1 {
279///         break;
280///     }
281/// }
282/// ```
283impl<'b> IntoIterator for &'b Backoff {
284    type Item = Option<Duration>;
285    type IntoIter = IntoIter;
286
287    fn into_iter(self) -> Self::IntoIter {
288        Self::IntoIter::new(self.clone())
289    }
290}
291
292/// Implements the `IntoIterator` trait for owned `Backoff` instances.
293///
294/// # Examples
295///
296/// ```rust
297/// use exponential_backoff::Backoff;
298/// use std::time::Duration;
299///
300/// let backoff = Backoff::default();
301/// let mut count = 0;
302///
303/// for duration in backoff {
304///     count += 1;
305///     if count > 1 {
306///         break;
307///     }
308/// }
309/// ```
310impl IntoIterator for Backoff {
311    type Item = Option<Duration>;
312    type IntoIter = IntoIter;
313
314    fn into_iter(self) -> Self::IntoIter {
315        Self::IntoIter::new(self)
316    }
317}
318
319/// Implements the `Default` trait for `Backoff`.
320///
321/// # Examples
322///
323/// ```rust
324/// use exponential_backoff::Backoff;
325/// use std::time::Duration;
326///
327/// let backoff = Backoff::default();
328/// assert_eq!(backoff.max_attempts(), 3);
329/// assert_eq!(backoff.min(), &Duration::from_millis(100));
330/// assert_eq!(backoff.max(), &Duration::from_secs(10));
331/// assert_eq!(backoff.jitter(), 0.3);
332/// assert_eq!(backoff.factor(), 2);
333/// ```
334impl Default for Backoff {
335    fn default() -> Self {
336        Self {
337            max_attempts: 3,
338            min: Duration::from_millis(100),
339            max: Duration::from_secs(10),
340            jitter: 0.3,
341            factor: 2,
342        }
343    }
344}