use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct VectorClock {
pub clocks: HashMap<String, u64>,
}
impl VectorClock {
pub fn new() -> Self {
Self {
clocks: HashMap::new(),
}
}
pub fn from_clocks(clocks: HashMap<String, u64>) -> Self {
Self { clocks }
}
pub fn increment(&mut self, instance_id: &str) {
let entry = self.clocks.entry(instance_id.to_string()).or_insert(0);
*entry += 1;
}
pub fn get(&self, instance_id: &str) -> u64 {
*self.clocks.get(instance_id).unwrap_or(&0)
}
pub fn merge(&mut self, other: &VectorClock) {
for (instance_id, other_clock) in &other.clocks {
let entry = self.clocks.entry(instance_id.clone()).or_insert(0);
*entry = (*entry).max(*other_clock);
}
}
pub fn happens_before(&self, other: &VectorClock) -> bool {
let mut at_least_one_less = false;
for (instance_id, other_value) in &other.clocks {
let self_value = self.get(instance_id);
if self_value > *other_value {
return false; }
if self_value < *other_value {
at_least_one_less = true;
}
}
for (instance_id, self_value) in &self.clocks {
let other_value = other.get(instance_id);
if self_value > &other_value {
return false;
}
}
at_least_one_less
}
pub fn is_concurrent(&self, other: &VectorClock) -> bool {
!self.happens_before(other) && !other.happens_before(self)
}
pub fn dominates(&self, other: &VectorClock) -> bool {
other
.clocks
.iter()
.all(|(instance_id, required)| self.get(instance_id) >= *required)
}
pub fn causal_cmp(&self, other: &VectorClock) -> Ordering {
if self == other {
return Ordering::Equal;
}
if self.happens_before(other) {
Ordering::Less
} else if other.happens_before(self) {
Ordering::Greater
} else {
let all_instances: std::collections::BTreeSet<_> =
self.clocks.keys().chain(other.clocks.keys()).collect();
for instance_id in all_instances {
let self_val = self.get(instance_id);
let other_val = other.get(instance_id);
if self_val != other_val {
return if self_val < other_val {
Ordering::Less
} else {
Ordering::Greater
};
}
}
Ordering::Equal
}
}
}
impl Default for VectorClock {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct StateVersion {
pub vector_clock: VectorClock,
pub timestamp_ms: u128,
}
impl StateVersion {
pub fn new(vector_clock: VectorClock, timestamp_ms: u128) -> Self {
Self {
vector_clock,
timestamp_ms,
}
}
pub fn from_clocks(clocks: HashMap<String, u64>, timestamp_ms: u128) -> Self {
Self {
vector_clock: VectorClock::from_clocks(clocks),
timestamp_ms,
}
}
pub fn happens_before(&self, other: &StateVersion) -> bool {
self.vector_clock.happens_before(&other.vector_clock)
}
pub fn is_concurrent(&self, other: &StateVersion) -> bool {
self.vector_clock.is_concurrent(&other.vector_clock)
}
pub fn causal_cmp(&self, other: &StateVersion) -> Ordering {
self.vector_clock.causal_cmp(&other.vector_clock)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CausalHistory {
pub state_version: StateVersion,
pub instance_id: String,
pub sequence: u64,
pub dependencies: Vec<VectorClock>,
}
impl CausalHistory {
pub fn new(state_version: StateVersion, instance_id: String, sequence: u64) -> Self {
Self {
state_version,
instance_id,
sequence,
dependencies: Vec::new(),
}
}
pub fn add_dependency(&mut self, dep_clock: VectorClock) {
if !self.dependencies.contains(&dep_clock) {
self.dependencies.push(dep_clock);
}
}
pub fn depends_on(&self, other: &CausalHistory) -> bool {
if self
.dependencies
.contains(&other.state_version.vector_clock)
{
return true;
}
other.state_version.happens_before(&self.state_version)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConflictResolutionStrategy {
LastWriteWins,
CausalConsistent,
ApplicationDefined,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConflictResolution {
pub winner_index: Option<usize>,
pub strategy: ConflictResolutionStrategy,
pub is_concurrent: bool,
pub is_causal: bool,
}
impl ConflictResolution {
pub fn resolve_lww(histories: &[CausalHistory]) -> Self {
if histories.is_empty() {
return Self {
winner_index: None,
strategy: ConflictResolutionStrategy::LastWriteWins,
is_concurrent: false,
is_causal: false,
};
}
if histories.len() == 1 {
return Self {
winner_index: Some(0),
strategy: ConflictResolutionStrategy::LastWriteWins,
is_concurrent: false,
is_causal: false,
};
}
let mut is_causal = false;
for i in 0..histories.len() {
for j in 0..histories.len() {
if i != j
&& histories[i]
.state_version
.happens_before(&histories[j].state_version)
{
is_causal = true;
break;
}
}
if is_causal {
break;
}
}
if is_causal {
let mut winner_idx = 0;
for i in 1..histories.len() {
if histories[winner_idx]
.state_version
.happens_before(&histories[i].state_version)
{
winner_idx = i;
}
}
return Self {
winner_index: Some(winner_idx),
strategy: ConflictResolutionStrategy::LastWriteWins,
is_concurrent: false,
is_causal: true,
};
}
let mut winner_idx = 0;
for i in 1..histories.len() {
let ts_cmp = histories[i]
.state_version
.timestamp_ms
.cmp(&histories[winner_idx].state_version.timestamp_ms);
if ts_cmp == std::cmp::Ordering::Greater {
winner_idx = i;
} else if ts_cmp == std::cmp::Ordering::Equal {
if histories[i].instance_id > histories[winner_idx].instance_id {
winner_idx = i;
}
}
}
Self {
winner_index: Some(winner_idx),
strategy: ConflictResolutionStrategy::LastWriteWins,
is_concurrent: true,
is_causal: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MergeResult {
pub merged_clock: VectorClock,
pub conflicts: Vec<(usize, usize, ConflictResolution)>,
pub changed: bool,
}
impl MergeResult {
pub fn new(merged_clock: VectorClock) -> Self {
Self {
merged_clock,
conflicts: Vec::new(),
changed: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vector_clock_increment() {
let mut clock = VectorClock::new();
clock.increment("inst1");
assert_eq!(clock.get("inst1"), 1);
assert_eq!(clock.get("inst2"), 0);
clock.increment("inst1");
assert_eq!(clock.get("inst1"), 2);
}
#[test]
fn vector_clock_merge() {
let mut clock1 = VectorClock::new();
clock1.increment("inst1");
clock1.increment("inst1");
let mut clock2 = VectorClock::new();
clock2.increment("inst2");
clock2.increment("inst2");
clock2.increment("inst2");
clock1.merge(&clock2);
assert_eq!(clock1.get("inst1"), 2);
assert_eq!(clock1.get("inst2"), 3);
}
#[test]
fn vector_clock_happens_before() {
let mut clock1 = VectorClock::new();
clock1.increment("inst1");
let mut clock2 = VectorClock::new();
clock2.increment("inst1");
clock2.increment("inst1");
assert!(clock1.happens_before(&clock2));
assert!(!clock2.happens_before(&clock1));
}
#[test]
fn vector_clock_concurrent() {
let mut clock1 = VectorClock::new();
clock1.increment("inst1");
let mut clock2 = VectorClock::new();
clock2.increment("inst2");
assert!(clock1.is_concurrent(&clock2));
assert!(clock2.is_concurrent(&clock1));
}
#[test]
fn state_version_causal_ordering() {
let mut clock1 = VectorClock::new();
clock1.increment("inst1");
let version1 = StateVersion::new(clock1, 1000);
let mut clock2 = VectorClock::new();
clock2.increment("inst1");
clock2.increment("inst1");
let version2 = StateVersion::new(clock2, 2000);
assert!(version1.happens_before(&version2));
assert!(version1.causal_cmp(&version2) == Ordering::Less);
}
#[test]
fn conflict_resolution_lww_causal() {
let mut clock1 = VectorClock::new();
clock1.increment("inst1");
let version1 = StateVersion::new(clock1, 1000);
let history1 = CausalHistory::new(version1, "inst1".to_string(), 1);
let mut clock2 = VectorClock::new();
clock2.increment("inst1");
clock2.increment("inst1");
let version2 = StateVersion::new(clock2, 2000);
let history2 = CausalHistory::new(version2, "inst1".to_string(), 2);
let resolution = ConflictResolution::resolve_lww(&[history1, history2]);
assert_eq!(resolution.winner_index, Some(1));
assert!(resolution.is_causal);
assert!(!resolution.is_concurrent);
}
#[test]
fn conflict_resolution_lww_concurrent() {
let mut clock1 = VectorClock::new();
clock1.increment("inst1");
let version1 = StateVersion::new(clock1, 1000);
let history1 = CausalHistory::new(version1, "inst1".to_string(), 1);
let mut clock2 = VectorClock::new();
clock2.increment("inst2");
let version2 = StateVersion::new(clock2, 2000);
let history2 = CausalHistory::new(version2, "inst2".to_string(), 1);
let resolution = ConflictResolution::resolve_lww(&[history1, history2]);
assert!(resolution.winner_index.is_some());
assert!(!resolution.is_causal);
assert!(resolution.is_concurrent);
}
#[test]
fn causal_history_dependency() {
let mut clock1 = VectorClock::new();
clock1.increment("inst1");
let version1 = StateVersion::new(clock1.clone(), 1000);
let history1 = CausalHistory::new(version1, "inst1".to_string(), 1);
let mut clock2 = VectorClock::new();
clock2.clone_from(&clock1);
clock2.increment("inst2");
let version2 = StateVersion::new(clock2, 2000);
let mut history2 = CausalHistory::new(version2, "inst2".to_string(), 1);
history2.add_dependency(clock1.clone());
assert!(history2.depends_on(&history1));
}
#[test]
fn vector_clock_lexicographic_tiebreaker() {
let mut clock1 = VectorClock::new();
clock1.clocks.insert("inst-a".to_string(), 1);
clock1.clocks.insert("inst-b".to_string(), 1);
let clock2 = VectorClock::from_clocks(clock1.clocks.clone());
let cmp_result = clock1.causal_cmp(&clock2);
assert_eq!(cmp_result, Ordering::Equal);
}
#[test]
fn concurrent_mutations_deterministic_resolution() {
let mut clock_a = VectorClock::new();
clock_a.increment("inst-a");
let version_a = StateVersion::new(clock_a, 1000);
let history_a = CausalHistory::new(version_a, "inst-a".to_string(), 1);
let mut clock_b = VectorClock::new();
clock_b.increment("inst-b");
let version_b = StateVersion::new(clock_b, 1000);
let history_b = CausalHistory::new(version_b, "inst-b".to_string(), 1);
let resolution = ConflictResolution::resolve_lww(&[history_a.clone(), history_b.clone()]);
assert!(resolution.is_concurrent);
assert!(!resolution.is_causal);
assert!(resolution.winner_index.is_some());
assert_eq!(
resolution.winner_index,
Some(1), );
}
#[test]
fn causal_chain_three_operations() {
let mut clock1 = VectorClock::new();
clock1.increment("inst1");
let version1 = StateVersion::new(clock1.clone(), 1000);
let history1 = CausalHistory::new(version1, "inst1".to_string(), 1);
let mut clock2 = clock1.clone();
clock2.increment("inst2");
let version2 = StateVersion::new(clock2.clone(), 2000);
let history2 = CausalHistory::new(version2, "inst2".to_string(), 1);
let mut clock3 = clock2.clone();
clock3.increment("inst3");
let version3 = StateVersion::new(clock3, 3000);
let history3 = CausalHistory::new(version3, "inst3".to_string(), 1);
assert!(history1
.state_version
.happens_before(&history2.state_version));
assert!(history2
.state_version
.happens_before(&history3.state_version));
assert!(history1
.state_version
.happens_before(&history3.state_version));
let resolution1 = ConflictResolution::resolve_lww(&[history1, history2, history3]);
assert_eq!(resolution1.winner_index, Some(2)); assert!(resolution1.is_causal);
}
#[test]
fn merge_result_construction() {
let mut merged_clock = VectorClock::new();
merged_clock.increment("inst1");
merged_clock.increment("inst2");
let mut merge_result = MergeResult::new(merged_clock.clone());
assert!(!merge_result.changed);
assert_eq!(merge_result.conflicts.len(), 0);
let history_a = CausalHistory::new(
StateVersion::new(VectorClock::new(), 1000),
"inst-a".to_string(),
1,
);
let history_b = CausalHistory::new(
StateVersion::new(VectorClock::new(), 1000),
"inst-b".to_string(),
1,
);
let resolution = ConflictResolution::resolve_lww(&[history_a, history_b]);
merge_result.conflicts.push((0, 1, resolution));
assert_eq!(merge_result.conflicts.len(), 1);
}
#[test]
fn vector_clock_partial_order() {
let mut clock1 = VectorClock::new();
clock1.increment("inst1");
let mut clock2 = VectorClock::new();
clock2.increment("inst1");
clock2.increment("inst2");
let mut clock3 = VectorClock::new();
clock3.increment("inst2");
clock3.increment("inst2");
assert!(clock1.happens_before(&clock2));
assert!(!clock2.happens_before(&clock1));
assert!(clock1.is_concurrent(&clock3));
assert!(clock3.is_concurrent(&clock1));
assert!(clock2.is_concurrent(&clock3));
}
#[test]
fn state_version_ordering() {
let mut clock1 = VectorClock::new();
clock1.increment("inst1");
let version1 = StateVersion::new(clock1, 1000);
let mut clock2 = VectorClock::new();
clock2.increment("inst1");
clock2.increment("inst1");
let version2 = StateVersion::new(clock2, 2000);
assert!(version1.happens_before(&version2));
assert_eq!(version1.causal_cmp(&version2), Ordering::Less);
assert_eq!(version2.causal_cmp(&version1), Ordering::Greater);
}
}