quanta/instant.rs
1use std::cmp::{Ord, Ordering, PartialOrd};
2use std::fmt;
3use std::ops::{Add, AddAssign, Sub, SubAssign};
4use std::time::Duration;
5
6/// A point-in-time wall-clock measurement.
7///
8/// Mimics most of the functionality of [`std::time::Instant`] but provides an additional method for
9/// using the "recent time" feature of `quanta`.
10///
11/// ## Monotonicity
12///
13/// On all platforms, `Instant` will try to use an OS API that guarantees monotonic behavior if
14/// available, which is the case for all supported platforms. In practice such guarantees are –
15/// under rare circumstances – broken by hardware, virtualization or operating system bugs. To work
16/// around these bugs and platforms not offering monotonic clocks [`duration_since`], [`elapsed`]
17/// and [`sub`] saturate to zero. In older `quanta` versions this lead to a panic instead.
18/// [`checked_duration_since`] can be used to detect and handle situations where monotonicity is
19/// violated, or `Instant`s are subtracted in the wrong order.
20///
21/// This workaround obscures programming errors where earlier and later instants are accidentally
22/// swapped. For this reason future `quanta` versions may reintroduce panics.
23///
24/// [`duration_since`]: Instant::duration_since
25/// [`elapsed`]: Instant::elapsed
26/// [`sub`]: Instant::sub
27/// [`checked_duration_since`]: Instant::checked_duration_since
28#[derive(Clone, Copy, PartialEq, Eq)]
29pub struct Instant(pub(crate) u64);
30
31impl Instant {
32 /// Gets the current time, scaled to reference time.
33 ///
34 /// This method depends on a lazily initialized global clock, which can take up to 200ms to
35 /// initialize and calibrate itself.
36 ///
37 /// This method is the spiritual equivalent of [`Instant::now`][instant_now]. It is guaranteed to
38 /// return a monotonically increasing value.
39 ///
40 /// [instant_now]: std::time::Instant::now
41 pub fn now() -> Instant {
42 crate::get_now()
43 }
44
45 /// Gets the most recent current time, scaled to reference time.
46 ///
47 /// This method provides ultra-low-overhead access to a slightly-delayed version of the current
48 /// time. Instead of querying the underlying source clock directly, a shared, global value is
49 /// read directly without the need to scale to reference time.
50 ///
51 /// The value is updated by running an "upkeep" thread or by calling [`set_recent`][set_recent]. An
52 /// upkeep thread can be configured and spawned via [`Upkeep`][upkeep].
53 ///
54 /// If the upkeep thread has not been started, or no value has been set manually, a lazily
55 /// initialized global clock will be used to get the current time. This clock can take up to
56 /// 200ms to initialize and calibrate itself.
57 ///
58 /// [set_recent]: crate::set_recent
59 /// [upkeep]: crate::Upkeep
60 pub fn recent() -> Instant {
61 crate::get_recent()
62 }
63
64 /// Returns the amount of time elapsed from another instant to this one.
65 ///
66 /// # Panics
67 ///
68 /// Previous versions of this method panicked when earlier was later than `self`. Currently,
69 /// this method saturates to zero. Future versions may reintroduce the panic in some
70 /// circumstances. See [Monotonicity].
71 ///
72 /// [Monotonicity]: Instant#monotonicity
73 ///
74 /// # Examples
75 ///
76 /// ```no_run
77 /// use quanta::Clock;
78 /// use std::time::Duration;
79 /// use std::thread::sleep;
80 ///
81 /// let mut clock = Clock::new();
82 /// let now = clock.now();
83 /// sleep(Duration::new(1, 0));
84 /// let new_now = clock.now();
85 /// println!("{:?}", new_now.duration_since(now));
86 /// ```
87 pub fn duration_since(&self, earlier: Instant) -> Duration {
88 self.checked_duration_since(earlier).unwrap_or_default()
89 }
90
91 /// Returns the amount of time elapsed from another instant to this one, or `None` if that
92 /// instant is earlier than this one.
93 ///
94 /// Due to [monotonicity bugs], even under correct logical ordering of the passed `Instant`s,
95 /// this method can return `None`.
96 ///
97 /// [monotonicity bugs]: Instant#monotonicity
98 ///
99 /// # Examples
100 ///
101 /// ```no_run
102 /// use quanta::Clock;
103 /// use std::time::Duration;
104 /// use std::thread::sleep;
105 ///
106 /// let mut clock = Clock::new();
107 /// let now = clock.now();
108 /// sleep(Duration::new(1, 0));
109 /// let new_now = clock.now();
110 /// println!("{:?}", new_now.checked_duration_since(now));
111 /// println!("{:?}", now.checked_duration_since(new_now)); // None
112 /// ```
113 pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
114 self.0.checked_sub(earlier.0).map(Duration::from_nanos)
115 }
116
117 /// Returns the amount of time elapsed from another instant to this one, or zero duration if
118 /// that instant is earlier than this one.
119 ///
120 /// Due to [monotonicity bugs], even under correct logical ordering of the passed `Instant`s,
121 /// this method can return `None`.
122 ///
123 /// [monotonicity bugs]: Instant#monotonicity
124 ///
125 /// # Examples
126 ///
127 /// ```no_run
128 /// use quanta::Clock;
129 /// use std::time::Duration;
130 /// use std::thread::sleep;
131 ///
132 /// let mut clock = Clock::new();
133 /// let now = clock.now();
134 /// sleep(Duration::new(1, 0));
135 /// let new_now = clock.now();
136 /// println!("{:?}", new_now.saturating_duration_since(now));
137 /// println!("{:?}", now.saturating_duration_since(new_now)); // 0ns
138 /// ```
139 pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
140 self.checked_duration_since(earlier).unwrap_or_default()
141 }
142
143 /// Returns the amount of time elapsed since this instant was created.
144 ///
145 /// # Panics
146 ///
147 /// Previous `quanta` versions panicked when the current time was earlier than self. Currently
148 /// this method returns a Duration of zero in that case. Future versions may reintroduce the
149 /// panic. See [Monotonicity].
150 ///
151 /// [Monotonicity]: Instant#monotonicity
152 ///
153 /// # Examples
154 ///
155 /// ```no_run
156 /// use quanta::Clock;
157 /// use std::time::Duration;
158 /// use std::thread::sleep;
159 ///
160 /// let mut clock = Clock::new();
161 /// let now = clock.now();
162 /// sleep(Duration::new(1, 0));
163 /// let elapsed = now.elapsed();
164 /// println!("{:?}", elapsed); // ≥ 1s
165 /// ```
166 pub fn elapsed(&self) -> Duration {
167 Instant::now() - *self
168 }
169
170 /// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
171 /// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
172 /// otherwise.
173 pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
174 self.0.checked_add(duration.as_nanos() as u64).map(Instant)
175 }
176
177 /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
178 /// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
179 /// otherwise.
180 pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
181 self.0.checked_sub(duration.as_nanos() as u64).map(Instant)
182 }
183}
184
185impl Add<Duration> for Instant {
186 type Output = Instant;
187
188 /// # Panics
189 ///
190 /// This function may panic if the resulting point in time cannot be represented by the
191 /// underlying data structure. See [`Instant::checked_add`] for a version without panic.
192 fn add(self, other: Duration) -> Instant {
193 self.checked_add(other)
194 .expect("overflow when adding duration to instant")
195 }
196}
197
198impl AddAssign<Duration> for Instant {
199 fn add_assign(&mut self, other: Duration) {
200 // This is not millenium-safe, but, I think that's OK. :)
201 self.0 = self.0 + other.as_nanos() as u64;
202 }
203}
204
205impl Sub<Duration> for Instant {
206 type Output = Instant;
207
208 fn sub(self, other: Duration) -> Instant {
209 self.checked_sub(other)
210 .expect("overflow when subtracting duration from instant")
211 }
212}
213
214impl SubAssign<Duration> for Instant {
215 fn sub_assign(&mut self, other: Duration) {
216 // This is not millenium-safe, but, I think that's OK. :)
217 self.0 = self.0 - other.as_nanos() as u64;
218 }
219}
220
221impl Sub<Instant> for Instant {
222 type Output = Duration;
223
224 /// Returns the amount of time elapsed from another instant to this one,
225 /// or zero duration if that instant is later than this one.
226 ///
227 /// # Panics
228 ///
229 /// Previous `quanta` versions panicked when `other` was later than `self`. Currently this
230 /// method saturates. Future versions may reintroduce the panic in some circumstances.
231 /// See [Monotonicity].
232 ///
233 /// [Monotonicity]: Instant#monotonicity
234 fn sub(self, other: Instant) -> Duration {
235 self.duration_since(other)
236 }
237}
238
239impl PartialOrd for Instant {
240 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
241 Some(self.cmp(other))
242 }
243}
244
245impl Ord for Instant {
246 fn cmp(&self, other: &Self) -> Ordering {
247 self.0.cmp(&other.0)
248 }
249}
250
251impl fmt::Debug for Instant {
252 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253 self.0.fmt(f)
254 }
255}
256
257#[cfg(feature = "prost")]
258impl Into<prost_types::Timestamp> for Instant {
259 fn into(self) -> prost_types::Timestamp {
260 let dur = Duration::from_nanos(self.0);
261 let secs = if dur.as_secs() > i64::MAX as u64 {
262 i64::MAX
263 } else {
264 dur.as_secs() as i64
265 };
266 let nsecs = if dur.subsec_nanos() > i32::MAX as u32 {
267 i32::MAX
268 } else {
269 dur.subsec_nanos() as i32
270 };
271 prost_types::Timestamp {
272 seconds: secs,
273 nanos: nsecs,
274 }
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use once_cell::sync::Lazy;
281
282 use super::Instant;
283 use crate::{with_clock, Clock};
284 use std::time::Duration;
285 use std::{sync::Mutex, thread};
286
287 static RECENT_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
288
289 #[test]
290 #[cfg_attr(
291 all(target_arch = "wasm32", target_os = "unknown"),
292 ignore = "WASM thread cannot sleep"
293 )]
294 fn test_now() {
295 let _guard = RECENT_LOCK.lock().unwrap();
296
297 let t0 = Instant::now();
298 thread::sleep(Duration::from_millis(15));
299 let t1 = Instant::now();
300
301 assert!(t0.0 > 0);
302 assert!(t1.0 > 0);
303
304 let result = t1 - t0;
305 let threshold = Duration::from_millis(14);
306 assert!(result > threshold);
307 }
308
309 #[test]
310 #[cfg_attr(
311 all(target_arch = "wasm32", target_os = "unknown"),
312 ignore = "WASM thread cannot sleep"
313 )]
314 fn test_recent() {
315 let _guard = RECENT_LOCK.lock().unwrap();
316
317 // Ensures that the recent global value is zero so that the fallback logic can kick in.
318 crate::set_recent(Instant(0));
319
320 let t0 = Instant::recent();
321 thread::sleep(Duration::from_millis(15));
322 let t1 = Instant::recent();
323
324 assert!(t0.0 > 0);
325 assert!(t1.0 > 0);
326
327 let result = t1 - t0;
328 let threshold = Duration::from_millis(14);
329 assert!(
330 result > threshold,
331 "t1 should be greater than t0 by at least 14ms, was only {}ms (t0: {}, t1: {})",
332 result.as_millis(),
333 t0.0,
334 t1.0
335 );
336
337 crate::set_recent(Instant(1));
338 let t2 = Instant::recent();
339 thread::sleep(Duration::from_millis(15));
340 let t3 = Instant::recent();
341 assert_eq!(t2, t3);
342 }
343
344 #[test]
345 #[cfg_attr(
346 all(target_arch = "wasm32", target_os = "unknown"),
347 wasm_bindgen_test::wasm_bindgen_test
348 )]
349 fn test_mocking() {
350 let _guard = RECENT_LOCK.lock().unwrap();
351
352 // Ensures that the recent global value is zero so that the fallback logic can kick in.
353 crate::set_recent(Instant(0));
354
355 let (clock, mock) = Clock::mock();
356 with_clock(&clock, move || {
357 let t0 = Instant::now();
358 mock.increment(42);
359 let t1 = Instant::now();
360
361 assert_eq!(t0.0, 0);
362 assert_eq!(t1.0, 42);
363
364 let t2 = Instant::recent();
365 mock.increment(420);
366 let t3 = Instant::recent();
367
368 assert_eq!(t2.0, 42);
369 assert_eq!(t3.0, 462);
370
371 crate::set_recent(Instant(1440));
372 let t4 = Instant::recent();
373 assert_eq!(t4.0, 1440);
374 })
375 }
376}