backoff/lib.rs
1#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! `ExponentialBackoff` is a backoff implementation that increases the backoff
5//! period for each retry attempt using a randomization function that grows exponentially.
6//!
7//! [`next_backoff`]: backoff/trait.Backoff.html#tymethod.next_backoff
8//! [`reset`]: backoff/trait.Backoff.html#tymethod.reset
9//!
10//! [`next_backoff`] is calculated using the following formula:
11//!
12//!```text
13//! randomized interval =
14//! retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor])
15//!```
16//!
17//! In other words [`next_backoff`] will range between the randomization factor
18//! percentage below and above the retry interval.
19//!
20//! For example, given the following parameters:
21//!
22//!```text
23//!retry_interval = 2
24//!randomization_factor = 0.5
25//!multiplier = 2
26//!```
27//!
28//! the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
29//! multiplied by the exponential, that is, between 2 and 6 seconds.
30//!
31//! **Note**: `max_interval` caps the `retry_interval` and not the randomized interval.
32//!
33//! If the time elapsed since an [`ExponentialBackoff`](type.ExponentialBackoff.html) instance is created goes past the
34//! `max_elapsed_time`, then the method [`next_backoff`] starts returning `None`.
35//!
36//! The elapsed time can be reset by calling [`reset`].
37//!
38//! Example: Given the following default arguments, for 10 tries the sequence will be,
39//! and assuming we go over the `max_elapsed_time` on the 10th try:
40//!
41//! Request # | `retry_interval` (seconds) | Randomized Interval (seconds)
42//! -----------|--------------------------|--------------------------------
43//! 1 | 0.5 | [0.25, 0.75]
44//! 2 | 0.75 | [0.375, 1.125]
45//! 3 | 1.125 | [0.562, 1.687]
46//! 4 | 1.687 | [0.8435, 2.53]
47//! 5 | 2.53 | [1.265, 3.795]
48//! 6 | 3.795 | [1.897, 5.692]
49//! 7 | 5.692 | [2.846, 8.538]
50//! 8 | 8.538 | [4.269, 12.807]
51//! 9 | 12.807 | [6.403, 19.210]
52//! 10 | 19.210 | None
53//!
54//! # Examples
55//!
56//! ## Permanent errors
57//!
58//! Permanent errors are not retried. You have to wrap your error value explicitly
59//! into `Error::Permanent`. You can use `Result`'s `map_err` method.
60//!
61//! `examples/permanent_error.rs`:
62//!
63//! ```rust,no_run
64//! use backoff::{Error, ExponentialBackoff};
65//! use reqwest::Url;
66//!
67//! use std::fmt::Display;
68//! use std::io::{self, Read};
69//!
70//! fn new_io_err<E: Display>(err: E) -> io::Error {
71//! io::Error::new(io::ErrorKind::Other, err.to_string())
72//! }
73//!
74//! fn fetch_url(url: &str) -> Result<String, Error<io::Error>> {
75//! let op = || {
76//! println!("Fetching {}", url);
77//! let url = Url::parse(url)
78//! .map_err(new_io_err)
79//! // Permanent errors need to be explicitly constructed.
80//! .map_err(Error::Permanent)?;
81//!
82//! let mut resp = reqwest::blocking::get(url)
83//! // Transient errors can be constructed with the ? operator
84//! // or with the try! macro. No explicit conversion needed
85//! // from E: Error to backoff::Error;
86//! .map_err(new_io_err)?;
87//!
88//! let mut content = String::new();
89//! let _ = resp.read_to_string(&mut content);
90//! Ok(content)
91//! };
92//!
93//! let backoff = ExponentialBackoff::default();
94//! backoff::retry(backoff, op)
95//! }
96//!
97//! fn main() {
98//! match fetch_url("https::///wrong URL") {
99//! Ok(_) => println!("Successfully fetched"),
100//! Err(err) => panic!("Failed to fetch: {}", err),
101//! }
102//! }
103//! ```
104//!
105//! ## Transient errors
106//!
107//! Transient errors can be constructed by wrapping your error value into `Error::transient`.
108//! By using the ? operator or the `try!` macro, you always get transient errors.
109//!
110//! You can also construct transient errors that are retried after a given
111//! interval with `Error::retry_after()` - useful for 429 errors.
112//!
113//! `examples/retry.rs`:
114//!
115//! ```rust
116//! use backoff::{retry, Error, ExponentialBackoff};
117//!
118//! use std::io::Read;
119//!
120//! fn fetch_url(url: &str) -> Result<String, Error<reqwest::Error>> {
121//! let mut op = || {
122//! println!("Fetching {}", url);
123//! let mut resp = reqwest::blocking::get(url)?;
124//!
125//! let mut content = String::new();
126//! let _ = resp.read_to_string(&mut content);
127//! Ok(content)
128//! };
129//!
130//! let backoff = ExponentialBackoff::default();
131//! retry(backoff, op)
132//! }
133//!
134//! fn main() {
135//! match fetch_url("https://www.rust-lang.org") {
136//! Ok(_) => println!("Sucessfully fetched"),
137//! Err(err) => panic!("Failed to fetch: {}", err),
138//! }
139//! }
140//! ```
141//!
142//! Output with internet connection:
143//!
144//! ```text
145//! $ time cargo run --example retry
146//! Compiling backoff v0.1.0 (file:///home/tibi/workspace/backoff)
147//! Finished dev [unoptimized + debuginfo] target(s) in 1.54 secs
148//! Running `target/debug/examples/retry`
149//! Fetching https://www.rust-lang.org
150//! Sucessfully fetched
151//!
152//! real 0m2.003s
153//! user 0m1.536s
154//! sys 0m0.184s
155//! ```
156//!
157//! Output without internet connection
158//!
159//! ```text
160//! $ time cargo run --example retry
161//! Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
162//! Running `target/debug/examples/retry`
163//! Fetching https://www.rust-lang.org
164//! Fetching https://www.rust-lang.org
165//! Fetching https://www.rust-lang.org
166//! Fetching https://www.rust-lang.org
167//! ^C
168//!
169//! real 0m2.826s
170//! user 0m0.008s
171//! sys 0m0.000s
172//! ```
173//!
174//! ### Async
175//!
176//! Please set either the `tokio` or `async-std` features in Cargo.toml to enable the async support of this library, i.e.:
177//!
178//! ```toml
179//! backoff = { version = "x.y.z", features = ["tokio"] }
180//! ```
181//!
182//! A `Future<Output = Result<T, backoff::Error<E>>` can be easily retried:
183//!
184//! `examples/async.rs`:
185//!
186//! ```rust,no_run,ignore
187//!
188//! extern crate tokio_1 as tokio;
189//!
190//! use backoff::ExponentialBackoff;
191//!
192//! async fn fetch_url(url: &str) -> Result<String, reqwest::Error> {
193//! backoff::future::retry(ExponentialBackoff::default(), || async {
194//! println!("Fetching {}", url);
195//! Ok(reqwest::get(url).await?.text().await?)
196//! })
197//! .await
198//! }
199//!
200//! #[tokio::main]
201//! async fn main() {
202//! match fetch_url("https://www.rust-lang.org").await {
203//! Ok(_) => println!("Successfully fetched"),
204//! Err(err) => panic!("Failed to fetch: {}", err),
205//! }
206//! }
207//! ```
208//! # Feature flags
209//!
210//! - `futures`: enables futures support,
211//! - `tokio`: enables support for the [tokio](https://crates.io/crates/tokio) async runtime, implies `futures`,
212//! - `async-std`: enables support for the [async-std](https://crates.io/crates/async-std) async runtime, implies `futures`,
213//! - `wasm-bindgen`: enabled support for [wasm-bindgen](https://crates.io/crates/wasm-bindgen).
214
215pub mod backoff;
216mod clock;
217pub mod default;
218mod error;
219pub mod exponential;
220
221#[cfg(feature = "futures")]
222#[cfg_attr(docsrs, doc(cfg(feature = "futures")))]
223pub mod future;
224
225mod retry;
226
227pub use crate::clock::{Clock, SystemClock};
228pub use crate::error::Error;
229pub use crate::retry::{retry, retry_notify, Notify};
230
231/// Exponential backoff policy with system's clock.
232///
233/// This type is preferred over
234/// `exponential::ExponentialBackoff` as it is generic over any [Clocks](trait.Clock.html)
235/// and in the real world mostly system's clock is used.
236pub type ExponentialBackoff = exponential::ExponentialBackoff<SystemClock>;
237
238/// Builder for exponential backoff policy with system's clock.
239pub type ExponentialBackoffBuilder = exponential::ExponentialBackoffBuilder<SystemClock>;