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