Skip to main content

mongreldb_types/
hlc.rs

1//! Hybrid Logical Clock timestamps, the node clock, and the legacy-to-HLC
2//! migration model (spec section 8).
3//!
4//! [`HlcTimestamp`] is the commit/visibility timestamp of the target MVCC
5//! model. Ordering is lexicographic by
6//! `(physical_micros, logical, node_tiebreaker)`, which the derived
7//! `PartialOrd`/`Ord` implement via field declaration order.
8//!
9//! [`HlcClock`] implements the section 8.2 clock rules: physical time may
10//! move backward but returned timestamps never do, a received timestamp
11//! advances the local clock, and clock skew is monitored. Once the maximum
12//! observed skew exceeds the configured limit, timestamp allocation through
13//! [`HlcClock::now`] and [`HlcClock::observe`] fails closed with
14//! [`ClockSkewError`].
15//!
16//! [`MigrationWatermark`] carries the section 8.4 migration state. The stored
17//! format is always explicit (`mvcc_format_version` plus the watermark); it
18//! is never inferred from byte length.
19
20use core::cmp::Ordering;
21use core::fmt;
22use core::str::FromStr;
23use std::sync::{Arc, Mutex};
24use std::time::{Duration, SystemTime, UNIX_EPOCH};
25
26/// A hybrid-logical-clock timestamp (spec section 8.1).
27#[derive(
28    Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, serde::Serialize, serde::Deserialize,
29)]
30pub struct HlcTimestamp {
31    /// Physical wall-clock component in microseconds since the Unix epoch.
32    pub physical_micros: u64,
33    /// Logical counter, bumped when physical time does not advance.
34    pub logical: u32,
35    /// Node tiebreaker so equal physical+logical values order deterministically.
36    pub node_tiebreaker: u32,
37}
38
39impl HlcTimestamp {
40    /// The smallest possible timestamp.
41    pub const ZERO: Self = Self {
42        physical_micros: 0,
43        logical: 0,
44        node_tiebreaker: 0,
45    };
46
47    /// The largest possible timestamp (unbounded HLC visibility).
48    pub const MAX: Self = Self {
49        physical_micros: u64::MAX,
50        logical: u32::MAX,
51        node_tiebreaker: u32::MAX,
52    };
53}
54
55impl fmt::Display for HlcTimestamp {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        write!(
58            f,
59            "{}.{}.{}",
60            self.physical_micros, self.logical, self.node_tiebreaker
61        )
62    }
63}
64
65impl fmt::Debug for HlcTimestamp {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        fmt::Display::fmt(self, f)
68    }
69}
70
71/// Error returned when parsing a textual [`HlcTimestamp`] fails.
72#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
73pub enum HlcParseError {
74    /// The text was not `<physical_micros>.<logical>.<node_tiebreaker>`.
75    #[error(
76        "invalid HLC timestamp `{0}`: expected `<physical_micros>.<logical>.<node_tiebreaker>`"
77    )]
78    InvalidFormat(String),
79    /// One of the three dot-separated components was not an unsigned integer.
80    #[error("invalid HLC timestamp `{0}`: component `{1}` is not an unsigned integer")]
81    InvalidComponent(String, String),
82}
83
84impl FromStr for HlcTimestamp {
85    type Err = HlcParseError;
86
87    fn from_str(text: &str) -> Result<Self, Self::Err> {
88        fn parse_component<T: FromStr>(text: &str, component: &str) -> Result<T, HlcParseError> {
89            component
90                .parse::<T>()
91                .map_err(|_| HlcParseError::InvalidComponent(text.to_owned(), component.to_owned()))
92        }
93
94        let mut parts = text.split('.');
95        let (Some(physical), Some(logical), Some(node), None) =
96            (parts.next(), parts.next(), parts.next(), parts.next())
97        else {
98            return Err(HlcParseError::InvalidFormat(text.to_owned()));
99        };
100        Ok(Self {
101            physical_micros: parse_component::<u64>(text, physical)?,
102            logical: parse_component::<u32>(text, logical)?,
103            node_tiebreaker: parse_component::<u32>(text, node)?,
104        })
105    }
106}
107
108/// Error returned when clock skew exceeds the configured maximum (spec
109/// section 8.2): excessive skew rejects timestamp allocation.
110#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
111#[error("clock skew of {observed:?} exceeds the configured maximum {maximum:?}")]
112pub struct ClockSkewError {
113    /// Largest skew observed so far (high-water mark, never decreases).
114    pub observed: Duration,
115    /// Configured maximum acceptable skew.
116    pub maximum: Duration,
117}
118
119/// Injectable wall-clock source: microseconds since the Unix epoch.
120pub type WallClockSource = Arc<dyn Fn() -> u64 + Send + Sync>;
121
122/// Mutable state guarded by the [`HlcClock`] mutex.
123#[derive(Debug, Clone, Copy)]
124struct ClockState {
125    /// Physical component of the last timestamp the clock produced.
126    physical_micros: u64,
127    /// Logical component of the last timestamp the clock produced.
128    logical: u32,
129    /// High-water mark of `|remote.physical_micros - local physical|` in
130    /// microseconds. Never decreases; while it exceeds the configured
131    /// `max_skew`, timestamp allocation is rejected.
132    max_observed_skew_micros: u64,
133}
134
135/// A thread-safe hybrid logical clock (spec section 8.2).
136///
137/// Every timestamp handed out by [`Self::now`], [`Self::observe`], and
138/// [`Self::next_after`] is strictly greater than every timestamp the same
139/// clock handed out before, even when the physical wall clock regresses:
140/// when physical time does not advance, the logical counter is bumped
141/// instead. The logical counter saturates at `u32::MAX` rather than moving
142/// backward; physical time advancing again resets it.
143///
144/// The wall-clock source is injected at construction so tests fully control
145/// time; `node_tiebreaker` is attached to every returned timestamp.
146pub struct HlcClock {
147    state: Mutex<ClockState>,
148    wall: WallClockSource,
149    node_tiebreaker: u32,
150    max_skew: Duration,
151}
152
153impl HlcClock {
154    /// Creates a clock reading the system wall clock.
155    pub fn new(node_tiebreaker: u32, max_skew: Duration) -> Self {
156        Self::with_time_source(node_tiebreaker, max_skew, Arc::new(system_time_micros))
157    }
158
159    /// Creates a clock with an injected wall-clock source.
160    pub fn with_time_source(
161        node_tiebreaker: u32,
162        max_skew: Duration,
163        wall: WallClockSource,
164    ) -> Self {
165        Self {
166            state: Mutex::new(ClockState {
167                physical_micros: 0,
168                logical: 0,
169                max_observed_skew_micros: 0,
170            }),
171            wall,
172            node_tiebreaker,
173            max_skew,
174        }
175    }
176
177    /// The node tiebreaker attached to every timestamp this clock returns.
178    pub fn node_tiebreaker(&self) -> u32 {
179        self.node_tiebreaker
180    }
181
182    /// The configured maximum acceptable clock skew.
183    pub fn max_skew(&self) -> Duration {
184        self.max_skew
185    }
186
187    /// High-water mark of the skew between received timestamps and the local
188    /// physical clock (spec section 8.2 skew monitoring).
189    pub fn max_observed_skew(&self) -> Duration {
190        let state = self.state.lock().expect("HLC clock state poisoned");
191        Duration::from_micros(state.max_observed_skew_micros)
192    }
193
194    /// Allocates a fresh timestamp.
195    ///
196    /// Never moves backward: if the physical wall clock has not advanced past
197    /// the last produced timestamp, the logical counter is bumped instead.
198    ///
199    /// # Errors
200    ///
201    /// Returns [`ClockSkewError`] once excessive skew has been observed.
202    pub fn now(&self) -> Result<HlcTimestamp, ClockSkewError> {
203        let wall = (self.wall)();
204        let mut state = self.state.lock().expect("HLC clock state poisoned");
205        self.reject_excessive_skew(&state)?;
206        Self::advance(&mut state, wall);
207        Ok(self.stamp(&state))
208    }
209
210    /// Advances the local clock past `remote` and returns the new timestamp
211    /// (spec section 8.2: a received timestamp advances the local clock).
212    ///
213    /// The skew between `remote.physical_micros` and the local physical clock
214    /// is folded into [`Self::max_observed_skew`].
215    ///
216    /// # Errors
217    ///
218    /// Returns [`ClockSkewError`] when the skew of `remote` (or of any earlier
219    /// observation) exceeds the configured maximum; the local clock is left
220    /// unchanged in that case.
221    pub fn observe(&self, remote: HlcTimestamp) -> Result<HlcTimestamp, ClockSkewError> {
222        let wall = (self.wall)();
223        let mut state = self.state.lock().expect("HLC clock state poisoned");
224
225        let skew_micros = remote.physical_micros.abs_diff(wall);
226        state.max_observed_skew_micros = state.max_observed_skew_micros.max(skew_micros);
227        self.reject_excessive_skew(&state)?;
228
229        let local = (state.physical_micros, state.logical);
230        let physical = wall.max(local.0).max(remote.physical_micros);
231        let logical = if physical == local.0 && physical == remote.physical_micros {
232            local.1.max(remote.logical).saturating_add(1)
233        } else if physical == local.0 {
234            local.1.saturating_add(1)
235        } else if physical == remote.physical_micros {
236            remote.logical.saturating_add(1)
237        } else {
238            0
239        };
240        state.physical_micros = physical;
241        state.logical = logical;
242        Ok(self.stamp(&state))
243    }
244
245    /// Returns a timestamp strictly greater than `minimum`, advancing the
246    /// local clock when necessary.
247    ///
248    /// Does not consult skew state: [`Self::now`] and [`Self::observe`] are
249    /// the fail-closed allocation paths.
250    pub fn next_after(&self, minimum: HlcTimestamp) -> HlcTimestamp {
251        let wall = (self.wall)();
252        let mut state = self.state.lock().expect("HLC clock state poisoned");
253        Self::advance(&mut state, wall);
254        let mut candidate = self.stamp(&state);
255        if candidate <= minimum {
256            let (physical, logical) = if minimum.logical < u32::MAX {
257                (minimum.physical_micros, minimum.logical + 1)
258            } else {
259                (minimum.physical_micros.saturating_add(1), 0)
260            };
261            if physical > state.physical_micros {
262                state.physical_micros = physical;
263                state.logical = logical;
264            } else {
265                state.logical = state.logical.max(logical);
266            }
267            candidate = self.stamp(&state);
268        }
269        candidate
270    }
271
272    /// Commit timestamp strictly greater than every participant read/write
273    /// timestamp (spec section 8.2).
274    pub fn commit_timestamp(
275        &self,
276        participants: impl IntoIterator<Item = HlcTimestamp>,
277    ) -> HlcTimestamp {
278        let maximum = participants.into_iter().max().unwrap_or(HlcTimestamp::ZERO);
279        self.next_after(maximum)
280    }
281
282    /// The standard HLC tick: adopt the wall clock when it is ahead,
283    /// otherwise bump the logical counter.
284    fn advance(state: &mut ClockState, wall: u64) {
285        if wall > state.physical_micros {
286            state.physical_micros = wall;
287            state.logical = 0;
288        } else {
289            state.logical = state.logical.saturating_add(1);
290        }
291    }
292
293    fn reject_excessive_skew(&self, state: &ClockState) -> Result<(), ClockSkewError> {
294        let observed = Duration::from_micros(state.max_observed_skew_micros);
295        if observed > self.max_skew {
296            Err(ClockSkewError {
297                observed,
298                maximum: self.max_skew,
299            })
300        } else {
301            Ok(())
302        }
303    }
304
305    fn stamp(&self, state: &ClockState) -> HlcTimestamp {
306        HlcTimestamp {
307            physical_micros: state.physical_micros,
308            logical: state.logical,
309            node_tiebreaker: self.node_tiebreaker,
310        }
311    }
312}
313
314/// Microseconds since the Unix epoch according to the system wall clock.
315/// Saturates at `u64::MAX`; a clock before the epoch reads as zero.
316fn system_time_micros() -> u64 {
317    let micros = SystemTime::now()
318        .duration_since(UNIX_EPOCH)
319        .map(|d| d.as_micros())
320        .unwrap_or(0);
321    u64::try_from(micros).unwrap_or(u64::MAX)
322}
323
324/// Version stamp stored on row versions during the legacy-to-HLC migration
325/// (spec section 8.4).
326///
327/// Comparison semantics are defined relative to an explicit migration
328/// watermark: all `LegacyEpoch` values sort before the watermark and new HLC
329/// values sort at or after it. The format is always explicit; it is never
330/// inferred from byte length.
331#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
332pub enum StoredVersionStamp {
333    /// Pre-migration logical epoch counter.
334    LegacyEpoch(u64),
335    /// Post-migration hybrid logical clock timestamp.
336    Hlc(HlcTimestamp),
337}
338
339/// Durable migration state for the legacy-to-HLC cut-over (spec section 8.4).
340///
341/// The database stores both fields. The format is always explicit; it is
342/// never inferred from byte length.
343#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
344pub struct MigrationWatermark {
345    /// Explicit MVCC storage format version (see [`Self::FORMAT_LEGACY_EPOCH`]
346    /// and [`Self::FORMAT_HLC`]).
347    pub mvcc_format_version: u32,
348    /// Cut-over timestamp: all legacy epochs sort before it, all new HLC
349    /// timestamps sort at or after it.
350    pub watermark: HlcTimestamp,
351}
352
353impl MigrationWatermark {
354    /// `mvcc_format_version` while row versions carry
355    /// [`StoredVersionStamp::LegacyEpoch`].
356    pub const FORMAT_LEGACY_EPOCH: u32 = 1;
357    /// `mvcc_format_version` once row versions carry
358    /// [`StoredVersionStamp::Hlc`].
359    pub const FORMAT_HLC: u32 = 2;
360
361    /// Total order over stored version stamps during migration: legacy epochs
362    /// order numerically among themselves, every legacy epoch sorts before
363    /// every HLC timestamp, and HLC timestamps keep their natural order.
364    pub fn cmp_stamps(a: &StoredVersionStamp, b: &StoredVersionStamp) -> Ordering {
365        match (a, b) {
366            (StoredVersionStamp::LegacyEpoch(x), StoredVersionStamp::LegacyEpoch(y)) => x.cmp(y),
367            (StoredVersionStamp::LegacyEpoch(_), StoredVersionStamp::Hlc(_)) => Ordering::Less,
368            (StoredVersionStamp::Hlc(_), StoredVersionStamp::LegacyEpoch(_)) => Ordering::Greater,
369            (StoredVersionStamp::Hlc(x), StoredVersionStamp::Hlc(y)) => x.cmp(y),
370        }
371    }
372
373    /// How a stamp orders against the watermark: every legacy epoch sorts
374    /// before it; HLC timestamps sort at or after it (a pre-watermark HLC
375    /// timestamp cannot occur once the watermark is enforced, so it clamps to
376    /// [`Ordering::Equal`]).
377    pub fn cmp_stamp_to_watermark(&self, stamp: &StoredVersionStamp) -> Ordering {
378        match stamp {
379            StoredVersionStamp::LegacyEpoch(_) => Ordering::Less,
380            StoredVersionStamp::Hlc(ts) => ts.cmp(&self.watermark).max(Ordering::Equal),
381        }
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
389
390    /// Deterministic, manually advanced wall-clock source.
391    struct ManualWall {
392        micros: Arc<AtomicU64>,
393    }
394
395    impl ManualWall {
396        fn new(micros: u64) -> Self {
397            Self {
398                micros: Arc::new(AtomicU64::new(micros)),
399            }
400        }
401
402        fn source(&self) -> WallClockSource {
403            let micros = Arc::clone(&self.micros);
404            Arc::new(move || micros.load(AtomicOrdering::Relaxed))
405        }
406
407        fn set(&self, micros: u64) {
408            self.micros.store(micros, AtomicOrdering::Relaxed);
409        }
410    }
411
412    fn ts(physical_micros: u64, logical: u32, node_tiebreaker: u32) -> HlcTimestamp {
413        HlcTimestamp {
414            physical_micros,
415            logical,
416            node_tiebreaker,
417        }
418    }
419
420    #[test]
421    fn lexicographic_ordering() {
422        assert!(ts(1, 0, 0) < ts(2, 0, 0));
423        assert!(ts(1, 0, 0) < ts(1, 1, 0));
424        assert!(ts(1, 0, 0) < ts(1, 0, 1));
425        assert!(ts(1, 1, 0) < ts(1, 1, 1));
426        assert!(HlcTimestamp::ZERO < ts(0, 0, 1));
427    }
428
429    /// ID: P0.5-X4 — physical wall clock moves backward without violating HLC order.
430    #[test]
431    fn now_never_moves_backward_when_physical_time_regresses() {
432        let wall = ManualWall::new(1_000_000);
433        let clock = HlcClock::with_time_source(7, Duration::from_secs(60), wall.source());
434
435        let first = clock.now().unwrap();
436        assert_eq!(first, ts(1_000_000, 0, 7));
437
438        wall.set(500_000); // physical time jumps backward
439        let second = clock.now().unwrap();
440        assert!(second > first);
441        assert_eq!(second, ts(1_000_000, 1, 7));
442
443        wall.set(100_000); // and further backward
444        let third = clock.now().unwrap();
445        assert!(third > second);
446        assert_eq!(third, ts(1_000_000, 2, 7));
447
448        wall.set(2_000_000); // physical time advances again
449        let fourth = clock.now().unwrap();
450        assert!(fourth > third);
451        assert_eq!(fourth, ts(2_000_000, 0, 7));
452
453        // observe + next_after also stay strictly ordered under regression.
454        wall.set(1); // extreme regression
455        let observed = clock
456            .observe(ts(2_000_000, 0, 99))
457            .expect("skew within bound");
458        assert!(observed > fourth);
459        let after = clock.next_after(observed);
460        assert!(after > observed);
461    }
462
463    #[test]
464    fn concurrent_now_is_unique_and_ordered() {
465        let wall = ManualWall::new(1_000_000);
466        let clock = Arc::new(HlcClock::with_time_source(
467            7,
468            Duration::from_secs(60),
469            wall.source(),
470        ));
471
472        let handles: Vec<_> = (0..8)
473            .map(|_| {
474                let clock = Arc::clone(&clock);
475                std::thread::spawn(move || {
476                    (0..250).map(|_| clock.now().unwrap()).collect::<Vec<_>>()
477                })
478            })
479            .collect();
480
481        let mut all = Vec::with_capacity(8 * 250);
482        for handle in handles {
483            all.extend(handle.join().unwrap());
484        }
485        assert_eq!(all.len(), 8 * 250);
486
487        all.sort();
488        all.dedup();
489        assert_eq!(all.len(), 8 * 250, "every timestamp must be unique");
490
491        // The wall clock never moved, so the logical counter covers 0..2000.
492        for (index, stamp) in all.iter().enumerate() {
493            assert_eq!(stamp.physical_micros, 1_000_000);
494            assert_eq!(stamp.logical as usize, index);
495            assert_eq!(stamp.node_tiebreaker, 7);
496        }
497    }
498
499    #[test]
500    fn observe_advances_local_clock_past_remote() {
501        let wall = ManualWall::new(1_000);
502        let clock = HlcClock::with_time_source(3, Duration::from_secs(600), wall.source());
503
504        // A remote ahead of local physical time advances the local clock.
505        let remote = ts(5_000, 7, 9);
506        let observed = clock.observe(remote).unwrap();
507        assert!(observed > remote);
508        assert_eq!(observed, ts(5_000, 8, 3));
509
510        // The local clock stays ahead of the remote afterwards.
511        let next = clock.now().unwrap();
512        assert!(next > remote);
513        assert!(next > observed);
514        assert_eq!(next, ts(5_000, 9, 3));
515
516        // Skew monitoring folded the observation in.
517        assert_eq!(clock.max_observed_skew(), Duration::from_micros(4_000));
518
519        // Same physical component, higher logical: still advances.
520        let remote = ts(5_000, 20, 9);
521        let observed = clock.observe(remote).unwrap();
522        assert!(observed > remote);
523        assert_eq!(observed, ts(5_000, 21, 3));
524
525        // A stale remote does not move the clock backward.
526        let stale = ts(2_000, 0, 9);
527        let observed = clock.observe(stale).unwrap();
528        assert!(observed > stale);
529        assert_eq!(observed, ts(5_000, 22, 3));
530    }
531
532    #[test]
533    fn observe_rejects_excessive_skew_and_rejects_allocation() {
534        let wall = ManualWall::new(10_000);
535        let clock = HlcClock::with_time_source(1, Duration::from_micros(1_000), wall.source());
536
537        let remote = ts(15_000, 0, 2);
538        let err = clock.observe(remote).unwrap_err();
539        assert_eq!(
540            err,
541            ClockSkewError {
542                observed: Duration::from_micros(5_000),
543                maximum: Duration::from_micros(1_000),
544            }
545        );
546        assert_eq!(clock.max_observed_skew(), Duration::from_micros(5_000));
547
548        // The failed observation did not advance the clock.
549        let next = clock.next_after(HlcTimestamp::ZERO);
550        assert_eq!(next, ts(10_000, 0, 1));
551
552        // Excessive skew rejects further timestamp allocation.
553        assert!(clock.now().is_err());
554        assert!(clock.observe(ts(10_500, 0, 2)).is_err());
555    }
556
557    #[test]
558    fn next_after_is_strictly_greater() {
559        let wall = ManualWall::new(1_000);
560        let clock = HlcClock::with_time_source(5, Duration::from_secs(60), wall.source());
561
562        // A fresh tick already exceeds a small minimum.
563        assert_eq!(clock.next_after(ts(1, 1, 1)), ts(1_000, 0, 5)); // state (1000, 0)
564
565        // Same tick components but a higher tiebreaker on the minimum forces
566        // the strict-successor path.
567        let minimum = ts(1_000, 1, u32::MAX);
568        let next = clock.next_after(minimum); // tick -> (1000, 1); candidate <= minimum
569        assert!(next > minimum);
570        assert_eq!(next, ts(1_000, 2, 5)); // state (1000, 2)
571
572        // A minimum ahead of the wall clock jumps the physical component.
573        let minimum = ts(9_999, 42, 0);
574        let next = clock.next_after(minimum);
575        assert!(next > minimum);
576        assert_eq!(next, ts(9_999, 43, 5)); // state (9999, 43)
577
578        // Logical overflow rolls into the physical component.
579        let minimum = ts(9_999, u32::MAX, u32::MAX);
580        let next = clock.next_after(minimum);
581        assert!(next > minimum);
582        assert_eq!(next, ts(10_000, 0, 5)); // state (10000, 0)
583
584        // Consecutive calls stay strictly increasing.
585        let mut previous = next;
586        for _ in 0..100 {
587            let current = clock.next_after(HlcTimestamp::ZERO);
588            assert!(current > previous);
589            previous = current;
590        }
591    }
592
593    #[test]
594    fn commit_timestamp_exceeds_every_participant() {
595        let wall = ManualWall::new(5_000);
596        let clock = HlcClock::with_time_source(4, Duration::from_secs(60), wall.source());
597
598        let participants = [ts(5_000, 3, 9), ts(7_000, 0, 1), ts(6_999, 12, 12)];
599        let commit = clock.commit_timestamp(participants);
600        for participant in participants {
601            assert!(commit > participant);
602        }
603        assert_eq!(commit, ts(7_000, 1, 4));
604
605        // No participants: still a valid fresh timestamp.
606        let commit = clock.commit_timestamp(Vec::new());
607        assert!(commit > HlcTimestamp::ZERO);
608        assert_eq!(commit, ts(7_000, 2, 4));
609    }
610
611    #[test]
612    fn legacy_stamps_sort_before_watermark_and_hlc_at_or_after() {
613        let watermark = MigrationWatermark {
614            mvcc_format_version: MigrationWatermark::FORMAT_HLC,
615            watermark: ts(1_000, 0, 0),
616        };
617
618        // Every legacy epoch sorts before the watermark, however large.
619        assert_eq!(
620            watermark.cmp_stamp_to_watermark(&StoredVersionStamp::LegacyEpoch(u64::MAX)),
621            Ordering::Less
622        );
623        // HLC timestamps sort at or after the watermark.
624        assert_eq!(
625            watermark.cmp_stamp_to_watermark(&StoredVersionStamp::Hlc(ts(1_000, 0, 0))),
626            Ordering::Equal
627        );
628        assert_eq!(
629            watermark.cmp_stamp_to_watermark(&StoredVersionStamp::Hlc(ts(1_000, 0, 1))),
630            Ordering::Greater
631        );
632        // A pre-watermark HLC timestamp cannot occur once the watermark is
633        // enforced; the comparison clamps it to "at the watermark".
634        assert_eq!(
635            watermark.cmp_stamp_to_watermark(&StoredVersionStamp::Hlc(ts(999, 99, 99))),
636            Ordering::Equal
637        );
638
639        // Total order during migration.
640        assert_eq!(
641            MigrationWatermark::cmp_stamps(
642                &StoredVersionStamp::LegacyEpoch(u64::MAX),
643                &StoredVersionStamp::Hlc(HlcTimestamp::ZERO),
644            ),
645            Ordering::Less
646        );
647        assert_eq!(
648            MigrationWatermark::cmp_stamps(
649                &StoredVersionStamp::Hlc(HlcTimestamp::ZERO),
650                &StoredVersionStamp::LegacyEpoch(0),
651            ),
652            Ordering::Greater
653        );
654        assert_eq!(
655            MigrationWatermark::cmp_stamps(
656                &StoredVersionStamp::LegacyEpoch(7),
657                &StoredVersionStamp::LegacyEpoch(9),
658            ),
659            Ordering::Less
660        );
661        assert_eq!(
662            MigrationWatermark::cmp_stamps(
663                &StoredVersionStamp::Hlc(ts(1, 0, 0)),
664                &StoredVersionStamp::Hlc(ts(1, 0, 1)),
665            ),
666            Ordering::Less
667        );
668    }
669
670    #[test]
671    fn display_and_from_str_round_trip() {
672        for stamp in [
673            HlcTimestamp::ZERO,
674            ts(1, 2, 3),
675            ts(1_756_000_000_000_000, 42, 9),
676            ts(u64::MAX, u32::MAX, u32::MAX),
677        ] {
678            let text = stamp.to_string();
679            let parsed: HlcTimestamp = text.parse().unwrap();
680            assert_eq!(parsed, stamp);
681        }
682        assert_eq!(ts(1, 2, 3).to_string(), "1.2.3");
683        assert_eq!(format!("{:?}", ts(1, 2, 3)), "1.2.3");
684    }
685
686    #[test]
687    fn from_str_rejects_malformed_text() {
688        assert_eq!(
689            "".parse::<HlcTimestamp>(),
690            Err(HlcParseError::InvalidFormat(String::new()))
691        );
692        assert_eq!(
693            "1.2".parse::<HlcTimestamp>(),
694            Err(HlcParseError::InvalidFormat("1.2".to_owned()))
695        );
696        assert_eq!(
697            "1.2.3.4".parse::<HlcTimestamp>(),
698            Err(HlcParseError::InvalidFormat("1.2.3.4".to_owned()))
699        );
700        assert_eq!(
701            "1.2.x".parse::<HlcTimestamp>(),
702            Err(HlcParseError::InvalidComponent(
703                "1.2.x".to_owned(),
704                "x".to_owned()
705            ))
706        );
707        assert_eq!(
708            "18446744073709551616.0.0".parse::<HlcTimestamp>(),
709            Err(HlcParseError::InvalidComponent(
710                "18446744073709551616.0.0".to_owned(),
711                "18446744073709551616".to_owned()
712            ))
713        );
714        assert!("1.2.-3".parse::<HlcTimestamp>().is_err());
715        assert!("1.2.3 ".parse::<HlcTimestamp>().is_err());
716    }
717
718    #[test]
719    fn serde_round_trip() {
720        let stamp = ts(1_756_000_000_000_000, 42, 9);
721        assert_eq!(
722            tokens::to_tokens(&stamp),
723            tokens::Token::Struct(
724                "HlcTimestamp",
725                vec![
726                    tokens::Token::U64(1_756_000_000_000_000),
727                    tokens::Token::U32(42),
728                    tokens::Token::U32(9),
729                ],
730            )
731        );
732        assert_eq!(
733            tokens::from_tokens::<HlcTimestamp>(tokens::to_tokens(&stamp)),
734            stamp
735        );
736
737        for stored in [
738            StoredVersionStamp::LegacyEpoch(123_456),
739            StoredVersionStamp::Hlc(stamp),
740        ] {
741            assert_eq!(
742                tokens::from_tokens::<StoredVersionStamp>(tokens::to_tokens(&stored)),
743                stored
744            );
745        }
746
747        let watermark = MigrationWatermark {
748            mvcc_format_version: MigrationWatermark::FORMAT_HLC,
749            watermark: stamp,
750        };
751        assert_eq!(
752            tokens::from_tokens::<MigrationWatermark>(tokens::to_tokens(&watermark)),
753            watermark
754        );
755    }
756
757    #[test]
758    fn clock_is_send_and_sync() {
759        fn assert_send_sync<T: Send + Sync>() {}
760        assert_send_sync::<HlcClock>();
761    }
762
763    #[test]
764    fn system_clock_smoke() {
765        let clock = HlcClock::new(1, Duration::from_secs(60));
766        let stamp = clock.now().unwrap();
767        assert!(stamp.physical_micros > 0);
768        assert_eq!(stamp.node_tiebreaker, 1);
769    }
770
771    /// Minimal serde round-trip support: the crate intentionally has no
772    /// serialization-format dependency, so tests record values into a token
773    /// tree and deserialize back from it.
774    mod tokens {
775        use core::fmt;
776        use serde::de::{self, EnumAccess, SeqAccess, VariantAccess, Visitor};
777        use serde::ser::{Impossible, SerializeStruct};
778        use serde::{Deserialize, Deserializer, Serialize, Serializer};
779
780        #[derive(Debug, Clone, PartialEq, Eq)]
781        pub enum Token {
782            U32(u32),
783            U64(u64),
784            Struct(&'static str, Vec<Token>),
785            NewtypeVariant(&'static str, &'static str, Box<Token>),
786        }
787
788        pub fn to_tokens<T: Serialize>(value: &T) -> Token {
789            value
790                .serialize(TokenSerializer)
791                .expect("serialize to tokens")
792        }
793
794        pub fn from_tokens<T: for<'de> Deserialize<'de>>(token: Token) -> T {
795            T::deserialize(TokenDeserializer(token)).expect("deserialize from tokens")
796        }
797
798        #[derive(Debug)]
799        pub struct TokenError(&'static str);
800
801        impl fmt::Display for TokenError {
802            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
803                f.write_str(self.0)
804            }
805        }
806
807        impl std::error::Error for TokenError {}
808
809        impl serde::ser::Error for TokenError {
810            fn custom<T: fmt::Display>(_msg: T) -> Self {
811                TokenError("custom serialization error")
812            }
813        }
814
815        impl serde::de::Error for TokenError {
816            fn custom<T: fmt::Display>(_msg: T) -> Self {
817                TokenError("custom deserialization error")
818            }
819        }
820
821        struct TokenSerializer;
822
823        struct TokenStructSerializer {
824            name: &'static str,
825            fields: Vec<Token>,
826        }
827
828        impl SerializeStruct for TokenStructSerializer {
829            type Ok = Token;
830            type Error = TokenError;
831
832            fn serialize_field<T: ?Sized + Serialize>(
833                &mut self,
834                _key: &'static str,
835                value: &T,
836            ) -> Result<(), TokenError> {
837                self.fields.push(value.serialize(TokenSerializer)?);
838                Ok(())
839            }
840
841            fn end(self) -> Result<Token, TokenError> {
842                Ok(Token::Struct(self.name, self.fields))
843            }
844        }
845
846        impl Serializer for TokenSerializer {
847            type Ok = Token;
848            type Error = TokenError;
849            type SerializeSeq = Impossible<Token, TokenError>;
850            type SerializeTuple = Impossible<Token, TokenError>;
851            type SerializeTupleStruct = Impossible<Token, TokenError>;
852            type SerializeTupleVariant = Impossible<Token, TokenError>;
853            type SerializeMap = Impossible<Token, TokenError>;
854            type SerializeStruct = TokenStructSerializer;
855            type SerializeStructVariant = Impossible<Token, TokenError>;
856
857            fn serialize_u32(self, v: u32) -> Result<Token, TokenError> {
858                Ok(Token::U32(v))
859            }
860
861            fn serialize_u64(self, v: u64) -> Result<Token, TokenError> {
862                Ok(Token::U64(v))
863            }
864
865            fn serialize_struct(
866                self,
867                name: &'static str,
868                _len: usize,
869            ) -> Result<TokenStructSerializer, TokenError> {
870                Ok(TokenStructSerializer {
871                    name,
872                    fields: Vec::new(),
873                })
874            }
875
876            fn serialize_newtype_variant<T: ?Sized + Serialize>(
877                self,
878                name: &'static str,
879                _variant_index: u32,
880                variant: &'static str,
881                value: &T,
882            ) -> Result<Token, TokenError> {
883                Ok(Token::NewtypeVariant(
884                    name,
885                    variant,
886                    Box::new(value.serialize(TokenSerializer)?),
887                ))
888            }
889
890            fn serialize_bool(self, _v: bool) -> Result<Token, TokenError> {
891                Err(TokenError("bool unsupported"))
892            }
893
894            fn serialize_i8(self, _v: i8) -> Result<Token, TokenError> {
895                Err(TokenError("i8 unsupported"))
896            }
897
898            fn serialize_i16(self, _v: i16) -> Result<Token, TokenError> {
899                Err(TokenError("i16 unsupported"))
900            }
901
902            fn serialize_i32(self, _v: i32) -> Result<Token, TokenError> {
903                Err(TokenError("i32 unsupported"))
904            }
905
906            fn serialize_i64(self, _v: i64) -> Result<Token, TokenError> {
907                Err(TokenError("i64 unsupported"))
908            }
909
910            fn serialize_u8(self, _v: u8) -> Result<Token, TokenError> {
911                Err(TokenError("u8 unsupported"))
912            }
913
914            fn serialize_u16(self, _v: u16) -> Result<Token, TokenError> {
915                Err(TokenError("u16 unsupported"))
916            }
917
918            fn serialize_f32(self, _v: f32) -> Result<Token, TokenError> {
919                Err(TokenError("f32 unsupported"))
920            }
921
922            fn serialize_f64(self, _v: f64) -> Result<Token, TokenError> {
923                Err(TokenError("f64 unsupported"))
924            }
925
926            fn serialize_char(self, _v: char) -> Result<Token, TokenError> {
927                Err(TokenError("char unsupported"))
928            }
929
930            fn serialize_str(self, _v: &str) -> Result<Token, TokenError> {
931                Err(TokenError("str unsupported"))
932            }
933
934            fn serialize_bytes(self, _v: &[u8]) -> Result<Token, TokenError> {
935                Err(TokenError("bytes unsupported"))
936            }
937
938            fn serialize_none(self) -> Result<Token, TokenError> {
939                Err(TokenError("none unsupported"))
940            }
941
942            fn serialize_some<T: ?Sized + Serialize>(
943                self,
944                _value: &T,
945            ) -> Result<Token, TokenError> {
946                Err(TokenError("some unsupported"))
947            }
948
949            fn serialize_unit(self) -> Result<Token, TokenError> {
950                Err(TokenError("unit unsupported"))
951            }
952
953            fn serialize_unit_struct(self, _name: &'static str) -> Result<Token, TokenError> {
954                Err(TokenError("unit struct unsupported"))
955            }
956
957            fn serialize_unit_variant(
958                self,
959                _name: &'static str,
960                _variant_index: u32,
961                _variant: &'static str,
962            ) -> Result<Token, TokenError> {
963                Err(TokenError("unit variant unsupported"))
964            }
965
966            fn serialize_newtype_struct<T: ?Sized + Serialize>(
967                self,
968                _name: &'static str,
969                _value: &T,
970            ) -> Result<Token, TokenError> {
971                Err(TokenError("newtype struct unsupported"))
972            }
973
974            fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, TokenError> {
975                Err(TokenError("seq unsupported"))
976            }
977
978            fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, TokenError> {
979                Err(TokenError("tuple unsupported"))
980            }
981
982            fn serialize_tuple_struct(
983                self,
984                _name: &'static str,
985                _len: usize,
986            ) -> Result<Self::SerializeTupleStruct, TokenError> {
987                Err(TokenError("tuple struct unsupported"))
988            }
989
990            fn serialize_tuple_variant(
991                self,
992                _name: &'static str,
993                _variant_index: u32,
994                _variant: &'static str,
995                _len: usize,
996            ) -> Result<Self::SerializeTupleVariant, TokenError> {
997                Err(TokenError("tuple variant unsupported"))
998            }
999
1000            fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, TokenError> {
1001                Err(TokenError("map unsupported"))
1002            }
1003
1004            fn serialize_struct_variant(
1005                self,
1006                _name: &'static str,
1007                _variant_index: u32,
1008                _variant: &'static str,
1009                _len: usize,
1010            ) -> Result<Self::SerializeStructVariant, TokenError> {
1011                Err(TokenError("struct variant unsupported"))
1012            }
1013        }
1014
1015        struct TokenDeserializer(Token);
1016
1017        struct TokenSeqAccess {
1018            iter: std::vec::IntoIter<Token>,
1019        }
1020
1021        impl<'de> SeqAccess<'de> for TokenSeqAccess {
1022            type Error = TokenError;
1023
1024            fn next_element_seed<T: de::DeserializeSeed<'de>>(
1025                &mut self,
1026                seed: T,
1027            ) -> Result<Option<T::Value>, TokenError> {
1028                match self.iter.next() {
1029                    Some(token) => seed.deserialize(TokenDeserializer(token)).map(Some),
1030                    None => Ok(None),
1031                }
1032            }
1033        }
1034
1035        struct TokenEnumAccess {
1036            variant: &'static str,
1037            value: Token,
1038        }
1039
1040        impl<'de> EnumAccess<'de> for TokenEnumAccess {
1041            type Error = TokenError;
1042            type Variant = TokenVariantAccess;
1043
1044            fn variant_seed<V: de::DeserializeSeed<'de>>(
1045                self,
1046                seed: V,
1047            ) -> Result<(V::Value, TokenVariantAccess), TokenError> {
1048                let value =
1049                    seed.deserialize(de::value::BorrowedStrDeserializer::new(self.variant))?;
1050                Ok((value, TokenVariantAccess { value: self.value }))
1051            }
1052        }
1053
1054        struct TokenVariantAccess {
1055            value: Token,
1056        }
1057
1058        impl<'de> VariantAccess<'de> for TokenVariantAccess {
1059            type Error = TokenError;
1060
1061            fn unit_variant(self) -> Result<(), TokenError> {
1062                Err(TokenError("unit variants unsupported"))
1063            }
1064
1065            fn newtype_variant_seed<T: de::DeserializeSeed<'de>>(
1066                self,
1067                seed: T,
1068            ) -> Result<T::Value, TokenError> {
1069                seed.deserialize(TokenDeserializer(self.value))
1070            }
1071
1072            fn tuple_variant<V: Visitor<'de>>(
1073                self,
1074                _len: usize,
1075                _visitor: V,
1076            ) -> Result<V::Value, TokenError> {
1077                Err(TokenError("tuple variants unsupported"))
1078            }
1079
1080            fn struct_variant<V: Visitor<'de>>(
1081                self,
1082                _fields: &'static [&'static str],
1083                _visitor: V,
1084            ) -> Result<V::Value, TokenError> {
1085                Err(TokenError("struct variants unsupported"))
1086            }
1087        }
1088
1089        impl<'de> Deserializer<'de> for TokenDeserializer {
1090            type Error = TokenError;
1091
1092            fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, TokenError> {
1093                match self.0 {
1094                    Token::U32(v) => visitor.visit_u32(v),
1095                    Token::U64(v) => visitor.visit_u64(v),
1096                    Token::Struct(_, fields) => visitor.visit_seq(TokenSeqAccess {
1097                        iter: fields.into_iter(),
1098                    }),
1099                    Token::NewtypeVariant(_, variant, value) => {
1100                        visitor.visit_enum(TokenEnumAccess {
1101                            variant,
1102                            value: *value,
1103                        })
1104                    }
1105                }
1106            }
1107
1108            fn deserialize_u32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, TokenError> {
1109                match self.0 {
1110                    Token::U32(v) => visitor.visit_u32(v),
1111                    _ => Err(TokenError("expected u32 token")),
1112                }
1113            }
1114
1115            fn deserialize_u64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, TokenError> {
1116                match self.0 {
1117                    Token::U64(v) => visitor.visit_u64(v),
1118                    _ => Err(TokenError("expected u64 token")),
1119                }
1120            }
1121
1122            fn deserialize_struct<V: Visitor<'de>>(
1123                self,
1124                _name: &'static str,
1125                _fields: &'static [&'static str],
1126                visitor: V,
1127            ) -> Result<V::Value, TokenError> {
1128                match self.0 {
1129                    Token::Struct(_, fields) => visitor.visit_seq(TokenSeqAccess {
1130                        iter: fields.into_iter(),
1131                    }),
1132                    _ => Err(TokenError("expected struct token")),
1133                }
1134            }
1135
1136            fn deserialize_enum<V: Visitor<'de>>(
1137                self,
1138                _name: &'static str,
1139                _variants: &'static [&'static str],
1140                visitor: V,
1141            ) -> Result<V::Value, TokenError> {
1142                match self.0 {
1143                    Token::NewtypeVariant(_, variant, value) => {
1144                        visitor.visit_enum(TokenEnumAccess {
1145                            variant,
1146                            value: *value,
1147                        })
1148                    }
1149                    _ => Err(TokenError("expected enum token")),
1150                }
1151            }
1152
1153            serde::forward_to_deserialize_any! {
1154                bool i8 i16 i32 i64 i128 u8 u16 u128 f32 f64 char str string
1155                bytes byte_buf option unit unit_struct newtype_struct seq tuple
1156                tuple_struct map identifier ignored_any
1157            }
1158        }
1159    }
1160}