From d67a7e5d0b777199aad7a946cbdb0acc6aef5f09 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 6 Aug 2026 15:42:39 -0400 Subject: [PATCH] =?UTF-8?q?refactor(mesh):=20step=204=20=E2=80=94=20drop?= =?UTF-8?q?=20the=20receptionist=20extension=20and=20akka.cluster.client?= =?UTF-8?q?=20from=20akka.conf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit akka.extensions loses ClusterClientReceptionistExtensionProvider and the whole akka.cluster.client block (buffer-size / reconnect-timeout / receptionist.role) goes with it. The DistributedPubSub extension and the maximum-frame-size / log-frame-size-exceeding canary both stay — DPS is the pair-local transport and the frame canary guards every remoting message, neither of which the cross-mesh deletion touches. MeshTransportHoconTests drops its three ClusterClientSettings/ClusterReceptionistSettings asserts and keeps the frame-size pin and the DPS-extension pin, renamed since it no longer has a receptionist to survive alongside. --- .../Resources/akka.conf | 35 ++---------- .../MeshTransportHoconTests.cs | 55 ++----------------- 2 files changed, 10 insertions(+), 80 deletions(-) diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/Resources/akka.conf b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/Resources/akka.conf index b7e7a8a7..56648f0b 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/Resources/akka.conf +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/Resources/akka.conf @@ -13,14 +13,12 @@ akka { # since the Serilog logger can be slow to initialize at startup. logger-startup-timeout = 30s + # DistributedPubSub is PAIR-LOCAL and stays: it carries MeshTransport:Mode = Dps (the + # single-mesh default), the node-local alarm-command publishes and the node<->node + # redundancy-state probe leg. What is gone is the cross-mesh command transport, which is now + # gRPC and needs no Akka extension at all. extensions = [ "Akka.Cluster.Tools.PublishSubscribe.DistributedPubSubExtensionProvider, Akka.Cluster.Tools" - - # Per-cluster mesh Phase 2 — the central<->node command boundary. NOT auto-started: without - # this entry the receptionist materialises only if some code path happens to call - # ClusterClientReceptionist.Get(system) first, which makes "is the boundary listening?" - # depend on startup ordering rather than on configuration. - "Akka.Cluster.Tools.Client.ClusterClientReceptionistExtensionProvider, Akka.Cluster.Tools" ] actor { @@ -94,31 +92,6 @@ akka { down-removal-margin = 15s run-coordinated-shutdown-when-down = on - # CLUSTERCLIENT — the central<->node command boundary (per-cluster mesh Phase 2). - client { - # DELIBERATE, and NOT what the sister project runs: they never wrote this section, so - # they inherit buffer-size = 1000. Zero means "if the receptionist is unknown, drop the - # message now". Buffering would replay a DispatchDeployment on reconnect and apply a - # deployment ConfigPublishCoordinator had already sealed as TimedOut — the node would - # then be running a configuration the database says failed. Fail-fast keeps the DB the - # single record of truth, and reproduces today's DPS behaviour exactly: a node that is - # down misses the publish and self-heals from the DB on restart. - buffer-size = 0 - - # Retry forever rather than self-stopping the client. Restates Akka's default; stated - # explicitly because the behaviour is load-bearing — a central node down for an hour - # must not require a driver-node restart to become reachable again. - reconnect-timeout = off - - receptionist { - # Empty = every member hosts a receptionist. This is what makes the comm actors - # "registered per node, not as a singleton" work: contact rotation reaches whichever - # node answers. Restates Akka's default; do NOT scope this to a role, or the - # boundary becomes reachable through one node only and rotation silently fails. - role = "" - } - } - singleton { singleton-name = "singleton" } diff --git a/tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests/MeshTransportHoconTests.cs b/tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests/MeshTransportHoconTests.cs index 1960d0c3..9bdb1b0d 100644 --- a/tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests/MeshTransportHoconTests.cs +++ b/tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests/MeshTransportHoconTests.cs @@ -1,5 +1,4 @@ using Akka.Actor; -using Akka.Cluster.Tools.Client; using Akka.Configuration; using Shouldly; using Xunit; @@ -7,8 +6,8 @@ using Xunit; namespace ZB.MOM.WW.OtOpcUa.Cluster.Tests; /// -/// Asserts the ClusterClient / receptionist / frame-size settings that are actually in -/// force on a real built from the shipped base config. +/// Asserts the remoting frame-size canary and the DistributedPubSub extension that are actually +/// in force on a real built from the shipped base config. /// /// /// Asserting the akka.conf text instead would pass while a competing HOCON fragment @@ -29,38 +28,6 @@ public class MeshTransportHoconTests : IDisposable _sys = ActorSystem.Create("mesh-hocon-probe", config); } - [Fact] - public void Cluster_client_buffering_is_disabled() - { - // Asserted through ClusterClientSettings — the object the client is actually built from — - // NOT through Config.GetInt. GetInt returns 0 for a MISSING key, so a config assertion here - // passes identically whether the setting is explicitly 0 or absent with Akka's default of - // 1000 in force. It was green before this feature existed; that is the whole defect class. - // - // buffer-size = 0 means "drop now if the receptionist is unknown". Buffering would replay a - // DispatchDeployment on reconnect and apply a deployment the coordinator already sealed as - // TimedOut, leaving the node running a configuration the database says failed. This is a - // behaviour decision, not a tuning knob. - ClusterClientSettings.Create(_sys).BufferSize.ShouldBe(0); - } - - [Fact] - public void Cluster_client_retries_forever_rather_than_self_stopping() - { - // reconnect-timeout = off surfaces as a null TimeSpan on the settings object. A central node - // down for an hour must not require a driver-node restart to become reachable again. - ClusterClientSettings.Create(_sys).ReconnectTimeout.ShouldBeNull(); - } - - [Fact] - public void Receptionist_serves_every_member_regardless_of_role() - { - // An empty role is what makes "registered per node, not as a singleton" work: every member - // hosts a receptionist, so ClusterClient contact rotation reaches whichever node answers. - // Scoping this to a role would make the boundary reachable through one node only. - ClusterReceptionistSettings.Create(_sys).Role.ShouldBeNull(); - } - [Fact] public void Oversized_frames_are_logged_rather_than_dropped_silently() { @@ -73,21 +40,11 @@ public class MeshTransportHoconTests : IDisposable } [Fact] - public void Receptionist_extension_is_registered_at_startup() + public void Distributed_pub_sub_extension_is_registered_at_startup() { - // Not auto-started. Without this entry the receptionist materialises only if some code path - // happens to call ClusterClientReceptionist.Get(system) first, which makes "is the boundary - // listening?" depend on startup ordering rather than on configuration. - _sys.Settings.Config.GetStringList("akka.extensions") - .ShouldContain(s => s.Contains("ClusterClientReceptionistExtensionProvider")); - } - - [Fact] - public void Distributed_pub_sub_extension_survives_alongside_the_receptionist() - { - // The dark switch keeps both transports live for one phase, and DPS additionally carries - // traffic Phase 2 does not touch at all — node-local alarm-command publishes and the - // node<->node redundancy-state probe leg. Losing it here would break far more than deploys. + // DPS is PAIR-LOCAL and survives the cross-mesh transport's deletion: it carries + // MeshTransport:Mode = Dps (the single-mesh default), the node-local alarm-command publishes + // and the node<->node redundancy-state probe leg. Losing it would break far more than deploys. _sys.Settings.Config.GetStringList("akka.extensions") .ShouldContain(s => s.Contains("DistributedPubSubExtensionProvider")); }