use chrono::Utc;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Default,
)]
pub struct Timestamp {
wall_ms: u64,
counter: u32,
node_id: u64,
}
impl Timestamp {
pub fn new(wall_ms: u64, counter: u32, node_id: u64) -> Timestamp {
Timestamp {
wall_ms,
counter,
node_id,
}
}
pub fn wall_ms(&self) -> u64 {
self.wall_ms
}
pub fn counter(&self) -> u32 {
self.counter
}
pub fn node_id(&self) -> u64 {
self.node_id
}
}
fn phys_now_ms() -> u64 {
Utc::now().timestamp_millis().max(0) as u64
}
pub trait Clock: Send + Sync + 'static {
fn now(&self) -> Timestamp;
fn observe(&self, remote: Timestamp);
}
#[derive(Debug)]
pub(crate) struct HlcClock {
node_id: u64,
last: Mutex<Timestamp>,
}
impl HlcClock {
pub fn new(node_id: u64) -> HlcClock {
HlcClock {
node_id,
last: Mutex::new(Timestamp {
wall_ms: 0,
counter: 0,
node_id,
}),
}
}
}
impl Clock for HlcClock {
fn now(&self) -> Timestamp {
let pt = phys_now_ms();
let mut last = self.last.lock();
let next = if pt > last.wall_ms {
Timestamp {
wall_ms: pt,
counter: 0,
node_id: self.node_id,
}
} else {
Timestamp {
wall_ms: last.wall_ms,
counter: last.counter + 1,
node_id: self.node_id,
}
};
*last = next;
next
}
fn observe(&self, remote: Timestamp) {
let pt = phys_now_ms();
let mut last = self.last.lock();
let max_wall = pt.max(last.wall_ms).max(remote.wall_ms);
let counter = if max_wall == last.wall_ms && max_wall == remote.wall_ms {
last.counter.max(remote.counter) + 1
} else if max_wall == last.wall_ms {
last.counter + 1
} else if max_wall == remote.wall_ms {
remote.counter + 1
} else {
0
};
*last = Timestamp {
wall_ms: max_wall,
counter,
node_id: self.node_id,
};
}
}
pub trait Timestamped {
fn timestamp(&self) -> Timestamp;
}
impl<V> Timestamped for (Timestamp, V) {
fn timestamp(&self) -> Timestamp {
self.0
}
}
#[cfg(test)]
#[derive(Debug)]
pub(crate) struct ManualClock {
node_id: u64,
last: Mutex<Timestamp>,
}
#[cfg(test)]
impl ManualClock {
pub(crate) fn new(node_id: u64) -> ManualClock {
ManualClock {
node_id,
last: Mutex::new(Timestamp::new(0, 0, node_id)),
}
}
}
#[cfg(test)]
impl Clock for ManualClock {
fn now(&self) -> Timestamp {
let mut last = self.last.lock();
let next = Timestamp::new(last.wall_ms(), last.counter() + 1, self.node_id);
*last = next;
next
}
fn observe(&self, remote: Timestamp) {
let mut last = self.last.lock();
if remote > *last {
*last = Timestamp::new(remote.wall_ms(), remote.counter(), self.node_id);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn now_is_strictly_monotonic() {
let clock = HlcClock::new(1);
let mut prev = clock.now();
for _ in 0..10_000 {
let next = clock.now();
assert!(next > prev, "{next:?} !> {prev:?}");
prev = next;
}
}
#[test]
fn counter_increments_when_wall_does_not_advance() {
let clock = HlcClock::new(1);
clock.observe(Timestamp::new(u64::MAX - 100, 0, 9));
let a = clock.now();
let b = clock.now();
assert_eq!(a.wall_ms(), b.wall_ms());
assert_eq!(b.counter(), a.counter() + 1);
}
#[test]
fn observe_advances_past_a_future_timestamp() {
let clock = HlcClock::new(1);
let future = Timestamp::new(phys_now_ms() + 10_000_000, 5, 2);
clock.observe(future);
let local = clock.now();
assert!(
local > future,
"local write {local:?} was not ordered after observed future timestamp {future:?}"
);
}
#[test]
fn total_order_breaks_ties_on_node_id() {
let a = Timestamp::new(100, 0, 1);
let b = Timestamp::new(100, 0, 2);
assert!(a < b);
assert!(b > a);
assert!(Timestamp::new(100, 1, 1) > Timestamp::new(100, 0, 2));
assert!(Timestamp::new(101, 0, 1) > Timestamp::new(100, 9, 9));
}
#[test]
fn manual_clock_is_deterministic() {
let clock = ManualClock::new(7);
assert_eq!(clock.now(), Timestamp::new(0, 1, 7));
assert_eq!(clock.now(), Timestamp::new(0, 2, 7));
let remote = Timestamp::new(50, 4, 9);
clock.observe(remote);
let local = clock.now();
assert_eq!(local, Timestamp::new(50, 5, 7));
assert!(local > remote);
}
}