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 */ }
}| Method | Description |
|---|---|
| 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. |
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;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:
| Law | Description |
|---|---|
| Monotonicity | now_ns never moves backwards within a process. |
| Wakes before advance | A woken timer observes now equal to its own deadline before time runs any further. |
| Scheduled, not observed | Interval ticks carry the grid stamp, not the firing moment. |
| O(1) dead-waiter discard | A cancelled sleep is dropped on pop without a wake or a yield. |
| Current-thread ordering | Strict wake ordering holds on a current-thread runtime. |