backon/
lib.rs

1#![doc(
2    html_logo_url = "https://raw.githubusercontent.com/Xuanwo/backon/main/.github/assets/logo.jpeg"
3)]
4#![cfg_attr(docsrs, feature(doc_auto_cfg))]
5
6//! [![Build Status]][actions] [![Latest Version]][crates.io] [![](https://img.shields.io/discord/1111711408875393035?logo=discord&label=discord)](https://discord.gg/8ARnvtJePD)
7//!
8//! [Build Status]: https://img.shields.io/github/actions/workflow/status/Xuanwo/backon/ci.yml?branch=main
9//! [actions]: https://github.com/Xuanwo/backon/actions?query=branch%3Amain
10//! [Latest Version]: https://img.shields.io/crates/v/backon.svg
11//! [crates.io]: https://crates.io/crates/backon
12//!
13//! <img src="https://raw.githubusercontent.com/Xuanwo/backon/main/.github/assets/logo.jpeg" alt="BackON" width="38.2%"/>
14//!
15//! Make **retry** like a built-in feature provided by Rust.
16//!
17//! - **Simple**: Just like a built-in feature: `your_fn.retry(ExponentialBuilder::default()).await`.
18//! - **Flexible**: Supports both blocking and async functions.
19//! - **Powerful**: Allows control over retry behavior such as [`when`](https://docs.rs/backon/latest/backon/struct.Retry.html#method.when) and [`notify`](https://docs.rs/backon/latest/backon/struct.Retry.html#method.notify).
20//! - **Customizable**: Supports custom retry strategies like [exponential](https://docs.rs/backon/latest/backon/struct.ExponentialBuilder.html), [constant](https://docs.rs/backon/latest/backon/struct.ConstantBuilder.html), etc.
21//!
22//! # Backoff
23//!
24//! Retry in BackON requires a backoff strategy. BackON will accept a [`BackoffBuilder`] which will generate a new [`Backoff`] for each retry. It also accepts any object that implements [`Backoff`]. You can therefore easily implement your own custom backoff strategy.
25//!
26//! BackON provides several backoff implementations with reasonable defaults:
27//!
28//! - [`ConstantBuilder`]: backoff with a constant delay, limited to a specific number of attempts.
29//! - [`ExponentialBuilder`]: backoff with an exponential delay, also supports jitter.
30//! - [`FibonacciBuilder`]: backoff with a fibonacci delay, also supports jitter.
31//!
32//! # Sleep
33//!
34//! Retry in BackON requires an implementation for sleeping, such an implementation
35//! is called a Sleeper, it will implement [`Sleeper`] or [`BlockingSleeper`] depending
36//! on if it is going to be used in an asynchronous context.
37//!
38//! ## Default Sleeper
39//!
40//! Currently, BackON has 3 built-in Sleeper implementations for different
41//! environments, they are gated under their own features, which are enabled
42//! by default:
43//!
44//! |      `Sleeper`       | feature            | Environment |  Asynchronous |
45//! |----------------------|--------------------|-------------|---------------|
46//! | [`TokioSleeper`]     | tokio-sleep        | non-wasm32  |  Yes          |
47//! | [`GlooTimersSleep`]  | gloo-timers-sleep  |   wasm32    |  Yes          |
48//! | [`FutureTimerSleep`] | future-timer-sleep |wasm/non-wasm|  Yes          |
49//! | [`EmbassySleep`]     | embassy-sleep      |   no_std    |  Yes          |
50//! | [`StdSleeper`]       | std-blocking-sleep |    std      |  No           |
51//!
52//! ## Custom Sleeper
53//!
54//! If you do not want to use the built-in Sleeper, you CAN provide a custom
55//! implementation, let's implement an asynchronous dummy Sleeper that does
56//! not sleep at all. You will find it pretty similar when you implement a
57//! blocking one.
58//!
59//! ```
60//! use std::time::Duration;
61//!
62//! use backon::Sleeper;
63//!
64//! /// A dummy `Sleeper` impl that prints then becomes ready!
65//! struct DummySleeper;
66//!
67//! impl Sleeper for DummySleeper {
68//!     type Sleep = std::future::Ready<()>;
69//!
70//!     fn sleep(&self, dur: Duration) -> Self::Sleep {
71//!         println!("Hello from DummySleeper!");
72//!         std::future::ready(())
73//!     }
74//! }
75//! ```
76//!
77//! ## The empty Sleeper
78//!
79//! If neither feature is enabled nor a custom implementation is provided, BackON
80//! will fallback to the empty sleeper, in which case, a compile-time error that
81//! `PleaseEnableAFeatureOrProvideACustomSleeper needs to implement Sleeper or
82//! BlockingSleeper` will be raised to remind you to choose or bring a real Sleeper
83//! implementation.
84//!
85//! # Retry
86//!
87//! For additional examples, please visit [`docs::examples`].
88//!
89//! ## Retry an async function
90//!
91//! ```rust
92//! use anyhow::Result;
93//! use backon::ExponentialBuilder;
94//! use backon::Retryable;
95//! use core::time::Duration;
96//!
97//! async fn fetch() -> Result<String> {
98//!     Ok("hello, world!".to_string())
99//! }
100//!
101//! #[tokio::main]
102//! async fn main() -> Result<()> {
103//!     let content = fetch
104//!         // Retry with exponential backoff
105//!         .retry(ExponentialBuilder::default())
106//!         // Sleep implementation, default to tokio::time::sleep if `tokio-sleep` has been enabled.
107//!         .sleep(tokio::time::sleep)
108//!         // When to retry
109//!         .when(|e| e.to_string() == "EOF")
110//!         // Notify when retrying
111//!         .notify(|err: &anyhow::Error, dur: Duration| {
112//!             println!("retrying {:?} after {:?}", err, dur);
113//!         })
114//!         .await?;
115//!     println!("fetch succeeded: {}", content);
116//!
117//!     Ok(())
118//! }
119//! ```
120//!
121//! ## Retry a blocking function
122//!
123//! ```rust
124//! use anyhow::Result;
125//! use backon::BlockingRetryable;
126//! use backon::ExponentialBuilder;
127//! use core::time::Duration;
128//!
129//! fn fetch() -> Result<String> {
130//!     Ok("hello, world!".to_string())
131//! }
132//!
133//! fn main() -> Result<()> {
134//!     let content = fetch
135//!         // Retry with exponential backoff
136//!         .retry(ExponentialBuilder::default())
137//!         // Sleep implementation, default to std::thread::sleep if `std-blocking-sleep` has been enabled.
138//!         .sleep(std::thread::sleep)
139//!         // When to retry
140//!         .when(|e| e.to_string() == "EOF")
141//!         // Notify when retrying
142//!         .notify(|err: &anyhow::Error, dur: Duration| {
143//!             println!("retrying {:?} after {:?}", err, dur);
144//!         })
145//!         .call()?;
146//!     println!("fetch succeeded: {}", content);
147//!
148//!     Ok(())
149//! }
150//! ```
151//!
152//! ## Retry an async function with context
153//!
154//! Sometimes users can meet the problem that the async function is needs to take `FnMut`:
155//!
156//! ```shell
157//! error: captured variable cannot escape `FnMut` closure body
158//!    --> src/retry.rs:404:27
159//!     |
160//! 400 |         let mut test = Test;
161//!     |             -------- variable defined here
162//! ...
163//! 404 |         let result = { || async { test.hello().await } }
164//!     |                         - ^^^^^^^^----^^^^^^^^^^^^^^^^
165//!     |                         | |       |
166//!     |                         | |       variable captured here
167//!     |                         | returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
168//!     |                         inferred to be a `FnMut` closure
169//!     |
170//!     = note: `FnMut` closures only have access to their captured variables while they are executing...
171//!     = note: ...therefore, they cannot allow references to captured variables to escape
172//! ```
173//!
174//! `RetryableWithContext` is designed for this, it allows you to pass a context
175//! to the retry function, and return it back after the retry is done.
176//!
177//! ```no_run
178//! use anyhow::anyhow;
179//! use anyhow::Result;
180//! use backon::ExponentialBuilder;
181//! use backon::RetryableWithContext;
182//!
183//! struct Test;
184//!
185//! impl Test {
186//!     async fn hello(&mut self) -> Result<usize> {
187//!         Err(anyhow!("not retryable"))
188//!     }
189//! }
190//!
191//! #[tokio::main(flavor = "current_thread")]
192//! async fn main() -> Result<()> {
193//!     let mut test = Test;
194//!
195//!     // (Test, Result<usize>)
196//!     let (_, result) = {
197//!         |mut v: Test| async {
198//!             let res = v.hello().await;
199//!             (v, res)
200//!         }
201//!     }
202//!     .retry(ExponentialBuilder::default())
203//!     .context(test)
204//!     .await;
205//!
206//!     Ok(())
207//! }
208//! ```
209
210#![deny(missing_docs)]
211#![deny(unused_qualifications)]
212#![no_std]
213
214#[cfg(feature = "std-blocking-sleep")]
215extern crate std;
216
217mod backoff;
218pub use backoff::*;
219
220mod retry;
221pub use retry::Retry;
222pub use retry::Retryable;
223
224mod retry_with_context;
225pub use retry_with_context::RetryWithContext;
226pub use retry_with_context::RetryableWithContext;
227
228mod sleep;
229pub use sleep::DefaultSleeper;
230#[cfg(all(target_arch = "wasm32", feature = "gloo-timers-sleep"))]
231pub use sleep::GlooTimersSleep;
232pub use sleep::Sleeper;
233#[cfg(all(not(target_arch = "wasm32"), feature = "tokio-sleep"))]
234pub use sleep::TokioSleeper;
235
236mod blocking_retry;
237pub use blocking_retry::BlockingRetry;
238pub use blocking_retry::BlockingRetryable;
239
240mod blocking_retry_with_context;
241pub use blocking_retry_with_context::BlockingRetryWithContext;
242pub use blocking_retry_with_context::BlockingRetryableWithContext;
243
244mod blocking_sleep;
245pub use blocking_sleep::BlockingSleeper;
246pub use blocking_sleep::DefaultBlockingSleeper;
247#[cfg(feature = "std-blocking-sleep")]
248pub use blocking_sleep::StdSleeper;
249
250#[cfg(feature = "embassy-sleep")]
251mod embassy_timer_sleep;
252#[cfg(feature = "embassy-sleep")]
253pub use embassy_timer_sleep::EmbassySleeper;
254
255#[cfg(docsrs)]
256pub mod docs;