microvms_edges/clock.rs
1// SPDX-License-Identifier: Apache-2.0
2//! The production clock.
3//!
4//! The port is `microvms_app::clock::Clock`, and why there's one of it is written there.
5
6use std::future::Future;
7use std::pin::Pin;
8use std::time::Duration;
9
10use microvms_app::clock::Clock;
11
12/// The production clock: tokio's monotonic clock and the system's wall clock.
13///
14/// tokio's rather than `std`'s for a reason that is easy to get backwards: under a
15/// deterministic simulator the tokio clock is virtual and `std::time::Instant` isn't, so a
16/// sixty-minute boundary crossed in simulated time is invisible to `std`. A client that timed
17/// its token or its launch wait against `std::time::Instant` would pass a long simulation
18/// without ever re-minting or timing out. Outside a runtime whose clock is paused, which only
19/// a simulator or a test sets up, `tokio::time::Instant::now()` is the std monotonic clock, so
20/// production behavior is the same as it was over `std`.
21///
22/// The wall reading stays on `SystemTime` under a simulator. Nothing times a deadline on it.
23#[derive(Debug)]
24pub struct TokioClock {
25 base: tokio::time::Instant,
26}
27
28impl TokioClock {
29 pub fn new() -> Self {
30 Self {
31 base: tokio::time::Instant::now(),
32 }
33 }
34}
35
36impl Default for TokioClock {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl Clock for TokioClock {
43 fn elapsed(&self) -> Duration {
44 self.base.elapsed()
45 }
46
47 fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
48 Box::pin(tokio::time::sleep(duration))
49 }
50
51 fn unix_now(&self) -> Duration {
52 std::time::SystemTime::now()
53 .duration_since(std::time::UNIX_EPOCH)
54 .unwrap_or_default()
55 }
56}
57
58/// The control plane's name for [`TokioClock`], kept so `control::SystemClock::new()` and
59/// `SystemClock::default()` still resolve.
60pub use TokioClock as SystemClock;
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 /// The production clock's wall reading is the system's, and its sleep is tokio's, so a
67 /// paused runtime moves `elapsed` by exactly the slept duration.
68 #[tokio::test(start_paused = true)]
69 async fn the_production_clock_measures_tokio_time() {
70 let clock = TokioClock::new();
71 clock.sleep(Duration::from_secs(1800)).await;
72 assert_eq!(clock.elapsed(), Duration::from_secs(1800));
73 let system = std::time::SystemTime::now()
74 .duration_since(std::time::UNIX_EPOCH)
75 .expect("the host clock is after 1970");
76 assert!(clock.unix_now().abs_diff(system) < Duration::from_secs(60));
77 }
78}