diff --git a/src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/ClusterSingletonProxySpec.cs b/src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/ClusterSingletonProxySpec.cs index ac38de9574f..b0935f41d3a 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/ClusterSingletonProxySpec.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools.Tests/Singleton/ClusterSingletonProxySpec.cs @@ -6,6 +6,7 @@ //----------------------------------------------------------------------- using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Akka.Actor; @@ -14,11 +15,18 @@ using Akka.Event; using Akka.TestKit; using Xunit; +using FluentAssertions; +using FluentAssertions.Extensions; +using Xunit.Abstractions; namespace Akka.Cluster.Tools.Tests.Singleton { public class ClusterSingletonProxySpec : TestKit.Xunit2.TestKit { + public ClusterSingletonProxySpec(ITestOutputHelper output): base(output: output) + { + } + [Fact] public void ClusterSingletonProxy_must_correctly_identify_the_singleton() { @@ -67,12 +75,177 @@ await AwaitConditionAsync( } } + [Fact(DisplayName = "ClusterSingletonProxy should detect if its associated singleton failed to start after a period")] + public async Task ClusterSingletonProxySingletonTimeoutTest() + { + ActorSys seed = null; + ActorSys testSystem = null; + + try + { + seed = new ActorSys(output: Output); + seed.Cluster.Join(seed.Cluster.SelfAddress); + + // singleton proxy is waiting for a singleton in a non-existent role + testSystem = new ActorSys( + config: """ + akka.cluster.singleton-proxy { + role = "non-existent" + log-singleton-identification-failure = true + singleton-identification-failure-period = 500ms + } + """, + output: Output); + + testSystem.IgnoreMessages(); + testSystem.Sys.EventStream.Subscribe(testSystem + .TestActor); + testSystem.Cluster.Join(seed.Cluster.SelfAddress); + await AwaitConditionAsync(() => + Task.FromResult(testSystem.Cluster.State.Members.Count(m => m.Status == MemberStatus.Up) == 2)); + testSystem.IgnoreNoMessages(); + + // proxy will emit IdentifySingletonTimedOut event locally if it could not find its associated singleton + // within the detection period + await AssertTimeoutFired(); + + // proxy will continue to emit IdentifySingletonTimedOut event locally if it could not find its + // associated singleton within the detection period + await AssertTimeoutFired(); + + return; + async Task AssertTimeoutFired() + { + var msg = await testSystem.ExpectMsgAsync(1.Seconds()); + msg.SingletonName.Should().Be("singleton"); + msg.Role.Should().Be("non-existent"); + } + } + finally + { + var tasks = new List(); + + if(seed is not null) + tasks.Add(seed.Sys.Terminate()); + if(testSystem is not null) + tasks.Add(testSystem.Sys.Terminate()); + + if(tasks.Any()) + await Task.WhenAll(tasks); + } + } + + [Fact(DisplayName = "ClusterSingletonProxy should not start singleton identify detection if a singleton reference already found")] + public async Task ClusterSingletonProxySingletonTimeoutTest2() + { + const string seedConfig = """ + akka.cluster { + roles = [seed] # only start singletons on seed role + min-nr-of-members = 1 + singleton.role = seed # only start singletons on seed role + singleton-proxy.role = seed # only start singletons on seed role + } + """; + + ActorSys seed = null; + ActorSys seed2 = null; + ActorSys testSystem = null; + + try + { + seed = new ActorSys(config: seedConfig, output: Output); + seed.Cluster.Join(seed.Cluster.SelfAddress); + + // need to make sure that cluster member age is correct. seed node should be oldest. + await AwaitConditionAsync( + () => Task.FromResult(Cluster.Get(seed.Sys).State.Members.Count(m => m.Status == MemberStatus.Up) == 1), + TimeSpan.FromSeconds(30)); + + seed2 = new ActorSys(config: seedConfig, output: Output); + seed2.Cluster.Join(seed.Cluster.SelfAddress); + + // singleton proxy is waiting for a singleton in seed role + testSystem = new ActorSys( + config: """ + akka.cluster { + roles = [proxy] + singleton.role = seed # only start singletons on seed role + singleton-proxy { + role = seed # only start singletons on seed role + log-singleton-identification-failure = true + singleton-identification-failure-period = 500ms + } + } + """, + startSingleton: false, + output: Output); + + testSystem.Sys.EventStream.Subscribe(testSystem.TestActor); + testSystem.Cluster.Join(seed.Cluster.SelfAddress); + + await AwaitAssertAsync(async () => + { + var result = await testSystem.ExpectMsgAsync(); + result.Result.Should().Be(ClusterSingletonProxy.IdentifyResult.Success); + }); + + testSystem.TestProxy("hello"); + + // timeout event should not fire + await testSystem.ExpectNoMsgAsync(1.Seconds()); + + // Second seed node left the cluster, no timeout should be fired because singleton is homed in the first seed + await seed2.Sys.Terminate(); + + // wait until MemberRemoved is triggered + await AwaitConditionAsync( + () => Task.FromResult(Cluster.Get(seed.Sys).State.Members.Count(m => m.Status == MemberStatus.Up) == 2), + TimeSpan.FromSeconds(30)); + + // timeout event should not fire + await testSystem.ExpectNoMsgAsync(1.Seconds()); + + // First seed node which homed the singleton left the cluster + await seed.Sys.Terminate(); + + await AwaitAssertAsync(async () => + { + var result = await testSystem.ExpectMsgAsync(); + result.Result.Should().Be(ClusterSingletonProxy.IdentifyResult.Timeout); + }, + TimeSpan.FromSeconds(30)); + + // Proxy will emit IdentifySingletonTimedOut event locally because it lost the singleton reference + // and no nodes are eligible to home the singleton + await testSystem.ExpectMsgAsync(3.Seconds()); + } + finally + { + var tasks = new List(); + + if(seed is not null) + tasks.Add(seed.Sys.Terminate()); + if(seed2 is not null) + tasks.Add(seed2.Sys.Terminate()); + if(testSystem is not null) + tasks.Add(testSystem.Sys.Terminate()); + + if(tasks.Any()) + await Task.WhenAll(tasks); + } + } + private class ActorSys : TestKit.Xunit2.TestKit { public Cluster Cluster { get; } - public ActorSys(string name = "ClusterSingletonProxySystem", Address joinTo = null, int bufferSize = 1000) - : base(ActorSystem.Create(name, ConfigurationFactory.ParseString(_cfg).WithFallback(TestKit.Configs.TestConfigs.DefaultConfig))) + public ActorSys(string name = "ClusterSingletonProxySystem", Address joinTo = null, int bufferSize = 1000, string config = null, bool startSingleton = true, ITestOutputHelper output = null) + : base(ActorSystem.Create( + name: name, + config: config is null + ? ConfigurationFactory.ParseString(_cfg).WithFallback(DefaultConfig) + : ConfigurationFactory.ParseString(config).WithFallback(_cfg).WithFallback(DefaultConfig)), + output: output) { Cluster = Cluster.Get(Sys); if (joinTo != null) @@ -80,12 +253,15 @@ public ActorSys(string name = "ClusterSingletonProxySystem", Address joinTo = nu Cluster.Join(joinTo); } - Cluster.RegisterOnMemberUp(() => + if (startSingleton) { - Sys.ActorOf(ClusterSingletonManager.Props(Props.Create(() => new Singleton()), PoisonPill.Instance, - ClusterSingletonManagerSettings.Create(Sys) - .WithRemovalMargin(TimeSpan.FromSeconds(5))), "singletonmanager"); - }); + Cluster.RegisterOnMemberUp(() => + { + Sys.ActorOf(ClusterSingletonManager.Props(Props.Create(() => new Singleton()), PoisonPill.Instance, + ClusterSingletonManagerSettings.Create(Sys) + .WithRemovalMargin(TimeSpan.FromSeconds(5))), "singletonmanager"); + }); + } Proxy = Sys.ActorOf( diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxy.cs b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxy.cs index 8013520ae6f..b8249ae9c83 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxy.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxy.cs @@ -37,7 +37,7 @@ namespace Akka.Cluster.Tools.Singleton /// Note that this is a best effort implementation: messages can always be lost due to the distributed nature of the actors involved. /// /// - public sealed class ClusterSingletonProxy : ReceiveActor + public sealed class ClusterSingletonProxy : ReceiveActor, IWithTimers { /// /// TBD @@ -51,6 +51,47 @@ internal sealed class TryToIdentifySingleton : INoSerializationVerificationNeede private TryToIdentifySingleton() { } } + /// + /// Used by the proxy to signal that no singleton has been found after a period of time + /// + internal sealed class IdentifySingletonTimeOutTick : INoSerializationVerificationNeeded + { + /// + /// TBD + /// + public static IdentifySingletonTimeOutTick Instance { get; } = new(); + private IdentifySingletonTimeOutTick() { } + } + + public enum IdentifyResult + { + Success, + Timeout, + } + + /// + /// Used by the proxy to signal that no singleton has been found after a period of time + /// + public sealed class IdentifySingletonResult : INoSerializationVerificationNeeded + { + public static IdentifySingletonResult Success(string singletonName, string role) + => new (singletonName, role, IdentifyResult.Success); + + public static IdentifySingletonResult Timeout(string singletonName, string role) + => new (singletonName, role, IdentifyResult.Timeout); + + public IdentifySingletonResult(string singletonName, string role, IdentifyResult result) + { + SingletonName = singletonName; + Role = role; + Result = result; + } + + public IdentifyResult Result { get; } + public string SingletonName { get; } + public string Role { get; } + } + /// /// Returns default HOCON configuration for the cluster singleton. /// @@ -84,9 +125,9 @@ public static Props Props(string singletonManagerPath, ClusterSingletonProxySett private int _identityCounter = 0; private string _identityId; private IActorRef _singleton = null; - private ICancelable _identityTimer = null; private ImmutableSortedSet _membersByAge; private ILoggingAdapter _log; + private bool _isIdentifying; /// /// TBD @@ -126,19 +167,40 @@ public ClusterSingletonProxy(string singletonManagerPath, ClusterSingletonProxyS _singleton = subject; Context.Watch(subject); CancelTimer(); + Context.System.EventStream.Publish(IdentifySingletonResult.Success( + singletonName: _settings.SingletonName, + role: _settings.Role)); SendBuffered(); } }); Receive(_ => { var oldest = _membersByAge.FirstOrDefault(); - if (oldest != null && _identityTimer != null) + if (oldest != null && _isIdentifying) { var singletonAddress = new RootActorPath(oldest.Address) / _singletonPath; Log.Debug("Trying to identify singleton at [{0}]", singletonAddress); Context.ActorSelection(singletonAddress).Tell(new Identify(_identityId)); } }); + Receive(_ => + { + // We somehow missed a CancelTimer() and a singleton reference was found when we waited, + // ignoring the timeout tick message. + if (_singleton is not null) + { + Timers.Cancel(IdentifySingletonTimeOutTick.Instance); + return; + } + + Log.Warning( + "ClusterSingletonProxy failed to find an associated singleton named [{0}] in role [{1}] after {2} seconds.", + _settings.SingletonName, _settings.Role, _settings.SingletonIdentificationFailurePeriod.TotalSeconds); + + Context.System.EventStream.Publish(IdentifySingletonResult.Timeout( + singletonName: _settings.SingletonName, + role: _settings.Role)); + }); Receive(terminated => { if (Equals(_singleton, terminated.ActorRef)) @@ -164,6 +226,8 @@ public ClusterSingletonProxy(string singletonManagerPath, ClusterSingletonProxyS }); } + public ITimerScheduler Timers { get; set; } + private ILoggingAdapter Log => _log ??= Context.GetLogger(); /// @@ -173,6 +237,7 @@ protected override void PreStart() { CancelTimer(); _cluster.Subscribe(Self, typeof(ClusterEvent.IMemberEvent)); + TrackIdentifyTimeout(); } /// @@ -186,11 +251,8 @@ protected override void PostStop() private void CancelTimer() { - if (_identityTimer != null) - { - _identityTimer.Cancel(); - _identityTimer = null; - } + Timers.CancelAll(); + _isIdentifying = false; } private bool MatchingRole(Member member) @@ -216,12 +278,28 @@ private void IdentifySingleton() _identityId = CreateIdentifyId(_identityCounter); _singleton = null; CancelTimer(); - _identityTimer = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable( + + Timers.StartPeriodicTimer( + key: TryToIdentifySingleton.Instance, + msg: TryToIdentifySingleton.Instance, initialDelay: TimeSpan.Zero, interval: _settings.SingletonIdentificationInterval, - receiver: Self, - message: TryToIdentifySingleton.Instance, sender: Self); + _isIdentifying = true; + + // start identify timeout every time we try to identify a new singleton + TrackIdentifyTimeout(); + } + + private void TrackIdentifyTimeout() + { + if(_settings.LogSingletonIdentificationFailure) + Timers.StartPeriodicTimer( + key: IdentifySingletonTimeOutTick.Instance, + msg: IdentifySingletonTimeOutTick.Instance, + initialDelay: TimeSpan.Zero, + interval: _settings.SingletonIdentificationFailurePeriod, + sender: Self); } private void TrackChanges(Action block) diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxySettings.cs b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxySettings.cs index 668b104031f..9c1cd74a398 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxySettings.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonProxySettings.cs @@ -57,7 +57,9 @@ public static ClusterSingletonProxySettings Create(Config config, bool considerA role: role, singletonIdentificationInterval: config.GetTimeSpan("singleton-identification-interval"), bufferSize: config.GetInt("buffer-size", 0), - considerAppVersion: considerAppVersion); + considerAppVersion: considerAppVersion, + logSingletonIdentificationFailure: config.GetBoolean("log-singleton-identification-failure", true), + singletonIdentificationFailurePeriod: config.GetTimeSpan("singleton-identification-failure-period", TimeSpan.FromSeconds(30))); } /// @@ -86,6 +88,49 @@ public static ClusterSingletonProxySettings Create(Config config, bool considerA /// When set to true, singleton instance will be created on the oldest member with the highest number. /// public bool ConsiderAppVersion { get; } + + /// + /// Should the singleton proxy publish a warning if no singleton actor were found after a period of time + /// + public bool LogSingletonIdentificationFailure { get; } + + /// + /// The period the proxy will wait until it logs a missing singleton warning, defaults to 1 minute + /// + public TimeSpan SingletonIdentificationFailurePeriod { get; } + + /// + /// Creates new instance of the . + /// + /// The actor name of the singleton actor that is started by the . + /// The role of the cluster nodes where the singleton can be deployed. If None, then any node will do. + /// Interval at which the proxy will try to resolve the singleton instance. + /// + /// If the location of the singleton is unknown the proxy will buffer this number of messages and deliver them + /// when the singleton is identified.When the buffer is full old messages will be dropped when new messages + /// 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. + /// + /// + [Obsolete("Use constructor with logSingletonIdentificationFailure and logSingletonIdentificationInterval parameter instead. Since v1.5.30")] + public ClusterSingletonProxySettings( + string singletonName, + string role, + TimeSpan singletonIdentificationInterval, + int bufferSize, + bool considerAppVersion) + : this(singletonName, role, singletonIdentificationInterval, bufferSize, considerAppVersion, true, TimeSpan.FromSeconds(30)) + { + } /// /// Creates new instance of the . @@ -104,6 +149,12 @@ public static ClusterSingletonProxySettings Create(Config config, bool considerA /// 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. /// + /// + /// Should the singleton proxy log a warning if no singleton actor were found after a period of time + /// + /// + /// The period the proxy will wait until it logs a missing singleton warning, defaults to 1 minute + /// /// /// This exception is thrown when either the specified /// or is less than or equal to zero. @@ -114,7 +165,9 @@ public ClusterSingletonProxySettings( string role, TimeSpan singletonIdentificationInterval, int bufferSize, - bool considerAppVersion) + bool considerAppVersion, + bool logSingletonIdentificationFailure, + TimeSpan singletonIdentificationFailurePeriod) { if (string.IsNullOrEmpty(singletonName)) throw new ArgumentNullException(nameof(singletonName)); @@ -128,6 +181,8 @@ public ClusterSingletonProxySettings( SingletonIdentificationInterval = singletonIdentificationInterval; BufferSize = bufferSize; ConsiderAppVersion = considerAppVersion; + LogSingletonIdentificationFailure = logSingletonIdentificationFailure; + SingletonIdentificationFailurePeriod = singletonIdentificationFailurePeriod; } /// @@ -184,19 +239,33 @@ public ClusterSingletonProxySettings WithBufferSize(int bufferSize) return Copy(bufferSize: bufferSize); } + public ClusterSingletonProxySettings WithLogSingletonIdentificationFailure(bool logSingletonIdentificationFailure) + { + return Copy(logSingletonIdentificationFailure: logSingletonIdentificationFailure); + } + + public ClusterSingletonProxySettings WithSingletonIdentificationFailureDuration(TimeSpan singletonIdentificationFailurePeriod) + { + return Copy(singletonIdentificationFailurePeriod: singletonIdentificationFailurePeriod); + } + private ClusterSingletonProxySettings Copy( string singletonName = null, Option role = default, TimeSpan? singletonIdentificationInterval = null, int? bufferSize = null, - bool? considerAppVersion = null) + bool? considerAppVersion = null, + bool? logSingletonIdentificationFailure = null, + TimeSpan? singletonIdentificationFailurePeriod = null) { return new ClusterSingletonProxySettings( singletonName: singletonName ?? SingletonName, role: role.HasValue ? role.Value : Role, singletonIdentificationInterval: singletonIdentificationInterval ?? SingletonIdentificationInterval, bufferSize: bufferSize ?? BufferSize, - considerAppVersion: considerAppVersion ?? ConsiderAppVersion); + considerAppVersion: considerAppVersion ?? ConsiderAppVersion, + logSingletonIdentificationFailure: logSingletonIdentificationFailure ?? LogSingletonIdentificationFailure, + singletonIdentificationFailurePeriod: singletonIdentificationFailurePeriod ?? SingletonIdentificationFailurePeriod); } } } diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonSettings.cs b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonSettings.cs index b42cfa18c42..478745354d9 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonSettings.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/ClusterSingletonSettings.cs @@ -154,7 +154,7 @@ internal ClusterSingletonManagerSettings ToManagerSettings(string singletonName) [InternalApi] internal ClusterSingletonProxySettings ToProxySettings(string singletonName) => - new(singletonName, Role, SingletonIdentificationInterval, BufferSize, ConsiderAppVersion); + new(singletonName, Role, SingletonIdentificationInterval, BufferSize, ConsiderAppVersion, true, TimeSpan.FromSeconds(30)); [InternalApi] internal bool ShouldRunManager(Cluster cluster) => string.IsNullOrEmpty(Role) || cluster.SelfMember.Roles.Contains(Role); diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/reference.conf b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/reference.conf index ea374bbffb5..fdb5dd80703 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/reference.conf +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/reference.conf @@ -61,6 +61,12 @@ akka.cluster.singleton-proxy { # Interval at which the proxy will try to resolve the singleton instance. singleton-identification-interval = 1s + + # Should the singleton proxy log a warning if no singleton actor were found after a period of time + log-singleton-identification-failure = true + + # The period the proxy will wait until it logs a missing singleton warning, defaults to 30 seconds + singleton-identification-failure-period = 30s # If the location of the singleton is unknown the proxy will buffer this # number of messages and deliver them when the singleton is identified. 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 bae1c134565..02c86f21446 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 @@ -437,26 +437,48 @@ namespace Akka.Cluster.Tools.Singleton public ClusterSingletonProvider() { } public override Akka.Cluster.Tools.Singleton.ClusterSingleton CreateExtension(Akka.Actor.ExtendedActorSystem system) { } } - public sealed class ClusterSingletonProxy : Akka.Actor.ReceiveActor + public sealed class ClusterSingletonProxy : Akka.Actor.ReceiveActor, Akka.Actor.IWithTimers { public ClusterSingletonProxy(string singletonManagerPath, Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings settings) { } + public Akka.Actor.ITimerScheduler Timers { get; set; } public static Akka.Configuration.Config DefaultConfig() { } protected override void PostStop() { } protected override void PreStart() { } public static Akka.Actor.Props Props(string singletonManagerPath, Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings settings) { } + public enum IdentifyResult + { + Success = 0, + Timeout = 1, + } + public sealed class IdentifySingletonResult : Akka.Actor.INoSerializationVerificationNeeded + { + public IdentifySingletonResult(string singletonName, string role, Akka.Cluster.Tools.Singleton.ClusterSingletonProxy.IdentifyResult result) { } + public Akka.Cluster.Tools.Singleton.ClusterSingletonProxy.IdentifyResult Result { get; } + public string Role { get; } + public string SingletonName { get; } + public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxy.IdentifySingletonResult Success(string singletonName, string role) { } + public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxy.IdentifySingletonResult Timeout(string singletonName, string role) { } + } } public sealed class ClusterSingletonProxySettings : Akka.Actor.INoSerializationVerificationNeeded { + [System.ObsoleteAttribute("Use constructor with logSingletonIdentificationFailure and logSingletonIdentifica" + + "tionInterval parameter instead. Since v1.5.30")] public ClusterSingletonProxySettings(string singletonName, string role, System.TimeSpan singletonIdentificationInterval, int bufferSize, bool considerAppVersion) { } + public ClusterSingletonProxySettings(string singletonName, string role, System.TimeSpan singletonIdentificationInterval, int bufferSize, bool considerAppVersion, bool logSingletonIdentificationFailure, System.TimeSpan singletonIdentificationFailurePeriod) { } public int BufferSize { get; } public bool ConsiderAppVersion { get; } + public bool LogSingletonIdentificationFailure { get; } public string Role { get; } + public System.TimeSpan SingletonIdentificationFailurePeriod { 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, bool considerAppVersion) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithBufferSize(int bufferSize) { } + public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithLogSingletonIdentificationFailure(bool logSingletonIdentificationFailure) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithRole(string role) { } + public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithSingletonIdentificationFailureDuration(System.TimeSpan singletonIdentificationFailurePeriod) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithSingletonIdentificationInterval(System.TimeSpan singletonIdentificationInterval) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithSingletonName(string singletonName) { } } 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 7d54ed7767b..cdca3a7373e 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 @@ -437,26 +437,48 @@ namespace Akka.Cluster.Tools.Singleton public ClusterSingletonProvider() { } public override Akka.Cluster.Tools.Singleton.ClusterSingleton CreateExtension(Akka.Actor.ExtendedActorSystem system) { } } - public sealed class ClusterSingletonProxy : Akka.Actor.ReceiveActor + public sealed class ClusterSingletonProxy : Akka.Actor.ReceiveActor, Akka.Actor.IWithTimers { public ClusterSingletonProxy(string singletonManagerPath, Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings settings) { } + public Akka.Actor.ITimerScheduler Timers { get; set; } public static Akka.Configuration.Config DefaultConfig() { } protected override void PostStop() { } protected override void PreStart() { } public static Akka.Actor.Props Props(string singletonManagerPath, Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings settings) { } + public enum IdentifyResult + { + Success = 0, + Timeout = 1, + } + public sealed class IdentifySingletonResult : Akka.Actor.INoSerializationVerificationNeeded + { + public IdentifySingletonResult(string singletonName, string role, Akka.Cluster.Tools.Singleton.ClusterSingletonProxy.IdentifyResult result) { } + public Akka.Cluster.Tools.Singleton.ClusterSingletonProxy.IdentifyResult Result { get; } + public string Role { get; } + public string SingletonName { get; } + public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxy.IdentifySingletonResult Success(string singletonName, string role) { } + public static Akka.Cluster.Tools.Singleton.ClusterSingletonProxy.IdentifySingletonResult Timeout(string singletonName, string role) { } + } } public sealed class ClusterSingletonProxySettings : Akka.Actor.INoSerializationVerificationNeeded { + [System.ObsoleteAttribute("Use constructor with logSingletonIdentificationFailure and logSingletonIdentifica" + + "tionInterval parameter instead. Since v1.5.30")] public ClusterSingletonProxySettings(string singletonName, string role, System.TimeSpan singletonIdentificationInterval, int bufferSize, bool considerAppVersion) { } + public ClusterSingletonProxySettings(string singletonName, string role, System.TimeSpan singletonIdentificationInterval, int bufferSize, bool considerAppVersion, bool logSingletonIdentificationFailure, System.TimeSpan singletonIdentificationFailurePeriod) { } public int BufferSize { get; } public bool ConsiderAppVersion { get; } + public bool LogSingletonIdentificationFailure { get; } public string Role { get; } + public System.TimeSpan SingletonIdentificationFailurePeriod { 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, bool considerAppVersion) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithBufferSize(int bufferSize) { } + public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithLogSingletonIdentificationFailure(bool logSingletonIdentificationFailure) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithRole(string role) { } + public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithSingletonIdentificationFailureDuration(System.TimeSpan singletonIdentificationFailurePeriod) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithSingletonIdentificationInterval(System.TimeSpan singletonIdentificationInterval) { } public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithSingletonName(string singletonName) { } }