Skip to main content

entelix_core/
time.rs

1//! `Clock` — monotonic-clock abstraction shared across the
2//! workspace. Living in `entelix-core` so any sub-crate that needs
3//! a time source (rate limiters, retry backoff, cost-rate windows,
4//! TTL pruning) can take a `&dyn Clock` without depending on
5//! `entelix-policy`.
6//!
7//! Production code wires [`SystemClock`] (delegates to
8//! `tokio::time::Instant`); tests pass a deterministic clock so
9//! `tokio::time::pause()` + manual `advance` make consumers walk a
10//! known schedule.
11
12/// Monotonic-clock abstraction. Implementors must produce strictly
13/// non-decreasing values; jitter or skew breaks any consumer that
14/// computes elapsed time from the difference of two reads
15/// (rate-limit buckets, exponential backoff, TTL windows).
16pub trait Clock: Send + Sync + 'static {
17    /// Microseconds since some fixed origin. The origin doesn't
18    /// matter — only differences are read by consumers.
19    fn now_micros(&self) -> u64;
20}
21
22/// `Clock` backed by `tokio::time::Instant`. Honours
23/// `tokio::time::pause` so test harnesses can simulate elapsed
24/// time without real waits.
25#[derive(Clone, Copy, Debug, Default)]
26pub struct SystemClock;
27
28impl Clock for SystemClock {
29    fn now_micros(&self) -> u64 {
30        // `tokio::time::Instant` uses a monotonic source; converting
31        // through a stable origin keeps the absolute value bounded
32        // for the process lifetime.
33        let origin = origin_instant();
34        let now = tokio::time::Instant::now();
35        u64::try_from(now.duration_since(origin).as_micros()).unwrap_or(u64::MAX)
36    }
37}
38
39fn origin_instant() -> tokio::time::Instant {
40    use std::sync::OnceLock;
41    static ORIGIN: OnceLock<tokio::time::Instant> = OnceLock::new();
42    *ORIGIN.get_or_init(tokio::time::Instant::now)
43}