fix(tests): StopNodeBAsync also has to stop the node, not just dispose it
v2-ci / build (push) Failing after 1m36s
v2-ci / unit-tests (push) Has been skipped

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
This commit is contained in:
Joseph Doherty
2026-07-21 10:52:57 -04:00
parent 835fc08c3f
commit 8838e92d0a
@@ -204,15 +204,22 @@ public sealed class TwoNodeClusterHarness : IAsyncDisposable
}
/// <summary>
/// Gracefully shuts down node B via <see cref="WebApplication.DisposeAsync"/>, which runs
/// CoordinatedShutdown → Cluster.Leave. Node A sees the member transition to Removed within
/// a couple of seconds. Use this for failover scenarios; call <see cref="RestartNodeBAsync"/>
/// 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 <see cref="RestartNodeBAsync"/> to bring it back on
/// the same Akka port.
/// </summary>
/// <remarks>
/// This used to call <c>DisposeAsync</c> alone, on the stated belief that disposal "runs
/// CoordinatedShutdown". It does not — see <see cref="ShutDownAsync"/>. 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.
/// </remarks>
public async Task StopNodeBAsync()
{
if (NodeB is null) return;
await NodeB.DisposeAsync();
await ShutDownAsync(NodeB);
NodeB = null!;
}