Overview

Core concepts

One trait, an epoch-nanosecond timeline, two implementations, and grid-aligned intervals — plus the determinism laws they uphold.

The timeline

Time is i64 nanoseconds since the UNIX epoch — never a tokio Instant. Modelling the timeline as a plain integer is what lets a virtual clock own its own timeline as a first-class implementation rather than a hack: it can be constructed at any start point and advanced explicitly.

The Clock trait

Every component reads time through Clock. It is an async trait, Send + Sync + Debug + 'static, so it lives comfortably behind an Arc<dyn Clock>.

pub trait Clock: Send + Sync + Debug + 'static {
    fn now_ns(&self) -> i64;
    async fn sleep_until(&self, deadline_ns: i64);
    async fn sleep(&self, duration: Duration) { /* default: relative to now */ }
}
MethodDescription
now_ns()Current time, nanoseconds since the epoch. Non-decreasing within a process.
sleep_until(deadline_ns)Resolve when the clock reaches deadline_ns. A past-or-current deadline resolves immediately — which is what makes tests race-free: advancing before a sleeper registers still wakes it. Cancel-safe.
sleep(duration)Sleep for duration from now. A default method layered on sleep_until.
Cancel-safety

Every sleep_until caller is expected to sit inside a biased tokio::select! alongside shutdown. A cancelled wait drops cleanly; nothing is consumed until the sleep actually completes.

SystemClock — the live clock

SystemClock::new() performs the one sanctioned wall-clock read in the codebase, anchoring the epoch timeline to a monotonic Instant. From then on now_ns is the anchor plus the monotonic elapsed time, so it never goes backwards across an NTP step and costs no SystemTime syscall per call.

VirtualClock — the replay clock

VirtualClock::new(start_ns) owns a timeline you advance explicitly with advance_to(t_ns). It is the production replay clock and the test clock both. Waiters register on a binary heap keyed by (deadline, registration-order); advancing pops the due ones in that order.

let clock = VirtualClock::new(0);

// advance_to wakes due waiters in (deadline, seq) order, sets now to
// each waiter's deadline before waking it, and yields so the woken
// task runs while now == its deadline. Then now lands on t_ns.
clock.advance_to(3_000).await;
One advancer

In production exactly one component — the replay producer — calls advance_to; nothing else may. It advances virtual time to each recorded event's timestamp before emitting the event, which is what makes “live and replay share one code path” literally true.

Grid-aligned intervals

interval(clock, period) builds a ClockInterval whose first tick is at now + period. Each tick() returns the scheduled grid-aligned timestamp — not the observed time of firing — so downstream consumers (1-second publications, websocket heartbeat ts values) carry stamps that replay reproduces exactly.

Missed-tick behavior matches tokio's MissedTickBehavior::Skip: after a consumer stall the armed deadline fires late once (a prompt catch-up), then the schedule realigns to the first grid point strictly after now. Further missed ticks are skipped, never bursted.

The determinism laws

These contracts are load-bearing for every consumer's replay correctness, and are pinned by the unit suite that travelled with the code:

LawDescription
Monotonicitynow_ns never moves backwards within a process.
Wakes before advanceA woken timer observes now equal to its own deadline before time runs any further.
Scheduled, not observedInterval ticks carry the grid stamp, not the firing moment.
O(1) dead-waiter discardA cancelled sleep is dropped on pop without a wake or a yield.
Current-thread orderingStrict wake ordering holds on a current-thread runtime.