Application Sleeper Policy

Version 1.0.0 Updated Aug 24, 2026

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.