From 5c1789032cb2f957509168dc55ca359862f33078 Mon Sep 17 00:00:00 2001
From: Matt Kotsenas <51421+MattKotsenas@users.noreply.github.com>
Date: Wed, 25 Feb 2026 14:25:28 -0600
Subject: [PATCH 1/2] fix: VectorClock != operator returns negation of ==
instead of IsConcurrent
The != operator incorrectly delegated to IsConcurrentWith instead of
being the logical negation of ==. This meant a != b returned false when
one clock was Before or After the other, violating the fundamental
contract that != is the negation of ==.
Changed from IsConcurrentWith to !IsSameAs. Added regression assertions
in existing comparison tests that cover Before and After relationships.
---
src/core/Akka.Cluster.Tests/VectorClockSpec.cs | 3 +++
src/core/Akka.Cluster/VectorClock.cs | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/core/Akka.Cluster.Tests/VectorClockSpec.cs b/src/core/Akka.Cluster.Tests/VectorClockSpec.cs
index cb2fa563223..e72a63c18bf 100644
--- a/src/core/Akka.Cluster.Tests/VectorClockSpec.cs
+++ b/src/core/Akka.Cluster.Tests/VectorClockSpec.cs
@@ -60,6 +60,7 @@ public void A_VectorClock_must_pass_misc_comparison_test2()
var clock5_2 = clock4_2.Increment(VectorClock.Node.Create("3"));
(clock4_1 < clock5_2).Should().BeTrue();
+ (clock4_1 != clock5_2).Should().BeTrue("before relationship means not equal");
}
[Fact]
@@ -106,6 +107,8 @@ public void A_VectorClock_must_pass_misc_comparison_test5()
(clock3_1 < clock5_2).Should().BeTrue();
(clock5_2 > clock3_1).Should().BeTrue();
+ (clock3_1 != clock5_2).Should().BeTrue("before relationship means not equal");
+ (clock5_2 != clock3_1).Should().BeTrue("after relationship means not equal");
}
[Fact]
diff --git a/src/core/Akka.Cluster/VectorClock.cs b/src/core/Akka.Cluster/VectorClock.cs
index 68ca711787c..f9965eaecb6 100644
--- a/src/core/Akka.Cluster/VectorClock.cs
+++ b/src/core/Akka.Cluster/VectorClock.cs
@@ -339,7 +339,7 @@ public bool IsSameAs(VectorClock that)
if (ReferenceEquals(left, null))
return false;
- return left.IsConcurrentWith(right);
+ return !left.IsSameAs(right);
}
private static readonly (Node, long) CmpEndMarker = (Node.Create("endmarker"), Timestamp.EndMarker);
From 3b50bf40b2d55299214c54fb6efcf927cf0af0d1 Mon Sep 17 00:00:00 2001
From: Matt Kotsenas <51421+MattKotsenas@users.noreply.github.com>
Date: Wed, 25 Feb 2026 14:29:29 -0600
Subject: [PATCH 2/2] fix: VectorClock == and != operators handle null
correctly
Previously null == null returned false and something ==/!= null threw
NullReferenceException. Now follows standard C# equality pattern:
ReferenceEquals for both-null and either-null, then delegate to
IsSameAs. The != operator delegates to !(left == right) to guarantee
consistency.
---
.../Akka.Cluster.Tests/VectorClockSpec.cs | 22 +++++++++++++++++++
src/core/Akka.Cluster/VectorClock.cs | 9 ++++----
2 files changed, 26 insertions(+), 5 deletions(-)
diff --git a/src/core/Akka.Cluster.Tests/VectorClockSpec.cs b/src/core/Akka.Cluster.Tests/VectorClockSpec.cs
index e72a63c18bf..2e2af4d24d2 100644
--- a/src/core/Akka.Cluster.Tests/VectorClockSpec.cs
+++ b/src/core/Akka.Cluster.Tests/VectorClockSpec.cs
@@ -29,6 +29,28 @@ public void A_VectorClock_must_not_happen_before_itself()
(clock1 != clock2).Should().BeFalse();
}
+ [Fact]
+ public void A_VectorClock_must_handle_null_equality_correctly()
+ {
+ VectorClock nullClock = null;
+ var clock = VectorClock.Create();
+
+ // null == null should be true
+ (nullClock == null).Should().BeTrue();
+ (null == nullClock).Should().BeTrue();
+
+ // null != null should be false
+ (nullClock != null).Should().BeFalse();
+
+ // clock == null and null == clock should be false
+ (clock == null).Should().BeFalse();
+ (null == clock).Should().BeFalse();
+
+ // clock != null and null != clock should be true
+ (clock != null).Should().BeTrue();
+ (null != clock).Should().BeTrue();
+ }
+
[Fact]
public void A_VectorClock_must_pass_misc_comparison_test1()
{
diff --git a/src/core/Akka.Cluster/VectorClock.cs b/src/core/Akka.Cluster/VectorClock.cs
index f9965eaecb6..81108810398 100644
--- a/src/core/Akka.Cluster/VectorClock.cs
+++ b/src/core/Akka.Cluster/VectorClock.cs
@@ -322,7 +322,9 @@ public bool IsSameAs(VectorClock that)
/// true if both vector clocks are equal; otherwise false
public static bool operator ==(VectorClock left, VectorClock right)
{
- if (ReferenceEquals(left, null))
+ if (ReferenceEquals(left, right))
+ return true;
+ if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
return false;
return left.IsSameAs(right);
@@ -336,10 +338,7 @@ public bool IsSameAs(VectorClock that)
/// true if both vector clocks are not equal; otherwise false
public static bool operator !=(VectorClock left, VectorClock right)
{
- if (ReferenceEquals(left, null))
- return false;
-
- return !left.IsSameAs(right);
+ return !(left == right);
}
private static readonly (Node, long) CmpEndMarker = (Node.Create("endmarker"), Timestamp.EndMarker);