Skip to content

Backport MNTR conductor reliability to v1.5 (#8431 barrier honesty + #8485 conductor fixes) - #8488

Merged
Aaronontheweb merged 2 commits into
v1.5from
backport/mntr-conductor-reliability-to-v1.5
Aug 26, 2026
Merged

Backport MNTR conductor reliability to v1.5 (#8431 barrier honesty + #8485 conductor fixes)#8488
Aaronontheweb merged 2 commits into
v1.5from
backport/mntr-conductor-reliability-to-v1.5

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Member

Backports the multi-node conductor reliability line from dev, so v1.5's own MNTR gate (which every backport PR runs through) reports failures honestly and survives node restarts. Two commits.

Barrier honesty (#8431). Three sites in Player.cs constructed a bare new Failure(...), which inside an FSM subclass binds to FSMBase.Failure rather than Status.Failure - so the barrier ask completed successfully and the failure was silently swallowed. The asking node walked on unsynchronized, and the eventual breakage surfaced on some other node, at some other barrier, as an unrelated-looking flake. All three now reply Status.Failure, plus the discarded-ask cleanup, plus a one-line runner fix where a failed node's output was labeled "passed." The binding bug was demonstrated end-to-end against v1.5's own binaries during validation: the unqualified reply is FSMBase.Failure, the ask completes successfully with it, and the same reply as Status.Failure faults the ask with the real barrier error.

Deliberately not taken from the dev squash: the runner's node-hang kill backstop (dev's implementation needs Process.Kill(entireProcessTree:), which netstandard2.0 - v1.5's TestAdapter target - does not have; a parameterless Kill would orphan child node processes, which is worse than no backstop; needs a netstandard-safe follow-up), and dev-specific spec content.

Conductor fixes (#8485). When a node restarts its ActorSystem mid-spec, the old connection's ServerFSM death fired ClientDisconnected, which the controller matched by role name - evicting the freshly re-registered node - and the barrier coordinator then dropped the evicted node's arrivals with no reply at all, hanging it for its full ask timeout while the other nodes' barrier expired. Disconnects are now matched against the registered FSM identity, unregistered arrivals get an explicit BarrierResult(false), and ReleaseAll detaches the shared event-loop groups so a second conductor in one process gets a live pool. Cherry-picked clean, zero conflicts - v1.5's conductor had not drifted at any touched site.

Validation on v1.5: Akka.Remote.TestKit and the TestAdapter build at 0 warnings; Akka.Remote.TestKit.Tests all green including both ported tests; revert-proofs performed (reverting the identity check fails the re-registration test; reverting the coordinator reply hangs the arrival test on exactly the old symptom); ReDeployment MNTR specs 6/6, three consecutive runs.

Expectation for this branch's MNTR lane: with barrier failures now honest, latent spec failures the old code silently swallowed may start surfacing - attributed to the node that actually broke instead of the nodes downstream of it. That is the intended effect, and the same effect on dev is what made the rest of the de-flake campaign possible.

ClientFSM replied to a failed barrier with 'new Failure(...)'. Because ClientFSM derives from
FSM<,>, that unqualified name binds to the inherited nested FSMBase.Failure - an FSM termination
reason - not Akka.Actor.Status.Failure.

FutureActorRef's ask-completion switch faults only on ISystemMessage, Status.Failure and
Akka.Actor.Failure. FSMBase.Failure matches none of them, so it fell through to
'case T t: TrySetResult(t)' and the ask COMPLETED SUCCESSFULLY. EnterBarrierAsync discarded the
result and logged 'passed barrier', so every non-success outcome - timeout, wrong barrier, client
lost, duplicate node - was silently swallowed and the node walked on unsynchronized.

The damage is misattribution rather than fabricated passes: when one node fails and never reaches a
barrier, the remaining nodes should fail cleanly on a broken rendezvous. Instead they continued into
the next phase out of sync and produced their own unrelated failures, turning one localized error
into a multi-node mess.

Use Status.Failure at all three sites in v1.5's Player.cs, matching the two places in the same file
that already got it right. Also drop the unused ask result now that a failure propagates as an
exception.

Healthy barriers are unaffected - a successful BarrierResult still replies with the barrier name.

Taken from the dev squash dcd9acb:
  - src/core/Akka.Remote.TestKit/Player.cs in full: all three `new Failure(...)` ->
    `new Status.Failure(...)` sites plus the discarded-ask cleanup in EnterAsync. Applied cleanly.
  - One line of MultiNodeTestRunner.cs: the failure path reported a failed node as
    "<spec> passed." Corrected to "<spec> failed." Same honesty theme, compiles as-is.

Dropped from the dev squash:
  - The MultiNodeTestRunner hang backstop (DefaultNodeExitTimeout plus the Task.WhenAny/kill block).
    It applies textually but does not compile on v1.5: dev's Akka.MultiNode.TestAdapter targets
    net10.0 while v1.5's targets netstandard2.0, which has no Process.Kill(bool) overload. Falling
    back to the parameterless Kill() would leave the node's child processes orphaned on CI, which is
    worse than no backstop, so this is left for a separate change.
  - The ShardedDaemonProcessSpec redesign. Dev-specific test content, separately owned, no bearing on
    barrier honesty.
A node that calls StartNewSystem reconnects to the conductor under the same
role name on a fresh channel. The ServerFSM for the connection it replaced
then terminates and reports ClientDisconnected, and the controller removed
the role by name - evicting the live registration that had just replaced it.
From that point the barrier coordinator held no registration for the node, so
it dropped every arrival from it and the next barrier stalled until it timed
out. The evicting disconnect and the new registration arrive from independent
sources, so which one lands first is pure timing; a slow agent loses the race.

Four changes:

- ServerFSM names itself as the sender of its ClientDisconnected, and the
  controller only lets a ServerFSM evict the registration it owns.
- BarrierCoordinator answers an arrival from an unregistered client with a
  failed BarrierResult while waiting, instead of dropping it. Dropping it left
  that node blocked on an ask nothing would complete, so it reported a 60
  second ask timeout long after the barrier had already failed on another
  node, which hid the real cause. Idle already answered this way.
- StartNewSystemAsync pins Artery's canonical host and port as well as the
  classic ones. A node is restarted so that deployments and associations aimed
  at it still resolve, and writing only the classic key left Artery free to
  take a fresh ephemeral port whenever multinode.port is 0.
- RemoteConnection.ReleaseAll detaches the event loop groups before shutting
  them down, so a later CreateConnection builds fresh ones instead of getting
  a dead pool. It also shuts the server worker pool down, which it leaked.

Both new tests fail without the corresponding fix: the controller test finds
the node gone from GetNodes, and the barrier test times out waiting for a
reply that never comes.

Backport of #8485. The cherry-pick applied clean on all six files - no
conflicts, byte-identical to the source commit. The Artery bullet above
describes work that lives outside this commit and has no v1.5 counterpart:
v1.5 ships no Artery transport, so nothing there needed porting.

(cherry picked from commit e9036d0)
@Aaronontheweb
Aaronontheweb merged commit 00c4f1a into v1.5 Aug 26, 2026
10 of 12 checks passed
@Aaronontheweb
Aaronontheweb deleted the backport/mntr-conductor-reliability-to-v1.5 branch August 26, 2026 12:53
@Aaronontheweb Aaronontheweb added this to the 1.5.71 milestone Aug 26, 2026
Aaronontheweb added a commit that referenced this pull request Aug 27, 2026
Backport of dev commit dcd9acb to v1.5, scoped to the
ShardedDaemonProcessSpec rewrite. The Player.cs / MultiNodeTestRunner.cs
portions of #8431 were already delivered to v1.5 in the MNTR conductor
reliability backport (#8488), so only the spec change is taken here.

Rewrite the ShardedDaemonProcess multinode spec to be async and resistant
to slow-CI barrier timeouts:
- task-returning TestKit methods throughout (no sync-over-async)
- single cluster-wide collector probe on 'first' so shards reallocated
  during cluster settle are reported correctly
- fish for a complete distinct-ID set instead of assuming 4 messages
- raise testconductor barrier-timeout to 60s for slow agents
- fix HOCON brace nesting that dropped keep-alive-interval

Adapted to v1.5: AwaitClusterUpAsync(CancellationToken.None, ...) to match
the v1.5 TestKit signature.

(cherry picked from commit dcd9acb)
This was referenced Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant