Async Retry with Exponential Backoff
Retry fallible async operations with capped exponential delays, per-attempt jitter, and a final error carrying the attempt count.
Code
use std::future::Future;
use std::time::Duration;
use tokio::time::sleep;
#[derive(Debug)]
struct RetryError {
attempts: u32,
last: String,
}
async fn retry<F, Fut, T, E>(
attempts: u32,
base: Duration,
mut operation: F,
) -> Result<T, RetryError>
where
F: FnMut(u32) -> Fut,
Fut: Future<Output = Result<T, E>>,
E: std::fmt::Display,
{
if attempts == 0 {
return Err(RetryError {
attempts,
last: "attempts must be positive".to_string(),
});
}
let mut last = String::new();
for attempt in 1..=attempts {
match operation(attempt).await {
Ok(value) => return Ok(value),
Err(error) => {
last = error.to_string();
if attempt < attempts {
let shift = (attempt - 1).min(5);
let jitter_ms = u64::from(attempt * 37 % 11);
let delay = base * (1u32 << shift) + Duration::from_millis(jitter_ms);
sleep(delay).await;
}
}
}
}
Err(RetryError { attempts, last })
}
#[tokio::main]
async fn main() {
let mut calls = 0u32;
let result = retry(4, Duration::from_millis(10), |_attempt| {
calls += 1;
let current = calls;
async move {
if current < 3 {
Err(format!("attempt {current} failed"))
} else {
Ok(current)
}
}
})
.await;
println!("{result:?}");
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.