Skip to main content

reifydb_runtime/context/clock/
host.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4#![allow(clippy::disallowed_types)]
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
16use reifydb_value::value::{datetime::DateTime, duration::Duration as RvDuration};
17
18use crate::{
19	context::clock::{TimerId, TimerWake},
20	sync::mutex::Mutex,
21};
22
23#[allow(clippy::disallowed_methods)]
24#[inline(always)]
25fn platform_now_nanos() -> u64 {
26	SystemTime::now().duration_since(UNIX_EPOCH).expect("System time is before Unix epoch").as_nanos() as u64
27}
28
29#[derive(Clone)]
30pub enum Clock {
31	Real,
32
33	Mock(MockClock),
34}
35
36impl Clock {
37	pub fn now(&self) -> DateTime {
38		match self {
39			Clock::Real => DateTime::from_nanos(
40				i64::try_from(platform_now_nanos()).expect("system clock past 2262-04-11"),
41			),
42			Clock::Mock(mock) => mock.now(),
43		}
44	}
45
46	#[allow(clippy::disallowed_methods)]
47	pub fn instant(&self) -> Instant {
48		match self {
49			Clock::Real => Instant {
50				inner: InstantInner::Real(time::Instant::now()),
51			},
52			Clock::Mock(mock) => Instant {
53				inner: InstantInner::Mock {
54					captured_nanos: u64::try_from(mock.now().to_nanos())
55						.expect("mock clock is never before the Unix epoch"),
56					clock: mock.clone(),
57				},
58			},
59		}
60	}
61
62	pub fn testing() -> Self {
63		#[cfg(reifydb_dst)]
64		return Clock::Mock(MockClock::from_millis(0));
65		#[cfg(not(reifydb_dst))]
66		return Clock::Real;
67	}
68
69	pub fn is_mock(&self) -> bool {
70		matches!(self, Clock::Mock(_))
71	}
72
73	pub fn as_mock(&self) -> Option<&MockClock> {
74		match self {
75			Clock::Mock(mock) => Some(mock),
76			Clock::Real => None,
77		}
78	}
79}
80
81#[derive(Clone)]
82pub struct MockClock {
83	inner: Arc<MockClockInner>,
84}
85
86struct MockClockInner {
87	time_nanos: AtomicU64,
88	timers: Mutex<Vec<Timer>>,
89	next_timer_id: AtomicU64,
90}
91
92struct Timer {
93	id: u64,
94	deadline_nanos: u64,
95	wake: Arc<dyn TimerWake>,
96}
97
98impl MockClock {
99	pub fn new(initial_nanos: u64) -> Self {
100		Self {
101			inner: Arc::new(MockClockInner {
102				time_nanos: AtomicU64::new(initial_nanos),
103				timers: Mutex::new(Vec::new()),
104				next_timer_id: AtomicU64::new(0),
105			}),
106		}
107	}
108
109	pub fn from_millis(millis: u64) -> Self {
110		Self::new(millis * 1_000_000)
111	}
112
113	pub fn now(&self) -> DateTime {
114		DateTime::from_nanos(
115			i64::try_from(self.inner.time_nanos.load(Ordering::Acquire))
116				.expect("mock clock past 2262-04-11"),
117		)
118	}
119
120	pub fn set_nanos(&self, nanos: u64) {
121		self.inner.time_nanos.store(nanos, Ordering::Release);
122		self.fire_due(nanos);
123	}
124
125	pub fn register_timer(&self, deadline_nanos: u64, wake: Arc<dyn TimerWake>) -> TimerId {
126		let id = self.inner.next_timer_id.fetch_add(1, Ordering::Relaxed);
127		self.inner.timers.lock().push(Timer {
128			id,
129			deadline_nanos,
130			wake,
131		});
132		TimerId(id)
133	}
134
135	pub fn cancel_timer(&self, timer: TimerId) {
136		self.inner.timers.lock().retain(|entry| entry.id != timer.0);
137	}
138
139	fn fire_due(&self, now_nanos: u64) {
140		let due = {
141			let mut timers = self.inner.timers.lock();
142			let mut due: Vec<Arc<dyn TimerWake>> = Vec::new();
143			timers.retain(|entry| {
144				if entry.deadline_nanos <= now_nanos {
145					due.push(entry.wake.clone());
146					return false;
147				}
148				true
149			});
150			due
151		};
152
153		for wake in due {
154			wake.wake();
155		}
156	}
157
158	pub fn set_micros(&self, micros: u64) {
159		self.set_nanos(micros * 1_000);
160	}
161
162	pub fn set_millis(&self, millis: u64) {
163		self.set_nanos(millis * 1_000_000);
164	}
165
166	pub fn advance_nanos(&self, nanos: u64) {
167		let current = u64::try_from(self.now().to_nanos()).expect("mock clock is never before the Unix epoch");
168		self.set_nanos(current.saturating_add(nanos));
169	}
170
171	pub fn advance_micros(&self, micros: u64) {
172		self.advance_nanos(micros * 1_000);
173	}
174
175	pub fn advance_millis(&self, millis: u64) {
176		self.advance_nanos(millis * 1_000_000);
177	}
178
179	pub fn advance_secs(&self, secs: u64) {
180		self.advance_nanos(secs * 1_000_000_000);
181	}
182
183	pub fn advance_minutes(&self, minutes: u64) {
184		self.advance_secs(minutes * 60);
185	}
186
187	pub fn advance_hours(&self, hours: u64) {
188		self.advance_secs(hours * 3600);
189	}
190
191	pub fn advance_days(&self, days: u64) {
192		self.advance_secs(days * 86400);
193	}
194}
195
196#[derive(Clone)]
197enum InstantInner {
198	Real(time::Instant),
199	Mock {
200		captured_nanos: u64,
201		clock: MockClock,
202	},
203}
204
205#[derive(Clone)]
206pub struct Instant {
207	inner: InstantInner,
208}
209
210impl Instant {
211	#[inline]
212	pub fn elapsed(&self) -> Duration {
213		match &self.inner {
214			InstantInner::Real(instant) => instant.elapsed(),
215			InstantInner::Mock {
216				captured_nanos,
217				clock,
218			} => {
219				let now = u64::try_from(clock.now().to_nanos())
220					.expect("mock clock is never before the Unix epoch");
221				let elapsed_nanos = now.saturating_sub(*captured_nanos);
222				Duration::from_nanos(elapsed_nanos)
223			}
224		}
225	}
226
227	#[inline]
228	pub fn duration_since(&self, earlier: &Instant) -> Duration {
229		match (&self.inner, &earlier.inner) {
230			(InstantInner::Real(this), InstantInner::Real(other)) => this.duration_since(*other),
231			(
232				InstantInner::Mock {
233					captured_nanos: this_nanos,
234					..
235				},
236				InstantInner::Mock {
237					captured_nanos: other_nanos,
238					..
239				},
240			) => {
241				let elapsed = this_nanos.saturating_sub(*other_nanos);
242				Duration::from_nanos(elapsed)
243			}
244			_ => panic!("Cannot compare instants from different clock types"),
245		}
246	}
247}
248
249impl PartialEq for Instant {
250	fn eq(&self, other: &Self) -> bool {
251		match (&self.inner, &other.inner) {
252			(InstantInner::Real(a), InstantInner::Real(b)) => a == b,
253			(
254				InstantInner::Mock {
255					captured_nanos: a,
256					..
257				},
258				InstantInner::Mock {
259					captured_nanos: b,
260					..
261				},
262			) => a == b,
263			_ => panic!("Cannot compare instants from different clock types"),
264		}
265	}
266}
267
268impl Eq for Instant {}
269
270impl PartialOrd for Instant {
271	fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
272		Some(self.cmp(other))
273	}
274}
275
276impl Ord for Instant {
277	fn cmp(&self, other: &Self) -> cmp::Ordering {
278		match (&self.inner, &other.inner) {
279			(InstantInner::Real(a), InstantInner::Real(b)) => a.cmp(b),
280			(
281				InstantInner::Mock {
282					captured_nanos: a,
283					..
284				},
285				InstantInner::Mock {
286					captured_nanos: b,
287					..
288				},
289			) => a.cmp(b),
290			_ => panic!("Cannot compare instants from different clock types"),
291		}
292	}
293}
294
295impl ops::Add<Duration> for Instant {
296	type Output = Instant;
297
298	fn add(self, duration: Duration) -> Instant {
299		match self.inner {
300			InstantInner::Real(instant) => Instant {
301				inner: InstantInner::Real(instant + duration),
302			},
303			InstantInner::Mock {
304				captured_nanos,
305				clock,
306			} => Instant {
307				inner: InstantInner::Mock {
308					captured_nanos: captured_nanos.saturating_add(duration.as_nanos() as u64),
309					clock,
310				},
311			},
312		}
313	}
314}
315
316impl ops::Add<RvDuration> for Instant {
317	type Output = Instant;
318
319	fn add(self, duration: RvDuration) -> Instant {
320		self + duration.to_std()
321	}
322}
323
324impl ops::Sub for &Instant {
325	type Output = Duration;
326
327	fn sub(self, other: &Instant) -> Duration {
328		self.duration_since(other)
329	}
330}
331
332impl fmt::Debug for Instant {
333	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334		match &self.inner {
335			InstantInner::Real(instant) => f.debug_tuple("Instant::Real").field(instant).finish(),
336			InstantInner::Mock {
337				captured_nanos,
338				..
339			} => f.debug_tuple("Instant::Mock").field(captured_nanos).finish(),
340		}
341	}
342}