Skip to main content

clawft_kernel/
mesh_clock.rs

1//! Injectable clock for mesh time-dependent components (WEFT-113).
2//!
3//! Mesh heartbeat, dedup TTL, service-resolution cache, and related
4//! logic must not hardwire `std::time::Instant::now()` / `SystemTime`
5//! if deterministic tests are required. Production code binds
6//! [`RealClock`]; tests bind [`MockClock`].
7
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::sync::Arc;
10use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
11
12/// Monotonic timestamp relative to an opaque origin (nanoseconds).
13///
14/// Comparable and subtractable without depending on wall-clock time.
15/// Values from different clock instances are not comparable.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
17pub struct MonoTime {
18    nanos: u64,
19}
20
21impl MonoTime {
22    /// Construct from nanoseconds since the clock origin.
23    pub const fn from_nanos(nanos: u64) -> Self {
24        Self { nanos }
25    }
26
27    /// Nanoseconds since the clock origin.
28    pub const fn as_nanos(self) -> u64 {
29        self.nanos
30    }
31
32    /// Duration elapsed from `earlier` to `self` (saturating).
33    pub fn duration_since(self, earlier: Self) -> Duration {
34        Duration::from_nanos(self.nanos.saturating_sub(earlier.nanos))
35    }
36
37    /// Alias used by heartbeat/dedup call sites for readability.
38    pub fn elapsed_since(self, earlier: Self) -> Duration {
39        self.duration_since(earlier)
40    }
41}
42
43/// Injectable time source for mesh components.
44///
45/// # Production
46///
47/// Bind [`RealClock`] (or `Arc::new(RealClock)`).
48///
49/// # Tests
50///
51/// Bind [`MockClock`] and call [`MockClock::advance`] to drive timeouts
52/// without sleeping.
53pub trait Clock: Send + Sync + 'static {
54    /// Current monotonic time (for timeouts / TTL).
55    fn now(&self) -> MonoTime;
56
57    /// Wall-clock microseconds since Unix epoch (for mesh time sync).
58    fn unix_time_us(&self) -> u64;
59}
60
61/// Production clock backed by `std::time`.
62#[derive(Debug, Clone, Copy, Default)]
63pub struct RealClock;
64
65impl RealClock {
66    /// Create a production clock.
67    pub const fn new() -> Self {
68        Self
69    }
70
71    /// Shared production clock handle.
72    pub fn shared() -> Arc<dyn Clock> {
73        Arc::new(Self)
74    }
75}
76
77impl Clock for RealClock {
78    fn now(&self) -> MonoTime {
79        // Instant is not epoch-based; measure from a process-global origin.
80        static ORIGIN: std::sync::OnceLock<Instant> = std::sync::OnceLock::new();
81        let origin = ORIGIN.get_or_init(Instant::now);
82        MonoTime::from_nanos(origin.elapsed().as_nanos() as u64)
83    }
84
85    fn unix_time_us(&self) -> u64 {
86        SystemTime::now()
87            .duration_since(UNIX_EPOCH)
88            .unwrap_or_default()
89            .as_micros() as u64
90    }
91}
92
93/// Controllable clock for deterministic time-dependent tests (WEFT-112).
94///
95/// Starts at monotonic `0` and unix epoch `0` unless configured.
96#[derive(Debug, Clone)]
97pub struct MockClock {
98    mono_nanos: Arc<AtomicU64>,
99    unix_us: Arc<AtomicU64>,
100}
101
102impl MockClock {
103    /// Create a mock clock at t=0 (mono and unix).
104    pub fn new() -> Self {
105        Self {
106            mono_nanos: Arc::new(AtomicU64::new(0)),
107            unix_us: Arc::new(AtomicU64::new(0)),
108        }
109    }
110
111    /// Create a mock clock with an initial unix time (microseconds).
112    pub fn with_unix_us(unix_us: u64) -> Self {
113        Self {
114            mono_nanos: Arc::new(AtomicU64::new(0)),
115            unix_us: Arc::new(AtomicU64::new(unix_us)),
116        }
117    }
118
119    /// Advance both mono and unix time by `duration`.
120    pub fn advance(&self, duration: Duration) {
121        let nanos = duration.as_nanos() as u64;
122        let us = duration.as_micros() as u64;
123        self.mono_nanos.fetch_add(nanos, Ordering::SeqCst);
124        self.unix_us.fetch_add(us, Ordering::SeqCst);
125    }
126
127    /// Set absolute monotonic time (nanoseconds since origin).
128    pub fn set_mono_nanos(&self, nanos: u64) {
129        self.mono_nanos.store(nanos, Ordering::SeqCst);
130    }
131
132    /// Set absolute unix time (microseconds).
133    pub fn set_unix_us(&self, us: u64) {
134        self.unix_us.store(us, Ordering::SeqCst);
135    }
136
137    /// Shared handle as `Arc<dyn Clock>`.
138    pub fn shared(self) -> Arc<dyn Clock> {
139        Arc::new(self)
140    }
141}
142
143impl Default for MockClock {
144    fn default() -> Self {
145        Self::new()
146    }
147}
148
149impl Clock for MockClock {
150    fn now(&self) -> MonoTime {
151        MonoTime::from_nanos(self.mono_nanos.load(Ordering::SeqCst))
152    }
153
154    fn unix_time_us(&self) -> u64 {
155        self.unix_us.load(Ordering::SeqCst)
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn mono_time_duration_since_saturating() {
165        let a = MonoTime::from_nanos(1_000);
166        let b = MonoTime::from_nanos(3_000);
167        assert_eq!(b.duration_since(a), Duration::from_nanos(2_000));
168        assert_eq!(a.duration_since(b), Duration::from_nanos(0));
169    }
170
171    #[test]
172    fn mock_clock_advance() {
173        let clock = MockClock::new();
174        assert_eq!(clock.now().as_nanos(), 0);
175        assert_eq!(clock.unix_time_us(), 0);
176        clock.advance(Duration::from_millis(500));
177        assert_eq!(clock.now().as_nanos(), 500_000_000);
178        assert_eq!(clock.unix_time_us(), 500_000);
179    }
180
181    #[test]
182    fn real_clock_unix_plausible() {
183        let t = RealClock.unix_time_us();
184        // After 2020-01-01, before 2050-01-01.
185        assert!(t > 1_577_836_800_000_000);
186        assert!(t < 2_524_608_000_000_000);
187    }
188
189    #[test]
190    fn real_clock_mono_advances() {
191        let c = RealClock;
192        let a = c.now();
193        let b = c.now();
194        assert!(b >= a);
195    }
196}