Skip to content

De-flake ClusterSpec: stop passing an already-cancelled token to its WithinAsync block - #8540

Merged
Aaronontheweb merged 2 commits into
devfrom
fix/clusterspec-leaveasync-cancelled-token
Sep 10, 2026
Merged

Aaronontheweb merged 2 commits into
devfrom
fix/clusterspec-leaveasync-cancelled-token

Conversation

@Aaronontheweb

@Aaronontheweb Aaronontheweb commented Sep 9, 2026 •

Copy link
Copy Markdown
Member

What changes

ClusterSpec.A_cluster_must_cancel_LeaveAsync_task_if_CancellationToken_fired_before_node_left no longer passes an already-cancelled token to its own wait block. One synchronous ExpectMsg inside that block becomes await ExpectMsgAsync, per the repo rule that tests use the async TestKit calls.

Why

The test creates a token source, calls LeaveAsync with its token, cancels it, confirms that task is cancelled, and then passed the same cancelled token as the cancellationToken: of the 10 second WithinAsync block that drives the leader through Leaving, Exiting, and Removed. WithinAsync races the block against a delay bound to that token. With a cancelled token the delay is born cancelled, so whenever the block hits a real suspension point before the race is checked, WithinAsync throws OperationCanceledException. A fast run finishes the block synchronously and hides it. A slow run, like CI build 131162 on a one-line generator PR, fails in 71 ms with a message that has nothing to do with the cancellation behavior under test.

The bug has been in the test since the cancellationToken: argument was added in 2022. No prior fix targeted it. The de-flake inventory for this week's PR builds records the history.

How it was checked

dotnet build src/core/Akka.Cluster.Tests -c Release -warnaserror clean. The single test run 25 times in a row: 25 passes. The whole ClusterSpec class: 20 passed.

Second commit: the remaining synchronous TestKit calls move to the async API

Per the maintainer's rule that a touched file migrates in the same PR: the seven Shutdown(system) calls in the file's finally blocks become await ShutdownAsync(system). Three _cluster.Shutdown() calls stay; that is the cluster extension's own method, not a TestKit wait, and it has no async form. No assertion changes. The whole ClusterSpec class run three times after the migration: 20 passed each time.

@Aaronontheweb Aaronontheweb added tests akka.net v1.6 Akka.NET v1.6-related issues akka-cluster and removed akka.net v1.6 Akka.NET v1.6-related issues labels Sep 9, 2026
…WithinAsync block

A_cluster_must_cancel_LeaveAsync_task_if_CancellationToken_fired_before_node_left
cancels `cts` to prove LeaveAsync(cts.Token) is cancelled, then reused that same
already-cancelled token as the `cancellationToken:` argument to the 10s
WithinAsync block that drives Leaving -> Exiting -> Removed. WithinAsync races
the block against a delay bound to that token; with the token born cancelled,
any real suspension point inside the block before the race resolves throws
OperationCanceledException out of WithinAsync (seen on CI as a 71ms failure).
Fast/synchronous runs hid the bug; slower runs exposed it.

Fix: drop the cancellationToken: argument so the block uses the default token.
While here, switch the synchronous ExpectMsg<ClusterEvent.MemberRemoved>() call
inside the block to the async ExpectMsgAsync, per the repo's async-only TestKit
convention.
…sync API

Migrates all 7 TestKit Shutdown(ActorSystem) calls in ClusterSpec.cs to ShutdownAsync, awaited in their enclosing async Task facts.
@Aaronontheweb
Aaronontheweb force-pushed the fix/clusterspec-leaveasync-cancelled-token branch from 006f48e to 5ec4293 Compare September 9, 2026 18:18

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Aaronontheweb
Aaronontheweb merged commit 9705d36 into dev Sep 10, 2026
15 checks passed
@Aaronontheweb
Aaronontheweb deleted the fix/clusterspec-leaveasync-cancelled-token branch September 10, 2026 12:57
Aaronontheweb added a commit that referenced this pull request Sep 12, 2026
…WithinAsync block (#8540)

* De-flake ClusterSpec: stop passing an already-cancelled token to the WithinAsync block

A_cluster_must_cancel_LeaveAsync_task_if_CancellationToken_fired_before_node_left
cancels `cts` to prove LeaveAsync(cts.Token) is cancelled, then reused that same
already-cancelled token as the `cancellationToken:` argument to the 10s
WithinAsync block that drives Leaving -> Exiting -> Removed. WithinAsync races
the block against a delay bound to that token; with the token born cancelled,
any real suspension point inside the block before the race resolves throws
OperationCanceledException out of WithinAsync (seen on CI as a 71ms failure).
Fast/synchronous runs hid the bug; slower runs exposed it.

Fix: drop the cancellationToken: argument so the block uses the default token.
While here, switch the synchronous ExpectMsg<ClusterEvent.MemberRemoved>() call
inside the block to the async ExpectMsgAsync, per the repo's async-only TestKit
convention.

* ClusterSpec: migrate the remaining synchronous TestKit calls to the async API

Migrates all 7 TestKit Shutdown(ActorSystem) calls in ClusterSpec.cs to ShutdownAsync, awaited in their enclosing async Task facts.

(cherry picked from commit 9705d36)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant