use std::collections::hash_map::RandomState;
use std::hash::BuildHasher;
use std::time::Duration;
const BASE: Duration = Duration::from_millis(500);
const CAP: Duration = Duration::from_secs(8);
pub(crate) const MAX_RETRY_AFTER: Duration = Duration::from_secs(60);
pub(crate) fn parse_retry_after(value: &str) -> Option<Duration> {
let value = value.trim();
if value.is_empty() || !value.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
value.parse::<u64>().ok().map(Duration::from_secs)
}
pub(crate) fn delay(attempt: u32, retry_after: Option<Duration>) -> Duration {
if let Some(wait) = retry_after {
return wait;
}
backoff(attempt, random_unit())
}
pub(crate) fn backoff(attempt: u32, unit: f64) -> Duration {
let base = BASE.saturating_mul(1u32.checked_shl(attempt).unwrap_or(u32::MAX));
let factor = 0.8 + 0.4 * unit.clamp(0.0, 1.0);
base.mul_f64(factor).min(CAP)
}
fn random_unit() -> f64 {
let bits = RandomState::new().hash_one(std::time::Instant::now());
(bits >> 11) as f64 / (1u64 << 53) as f64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn backoff_doubles_with_jitter_and_caps() {
assert_eq!(backoff(0, 0.5), Duration::from_millis(500));
assert_eq!(backoff(1, 0.5), Duration::from_millis(1000));
assert_eq!(backoff(2, 0.5), Duration::from_millis(2000));
assert_eq!(backoff(0, 0.0), Duration::from_millis(400));
assert_eq!(backoff(0, 1.0), Duration::from_millis(600));
assert_eq!(backoff(4, 1.0), CAP);
assert_eq!(backoff(40, 0.5), CAP);
}
#[test]
fn jitter_stays_in_range() {
for attempt in 0..6 {
for _ in 0..200 {
let d = delay(attempt, None);
let nominal = BASE * 2u32.pow(attempt);
assert!(d >= nominal.mul_f64(0.8).min(CAP), "{d:?} too short");
assert!(d <= nominal.mul_f64(1.2).min(CAP), "{d:?} too long");
}
}
}
#[test]
fn retry_after_wins_and_must_be_an_integer() {
assert_eq!(parse_retry_after("3"), Some(Duration::from_secs(3)));
assert_eq!(parse_retry_after(" 0 "), Some(Duration::ZERO));
assert_eq!(parse_retry_after("-1"), None);
assert_eq!(parse_retry_after("1.5"), None);
assert_eq!(parse_retry_after("Wed, 21 Oct 2026 07:28:00 GMT"), None);
assert_eq!(
delay(0, Some(Duration::from_secs(1))),
Duration::from_secs(1)
);
}
}