rust
49 lines · 8 steps
Async retry with exponential backoff in Rust
A generic async helper retries a fallible operation, backing off exponentially with jitter until it succeeds or runs out of attempts.
Explained by
highlit
1use std::time::Duration;
2use tokio::time::sleep;
3
4#[derive(Debug)]
5pub struct RetryConfig {
6 pub max_attempts: u32,
7 pub base_delay: Duration,
8 pub max_delay: Duration,
9}
10
11impl Default for RetryConfig {
12 fn default() -> Self {
13 Self {
14 max_attempts: 5,
15 base_delay: Duration::from_millis(200),
16 max_delay: Duration::from_secs(30),
17 }
18 }
19}
20
21pub async fn retry_with_backoff<F, Fut, T, E>(
22 config: &RetryConfig,
23 mut operation: F,
24) -> Result<T, E>
25where
26 F: FnMut() -> Fut,
27 Fut: std::future::Future<Output = Result<T, E>>,
28 E: std::fmt::Display,
29{
30 let mut attempt = 0;
31
32 loop {
33 attempt += 1;
34 match operation().await {
35 Ok(value) => return Ok(value),
36 Err(err) if attempt >= config.max_attempts => {
37 tracing::error!(attempt, %err, "operation failed, exhausted retries");
38 return Err(err);
39 }
40 Err(err) => {
41 let exp = config.base_delay.saturating_mul(1 << (attempt - 1));
42 let delay = exp.min(config.max_delay);
43 let jitter = Duration::from_millis(fastrand::u64(0..=100));
44 tracing::warn!(attempt, ?delay, %err, "retrying after backoff");
45 sleep(delay + jitter).await;
46 }
47 }
48 }
49}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Exponential backoff with a capped maximum and random jitter spreads out retries and avoids thundering-herd retries.
- 2Generic bounds over a future-returning closure let one helper wrap any async fallible operation.
- 3Saturating arithmetic and a max clamp keep the delay calculation safe from overflow at high attempt counts.
Related explainers
javascript
async function copyToClipboard(text) { if (navigator.clipboard && window.isSecureContext) { try { await navigator.clipboard.writeText(text);
Copying to the clipboard with a fallback
clipboard
progressive-enhancement
dom
Intermediate
8 steps
typescript
interface PollOptions<T> { intervalMs?: number; timeoutMs?: number; signal?: AbortSignal;
A cancellable polling helper in TypeScript
polling
async-await
abortsignal
Intermediate
9 steps
rust
use std::fs; use std::io; use std::path::{Path, PathBuf};
Recursive file search by extension in Rust
recursion
filesystem
error-handling
Intermediate
8 steps
typescript
type Ok<T> = { ok: true; value: T }; type Err<E> = { ok: false; error: E }; export type Result<T, E> = Ok<T> | Err<E>;
A Result type for typed error handling
discriminated-union
error-handling
type-guards
Intermediate
8 steps
javascript
'use server' import { z } from 'zod' import { redirect } from 'next/navigation'
How a Next.js Server Action validates a form
server-actions
form-validation
zod
Intermediate
7 steps
javascript
const cache = new Map(); const inflight = new Map(); async function fetchResults(query) {
Building a typeahead with an LRU cache
caching
lru-eviction
request-deduplication
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/async-retry-with-exponential-backoff-in-rust-explained-rust-7791/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.