Expand description
Make retry like a built-in feature provided by Rust.
- Simple: Just like a built-in feature:
your_fn.retry(ExponentialBuilder::default()).await. - Flexible: Supports both blocking and async functions.
- Powerful: Allows control over retry behavior such as
whenandnotify. - Customizable: Supports custom retry strategies like exponential, constant, etc.
§Backoff
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.
BackON provides several backoff implementations with reasonable defaults:
ConstantBuilder: backoff with a constant delay, limited to a specific number of attempts.ExponentialBuilder: backoff with an exponential delay, also supports jitter.FibonacciBuilder: backoff with a fibonacci delay, also supports jitter.
§Sleep
Retry in BackON requires an implementation for sleeping, such an implementation
is called a Sleeper, it will implement Sleeper or BlockingSleeper depending
on if it is going to be used in an asynchronous context.
§Default Sleeper
Currently, BackON has 3 built-in Sleeper implementations for different environments, they are gated under their own features, which are enabled by default:
Sleeper | feature | Environment | Asynchronous |
|---|---|---|---|
TokioSleeper | tokio-sleep | non-wasm32 | Yes |
[GlooTimersSleep] | gloo-timers-sleep | wasm32 | Yes |
[FutureTimerSleep] | future-timer-sleep | wasm/non-wasm | Yes |
[EmbassySleep] | embassy-sleep | no_std | Yes |
StdSleeper | std-blocking-sleep | std | No |
§Custom Sleeper
If you do not want to use the built-in Sleeper, you CAN provide a custom implementation, let’s implement an asynchronous dummy Sleeper that does not sleep at all. You will find it pretty similar when you implement a blocking one.
use std::time::Duration;
use backon::Sleeper;
/// A dummy `Sleeper` impl that prints then becomes ready!
struct DummySleeper;
impl Sleeper for DummySleeper {
type Sleep = std::future::Ready<()>;
fn sleep(&self, dur: Duration) -> Self::Sleep {
println!("Hello from DummySleeper!");
std::future::ready(())
}
}§The empty Sleeper
If neither feature is enabled nor a custom implementation is provided, BackON
will fallback to the empty sleeper, in which case, a compile-time error that
PleaseEnableAFeatureOrProvideACustomSleeper needs to implement Sleeper or BlockingSleeper will be raised to remind you to choose or bring a real Sleeper
implementation.
§Retry
For additional examples, please visit [docs::examples].
§Retry an async function
use anyhow::Result;
use backon::ExponentialBuilder;
use backon::Retryable;
use core::time::Duration;
async fn fetch() -> Result<String> {
Ok("hello, world!".to_string())
}
#[tokio::main]
async fn main() -> Result<()> {
let content = fetch
// Retry with exponential backoff
.retry(ExponentialBuilder::default())
// Sleep implementation, default to tokio::time::sleep if `tokio-sleep` has been enabled.
.sleep(tokio::time::sleep)
// When to retry
.when(|e| e.to_string() == "EOF")
// Notify when retrying
.notify(|err: &anyhow::Error, dur: Duration| {
println!("retrying {:?} after {:?}", err, dur);
})
.await?;
println!("fetch succeeded: {}", content);
Ok(())
}§Retry a blocking function
use anyhow::Result;
use backon::BlockingRetryable;
use backon::ExponentialBuilder;
use core::time::Duration;
fn fetch() -> Result<String> {
Ok("hello, world!".to_string())
}
fn main() -> Result<()> {
let content = fetch
// Retry with exponential backoff
.retry(ExponentialBuilder::default())
// Sleep implementation, default to std::thread::sleep if `std-blocking-sleep` has been enabled.
.sleep(std::thread::sleep)
// When to retry
.when(|e| e.to_string() == "EOF")
// Notify when retrying
.notify(|err: &anyhow::Error, dur: Duration| {
println!("retrying {:?} after {:?}", err, dur);
})
.call()?;
println!("fetch succeeded: {}", content);
Ok(())
}§Retry an async function with context
Sometimes users can meet the problem that the async function is needs to take FnMut:
error: captured variable cannot escape `FnMut` closure body
--> src/retry.rs:404:27
|
400 | let mut test = Test;
| -------- variable defined here
...
404 | let result = { || async { test.hello().await } }
| - ^^^^^^^^----^^^^^^^^^^^^^^^^
| | | |
| | | variable captured here
| | returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
| inferred to be a `FnMut` closure
|
= note: `FnMut` closures only have access to their captured variables while they are executing...
= note: ...therefore, they cannot allow references to captured variables to escapeRetryableWithContext is designed for this, it allows you to pass a context
to the retry function, and return it back after the retry is done.
use anyhow::anyhow;
use anyhow::Result;
use backon::ExponentialBuilder;
use backon::RetryableWithContext;
struct Test;
impl Test {
async fn hello(&mut self) -> Result<usize> {
Err(anyhow!("not retryable"))
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let mut test = Test;
// (Test, Result<usize>)
let (_, result) = {
|mut v: Test| async {
let res = v.hello().await;
(v, res)
}
}
.retry(ExponentialBuilder::default())
.context(test)
.await;
Ok(())
}Structs§
- Blocking
Retry - Retry structure generated by
BlockingRetryable. - Blocking
Retry With Context - Retry structure generated by
BlockingRetryableWithContext. - Constant
Builder - ConstantBuilder is used to create a [
ConstantBackoff], providing a steady delay with a fixed number of retries. - Exponential
Builder - ExponentialBuilder is used to construct an [
ExponentialBackoff] that offers delays with exponential retries. - Fibonacci
Builder - FibonacciBuilder is used to build a [
FibonacciBackoff] which offers a delay with Fibonacci-based retries. - Retry
- Struct generated by
Retryable. - Retry
With Context - Retry struct generated by
RetryableWithContext. - StdSleeper
- The implementation of
StdSleeperusesstd::thread::sleep. - Tokio
Sleeper - The default implementation of
Sleeperusestokio::time::sleep.
Traits§
- Backoff
- Backoff is an
Iteratorthat returnsDuration. - Backoff
Builder - BackoffBuilder is utilized to construct a new backoff.
- Blocking
Retryable - BlockingRetryable adds retry support for blocking functions.
- Blocking
Retryable With Context - BlockingRetryableWithContext adds retry support for blocking functions.
- Blocking
Sleeper - A sleeper is used sleep for a specified duration.
- Retryable
- Retryable will add retry support for functions that produce futures with results.
- Retryable
With Context RetryableWithContextadds retry support for functions that produce futures with results and context.- Sleeper
- A sleeper is used to generate a future that completes after a specified duration.
Type Aliases§
- Default
Blocking Sleeper - The default implementation of
Sleeperwhile featurestd-blocking-sleepenabled. - Default
Sleeper - The default implementation of
Sleeperwhile featuretokio-sleepenabled.