Monotony is a clock abstraction, not a sleeper abstraction. It does not own
blocking sleep, async runtime timers, retry policy, timeout policy, or
logical-time scaling. Applications that need both elapsed-time measurement and
waiting should compose MonotonicClock with their own sleeper boundary.
use std::time::{Duration, Instant};
use monotony::{MonotonicClock, test_util::SharedManualMonotonicClock};
trait Sleeper {
fn sleep(&mut self, duration: Duration);
}
struct AdvancingSleeper {
clock: SharedManualMonotonicClock,
total_slept: Duration,
}
impl AdvancingSleeper {
const fn new(clock: SharedManualMonotonicClock) -> Self {
Self {
clock,
total_slept: Duration::ZERO,
}
}
}
impl Sleeper for AdvancingSleeper {
fn sleep(&mut self, duration: Duration) {
self.clock.advance(duration);
self.total_slept += duration;
}
}
#[derive(Clone, Copy, Debug)]
struct WaitPolicy {
timeout: Duration,
interval: Duration,
}
fn wait_until_timeout(
clock: &dyn MonotonicClock,
sleeper: &mut dyn Sleeper,
started_at: Instant,
policy: WaitPolicy,
) {
assert!(
policy.interval > Duration::ZERO,
"wait interval must be greater than zero"
);
loop {
let elapsed = clock.now().duration_since(started_at);
if elapsed >= policy.timeout {
break;
}
let remaining = policy.timeout.saturating_sub(elapsed);
sleeper.sleep(remaining.min(policy.interval));
}
}
let started_at = Instant::now();
let observed_clock = SharedManualMonotonicClock::new(started_at);
let mut sleeper = AdvancingSleeper::new(observed_clock.clone());
wait_until_timeout(
&observed_clock,
&mut sleeper,
started_at,
WaitPolicy {
timeout: Duration::from_secs(5),
interval: Duration::from_secs(1),
},
);
assert_eq!(sleeper.total_slept, Duration::from_secs(5));
assert!(observed_clock.now().duration_since(started_at) >= Duration::from_secs(5));
In production, the same application-owned Sleeper trait could call
std::thread::sleep, delegate to an async runtime, or apply a test-time
acceleration policy. Monotony stays responsible only for monotonic time
observation.