use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
time::Duration,
};
pub fn jitter_ttl(base: Duration, ratio: f64, seed: impl Hash) -> Duration {
if base.is_zero() {
return base;
}
let ratio = ratio.clamp(0.0, 1.0);
if ratio == 0.0 {
return base;
}
let mut hasher = DefaultHasher::new();
seed.hash(&mut hasher);
let bucket = (hasher.finish() % 10_001) as f64 / 10_000.0;
let factor = 1.0 - ratio + bucket * ratio * 2.0;
Duration::from_secs_f64((base.as_secs_f64() * factor).max(0.001))
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::jitter_ttl;
#[test]
fn jitter_stays_inside_ratio_bounds() {
let ttl = jitter_ttl(Duration::from_secs(100), 0.05, "user:1");
assert!(ttl >= Duration::from_secs(95));
assert!(ttl <= Duration::from_secs(105));
}
#[test]
fn zero_ratio_keeps_original_ttl() {
let ttl = Duration::from_secs(60);
assert_eq!(jitter_ttl(ttl, 0.0, "key"), ttl);
}
}