<p align="center">
<img src="https://raw.githubusercontent.com/satyakwok/reliakit/main/assets/reliakit-logo.png" alt="Reliakit" width="400">
</p>
# reliakit-backoff
[](https://crates.io/crates/reliakit-backoff)
[](https://crates.io/crates/reliakit-backoff)
[](https://docs.rs/reliakit-backoff)
[](https://github.com/satyakwok/reliakit/actions/workflows/ci.yml)
[](https://codecov.io/gh/satyakwok/reliakit/tree/main/crates/reliakit-backoff)
[](https://github.com/satyakwok/reliakit/blob/main/LICENSE)
Clock-agnostic retry backoff policies for Rust.
`reliakit-backoff` computes *how long to wait* between retries. It does not
sleep, spawn tasks, or read the clock — you decide when to call it and how to
wait. That keeps it usable from synchronous code, any async runtime, and
`no_std` / embedded targets, with deterministic tests.
The crate has no dependencies, is `#![no_std]`, and forbids unsafe code.
## What This Crate Does
- `Backoff` — a small `Copy` policy: a base delay, a growth strategy (constant,
linear, or exponential), an optional maximum delay, and an optional retry
limit.
- `Backoff::delay(attempt)` — maps a zero-based attempt number to the delay to
wait, or `None` once the retry limit is reached. All arithmetic saturates, so
large attempt numbers never overflow, panic, or hang.
- `Backoff::delays()` — an iterator over successive delays.
- `full_jitter` / `equal_jitter` — pure jitter helpers that take a
caller-supplied random value, so the crate stays dependency-free and the math
stays testable.
## What This Crate Does Not Do
It does not sleep, retry for you, manage tasks, or own a random number
generator. It computes delays; you drive the loop and supply randomness. For an
opinionated executor, combine it with your runtime's timer.
## Installation
```toml
[dependencies]
reliakit-backoff = "0.1"
```
This crate is `no_std` and has no feature flags; it depends only on `core`.
## Example
```rust
use core::time::Duration;
use reliakit_backoff::{full_jitter, Backoff};
let policy = Backoff::exponential(Duration::from_millis(100), 2)
.with_max_delay(Duration::from_secs(2))
.with_max_retries(5);
assert_eq!(policy.delay(0), Some(Duration::from_millis(100)));
assert_eq!(policy.delay(1), Some(Duration::from_millis(200)));
assert_eq!(policy.delay(5), None); // retry limit reached
// Drive your own loop; supply randomness for jitter from your RNG.
let rand = 0x1234_5678u32;
for base in policy.delays() {
let wait = full_jitter(base, rand);
// sleep(wait); if try_operation().is_ok() { break; }
let _ = wait;
}
```
## Strategies
| `Backoff::constant(base)` | `base` |
| `Backoff::linear(base, step)` | `base + step * n` |
| `Backoff::exponential(base, factor)` | `base * factor^n` |
All are clamped to `with_max_delay(..)` and stop at `with_max_retries(..)`.
`factor` is an integer multiplier (e.g. `2` doubles each attempt).
## Jitter
| `full_jitter(delay, rand)` | `0 ..= delay` |
| `equal_jitter(delay, rand)` | `delay/2 ..= delay` |
`rand` is interpreted as the fraction `rand / u32::MAX`. Source it from `rand`,
`getrandom`, or a hardware RNG.
## Safety
This crate is `#![forbid(unsafe_code)]` and `#![no_std]`.
## Minimum Supported Rust Version
Rust `1.85` and newer. No nightly features are used.
## License
Licensed under the MIT License. See [`LICENSE`](https://github.com/satyakwok/reliakit/blob/main/LICENSE).