Skip to main content

Crate mailrs_backoff

Crate mailrs_backoff 

Source
Expand description

§mailrs-backoff

Crates.io docs.rs License

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

Presetinitialmultipliermaxjitter
smtp_outbound60s2.58hFull
auth_lockout30min2.024hNone
webhook60s2.06hEqual

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 configured Jitter policy.
  • 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):

OperationMedian
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.