Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Clock Injection — Testable Time

retry and repeat use a live clock by default. For deterministic tests, use the explicit-clock variants: retry_with_clock, repeat_with_clock, and their interruption-aware forms.

The Clock Trait

use std::time::{Duration, Instant};
use effectful::{Effect, Never};

trait Clock {
    fn now(&self) -> Instant;
    fn sleep(&self, duration: Duration) -> Effect<(), Never, ()>;
    fn sleep_until(&self, deadline: Instant) -> Effect<(), Never, ()>;
}

Clock is monotonic-time oriented. Calendar time for logging is exposed separately through LiveClock::now_utc().

Production: LiveClock

use effectful::{LiveClock, ThreadSleepRuntime};

let live_clock = LiveClock::new(ThreadSleepRuntime);

LiveClock delegates sleeping and now() to a runtime.

Testing: TestClock

use std::time::{Duration, Instant};
use effectful::{Clock, TestClock};

let start = Instant::now();
let clock = TestClock::new(start);

assert_eq!(clock.now(), start);

clock.advance(Duration::from_secs(60));
assert_eq!(clock.now(), start + Duration::from_secs(60));

TestClock records pending sleeps. Advancing or setting time drops pending sleeps whose deadlines have elapsed.

Test Example

use std::sync::{Arc, atomic::{AtomicU32, Ordering}};
use std::time::{Duration, Instant};
use effectful::{Schedule, TestClock, retry_with_clock, run_blocking};

let clock = TestClock::new(Instant::now());
let attempts = Arc::new(AtomicU32::new(0));

let effect = retry_with_clock(
    {
        let attempts = attempts.clone();
        move || failing_operation(attempts.clone())
    },
    Schedule::exponential(Duration::from_secs(1)).compose(Schedule::recurs(3)),
    clock.clone(),
    None,
);

let result = run_blocking(effect, ());
assert!(result.is_err());
assert_eq!(attempts.load(Ordering::Relaxed), 4); // initial + 3 retries

This test runs without sleeping in real time because TestClock::sleep only records deadlines.

Clock as a Service

For application logic that needs time directly, model the clock as a service in your environment. The scheduling helpers accept a clock value explicitly; your own services can do the same.