Repository navigation
Expand file tree
/
Copy pathVectorClock.cs
More file actions
59 lines (48 loc) · 2.7 KB
/
Copy pathVectorClock.cs
File metadata and controls
59 lines (48 loc) · 2.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using System.Collections.Immutable;
namespace CSharpExercises.Distributed;
public enum Causality { Equal, Before, After, Concurrent }
/// <summary>
/// Vector clocks capture causality exactly: each node keeps a counter per node. a happened before b if every counter
/// in a is ≤ the one in b (and they differ); if neither dominates, the events are concurrent: a genuine conflict that
/// a database such as Dynamo or Riak must keep both versions of ("siblings") for the application to resolve.
/// </summary>
public sealed record VectorClock(ImmutableSortedDictionary<string, long> Counters)
{
public static VectorClock Empty { get; } = new(ImmutableSortedDictionary<string, long>.Empty);
public long this[string node] => Counters.GetValueOrDefault(node);
public VectorClock Increment(string node) => new(Counters.SetItem(node, this[node] + 1));
/// <summary>On receiving: take the maximum of each counter, then count the receive event.</summary>
public VectorClock Merge(VectorClock other, string receivingNode) =>
new VectorClock(Counters.Keys.Union(other.Counters.Keys)
.Aggregate(ImmutableSortedDictionary<string, long>.Empty, (acc, k) => acc.SetItem(k, Math.Max(this[k], other[k]))))
.Increment(receivingNode);
public Causality CompareTo(VectorClock other)
{
var keys = Counters.Keys.Union(other.Counters.Keys).ToList();
var less = keys.Any(k => this[k] < other[k]);
var greater = keys.Any(k => this[k] > other[k]);
return (less, greater) switch
{
(false, false) => Causality.Equal,
(true, false) => Causality.Before,
(false, true) => Causality.After,
_ => Causality.Concurrent,
};
}
public override string ToString() => "{" + string.Join(", ", Counters.Select(c => $"{c.Key}:{c.Value}")) + "}";
}
/// <summary>A key-value store replica that keeps concurrent writes as siblings instead of silently losing one.</summary>
public sealed class VersionedStore
{
private readonly Dictionary<string, List<(string Value, VectorClock Clock)>> _data = new();
public IReadOnlyList<(string Value, VectorClock Clock)> Get(string key) => _data.GetValueOrDefault(key) ?? [];
/// <summary>Stores a write, dropping every version it supersedes and keeping the concurrent ones.</summary>
public void Put(string key, string value, VectorClock clock)
{
var versions = _data.GetValueOrDefault(key) ?? [];
if (versions.Any(v => v.Clock.CompareTo(clock) is Causality.After or Causality.Equal)) return; // stale write
versions.RemoveAll(v => v.Clock.CompareTo(clock) == Causality.Before);
versions.Add((value, clock));
_data[key] = versions;
}
}