Skip to main content

subms_rate_limiter/features/
clock.rs

1//! Injected monotonic clock. Production wires `SystemClock`
2//! (`Instant::elapsed` under the hood); tests wire `TestClock` to
3//! advance time deterministically without wall-clock sleeps.
4
5use std::sync::Mutex;
6use std::time::Instant;
7
8/// Monotonic ns-precision clock. Implementations must be thread-safe
9/// and must never go backwards.
10pub trait Clock: Send + Sync {
11    /// Nanoseconds since the clock's origin. Monotonic non-decreasing.
12    fn now_ns(&self) -> u64;
13}
14
15/// Wall-clock implementation. Origin is the moment the instance is
16/// constructed; `now_ns` returns `Instant::elapsed` against that origin.
17pub struct SystemClock {
18    origin: Instant,
19}
20
21impl SystemClock {
22    pub fn new() -> Self {
23        Self {
24            origin: Instant::now(),
25        }
26    }
27}
28
29impl Default for SystemClock {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl Clock for SystemClock {
36    fn now_ns(&self) -> u64 {
37        self.origin.elapsed().as_nanos() as u64
38    }
39}
40
41/// Deterministic clock for tests. `advance(ns)` moves the clock
42/// forward; `now_ns()` reads the current value.
43pub struct TestClock {
44    now: Mutex<u64>,
45}
46
47impl TestClock {
48    pub fn new() -> Self {
49        Self { now: Mutex::new(0) }
50    }
51
52    pub fn with_start(start_ns: u64) -> Self {
53        Self {
54            now: Mutex::new(start_ns),
55        }
56    }
57
58    pub fn advance(&self, ns: u64) {
59        let mut g = self.now.lock().unwrap();
60        *g = g.saturating_add(ns);
61    }
62
63    pub fn advance_ms(&self, ms: u64) {
64        self.advance(ms.saturating_mul(1_000_000));
65    }
66}
67
68impl Default for TestClock {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74impl Clock for TestClock {
75    fn now_ns(&self) -> u64 {
76        *self.now.lock().unwrap()
77    }
78}
79
80#[cfg(test)]
81#[path = "clock_tests.rs"]
82mod tests;