use std::future::Future;
use std::time::Duration;
use crate::rng::{sample_bounded, validate_sampling, CryptoRng, RngSource, Sampling};
#[derive(Debug)]
pub struct Delay {
min: Duration,
max: Option<Duration>,
sampling: Sampling,
rng: RngSource,
}
impl Delay {
pub fn uniform(min: Duration, max: Duration) -> Self {
assert!(
min <= max,
"delay bounds inverted: min {min:?} > max {max:?}"
);
Self {
min,
max: Some(max),
sampling: Sampling::Uniform,
rng: RngSource::default(),
}
}
pub fn poisson(mean: Duration) -> Self {
let mean_nanos = mean.as_nanos() as f64;
let sampling = Sampling::Poisson { mean: mean_nanos };
validate_sampling(&sampling);
Self {
min: Duration::ZERO,
max: None,
sampling,
rng: RngSource::default(),
}
}
pub fn normal(mean: Duration, std_dev: Duration) -> Self {
let sampling = Sampling::Normal {
mean: mean.as_nanos() as f64,
std_dev: std_dev.as_nanos() as f64,
};
validate_sampling(&sampling);
Self {
min: Duration::ZERO,
max: None,
sampling,
rng: RngSource::default(),
}
}
pub fn min(mut self, min: Duration) -> Self {
if let Some(max) = self.max {
assert!(
min <= max,
"delay bounds inverted: min {min:?} > max {max:?}"
);
}
self.min = min;
self
}
pub fn max(mut self, max: Duration) -> Self {
assert!(
self.min <= max,
"delay bounds inverted: min {:?} > max {max:?}",
self.min
);
self.max = Some(max);
self
}
pub fn bounds(mut self, min: Duration, max: Duration) -> Self {
assert!(
min <= max,
"delay bounds inverted: min {min:?} > max {max:?}"
);
self.min = min;
self.max = Some(max);
self
}
pub fn seed(mut self, seed: [u8; 32]) -> Self {
self.rng = RngSource::seeded(seed);
self
}
pub fn with_rng<R: CryptoRng + Send + 'static>(mut self, rng: R) -> Self {
self.rng = RngSource::custom(rng);
self
}
pub fn sample(&mut self) -> Duration {
let min = self.min.as_nanos() as f64;
let max = self
.max
.map(|m| m.as_nanos() as f64)
.unwrap_or(f64::INFINITY);
let nanos = sample_bounded(&mut self.rng, &self.sampling, min, max);
Duration::from_nanos(nanos.round() as u64)
}
pub async fn run<F: Future>(&mut self, future: F) -> F::Output {
let delay = self.sample();
crate::timer::sleep(delay).await;
future.await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uniform_samples_within_bounds() {
let mut d =
Delay::uniform(Duration::from_millis(5), Duration::from_millis(50)).seed([3u8; 32]);
for _ in 0..1000 {
let s = d.sample();
assert!(s >= Duration::from_millis(5) && s <= Duration::from_millis(50));
}
}
#[test]
fn poisson_samples_respect_max() {
let mut d = Delay::poisson(Duration::from_secs(3))
.max(Duration::from_secs(5))
.seed([4u8; 32]);
for _ in 0..1000 {
assert!(d.sample() <= Duration::from_secs(5));
}
}
#[test]
fn normal_samples_respect_bounds() {
let mut d = Delay::normal(Duration::from_secs(5), Duration::from_secs(2))
.bounds(Duration::from_secs(1), Duration::from_secs(8))
.seed([5u8; 32]);
for _ in 0..1000 {
let s = d.sample();
assert!(s >= Duration::from_secs(1) && s <= Duration::from_secs(8));
}
}
#[test]
fn poisson_with_min_only_terminates_and_shifts() {
let mut d = Delay::poisson(Duration::from_secs(2))
.min(Duration::from_secs(600))
.seed([8u8; 32]);
let n = 5_000u32;
let mut total = Duration::ZERO;
for _ in 0..n {
let s = d.sample();
assert!(s >= Duration::from_secs(600));
total += s;
}
let mean = total / n;
assert!(
mean >= Duration::from_secs(601) && mean <= Duration::from_secs(603),
"min-truncated poisson mean should be ~min + mean, got {mean:?}"
);
}
#[test]
fn bounds_rebind_below_previous_min() {
let mut d = Delay::uniform(Duration::from_secs(100), Duration::from_secs(200))
.bounds(Duration::ZERO, Duration::from_secs(20))
.seed([12u8; 32]);
for _ in 0..100 {
assert!(d.sample() <= Duration::from_secs(20));
}
}
#[test]
#[should_panic(expected = "delay bounds inverted")]
fn bounds_rejects_inverted_pair() {
let _ = Delay::poisson(Duration::from_secs(1))
.bounds(Duration::from_secs(9), Duration::from_secs(3));
}
#[test]
fn samples_are_independent_per_call() {
let mut d = Delay::uniform(Duration::ZERO, Duration::from_secs(1000)).seed([6u8; 32]);
let first = d.sample();
assert!(
(0..100).map(|_| d.sample()).any(|s| s != first),
"sampler returned the same value on every call"
);
}
#[test]
fn same_seed_reproduces_delay_sequence() {
let mut a = Delay::poisson(Duration::from_secs(2))
.max(Duration::from_secs(30))
.seed([9u8; 32]);
let mut b = Delay::poisson(Duration::from_secs(2))
.max(Duration::from_secs(30))
.seed([9u8; 32]);
for _ in 0..100 {
assert_eq!(a.sample(), b.sample());
}
}
#[test]
#[should_panic(expected = "delay bounds inverted")]
fn inverted_bounds_panic() {
let _ = Delay::uniform(Duration::from_secs(2), Duration::from_secs(1));
}
}