Skip to main content

zeppelin_embed/lifecycle/
clock.rs

1//! Monotonic time authority used by store-owned deadline checks.
2
3use std::time::Instant;
4
5/// A monotonic time source.
6///
7/// Production stores use [`SystemMonotonicClock`]. The trait is public so
8/// deterministic test dependencies can advance time without sleeping.
9pub trait MonotonicClock: Send + Sync {
10    /// Returns the current monotonic instant.
11    fn now(&self) -> Instant;
12}
13
14/// The standard-library monotonic clock used by ordinary stores.
15#[derive(Clone, Copy, Debug, Default)]
16pub struct SystemMonotonicClock;
17
18impl MonotonicClock for SystemMonotonicClock {
19    fn now(&self) -> Instant {
20        Instant::now()
21    }
22}
23
24/// A manually advanced monotonic clock for deterministic fault tests.
25#[cfg(any(test, feature = "test-seams"))]
26#[derive(Debug)]
27pub struct ManualMonotonicClock {
28    base: Instant,
29    offset: std::sync::Mutex<std::time::Duration>,
30}
31
32#[cfg(any(test, feature = "test-seams"))]
33impl ManualMonotonicClock {
34    /// Creates a frozen clock at the current monotonic instant.
35    #[must_use]
36    pub fn new() -> Self {
37        Self::starting_at(Instant::now())
38    }
39
40    /// Creates a frozen clock at an explicit monotonic instant.
41    ///
42    /// This supports deterministic tests whose logical clock predates other
43    /// test setup without requiring a wall-clock sleep.
44    #[must_use]
45    pub fn starting_at(base: Instant) -> Self {
46        Self {
47            base,
48            offset: std::sync::Mutex::new(std::time::Duration::ZERO),
49        }
50    }
51
52    /// Advances the clock without sleeping.
53    pub fn advance(&self, duration: std::time::Duration) {
54        if let Ok(mut offset) = self.offset.lock() {
55            *offset = offset.saturating_add(duration);
56        }
57    }
58}
59
60#[cfg(any(test, feature = "test-seams"))]
61impl Default for ManualMonotonicClock {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67#[cfg(any(test, feature = "test-seams"))]
68impl MonotonicClock for ManualMonotonicClock {
69    fn now(&self) -> Instant {
70        let offset = self
71            .offset
72            .lock()
73            .map_or(std::time::Duration::ZERO, |offset| *offset);
74        self.base.checked_add(offset).unwrap_or(self.base)
75    }
76}