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