diff options
Diffstat (limited to 'crdt')
| -rw-r--r-- | crdt/src/lib.rs | 16 | ||||
| -rw-r--r-- | crdt/src/vector_clock.rs | 75 |
2 files changed, 69 insertions, 22 deletions
diff --git a/crdt/src/lib.rs b/crdt/src/lib.rs index 7b426fc..88133a6 100644 --- a/crdt/src/lib.rs +++ b/crdt/src/lib.rs @@ -66,7 +66,12 @@ impl Doc { } } - self.ops.sort_by(|op1, op2| op1.clock.cmp(&op2.clock)); + self.ops.sort_by(|op1, op2| { + op1.clock + .partial_cmp(&op2.clock) + // Tie-breaker: yse op ID + .unwrap_or_else(|| op1.id.cmp(&op2.id)) + }); } pub fn realize(&self) -> Result<RealizedDoc, Error> { @@ -317,6 +322,15 @@ mod tests { ); } + assert_eq!( + doc1.ops + .last() + .unwrap() + .clock + .partial_cmp(&doc2.ops.last().unwrap().clock), + None + ); + doc1.merge(&doc2); assert_eq!(doc1.ops.len(), 3); diff --git a/crdt/src/vector_clock.rs b/crdt/src/vector_clock.rs index e5a39c4..f6ded56 100644 --- a/crdt/src/vector_clock.rs +++ b/crdt/src/vector_clock.rs @@ -22,6 +22,11 @@ impl VectorClock { m.insert(actor_id.clone(), self.get(actor_id) + 1); VectorClock(m) } + + /// Returns true if this clock is concurrent with another (neither happens before the other) + pub fn is_concurrent_with(&self, other: &VectorClock) -> bool { + self.partial_cmp(other).is_none() + } } impl PartialOrd for VectorClock { @@ -39,15 +44,7 @@ impl PartialOrd for VectorClock { let greater = orderings.contains(&Ordering::Greater); match (less, greater) { - (true, true) => { - let mut actors: Vec<_> = self.0.keys().collect(); - actors.sort(); - - let mut other_actors: Vec<_> = other.0.keys().collect(); - other_actors.sort(); - - Some(actors.cmp(&other_actors)) - } + (true, true) => None, (true, false) => Some(Ordering::Less), (false, true) => Some(Ordering::Greater), (false, false) => Some(Ordering::Equal), @@ -55,12 +52,6 @@ impl PartialOrd for VectorClock { } } -impl Ord for VectorClock { - fn cmp(&self, other: &Self) -> Ordering { - self.partial_cmp(other).unwrap() - } -} - #[cfg(test)] mod tests { use super::*; @@ -87,8 +78,8 @@ mod tests { alice = alice.inc(&alice_id); - assert!(alice < bob); - assert!(bob > alice); + assert!(!(alice < bob)); + assert!(!(alice > bob)); alice = alice.inc(&bob_id); bob = bob.inc(&alice_id); @@ -111,13 +102,55 @@ mod tests { bob = bob.inc(&bob_id); - assert!(alice > bob); - assert!(bob < alice); + assert!(!(alice > bob)); + assert!(!(alice < bob)); let clock_a = VectorClock::new().inc(&alice_id).inc(&carol_id); let clock_b = VectorClock::new().inc(&bob_id).inc(&carol_id); - assert!(clock_a < clock_b); - assert!(clock_b > clock_a); + assert!(!(clock_a > clock_b)); + assert!(!(clock_a < clock_b)); + } + + #[test] + fn concurrent_clocks() { + let alice_id = Uuid::now_v7(); + let bob_id = Uuid::now_v7(); + let carol_id = Uuid::now_v7(); + + // Equal clocks are not concurrent + let clock1 = VectorClock::new(); + let clock2 = VectorClock::new(); + assert!(!clock1.is_concurrent_with(&clock2)); + + // Causally ordered clocks are not concurrent + let clock_before = VectorClock::new().inc(&alice_id); + let clock_after = VectorClock::new().inc(&alice_id).inc(&bob_id); + assert!(!clock_before.is_concurrent_with(&clock_after)); + assert!(!clock_after.is_concurrent_with(&clock_before)); + + // Clocks from different actors are concurrent + let alice_clock = VectorClock::new().inc(&alice_id); + let bob_clock = VectorClock::new().inc(&bob_id); + assert!(alice_clock.is_concurrent_with(&bob_clock)); + assert!(bob_clock.is_concurrent_with(&alice_clock)); + + // Complex concurrent case: diverged branches + let clock_a = VectorClock::new() + .inc(&alice_id) + .inc(&alice_id) + .inc(&bob_id) + .inc(&carol_id); + + let clock_b = VectorClock::new() + .inc(&alice_id) + .inc(&alice_id) + .inc(&bob_id) + .inc(&bob_id); + + // clock_a: {alice: 2, bob: 1, carol: 1} + // clock_b: {alice: 2, bob: 2} + // carol: 1 > 0, but bob: 1 < 2 → concurrent + assert!(clock_a.is_concurrent_with(&clock_b)); } } |
