Skip to main content

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}