use std::cell::Cell;
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
use std::time::Duration;
pub(crate) fn capped_exponential(base: Duration, factor: f64, n: u32, cap: Duration) -> Duration {
if base.is_zero() {
return Duration::ZERO;
}
let factor = normalized_factor(factor);
if let Some(fixed) = fixed_backoff(base, factor, n, cap) {
return fixed;
}
let exponent = n.min(i32::MAX as u32) as i32;
let secs = (base.as_secs_f64() * factor.powi(exponent)).min(cap.as_secs_f64());
Duration::try_from_secs_f64(secs).unwrap_or(cap)
}
#[mutants::skip]
fn normalized_factor(factor: f64) -> f64 {
if factor.is_finite() && factor > 1.0 {
factor
} else {
1.0
}
}
#[mutants::skip]
fn fixed_backoff(base: Duration, factor: f64, n: u32, cap: Duration) -> Option<Duration> {
(factor == 1.0 || n == 0).then(|| base.min(cap))
}
fn xorshift64(mut state: u64) -> u64 {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
}
pub(crate) fn unit_random_f64() -> f64 {
thread_local! {
static STATE: Cell<u64> = Cell::new(seed());
}
STATE.with(|state| {
let x = xorshift64(state.get());
state.set(x);
(x >> 11) as f64 / (1u64 << 53) as f64
})
}
#[mutants::skip]
fn seed() -> u64 {
let mut hasher = RandomState::new().build_hasher();
hasher.write_u64(0x9E37_79B9_7F4A_7C15);
hasher.finish() | 1 }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_base_never_waits() {
assert_eq!(
capped_exponential(Duration::ZERO, 2.0, 0, Duration::from_secs(1)),
Duration::ZERO
);
assert_eq!(
capped_exponential(Duration::ZERO, 2.0, u32::MAX, Duration::from_secs(1)),
Duration::ZERO
);
}
#[test]
fn exponential_growth_and_cap() {
let base = Duration::from_millis(100);
let cap = Duration::from_millis(450);
assert_eq!(capped_exponential(base, 2.0, 0, cap), base);
assert_eq!(
capped_exponential(base, 2.0, 1, cap),
Duration::from_millis(200)
);
assert_eq!(
capped_exponential(base, 2.0, 2, cap),
Duration::from_millis(400)
);
assert_eq!(capped_exponential(base, 2.0, 3, cap), cap); assert_eq!(capped_exponential(base, 2.0, 50, cap), cap); }
#[test]
fn non_finite_or_sub_unit_factor_is_fixed() {
let base = Duration::from_millis(100);
let cap = Duration::from_secs(5);
for bad in [
f64::NAN,
f64::INFINITY,
f64::NEG_INFINITY,
-1.0,
0.0,
0.5,
1.0,
] {
assert_eq!(capped_exponential(base, bad, 0, cap), base, "bad = {bad}");
assert_eq!(capped_exponential(base, bad, 3, cap), base, "bad = {bad}");
}
}
#[test]
fn unit_random_f64_is_in_range() {
for _ in 0..256 {
let u = unit_random_f64();
assert!((0.0..1.0).contains(&u), "out of [0, 1): {u}");
}
}
#[test]
fn xorshift_step_is_pinned_bit_for_bit() {
assert_eq!(xorshift64(1), 0x0000_0000_4082_2041);
assert_eq!(xorshift64(0x9E37_79B9_7F4A_7C15), 0xDC1B_77AE_0BF3_4DAD);
assert_eq!(xorshift64(u64::MAX), 0x0000_0000_3F80_1FC0);
}
}