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}