Skip to main content

grate_limiter/
clock.rs

1use std::sync::Arc;
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::time::Instant;
4
5/// Monotonic timestamp in nanoseconds since engine creation.
6///
7/// Uses monotonic time internally — immune to NTP drift, daylight saving, and clock rewinds.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct Timestamp(pub(crate) u64);
10
11impl Timestamp {
12    /// Creates a timestamp at the zero epoch (engine start).
13    pub const ZERO: Timestamp = Timestamp(0);
14
15    /// Returns the timestamp value in nanoseconds.
16    pub fn as_nanos(&self) -> u64 {
17        self.0
18    }
19
20    /// Returns the timestamp value in milliseconds.
21    pub fn as_millis(&self) -> u64 {
22        self.0 / 1_000_000
23    }
24
25    /// Returns the timestamp value in seconds (f64).
26    pub fn as_secs_f64(&self) -> f64 {
27        self.0 as f64 / 1_000_000_000.0
28    }
29
30    /// Duration since another timestamp in nanoseconds.
31    /// Returns 0 if `other` is after `self`.
32    pub fn duration_since(&self, other: Timestamp) -> u64 {
33        self.0.saturating_sub(other.0)
34    }
35
36    /// Add nanoseconds to this timestamp.
37    pub fn add_nanos(&self, nanos: u64) -> Timestamp {
38        Timestamp(self.0.saturating_add(nanos))
39    }
40
41    /// Add milliseconds to this timestamp.
42    pub fn add_millis(&self, millis: u64) -> Timestamp {
43        Timestamp(self.0.saturating_add(millis * 1_000_000))
44    }
45
46    /// Add seconds to this timestamp.
47    pub fn add_secs(&self, secs: u64) -> Timestamp {
48        Timestamp(self.0.saturating_add(secs * 1_000_000_000))
49    }
50}
51
52/// Clock abstraction for monotonic time.
53///
54/// Use [`RealClock`] in production and [`MockClock`] for deterministic testing.
55pub trait Clock: Send + Sync {
56    /// Returns the current monotonic timestamp.
57    fn now(&self) -> Timestamp;
58}
59
60/// Real monotonic clock backed by [`std::time::Instant`].
61pub struct RealClock {
62    epoch: Instant,
63}
64
65impl RealClock {
66    pub fn new() -> Self {
67        Self {
68            epoch: Instant::now(),
69        }
70    }
71}
72
73impl Default for RealClock {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79impl Clock for RealClock {
80    fn now(&self) -> Timestamp {
81        let elapsed = self.epoch.elapsed();
82        Timestamp(elapsed.as_nanos() as u64)
83    }
84}
85
86/// Mock clock for deterministic testing.
87///
88/// Time only advances when explicitly told to, enabling fully reproducible test scenarios.
89///
90/// # Example
91///
92/// ```rust
93/// use grate_limiter::{MockClock, Clock};
94/// use std::sync::Arc;
95///
96/// let clock = Arc::new(MockClock::new());
97/// assert_eq!(clock.now().as_millis(), 0);
98///
99/// clock.advance_ms(5000);
100/// assert_eq!(clock.now().as_millis(), 5000);
101/// ```
102pub struct MockClock {
103    nanos: AtomicU64,
104}
105
106impl MockClock {
107    pub fn new() -> Self {
108        Self {
109            nanos: AtomicU64::new(0),
110        }
111    }
112
113    /// Create a mock clock starting at a specific timestamp.
114    pub fn at(timestamp: Timestamp) -> Self {
115        Self {
116            nanos: AtomicU64::new(timestamp.0),
117        }
118    }
119
120    /// Advance time by the given number of nanoseconds.
121    pub fn advance_nanos(&self, nanos: u64) {
122        self.nanos.fetch_add(nanos, Ordering::Release);
123    }
124
125    /// Advance time by the given number of milliseconds.
126    pub fn advance_ms(&self, ms: u64) {
127        self.advance_nanos(ms * 1_000_000);
128    }
129
130    /// Advance time by the given number of seconds.
131    pub fn advance_secs(&self, secs: u64) {
132        self.advance_nanos(secs * 1_000_000_000);
133    }
134
135    /// Set time to a specific timestamp.
136    pub fn set(&self, timestamp: Timestamp) {
137        self.nanos.store(timestamp.0, Ordering::Release);
138    }
139}
140
141impl Default for MockClock {
142    fn default() -> Self {
143        Self::new()
144    }
145}
146
147impl Clock for MockClock {
148    fn now(&self) -> Timestamp {
149        Timestamp(self.nanos.load(Ordering::Acquire))
150    }
151}
152
153/// Default clock factory — returns a real clock.
154pub(crate) fn default_clock() -> Arc<dyn Clock> {
155    Arc::new(RealClock::new())
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn timestamp_arithmetic() {
164        let t = Timestamp::ZERO;
165        assert_eq!(t.as_nanos(), 0);
166        assert_eq!(t.as_millis(), 0);
167
168        let t2 = t.add_millis(1500);
169        assert_eq!(t2.as_millis(), 1500);
170        assert_eq!(t2.as_nanos(), 1_500_000_000);
171
172        let t3 = t2.add_secs(2);
173        assert_eq!(t3.as_millis(), 3500);
174
175        assert_eq!(t3.duration_since(t2), 2_000_000_000);
176        assert_eq!(t2.duration_since(t3), 0); // saturating
177    }
178
179    #[test]
180    fn mock_clock_advances() {
181        let clock = MockClock::new();
182        assert_eq!(clock.now(), Timestamp::ZERO);
183
184        clock.advance_ms(100);
185        assert_eq!(clock.now().as_millis(), 100);
186
187        clock.advance_secs(1);
188        assert_eq!(clock.now().as_millis(), 1100);
189    }
190
191    #[test]
192    fn mock_clock_set() {
193        let clock = MockClock::new();
194        clock.set(Timestamp(5_000_000_000));
195        assert_eq!(clock.now().as_secs_f64(), 5.0);
196    }
197
198    #[test]
199    fn real_clock_monotonic() {
200        let clock = RealClock::new();
201        let t1 = clock.now();
202        let t2 = clock.now();
203        assert!(t2 >= t1);
204    }
205
206    #[test]
207    fn timestamp_ordering() {
208        let a = Timestamp(100);
209        let b = Timestamp(200);
210        assert!(a < b);
211        assert!(b > a);
212        assert_eq!(a, Timestamp(100));
213    }
214}