//! The hybrid logical clock on every event (spec §9.1), with Oxygen's drift //! bound (oxygen spec §6.4): observing a remote time never moves this clock //! more than five minutes ahead of the local wall clock. use std::sync::Mutex; /// How far ahead of the local clock an observed time may move ours. pub const MAX_DRIFT_MS: u64 = 5 * 60 * 1000; /// A hybrid logical clock reading: wall milliseconds and a counter. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct Hlc { pub wall_ms: u64, pub logical: u32, } /// The clock: monotonic across calls, whatever the wall clock does. #[derive(Debug, Default)] pub struct Clock { last: Mutex, } impl Clock { /// A clock that continues from `last` (the ledger's newest reading). pub fn starting_at(last: Hlc) -> Clock { Clock { last: Mutex::new(last) } } /// The next reading for a local event, at wall time `now_ms`. pub fn tick(&self, now_ms: u64) -> Hlc { let mut last = self.last.lock().unwrap(); *last = if now_ms > last.wall_ms { Hlc { wall_ms: now_ms, logical: 0 } } else { Hlc { wall_ms: last.wall_ms, logical: last.logical.saturating_add(1) } }; *last } /// Take a remote reading into account, as on receiving another /// Spectra's event; the remote reading is kept as it is, only this /// clock's own advance is bounded. pub fn observe(&self, remote: Hlc, now_ms: u64) { let mut last = self.last.lock().unwrap(); let bound = now_ms.saturating_add(MAX_DRIFT_MS); let remote = if remote.wall_ms > bound { Hlc { wall_ms: bound, logical: 0 } } else { remote }; if remote > *last { *last = remote; } } } #[cfg(test)] mod tests { use super::*; #[test] fn ticks_are_monotonic_when_the_wall_clock_stalls_or_steps_back() { let c = Clock::default(); let a = c.tick(1_000); let b = c.tick(1_000); let d = c.tick(500); assert!(a < b && b < d); assert_eq!(c.tick(2_000), Hlc { wall_ms: 2_000, logical: 0 }); } #[test] fn a_far_future_remote_time_moves_the_clock_by_the_drift_bound_at_most() { let c = Clock::default(); c.observe(Hlc { wall_ms: 10_000_000_000, logical: 7 }, 1_000); assert_eq!(c.tick(1_000).wall_ms, 1_000 + MAX_DRIFT_MS); } }