local_clock/
clock_impls.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Some useful built-in implementations of `LocalClock`.
5
6use super::LocalClock;
7use super::LocalClockDelta;
8use super::LocalClockTime;
9use parking_lot::Mutex;
10use std::sync::Arc;
11use std::time::SystemTime;
12
13/// An implementation of [`LocalClock`] backed by [`std::time::SystemTime`].
14///
15/// # A note on Time Travel
16///
17/// The time reported by [`std::time::SystemTime`] may be radically altered by
18/// external factors, such as the system operator manually setting the global
19/// system clock. If this happens, `SystemTimeClock` will _also_ jump
20/// forwards/backwards in time in-tandem, depending on how far the clock was
21/// rolled back/forwards!
22///
23/// If this is something that concerns you, you might want to consider writing a
24/// custom [`LocalClock`] implementation backed by something akin to Linux's
25/// `CLOCK_BOOTTIME`, which provides a stable monotonically increasing clock
26/// resilient against host suspends / resumes, and not subject to unexpected
27/// negative / positive time jumps.
28#[derive(Debug)]
29#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
30pub struct SystemTimeClock {
31    offset_from_system_time: LocalClockDelta,
32}
33
34impl SystemTimeClock {
35    /// Create a new [`SystemTimeClock`], set to the current [`SystemTime`] plus
36    /// `initial_delta`.
37    pub fn new(initial_delta: LocalClockDelta) -> SystemTimeClock {
38        SystemTimeClock {
39            offset_from_system_time: initial_delta,
40        }
41    }
42}
43
44impl LocalClock for SystemTimeClock {
45    fn get_time(&mut self) -> LocalClockTime {
46        LocalClockTime::from(SystemTime::now()) + self.offset_from_system_time
47    }
48
49    fn set_time(&mut self, new_time: LocalClockTime) {
50        self.offset_from_system_time = new_time - LocalClockTime::from(SystemTime::now());
51    }
52}
53
54/// A mock implementation of [`LocalClock`], which is manually ticked via a
55/// [`MockLocalClockAccessor`]. Useful for tests.
56#[derive(Debug)]
57#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
58pub struct MockLocalClock {
59    time: Arc<Mutex<LocalClockTime>>,
60}
61
62impl MockLocalClock {
63    /// Create a new [`MockLocalClock`]
64    pub fn new() -> Self {
65        MockLocalClock {
66            time: Arc::new(Mutex::new(LocalClockTime::from_millis_since_unix_epoch(
67                1337,
68            ))),
69        }
70    }
71
72    /// Return a new [`MockLocalClockAccessor`], which can tick the clock.
73    pub fn accessor(&self) -> MockLocalClockAccessor {
74        MockLocalClockAccessor {
75            time: self.time.clone(),
76        }
77    }
78}
79
80impl LocalClock for MockLocalClock {
81    fn get_time(&mut self) -> LocalClockTime {
82        *self.time.lock()
83    }
84
85    fn set_time(&mut self, new_time: LocalClockTime) {
86        *self.time.lock() = new_time
87    }
88}
89
90/// Handle to manually tick an instance of [`MockLocalClock`].
91#[derive(Clone)]
92pub struct MockLocalClockAccessor {
93    time: Arc<Mutex<LocalClockTime>>,
94}
95
96impl MockLocalClockAccessor {
97    /// Bump the amount of mock time that's passed.
98    pub fn tick(&self, d: std::time::Duration) {
99        let mut time = self.time.lock();
100        *time = *time + LocalClockDelta::from_millis(d.as_millis().try_into().unwrap())
101    }
102
103    /// Bump the amount of mock time that's passed backwards in time.
104    pub fn tick_backwards(&self, d: std::time::Duration) {
105        let mut time = self.time.lock();
106        *time = *time - LocalClockDelta::from_millis(d.as_millis().try_into().unwrap())
107    }
108
109    /// Get the current clock time.
110    pub fn get_time(&self) -> LocalClockTime {
111        *self.time.lock()
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn naive_system_time() {
121        let mut clock = SystemTimeClock::new(LocalClockDelta::ZERO);
122
123        let time = clock.get_time();
124        std::thread::sleep(std::time::Duration::from_secs(1));
125        let new_time = clock.get_time();
126
127        let delta = new_time - time;
128
129        // cannot use assert_eq, because there is a *bit* of extra time elapsed
130        // aside from the thread sleep.
131        assert!(delta >= std::time::Duration::from_secs(1).into());
132        assert!(delta < std::time::Duration::from_secs(2).into()); // sanity check
133    }
134
135    #[test]
136    fn initial_delta() {
137        let mut clock = SystemTimeClock::new(LocalClockDelta::from_millis(1000));
138
139        let time = clock.get_time();
140        let now = LocalClockTime::from(SystemTime::now());
141
142        let delta = time - now;
143
144        // cannot use assert_eq, because there is a *bit* of extra time elapsed
145        // aside from the thread sleep.
146        assert!(delta >= LocalClockDelta::from_millis(1000));
147        assert!(delta < LocalClockDelta::from_millis(2000)); // sanity check
148    }
149
150    #[test]
151    fn naive_set_time_backwards() {
152        let mut clock = SystemTimeClock::new(LocalClockDelta::ZERO);
153
154        clock.set_time(LocalClockTime::from_millis_since_unix_epoch(0));
155
156        let time = clock.get_time();
157        std::thread::sleep(std::time::Duration::from_secs(1));
158        let new_time = clock.get_time();
159
160        let delta = new_time - time;
161
162        // cannot use assert_eq, because there is a *bit* of extra time elapsed
163        // aside from the thread sleep.
164        assert!(delta >= std::time::Duration::from_secs(1).into());
165        assert!(delta < std::time::Duration::from_secs(2).into()); // sanity check
166    }
167}