ash_lock/clock.rs
1//! Causality clock used to stamp lock operations.
2//!
3//! ADOPTS `ash-time`: when the `ash-time` feature is enabled, every acquire /
4//! renew / release is stamped with a Hybrid Logical Clock timestamp from
5//! `ash_time::HlcClock`. HLC timestamps are totally ordered and causally
6//! consistent, which makes them a natural fit for a coordination service: the
7//! stamp on a [`LockGuard`](crate::guard::LockGuard) lets callers reason about
8//! the causal order of lock events across nodes (e.g. for audit logs or
9//! debugging split-brain), independently of the fencing token.
10//!
11//! Without the feature, a monotonic in-process counter provides the same
12//! *interface* (`Stamp` is still totally ordered locally) so downstream code
13//! never needs `cfg` guards. The counter's `physical` field is left at 0 to
14//! make it obvious the value is not wall-clock-derived.
15
16#[cfg(not(feature = "ash-time"))]
17use std::sync::atomic::{AtomicU64, Ordering};
18
19#[cfg(feature = "ash-time")]
20use crate::error::Error;
21use crate::error::Result;
22
23/// A causally-ordered stamp for a lock operation.
24///
25/// This mirrors `ash_time::HlcTimestamp` (a `(physical, logical)` pair) so the
26/// public shape is identical whether or not the `ash-time` feature is on.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub struct Stamp {
29 /// Wall-clock component (nanoseconds since the Unix epoch) when backed by
30 /// `ash-time`; `0` in the fallback counter mode.
31 pub physical: u64,
32 /// Logical tie-breaker / fallback monotonic counter.
33 pub logical: u64,
34}
35
36#[cfg(feature = "ash-time")]
37impl From<ash_time::HlcTimestamp> for Stamp {
38 fn from(ts: ash_time::HlcTimestamp) -> Self {
39 Stamp {
40 physical: ts.physical,
41 logical: ts.logical as u64,
42 }
43 }
44}
45
46/// A source of causally-ordered [`Stamp`]s, shared across a client's operations.
47pub struct Clock {
48 #[cfg(feature = "ash-time")]
49 hlc: ash_time::HlcClock,
50 #[cfg(not(feature = "ash-time"))]
51 counter: AtomicU64,
52}
53
54impl Clock {
55 /// Create a fresh clock.
56 pub fn new() -> Self {
57 #[cfg(feature = "ash-time")]
58 {
59 Self {
60 hlc: ash_time::HlcClock::new(),
61 }
62 }
63 #[cfg(not(feature = "ash-time"))]
64 {
65 Self {
66 counter: AtomicU64::new(0),
67 }
68 }
69 }
70
71 /// Produce a stamp for a new local event (a lock operation).
72 ///
73 /// The result is strictly greater than every stamp previously produced by
74 /// this clock, in both backends.
75 pub fn stamp(&self) -> Result<Stamp> {
76 #[cfg(feature = "ash-time")]
77 {
78 self.hlc
79 .now()
80 .map(Stamp::from)
81 .map_err(|e| Error::cluster(format!("hlc clock error: {e}")))
82 }
83 #[cfg(not(feature = "ash-time"))]
84 {
85 let logical = self.counter.fetch_add(1, Ordering::Relaxed) + 1;
86 Ok(Stamp {
87 physical: 0,
88 logical,
89 })
90 }
91 }
92}
93
94impl Default for Clock {
95 fn default() -> Self {
96 Self::new()
97 }
98}