| Crates.io | chrono-machines |
| lib.rs | chrono-machines |
| version | 0.3.0 |
| created_at | 2025-10-15 04:12:08.946772+00 |
| updated_at | 2025-12-27 23:52:37.2773+00 |
| description | Exponential, constant, and Fibonacci backoff retry library with full jitter support - no_std compatible |
| homepage | |
| repository | https://github.com/seuros/chrono_machines |
| max_upload_size | |
| id | 1883721 |
| size | 95,989 |
Pure Rust exponential backoff and retry library with full jitter support.
no_std Compatible: Works in embedded environments (with optional alloc).retry() API returning rich RetryOutcomenotify, on_success, and on_failure callbacksAdd to your Cargo.toml:
[dependencies]
chrono_machines = "0.2"
use chrono_machines::Policy;
let policy = Policy {
max_attempts: 5,
base_delay_ms: 100,
multiplier: 2.0,
max_delay_ms: 10_000,
};
// Calculate delay for first retry using full jitter (1.0)
let delay_ms = policy.calculate_delay(1, 1.0);
println!("Wait {}ms before retry", delay_ms);
use chrono_machines::Policy;
use rand::rngs::SmallRng;
use rand::SeedableRng;
let policy = Policy::default();
let mut rng = SmallRng::seed_from_u64(12345);
// Calculate delay with 50% jitter (0.5)
let delay = policy.calculate_delay_with_rng(1, 0.5, &mut rng);
use chrono_machines::{backoff::ExponentialBackoff, retry::Retryable};
let mut attempts = 0;
let operation = || {
attempts += 1;
if attempts < 3 {
Err("not yet")
} else {
Ok("success")
}
};
# #[cfg(feature = "std")]
let outcome = operation
.retry(ExponentialBackoff::default().max_attempts(5))
.notify(|err, attempt, delay| {
println!("attempt {attempt} failed ({err}), next delay {delay}ms");
})
.on_success(|value, attempt| {
println!("attempt {attempt} succeeded with {value}");
})
.on_failure(|err| {
eprintln!("retry stopped: {err}");
})
.call()
.expect("retry succeeds");
println!("took {} attempts", outcome.attempts());
println!("value = {}", outcome.into_inner());
std)use chrono_machines::{
backoff::{BackoffPolicy, ExponentialBackoff},
register_global_policy,
retry_with_policy,
};
register_global_policy(
"api",
BackoffPolicy::from(ExponentialBackoff::new().base_delay_ms(250).max_attempts(4)),
);
let outcome = retry_with_policy("api", || {
// fallible operation
Ok::<_, &'static str>("done")
})
.expect("named policy is registered");
assert_eq!(outcome.attempts(), 1);
ChronoMachines implements full jitter exponential backoff:
delay = random(0, min(base * multiplier^(attempt-1), max)) // with jitter_factor = 1.0
This approach:
base * multiplier^(attempt-1)max_delay_msjitter_factorFull jitter prevents the "thundering herd" problem where multiple clients retry simultaneously, overwhelming a recovering service. By randomizing the delay, retries are naturally distributed over time.
std (default)Enables standard library support and StdRng for calculate_delay() method.
no_stdDisable default features for no_std environments:
[dependencies]
chrono_machines = { version = "0.2", default-features = false }
You'll need to provide your own RNG and use calculate_delay_with_rng().
allocEnable the lightweight, vector-backed PolicyRegistry without the standard
library:
[dependencies]
chrono_machines = { version = "0.2", default-features = false, features = ["alloc"] }
Delays grow exponentially: base * multiplier^(attempt-1)
use chrono_machines::{ExponentialBackoff, Retryable};
operation.retry(
ExponentialBackoff::new()
.base_delay_ms(100)
.multiplier(2.0)
.max_delay_ms(10_000)
).call()
Fixed delay with optional jitter
use chrono_machines::{ConstantBackoff, Retryable};
operation.retry(
ConstantBackoff::new()
.delay_ms(500)
.jitter_factor(0.1)
).call()
Delays grow by Fibonacci sequence: 1, 1, 2, 3, 5, 8, 13...
use chrono_machines::{FibonacciBackoff, Retryable};
operation.retry(
FibonacciBackoff::new()
.base_delay_ms(100)
.max_delay_ms(5_000)
).call()
MIT