From 9acaf00f04cf6e33958d00badf4001e010595b48 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Fri, 19 Aug 2022 03:15:30 +0700 Subject: [PATCH] Cluster singleton should consider Member AppVersion during hand over. (#6065) * Cluster singleton should consider Member AppVersion during hand over. * Add `akka.cluster.singleton.consider-app-version` HOCON setting as opt-in flag * Update API Verify list * Add MemberAgeOrdering spec * Change insertion order * Update API Verify list Co-authored-by: Aaron Stannard --- .../Singleton/ClusterSingletonRestart3Spec.cs | 135 ++++++++++++++++++ .../Singleton/MemberAgeOrderingSpec.cs | 89 ++++++++++++ .../Singleton/ClusterSingletonManager.cs | 4 +- .../ClusterSingletonManagerSettings.cs | 51 ++++++- .../Singleton/ClusterSingletonProxy.cs | 10 +- .../ClusterSingletonProxySettings.cs | 41 +++++- .../Singleton/ClusterSingletonSettings.cs | 40 ++++-- .../Singleton/MemberAgeOrdering.cs | 20 ++- .../Singleton/OldestChangedBuffer.cs | 13 +- .../Singleton/reference.conf | 5 + ...reAPISpec.ApproveCluster.Core.verified.txt | 1 + ...APISpec.ApproveCluster.DotNet.verified.txt | 1 + ...oreAPISpec.ApproveCluster.Net.verified.txt | 1 + ...Spec.ApproveClusterTools.Core.verified.txt | 11 +- ...ec.ApproveClusterTools.DotNet.verified.txt | 11 +- ...ISpec.ApproveClusterTools.Net.verified.txt | 11 +- .../Akka.Cluster/Properties/AssemblyInfo.cs | 1 + src/core/Akka/Util/AppVersion.cs | 8 +- 18 files changed, 406 insertions(+), 47 deletions(-) create mode 100644 src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/ClusterSingletonRestart3Spec.cs create mode 100644 src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/MemberAgeOrderingSpec.cs diff --git a/src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/ClusterSingletonRestart3Spec.cs b/src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/ClusterSingletonRestart3Spec.cs new file mode 100644 index 00000000000..f873845dd1e --- /dev/null +++ b/src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/ClusterSingletonRestart3Spec.cs @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Akka.Actor; +using Akka.Cluster.Tools.Singleton; +using Akka.Configuration; +using Akka.TestKit; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + +namespace Akka.Cluster.Tools.Tests.Singleton +{ + public class ClusterSingletonRestart3Spec : AkkaSpec + { + private readonly ActorSystem _sys1; + private readonly ActorSystem _sys2; + private readonly ActorSystem _sys3; + + public ClusterSingletonRestart3Spec(ITestOutputHelper output) : base(@" + akka.loglevel = DEBUG + akka.actor.provider = ""cluster"" + akka.cluster.app-version = ""1.0.0"" + akka.cluster.auto-down-unreachable-after = 2s + akka.cluster.singleton.min-number-of-hand-over-retries = 5 + akka.cluster.singleton.consider-app-version = true + akka.remote { + dot-netty.tcp { + hostname = ""127.0.0.1"" + port = 0 + } + }", output) + { + _sys1 = Sys; + _sys2 = ActorSystem.Create(Sys.Name, Sys.Settings.Config); + InitializeLogger(_sys2); + _sys3 = ActorSystem.Create(Sys.Name, ConfigurationFactory.ParseString("akka.cluster.app-version = \"1.0.2\"") + .WithFallback(Sys.Settings.Config)); + InitializeLogger(_sys3); + } + + public void Join(ActorSystem from, ActorSystem to) + { + from.ActorOf(ClusterSingletonManager.Props(Props.Create(() => new Singleton()), + PoisonPill.Instance, + ClusterSingletonManagerSettings.Create(from)), "echo"); + + + Within(TimeSpan.FromSeconds(45), () => + { + AwaitAssert(() => + { + Cluster.Get(from).Join(Cluster.Get(to).SelfAddress); + Cluster.Get(from).State.Members.Select(x => x.UniqueAddress).Should().Contain(Cluster.Get(from).SelfUniqueAddress); + Cluster.Get(from) + .State.Members.Select(x => x.Status) + .ToImmutableHashSet() + .Should() + .Equal(ImmutableHashSet.Empty.Add(MemberStatus.Up)); + }); + }); + } + + [Fact] + public void Singleton_should_consider_AppVersion_when_handing_over() + { + Join(_sys1, _sys1); + Join(_sys2, _sys1); + + var proxy2 = _sys2.ActorOf( + ClusterSingletonProxy.Props("user/echo", ClusterSingletonProxySettings.Create(_sys2)), "proxy2"); + + Within(TimeSpan.FromSeconds(5), () => + { + AwaitAssert(() => + { + var probe = CreateTestProbe(_sys2); + proxy2.Tell("poke", probe.Ref); + var singleton = probe.ExpectMsg(TimeSpan.FromSeconds(1)); + singleton.Should().Be(Cluster.Get(_sys1).SelfMember); + singleton.AppVersion.Version.Should().Be("1.0.0"); + }); + }); + + // A new node with higher AppVersion joins the cluster + Join(_sys3, _sys1); + + // Old node with the singleton instance left the cluster + Cluster.Get(_sys1).Leave(Cluster.Get(_sys1).SelfAddress); + + // let it stabilize + Task.Delay(TimeSpan.FromSeconds(5)).Wait(); + + Within(TimeSpan.FromSeconds(10), () => + { + AwaitAssert(() => + { + var probe = CreateTestProbe(_sys2); + proxy2.Tell("poke", probe.Ref); + + // note that _sys3 has a higher app-version, so the singleton should start there + var singleton = probe.ExpectMsg(TimeSpan.FromSeconds(1)); + singleton.Should().Be(Cluster.Get(_sys3).SelfMember); + singleton.AppVersion.Version.Should().Be("1.0.2"); + }); + }); + } + + protected override async Task AfterAllAsync() + { + await base.AfterAllAsync(); + await ShutdownAsync(_sys2); + await ShutdownAsync(_sys3); + } + + public class Singleton : ReceiveActor + { + public Singleton() + { + ReceiveAny(o => + { + Sender.Tell(Cluster.Get(Context.System).SelfMember); + }); + } + } + } +} diff --git a/src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/MemberAgeOrderingSpec.cs b/src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/MemberAgeOrderingSpec.cs new file mode 100644 index 00000000000..82d3d4d6949 --- /dev/null +++ b/src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/MemberAgeOrderingSpec.cs @@ -0,0 +1,89 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2022 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Akka.Actor; +using Akka.Cluster.Tools.Singleton; +using Akka.Util; +using FluentAssertions; +using Xunit; + +namespace Akka.Cluster.Tools.Tests.Singleton +{ + public class MemberAgeOrderingSpec + { + [Fact(DisplayName = "MemberAgeOrdering should sort based on UpNumber")] + public void SortByUpNumberTest() + { + var members = new SortedSet(MemberAgeOrdering.DescendingWithAppVersion) + { + Create(Address.Parse("akka://sys@darkstar:1112"), upNumber: 3), + Create(Address.Parse("akka://sys@darkstar:1113"), upNumber: 1), + Create(Address.Parse("akka://sys@darkstar:1111"), upNumber: 9), + }; + + var seq = members.ToList(); + seq.Count.Should().Be(3); + seq[0].Should().Be(Create(Address.Parse("akka://sys@darkstar:1113"), upNumber: 1)); + seq[1].Should().Be(Create(Address.Parse("akka://sys@darkstar:1112"), upNumber: 3)); + seq[2].Should().Be(Create(Address.Parse("akka://sys@darkstar:1111"), upNumber: 9)); + } + + [Fact(DisplayName = "MemberAgeOrdering should sort based on Address if UpNumber is the same")] + public void SortByAddressTest() + { + var members = new SortedSet(MemberAgeOrdering.DescendingWithAppVersion) + { + Create(Address.Parse("akka://sys@darkstar:1112"), upNumber: 1), + Create(Address.Parse("akka://sys@darkstar:1113"), upNumber: 1), + Create(Address.Parse("akka://sys@darkstar:1111"), upNumber: 1), + }; + + var seq = members.ToList(); + seq.Count.Should().Be(3); + seq[0].Should().Be(Create(Address.Parse("akka://sys@darkstar:1111"), upNumber: 1)); + seq[1].Should().Be(Create(Address.Parse("akka://sys@darkstar:1112"), upNumber: 1)); + seq[2].Should().Be(Create(Address.Parse("akka://sys@darkstar:1113"), upNumber: 1)); + } + + [Fact(DisplayName = "MemberAgeOrdering should prefer AppVersion over UpNumber")] + public void SortByAppVersionTest() + { + var members = new SortedSet(MemberAgeOrdering.DescendingWithAppVersion) + { + Create(Address.Parse("akka://sys@darkstar:1112"), upNumber: 3, appVersion: AppVersion.Create("1.0.0")), + Create(Address.Parse("akka://sys@darkstar:1113"), upNumber: 1, appVersion: AppVersion.Create("1.0.0")), + Create(Address.Parse("akka://sys@darkstar:1111"), upNumber: 2, appVersion: AppVersion.Create("1.0.0")), + Create(Address.Parse("akka://sys@darkstar:1114"), upNumber: 7, appVersion: AppVersion.Create("1.0.2")), + Create(Address.Parse("akka://sys@darkstar:1115"), upNumber: 8, appVersion: AppVersion.Create("1.0.2")), + Create(Address.Parse("akka://sys@darkstar:1116"), upNumber: 6, appVersion: AppVersion.Create("1.0.2")), + }; + + var seq = members.ToList(); + seq.Count.Should().Be(6); + seq[0].Should().Be(Create(Address.Parse("akka://sys@darkstar:1116"), upNumber: 6, appVersion: AppVersion.Create("1.0.2"))); + seq[1].Should().Be(Create(Address.Parse("akka://sys@darkstar:1114"), upNumber: 7, appVersion: AppVersion.Create("1.0.2"))); + seq[2].Should().Be(Create(Address.Parse("akka://sys@darkstar:1115"), upNumber: 8, appVersion: AppVersion.Create("1.0.2"))); + seq[3].Should().Be(Create(Address.Parse("akka://sys@darkstar:1113"), upNumber: 1, appVersion: AppVersion.Create("1.0.0"))); + seq[4].Should().Be(Create(Address.Parse("akka://sys@darkstar:1111"), upNumber: 2, appVersion: AppVersion.Create("1.0.0"))); + seq[5].Should().Be(Create(Address.Parse("akka://sys@darkstar:1112"), upNumber: 3, appVersion: AppVersion.Create("1.0.0"))); + } + + public static Member Create( + Address address, + MemberStatus status = MemberStatus.Up, + ImmutableHashSet roles = null, + int uid = 0, + int upNumber = 0, + AppVersion appVersion = null) + { + return Member.Create(new UniqueAddress(address, uid), upNumber, status, roles ?? ImmutableHashSet.Empty, appVersion ?? AppVersion.Zero); + } + } +} \ No newline at end of file diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonManager.cs b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonManager.cs index 2e85b93d4bb..ecc61837fa7 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonManager.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonManager.cs @@ -880,7 +880,9 @@ private void InitializeFSM() { case StartOldestChangedBuffer _: { - _oldestChangedBuffer = Context.ActorOf(Actor.Props.Create(_settings.Role).WithDispatcher(Context.Props.Dispatcher)); + _oldestChangedBuffer = Context.ActorOf( + Actor.Props.Create(() => new OldestChangedBuffer(_settings.Role, _settings.ConsiderAppVersion)) + .WithDispatcher(Context.Props.Dispatcher)); GetNextOldestChanged(); return Stay(); } diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonManagerSettings.cs b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonManagerSettings.cs index bd667c8e39b..f75c6d98d0c 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonManagerSettings.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonManagerSettings.cs @@ -57,7 +57,8 @@ public static ClusterSingletonManagerSettings Create(Config config) role: RoleOption(config.GetString("role")), removalMargin: TimeSpan.Zero, // defaults to ClusterSettings.DownRemovalMargin handOverRetryInterval: config.GetTimeSpan("hand-over-retry-interval"), - leaseSettings: lease); + leaseSettings: lease, + considerAppVersion: config.GetBoolean("consider-app-version")); } private static string RoleOption(string role) @@ -91,6 +92,14 @@ private static string RoleOption(string role) /// LeaseSettings for acquiring before creating the singleton actor /// public LeaseUsageSettings LeaseSettings { get; } + + + /// + /// Should be considered when the cluster singleton instance is being moved to another node. + /// When set to false, singleton instance will always be created on oldest member. + /// When set to true, singleton instance will be created on the oldest member with the highest number. + /// + public bool ConsiderAppVersion { get; } /// /// Creates a new instance of the . @@ -114,9 +123,19 @@ private static string RoleOption(string role) /// over has started or the previous oldest member is removed from the cluster /// (+ ). /// + /// + /// Should be considered when the cluster singleton instance is being moved to another node. + /// When set to false, singleton instance will always be created on oldest member. + /// When set to true, singleton instance will be created on the oldest member with the highest number. + /// /// TBD - public ClusterSingletonManagerSettings(string singletonName, string role, TimeSpan removalMargin, TimeSpan handOverRetryInterval) - : this(singletonName, role, removalMargin, handOverRetryInterval, null) + public ClusterSingletonManagerSettings( + string singletonName, + string role, + TimeSpan removalMargin, + TimeSpan handOverRetryInterval, + bool considerAppVersion) + : this(singletonName, role, removalMargin, handOverRetryInterval, null, considerAppVersion) { } @@ -143,8 +162,19 @@ public ClusterSingletonManagerSettings(string singletonName, string role, TimeSp /// (+ ). /// /// LeaseSettings for acquiring before creating the singleton actor + /// + /// Should be considered when the cluster singleton instance is being moved to another node. + /// When set to false, singleton instance will always be created on oldest member. + /// When set to true, singleton instance will be created on the oldest member with the highest number. + /// /// TBD - public ClusterSingletonManagerSettings(string singletonName, string role, TimeSpan removalMargin, TimeSpan handOverRetryInterval, LeaseUsageSettings leaseSettings) + public ClusterSingletonManagerSettings( + string singletonName, + string role, + TimeSpan removalMargin, + TimeSpan handOverRetryInterval, + LeaseUsageSettings leaseSettings, + bool considerAppVersion) { if (string.IsNullOrWhiteSpace(singletonName)) throw new ArgumentNullException(nameof(singletonName)); @@ -158,6 +188,7 @@ public ClusterSingletonManagerSettings(string singletonName, string role, TimeSp RemovalMargin = removalMargin; HandOverRetryInterval = handOverRetryInterval; LeaseSettings = leaseSettings; + ConsiderAppVersion = considerAppVersion; } /// @@ -210,15 +241,21 @@ public ClusterSingletonManagerSettings WithLeaseSettings(LeaseUsageSettings leas return Copy(leaseSettings: leaseSettings); } - private ClusterSingletonManagerSettings Copy(string singletonName = null, Option role = default, TimeSpan? removalMargin = null, - TimeSpan? handOverRetryInterval = null, Option leaseSettings = default) + private ClusterSingletonManagerSettings Copy( + string singletonName = null, + Option role = default, + TimeSpan? removalMargin = null, + TimeSpan? handOverRetryInterval = null, + Option leaseSettings = default, + bool? considerAppVersion = null) { return new ClusterSingletonManagerSettings( singletonName: singletonName ?? SingletonName, role: role.HasValue ? role.Value : Role, removalMargin: removalMargin ?? RemovalMargin, handOverRetryInterval: handOverRetryInterval ?? HandOverRetryInterval, - leaseSettings: leaseSettings.HasValue ? leaseSettings.Value : LeaseSettings + leaseSettings: leaseSettings.HasValue ? leaseSettings.Value : LeaseSettings, + considerAppVersion: considerAppVersion ?? ConsiderAppVersion ); } } diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxy.cs b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxy.cs index 7f84207355f..d017393369c 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxy.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxy.cs @@ -76,6 +76,7 @@ public static Props Props(string singletonManagerPath, ClusterSingletonProxySett .WithDeploy(Deploy.Local); } + private readonly MemberAgeOrdering _memberAgeComparer; private readonly ClusterSingletonProxySettings _settings; private readonly Cluster _cluster = Cluster.Get(Context.System); private readonly Queue> _buffer = new Queue>(); // queue seems to fit better @@ -84,7 +85,7 @@ public static Props Props(string singletonManagerPath, ClusterSingletonProxySett private string _identityId; private IActorRef _singleton = null; private ICancelable _identityTimer = null; - private ImmutableSortedSet _membersByAge = ImmutableSortedSet.Empty.WithComparer(MemberAgeOrdering.Descending); + private ImmutableSortedSet _membersByAge; private ILoggingAdapter _log; /// @@ -98,6 +99,11 @@ public ClusterSingletonProxy(string singletonManagerPath, ClusterSingletonProxyS _singletonPath = (singletonManagerPath + "/" + settings.SingletonName).Split('/'); _identityId = CreateIdentifyId(_identityCounter); + _memberAgeComparer = settings.ConsiderAppVersion + ? MemberAgeOrdering.DescendingWithAppVersion + : MemberAgeOrdering.Descending; + _membersByAge = ImmutableSortedSet.Empty.WithComparer(_memberAgeComparer); + Receive(s => HandleInitial(s)); Receive(m => Add(m.Member)); Receive(m => Remove(m.Member)); @@ -197,7 +203,7 @@ private void HandleInitial(ClusterEvent.CurrentClusterState state) TrackChanges(() => _membersByAge = state.Members .Where(m => m.Status == MemberStatus.Up && MatchingRole(m)) - .ToImmutableSortedSet(MemberAgeOrdering.Descending)); + .ToImmutableSortedSet(_memberAgeComparer)); } // Discard old singleton ActorRef and send a periodic message to self to identify the singleton. diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxySettings.cs b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxySettings.cs index b055a01cf89..05a4571f23e 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxySettings.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxySettings.cs @@ -32,7 +32,9 @@ public static ClusterSingletonProxySettings Create(ActorSystem system) if (config.IsNullOrEmpty()) throw ConfigurationException.NullOrEmptyConfig("akka.cluster.singleton-proxy"); - return Create(config); + var considerAppVersion = + system.Settings.Config.GetBoolean("akka.cluster.singleton.consider-app-version", false); + return Create(config, considerAppVersion); } /// @@ -40,8 +42,9 @@ public static ClusterSingletonProxySettings Create(ActorSystem system) /// the default configuration `akka.cluster.singleton-proxy`. /// /// TBD + /// TBD /// TBD - public static ClusterSingletonProxySettings Create(Config config) + public static ClusterSingletonProxySettings Create(Config config, bool considerAppVersion) { if (config.IsNullOrEmpty()) throw ConfigurationException.NullOrEmptyConfig(); @@ -53,7 +56,8 @@ public static ClusterSingletonProxySettings Create(Config config) singletonName: config.GetString("singleton-name"), role: role, singletonIdentificationInterval: config.GetTimeSpan("singleton-identification-interval"), - bufferSize: config.GetInt("buffer-size", 0)); + bufferSize: config.GetInt("buffer-size", 0), + considerAppVersion: considerAppVersion); } /// @@ -75,6 +79,13 @@ public static ClusterSingletonProxySettings Create(Config config) /// If the location of the singleton is unknown the proxy will buffer this number of messages and deliver them when the singleton is identified. /// public int BufferSize { get; } + + /// + /// Should be considered when the cluster singleton instance is being moved to another node. + /// When set to false, singleton instance will always be created on oldest member. + /// When set to true, singleton instance will be created on the oldest member with the highest number. + /// + public bool ConsiderAppVersion { get; } /// /// Creates new instance of the . @@ -88,12 +99,22 @@ public static ClusterSingletonProxySettings Create(Config config) /// are sent via the proxy. Use 0 to disable buffering, i.e.messages will be dropped immediately if the location /// of the singleton is unknown. /// + /// + /// Should be considered when the cluster singleton instance is being moved to another node. + /// When set to false, singleton instance will always be created on oldest member. + /// When set to true, singleton instance will be created on the oldest member with the highest number. + /// /// /// This exception is thrown when either the specified /// or is less than or equal to zero. /// /// - public ClusterSingletonProxySettings(string singletonName, string role, TimeSpan singletonIdentificationInterval, int bufferSize) + public ClusterSingletonProxySettings( + string singletonName, + string role, + TimeSpan singletonIdentificationInterval, + int bufferSize, + bool considerAppVersion) { if (string.IsNullOrEmpty(singletonName)) throw new ArgumentNullException(nameof(singletonName)); @@ -106,6 +127,7 @@ public ClusterSingletonProxySettings(string singletonName, string role, TimeSpan Role = role; SingletonIdentificationInterval = singletonIdentificationInterval; BufferSize = bufferSize; + ConsiderAppVersion = considerAppVersion; } /// @@ -176,14 +198,19 @@ public ClusterSingletonProxySettings WithBufferSize(int bufferSize) return Copy(bufferSize: bufferSize); } - private ClusterSingletonProxySettings Copy(string singletonName = null, Option role = default, - TimeSpan? singletonIdentificationInterval = null, int? bufferSize = null) + private ClusterSingletonProxySettings Copy( + string singletonName = null, + Option role = default, + TimeSpan? singletonIdentificationInterval = null, + int? bufferSize = null, + bool? considerAppVersion = null) { return new ClusterSingletonProxySettings( singletonName: singletonName ?? SingletonName, role: role.HasValue ? role.Value : Role, singletonIdentificationInterval: singletonIdentificationInterval ?? SingletonIdentificationInterval, - bufferSize: bufferSize ?? BufferSize); + bufferSize: bufferSize ?? BufferSize, + considerAppVersion: considerAppVersion ?? ConsiderAppVersion); } } } diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonSettings.cs b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonSettings.cs index 6a2cdeaa74b..cdd74ce8598 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonSettings.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonSettings.cs @@ -56,6 +56,13 @@ public class ClusterSingletonSettings : INoSerializationVerificationNeeded /// LeaseSettings for acquiring before creating the singleton actor. /// public LeaseUsageSettings LeaseSettings { get; } + + /// + /// Should be considered when the cluster singleton instance is being moved to another node. + /// When set to false, singleton instance will always be created on oldest member. + /// When set to true, singleton instance will be created on the oldest member with the highest number. + /// + public bool ConsiderAppVersion { get; } /// /// Create settings from the default configuration `akka.cluster`. @@ -72,7 +79,7 @@ public static ClusterSingletonSettings Create(ActorSystem system) public static ClusterSingletonSettings Create(Config config) { var mgrSettings = ClusterSingletonManagerSettings.Create(config.GetConfig("singleton")); - var proxySettings = ClusterSingletonProxySettings.Create(config.GetConfig("singleton-proxy")); + var proxySettings = ClusterSingletonProxySettings.Create(config.GetConfig("singleton-proxy"), false); return new ClusterSingletonSettings( mgrSettings.Role, @@ -80,10 +87,18 @@ public static ClusterSingletonSettings Create(Config config) mgrSettings.RemovalMargin, mgrSettings.HandOverRetryInterval, proxySettings.BufferSize, - mgrSettings.LeaseSettings); + mgrSettings.LeaseSettings, + mgrSettings.ConsiderAppVersion); } - private ClusterSingletonSettings(string role, TimeSpan singletonIdentificationInterval, TimeSpan removalMargin, TimeSpan handOverRetryInterval, int bufferSize, LeaseUsageSettings leaseSettings) + private ClusterSingletonSettings( + string role, + TimeSpan singletonIdentificationInterval, + TimeSpan removalMargin, + TimeSpan handOverRetryInterval, + int bufferSize, + LeaseUsageSettings leaseSettings, + bool considerAppVersion) { if (singletonIdentificationInterval == TimeSpan.Zero) throw new ArgumentException("singletonIdentificationInterval must be positive", nameof(singletonIdentificationInterval)); @@ -103,6 +118,7 @@ private ClusterSingletonSettings(string role, TimeSpan singletonIdentificationIn HandOverRetryInterval = handOverRetryInterval; BufferSize = bufferSize; LeaseSettings = leaseSettings; + ConsiderAppVersion = considerAppVersion; } public ClusterSingletonSettings WithRole(string role) => Copy(role: role); @@ -113,7 +129,14 @@ private ClusterSingletonSettings(string role, TimeSpan singletonIdentificationIn public ClusterSingletonSettings WithLeaseSettings(LeaseUsageSettings leaseSettings) => Copy(leaseSettings: leaseSettings); - private ClusterSingletonSettings Copy(Option role = default, TimeSpan? singletonIdentificationInterval = null, TimeSpan? removalMargin = null, TimeSpan? handOverRetryInterval = null, int? bufferSize = null, Option leaseSettings = default) + private ClusterSingletonSettings Copy( + Option role = default, + TimeSpan? singletonIdentificationInterval = null, + TimeSpan? removalMargin = null, + TimeSpan? handOverRetryInterval = null, + int? bufferSize = null, + Option leaseSettings = default, + bool? considerAppVersion = null) { return new ClusterSingletonSettings( role: role.HasValue ? role.Value : Role, @@ -121,21 +144,22 @@ private ClusterSingletonSettings Copy(Option role = default, TimeSpan? s removalMargin: removalMargin ?? RemovalMargin, handOverRetryInterval: handOverRetryInterval ?? HandOverRetryInterval, bufferSize: bufferSize ?? BufferSize, - leaseSettings: leaseSettings.HasValue ? leaseSettings.Value : LeaseSettings); + leaseSettings: leaseSettings.HasValue ? leaseSettings.Value : LeaseSettings, + considerAppVersion: considerAppVersion ?? ConsiderAppVersion); } [InternalApi] internal ClusterSingletonManagerSettings ToManagerSettings(string singletonName) => - new ClusterSingletonManagerSettings(singletonName, Role, RemovalMargin, HandOverRetryInterval, LeaseSettings); + new ClusterSingletonManagerSettings(singletonName, Role, RemovalMargin, HandOverRetryInterval, LeaseSettings, ConsiderAppVersion); [InternalApi] internal ClusterSingletonProxySettings ToProxySettings(string singletonName) => - new ClusterSingletonProxySettings(singletonName, Role, SingletonIdentificationInterval, BufferSize); + new ClusterSingletonProxySettings(singletonName, Role, SingletonIdentificationInterval, BufferSize, ConsiderAppVersion); [InternalApi] internal bool ShouldRunManager(Cluster cluster) => string.IsNullOrEmpty(Role) || cluster.SelfMember.Roles.Contains(Role); public override string ToString() => - $"ClusterSingletonSettings({Role}, {SingletonIdentificationInterval}, {RemovalMargin}, {HandOverRetryInterval}, {BufferSize}, {LeaseSettings})"; + $"ClusterSingletonSettings({Role}, {SingletonIdentificationInterval}, {RemovalMargin}, {HandOverRetryInterval}, {BufferSize}, {LeaseSettings}, {ConsiderAppVersion})"; } } diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/MemberAgeOrdering.cs b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/MemberAgeOrdering.cs index bac6aeb6baa..3d0e05c9637 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/MemberAgeOrdering.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/MemberAgeOrdering.cs @@ -15,15 +15,25 @@ namespace Akka.Cluster.Tools.Singleton internal sealed class MemberAgeOrdering : IComparer { private readonly bool _ascending; + private readonly bool _considerAppVersion; - private MemberAgeOrdering(bool ascending) + private MemberAgeOrdering(bool ascending, bool considerAppVersion) { _ascending = ascending; + _considerAppVersion = considerAppVersion; } /// public int Compare(Member x, Member y) { + if (_considerAppVersion) + { + // prefer nodes with the highest app version, even if they're younger + var appVersionDiff = x.AppVersion.CompareTo(y.AppVersion); + if (appVersionDiff != 0) + return _ascending ? appVersionDiff : appVersionDiff * -1; + } + if (x.Equals(y)) return 0; return x.IsOlderThan(y) ? (_ascending ? 1 : -1) @@ -33,11 +43,15 @@ public int Compare(Member x, Member y) /// /// TBD /// - public static readonly MemberAgeOrdering Ascending = new MemberAgeOrdering(true); + public static readonly MemberAgeOrdering Ascending = new MemberAgeOrdering(true, false); + + public static readonly MemberAgeOrdering AscendingWithAppVersion = new MemberAgeOrdering(true, true); /// /// TBD /// - public static readonly MemberAgeOrdering Descending = new MemberAgeOrdering(false); + public static readonly MemberAgeOrdering Descending = new MemberAgeOrdering(false, false); + + public static readonly MemberAgeOrdering DescendingWithAppVersion = new MemberAgeOrdering(false, true); } } diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/OldestChangedBuffer.cs b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/OldestChangedBuffer.cs index 391de836287..94f2dc9dd52 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/OldestChangedBuffer.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/OldestChangedBuffer.cs @@ -92,15 +92,22 @@ public OldestChanged(UniqueAddress oldest) #endregion + private readonly MemberAgeOrdering _memberAgeComparer; private readonly CoordinatedShutdown _coordShutdown = CoordinatedShutdown.Get(Context.System); /// /// Creates a new instance of the . /// /// The role for which we're watching for membership changes. - public OldestChangedBuffer(string role) + /// Should cluster AppVersion be considered when sorting member age + public OldestChangedBuffer(string role, bool considerAppVersion) { _role = role; + _memberAgeComparer = considerAppVersion + ? MemberAgeOrdering.DescendingWithAppVersion + : MemberAgeOrdering.Descending; + _membersByAge = ImmutableSortedSet.Empty.WithComparer(_memberAgeComparer); + SetupCoordinatedShutdown(); } @@ -130,7 +137,7 @@ private void SetupCoordinatedShutdown() } private readonly string _role; - private ImmutableSortedSet _membersByAge = ImmutableSortedSet.Empty.WithComparer(MemberAgeOrdering.Descending); + private ImmutableSortedSet _membersByAge; private ImmutableQueue _changes = ImmutableQueue.Empty; private readonly Cluster _cluster = Cluster.Get(Context.System); @@ -156,7 +163,7 @@ private void HandleInitial(ClusterEvent.CurrentClusterState state) // all members except Joining and WeaklyUp _membersByAge = state.Members .Where(m => m.UpNumber != int.MaxValue && MatchingRole(m)) - .ToImmutableSortedSet(MemberAgeOrdering.Descending); + .ToImmutableSortedSet(_memberAgeComparer); // If there is some removal in progress of an older node it's not safe to immediately become oldest, // removal of younger nodes doesn't matter. Note that it can also be started via restart after diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/reference.conf b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/reference.conf index 7cc6aec748f..ea374bbffb5 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/reference.conf +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/reference.conf @@ -42,6 +42,11 @@ akka.cluster.singleton { # The interval between retries for acquiring the lease lease-retry-interval = 5s + + # Should akka.cluster.app-version be considered when the cluster singleton instance is being moved to another node + # When set to false, singleton instance will always be created on oldest member + # When set to true, singleton instance will be created on the oldest node with the highest member app-version number + consider-app-version = false } # //#singleton-config diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Core.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Core.verified.txt index be4c326589f..b12b0568fb1 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Core.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Core.verified.txt @@ -9,6 +9,7 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tests.MultiNode")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tests.Performance")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tools")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tools.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tools.Tests.MultiNode")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.DistributedData")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Management.Cluster.Http")] diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.DotNet.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.DotNet.verified.txt index 435b9d20d83..d9030477ef3 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.DotNet.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.DotNet.verified.txt @@ -9,6 +9,7 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tests.MultiNode")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tests.Performance")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tools")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tools.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tools.Tests.MultiNode")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.DistributedData")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Management.Cluster.Http")] diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Net.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Net.verified.txt index be4c326589f..b12b0568fb1 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Net.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Net.verified.txt @@ -9,6 +9,7 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tests.MultiNode")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tests.Performance")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tools")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tools.Tests")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Tools.Tests.MultiNode")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.DistributedData")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Management.Cluster.Http")] diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.Core.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.Core.verified.txt index fdcbd750973..0657621c73f 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.Core.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.Core.verified.txt @@ -356,8 +356,9 @@ namespace Akka.Cluster.Tools.Singleton } public sealed class ClusterSingletonManagerSettings : Akka.Actor.INoSerializationVerificationNeeded { - public ClusterSingletonManagerSettings(string singletonName, string role, System.TimeSpan removalMargin, System.TimeSpan handOverRetryInterval) { } - public ClusterSingletonManagerSettings(string singletonName, string role, System.TimeSpan removalMargin, System.TimeSpan handOverRetryInterval, Akka.Coordination.LeaseUsageSettings leaseSettings) { } + public ClusterSingletonManagerSettings(string singletonName, string role, System.TimeSpan removalMargin, System.TimeSpan handOverRetryInterval, bool considerAppVersion) { } + public ClusterSingletonManagerSettings(string singletonName, string role, System.TimeSpan removalMargin, System.TimeSpan handOverRetryInterval, Akka.Coordination.LeaseUsageSettings leaseSettings, bool considerAppVersion) { } + public bool ConsiderAppVersion { get; } public System.TimeSpan HandOverRetryInterval { get; } public Akka.Coordination.LeaseUsageSettings LeaseSettings { get; } public System.TimeSpan RemovalMargin { get; } @@ -386,13 +387,14 @@ namespace Akka.Cluster.Tools.Singleton } public sealed class ClusterSingletonProxySettings : Akka.Actor.INoSerializationVerificationNeeded { - public ClusterSingletonProxySettings(string singletonName, string role, System.TimeSpan singletonIdentificationInterval, int bufferSize) { } + public ClusterSingletonProxySettings(string singletonName, string role, System.TimeSpan singletonIdentificationInterval, int bufferSize, bool considerAppVersion) { } public int BufferSize { get; } + public bool ConsiderAppVersion { get; } public string Role { get; } public System.TimeSpan SingletonIdentificationInterval { get; } public string SingletonName { get; } public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings Create(Akka.Actor.ActorSystem system) { } - public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings Create(Akka.Configuration.Config config) { } + public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings Create(Akka.Configuration.Config config, bool considerAppVersion) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithBufferSize(int bufferSize) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithRole(string role) { } [System.ObsoleteAttribute("For compatibility reasons only. Use method with TimeSpan parameter instead")] @@ -403,6 +405,7 @@ namespace Akka.Cluster.Tools.Singleton public class ClusterSingletonSettings : Akka.Actor.INoSerializationVerificationNeeded { public int BufferSize { get; } + public bool ConsiderAppVersion { get; } public System.TimeSpan HandOverRetryInterval { get; } public Akka.Coordination.LeaseUsageSettings LeaseSettings { get; } public System.TimeSpan RemovalMargin { get; } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.DotNet.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.DotNet.verified.txt index 8f662778101..f377d456db0 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.DotNet.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.DotNet.verified.txt @@ -356,8 +356,9 @@ namespace Akka.Cluster.Tools.Singleton } public sealed class ClusterSingletonManagerSettings : Akka.Actor.INoSerializationVerificationNeeded { - public ClusterSingletonManagerSettings(string singletonName, string role, System.TimeSpan removalMargin, System.TimeSpan handOverRetryInterval) { } - public ClusterSingletonManagerSettings(string singletonName, string role, System.TimeSpan removalMargin, System.TimeSpan handOverRetryInterval, Akka.Coordination.LeaseUsageSettings leaseSettings) { } + public ClusterSingletonManagerSettings(string singletonName, string role, System.TimeSpan removalMargin, System.TimeSpan handOverRetryInterval, bool considerAppVersion) { } + public ClusterSingletonManagerSettings(string singletonName, string role, System.TimeSpan removalMargin, System.TimeSpan handOverRetryInterval, Akka.Coordination.LeaseUsageSettings leaseSettings, bool considerAppVersion) { } + public bool ConsiderAppVersion { get; } public System.TimeSpan HandOverRetryInterval { get; } public Akka.Coordination.LeaseUsageSettings LeaseSettings { get; } public System.TimeSpan RemovalMargin { get; } @@ -386,13 +387,14 @@ namespace Akka.Cluster.Tools.Singleton } public sealed class ClusterSingletonProxySettings : Akka.Actor.INoSerializationVerificationNeeded { - public ClusterSingletonProxySettings(string singletonName, string role, System.TimeSpan singletonIdentificationInterval, int bufferSize) { } + public ClusterSingletonProxySettings(string singletonName, string role, System.TimeSpan singletonIdentificationInterval, int bufferSize, bool considerAppVersion) { } public int BufferSize { get; } + public bool ConsiderAppVersion { get; } public string Role { get; } public System.TimeSpan SingletonIdentificationInterval { get; } public string SingletonName { get; } public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings Create(Akka.Actor.ActorSystem system) { } - public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings Create(Akka.Configuration.Config config) { } + public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings Create(Akka.Configuration.Config config, bool considerAppVersion) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithBufferSize(int bufferSize) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithRole(string role) { } [System.ObsoleteAttribute("For compatibility reasons only. Use method with TimeSpan parameter instead")] @@ -403,6 +405,7 @@ namespace Akka.Cluster.Tools.Singleton public class ClusterSingletonSettings : Akka.Actor.INoSerializationVerificationNeeded { public int BufferSize { get; } + public bool ConsiderAppVersion { get; } public System.TimeSpan HandOverRetryInterval { get; } public Akka.Coordination.LeaseUsageSettings LeaseSettings { get; } public System.TimeSpan RemovalMargin { get; } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.Net.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.Net.verified.txt index fdcbd750973..0657621c73f 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.Net.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.Net.verified.txt @@ -356,8 +356,9 @@ namespace Akka.Cluster.Tools.Singleton } public sealed class ClusterSingletonManagerSettings : Akka.Actor.INoSerializationVerificationNeeded { - public ClusterSingletonManagerSettings(string singletonName, string role, System.TimeSpan removalMargin, System.TimeSpan handOverRetryInterval) { } - public ClusterSingletonManagerSettings(string singletonName, string role, System.TimeSpan removalMargin, System.TimeSpan handOverRetryInterval, Akka.Coordination.LeaseUsageSettings leaseSettings) { } + public ClusterSingletonManagerSettings(string singletonName, string role, System.TimeSpan removalMargin, System.TimeSpan handOverRetryInterval, bool considerAppVersion) { } + public ClusterSingletonManagerSettings(string singletonName, string role, System.TimeSpan removalMargin, System.TimeSpan handOverRetryInterval, Akka.Coordination.LeaseUsageSettings leaseSettings, bool considerAppVersion) { } + public bool ConsiderAppVersion { get; } public System.TimeSpan HandOverRetryInterval { get; } public Akka.Coordination.LeaseUsageSettings LeaseSettings { get; } public System.TimeSpan RemovalMargin { get; } @@ -386,13 +387,14 @@ namespace Akka.Cluster.Tools.Singleton } public sealed class ClusterSingletonProxySettings : Akka.Actor.INoSerializationVerificationNeeded { - public ClusterSingletonProxySettings(string singletonName, string role, System.TimeSpan singletonIdentificationInterval, int bufferSize) { } + public ClusterSingletonProxySettings(string singletonName, string role, System.TimeSpan singletonIdentificationInterval, int bufferSize, bool considerAppVersion) { } public int BufferSize { get; } + public bool ConsiderAppVersion { get; } public string Role { get; } public System.TimeSpan SingletonIdentificationInterval { get; } public string SingletonName { get; } public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings Create(Akka.Actor.ActorSystem system) { } - public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings Create(Akka.Configuration.Config config) { } + public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings Create(Akka.Configuration.Config config, bool considerAppVersion) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithBufferSize(int bufferSize) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithRole(string role) { } [System.ObsoleteAttribute("For compatibility reasons only. Use method with TimeSpan parameter instead")] @@ -403,6 +405,7 @@ namespace Akka.Cluster.Tools.Singleton public class ClusterSingletonSettings : Akka.Actor.INoSerializationVerificationNeeded { public int BufferSize { get; } + public bool ConsiderAppVersion { get; } public System.TimeSpan HandOverRetryInterval { get; } public Akka.Coordination.LeaseUsageSettings LeaseSettings { get; } public System.TimeSpan RemovalMargin { get; } diff --git a/src/core/Akka.Cluster/Properties/AssemblyInfo.cs b/src/core/Akka.Cluster/Properties/AssemblyInfo.cs index dfa9b35f7e6..e2b7339926d 100644 --- a/src/core/Akka.Cluster/Properties/AssemblyInfo.cs +++ b/src/core/Akka.Cluster/Properties/AssemblyInfo.cs @@ -16,6 +16,7 @@ [assembly: InternalsVisibleTo("Akka.Cluster.Tests.Performance")] [assembly: InternalsVisibleTo("Akka.Cluster.TestKit")] [assembly: InternalsVisibleTo("Akka.Cluster.Tools")] +[assembly: InternalsVisibleTo("Akka.Cluster.Tools.Tests")] [assembly: InternalsVisibleTo("Akka.Cluster.Tools.Tests.MultiNode")] [assembly: InternalsVisibleTo("Akka.Cluster.Sharding")] [assembly: InternalsVisibleTo("Akka.Cluster.Sharding.Tests")] diff --git a/src/core/Akka/Util/AppVersion.cs b/src/core/Akka/Util/AppVersion.cs index 26dbefdc961..03494973c10 100644 --- a/src/core/Akka/Util/AppVersion.cs +++ b/src/core/Akka/Util/AppVersion.cs @@ -208,7 +208,7 @@ private AppVersion Parse() public int CompareTo(AppVersion other) { - if (Version == other.Version) // String equals without requiring parse + if (string.Equals(Version, other.Version, StringComparison.Ordinal)) // String equals without requiring parse return 0; else { @@ -232,7 +232,7 @@ public int CompareTo(AppVersion other) if (other._rest == "" && _rest != "") diff = -1; else - diff = _rest.CompareTo(other._rest); + diff = string.Compare(_rest, other._rest, StringComparison.Ordinal); } } } @@ -243,12 +243,12 @@ public int CompareTo(AppVersion other) public bool Equals(AppVersion other) { - return other != null && Version == other.Version; + return other != null && string.Equals(Version, other.Version, StringComparison.Ordinal); } public override bool Equals(object obj) { - return base.Equals(obj as AppVersion); + return obj is AppVersion av && Equals(av); } public static bool operator ==(AppVersion first, AppVersion second)