Skip to main content

reifydb_runtime/context/clock/
native.rs

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