Skip to main content

reifydb_runtime/context/clock/
native.rs

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