kayrx/timer/clock.rs
1//! A configurable source of time.
2//!
3//! This module provides an API to get the current instant in such a way that
4//! the source of time may be configured. This allows mocking out the source of
5//! time in tests.
6//!
7//! The [`now`][n] function returns the current [`Instant`]. By default, it delegates
8//! to [`Instant::now`].
9//!
10//! The source of time used by [`now`][n] can be configured by implementing the
11//! [`Now`] trait and passing an instance to [`with_default`].
12//!
13//! [n]: fn.now.html
14//! [`Now`]: trait.Now.html
15//! [`Instant`]: std::time::Instant
16//! [`Instant::now`]: std::time::Instant::now
17//! [`with_default`]: fn.with_default.html
18
19use std::cell::Cell;
20use std::fmt;
21use std::sync::Arc;
22use std::time::Instant;
23
24use super::driver;
25
26/// Returns [`Instant`] values representing the current instant in time.
27///
28/// This allows customizing the source of time which is especially useful for
29/// testing.
30///
31/// Implementations must ensure that calls to `now` return monotonically
32/// increasing [`Instant`] values.
33///
34/// [`Instant`]: std::time::Instant
35pub trait Now: Send + Sync + 'static {
36 /// Returns an instant corresponding to "now".
37 fn now(&self) -> Instant;
38}
39
40/// A handle to a source of time.
41///
42/// `Clock` instances return [`Instant`] values corresponding to "now". The source
43/// of these values is configurable. The default source is [`Instant::now`].
44///
45/// [`Instant`]: std::time::Instant
46/// [`Instant::now`]: std::time::Instant::now
47#[derive(Default, Clone)]
48pub struct Clock {
49 now: Option<Arc<dyn Now>>,
50}
51
52thread_local! {
53 /// Thread-local tracking the current clock
54 static CLOCK: Cell<Option<*const Clock>> = Cell::new(None)
55}
56
57/// Returns an `Instant` corresponding to "now".
58///
59/// This function delegates to the source of time configured for the current
60/// execution context. By default, this is `Instant::now()`.
61///
62/// Note that, because the source of time is configurable, it is possible to
63/// observe non-monotonic behavior when calling `now` from different
64/// executors.
65///
66/// See [module](index.html) level documentation for more details.
67///
68/// # Examples
69///
70/// ```
71/// # use kayrx::timer::clock;
72/// let now = clock::now();
73/// ```
74pub fn now() -> Instant {
75 CLOCK.with(|current| match current.get() {
76 Some(ptr) => unsafe { (*ptr).now() },
77 None => Instant::now(),
78 })
79}
80
81impl Clock {
82 /// Return a new `Clock` instance that uses the current execution context's
83 /// source of time.
84 pub fn new() -> Clock {
85 CLOCK.with(|current| match current.get() {
86 Some(ptr) => unsafe { (*ptr).clone() },
87 None => Clock::system(),
88 })
89 }
90
91 /// Return a new `Clock` instance that uses `now` as the source of time.
92 pub fn new_with_now<T: Now>(now: T) -> Clock {
93 Clock {
94 now: Some(Arc::new(now)),
95 }
96 }
97
98 /// Return a new `Clock` instance that uses [`Instant::now`] as the source
99 /// of time.
100 ///
101 /// [`Instant::now`]: std::time::Instant::now
102 pub fn system() -> Clock {
103 Clock { now: None }
104 }
105
106 /// Returns an instant corresponding to "now" by using the instance's source
107 /// of time.
108 pub fn now(&self) -> Instant {
109 match self.now {
110 Some(ref now) => now.now(),
111 None => Instant::now(),
112 }
113 }
114}
115
116#[allow(deprecated)]
117impl driver::Now for Clock {
118 fn now(&mut self) -> Instant {
119 Clock::now(self)
120 }
121}
122
123impl fmt::Debug for Clock {
124 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
125 fmt.debug_struct("Clock")
126 .field("now", {
127 if self.now.is_some() {
128 &"Some(Arc<Now>)"
129 } else {
130 &"None"
131 }
132 })
133 .finish()
134 }
135}
136
137/// Set the default clock for the duration of the closure.
138///
139/// # Panics
140///
141/// This function panics if there already is a default clock set.
142pub fn with_default<F, R>(clock: &Clock, f: F) -> R
143where
144 F: FnOnce() -> R,
145{
146 CLOCK.with(|cell| {
147 assert!(
148 cell.get().is_none(),
149 "default clock already set for execution context"
150 );
151
152 // Ensure that the clock is removed from the thread-local context
153 // when leaving the scope. This handles cases that involve panicking.
154 struct Reset<'a>(&'a Cell<Option<*const Clock>>);
155
156 impl Drop for Reset<'_> {
157 fn drop(&mut self) {
158 self.0.set(None);
159 }
160 }
161
162 let _reset = Reset(cell);
163
164 cell.set(Some(clock as *const Clock));
165
166 f()
167 })
168}