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);