Skip to content

Commit

Permalink
Deprecate ClusterSingleton.Init() and add missing singleton feature…
Browse files Browse the repository at this point in the history
… to `ClusterSingletonSettings` (#7387)

* Deprecate `ClusterSingleton.Init()`

* Update API Approval list

* Update akkadotnet-v1.5-upgrade-advisories.md

* Update akkadotnet-v1.5-upgrade-advisories.md

---------

Co-authored-by: Aaron Stannard <[email protected]>
  • Loading branch information
Arkatufus and Aaronontheweb authored Nov 15, 2024
1 parent 4082e9c commit 36b0ec6
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 11 deletions.
39 changes: 39 additions & 0 deletions docs/community/whats-new/akkadotnet-v1.5-upgrade-advisories.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,45 @@ This document contains specific upgrade suggestions, warnings, and notices that
<iframe width="560" height="315" src="https://www.youtube.com/embed/-UPestlIw4k" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<!-- markdownlint-enable MD033 -->

## Upgrading to Akka.NET v1.5.32

### Future Breaking Change in `Akka.Cluster.Tools`

The method `ClusterSingleton.Init()` will be removed in future v1.6, if you're using this method, you need to convert it to use `ClusterSingletonManager.Props` and `ClusterSingletonProxy.Props` instead.

In order to preserve backward compatibility within your cluster, if you're using this convention:

```csharp
var settings = ClusterSingletonSettings.Create(system);
var singletonActor = SingletonActor.Create(Counter.Props, "GlobalCounter")
.WithStopMessage(MyStopMessage.Instance)
.WithSettings(settings);
var proxy = singleton.Init(singletonActor);
```

You will need to convert it to:

```csharp
var managerSettings = ClusterSingletonManagerSettings.Create(system)
.WithSingletonName("GlobalCounter");
system.ActorOf(
props: ClusterSingletonManager.Props(
singletonProps: Counter.Props,
terminationMessage: MyStopMessage.Instance,
settings: managerSettings),
name: "singletonManagerGlobalCounter");

var proxySettings = ClusterSingletonProxySettings.Create(system)
.WithSingletonName("GlobalCounter");
var proxy = system.ActorOf(
props: ClusterSingletonProxy.Props(
singletonManagerPath: "/user/singletonManagerGlobalCounter",
settings: proxySettings),
name: "singletonProxyGlobalCounter");
```

Note that to preserve backward compatibility between cluster nodes, the singleton manager actor name **MUST** be in the `$"singletonManager{singletonName}"` format.

## Upgrading to Akka.NET v1.5.31

Akka.NET v1.5.31 introduces a breaking behavior change to actor `Stash`. In previous behavior, `Stash` will filter out any messages that are identical (see explanation below) when it is prepended with another. It will not do so now, which is the actual intended behavior.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Collections.Concurrent;
using Akka.Actor;
using Akka.Annotations;
using Akka.Configuration;
using Akka.Util;

namespace Akka.Cluster.Tools.Singleton
Expand All @@ -22,6 +23,17 @@ public class ClusterSingleton : IExtension
{
private readonly ActorSystem _system;
private readonly Lazy<Cluster> _cluster;

/// <summary>
/// Returns default HOCON configuration for the cluster singleton.
/// </summary>
public static Config DefaultConfig()
{
return ConfigurationFactory.FromResource<ClusterSingleton>(
"Akka.Cluster.Tools.Singleton.reference.conf");
}

// Cache for singleton proxies, remove in v1.6
private readonly ConcurrentDictionary<string, IActorRef> _proxies = new();

public static ClusterSingleton Get(ActorSystem system) =>
Expand All @@ -30,6 +42,7 @@ public static ClusterSingleton Get(ActorSystem system) =>
public ClusterSingleton(ExtendedActorSystem system)
{
_system = system;
_system.Settings.InjectTopLevelFallback(DefaultConfig());
_cluster = new Lazy<Cluster>(() => Cluster.Get(system));
}

Expand All @@ -40,6 +53,10 @@ public ClusterSingleton(ExtendedActorSystem system)
/// <para>If there already is a proxy running for the given `singletonName` on this node, an <see cref="IActorRef"/> to that is returned.</para>
/// </summary>
/// <returns>A proxy actor that can be used to communicate with the singleton in the cluster</returns>
[Obsolete("This convenience method is deprecated and will be removed in v1.6, " +
"please use ClusterSingletonManager.Props and ClusterSingletonProxy.Props directly instead. " +
"See https://getakka.net/community/whats-new/akkadotnet-v1.5-upgrade-advisories.html#upgrading-to-akkanet-v1532. " +
"Since 1.5.32.")]
public IActorRef Init(SingletonActor singleton)
{
var settings = singleton.Settings.GetOrElse(ClusterSingletonSettings.Create(_system));
Expand All @@ -63,21 +80,23 @@ public IActorRef Init(SingletonActor singleton)
return GetProxy(singleton.Name, settings);
}

[Obsolete("Deprecated, remove in v1.6")]
private IActorRef GetProxy(string name, ClusterSingletonSettings settings)
{
IActorRef ProxyCreator()
{
var proxyName = $"singletonProxy{name}";
return _system.ActorOf(ClusterSingletonProxy.Props(
singletonManagerPath: $"/user/{ManagerNameFor(name)}",
settings: settings.ToProxySettings(name)),
proxyName);
return _system.ActorOf(
props: ClusterSingletonProxy.Props(
singletonManagerPath: $"/user/{ManagerNameFor(name)}",
settings: settings.ToProxySettings(name)),
name: proxyName);
}

return _proxies.GetOrAdd(name, _ => ProxyCreator());
}


[Obsolete("Deprecated, remove in v1.6")]
private string ManagerNameFor(string singletonName) => $"singletonManager{singletonName}";
}

Expand All @@ -86,6 +105,10 @@ public class ClusterSingletonProvider : ExtensionIdProvider<ClusterSingleton>
public override ClusterSingleton CreateExtension(ExtendedActorSystem system) => new(system);
}

[Obsolete("This setting class is deprecated and will be removed in v1.6, " +
"please use ClusterSingletonManager.Props and ClusterSingletonProxy.Props directly instead. " +
"See https://getakka.net/community/whats-new/akkadotnet-v1.5-upgrade-advisories.html#upgrading-to-akkanet-v1532. " +
"Since 1.5.32.")]
public class SingletonActor
{
public string Name { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ public sealed class ClusterSingletonManager : FSM<ClusterSingletonState, ICluste
/// Returns default HOCON configuration for the cluster singleton.
/// </summary>
/// <returns>TBD</returns>
[Obsolete("Deprecated and will be removed in v1.6, please use ClusterSingleton.DefaultConfig() instead. Since 1.5.32.")]
public static Config DefaultConfig()
{
return ConfigurationFactory.FromResource<ClusterSingletonManager>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ namespace Akka.Cluster.Tools.Singleton
/// The settings used for the <see cref="ClusterSingleton"/>
/// </summary>
[Serializable]
[Obsolete("This setting class is deprecated and will be removed in v1.6, " +
"please use ClusterSingletonManager.Props and ClusterSingletonProxy.Props directly instead. " +
"See https://getakka.net/community/whats-new/akkadotnet-v1.5-upgrade-advisories.html#upgrading-to-akkanet-v1532. " +
"Since 1.5.32.")]
public class ClusterSingletonSettings : INoSerializationVerificationNeeded
{
/// <summary>
Expand Down Expand Up @@ -64,6 +68,16 @@ public class ClusterSingletonSettings : INoSerializationVerificationNeeded
/// </summary>
public bool ConsiderAppVersion { get; }

/// <summary>
/// Should the singleton proxy publish a warning if no singleton actor were found after a period of time
/// </summary>
public bool LogSingletonIdentificationFailure { get; }

/// <summary>
/// The period the proxy will wait until it logs a missing singleton warning, defaults to 1 minute
/// </summary>
public TimeSpan SingletonIdentificationFailurePeriod { get; }

/// <summary>
/// Create settings from the default configuration `akka.cluster`.
/// </summary>
Expand All @@ -88,7 +102,9 @@ public static ClusterSingletonSettings Create(Config config)
mgrSettings.HandOverRetryInterval,
proxySettings.BufferSize,
mgrSettings.LeaseSettings,
false);
false,
proxySettings.LogSingletonIdentificationFailure,
proxySettings.SingletonIdentificationFailurePeriod);
}

private ClusterSingletonSettings(
Expand All @@ -98,7 +114,9 @@ private ClusterSingletonSettings(
TimeSpan handOverRetryInterval,
int bufferSize,
LeaseUsageSettings leaseSettings,
bool considerAppVersion)
bool considerAppVersion,
bool logSingletonIdentificationFailure,
TimeSpan singletonIdentificationFailurePeriod)
{
if (singletonIdentificationInterval == TimeSpan.Zero)
throw new ArgumentException("singletonIdentificationInterval must be positive", nameof(singletonIdentificationInterval));
Expand All @@ -119,24 +137,39 @@ private ClusterSingletonSettings(
BufferSize = bufferSize;
LeaseSettings = leaseSettings;
ConsiderAppVersion = considerAppVersion;
LogSingletonIdentificationFailure = logSingletonIdentificationFailure;
SingletonIdentificationFailurePeriod = singletonIdentificationFailurePeriod;
}

public ClusterSingletonSettings WithRole(string role) => Copy(role: role);

public ClusterSingletonSettings WithSingletonIdentificationInterval(TimeSpan singletonIdentificationInterval)
=> Copy(singletonIdentificationInterval: singletonIdentificationInterval);

public ClusterSingletonSettings WithRemovalMargin(TimeSpan removalMargin) => Copy(removalMargin: removalMargin);

public ClusterSingletonSettings WithHandOverRetryInterval(TimeSpan handOverRetryInterval) => Copy(handOverRetryInterval: handOverRetryInterval);

public ClusterSingletonSettings WithBufferSize(int bufferSize) => Copy(bufferSize: bufferSize);

public ClusterSingletonSettings WithLeaseSettings(LeaseUsageSettings leaseSettings) => Copy(leaseSettings: leaseSettings);

public ClusterSingletonSettings WithLogSingletonIdentificationFailure(bool logSingletonIdentificationFailure)
=> Copy(logSingletonIdentificationFailure: logSingletonIdentificationFailure);

public ClusterSingletonSettings WithSingletonIdentificationFailurePeriod(TimeSpan singletonIdentificationFailurePeriod)
=> Copy(singletonIdentificationFailurePeriod: singletonIdentificationFailurePeriod);

private ClusterSingletonSettings Copy(
Option<string> role = default,
TimeSpan? singletonIdentificationInterval = null,
TimeSpan? removalMargin = null,
TimeSpan? handOverRetryInterval = null,
int? bufferSize = null,
Option<LeaseUsageSettings> leaseSettings = default,
bool? considerAppVersion = null)
bool? considerAppVersion = null,
bool? logSingletonIdentificationFailure = null,
TimeSpan? singletonIdentificationFailurePeriod = null)
{
return new ClusterSingletonSettings(
role: role.HasValue ? role.Value : Role,
Expand All @@ -145,16 +178,18 @@ private ClusterSingletonSettings Copy(
handOverRetryInterval: handOverRetryInterval ?? HandOverRetryInterval,
bufferSize: bufferSize ?? BufferSize,
leaseSettings: leaseSettings.HasValue ? leaseSettings.Value : LeaseSettings,
considerAppVersion: considerAppVersion ?? ConsiderAppVersion);
considerAppVersion: considerAppVersion ?? ConsiderAppVersion,
logSingletonIdentificationFailure: logSingletonIdentificationFailure ?? LogSingletonIdentificationFailure,
singletonIdentificationFailurePeriod: singletonIdentificationFailurePeriod ?? SingletonIdentificationFailurePeriod);
}

[InternalApi]
internal ClusterSingletonManagerSettings ToManagerSettings(string singletonName) =>
new(singletonName, Role, RemovalMargin, HandOverRetryInterval, LeaseSettings, ConsiderAppVersion);
new(singletonName, Role, RemovalMargin, HandOverRetryInterval, LeaseSettings, false);

[InternalApi]
internal ClusterSingletonProxySettings ToProxySettings(string singletonName) =>
new(singletonName, Role, SingletonIdentificationInterval, BufferSize, ConsiderAppVersion, true, TimeSpan.FromSeconds(30));
new(singletonName, Role, SingletonIdentificationInterval, BufferSize, false, LogSingletonIdentificationFailure, SingletonIdentificationFailurePeriod);

[InternalApi]
internal bool ShouldRunManager(Cluster cluster) => string.IsNullOrEmpty(Role) || cluster.SelfMember.Roles.Contains(Role);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,9 @@ namespace Akka.Cluster.Tools.Singleton
public class ClusterSingleton : Akka.Actor.IExtension
{
public ClusterSingleton(Akka.Actor.ExtendedActorSystem system) { }
public static Akka.Configuration.Config DefaultConfig() { }
public static Akka.Cluster.Tools.Singleton.ClusterSingleton Get(Akka.Actor.ActorSystem system) { }
[System.ObsoleteAttribute(@"This convenience method is deprecated and will be removed in v1.6, please use ClusterSingletonManager.Props and ClusterSingletonProxy.Props directly instead. See https://getakka.net/community/whats-new/akkadotnet-v1.5-upgrade-advisories.html#upgrading-to-akkanet-v1532. Since 1.5.32.")]
public Akka.Actor.IActorRef Init(Akka.Cluster.Tools.Singleton.SingletonActor singleton) { }
}
[System.Runtime.CompilerServices.NullableAttribute(new byte[] {
Expand All @@ -401,6 +403,8 @@ namespace Akka.Cluster.Tools.Singleton
public sealed class ClusterSingletonManager : Akka.Actor.FSM<Akka.Cluster.Tools.Singleton.ClusterSingletonState, Akka.Cluster.Tools.Singleton.IClusterSingletonData>
{
public ClusterSingletonManager(Akka.Actor.Props singletonProps, object terminationMessage, Akka.Cluster.Tools.Singleton.ClusterSingletonManagerSettings settings) { }
[System.ObsoleteAttribute("Deprecated and will be removed in v1.6, please use ClusterSingleton.DefaultConfig" +
"() instead. Since 1.5.32.")]
public static Akka.Configuration.Config DefaultConfig() { }
protected override void PostStop() { }
protected override void PreStart() { }
Expand Down Expand Up @@ -482,22 +486,29 @@ namespace Akka.Cluster.Tools.Singleton
public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithSingletonIdentificationInterval(System.TimeSpan singletonIdentificationInterval) { }
public Akka.Cluster.Tools.Singleton.ClusterSingletonProxySettings WithSingletonName(string singletonName) { }
}
[System.ObsoleteAttribute(@"This setting class is deprecated and will be removed in v1.6, please use ClusterSingletonManager.Props and ClusterSingletonProxy.Props directly instead. See https://getakka.net/community/whats-new/akkadotnet-v1.5-upgrade-advisories.html#upgrading-to-akkanet-v1532. Since 1.5.32.")]
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 bool LogSingletonIdentificationFailure { get; }
public System.TimeSpan RemovalMargin { get; }
public string Role { get; }
public System.TimeSpan SingletonIdentificationFailurePeriod { get; }
public System.TimeSpan SingletonIdentificationInterval { get; }
public static Akka.Cluster.Tools.Singleton.ClusterSingletonSettings Create(Akka.Actor.ActorSystem system) { }
public static Akka.Cluster.Tools.Singleton.ClusterSingletonSettings Create(Akka.Configuration.Config config) { }
public override string ToString() { }
public Akka.Cluster.Tools.Singleton.ClusterSingletonSettings WithBufferSize(int bufferSize) { }
public Akka.Cluster.Tools.Singleton.ClusterSingletonSettings WithHandOverRetryInterval(System.TimeSpan handOverRetryInterval) { }
public Akka.Cluster.Tools.Singleton.ClusterSingletonSettings WithLeaseSettings(Akka.Coordination.LeaseUsageSettings leaseSettings) { }
public Akka.Cluster.Tools.Singleton.ClusterSingletonSettings WithLogSingletonIdentificationFailure(bool logSingletonIdentificationFailure) { }
public Akka.Cluster.Tools.Singleton.ClusterSingletonSettings WithRemovalMargin(System.TimeSpan removalMargin) { }
public Akka.Cluster.Tools.Singleton.ClusterSingletonSettings WithRole(string role) { }
public Akka.Cluster.Tools.Singleton.ClusterSingletonSettings WithSingletonIdentificationFailurePeriod(System.TimeSpan singletonIdentificationFailurePeriod) { }
public Akka.Cluster.Tools.Singleton.ClusterSingletonSettings WithSingletonIdentificationInterval(System.TimeSpan singletonIdentificationInterval) { }
}
public enum ClusterSingletonState
{
Expand All @@ -514,6 +525,7 @@ namespace Akka.Cluster.Tools.Singleton
}
public interface IClusterSingletonData { }
public interface IClusterSingletonMessage { }
[System.ObsoleteAttribute(@"This setting class is deprecated and will be removed in v1.6, please use ClusterSingletonManager.Props and ClusterSingletonProxy.Props directly instead. See https://getakka.net/community/whats-new/akkadotnet-v1.5-upgrade-advisories.html#upgrading-to-akkanet-v1532. Since 1.5.32.")]
public class SingletonActor
{
public string Name { get; }
Expand Down
Loading

0 comments on commit 36b0ec6

Please sign in to comment.