OXYGENHUB

Coverage / crates/spectra-core/src/hlc.rs 100.0%

31 of 31 lines run, 6 of 8 functions · run #29 on main · run missed

1
//! The hybrid logical clock on every event (spec §9.1), with Oxygen's drift
2
//! bound (oxygen spec §6.4): observing a remote time never moves this clock
3
//! more than five minutes ahead of the local wall clock.
4
5
use std::sync::Mutex;
6
7
/// How far ahead of the local clock an observed time may move ours.
8
pub const MAX_DRIFT_MS: u64 = 5 * 60 * 1000;
9
10
/// A hybrid logical clock reading: wall milliseconds and a counter.
11
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
12
pub struct Hlc {
13
    pub wall_ms: u64,
14
    pub logical: u32,
15
}
16
17
/// The clock: monotonic across calls, whatever the wall clock does.
18
#[derive(Debug, Default)]
19
pub struct Clock {
20
    last: Mutex<Hlc>,
21
}
22
23
impl Clock {
24
    /// A clock that continues from `last` (the ledger's newest reading).
25207×
    pub fn starting_at(last: Hlc) -> Clock {
26207×
        Clock { last: Mutex::new(last) }
27207×
    }
28
29
    /// The next reading for a local event, at wall time `now_ms`.
3012313×
    pub fn tick(&self, now_ms: u64) -> Hlc {
3112313×
        let mut last = self.last.lock().unwrap();
3212313×
        *last = if now_ms > last.wall_ms {
332374×
            Hlc { wall_ms: now_ms, logical: 0 }
34
        } else {
359939×
            Hlc { wall_ms: last.wall_ms, logical: last.logical.saturating_add(1) }
36
        };
3712313×
        *last
3812313×
    }
39
40
    /// Take a remote reading into account, as on receiving another
41
    /// Spectra's event; the remote reading is kept as it is, only this
42
    /// clock's own advance is bounded.
431×
    pub fn observe(&self, remote: Hlc, now_ms: u64) {
441×
        let mut last = self.last.lock().unwrap();
451×
        let bound = now_ms.saturating_add(MAX_DRIFT_MS);
461×
        let remote = if remote.wall_ms > bound { Hlc { wall_ms: bound, logical: 0 } } else { remote };
471×
        if remote > *last {
481×
            *last = remote;
491×
        }
501×
    }
51
}
52
53
#[cfg(test)]
54
mod tests {
55
    use super::*;
56
57
    #[test]
581×
    fn ticks_are_monotonic_when_the_wall_clock_stalls_or_steps_back() {
591×
        let c = Clock::default();
601×
        let a = c.tick(1_000);
611×
        let b = c.tick(1_000);
621×
        let d = c.tick(500);
631×
        assert!(a < b && b < d);
641×
        assert_eq!(c.tick(2_000), Hlc { wall_ms: 2_000, logical: 0 });
651×
    }
66
67
    #[test]
681×
    fn a_far_future_remote_time_moves_the_clock_by_the_drift_bound_at_most() {
691×
        let c = Clock::default();
701×
        c.observe(Hlc { wall_ms: 10_000_000_000, logical: 7 }, 1_000);
711×
        assert_eq!(c.tick(1_000).wall_ms, 1_000 + MAX_DRIFT_MS);
721×
    }
73
}