liminal_server/console/wall.rs
1//! The one wall-clock reading the console's records are timed from.
2//!
3//! The wall clock is read once, when the hub is built, and every message's
4//! time is that reading plus the monotonic time elapsed since. A per-message
5//! clock read could fail (a clock set before the epoch) or step backwards;
6//! this cannot, and its one failure happens at start, where it is named.
7
8use std::time::{Instant, SystemTime, UNIX_EPOCH};
9
10use super::error::ConsoleError;
11
12/// A wall-clock reading in milliseconds since the epoch, paired with the
13/// monotonic instant it was taken at.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct WallBase {
16 unix_ms: u64,
17 at: Instant,
18}
19
20impl WallBase {
21 /// Reads the wall clock now.
22 ///
23 /// # Errors
24 /// Returns [`ConsoleError::Clock`] when the wall clock reads before the
25 /// epoch or past what milliseconds in a `u64` can hold.
26 pub fn now() -> Result<Self, ConsoleError> {
27 let at = Instant::now();
28 let since_epoch = SystemTime::now()
29 .duration_since(UNIX_EPOCH)
30 .map_err(|error| ConsoleError::Clock {
31 reason: format!("the wall clock reads before the epoch: {error}"),
32 })?;
33 let unix_ms = u64::try_from(since_epoch.as_millis()).map_err(|_| ConsoleError::Clock {
34 reason: "the wall clock is past u64 milliseconds".to_owned(),
35 })?;
36 Ok(Self { unix_ms, at })
37 }
38
39 /// A base fixed at `unix_ms` for `at`, for records whose times must be
40 /// reproducible.
41 #[must_use]
42 pub const fn fixed(unix_ms: u64, at: Instant) -> Self {
43 Self { unix_ms, at }
44 }
45
46 /// Wall time, in milliseconds since the epoch, of the monotonic instant
47 /// `seen_at`. An instant before the base reads as the base.
48 ///
49 /// # Errors
50 /// Returns [`ConsoleError::Clock`] when the sum passes u64 milliseconds.
51 pub fn unix_ms_at(&self, seen_at: Instant) -> Result<u64, ConsoleError> {
52 self.unix_ms_of(seen_at.max(self.at))
53 }
54
55 /// Wall time, in milliseconds since the epoch, of the monotonic instant
56 /// `at`, before or after the base.
57 ///
58 /// # Errors
59 /// Returns [`ConsoleError::Clock`] when it falls outside u64
60 /// milliseconds.
61 pub fn unix_ms_of(&self, at: Instant) -> Result<u64, ConsoleError> {
62 let wall = if at >= self.at {
63 u64::try_from(at.duration_since(self.at).as_millis())
64 .ok()
65 .and_then(|after| self.unix_ms.checked_add(after))
66 } else {
67 u64::try_from(self.at.duration_since(at).as_millis())
68 .ok()
69 .and_then(|before| self.unix_ms.checked_sub(before))
70 };
71 wall.ok_or_else(|| ConsoleError::Clock {
72 reason: "a time is outside u64 milliseconds since the epoch".to_owned(),
73 })
74 }
75}