Expand description
ExponentialBackoff
is a backoff implementation that increases the backoff
period for each retry attempt using a randomization function that grows exponentially.
next_backoff
is calculated using the following formula:
randomized interval =
retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor])
In other words next_backoff
will range between the randomization factor
percentage below and above the retry interval.
For example, given the following parameters:
retry_interval = 2
randomization_factor = 0.5
multiplier = 2
the actual backoff period used in the next retry attempt will range between 1 and 3 seconds, multiplied by the exponential, that is, between 2 and 6 seconds.
Note: max_interval
caps the retry_interval
and not the randomized interval.
If the time elapsed since an ExponentialBackoff
instance is created goes past the
max_elapsed_time
, then the method next_backoff
starts returning None
.
The elapsed time can be reset by calling reset
.
Example: Given the following default arguments, for 10 tries the sequence will be,
and assuming we go over the max_elapsed_time
on the 10th try:
Request # | retry_interval (seconds) | Randomized Interval (seconds) |
---|---|---|
1 | 0.5 | [0.25, 0.75] |
2 | 0.75 | [0.375, 1.125] |
3 | 1.125 | [0.562, 1.687] |
4 | 1.687 | [0.8435, 2.53] |
5 | 2.53 | [1.265, 3.795] |
6 | 3.795 | [1.897, 5.692] |
7 | 5.692 | [2.846, 8.538] |
8 | 8.538 | [4.269, 12.807] |
9 | 12.807 | [6.403, 19.210] |
10 | 19.210 | None |
§Examples
§Permanent errors
Permanent errors are not retried. You have to wrap your error value explicitly
into Error::Permanent
. You can use Result
’s map_err
method.
examples/permanent_error.rs
:
use backoff::{Error, ExponentialBackoff};
use reqwest::Url;
use std::fmt::Display;
use std::io::{self, Read};
fn new_io_err<E: Display>(err: E) -> io::Error {
io::Error::new(io::ErrorKind::Other, err.to_string())
}
fn fetch_url(url: &str) -> Result<String, Error<io::Error>> {
let op = || {
println!("Fetching {}", url);
let url = Url::parse(url)
.map_err(new_io_err)
// Permanent errors need to be explicitly constructed.
.map_err(Error::Permanent)?;
let mut resp = reqwest::blocking::get(url)
// Transient errors can be constructed with the ? operator
// or with the try! macro. No explicit conversion needed
// from E: Error to backoff::Error;
.map_err(new_io_err)?;
let mut content = String::new();
let _ = resp.read_to_string(&mut content);
Ok(content)
};
let backoff = ExponentialBackoff::default();
backoff::retry(backoff, op)
}
fn main() {
match fetch_url("https::///wrong URL") {
Ok(_) => println!("Successfully fetched"),
Err(err) => panic!("Failed to fetch: {}", err),
}
}
§Transient errors
Transient errors can be constructed by wrapping your error value into Error::transient
.
By using the ? operator or the try!
macro, you always get transient errors.
You can also construct transient errors that are retried after a given
interval with Error::retry_after()
- useful for 429 errors.
examples/retry.rs
:
use backoff::{retry, Error, ExponentialBackoff};
use std::io::Read;
fn fetch_url(url: &str) -> Result<String, Error<reqwest::Error>> {
let mut op = || {
println!("Fetching {}", url);
let mut resp = reqwest::blocking::get(url)?;
let mut content = String::new();
let _ = resp.read_to_string(&mut content);
Ok(content)
};
let backoff = ExponentialBackoff::default();
retry(backoff, op)
}
fn main() {
match fetch_url("https://www.rust-lang.org") {
Ok(_) => println!("Sucessfully fetched"),
Err(err) => panic!("Failed to fetch: {}", err),
}
}
Output with internet connection:
$ time cargo run --example retry
Compiling backoff v0.1.0 (file:///home/tibi/workspace/backoff)
Finished dev [unoptimized + debuginfo] target(s) in 1.54 secs
Running `target/debug/examples/retry`
Fetching https://www.rust-lang.org
Sucessfully fetched
real 0m2.003s
user 0m1.536s
sys 0m0.184s
Output without internet connection
$ time cargo run --example retry
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/examples/retry`
Fetching https://www.rust-lang.org
Fetching https://www.rust-lang.org
Fetching https://www.rust-lang.org
Fetching https://www.rust-lang.org
^C
real 0m2.826s
user 0m0.008s
sys 0m0.000s
§Async
Please set either the tokio
or async-std
features in Cargo.toml to enable the async support of this library, i.e.:
backoff = { version = "x.y.z", features = ["tokio"] }
A Future<Output = Result<T, backoff::Error<E>>
can be easily retried:
examples/async.rs
:
extern crate tokio_1 as tokio;
use backoff::ExponentialBackoff;
async fn fetch_url(url: &str) -> Result<String, reqwest::Error> {
backoff::future::retry(ExponentialBackoff::default(), || async {
println!("Fetching {}", url);
Ok(reqwest::get(url).await?.text().await?)
})
.await
}
#[tokio::main]
async fn main() {
match fetch_url("https://www.rust-lang.org").await {
Ok(_) => println!("Successfully fetched"),
Err(err) => panic!("Failed to fetch: {}", err),
}
}
§Feature flags
futures
: enables futures support,tokio
: enables support for the tokio async runtime, impliesfutures
,async-std
: enables support for the async-std async runtime, impliesfutures
,wasm-bindgen
: enabled support for wasm-bindgen.
Modules§
- Constants for the exponential backoff policy.
Structs§
SystemClock
uses the system’s clock to get the current time. This Clock should be used for real use-cases.
Enums§
- Error is the error value in an operation’s result.
Traits§
- Clock returns the current time.
- Notify is called in
retry_notify
in case of errors.
Functions§
- Retries this operation according to the backoff policy. backoff is reset before it is used.
- Retries this operation according to the backoff policy. Calls notify on failed attempts (in case of transient errors). backoff is reset before it is used.
Type Aliases§
- Exponential backoff policy with system’s clock.
- Builder for exponential backoff policy with system’s clock.