Skip to content

Backport: make DispatcherModelSpec.A_dispatcher_must_process_messages_one_at_a_time deterministic (backport of #8299) - #8300

Merged
Aaronontheweb merged 2 commits into
akkadotnet:v1.5from
Aaronontheweb:backport/v1.5-dispatcher-model-spec-flaky
Jul 2, 2026
Merged

Aaronontheweb merged 2 commits into
akkadotnet:v1.5from
Aaronontheweb:backport/v1.5-dispatcher-model-spec-flaky

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Member

This is the v1.5 backport of dev PR #8299 — a test-only stability fix. It is preserved as an individual cherry-pick (-x provenance) and is separate from the other in-flight backport PRs (#8296, #8298).

The flake

DispatcherModelSpec.A_dispatcher_must_process_messages_one_at_a_time (in the shared ActorModelSpec harness) was intermittently flaky under CI.

Root cause (test-only race, not a dispatcher bug): the test held the actor "busy" with a hard-coded, non-dilated Thread.Sleep(1000) inside the Wait handler, then asserted the second message's countdown against a Dilated(1.5s) deadline. That leaves only a ~500ms margin above the fixed 1s sleep. Under ThreadPool starvation on CI the margin is consumed and the legitimate countdown arrives late, producing a false failure. Isolation is never actually violated — the _busy Switch guards it and Restarts stays 0.

The fix (deterministic, latch-controlled)

Mirrors the sibling A_dispatcher_must_process_messages_in_parallel Meet(aStart, aStop) pattern:

  • Replace Wait(1000) with a test-controlled Meet(busyStarted, release) latch so the actor stays busy until the test explicitly releases it — removing the wall-clock race entirely.
  • While the actor is held busy, send CountDown(oneAtTime) and use AssertNoCountdown(..., 500ms) to positively prove the second message is not processed while busy — a stronger "one at a time" assertion than inferring it from a restart that never happens.
  • After releasing the gate, AssertCountdown(oneAtTime, Dilated(3s)) proves it is processed once free (matching the 3s the same test already grants the first message).

Message accounting is unchanged: the test still sends exactly three messages (CountDown, Meet, CountDown), so the trailing AssertRefDefaultZero(registers: 1, msgsReceived: 3, msgsProcessed: 3) counts remain correct.

Test-only change. No production code under src/core/Akka/Dispatch/ was modified — the dispatcher/mailbox behavior is correct.

Local validation (net10.0)

  • dotnet build src/core/Akka.Tests/Akka.Tests.csproj -c Release -warnaserror → 0 warnings, 0 errors
  • Target test run 5× (--filter FullyQualifiedName~process_messages_one_at_a_time) → all 5 passed (1/1 each)
  • Full DispatcherModelSpec suite → 8/8 passed

Cherry-picked cleanly from 2b1902f with no conflicts / no hand-porting required.

The `A_dispatcher_must_process_messages_one_at_a_time` test in the shared
`ActorModelSpec` harness was intermittently flaky under CI.

Root cause (test-only race, not a dispatcher bug):
The test held the actor "busy" with a hard-coded, NON-dilated
`Thread.Sleep(1000)` inside the `Wait` handler, then asserted the second
message's countdown against a `Dilated(1.5s)` deadline. That leaves only a
~500ms margin above the fixed 1s sleep. Under ThreadPool starvation on CI the
margin is consumed and the legitimate countdown arrives late, producing a false
failure. Isolation is never actually violated -- the `_busy` Switch guards it and
`Restarts` stays 0.

Fix (deterministic, latch-controlled -- mirrors the sibling
`A_dispatcher_must_process_messages_in_parallel` `Meet(aStart, aStop)` pattern):
- Replace `Wait(1000)` with a test-controlled `Meet(busyStarted, release)` latch
  so the actor stays busy until the test explicitly releases it, removing the
  wall-clock race entirely.
- While the actor is held busy, send `CountDown(oneAtTime)` and use
  `AssertNoCountdown(..., 500ms)` to POSITIVELY prove the second message is not
  processed while busy -- a stronger "one at a time" assertion than inferring it
  from a restart that never happens.
- After releasing the gate, `AssertCountdown(oneAtTime, Dilated(3s))` proves it
  IS processed once free (matching the 3s the same test already grants the first
  message).

Message accounting is unchanged: the test still sends exactly three messages
(CountDown, Meet, CountDown), so the trailing
`AssertRefDefaultZero(registers: 1, msgsReceived: 3, msgsProcessed: 3)` counts
remain correct.

Test-only change. No production code under src/core/Akka/Dispatch/ was modified;
the dispatcher/mailbox behavior is correct.

(cherry picked from commit 2b1902f)
@Aaronontheweb Aaronontheweb added tests akka.net v1.5 Issues affecting Akka.NET v1.5 akka-actor labels Jul 2, 2026
@Aaronontheweb
Aaronontheweb enabled auto-merge (squash) July 2, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

akka.net v1.5 Issues affecting Akka.NET v1.5 akka-actor tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant