use std::time::Duration;
#[must_use]
pub fn backoff_delay(attempt: u32, base: Duration, cap: Duration, jitter_frac: f64) -> Duration {
let factor = 1u32.checked_shl(attempt.min(16)).unwrap_or(u32::MAX);
let exp = base.saturating_mul(factor).min(cap);
let scale = 0.5_f64.mul_add(jitter_frac.clamp(0.0, 1.0), 0.5);
exp.mul_f64(scale)
}
#[cfg(test)]
mod tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs)]
use std::time::Duration;
use super::backoff_delay;
#[test]
fn backoff_doubles_and_stays_inside_the_equal_jitter_band() {
let base = Duration::from_millis(500);
let cap = Duration::from_secs(30);
for attempt in 0..4 {
let un_jittered = base * (1 << attempt);
assert_eq!(backoff_delay(attempt, base, cap, 0.0), un_jittered / 2);
assert_eq!(backoff_delay(attempt, base, cap, 1.0), un_jittered);
}
}
#[test]
fn backoff_caps_and_saturates() {
let base = Duration::from_millis(500);
let cap = Duration::from_secs(30);
assert_eq!(backoff_delay(20, base, cap, 1.0), cap);
assert_eq!(backoff_delay(u32::MAX, base, cap, 1.0), cap);
}
}