1use crate::remote::NodeId;
33use crate::time::{TimeSource, TimerDriverHandle, WallClock};
34use crate::types::Time;
35use parking_lot::Mutex;
36use serde::{Deserialize, Serialize};
37use std::collections::BTreeMap;
38use std::fmt;
39use std::sync::Arc;
40use std::sync::atomic::{AtomicU64, Ordering};
41
42pub trait LogicalClock: Send + Sync {
46 type Time: Clone + PartialOrd + Send + Sync + 'static;
48
49 #[must_use]
51 fn tick(&self) -> Self::Time;
52
53 #[must_use]
55 fn receive(&self, sender_time: &Self::Time) -> Self::Time;
56
57 #[must_use]
59 fn now(&self) -> Self::Time;
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
64pub struct LamportTime(u64);
65
66impl LamportTime {
67 #[must_use]
69 pub const fn raw(self) -> u64 {
70 self.0
71 }
72
73 #[must_use]
75 pub const fn from_raw(value: u64) -> Self {
76 Self(value)
77 }
78}
79
80#[derive(Default)]
82pub struct LamportClock {
83 counter: AtomicU64,
84}
85
86impl LamportClock {
87 #[must_use]
89 pub fn new() -> Self {
90 Self::default()
91 }
92
93 #[must_use]
95 pub fn with_start(start: u64) -> Self {
96 Self {
97 counter: AtomicU64::new(start),
98 }
99 }
100
101 #[must_use]
103 pub fn now(&self) -> LamportTime {
104 LamportTime(self.counter.load(Ordering::Acquire))
105 }
106
107 #[must_use]
109 pub fn tick(&self) -> LamportTime {
110 let mut current = self.counter.load(Ordering::Acquire);
111 loop {
112 let next = current
113 .checked_add(1)
114 .expect("Lamport clock overflowed while ticking");
115 match self.counter.compare_exchange_weak(
116 current,
117 next,
118 Ordering::AcqRel,
119 Ordering::Acquire,
120 ) {
121 Ok(_) => return LamportTime(next),
122 Err(actual) => current = actual,
123 }
124 }
125 }
126
127 #[must_use]
129 pub fn receive(&self, sender: LamportTime) -> LamportTime {
130 let mut current = self.counter.load(Ordering::Acquire);
131 loop {
132 let next = current
133 .max(sender.raw())
134 .checked_add(1)
135 .expect("Lamport clock overflowed while merging a received time");
136 match self.counter.compare_exchange_weak(
137 current,
138 next,
139 Ordering::AcqRel,
140 Ordering::Acquire,
141 ) {
142 Ok(_) => return LamportTime(next),
143 Err(actual) => current = actual,
144 }
145 }
146 }
147}
148
149impl fmt::Debug for LamportClock {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 f.debug_struct("LamportClock")
152 .field("counter", &self.counter.load(Ordering::Relaxed))
153 .finish()
154 }
155}
156
157impl LogicalClock for LamportClock {
158 type Time = LamportTime;
159
160 fn tick(&self) -> Self::Time {
161 Self::tick(self)
162 }
163
164 fn receive(&self, sender_time: &Self::Time) -> Self::Time {
165 Self::receive(self, *sender_time)
166 }
167
168 fn now(&self) -> Self::Time {
169 Self::now(self)
170 }
171}
172
173#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
175pub struct HybridTime {
176 physical: Time,
177 logical: u64,
178}
179
180impl HybridTime {
181 #[must_use]
183 pub const fn new(physical: Time, logical: u64) -> Self {
184 Self { physical, logical }
185 }
186
187 #[must_use]
189 pub const fn physical(self) -> Time {
190 self.physical
191 }
192
193 #[must_use]
195 pub const fn logical(self) -> u64 {
196 self.logical
197 }
198}
199
200#[derive(Debug)]
201struct HybridState {
202 last_physical: Time,
203 logical: u64,
204}
205
206pub struct HybridClock {
208 time_source: Arc<dyn TimeSource>,
209 state: Mutex<HybridState>,
210}
211
212impl HybridClock {
213 #[must_use]
215 pub fn new(time_source: Arc<dyn TimeSource>) -> Self {
216 let now = time_source.now();
217 Self {
218 time_source,
219 state: Mutex::new(HybridState {
220 last_physical: now,
221 logical: 0,
222 }),
223 }
224 }
225
226 #[must_use]
228 pub fn now(&self) -> HybridTime {
229 let state = self.state.lock();
230 let physical = self.physical_now(&state);
231 let logical = if physical == state.last_physical {
232 state.logical
233 } else {
234 0
235 };
236 HybridTime::new(physical, logical)
237 }
238
239 #[must_use]
241 pub fn tick(&self) -> HybridTime {
242 let mut state = self.state.lock();
243 let physical = self.physical_now(&state);
244 if physical == state.last_physical {
245 state.logical = state
246 .logical
247 .checked_add(1)
248 .expect("Hybrid clock logical counter overflowed while ticking");
249 } else {
250 state.last_physical = physical;
251 state.logical = 0;
252 }
253 HybridTime::new(state.last_physical, state.logical)
254 }
255
256 #[must_use]
258 pub fn receive(&self, sender: HybridTime) -> HybridTime {
259 let mut state = self.state.lock();
260 let physical_now = self.physical_now(&state);
261 let max_physical = physical_now.max(state.last_physical).max(sender.physical);
262
263 let next_logical = if max_physical == state.last_physical && max_physical == sender.physical
264 {
265 state
266 .logical
267 .max(sender.logical)
268 .checked_add(1)
269 .expect("Hybrid clock logical counter overflowed while merging equal physical time")
270 } else if max_physical == state.last_physical {
271 state.logical.checked_add(1).expect(
272 "Hybrid clock logical counter overflowed while advancing local logical time",
273 )
274 } else if max_physical == sender.physical {
275 sender.logical.checked_add(1).expect(
276 "Hybrid clock logical counter overflowed while incorporating a remote physical time",
277 )
278 } else {
279 0
280 };
281
282 state.last_physical = max_physical;
283 state.logical = next_logical;
284 HybridTime::new(state.last_physical, state.logical)
285 }
286
287 fn physical_now(&self, state: &HybridState) -> Time {
288 let physical = self.time_source.now();
289 if physical < state.last_physical {
290 state.last_physical
291 } else {
292 physical
293 }
294 }
295}
296
297impl fmt::Debug for HybridClock {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 let state = self.state.lock();
300 f.debug_struct("HybridClock")
301 .field("last_physical", &state.last_physical)
302 .field("logical", &state.logical)
303 .finish_non_exhaustive()
304 }
305}
306
307impl LogicalClock for HybridClock {
308 type Time = HybridTime;
309
310 fn tick(&self) -> Self::Time {
311 Self::tick(self)
312 }
313
314 fn receive(&self, sender_time: &Self::Time) -> Self::Time {
315 Self::receive(self, *sender_time)
316 }
317
318 fn now(&self) -> Self::Time {
319 Self::now(self)
320 }
321}
322
323pub struct VectorClockHandle {
325 node: NodeId,
327 clock: Mutex<VectorClock>,
329}
330
331impl VectorClockHandle {
332 #[must_use]
334 pub fn new(node: NodeId) -> Self {
335 Self {
336 node,
337 clock: Mutex::new(VectorClock::new()),
338 }
339 }
340
341 #[must_use]
343 pub fn current(&self) -> VectorClock {
344 self.clock.lock().clone()
345 }
346}
347
348impl fmt::Debug for VectorClockHandle {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 f.debug_struct("VectorClockHandle")
351 .field("node", &self.node)
352 .field("clock", &self.clock.lock())
353 .finish()
354 }
355}
356
357impl LogicalClock for VectorClockHandle {
358 type Time = VectorClock;
359
360 fn tick(&self) -> Self::Time {
361 let mut clock = self.clock.lock();
362 clock.increment(&self.node);
363 clock.clone()
364 }
365
366 fn receive(&self, sender_time: &Self::Time) -> Self::Time {
367 let mut clock = self.clock.lock();
368 clock.receive(&self.node, sender_time);
369 clock.clone()
370 }
371
372 fn now(&self) -> Self::Time {
373 self.clock.lock().clone()
374 }
375}
376
377#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
379#[serde(rename_all = "snake_case")]
380pub enum LogicalTime {
381 Lamport(LamportTime),
383 Vector(VectorClock),
385 Hybrid(HybridTime),
387}
388
389impl LogicalTime {
390 #[must_use]
392 pub const fn kind(&self) -> LogicalClockKind {
393 match self {
394 Self::Lamport(_) => LogicalClockKind::Lamport,
395 Self::Vector(_) => LogicalClockKind::Vector,
396 Self::Hybrid(_) => LogicalClockKind::Hybrid,
397 }
398 }
399
400 #[must_use]
418 pub fn causal_order(&self, other: &Self) -> CausalOrder {
419 match (self, other) {
420 (Self::Vector(a), Self::Vector(b)) => a.causal_order(b),
421 _ => CausalOrder::Concurrent,
422 }
423 }
424}
425
426impl PartialOrd for LogicalTime {
427 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
428 match (self, other) {
429 (Self::Lamport(a), Self::Lamport(b)) => a.partial_cmp(b),
430 (Self::Vector(a), Self::Vector(b)) => a.partial_cmp(b),
431 (Self::Hybrid(a), Self::Hybrid(b)) => a.partial_cmp(b),
432 _ => None,
433 }
434 }
435}
436
437#[derive(Clone, Copy, Debug, PartialEq, Eq)]
439pub enum LogicalClockKind {
440 Lamport,
442 Vector,
444 Hybrid,
446}
447
448#[derive(Clone, Debug)]
450pub enum LogicalClockMode {
451 Lamport,
453 Vector {
455 node: NodeId,
457 },
458 Hybrid,
460}
461
462#[derive(Clone)]
464pub enum LogicalClockHandle {
465 Lamport(Arc<LamportClock>),
467 Vector(Arc<VectorClockHandle>),
469 Hybrid(Arc<HybridClock>),
471}
472
473impl LogicalClockHandle {
474 #[must_use]
476 pub const fn kind(&self) -> LogicalClockKind {
477 match self {
478 Self::Lamport(_) => LogicalClockKind::Lamport,
479 Self::Vector(_) => LogicalClockKind::Vector,
480 Self::Hybrid(_) => LogicalClockKind::Hybrid,
481 }
482 }
483
484 #[must_use]
486 pub fn tick(&self) -> LogicalTime {
487 match self {
488 Self::Lamport(clock) => LogicalTime::Lamport(clock.tick()),
489 Self::Vector(clock) => LogicalTime::Vector(clock.tick()),
490 Self::Hybrid(clock) => LogicalTime::Hybrid(clock.tick()),
491 }
492 }
493
494 #[must_use]
496 pub fn receive(&self, sender_time: &LogicalTime) -> LogicalTime {
497 match (self, sender_time) {
498 (Self::Lamport(clock), LogicalTime::Lamport(time)) => {
499 LogicalTime::Lamport(clock.receive(*time))
500 }
501 (Self::Vector(clock), LogicalTime::Vector(time)) => {
502 LogicalTime::Vector(clock.receive(time))
503 }
504 (Self::Hybrid(clock), LogicalTime::Hybrid(time)) => {
505 LogicalTime::Hybrid(clock.receive(*time))
506 }
507 _ => self.tick(),
509 }
510 }
511
512 #[must_use]
514 pub fn now(&self) -> LogicalTime {
515 match self {
516 Self::Lamport(clock) => LogicalTime::Lamport(clock.now()),
517 Self::Vector(clock) => LogicalTime::Vector(clock.now()),
518 Self::Hybrid(clock) => LogicalTime::Hybrid(clock.now()),
519 }
520 }
521}
522
523impl fmt::Debug for LogicalClockHandle {
524 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
525 match self {
526 Self::Lamport(_) => f.write_str("LogicalClockHandle::Lamport"),
527 Self::Vector(_) => f.write_str("LogicalClockHandle::Vector"),
528 Self::Hybrid(_) => f.write_str("LogicalClockHandle::Hybrid"),
529 }
530 }
531}
532
533impl Default for LogicalClockHandle {
534 fn default() -> Self {
535 Self::Lamport(Arc::new(LamportClock::new()))
536 }
537}
538
539impl LogicalClockMode {
540 #[must_use]
542 pub fn build_handle(&self, timer_driver: Option<TimerDriverHandle>) -> LogicalClockHandle {
543 match self {
544 Self::Lamport => LogicalClockHandle::Lamport(Arc::new(LamportClock::new())),
545 Self::Vector { node } => {
546 LogicalClockHandle::Vector(Arc::new(VectorClockHandle::new(node.clone())))
547 }
548 Self::Hybrid => {
549 let time_source: Arc<dyn TimeSource> = match timer_driver {
550 Some(driver) => Arc::new(TimerDriverSource::new(driver)),
551 None => Arc::new(WallClock::new()),
552 };
553 LogicalClockHandle::Hybrid(Arc::new(HybridClock::new(time_source)))
554 }
555 }
556 }
557}
558
559#[derive(Clone)]
560struct TimerDriverSource {
561 timer: TimerDriverHandle,
562}
563
564impl TimerDriverSource {
565 fn new(timer: TimerDriverHandle) -> Self {
566 Self { timer }
567 }
568}
569
570impl fmt::Debug for TimerDriverSource {
571 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
572 f.debug_struct("TimerDriverSource").finish()
573 }
574}
575
576impl TimeSource for TimerDriverSource {
577 fn now(&self) -> Time {
578 self.timer.now()
579 }
580}
581
582#[derive(Clone, Default, Serialize, Deserialize)]
589pub struct VectorClock {
590 entries: BTreeMap<NodeId, u64>,
592}
593
594impl PartialEq for VectorClock {
606 fn eq(&self, other: &Self) -> bool {
607 self.entries
608 .keys()
609 .chain(other.entries.keys())
610 .all(|node| self.get(node) == other.get(node))
611 }
612}
613
614impl Eq for VectorClock {}
617
618impl VectorClock {
619 #[must_use]
621 pub fn new() -> Self {
622 Self::default()
623 }
624
625 #[must_use]
627 pub fn for_node(node: &NodeId) -> Self {
628 let mut vc = Self::new();
629 vc.entries.insert(node.clone(), 1);
630 vc
631 }
632
633 #[must_use]
635 pub fn get(&self, node: &NodeId) -> u64 {
636 self.entries.get(node).copied().unwrap_or(0)
637 }
638
639 pub fn increment(&mut self, node: &NodeId) -> u64 {
641 let entry = self.entries.entry(node.clone()).or_insert(0);
642 *entry = entry
643 .checked_add(1)
644 .expect("Vector clock counter overflowed while incrementing");
645 *entry
646 }
647
648 pub fn set(&mut self, node: &NodeId, value: u64) {
653 if value == 0 {
654 return;
655 }
656 let entry = self.entries.entry(node.clone()).or_insert(0);
657 if value > *entry {
658 *entry = value;
659 }
660 }
661
662 #[must_use]
666 pub fn merge(&self, other: &Self) -> Self {
667 let mut result = self.clone();
668 for (node, &value) in &other.entries {
669 let entry = result.entries.entry(node.clone()).or_insert(0);
670 if value > *entry {
671 *entry = value;
672 }
673 }
674 result
675 }
676
677 pub fn merge_in(&mut self, other: &Self) {
679 for (node, &value) in &other.entries {
680 let entry = self.entries.entry(node.clone()).or_insert(0);
681 if value > *entry {
682 *entry = value;
683 }
684 }
685 }
686
687 pub fn receive(&mut self, local_node: &NodeId, remote_clock: &Self) {
693 self.merge_in(remote_clock);
694 self.increment(local_node);
695 }
696
697 #[must_use]
699 pub fn causal_order(&self, other: &Self) -> CausalOrder {
700 let mut self_leq_other = true;
701 let mut other_leq_self = true;
702
703 let all_nodes: std::collections::BTreeSet<&NodeId> =
705 self.entries.keys().chain(other.entries.keys()).collect();
706
707 for node in all_nodes {
708 let a = self.get(node);
709 let b = other.get(node);
710 if a > b {
711 self_leq_other = false;
712 }
713 if b > a {
714 other_leq_self = false;
715 }
716 if !self_leq_other && !other_leq_self {
717 return CausalOrder::Concurrent;
718 }
719 }
720
721 match (self_leq_other, other_leq_self) {
722 (true, true) => CausalOrder::Equal,
723 (true, false) => CausalOrder::Before,
724 (false, true) => CausalOrder::After,
725 (false, false) => CausalOrder::Concurrent,
726 }
727 }
728
729 #[must_use]
731 pub fn happens_before(&self, other: &Self) -> bool {
732 self.causal_order(other) == CausalOrder::Before
733 }
734
735 #[must_use]
737 pub fn is_concurrent_with(&self, other: &Self) -> bool {
738 self.causal_order(other) == CausalOrder::Concurrent
739 }
740
741 #[must_use]
743 pub fn node_count(&self) -> usize {
744 self.entries.len()
745 }
746
747 #[must_use]
749 pub fn is_zero(&self) -> bool {
750 self.entries.is_empty()
751 }
752
753 pub fn iter(&self) -> impl Iterator<Item = (&NodeId, &u64)> {
755 self.entries.iter()
756 }
757}
758
759impl PartialOrd for VectorClock {
763 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
764 match self.causal_order(other) {
765 CausalOrder::Before => Some(std::cmp::Ordering::Less),
766 CausalOrder::After => Some(std::cmp::Ordering::Greater),
767 CausalOrder::Equal => Some(std::cmp::Ordering::Equal),
768 CausalOrder::Concurrent => None,
769 }
770 }
771}
772
773impl fmt::Debug for VectorClock {
774 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
775 write!(f, "VC{{")?;
776 for (i, (node, value)) in self.entries.iter().enumerate() {
777 if i > 0 {
778 write!(f, ", ")?;
779 }
780 write!(f, "{}:{}", node.as_str(), value)?;
781 }
782 write!(f, "}}")
783 }
784}
785
786impl fmt::Display for VectorClock {
787 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788 write!(f, "[")?;
789 for (i, (node, value)) in self.entries.iter().enumerate() {
790 if i > 0 {
791 write!(f, ", ")?;
792 }
793 write!(f, "{}={}", node.as_str(), value)?;
794 }
795 write!(f, "]")
796 }
797}
798
799#[derive(Debug, Clone, Copy, PartialEq, Eq)]
801pub enum CausalOrder {
802 Before,
804 After,
806 Equal,
808 Concurrent,
810}
811
812#[derive(Clone, Debug)]
817pub struct CausalEvent<T> {
818 pub origin: NodeId,
820 pub clock: VectorClock,
822 pub event: T,
824}
825
826impl<T> CausalEvent<T> {
827 pub fn new(origin: NodeId, clock: VectorClock, event: T) -> Self {
829 Self {
830 origin,
831 clock,
832 event,
833 }
834 }
835
836 pub fn happens_before<U>(&self, other: &CausalEvent<U>) -> bool {
838 self.clock.happens_before(&other.clock)
839 }
840
841 pub fn is_concurrent_with<U>(&self, other: &CausalEvent<U>) -> bool {
843 self.clock.is_concurrent_with(&other.clock)
844 }
845}
846
847#[derive(Clone, Debug)]
852pub struct CausalTracker {
853 node: NodeId,
855 clock: VectorClock,
857}
858
859impl CausalTracker {
860 #[must_use]
862 pub fn new(node: NodeId) -> Self {
863 Self {
864 node,
865 clock: VectorClock::new(),
866 }
867 }
868
869 pub fn record_local_event(&mut self) -> VectorClock {
873 self.clock.increment(&self.node);
874 self.clock.clone()
875 }
876
877 pub fn record<T>(&mut self, event: T) -> CausalEvent<T> {
879 let clock = self.record_local_event();
880 CausalEvent::new(self.node.clone(), clock, event)
881 }
882
883 pub fn on_send(&mut self) -> VectorClock {
886 self.record_local_event()
887 }
888
889 pub fn on_receive(&mut self, remote_clock: &VectorClock) {
892 self.clock.receive(&self.node, remote_clock);
893 }
894
895 #[must_use]
897 pub fn current_clock(&self) -> &VectorClock {
898 &self.clock
899 }
900
901 #[must_use]
903 pub fn node(&self) -> &NodeId {
904 &self.node
905 }
906}
907
908#[cfg(test)]
909mod tests {
910 #![allow(
911 clippy::pedantic,
912 clippy::nursery,
913 clippy::expect_fun_call,
914 clippy::map_unwrap_or,
915 clippy::cast_possible_wrap,
916 clippy::future_not_send
917 )]
918 use super::*;
919 use crate::time::VirtualClock;
920 use serde_json::{Value, json};
921 use std::sync::Arc;
922
923 fn node(name: &str) -> NodeId {
924 NodeId::new(name)
925 }
926
927 fn scrub_vclock_output(value: Value) -> Value {
928 fn scrub_node_names(text: &str) -> String {
929 text.replace("alpha-node", "[NODE_A]")
930 .replace("beta-node", "[NODE_B]")
931 }
932
933 fn scrub_value(value: &mut Value) {
934 match value {
935 Value::String(text) => *text = scrub_node_names(text),
936 Value::Array(values) => {
937 for entry in values {
938 scrub_value(entry);
939 }
940 }
941 Value::Object(map) => {
942 for entry in map.values_mut() {
943 scrub_value(entry);
944 }
945 }
946 Value::Null | Value::Bool(_) | Value::Number(_) => {}
947 }
948 }
949
950 let mut scrubbed = value;
951 scrub_value(&mut scrubbed);
952 scrubbed
953 }
954
955 #[test]
956 fn empty_clocks_are_equal() {
957 let a = VectorClock::new();
958 let b = VectorClock::new();
959 assert_eq!(a.causal_order(&b), CausalOrder::Equal);
960 assert_eq!(a.partial_cmp(&b), Some(std::cmp::Ordering::Equal));
961 }
962
963 #[test]
964 fn increment_creates_happens_before() {
965 let n = node("A");
966 let mut a = VectorClock::new();
967 let b = a.clone();
968 a.increment(&n);
969 assert_eq!(b.causal_order(&a), CausalOrder::Before);
970 assert!(b.happens_before(&a));
971 }
972
973 #[test]
974 fn concurrent_detection() {
975 let na = node("A");
976 let nb = node("B");
977 let mut a = VectorClock::new();
978 let mut b = VectorClock::new();
979 a.increment(&na);
980 b.increment(&nb);
981 assert_eq!(a.causal_order(&b), CausalOrder::Concurrent);
982 assert!(a.is_concurrent_with(&b));
983 assert_eq!(a.partial_cmp(&b), None);
984 }
985
986 #[test]
987 fn lamport_tick_and_receive() {
988 let clock = LamportClock::new();
989 let t1 = clock.tick();
990 let t2 = clock.tick();
991 assert!(t2 > t1);
992
993 let remote = LamportTime::from_raw(10);
994 let merged = clock.receive(remote);
995 assert!(merged.raw() > remote.raw());
996 }
997
998 #[test]
999 #[should_panic(expected = "Lamport clock overflowed while ticking")]
1000 fn lamport_tick_panics_on_overflow() {
1001 let clock = LamportClock::with_start(u64::MAX);
1002 let _ = clock.tick();
1003 }
1004
1005 #[test]
1006 #[should_panic(expected = "Lamport clock overflowed while merging a received time")]
1007 fn lamport_receive_panics_on_overflow() {
1008 let clock = LamportClock::with_start(u64::MAX - 1);
1009 let _ = clock.receive(LamportTime::from_raw(u64::MAX));
1010 }
1011
1012 #[test]
1013 fn hybrid_clock_deterministic_with_virtual_time() {
1014 let virtual_clock = Arc::new(VirtualClock::new());
1015 let hlc = HybridClock::new(virtual_clock.clone());
1016
1017 let t1 = hlc.tick();
1018 let t2 = hlc.tick();
1019 assert!(t2 >= t1);
1020
1021 virtual_clock.advance(1_000);
1022 let t3 = hlc.tick();
1023 assert!(t3.physical() >= t2.physical());
1024 }
1025
1026 #[test]
1027 fn hybrid_now_resets_logical_when_physical_advances() {
1028 let virtual_clock = Arc::new(VirtualClock::new());
1029 let hlc = HybridClock::new(virtual_clock.clone());
1030
1031 let t1 = hlc.tick();
1032 assert_eq!(t1.logical(), 1);
1033
1034 virtual_clock.advance(1_000);
1035 let observed = hlc.now();
1036 assert!(observed.physical() > t1.physical());
1037 assert_eq!(observed.logical(), 0);
1038
1039 let t2 = hlc.tick();
1040 assert!(t2 >= observed);
1041 }
1042
1043 #[test]
1044 #[should_panic(expected = "Hybrid clock logical counter overflowed while ticking")]
1045 fn hybrid_tick_panics_on_logical_overflow() {
1046 let time_source: Arc<dyn TimeSource> = Arc::new(VirtualClock::new());
1047 let hlc = HybridClock {
1048 time_source,
1049 state: Mutex::new(HybridState {
1050 last_physical: Time::ZERO,
1051 logical: u64::MAX,
1052 }),
1053 };
1054
1055 let _ = hlc.tick();
1056 }
1057
1058 #[test]
1059 #[should_panic(
1060 expected = "Hybrid clock logical counter overflowed while merging equal physical time"
1061 )]
1062 fn hybrid_receive_panics_on_equal_physical_logical_overflow() {
1063 let time_source: Arc<dyn TimeSource> = Arc::new(VirtualClock::new());
1064 let hlc = HybridClock {
1065 time_source,
1066 state: Mutex::new(HybridState {
1067 last_physical: Time::ZERO,
1068 logical: u64::MAX,
1069 }),
1070 };
1071
1072 let _ = hlc.receive(HybridTime::new(Time::ZERO, u64::MAX));
1073 }
1074
1075 #[test]
1076 fn merge_is_least_upper_bound() {
1077 let na = node("A");
1078 let nb = node("B");
1079 #[allow(clippy::many_single_char_names)]
1080 let mut a = VectorClock::new();
1081 a.increment(&na);
1082 a.increment(&na);
1083 let mut b = VectorClock::new();
1084 b.increment(&nb);
1085 b.increment(&nb);
1086 b.increment(&nb);
1087
1088 let merged = a.merge(&b);
1089 assert_eq!(merged.get(&na), 2);
1090 assert_eq!(merged.get(&nb), 3);
1091 assert!(a.happens_before(&merged));
1093 assert!(b.happens_before(&merged));
1094 }
1095
1096 #[test]
1097 fn merge_is_commutative() {
1098 let na = node("A");
1099 let nb = node("B");
1100 #[allow(clippy::many_single_char_names)]
1101 let mut a = VectorClock::new();
1102 a.increment(&na);
1103 let mut b = VectorClock::new();
1104 b.increment(&nb);
1105
1106 assert_eq!(a.merge(&b), b.merge(&a));
1107 }
1108
1109 #[test]
1110 fn merge_is_associative() {
1111 let na = node("A");
1112 let nb = node("B");
1113 let nc = node("C");
1114 #[allow(clippy::many_single_char_names)]
1115 let mut a = VectorClock::new();
1116 a.increment(&na);
1117 let mut b = VectorClock::new();
1118 b.increment(&nb);
1119 let mut c = VectorClock::new();
1120 c.increment(&nc);
1121
1122 let ab_c = a.merge(&b).merge(&c);
1123 let a_bc = a.merge(&b.merge(&c));
1124 assert_eq!(ab_c, a_bc);
1125 }
1126
1127 #[test]
1128 fn merge_is_idempotent() {
1129 let na = node("A");
1130 #[allow(clippy::many_single_char_names)]
1131 let mut a = VectorClock::new();
1132 a.increment(&na);
1133 assert_eq!(a.merge(&a), a);
1134 }
1135
1136 #[test]
1137 fn receive_merges_and_increments() {
1138 let na = node("A");
1139 let nb = node("B");
1140 #[allow(clippy::many_single_char_names)]
1141 let mut a = VectorClock::new();
1142 a.increment(&na); let mut b = VectorClock::new();
1145 b.increment(&nb); b.increment(&nb); a.receive(&na, &b); assert_eq!(a.get(&na), 2);
1151 assert_eq!(a.get(&nb), 2);
1152 }
1153
1154 #[test]
1155 fn vector_clock_output_snapshot_scrubbed() {
1156 let na = node("alpha-node");
1157 let nb = node("beta-node");
1158 #[allow(clippy::many_single_char_names)]
1159 let mut a = VectorClock::new();
1160 a.increment(&na);
1161 a.increment(&na);
1162
1163 let mut b = VectorClock::new();
1164 b.increment(&nb);
1165
1166 let merged = a.merge(&b);
1167 insta::assert_json_snapshot!(
1168 "vector_clock_output_scrubbed",
1169 scrub_vclock_output(json!({
1170 "display": merged.to_string(),
1171 "debug": format!("{merged:?}"),
1172 "order_vs_a": format!("{:?}", merged.causal_order(&a)),
1173 }))
1174 );
1175 }
1176
1177 #[test]
1178 fn for_node_initializes_to_one() {
1179 let n = node("X");
1180 let vc = VectorClock::for_node(&n);
1181 assert_eq!(vc.get(&n), 1);
1182 assert_eq!(vc.node_count(), 1);
1183 }
1184
1185 #[test]
1186 fn set_is_monotone() {
1187 let n = node("A");
1188 let mut vc = VectorClock::new();
1189 vc.set(&n, 3);
1190 assert_eq!(vc.get(&n), 3);
1191
1192 vc.set(&n, 1);
1194 assert_eq!(vc.get(&n), 3);
1195
1196 vc.set(&n, 7);
1198 assert_eq!(vc.get(&n), 7);
1199 }
1200
1201 #[test]
1202 #[should_panic(expected = "Vector clock counter overflowed while incrementing")]
1203 fn vector_clock_increment_panics_on_overflow() {
1204 let n = node("A");
1205 let mut vc = VectorClock::new();
1206 vc.entries.insert(n.clone(), u64::MAX);
1207 let _ = vc.increment(&n);
1208 }
1209
1210 #[test]
1211 #[should_panic(expected = "Vector clock counter overflowed while incrementing")]
1212 fn vector_clock_receive_panics_on_local_overflow() {
1213 let local = node("A");
1214 let remote = node("B");
1215 let mut vc = VectorClock::new();
1216 vc.entries.insert(local.clone(), u64::MAX);
1217
1218 let mut remote_clock = VectorClock::new();
1219 remote_clock.set(&remote, 1);
1220
1221 vc.receive(&local, &remote_clock);
1222 }
1223
1224 #[test]
1225 fn causal_tracker_send_receive_protocol() {
1226 let na = node("A");
1227 let nb = node("B");
1228 let mut tracker_a = CausalTracker::new(na.clone());
1229 let mut tracker_b = CausalTracker::new(nb.clone());
1230
1231 tracker_a.record_local_event(); let msg_clock = tracker_a.on_send(); assert_eq!(msg_clock.get(&na), 2);
1237
1238 tracker_b.on_receive(&msg_clock); assert_eq!(tracker_b.current_clock().get(&na), 2);
1241 assert_eq!(tracker_b.current_clock().get(&nb), 1);
1242
1243 tracker_b.record_local_event(); assert!(msg_clock.happens_before(tracker_b.current_clock()));
1248 }
1249
1250 #[test]
1251 fn causal_tracker_transcript_snapshot_scrubbed() {
1252 let na = node("alpha-node");
1253 let nb = node("beta-node");
1254 let mut tracker_a = CausalTracker::new(na);
1255 let mut tracker_b = CausalTracker::new(nb);
1256 let node_a = tracker_a.node().as_str().to_string();
1257 let node_b = tracker_b.node().as_str().to_string();
1258
1259 let a_local = tracker_a.record_local_event();
1260 let a_send = tracker_a.on_send();
1261 tracker_b.on_receive(&a_send);
1262 let b_after_receive = tracker_b.current_clock().clone();
1263 let b_local = tracker_b.record_local_event();
1264
1265 insta::assert_json_snapshot!(
1266 "causal_tracker_transcript_scrubbed",
1267 scrub_vclock_output(json!({
1268 "steps": [
1269 {
1270 "step": "a_local",
1271 "node": node_a,
1272 "clock": a_local.to_string(),
1273 },
1274 {
1275 "step": "a_send",
1276 "node": tracker_a.node().as_str(),
1277 "clock": a_send.to_string(),
1278 "order_vs_local": format!("{:?}", a_send.causal_order(&a_local)),
1279 },
1280 {
1281 "step": "b_receive",
1282 "node": node_b,
1283 "clock": b_after_receive.to_string(),
1284 "order_vs_send": format!("{:?}", b_after_receive.causal_order(&a_send)),
1285 },
1286 {
1287 "step": "b_local",
1288 "node": tracker_b.node().as_str(),
1289 "clock": b_local.to_string(),
1290 "send_happens_before": a_send.happens_before(&b_local),
1291 "receive_happens_before": b_after_receive.happens_before(&b_local),
1292 }
1293 ]
1294 }))
1295 );
1296 }
1297
1298 #[test]
1299 fn causal_event_ordering() {
1300 let na = node("A");
1301 let nb = node("B");
1302 let mut tracker_a = CausalTracker::new(na);
1303 let mut tracker_b = CausalTracker::new(nb);
1304
1305 let e1 = tracker_a.record("event-1");
1306 let e2 = tracker_b.record("event-2");
1307
1308 assert!(e1.is_concurrent_with(&e2));
1310 assert!(!e1.happens_before(&e2));
1311 }
1312
1313 #[test]
1314 fn display_formatting() {
1315 let na = node("A");
1316 let nb = node("B");
1317 let mut vc = VectorClock::new();
1318 vc.increment(&na);
1319 vc.increment(&nb);
1320 vc.increment(&nb);
1321 let display = format!("{vc}");
1322 assert!(display.contains("A=1"));
1323 assert!(display.contains("B=2"));
1324 }
1325
1326 #[test]
1327 fn partial_order_after() {
1328 let na = node("A");
1329 #[allow(clippy::many_single_char_names)]
1330 let mut a = VectorClock::new();
1331 a.increment(&na);
1332 let b = VectorClock::new();
1333 assert_eq!(a.causal_order(&b), CausalOrder::After);
1334 assert_eq!(a.partial_cmp(&b), Some(std::cmp::Ordering::Greater));
1335 }
1336
1337 #[test]
1338 fn three_node_diamond() {
1339 let na = node("A");
1344 let nb = node("B");
1345 let nc = node("C");
1346 let nd = node("D");
1347
1348 let mut ta = CausalTracker::new(na);
1349 let mut tb = CausalTracker::new(nb);
1350 let mut tc = CausalTracker::new(nc);
1351 let mut td = CausalTracker::new(nd);
1352
1353 let msg_to_b = ta.on_send();
1355 let msg_to_c = ta.on_send();
1356
1357 tb.on_receive(&msg_to_b);
1358 tc.on_receive(&msg_to_c);
1359
1360 let b_clock = tb.on_send();
1362 let c_clock = tc.on_send();
1363
1364 assert!(b_clock.is_concurrent_with(&c_clock));
1366
1367 td.on_receive(&b_clock);
1369 td.on_receive(&c_clock);
1370
1371 assert!(b_clock.happens_before(td.current_clock()));
1373 assert!(c_clock.happens_before(td.current_clock()));
1374 }
1375
1376 #[test]
1381 fn hybrid_time_debug_clone_copy_hash_ord() {
1382 use std::collections::HashSet;
1383 let ht = HybridTime::new(Time::from_nanos(1_000), 3);
1384 let dbg = format!("{ht:?}");
1385 assert!(dbg.contains("HybridTime"), "{dbg}");
1386 let copied = ht;
1387 let cloned = ht;
1388 assert_eq!(copied, cloned);
1389
1390 let earlier = HybridTime::new(Time::ZERO, 0);
1391 assert!(earlier < ht);
1392
1393 let mut set = HashSet::new();
1394 set.insert(ht);
1395 set.insert(earlier);
1396 assert_eq!(set.len(), 2);
1397 assert!(set.contains(&ht));
1398 }
1399
1400 #[test]
1401 fn logical_clock_kind_debug_clone_copy_eq() {
1402 let k = LogicalClockKind::Lamport;
1403 let dbg = format!("{k:?}");
1404 assert!(dbg.contains("Lamport"), "{dbg}");
1405 let copied = k;
1406 let cloned = k;
1407 assert_eq!(copied, cloned);
1408 assert_ne!(k, LogicalClockKind::Vector);
1409 assert_ne!(k, LogicalClockKind::Hybrid);
1410 }
1411
1412 #[test]
1413 fn logical_time_debug_clone_eq() {
1414 let lt = LogicalTime::Lamport(LamportTime::from_raw(5));
1415 let dbg = format!("{lt:?}");
1416 assert!(dbg.contains("Lamport"), "{dbg}");
1417 let cloned = lt.clone();
1418 assert_eq!(lt, cloned);
1419 }
1420
1421 #[test]
1422 fn logical_clock_mode_debug_clone() {
1423 let mode = LogicalClockMode::Lamport;
1424 let dbg = format!("{mode:?}");
1425 assert!(dbg.contains("Lamport"), "{dbg}");
1426 let cloned = mode;
1427 let dbg2 = format!("{cloned:?}");
1428 assert_eq!(dbg, dbg2);
1429 }
1430
1431 #[test]
1443 fn canonical_vector_clock_serialization_snapshot() {
1444 let na = node("alpha-node");
1445 let nb = node("beta-node");
1446
1447 let empty = VectorClock::new();
1449
1450 let single = VectorClock::for_node(&na);
1452
1453 #[allow(clippy::many_single_char_names)]
1455 let mut a = VectorClock::new();
1456 a.increment(&na);
1457 a.increment(&na);
1458 let mut b = VectorClock::new();
1459 b.increment(&nb);
1460 let merged = a.merge(&b);
1461
1462 let mut c = VectorClock::new();
1464 c.increment(&na);
1465 let mut d = VectorClock::new();
1466 d.increment(&nb);
1467 d.increment(&nb);
1468 c.receive(&na, &d); let before_lhs = {
1476 let mut v = VectorClock::new();
1477 v.increment(&na);
1478 v
1479 };
1480 let before_rhs = {
1481 let mut v = VectorClock::new();
1482 v.increment(&na);
1483 v.increment(&na);
1484 v
1485 };
1486 let equal_lhs = {
1487 let mut v = VectorClock::new();
1488 v.increment(&na);
1489 v.increment(&nb);
1490 v
1491 };
1492 let equal_rhs = {
1493 let mut v = VectorClock::new();
1494 v.increment(&na);
1495 v.increment(&nb);
1496 v
1497 };
1498 let concurrent_lhs = {
1499 let mut v = VectorClock::new();
1500 v.increment(&na);
1501 v
1502 };
1503 let concurrent_rhs = {
1504 let mut v = VectorClock::new();
1505 v.increment(&nb);
1506 v
1507 };
1508
1509 let mut zero_set = VectorClock::new();
1512 zero_set.set(&na, 0);
1513
1514 insta::assert_json_snapshot!(
1515 "canonical_vector_clock_serialization",
1516 scrub_vclock_output(json!({
1517 "states": {
1518 "empty": { "display": empty.to_string(), "debug": format!("{empty:?}"), "node_count": empty.node_count(), "is_zero": empty.is_zero() },
1519 "single": { "display": single.to_string(), "debug": format!("{single:?}"), "node_count": single.node_count(), "is_zero": single.is_zero() },
1520 "merged": { "display": merged.to_string(), "debug": format!("{merged:?}"), "node_count": merged.node_count(), "is_zero": merged.is_zero() },
1521 "post_receive": { "display": c.to_string(), "debug": format!("{c:?}"), "node_count": c.node_count(), "is_zero": c.is_zero() },
1522 "set_zero_noop": { "display": zero_set.to_string(), "debug": format!("{zero_set:?}"), "is_zero": zero_set.is_zero() },
1523 },
1524 "causal_orders": [
1525 { "case": "before", "lhs": before_lhs.to_string(), "rhs": before_rhs.to_string(), "verdict": format!("{:?}", before_lhs.causal_order(&before_rhs)) },
1526 { "case": "after", "lhs": before_rhs.to_string(), "rhs": before_lhs.to_string(), "verdict": format!("{:?}", before_rhs.causal_order(&before_lhs)) },
1527 { "case": "equal", "lhs": equal_lhs.to_string(), "rhs": equal_rhs.to_string(), "verdict": format!("{:?}", equal_lhs.causal_order(&equal_rhs)) },
1528 { "case": "concurrent", "lhs": concurrent_lhs.to_string(), "rhs": concurrent_rhs.to_string(), "verdict": format!("{:?}", concurrent_lhs.causal_order(&concurrent_rhs)) },
1529 ],
1530 "iter_determinism": {
1531 "merged_keys_in_order": merged.iter()
1533 .map(|(n, v)| format!("{}={}", n.as_str(), v))
1534 .collect::<Vec<_>>(),
1535 }
1536 }))
1537 );
1538 }
1539}