From 8838e92d0acd7c6ec5466b7e5dee08c9e8e0c4ca Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 21 Jul 2026 10:52:57 -0400 Subject: [PATCH] fix(tests): StopNodeBAsync also has to stop the node, not just dispose it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes 835fc08c, which fixed the harness's DisposeAsync but left StopNodeBAsync calling NodeB.DisposeAsync() directly — despite that commit's own message naming it as carrying the same bug. Caught by re-reading the code rather than trusting the commit message. This one is not just about memory. IHost.DisposeAsync never invokes IHostedService.StopAsync, so CoordinatedShutdown never runs and node B never performs a graceful Cluster.Leave. Node A instead notices only when the failure detector times the member out — a slower and materially different path from the graceful leave the failover tests are written to exercise. The doc comment asserted the opposite ("shuts down node B via DisposeAsync, which runs CoordinatedShutdown"); it now says what actually happens and why. Verified: the three failover suites that call it pass (5/5), and the project is unchanged at 194 passed with only the AbCip fixture failure, which needs a docker fixture that is not running. Also checked the sibling harness while here: LocalDbPairHarness already stops each host before disposing it, so it needed nothing. Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW --- .../TwoNodeClusterHarness.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/TwoNodeClusterHarness.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/TwoNodeClusterHarness.cs index 15175be0..cd18796f 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/TwoNodeClusterHarness.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/TwoNodeClusterHarness.cs @@ -204,15 +204,22 @@ public sealed class TwoNodeClusterHarness : IAsyncDisposable } /// - /// Gracefully shuts down node B via , which runs - /// CoordinatedShutdown → Cluster.Leave. Node A sees the member transition to Removed within - /// a couple of seconds. Use this for failover scenarios; call - /// to bring it back on the same Akka port. + /// Gracefully shuts node B down, so CoordinatedShutdown runs and the node performs a + /// Cluster.Leave; node A sees the member transition to Removed within a couple of seconds. + /// Use this for failover scenarios; call to bring it back on + /// the same Akka port. /// + /// + /// This used to call DisposeAsync alone, on the stated belief that disposal "runs + /// CoordinatedShutdown". It does not — see . A disposed-but-never- + /// stopped node never leaves the cluster gracefully; node A only notices once the failure + /// detector times it out, which is a slower and materially different path from the graceful + /// leave these failover tests mean to exercise. + /// public async Task StopNodeBAsync() { if (NodeB is null) return; - await NodeB.DisposeAsync(); + await ShutDownAsync(NodeB); NodeB = null!; }