From c3a9e708a239654e4dd53b5831fcf3722f046870 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 8 Jul 2026 14:48:18 -0400 Subject: [PATCH] =?UTF-8?q?fix(cluster):=20enable=20SBR=20downing=20provid?= =?UTF-8?q?er=20=E2=80=94=20automatic=20failover=20was=20inert=20for=20cra?= =?UTF-8?q?shes/partitions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Actors/AkkaHostedService.cs | 6 ++++++ .../HoconBuilderTests.cs | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/Actors/AkkaHostedService.cs b/src/ZB.MOM.WW.ScadaBridge.Host/Actors/AkkaHostedService.cs index c8f818ac..5f9a2930 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Host/Actors/AkkaHostedService.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Host/Actors/AkkaHostedService.cs @@ -201,6 +201,11 @@ public class AkkaHostedService : IHostedService /// rather than hard-coded, so the bound /// configuration value is actually consumed. /// + /// The split-brain-resolver downing-provider-class is installed + /// explicitly: Akka defaults to NoDowning, under which the entire + /// split-brain-resolver section is inert and singletons never migrate on a hard + /// crash or partition. Naming the SBR provider is what activates automatic downing. + /// /// Every duration is rendered via in /// milliseconds, so sub-second cluster timing values (e.g. a 750ms heartbeat) are /// preserved exactly instead of being rounded to whole seconds. @@ -251,6 +256,7 @@ akka {{ seed-nodes = [{seedNodesStr}] roles = [{rolesStr}] min-nr-of-members = {clusterOptions.MinNrOfMembers} + downing-provider-class = ""Akka.Cluster.SBR.SplitBrainResolverProvider, Akka.Cluster"" split-brain-resolver {{ active-strategy = {QuoteHocon(clusterOptions.SplitBrainResolverStrategy)} stable-after = {DurationHocon(clusterOptions.StableAfter)} diff --git a/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/HoconBuilderTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/HoconBuilderTests.cs index 85ba2606..b3b1b4ec 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/HoconBuilderTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/HoconBuilderTests.cs @@ -160,4 +160,22 @@ public class HoconBuilderTests TimeSpan.FromMilliseconds(7500), config.GetTimeSpan("akka.remote.transport-failure-detector.acceptable-heartbeat-pause")); } + + [Fact] + public void BuildHocon_EnablesSplitBrainResolverDowningProvider() + { + // Review 01 [Critical]: without downing-provider-class the entire + // split-brain-resolver section is inert (Akka default = NoDowning) and + // singletons never migrate on a hard crash. + var node = new NodeOptions { Role = "Central", NodeHostname = "node1", RemotingPort = 8081 }; + + var hocon = AkkaHostedService.BuildHocon( + node, DefaultCluster(), new[] { "Central" }, + TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15)); + + var config = ConfigurationFactory.ParseString(hocon); + Assert.Equal( + "Akka.Cluster.SBR.SplitBrainResolverProvider, Akka.Cluster", + config.GetString("akka.cluster.downing-provider-class")); + } }