Initial import of the CBDDC codebase with docs and tests. Add a .NET-focused gitignore to keep generated artifacts out of source control.
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
196
src/ZB.MOM.WW.CBDDC.Core/VectorClock.cs
Executable file
196
src/ZB.MOM.WW.CBDDC.Core/VectorClock.cs
Executable file
@@ -0,0 +1,196 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace ZB.MOM.WW.CBDDC.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Vector Clock for tracking causality in a distributed system.
|
||||
/// Maps NodeId -> HlcTimestamp to track the latest known state of each node.
|
||||
/// </summary>
|
||||
public class VectorClock
|
||||
{
|
||||
private readonly Dictionary<string, HlcTimestamp> _clock;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new empty vector clock.
|
||||
/// </summary>
|
||||
public VectorClock()
|
||||
{
|
||||
_clock = new Dictionary<string, HlcTimestamp>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new vector clock from an existing clock state.
|
||||
/// </summary>
|
||||
/// <param name="clock">The clock state to copy.</param>
|
||||
public VectorClock(Dictionary<string, HlcTimestamp> clock)
|
||||
{
|
||||
_clock = new Dictionary<string, HlcTimestamp>(clock, StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all node IDs in this vector clock.
|
||||
/// </summary>
|
||||
public IEnumerable<string> NodeIds => _clock.Keys;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timestamp for a specific node, or default if not present.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node identifier.</param>
|
||||
public HlcTimestamp GetTimestamp(string nodeId)
|
||||
{
|
||||
return _clock.TryGetValue(nodeId, out var ts) ? ts : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets or updates the timestamp for a specific node.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node identifier.</param>
|
||||
/// <param name="timestamp">The timestamp to set.</param>
|
||||
public void SetTimestamp(string nodeId, HlcTimestamp timestamp)
|
||||
{
|
||||
_clock[nodeId] = timestamp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges another vector clock into this one, taking the maximum timestamp for each node.
|
||||
/// </summary>
|
||||
/// <param name="other">The vector clock to merge from.</param>
|
||||
public void Merge(VectorClock other)
|
||||
{
|
||||
foreach (var nodeId in other.NodeIds)
|
||||
{
|
||||
var otherTs = other.GetTimestamp(nodeId);
|
||||
if (!_clock.TryGetValue(nodeId, out var currentTs) || otherTs.CompareTo(currentTs) > 0)
|
||||
{
|
||||
_clock[nodeId] = otherTs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares this vector clock with another to determine causality.
|
||||
/// Returns:
|
||||
/// - Positive: This is strictly ahead (dominates other)
|
||||
/// - Negative: Other is strictly ahead (other dominates this)
|
||||
/// - Zero: Concurrent (neither dominates)
|
||||
/// </summary>
|
||||
/// <param name="other">The vector clock to compare with.</param>
|
||||
public CausalityRelation CompareTo(VectorClock other)
|
||||
{
|
||||
bool thisAhead = false;
|
||||
bool otherAhead = false;
|
||||
|
||||
var allNodes = new HashSet<string>(_clock.Keys.Union(other._clock.Keys), StringComparer.Ordinal);
|
||||
|
||||
foreach (var nodeId in allNodes)
|
||||
{
|
||||
var thisTs = GetTimestamp(nodeId);
|
||||
var otherTs = other.GetTimestamp(nodeId);
|
||||
|
||||
int cmp = thisTs.CompareTo(otherTs);
|
||||
|
||||
if (cmp > 0)
|
||||
{
|
||||
thisAhead = true;
|
||||
}
|
||||
else if (cmp < 0)
|
||||
{
|
||||
otherAhead = true;
|
||||
}
|
||||
|
||||
// Early exit if concurrent
|
||||
if (thisAhead && otherAhead)
|
||||
{
|
||||
return CausalityRelation.Concurrent;
|
||||
}
|
||||
}
|
||||
|
||||
if (thisAhead && !otherAhead)
|
||||
return CausalityRelation.StrictlyAhead;
|
||||
if (otherAhead && !thisAhead)
|
||||
return CausalityRelation.StrictlyBehind;
|
||||
|
||||
return CausalityRelation.Equal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines which nodes have updates that this vector clock doesn't have.
|
||||
/// Returns node IDs where the other vector clock is ahead.
|
||||
/// </summary>
|
||||
/// <param name="other">The vector clock to compare against.</param>
|
||||
public IEnumerable<string> GetNodesWithUpdates(VectorClock other)
|
||||
{
|
||||
var allNodes = new HashSet<string>(_clock.Keys, StringComparer.Ordinal);
|
||||
foreach (var nodeId in other._clock.Keys)
|
||||
{
|
||||
allNodes.Add(nodeId);
|
||||
}
|
||||
|
||||
foreach (var nodeId in allNodes)
|
||||
{
|
||||
var thisTs = GetTimestamp(nodeId);
|
||||
var otherTs = other.GetTimestamp(nodeId);
|
||||
|
||||
if (otherTs.CompareTo(thisTs) > 0)
|
||||
{
|
||||
yield return nodeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines which nodes have updates that the other vector clock doesn't have.
|
||||
/// Returns node IDs where this vector clock is ahead.
|
||||
/// </summary>
|
||||
/// <param name="other">The vector clock to compare against.</param>
|
||||
public IEnumerable<string> GetNodesToPush(VectorClock other)
|
||||
{
|
||||
var allNodes = new HashSet<string>(_clock.Keys.Union(other._clock.Keys), StringComparer.Ordinal);
|
||||
|
||||
foreach (var nodeId in allNodes)
|
||||
{
|
||||
var thisTs = GetTimestamp(nodeId);
|
||||
var otherTs = other.GetTimestamp(nodeId);
|
||||
|
||||
if (thisTs.CompareTo(otherTs) > 0)
|
||||
{
|
||||
yield return nodeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a copy of this vector clock.
|
||||
/// </summary>
|
||||
public VectorClock Clone()
|
||||
{
|
||||
return new VectorClock(new Dictionary<string, HlcTimestamp>(_clock, StringComparer.Ordinal));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
if (_clock.Count == 0)
|
||||
return "{}";
|
||||
|
||||
var entries = _clock.Select(kvp => $"{kvp.Key}:{kvp.Value}");
|
||||
return "{" + string.Join(", ", entries) + "}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the causality relationship between two vector clocks.
|
||||
/// </summary>
|
||||
public enum CausalityRelation
|
||||
{
|
||||
/// <summary>Both vector clocks are equal.</summary>
|
||||
Equal,
|
||||
/// <summary>This vector clock is strictly ahead (dominates).</summary>
|
||||
StrictlyAhead,
|
||||
/// <summary>This vector clock is strictly behind (dominated).</summary>
|
||||
StrictlyBehind,
|
||||
/// <summary>Vector clocks are concurrent (neither dominates).</summary>
|
||||
Concurrent
|
||||
}
|
||||
Reference in New Issue
Block a user