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