Skip to main content

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}