Skip to main content

reifydb_runtime/context/clock/
native.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::{
5	cmp, fmt, ops,
6	sync::{
7		Arc,
8		atomic::{AtomicU64, Ordering},
9	},
10	time,
11	time::{Duration, SystemTime, UNIX_EPOCH},
12};
13
14#[allow(clippy::disallowed_methods)]
15#[inline(always)]
16fn platform_now_nanos() -> u64 {
17	SystemTime::now().duration_since(UNIX_EPOCH).expect("System time is before Unix epoch").as_nanos() as u64
18}
19
20/// A clock that provides time - either real system time or mock time for testing.
21#[derive(Clone)]
22pub enum Clock {
23	/// Real system clock - delegates to platform time
24	Real,
25	/// Mock clock with controllable time
26	Mock(MockClock),
27}
28
29impl Clock {
30	/// Get current time in nanoseconds since Unix epoch
31	pub fn now_nanos(&self) -> u64 {
32		match self {
33			Clock::Real => platform_now_nanos(),
34			Clock::Mock(mock) => mock.now_nanos(),
35		}
36	}
37
38	/// Get current time in microseconds since Unix epoch
39	pub fn now_micros(&self) -> u64 {
40		self.now_nanos() / 1_000
41	}
42
43	/// Get current time in milliseconds since Unix epoch
44	pub fn now_millis(&self) -> u64 {
45		self.now_nanos() / 1_000_000
46	}
47
48	/// Get current time in seconds since Unix epoch
49	pub fn now_secs(&self) -> u64 {
50		self.now_nanos() / 1_000_000_000
51	}
52
53	#[allow(clippy::disallowed_methods)]
54	pub fn instant(&self) -> Instant {
55		match self {
56			Clock::Real => Instant {
57				inner: InstantInner::Real(time::Instant::now()),
58			},
59			Clock::Mock(mock) => Instant {
60				inner: InstantInner::Mock {
61					captured_nanos: mock.now_nanos(),
62					clock: mock.clone(),
63				},
64			},
65		}
66	}
67}
68
69/// Mock clock with atomic time storage for thread-safe access.
70#[derive(Clone)]
71pub struct MockClock {
72	inner: Arc<MockClockInner>,
73}
74
75struct MockClockInner {
76	time_nanos: AtomicU64,
77}
78
79impl MockClock {
80	/// Create a new mock clock starting at the given nanoseconds
81	pub fn new(initial_nanos: u64) -> Self {
82		Self {
83			inner: Arc::new(MockClockInner {
84				time_nanos: AtomicU64::new(initial_nanos),
85			}),
86		}
87	}
88
89	/// Create a new mock clock starting at the given milliseconds
90	pub fn from_millis(millis: u64) -> Self {
91		Self::new(millis * 1_000_000)
92	}
93
94	/// Get current time in nanoseconds
95	pub fn now_nanos(&self) -> u64 {
96		self.inner.time_nanos.load(Ordering::Acquire)
97	}
98
99	/// Get current time in microseconds
100	pub fn now_micros(&self) -> u64 {
101		self.now_nanos() / 1_000
102	}
103
104	/// Get current time in milliseconds
105	pub fn now_millis(&self) -> u64 {
106		self.now_nanos() / 1_000_000
107	}
108
109	/// Get current time in seconds
110	pub fn now_secs(&self) -> u64 {
111		self.now_nanos() / 1_000_000_000
112	}
113
114	/// Set time to specific nanoseconds
115	pub fn set_nanos(&self, nanos: u64) {
116		self.inner.time_nanos.store(nanos, Ordering::Release);
117	}
118
119	/// Set time to specific microseconds
120	pub fn set_micros(&self, micros: u64) {
121		self.set_nanos(micros * 1_000);
122	}
123
124	/// Set time to specific milliseconds
125	pub fn set_millis(&self, millis: u64) {
126		self.set_nanos(millis * 1_000_000);
127	}
128
129	/// Advance time by nanoseconds
130	pub fn advance_nanos(&self, nanos: u64) {
131		self.set_nanos(self.now_nanos().saturating_add(nanos));
132	}
133
134	/// Advance time by microseconds
135	pub fn advance_micros(&self, micros: u64) {
136		self.advance_nanos(micros * 1_000);
137	}
138
139	/// Advance time by milliseconds
140	pub fn advance_millis(&self, millis: u64) {
141		self.advance_nanos(millis * 1_000_000);
142	}
143
144	/// Advance time by seconds
145	pub fn advance_secs(&self, secs: u64) {
146		self.advance_nanos(secs * 1_000_000_000);
147	}
148
149	/// Advance time by minutes
150	pub fn advance_minutes(&self, minutes: u64) {
151		self.advance_secs(minutes * 60);
152	}
153
154	/// Advance time by hours
155	pub fn advance_hours(&self, hours: u64) {
156		self.advance_secs(hours * 3600);
157	}
158
159	/// Advance time by days
160	pub fn advance_days(&self, days: u64) {
161		self.advance_secs(days * 86400);
162	}
163}
164
165#[derive(Clone)]
166enum InstantInner {
167	Real(time::Instant),
168	Mock {
169		captured_nanos: u64,
170		clock: MockClock,
171	},
172}
173
174#[derive(Clone)]
175pub struct Instant {
176	inner: InstantInner,
177}
178
179impl Instant {
180	#[inline]
181	pub fn elapsed(&self) -> Duration {
182		match &self.inner {
183			InstantInner::Real(instant) => instant.elapsed(),
184			InstantInner::Mock {
185				captured_nanos,
186				clock,
187			} => {
188				let now = clock.now_nanos();
189				let elapsed_nanos = now.saturating_sub(*captured_nanos);
190				Duration::from_nanos(elapsed_nanos)
191			}
192		}
193	}
194
195	#[inline]
196	pub fn duration_since(&self, earlier: &Instant) -> Duration {
197		match (&self.inner, &earlier.inner) {
198			(InstantInner::Real(this), InstantInner::Real(other)) => this.duration_since(*other),
199			(
200				InstantInner::Mock {
201					captured_nanos: this_nanos,
202					..
203				},
204				InstantInner::Mock {
205					captured_nanos: other_nanos,
206					..
207				},
208			) => {
209				let elapsed = this_nanos.saturating_sub(*other_nanos);
210				Duration::from_nanos(elapsed)
211			}
212			_ => panic!("Cannot compare instants from different clock types"),
213		}
214	}
215}
216
217impl PartialEq for Instant {
218	fn eq(&self, other: &Self) -> bool {
219		match (&self.inner, &other.inner) {
220			(InstantInner::Real(a), InstantInner::Real(b)) => a == b,
221			(
222				InstantInner::Mock {
223					captured_nanos: a,
224					..
225				},
226				InstantInner::Mock {
227					captured_nanos: b,
228					..
229				},
230			) => a == b,
231			_ => panic!("Cannot compare instants from different clock types"),
232		}
233	}
234}
235
236impl Eq for Instant {}
237
238impl PartialOrd for Instant {
239	fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
240		Some(self.cmp(other))
241	}
242}
243
244impl Ord for Instant {
245	fn cmp(&self, other: &Self) -> cmp::Ordering {
246		match (&self.inner, &other.inner) {
247			(InstantInner::Real(a), InstantInner::Real(b)) => a.cmp(b),
248			(
249				InstantInner::Mock {
250					captured_nanos: a,
251					..
252				},
253				InstantInner::Mock {
254					captured_nanos: b,
255					..
256				},
257			) => a.cmp(b),
258			_ => panic!("Cannot compare instants from different clock types"),
259		}
260	}
261}
262
263impl ops::Add<Duration> for Instant {
264	type Output = Instant;
265
266	fn add(self, duration: Duration) -> Instant {
267		match self.inner {
268			InstantInner::Real(instant) => Instant {
269				inner: InstantInner::Real(instant + duration),
270			},
271			InstantInner::Mock {
272				captured_nanos,
273				clock,
274			} => Instant {
275				inner: InstantInner::Mock {
276					captured_nanos: captured_nanos.saturating_add(duration.as_nanos() as u64),
277					clock,
278				},
279			},
280		}
281	}
282}
283
284impl ops::Sub for &Instant {
285	type Output = Duration;
286
287	fn sub(self, other: &Instant) -> Duration {
288		self.duration_since(other)
289	}
290}
291
292impl fmt::Debug for Instant {
293	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294		match &self.inner {
295			InstantInner::Real(instant) => f.debug_tuple("Instant::Real").field(instant).finish(),
296			InstantInner::Mock {
297				captured_nanos,
298				..
299			} => f.debug_tuple("Instant::Mock").field(captured_nanos).finish(),
300		}
301	}
302}