tokio_retry2/
lib.rs

1#![cfg_attr(
2    not(test),
3    deny(
4        clippy::exit,
5        clippy::panic,
6        clippy::unwrap_used,
7        clippy::expect_used,
8        clippy::indexing_slicing,
9        clippy::unimplemented,
10        clippy::todo
11    )
12)]
13//! This library provides extensible asynchronous retry behaviors
14//! for use with the ecosystem of [`tokio`](https://tokio.rs/) libraries.
15//!
16//! There are 4 backoff strategies:
17//! - `ExponentialBackoff`: base is considered the initial retry interval, so if defined from 500ms, the next retry will happen at 250000ms.
18//!     | attempt | delay |
19//!     |---------|-------|
20//!     | 1       | 500ms |
21//!     | 2       | 250000ms|
22//! - `ExponentialFactorBackoff`: this is a exponential backoff strategy with a base factor. What is exponentially configured is the factor, while the base retry delay is the same. So if a factor 2 is applied to an initial delay off 500ms, the attempts are as follows:
23//!     | attempt | delay |
24//!     |---------|-------|
25//!     | 1       | 500ms |
26//!     | 2       | 1000ms|
27//!     | 3       | 2000ms|
28//!     | 4       | 4000ms|
29//!
30//! - `FixedInterval`: in this backoff strategy, a fixed interval is used as constant. So if defined from 500ms, all attempts will happen at 500ms.
31//!     | attempt | delay |
32//!     |---------|-------|
33//!     | 1       | 500ms|
34//!     | 2       | 500ms|
35//!     | 3       | 500ms|
36//! - `FibonacciBackoff`: a Fibonacci backoff strategy is used. So if defined from 500ms, the next retry will happen at 500ms, and the following will be at 1000ms.
37//!     | attempt | delay |
38//!     |---------|-------|
39//!     | 1       | 500ms|
40//!     | 2       | 500ms|
41//!     | 3       | 1000ms|
42//!     | 4       | 1500ms|
43//! - `LinearBackoff`: a Linear Backoff strategy is used. So if defined from 500ms, with increment of 100ms, then the next retry will be 600ms. If `increment` is not defined it will be equal to `initial`.
44//!     | attempt | delay |
45//!     |---------|-------|
46//!     | 1       | 500ms|
47//!     | 2       | 600ms|
48//!     | 3       | 700ms|
49//!
50//! > All strategies can be jittered with the `jitter` feature.
51//!
52//! # Installation
53//!
54//! Add this to your `Cargo.toml`:
55//!
56//! ```toml
57//! [dependencies]
58//! tokio-retry2 = "0.9"
59//! ```
60//!
61//! # Example
62//!
63//! ```rust,no_run
64
65//! use tokio_retry2::{Retry, RetryError};
66//! use tokio_retry2::strategy::{ExponentialBackoff, MaxInterval};
67//!
68//! async fn action() -> Result<u64, RetryError<()>> {
69//!     // do some real-world stuff here...
70//!     RetryError::to_permanent(())
71//! }
72//!
73//! # #[tokio::main]
74//! # async fn main() -> Result<(), RetryError<()>> {
75//! let retry_strategy = ExponentialBackoff::from_millis(10)
76//!     .factor(1) // multiplication factor applied to delay
77//!     .max_delay_millis(100) // set max delay between retries to 500ms
78//!     .max_interval(1000) // set max interval to 1 second for all retries
79//!     .take(3);    // limit to 3 retries
80//!
81//! let result = Retry::spawn(retry_strategy, action).await?;
82//! // First retry in 10ms, second in 100ms, third in 100ms
83
84//! # Ok(())
85//! # }
86//! ```
87//!
88//! ## Error Handling
89//!
90//! One key difference between `tokio-retry2` and `tokio-retry` is the fact that `tokio-retry2`
91//! supports early exits from the retry loop based on your error type. This allows you to pattern match
92//! your errors and define if you want to continue retrying or not, while `tokio-retry` only supported conditional `RetryIf`.
93//! The following functions are helper functions to deal with it:
94//!
95//! ```rust,no_run
96//! use tokio_retry2::{Retry, RetryError};
97//! use std::time::Duration;
98//!
99//! async fn action() -> Result<u64, RetryError<usize>> {
100//!     // do some real-world stuff here...
101//!     // get and error named `err`
102//! #   let err = std::io::ErrorKind::AddrInUse;
103//!     match err {
104//!         std::io::ErrorKind::NotFound => RetryError::to_permanent(1)?, // equivalent to return Err(RetryError::permanent(2))`;
105//!         std::io::ErrorKind::PermissionDenied => {
106//!             return Err(RetryError::permanent(2)); // equivalent to `RetryError::to_permanent(2)`
107//!         }
108//!         std::io::ErrorKind::ConnectionRefused => {
109//!             return Err(RetryError::transient(3)); // equivalent to `RetryError::to_transient(3)`
110//!         }
111//!         std::io::ErrorKind::ConnectionReset => {
112//!             return Err(RetryError::retry_after(4, Duration::from_millis(10)));
113//!             // equivalent to `RetryError::to_retry_after(4, Duration::from_millis(10))`
114//!         }
115//!         std::io::ErrorKind::ConnectionAborted =>
116//!             // equivalent to `RetryError::to_retry_after(5, Duration::from_millis(15))`
117//!             RetryError::to_retry_after(5, Duration::from_millis(15))?,
118//!         err => RetryError::to_transient(6)? // equivalent to `return Err(RetryError::transient(6))`
119//!     };
120//!     Ok(0)
121//! }
122//! ```
123//!
124//! ## Features
125//! `[jitter]`
126//! - `jitter` ranges between 50% and 150% of the strategy delay.
127//! - `jitter_with_bounds(min: f64, max: f64)` ranges between `min * Duration` and `max * Duration`.
128//!
129//! To use jitter, add this to your Cargo.toml
130//!
131//! ```toml
132//! [dependencies]
133//! tokio-retry2 = { version = "6", features = ["jitter"] }
134//! ```
135//!
136//! # Example
137//!
138//! ## `jitter`
139//!
140//! ```rust,no_run
141//! # #[cfg(feature = "jitter")]
142//! # {
143//! use tokio_retry2::Retry;
144//! use tokio_retry2::strategy::{ExponentialBackoff, jitter, MaxInterval};
145//!
146//! let retry_strategy = ExponentialBackoff::from_millis(10)
147//!    .max_interval(10000) // set max interval to 10 seconds
148//!    .map(jitter) // add jitter to the retry interval
149//!    .take(3);    // limit to 3 retries
150//! # }
151//!````
152//!
153//! ## `jitter_with_bounds`
154//!
155//! ```rust,no_run
156//! # #[cfg(feature = "jitter")]
157//! # {
158//! use tokio_retry2::Retry;
159//! use tokio_retry2::strategy::{ExponentialFactorBackoff, jitter_with_bounds, MaxInterval};
160//!
161//! let retry_strategy = ExponentialFactorBackoff::from_millis(10, 2.)
162//!    .max_interval(10000) // set max interval to 10 seconds
163//!    .map(jitter_with_bounds(0.5, 1.2)) // add jitter ranging between 50% and 120% to the retry interval
164//!    .take(3);    // limit to 3 retries
165//! }
166//!````
167//! ## `jitter_range`
168//!
169//! > Limited to integer values
170//!
171//! ```rust,no_run
172//! # #[cfg(feature = "jitter")]
173//! # {
174//! use tokio_retry2::Retry;
175//! use tokio_retry2::strategy::{ExponentialFactorBackoff, jitter_range, MaxInterval};
176//!
177//! let retry_strategy = ExponentialFactorBackoff::from_millis(10, 2.)
178//!    .max_interval(10000) // set max interval to 10 seconds
179//!    .map(jitter_range(0..1)) // add jitter ranging between 0% and 100% to the retry interval
180//!    .take(3);    // limit to 3 retries
181//! }
182//!````
183//!
184//! ### NOTE:
185//! The time spent executing an action does not affect the intervals between
186//! retries. Therefore, for long-running functions it's a good idea to set up a deadline,
187//! to place an upper bound on the strategy execution time.
188
189#![allow(warnings)]
190
191mod action;
192mod condition;
193pub(crate) mod error;
194mod future;
195mod notify;
196/// Assorted retry strategies including fixed interval and exponential back-off.
197pub mod strategy;
198
199pub use action::Action;
200pub use condition::Condition;
201pub use error::{Error as RetryError, MapErr};
202pub use future::{Retry, RetryIf};
203pub use notify::Notify;