Crate vclock

source ·
Expand description

VClock is a vector clock implementated in Rust.

It offers a partial order of events in a distributed system. In practice, it implements the rust partial order trait over hash maps which maintain an integer count of each modification, per key.

VClock icon

§Examples

Basic usage:

use vclock::VClock;

let c1 = VClock::<&str, u64>::new("a");  // c1 is now a:0
let mut c2 = VClock::new("b");           // c2 is now b:0
c2.incr(&"a");                           // c1 is now a:0,b:0
assert!(c1 < c2, "c1 should be a child of c2");

Here is how to generate a conflict:

use vclock::VClock;

let mut c1: VClock<&str>=VClock::default();

c1.incr(&"a");
c1.incr(&"a");
c1.incr(&"a");
c1.incr(&"a");
c1.incr(&"b"); // c1 is now a:3, b:0

let c2 = c1.clone().next(&"a"); // c2 is now a:4,b:0
let c3 = c1.clone().next(&"b"); // c3 is now a:3,b:1

// Now let's assert there is a relationship c1 -> c2 and c1 -> c3.
// By relationship, we mean that there is a forward path of updates
// using incr() or next() from one member to the other.
assert!(c1<=c2, "c1={} should be a child of c2={}", c1, c2);
assert!(c2>=c1, "c2={} should be a parent of c1={}", c2, c1);
assert!(c1<=c3, "c1={} c1 should be a child of c3={}", c1, c3);
assert!(c3>=c1, "c3={} c1 should be a parent of c1={}", c3, c1);

// But there is no relationship between c2 and c3.
// In a distributed system with concurrent updates of an object,
// this allows the detection of a conflict.
assert!(!(c2<=c3), "c2={} should not be a child of c3={}", c2, c3);
assert!(!(c2>=c3), "c2={} should not be a parent of c3={}", c2, c3);
assert!(!(c3<=c2), "c3={} should not be a child of c2={}", c3, c2);
assert!(!(c3>=c2), "c3={} should not be a parent of c2={}", c3, c2);

Also, two objects having a different history path, but the same final state, will be considered the same. It is up to the program to ensure only one agent updates a given key:

use vclock::VClock;

let mut c1 = VClock::<&str, u64>::new("a"); // c1 is now a:0
let mut c2 = VClock::<&str, u64>::new("b"); // c2 is now b:0
c1.incr(&"c"); // c1 is now a:0, c:0
c1.incr(&"b"); // c1 is now a:0, b:0, c:0
c2.incr(&"a"); // c2 is now a:0, b:0, which is a value c1 never had
c2.incr(&"c"); // c2 is now a:0, b:0, c:0

// The following test shows clocks are considered the same.
// In a distributed system using vector clocks, only one agent
// would update a given key, and when updating this key, it should
// ensure than there is no conflict with the latest version it had locally.
assert_eq!(c1, c2, "c1={} and c2={} represent the same final state", c1, c2);

A typical conflict resolution:

use std::collections::HashMap;
use vclock::VClock;

// A dummy object with maintains a vector clock along with some data,
// here just a byte, to avoid complexifying the example with other issues.
#[derive(Default)]
struct Obj {
    vc: VClock<String>,
    val: u8,
}

impl Obj {
    // Update the data stored within the object. If there's no conflict,
    // the data is updated, the clock is set to the last value received,
    // and the function returns None.
    // If there is a conflict, then the previous data is returned, along
    // with the merged clock.
    fn update(
        &mut self,
        remote_clock: &VClock<String>,
        val: u8,
    ) -> Option<(u8, VClock<String>)> {
        if &self.vc < remote_clock {
            // Update value, no conflict
            self.vc = remote_clock.clone();
            self.val = val;
            None
        } else {
            // History conflict, update the clock, return the value,
            // and let the caller deal with it.
            Some((self.val, self.vc.clone().combine(remote_clock)))
        }
    }
}

let mut obj = Obj::default();
let mut h1 = HashMap::<String, usize>::new();
h1.insert("a".to_string(), 42);
let c1 = VClock::<String>::from(h1);
assert_eq!(None, obj.update(&c1, 10), "no history, no problem");

let mut h2 = HashMap::<String, usize>::new();
h2.insert("a".to_string(), 42);
h2.insert("b".to_string(), 10);
let c2 = VClock::<String>::from(h2);
assert_eq!(None, obj.update(&c2, 100), "forward history, updating");

let mut h3 = HashMap::<String, usize>::new();
h3.insert("a".to_string(), 43);
h3.insert("b".to_string(), 9);
let c3 = VClock::<String>::from(h3);
let mut h4 = HashMap::<String, usize>::new();
h4.insert("a".to_string(), 43);
h4.insert("b".to_string(), 10);
let c4 = VClock::<String>::from(h4);
assert_eq!(Some((100, c4)), obj.update(&c3, 50), "conflict between keys");

Use custom types:

use vclock::VClock;
use num_bigint::BigUint;
use std::hash::Hash;

#[derive(Hash, Eq, PartialEq, Clone)]
struct Coord{
    x: isize,
    y: isize,
}

let key = Coord{x: 1, y: 2};
let mut c1 = VClock::<Coord, BigUint>::default();
c1.incr(&key); // BigUint, can grow infinitely... (almost)

Modules§

Structs§

  • Vector clock with generic types.

Type Aliases§

  • Vector clock using u8 counters.
  • Vector clock using u16 counters.
  • Vector clock using u32 counters.
  • Vector clock using u64 counters. In doubt, use this.
  • Vector clock using u128 counters.
  • Vector clock using bigint counters.