using Akka.Actor;
using Akka.Cluster;
using Microsoft.Extensions.DependencyInjection;
namespace ZB.MOM.WW.Health.Akka;
///
/// implementation that computes directly
/// from the Akka cluster state — the local node is the oldest Up member, optionally within a
/// role scope. Register as a singleton.
///
///
///
/// Shares its rule with via
/// , so an endpoint gated by this type and the
/// /health/active probe an orchestrator routes by can never disagree about which node
/// is in charge.
///
///
/// Behaviour change in 0.3.0: through 0.2.1 this gate selected by
/// ClusterState.Leader, which is address-ordered and diverges from singleton placement
/// after any restart. See .
///
///
/// The is resolved lazily from the service provider; if it is not
/// yet available — e.g. during startup before Akka is initialised —
/// returns false (the safe default during startup, matching
/// the standby case). This gate reads the cluster state directly and does not resolve
/// from DI.
///
///
public sealed class AkkaActiveNodeGate : IActiveNodeGate
{
private readonly IServiceProvider _serviceProvider;
private readonly string? _role;
/// Initializes a new .
///
/// The application service provider. The is resolved lazily; if it is
/// not yet available returns false.
///
///
/// The role to scope the competition to, or null (the default) to consider every Up
/// member.
///
public AkkaActiveNodeGate(IServiceProvider serviceProvider, string? role = null)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_role = role;
}
///
public bool IsActiveNode
{
get
{
var system = _serviceProvider.GetService();
if (system is null)
return false;
try
{
return ClusterActiveNode.SelfIsActive(Cluster.Get(system), _role);
}
catch (Exception)
{
// ActorSystem exists but the cluster is not initialised yet. Same startup-safety
// rule as the health check, expressed as the gate's safe default: not active.
return false;
}
}
}
}