clock_lib/clock.rs
1//! Mockable clock abstraction.
2//!
3//! The [`Clock`] trait lets time-driven code — rate limiters, TTL caches,
4//! timeouts, retry backoff — depend on an injected source of time instead
5//! of calling the OS directly. Production code uses [`SystemClock`], which
6//! simply forwards to the OS. Tests use [`ManualClock`], which advances on
7//! demand so timing-driven behavior can be exercised deterministically
8//! without ever calling [`std::thread::sleep`].
9
10#[cfg(feature = "std")]
11use core::sync::atomic::{AtomicU64, Ordering};
12#[cfg(feature = "std")]
13use core::time::Duration;
14#[cfg(feature = "std")]
15use std::sync::Arc;
16#[cfg(feature = "std")]
17use std::time::{Instant, SystemTime};
18
19#[cfg(feature = "std")]
20use crate::{Monotonic, Wall};
21
22/// A source of time.
23///
24/// `Clock` exposes both kinds of reading [`clock-lib`](crate) draws apart:
25/// a monotonic reading via [`Clock::now`] and a wall-clock reading via
26/// [`Clock::wall`]. Implementations must be safe to share across threads.
27///
28/// In production, take a `Clock` (or `&dyn Clock`, or `Arc<dyn Clock>`) as
29/// a dependency and use [`SystemClock`] at the top of your call graph. In
30/// tests, substitute [`ManualClock`] and drive time forward by calling
31/// [`ManualClock::advance`].
32///
33/// `Clock` is also implemented for `Arc<C>` and `&C` where `C: Clock`,
34/// so the same value can be shared and reused freely.
35///
36/// # Examples
37///
38/// ```
39/// use clock_lib::{Clock, ManualClock, SystemClock};
40/// use std::time::Duration;
41///
42/// fn took_at_least<C: Clock>(clock: &C, start: clock_lib::Monotonic, target: Duration) -> bool {
43/// clock.now().duration_since(start) >= target
44/// }
45///
46/// // Production
47/// let sys = SystemClock::new();
48/// let start = sys.now();
49/// assert!(!took_at_least(&sys, start, Duration::from_secs(60 * 60)));
50///
51/// // Test — no sleep, fully deterministic
52/// let test = ManualClock::new();
53/// let start = test.now();
54/// test.advance(Duration::from_secs(60 * 60));
55/// assert!(took_at_least(&test, start, Duration::from_secs(60 * 60)));
56/// ```
57#[cfg(feature = "std")]
58#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
59pub trait Clock: Send + Sync {
60 /// Returns the current monotonic reading.
61 fn now(&self) -> Monotonic;
62
63 /// Returns the current wall-clock reading.
64 fn wall(&self) -> Wall;
65}
66
67/// A clock backed by the operating system.
68///
69/// `SystemClock` forwards [`Clock::now`] to [`Monotonic::now`] and
70/// [`Clock::wall`] to [`Wall::now`]. It is zero-sized, `Copy`, and the
71/// constructor is `const`, so it imposes no runtime cost over calling the
72/// free functions directly.
73///
74/// # Examples
75///
76/// ```
77/// use clock_lib::{Clock, SystemClock};
78///
79/// const CLOCK: SystemClock = SystemClock::new();
80/// let t = CLOCK.now();
81/// # let _ = t;
82/// ```
83#[cfg(feature = "std")]
84#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
85#[derive(Debug, Default, Copy, Clone)]
86pub struct SystemClock;
87
88#[cfg(feature = "std")]
89impl SystemClock {
90 /// Constructs a new system clock.
91 #[inline]
92 #[must_use]
93 pub const fn new() -> Self {
94 Self
95 }
96}
97
98#[cfg(feature = "std")]
99impl Clock for SystemClock {
100 #[inline]
101 fn now(&self) -> Monotonic {
102 Monotonic::now()
103 }
104
105 #[inline]
106 fn wall(&self) -> Wall {
107 Wall::now()
108 }
109}
110
111/// A clock under your control, for deterministic testing.
112///
113/// `ManualClock` captures the OS monotonic and wall-clock anchors at
114/// construction, then advances **only** when you call
115/// [`advance`](Self::advance). The clock never moves on its own, so tests
116/// against time-driven code become point-in-time deterministic.
117///
118/// `ManualClock` is `Send + Sync`. Wrap it in an [`Arc`] to share with the
119/// code under test; both the test driver and the production code can hold
120/// references to the same clock and observe consistent readings.
121///
122/// # Examples
123///
124/// ```
125/// use clock_lib::{Clock, ManualClock, Monotonic};
126/// use std::sync::Arc;
127/// use std::time::Duration;
128///
129/// // A "ttl expired?" check that can be driven without sleeping.
130/// fn expired<C: Clock>(clock: &C, stamp: Monotonic, ttl: Duration) -> bool {
131/// clock.now().duration_since(stamp) >= ttl
132/// }
133///
134/// let clock = Arc::new(ManualClock::new());
135/// let stamp = clock.now();
136///
137/// assert!(!expired(&*clock, stamp, Duration::from_secs(60)));
138///
139/// clock.advance(Duration::from_secs(60));
140/// assert!(expired(&*clock, stamp, Duration::from_secs(60)));
141/// ```
142#[cfg(feature = "std")]
143#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
144#[derive(Debug)]
145pub struct ManualClock {
146 monotonic_anchor: Instant,
147 wall_anchor: SystemTime,
148 offset_nanos: AtomicU64,
149}
150
151#[cfg(feature = "std")]
152impl ManualClock {
153 /// Constructs a new manual clock anchored at the current OS time.
154 ///
155 /// # Examples
156 ///
157 /// ```
158 /// use clock_lib::ManualClock;
159 ///
160 /// let clock = ManualClock::new();
161 /// # let _ = clock;
162 /// ```
163 #[inline]
164 #[must_use]
165 pub fn new() -> Self {
166 Self {
167 monotonic_anchor: Instant::now(),
168 wall_anchor: SystemTime::now(),
169 offset_nanos: AtomicU64::new(0),
170 }
171 }
172
173 /// Advances the clock forward by `by`.
174 ///
175 /// Successive calls accumulate. If the cumulative offset would exceed
176 /// [`u64::MAX`] nanoseconds (≈584 years), the offset saturates —
177 /// well outside any plausible test scenario.
178 ///
179 /// # Examples
180 ///
181 /// ```
182 /// use clock_lib::{Clock, ManualClock};
183 /// use std::time::Duration;
184 ///
185 /// let clock = ManualClock::new();
186 /// let a = clock.now();
187 /// clock.advance(Duration::from_secs(5));
188 /// let b = clock.now();
189 /// assert_eq!(b.duration_since(a), Duration::from_secs(5));
190 /// ```
191 #[inline]
192 pub fn advance(&self, by: Duration) {
193 let nanos = u64::try_from(by.as_nanos()).unwrap_or(u64::MAX);
194 let _ = self.offset_nanos.fetch_add(nanos, Ordering::Relaxed);
195 }
196
197 /// Returns the cumulative offset that has been added to this clock
198 /// since it was constructed.
199 ///
200 /// # Examples
201 ///
202 /// ```
203 /// use clock_lib::ManualClock;
204 /// use std::time::Duration;
205 ///
206 /// let clock = ManualClock::new();
207 /// clock.advance(Duration::from_secs(1));
208 /// clock.advance(Duration::from_secs(2));
209 /// assert_eq!(clock.offset(), Duration::from_secs(3));
210 /// ```
211 #[inline]
212 #[must_use]
213 pub fn offset(&self) -> Duration {
214 Duration::from_nanos(self.offset_nanos.load(Ordering::Relaxed))
215 }
216}
217
218#[cfg(feature = "std")]
219impl Default for ManualClock {
220 #[inline]
221 fn default() -> Self {
222 Self::new()
223 }
224}
225
226#[cfg(feature = "std")]
227impl Clock for ManualClock {
228 /// Returns the monotonic anchor plus the accumulated offset.
229 ///
230 /// # Panics
231 ///
232 /// Panics if the anchor plus offset is not representable as an
233 /// [`Instant`] on the current platform. This requires a cumulative
234 /// offset measured in centuries and never occurs in realistic tests.
235 fn now(&self) -> Monotonic {
236 let offset = Duration::from_nanos(self.offset_nanos.load(Ordering::Relaxed));
237 Monotonic(self.monotonic_anchor + offset)
238 }
239
240 /// Returns the wall-clock anchor plus the accumulated offset.
241 ///
242 /// # Panics
243 ///
244 /// Panics if the anchor plus offset is not representable as a
245 /// [`SystemTime`] on the current platform. This requires a cumulative
246 /// offset measured in centuries and never occurs in realistic tests.
247 fn wall(&self) -> Wall {
248 let offset = Duration::from_nanos(self.offset_nanos.load(Ordering::Relaxed));
249 Wall(self.wall_anchor + offset)
250 }
251}
252
253#[cfg(feature = "std")]
254impl<C: Clock + ?Sized> Clock for Arc<C> {
255 #[inline]
256 fn now(&self) -> Monotonic {
257 (**self).now()
258 }
259
260 #[inline]
261 fn wall(&self) -> Wall {
262 (**self).wall()
263 }
264}
265
266#[cfg(feature = "std")]
267impl<C: Clock + ?Sized> Clock for &C {
268 #[inline]
269 fn now(&self) -> Monotonic {
270 (**self).now()
271 }
272
273 #[inline]
274 fn wall(&self) -> Wall {
275 (**self).wall()
276 }
277}