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}