1pub mod tokio;
13
14#[cfg(any(test, feature = "testing"))]
15use std::sync::{
16 Arc,
17 atomic::{AtomicU64, Ordering},
18};
19use std::time::{Duration, Instant, SystemTime};
20
21pub trait Clock {
23 fn now(&self) -> Instant;
25}
26
27#[cfg(not(loom))]
32pub type SharedClock = std::sync::Arc<dyn Clock + Send + Sync + std::panic::RefUnwindSafe>;
33
34#[cfg(not(loom))]
37#[derive(Clone)]
38pub struct SyncClock(SharedClock);
39
40#[cfg(not(loom))]
41impl SyncClock {
42 #[must_use]
44 pub fn new(clock: SharedClock) -> Self {
45 SyncClock(clock)
46 }
47
48 #[must_use]
50 pub fn system() -> Self {
51 SyncClock(std::sync::Arc::new(SystemClock))
52 }
53}
54
55#[cfg(not(loom))]
56impl std::ops::Deref for SyncClock {
57 type Target = dyn Clock + Send + Sync + std::panic::RefUnwindSafe;
58
59 fn deref(&self) -> &Self::Target {
60 &*self.0
61 }
62}
63
64#[cfg(not(loom))]
65impl std::fmt::Debug for SyncClock {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 f.write_str("SyncClock")
68 }
69}
70
71#[derive(Clone, Copy, Debug, Default)]
73pub struct SystemClock;
74
75impl Clock for SystemClock {
76 #[inline]
77 fn now(&self) -> Instant {
78 Instant::now()
79 }
80}
81
82#[derive(Clone, Copy, Debug)]
87pub struct Stopwatch {
88 started: Instant,
89}
90
91impl Stopwatch {
92 #[inline]
94 #[must_use]
95 pub fn start<C: Clock + ?Sized>(clock: &C) -> Self {
96 Self {
97 started: clock.now(),
98 }
99 }
100
101 #[inline]
104 #[must_use]
105 pub fn elapsed<C: Clock + ?Sized>(&self, clock: &C) -> Duration {
106 clock.now().saturating_duration_since(self.started)
107 }
108}
109
110#[must_use]
115pub fn wall_now() -> SystemTime {
116 SystemTime::now()
117}
118
119#[cfg(any(test, feature = "testing"))]
121#[doc(hidden)]
122#[derive(Debug)]
123pub struct TestClock {
124 base: Instant,
125 offset_nanos: AtomicU64,
126}
127
128#[cfg(any(test, feature = "testing"))]
129impl TestClock {
130 #[must_use]
132 pub fn frozen() -> Arc<TestClock> {
133 Arc::new(TestClock {
134 base: Instant::now(),
135 offset_nanos: AtomicU64::new(0),
136 })
137 }
138
139 pub fn advance(&self, by: Duration) {
141 let nanos = u64::try_from(by.as_nanos()).expect("test clock advance fits in u64 nanos");
142 self.offset_nanos.fetch_add(nanos, Ordering::Relaxed);
143 }
144
145 pub fn advance_stepped(&self, total: Duration, step: Duration, mut between: impl FnMut()) {
151 assert!(!step.is_zero(), "advance_stepped needs a non-zero step");
152 let mut moved = Duration::ZERO;
153 while moved < total {
154 let chunk = step.min(total - moved);
155 self.advance(chunk);
156 moved += chunk;
157 between();
158 }
159 }
160}
161
162#[cfg(any(test, feature = "testing"))]
163impl Clock for TestClock {
164 fn now(&self) -> Instant {
165 self.base + Duration::from_nanos(self.offset_nanos.load(Ordering::Relaxed))
166 }
167}
168
169#[cfg(any(test, feature = "testing"))]
170impl Clock for &TestClock {
171 fn now(&self) -> Instant {
172 (**self).now()
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[cfg(not(loom))]
183 #[test]
184 fn a_sync_clock_debugs_as_its_name() {
185 assert_eq!(format!("{:?}", SyncClock::system()), "SyncClock");
186 }
187
188 #[test]
191 fn advance_stepped_reaches_total_in_steps() {
192 let clock = TestClock::frozen();
193 let start = clock.now();
194 let mut seen = Vec::new();
195 clock.advance_stepped(Duration::from_millis(25), Duration::from_millis(10), || {
196 seen.push(clock.now() - start);
197 });
198 assert_eq!(
199 seen,
200 [10, 20, 25].map(Duration::from_millis),
201 "one reading per step, the last one short"
202 );
203 }
204
205 #[test]
207 fn a_stopwatch_reports_the_time_its_clock_moved() {
208 let clock = TestClock::frozen();
209 let watch = Stopwatch::start(&*clock);
210 clock.advance(Duration::from_millis(250));
211 assert_eq!(watch.elapsed(&*clock), Duration::from_millis(250));
212 }
213
214 #[test]
215 #[should_panic(expected = "non-zero step")]
216 fn advance_stepped_rejects_a_zero_step() {
217 TestClock::frozen().advance_stepped(Duration::from_secs(1), Duration::ZERO, || {});
218 }
219}