tokio-retry

Composable async retry strategies — exponential backoff, Fibonacci backoff, and fixed intervals — for any Tokio future.

Library
Cargo
v0.3.2
144stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
63/100Good
Development Activity68
Maintenance44
Community60
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
75/100Good
Architecture78
Code Quality85
Innovation72
Learning Curve65

tokio-retry is a small, focused Rust crate that wraps any fallible async action in a retry loop driven by a pluggable delay strategy. Instead of hand-rolling a loop with tokio::time::sleep calls, you hand Retry::start an iterator of Durations and an FnMut() -> Future action; the crate polls the action, and on failure advances the strategy iterator to compute the next delay before trying again. The strategy module ships ExponentialBackoff, FibonacciBackoff, and FixedInterval as plain Iterator<Item = Duration> implementations, plus an optional jitter() helper that randomizes a delay to avoid thundering-herd retries across many clients.

Because strategies are just iterators, they compose with the standard iterator adapters — .take(n) caps the retry count, .map(jitter) adds randomness, and custom strategies are trivial to write by implementing Iterator<Item = Duration> yourself. RetryIf extends the same mechanism with a Condition<Error> predicate so only certain error types trigger a retry (e.g. retry on a transient network error but not on an authentication failure). The crate is no_std compatible outside of its tokio::time dependency, has no required feature flags beyond rand for jitter, and is a common building block underneath HTTP clients, database reconnection logic, and any other operation that talks to a flaky external system over a Tokio runtime.

What You Get

  • Retry::start(strategy, action) — drives an FnMut() -> Future action through repeated attempts using any Iterator<Item = Duration> as the delay schedule
  • RetryIf::start(strategy, action, condition) — the same driver, but only retries when a Condition<Error> predicate returns true for the failure
  • Three ready-made backoff strategies — ExponentialBackoff, FibonacciBackoff, and FixedInterval — each configurable with .factor() and .max_delay()
  • An optional jitter() helper (behind the default rand feature) that randomizes a computed delay to spread out simultaneous retries
  • no_std support outside of the tokio::time dependency, keeping the crate usable in constrained async runtimes

Common Use Cases

  • Retrying a failed HTTP request to a flaky upstream API with exponential backoff and jitter
  • Reconnecting to a database or message broker after a dropped connection using a fixed or Fibonacci interval
  • Wrapping cloud SDK calls that can hit transient rate limits, retrying only on retryable error codes via RetryIf
  • Polling an external job or task status endpoint with backoff until it reports completion or a hard failure
  • Building higher-level resilience utilities (circuit breakers, request queues) on top of a well-tested backoff primitive instead of reimplementing one

Under The Hood

Architecture tokio-retry’s core is a small pin-projected state machine in src/lib.rs: RetryState<A> is either Running { future: A::Future } or Sleeping { future: Sleep }, and RetryIf::poll alternates between the two — polling the in-flight action, and on Err, checking the Condition before advancing the strategy iterator, arming a tokio::time::sleep_until deadline, and switching state to Sleeping. Retry is a thin wrapper around RetryIf with an always-true condition, so the two entry points share one implementation. The strategy module is architecturally independent — each strategy is a plain Iterator<Item = Duration> with no knowledge of futures or polling at all, which is what lets .take(), .map(jitter), and custom iterators compose directly as retry schedules.

Tech Stack The crate targets Rust edition 2018 with an MSRV of 1.85, depends on tokio (only the time feature, not the full runtime), pin-project-lite for the state machine’s pinned enum, and an optional rand dependency (enabled by default) gating the jitter() function. It builds no_std aside from the tokio::time import, so it carries almost no transitive dependency weight. CI runs cargo test across Ubuntu, macOS, and Windows on both stable and beta Rust, plus a dedicated MSRV check, cargo doc with --document-private-items, cargo-semver-checks for API compatibility, and a cargo-deny dependency audit.

Code Quality Tests live in tests/future.rs as async integration tests (#[tokio::test]) covering single-attempt failure, retry-until-exhausted, retry-until-success, and conditional-retry paths, complemented by dense unit tests inside src/strategy.rs that exercise each backoff strategy’s saturation, max-delay clamping, and factor-scaling behavior. #[lints.rust] and #[lints.clippy] sections in Cargo.toml enable strict warnings (unreachable_pub, use_self, std_instead_of_core, upper_case_acronyms, manual_let_else, and more), and rustfmt/clippy gates run in CI across default, no-default, and all-features builds, so the small surface area is held to a high consistency bar.

API Design The public surface is deliberately tiny: two constructors (Retry::start, RetryIf::start), three strategy types with a shared .factor()/.max_delay() builder pattern, and a single jitter() free function. Any FnMut() -> impl Future<Output = Result<T, E>> works as an action with zero boilerplate — there’s no trait to implement for the common case — and strategies being plain iterators means existing knowledge of Iterator adapters transfers directly instead of requiring a bespoke DSL. The deprecated spawn() aliases (kept for one release with a #[deprecated] note pointing to start()) show a considered approach to breaking API changes.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search