Expand description
§mailrs-backoff
Exponential backoff with optional jitter. Pure delay math — no I/O, no async, no RNG dependency. Useful for any retry loop: SMTP outbound delivery, webhook re-delivery, auth-lockout penalty, HTTP client retries, …
Follows AWS Architecture Blog’s Exponential Backoff and Jitter taxonomy:
- None — deterministic, simple, but causes thundering-herd at scale
- Equal — half fixed, half random, bounded smoothing
- Full — uniform random
[0, base], AWS’s recommended default
§Quickstart
use mailrs_backoff::{Backoff, Jitter};
use std::time::Duration;
// Use a preset:
let b = Backoff::smtp_outbound();
// Or roll your own:
let b = Backoff::new(
Duration::from_secs(1),
2.0, // doubling
Duration::from_secs(300), // cap at 5 minutes
Jitter::Full,
);
let seed = rand_seed();
for attempt in 0..b_max_attempts() {
let delay = b.delay(attempt, seed);
// ... sleep then retry ...
if Backoff::should_give_up(attempt + 1, 10) {
break;
}
}§Why bring your own seed?
This crate has zero runtime dependencies and no RNG state of its own. That means:
- No transitive pull-in of
rand,getrandom,wasi-libc, etc. - Deterministic tests — pass the same seed, get the same delay
- You control the entropy source (cryptographic if you need it, cheap if you don’t)
Typical seed source:
// Cheap, non-crypto — fine for jitter purposes:
let seed = std::time::Instant::now().elapsed().as_nanos() as u64;
// Or, if you already have rand in your deps:
// let seed = rand::random::<u64>();For Jitter::None, the seed is ignored entirely.
§Presets
| Preset | initial | multiplier | max | jitter |
|---|---|---|---|---|
smtp_outbound | 60s | 2.5 | 8h | Full |
auth_lockout | 30min | 2.0 | 24h | None |
webhook | 60s | 2.0 | 6h | Equal |
These match (or extend with jitter) the policies used inside mailrs-*
crates — see workspace comments. Pick a preset or tune your own; the
struct fields are all pub.
§What this crate does
Backoff::base_delay(attempt)—min(initial × multiplier^attempt, max), pure exponential without jitter. Useful for logging the “scheduled” delay alongside the actual jittered one.Backoff::delay(attempt, seed)— base delay with jitter applied per the configuredJitterpolicy.Backoff::should_give_up(attempt, max)— convenience predicate for retry loops.- Three presets:
smtp_outbound,auth_lockout,webhook.
§What this crate does not
- No async sleep helper. You sleep with
tokio::time::sleep/std::thread::sleep/ whatever your runtime gives you. - No retry loop runner. This crate computes durations; the loop is yours.
- No RNG. Bring your own seed.
- No “cap on total elapsed”. If you want “give up after 30
minutes of cumulative retries,” wrap with your own deadline check —
one extra
if Instant::now() > deadline { break }line.
§Performance
Measured (criterion, M-series Mac, release, 100-sample median):
| Operation | Median |
|---|---|
base_delay(attempt=3) | ~8 ns |
delay(attempt=3, Jitter::None) | ~23 ns |
delay(attempt=3, Jitter::Equal) | ~31 ns |
delay(attempt=3, Jitter::Full) | ~11 ns |
delay(attempt=100, capped) | ~10 ns |
should_give_up | <1 ns |
Reproduce: cargo bench -p mailrs-backoff --bench backoff. Workspace
PERFORMANCE.md carries the same table.
§License
Apache-2.0 OR MIT.
Structs§
- Backoff
- Exponential-backoff configuration.
Enums§
- Jitter
- Jitter policy applied to each delay sample.