use rand::Rng;
use std::time::Duration;
pub fn next_delay(attempt: u32, initial_delay_ms: u32, cap_ms: u32) -> Duration {
let base = initial_delay_ms as u64;
let cap = cap_ms as u64;
let shift = attempt.min(62);
let ceiling = cap.min(base.saturating_mul(1u64 << shift));
let floor = base.min(ceiling);
let jittered = rand::thread_rng().gen_range(floor..=ceiling);
Duration::from_millis(jittered)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn delay_within_bounds() {
for attempt in 0..12u32 {
let d = next_delay(attempt, 1000, 3_600_000);
assert!(d.as_millis() <= 3_600_000);
}
}
#[test]
fn first_attempt_at_least_initial_delay() {
for _ in 0..200 {
let d = next_delay(0, 1000, 3_600_000);
assert!(d.as_millis() >= 1000, "delay was {}ms", d.as_millis());
assert!(d.as_millis() <= 1000); }
}
#[test]
fn second_attempt_within_double() {
for _ in 0..200 {
let d = next_delay(1, 1000, 3_600_000);
assert!(d.as_millis() >= 1000);
assert!(d.as_millis() <= 2000);
}
}
#[test]
fn caps_at_max() {
for attempt in 20..30u32 {
let d = next_delay(attempt, 1000, 60_000);
assert!(d.as_millis() <= 60_000);
assert!(d.as_millis() >= 1000);
}
}
}