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        let total_nanos = u64::try_from(duration.as_nanos()).ok()?;
175        self.0.checked_add(total_nanos).map(Instant)
176    }
177
178    /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
179    /// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
180    /// otherwise.
181    pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
182        let total_nanos = u64::try_from(duration.as_nanos()).ok()?;
183        self.0.checked_sub(total_nanos).map(Instant)
184    }
185}
186
187impl Add<Duration> for Instant {
188    type Output = Instant;
189
190    /// # Panics
191    ///
192    /// This function may panic if the resulting point in time cannot be represented by the
193    /// underlying data structure. See [`Instant::checked_add`] for a version without panic.
194    fn add(self, other: Duration) -> Instant {
195        self.checked_add(other)
196            .expect("overflow when adding duration to instant")
197    }
198}
199
200impl AddAssign<Duration> for Instant {
201    fn add_assign(&mut self, other: Duration) {
202        // This is not millenium-safe, but, I think that's OK. :)
203        self.0 = self.0 + other.as_nanos() as u64;
204    }
205}
206
207impl Sub<Duration> for Instant {
208    type Output = Instant;
209
210    fn sub(self, other: Duration) -> Instant {
211        self.checked_sub(other)
212            .expect("overflow when subtracting duration from instant")
213    }
214}
215
216impl SubAssign<Duration> for Instant {
217    fn sub_assign(&mut self, other: Duration) {
218        // This is not millenium-safe, but, I think that's OK. :)
219        self.0 = self.0 - other.as_nanos() as u64;
220    }
221}
222
223impl Sub<Instant> for Instant {
224    type Output = Duration;
225
226    /// Returns the amount of time elapsed from another instant to this one,
227    /// or zero duration if that instant is later than this one.
228    ///
229    /// # Panics
230    ///
231    /// Previous `quanta` versions panicked when `other` was later than `self`. Currently this
232    /// method saturates. Future versions may reintroduce the panic in some circumstances.
233    /// See [Monotonicity].
234    ///
235    /// [Monotonicity]: Instant#monotonicity
236    fn sub(self, other: Instant) -> Duration {
237        self.duration_since(other)
238    }
239}
240
241impl PartialOrd for Instant {
242    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
243        Some(self.cmp(other))
244    }
245}
246
247impl Ord for Instant {
248    fn cmp(&self, other: &Self) -> Ordering {
249        self.0.cmp(&other.0)
250    }
251}
252
253impl fmt::Debug for Instant {
254    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255        self.0.fmt(f)
256    }
257}
258
259#[cfg(feature = "prost")]
260impl Into<prost_types::Timestamp> for Instant {
261    fn into(self) -> prost_types::Timestamp {
262        let dur = Duration::from_nanos(self.0);
263        let secs = if dur.as_secs() > i64::MAX as u64 {
264            i64::MAX
265        } else {
266            dur.as_secs() as i64
267        };
268        let nsecs = if dur.subsec_nanos() > i32::MAX as u32 {
269            i32::MAX
270        } else {
271            dur.subsec_nanos() as i32
272        };
273        prost_types::Timestamp {
274            seconds: secs,
275            nanos: nsecs,
276        }
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use once_cell::sync::Lazy;
283
284    use super::Instant;
285    use crate::{with_clock, Clock};
286    use std::time::Duration;
287    use std::{sync::Mutex, thread};
288
289    static RECENT_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
290
291    #[test]
292    #[cfg_attr(
293        all(target_arch = "wasm32", target_os = "unknown"),
294        ignore = "WASM thread cannot sleep"
295    )]
296    fn test_now() {
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 {:?} (t0: {}, t1: {})",
332            result,
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
377    // Test fix for issue #109
378    #[test]
379    #[cfg_attr(
380        all(target_arch = "wasm32", target_os = "unknown"),
381        wasm_bindgen_test::wasm_bindgen_test
382    )]
383    fn checked_arithmetic_u64_overflow() {
384        fn nanos_to_dur(total_nanos: u128) -> Duration {
385            let nanos_per_sec = Duration::from_secs(1).as_nanos();
386            let secs = total_nanos / nanos_per_sec;
387            let nanos = total_nanos % nanos_per_sec;
388            let dur = Duration::new(secs as _, nanos as _);
389            assert_eq!(dur.as_nanos(), total_nanos);
390            dur
391        }
392
393        let dur = nanos_to_dur(1 << 64);
394        let now = Instant::now();
395
396        let behind = now.checked_sub(dur);
397        let ahead = now.checked_add(dur);
398
399        assert_ne!(Duration::ZERO, dur);
400        assert_ne!(Some(now), behind);
401        assert_ne!(Some(now), ahead);
402    }
403}