1use std::sync::Arc;
4use std::time::{Duration, SystemTime, UNIX_EPOCH};
5use std::{future::Future, pin::Pin};
6
7#[cfg(target_vendor = "apple")]
10#[path = "clock_apple.rs"]
11mod wall_timer;
12#[cfg(any(target_os = "linux", target_os = "android"))]
13#[path = "clock_linux.rs"]
14mod wall_timer;
15#[cfg(windows)]
16#[path = "clock_windows.rs"]
17mod wall_timer;
18
19pub trait Clock: Send + Sync {
24 fn now(&self) -> SystemTime;
26 fn elapsed(&self) -> Duration;
30 fn deadline(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send>> {
36 Box::pin(tokio::time::sleep(duration))
37 }
38 fn wait_out(&self, wait: Duration) -> Pin<Box<dyn Future<Output = ()> + Send>> {
43 self.deadline(wait)
44 }
45 fn sleep_until(
51 &self,
52 at: SystemTime,
53 ) -> Pin<Box<dyn Future<Output = Result<(), WallTimerError>> + Send>>;
54}
55
56#[derive(Clone, Debug, thiserror::Error)]
59pub enum WallTimerError {
60 #[error("this clock has no wall-clock timer coven can wait on")]
63 NoTimer,
64 #[error("the time is past what the system's wall timer holds")]
66 OutOfRange {
67 #[source]
70 source: Option<std::num::TryFromIntError>,
71 },
72 #[error("the system couldn't create a timer source")]
74 TimerSource,
75 #[error("the system's `{call}` failed")]
77 System {
78 call: &'static str,
80 #[source]
82 source: Arc<std::io::Error>,
83 },
84 #[error("the clock failed to wait for a wall-clock time")]
86 Clock(#[source] Arc<dyn std::error::Error + Send + Sync>),
87}
88
89impl WallTimerError {
90 pub fn clock(failure: impl std::error::Error + Send + Sync + 'static) -> Self {
92 Self::Clock(Arc::new(failure))
93 }
94
95 #[cfg(any(target_os = "linux", target_os = "android", windows))]
97 pub(crate) fn system(call: &'static str, source: impl Into<std::io::Error>) -> Self {
98 Self::System { call, source: Arc::new(source.into()) }
99 }
100}
101
102pub type ClockRef = Arc<dyn Clock>;
104
105pub struct SystemClock;
107
108impl Clock for SystemClock {
109 fn now(&self) -> SystemTime {
110 SystemTime::now()
111 }
112 fn elapsed(&self) -> Duration {
113 boot_time()
114 }
115 fn sleep_until(
120 &self,
121 at: SystemTime,
122 ) -> Pin<Box<dyn Future<Output = Result<(), WallTimerError>> + Send>> {
123 if at <= UNIX_EPOCH {
124 return Box::pin(std::future::ready(Ok(())));
125 }
126 wall_timer_sleep(at)
127 }
128}
129
130#[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android", windows))]
131fn wall_timer_sleep(
132 at: SystemTime,
133) -> Pin<Box<dyn Future<Output = Result<(), WallTimerError>> + Send>> {
134 Box::pin(wall_timer::sleep_until(at))
135}
136
137#[cfg(not(any(target_vendor = "apple", target_os = "linux", target_os = "android", windows)))]
140fn wall_timer_sleep(
141 _at: SystemTime,
142) -> Pin<Box<dyn Future<Output = Result<(), WallTimerError>> + Send>> {
143 Box::pin(std::future::ready(Err(WallTimerError::NoTimer)))
144}
145
146#[cfg(any(target_os = "linux", target_os = "android"))]
148fn boot_time() -> Duration {
149 timespec(rustix::time::clock_gettime(rustix::time::ClockId::Boottime))
150}
151
152#[cfg(target_vendor = "apple")]
155fn boot_time() -> Duration {
156 timespec(rustix::time::clock_gettime(rustix::time::ClockId::Monotonic))
157}
158
159#[cfg(all(unix, not(any(target_os = "linux", target_os = "android", target_vendor = "apple"))))]
162fn boot_time() -> Duration {
163 timespec(rustix::time::clock_gettime(rustix::time::ClockId::Monotonic))
164}
165
166#[cfg(unix)]
167fn timespec(time: rustix::time::Timespec) -> Duration {
168 Duration::new(
169 u64::try_from(time.tv_sec).expect("monotonic seconds are nonnegative"),
170 u32::try_from(time.tv_nsec).expect("nanoseconds below one second"),
171 )
172}
173
174#[cfg(windows)]
176fn boot_time() -> Duration {
177 let mut ticks = 0u64;
178 unsafe { windows_sys::Win32::System::WindowsProgramming::QueryInterruptTime(&mut ticks) };
180 Duration::from_nanos(ticks.saturating_mul(100))
181}
182
183#[cfg(any(test, feature = "test-utils"))]
191pub const HANG_GUARD: Duration = Duration::from_secs(300);
192
193#[cfg(any(test, feature = "test-utils"))]
197pub async fn within<F: Future>(future: F) -> Option<F::Output> {
198 tokio::time::timeout(HANG_GUARD, future).await.ok()
199}
200
201#[cfg(any(test, feature = "test-utils"))]
204pub fn wait_out_hang_guard() {
205 std::thread::sleep(HANG_GUARD);
206}
207
208#[cfg(feature = "test-utils")]
210pub struct FixedClock {
211 time: tokio::sync::watch::Sender<FixedTime>,
212 sleeps: tokio::sync::watch::Sender<(u64, Duration)>,
215}
216
217#[cfg(feature = "test-utils")]
218struct FixedTime {
219 wall: SystemTime,
220 elapsed: Duration,
221}
222
223#[cfg(feature = "test-utils")]
224impl FixedClock {
225 pub fn new(now: SystemTime) -> Self {
227 Self {
228 time: tokio::sync::watch::channel(FixedTime { wall: now, elapsed: Duration::ZERO }).0,
229 sleeps: tokio::sync::watch::channel((0, Duration::ZERO)).0,
230 }
231 }
232
233 pub fn set(&self, now: SystemTime) {
235 self.time.send_modify(|time| {
236 if let Ok(elapsed) = now.duration_since(time.wall) {
237 time.elapsed += elapsed;
238 }
239 time.wall = now;
240 });
241 }
242
243 pub fn sleeps_begun(&self) -> u64 {
245 self.sleeps.borrow().0
246 }
247
248 pub fn sleep_begun_after(
252 &self,
253 seen: u64,
254 ) -> impl Future<Output = (u64, Duration)> + Send + 'static {
255 let mut sleeps = self.sleeps.subscribe();
256 async move { *sleeps.wait_for(|(begun, _)| *begun > seen).await.expect("the clock is kept") }
257 }
258}
259
260#[cfg(feature = "test-utils")]
261impl Clock for FixedClock {
262 fn now(&self) -> SystemTime {
263 self.time.borrow().wall
264 }
265 fn elapsed(&self) -> Duration {
267 self.time.borrow().elapsed
268 }
269 fn deadline(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send>> {
270 let mut time = self.time.subscribe();
271 let deadline = time.borrow().elapsed + duration;
272 self.sleeps.send_modify(|(begun, latest)| {
273 *begun += 1;
274 *latest = deadline;
275 });
276 Box::pin(async move {
277 time.wait_for(|time| time.elapsed >= deadline)
278 .await
279 .expect("clock owner retains its sender");
280 })
281 }
282 fn sleep_until(
285 &self,
286 at: SystemTime,
287 ) -> Pin<Box<dyn Future<Output = Result<(), WallTimerError>> + Send>> {
288 let mut time = self.time.subscribe();
289 Box::pin(async move {
290 time.wait_for(|time| time.wall >= at).await.expect("clock owner retains its sender");
291 Ok(())
292 })
293 }
294}
295
296#[cfg(feature = "test-utils")]
297#[path = "clock_polling.rs"]
298mod polling;
299#[cfg(feature = "test-utils")]
300pub use polling::PollingClock;
301
302#[cfg(feature = "test-utils")]
304pub struct ClosureClock<F>(
305 pub F,
307);
308
309#[cfg(feature = "test-utils")]
310impl<F: Fn() -> SystemTime + Send + Sync> Clock for ClosureClock<F> {
311 fn now(&self) -> SystemTime {
312 (self.0)()
313 }
314 fn elapsed(&self) -> Duration {
316 Duration::ZERO
317 }
318 fn sleep_until(
320 &self,
321 _at: SystemTime,
322 ) -> Pin<Box<dyn Future<Output = Result<(), WallTimerError>> + Send>> {
323 Box::pin(std::future::ready(Err(WallTimerError::NoTimer)))
324 }
325}
326
327#[cfg(test)]
330static RELEASED: std::sync::Mutex<Vec<(SystemTime, tokio::sync::oneshot::Sender<()>)>> =
331 std::sync::Mutex::new(Vec::new());
332
333#[cfg(test)]
336fn when_released(at: SystemTime) -> tokio::sync::oneshot::Receiver<()> {
337 let (told, telling) = tokio::sync::oneshot::channel();
338 crate::locks::lock_unpoisoned(&RELEASED).push((at, told));
339 telling
340}
341
342#[cfg(all(
345 test,
346 any(target_vendor = "apple", target_os = "linux", target_os = "android", windows)
347))]
348fn released(at: SystemTime) {
349 let mut watched = crate::locks::lock_unpoisoned(&RELEASED);
350 let (found, rest): (Vec<_>, Vec<_>) =
351 std::mem::take(&mut *watched).into_iter().partition(|(watched, _)| *watched == at);
352 *watched = rest;
353 for (_, told) in found {
354 #[expect(
355 clippy::let_underscore_must_use,
356 reason = "a test that stopped listening has no use for it"
357 )]
358 let _ = told.send(());
359 }
360}
361
362#[cfg(all(test, any(target_os = "linux", target_os = "android", windows)))]
364struct ReleasedOnDrop(SystemTime);
365
366#[cfg(all(test, any(target_os = "linux", target_os = "android", windows)))]
367impl Drop for ReleasedOnDrop {
368 fn drop(&mut self) {
369 released(self.0);
370 }
371}
372
373#[cfg(test)]
374#[path = "clock_tests.rs"]
375mod tests;