Skip to main content

reifydb_runtime/clock/
native.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2025 ReifyDB
3
4//! Native clock implementation.
5
6use std::{
7	fmt,
8	sync::{
9		Arc,
10		atomic::{AtomicU64, Ordering},
11	},
12	time,
13	time::{Duration, SystemTime, UNIX_EPOCH},
14};
15
16#[inline(always)]
17fn platform_now_nanos() -> u128 {
18	SystemTime::now().duration_since(UNIX_EPOCH).expect("System time is before Unix epoch").as_nanos()
19}
20
21/// A clock that provides time - either real system time or mock time for testing.
22#[derive(Clone)]
23pub enum Clock {
24	/// Real system clock - delegates to platform time
25	Real,
26	/// Mock clock with controllable time
27	Mock(MockClock),
28}
29
30impl Clock {
31	/// Get current time in nanoseconds since Unix epoch
32	pub fn now_nanos(&self) -> u128 {
33		match self {
34			Clock::Real => platform_now_nanos(),
35			Clock::Mock(mock) => mock.now_nanos(),
36		}
37	}
38
39	/// Get current time in microseconds since Unix epoch
40	pub fn now_micros(&self) -> u64 {
41		(self.now_nanos() / 1_000) as u64
42	}
43
44	/// Get current time in milliseconds since Unix epoch
45	pub fn now_millis(&self) -> u64 {
46		(self.now_nanos() / 1_000_000) as u64
47	}
48
49	/// Get current time in seconds since Unix epoch
50	pub fn now_secs(&self) -> u64 {
51		(self.now_nanos() / 1_000_000_000) as u64
52	}
53
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
69impl Default for Clock {
70	fn default() -> Self {
71		Clock::Real
72	}
73}
74
75/// Mock clock with atomic time storage for thread-safe access.
76#[derive(Clone)]
77pub struct MockClock {
78	inner: Arc<MockClockInner>,
79}
80
81struct MockClockInner {
82	// Split u128 into two u64s for atomic access
83	time_high: AtomicU64,
84	time_low: AtomicU64,
85}
86
87impl MockClock {
88	/// Create a new mock clock starting at the given nanoseconds
89	pub fn new(initial_nanos: u128) -> Self {
90		Self {
91			inner: Arc::new(MockClockInner {
92				time_high: AtomicU64::new((initial_nanos >> 64) as u64),
93				time_low: AtomicU64::new(initial_nanos as u64),
94			}),
95		}
96	}
97
98	/// Create a new mock clock starting at the given milliseconds
99	pub fn from_millis(millis: u64) -> Self {
100		Self::new(millis as u128 * 1_000_000)
101	}
102
103	/// Get current time in nanoseconds
104	pub fn now_nanos(&self) -> u128 {
105		let high = self.inner.time_high.load(Ordering::Acquire) as u128;
106		let low = self.inner.time_low.load(Ordering::Acquire) as u128;
107		(high << 64) | low
108	}
109
110	/// Get current time in microseconds
111	pub fn now_micros(&self) -> u64 {
112		(self.now_nanos() / 1_000) as u64
113	}
114
115	/// Get current time in milliseconds
116	pub fn now_millis(&self) -> u64 {
117		(self.now_nanos() / 1_000_000) as u64
118	}
119
120	/// Get current time in seconds
121	pub fn now_secs(&self) -> u64 {
122		(self.now_nanos() / 1_000_000_000) as u64
123	}
124
125	/// Set time to specific nanoseconds
126	pub fn set_nanos(&self, nanos: u128) {
127		self.inner.time_high.store((nanos >> 64) as u64, Ordering::Release);
128		self.inner.time_low.store(nanos as u64, Ordering::Release);
129	}
130
131	/// Set time to specific microseconds
132	pub fn set_micros(&self, micros: u64) {
133		self.set_nanos(micros as u128 * 1_000);
134	}
135
136	/// Set time to specific milliseconds
137	pub fn set_millis(&self, millis: u64) {
138		self.set_nanos(millis as u128 * 1_000_000);
139	}
140
141	/// Advance time by nanoseconds
142	pub fn advance_nanos(&self, nanos: u128) {
143		self.set_nanos(self.now_nanos() + nanos);
144	}
145
146	/// Advance time by microseconds
147	pub fn advance_micros(&self, micros: u64) {
148		self.advance_nanos(micros as u128 * 1_000);
149	}
150
151	/// Advance time by milliseconds
152	pub fn advance_millis(&self, millis: u64) {
153		self.advance_nanos(millis as u128 * 1_000_000);
154	}
155}
156
157#[derive(Clone)]
158enum InstantInner {
159	Real(time::Instant),
160	Mock {
161		captured_nanos: u128,
162		clock: MockClock,
163	},
164}
165
166#[derive(Clone)]
167pub struct Instant {
168	inner: InstantInner,
169}
170
171impl Instant {
172	#[inline]
173	pub fn elapsed(&self) -> Duration {
174		match &self.inner {
175			InstantInner::Real(instant) => instant.elapsed(),
176			InstantInner::Mock {
177				captured_nanos,
178				clock,
179			} => {
180				let now = clock.now_nanos();
181				let elapsed_nanos = now.saturating_sub(*captured_nanos);
182				Duration::from_nanos(elapsed_nanos as u64)
183			}
184		}
185	}
186
187	#[inline]
188	pub fn duration_since(&self, earlier: Instant) -> Duration {
189		match (&self.inner, &earlier.inner) {
190			(InstantInner::Real(this), InstantInner::Real(other)) => this.duration_since(*other),
191			(
192				InstantInner::Mock {
193					captured_nanos: this_nanos,
194					..
195				},
196				InstantInner::Mock {
197					captured_nanos: other_nanos,
198					..
199				},
200			) => {
201				let elapsed = this_nanos.saturating_sub(*other_nanos);
202				Duration::from_nanos(elapsed as u64)
203			}
204			_ => panic!("Cannot compare instants from different clock types"),
205		}
206	}
207}
208
209impl fmt::Debug for Instant {
210	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211		match &self.inner {
212			InstantInner::Real(instant) => f.debug_tuple("Instant::Real").field(instant).finish(),
213			InstantInner::Mock {
214				captured_nanos,
215				..
216			} => f.debug_tuple("Instant::Mock").field(captured_nanos).finish(),
217		}
218	}
219}