Skip to main content

tollgate_store/
clock.rs

1//! The single place wall-clock time enters the system.
2
3use std::sync::Mutex;
4
5use jiff::{SignedDuration, Timestamp};
6
7/// Decode durable microseconds, including the fractional final second of
8/// Timestamp::MAX. The seconds/nanoseconds constructor checks the full domain;
9/// Jiff's microsecond constructor omits that final fraction from its bound.
10/// Euclidean division preserves pre-epoch timestamps too.
11pub fn timestamp_from_micros(value: i64) -> Result<Timestamp, crate::StoreError> {
12    Timestamp::new(
13        value.div_euclid(1_000_000),
14        (value.rem_euclid(1_000_000) * 1_000) as i32,
15    )
16    .map_err(|_| crate::StoreError("stored microseconds exceed the timestamp domain".into()))
17}
18
19/// Supplies `now` to the background planes. The core and admission layers
20/// take timestamps as arguments; implementations of this trait are the only
21/// code that decides what those timestamps are.
22pub trait Clock: Send + Sync + 'static {
23    /// The current instant, as this clock defines it.
24    fn now(&self) -> Timestamp;
25}
26
27/// Production clock.
28#[derive(Debug, Default, Clone, Copy)]
29pub struct SystemClock;
30
31impl Clock for SystemClock {
32    fn now(&self) -> Timestamp {
33        #[allow(
34            clippy::disallowed_methods,
35            reason = "this is the one production read of the business clock; every other caller takes a Timestamp from a Clock, which is what makes the rest of the system replayable"
36        )]
37        Timestamp::now()
38    }
39}
40
41/// Deterministic test clock: starts where you set it, moves when you say so.
42#[derive(Debug)]
43pub struct ManualClock(Mutex<Timestamp>);
44
45impl ManualClock {
46    /// A clock that reads `start` until it is moved.
47    #[must_use]
48    pub fn new(start: Timestamp) -> Self {
49        ManualClock(Mutex::new(start))
50    }
51
52    /// Move the clock to `to`, forwards or backwards.
53    ///
54    /// # Panics
55    ///
56    /// If an earlier call panicked while holding the clock's lock, as an
57    /// overflowing [`advance`](Self::advance) does.
58    pub fn set(&self, to: Timestamp) {
59        *self.0.lock().expect("manual clock poisoned") = to;
60    }
61
62    /// Move the clock by `by`, which may be negative.
63    ///
64    /// # Panics
65    ///
66    /// If the result falls outside the [`Timestamp`] range, or an earlier call
67    /// panicked while holding the clock's lock.
68    pub fn advance(&self, by: SignedDuration) {
69        let mut guard = self.0.lock().expect("manual clock poisoned");
70        *guard = guard.checked_add(by).expect("manual clock overflow");
71    }
72}
73
74impl Clock for ManualClock {
75    fn now(&self) -> Timestamp {
76        *self.0.lock().expect("manual clock poisoned")
77    }
78}