Skip to main content

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        eprintln!("delta: {delta:?}");
132        assert!(delta >= LocalClockDelta::from_millis(999)); // allow for rounding to lose a milli
133        assert!(delta < std::time::Duration::from_secs(2).into()); // sanity check
134    }
135
136    #[test]
137    fn initial_delta() {
138        let mut clock = SystemTimeClock::new(LocalClockDelta::from_millis(1000));
139
140        let time = clock.get_time();
141        let now = LocalClockTime::from(SystemTime::now());
142
143        let delta = time - now;
144
145        // cannot use assert_eq, because there is a *bit* of extra time elapsed
146        // aside from the thread sleep.
147        eprintln!("delta: {delta:?}");
148        assert!(delta >= LocalClockDelta::from_millis(999)); // allow for rounding to lose a milli
149        assert!(delta < LocalClockDelta::from_millis(2000)); // sanity check
150    }
151
152    #[test]
153    fn naive_set_time_backwards() {
154        let mut clock = SystemTimeClock::new(LocalClockDelta::ZERO);
155
156        clock.set_time(LocalClockTime::from_millis_since_unix_epoch(0));
157
158        let time = clock.get_time();
159        std::thread::sleep(std::time::Duration::from_secs(1));
160        let new_time = clock.get_time();
161
162        let delta = new_time - time;
163
164        // cannot use assert_eq, because there is a *bit* of extra time elapsed
165        // aside from the thread sleep.
166        eprintln!("delta: {delta:?}");
167        assert!(delta >= LocalClockDelta::from_millis(999)); // allow for rounding to lose a milli
168        assert!(delta < std::time::Duration::from_secs(2).into()); // sanity check
169    }
170}