197 lines
6.1 KiB
C#
Executable File
197 lines
6.1 KiB
C#
Executable File
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
|
|
}
|