Skip to content

De-flake two TestKit timing tests (TestFSMRef state timeout, WaitForRadioSilence reset) in Akka.TestKit and Akka.Hosting.TestKit - #8615

Merged
Aaronontheweb merged 2 commits into
akkadotnet:devfrom
Aaronontheweb:fix/testfsmref-state-timeout-bound
Sep 23, 2026
Merged

Aaronontheweb merged 2 commits into
akkadotnet:devfrom
Aaronontheweb:fix/testfsmref-state-timeout-bound

Conversation

@Aaronontheweb

@Aaronontheweb Aaronontheweb commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Two timing flakes, each present in both Akka.TestKit.Tests and the Akka.Hosting.TestKit.Tests copy imported in #8594. Both failed in AOT-stack builds, but neither touches the TestKit. One commit per test.

1. TestFSMRefSpec.A_TestFSMRef_must_allow_access_to_internal_state

Failed on a 2-vCPU Linux agent with: Block took 00:00:01.8249680, exceeding 00:00:00.5000000.

Cause. SetStateTimeout(100ms) schedules a TimeoutMarker, which the FSM then handles on the ThreadPool-backed dispatcher. The Hosting copy waited with a synchronous Within + AwaitCondition under a 500 ms cap, blocking an xUnit worker in the meantime. The core copy is async but has the same cap. Separately, Within's clock started only after SetStateTimeout returned, so a stall between the two calls could make the timeout look early.

Change. await AwaitConditionAsync(..., Dilated(5s)). A Stopwatch started before SetStateTimeout asserts at least 80 ms passed. A 10 ms scheduler tick can fire the 100 ms timeout at about 90 ms, which still clears 80 ms.

2. WaitForRadioSilenceAsync_should_reset_timer_twice_only → ..._should_reset_the_timer_on_each_message

Failed on a 2-vCPU Windows agent: it got {1} instead of {1, 2}.

Cause. Each receipt re-arms the window: a fresh CancellationTokenSource(max) per ReceiveOneAsync, in TestKitBase_Receive.cs:168-183, 245-276. Message 2 was sent 1.5 s after message 1, into a 3 s window that message 1 had just opened, so a 1.5 s oversleep pushed it out.

Change.

  • Message 2 is sent 400 ms after message 1, in a 4 s window, which leaves 3.6 s of slack.
  • The reset is proved from the elapsed time. The silence can't end before max after message 2 was sent (the CTS timers never fire early), while with no reset it would end about 400 ms sooner. Delays only lengthen the measured time.
  • The old trailing Tell(3) after a 6 s sleep asserted nothing and is dropped. The maxMessages violation is already covered by WaitForRadioSilenceAsync_should_fail_immediately_with_bad_input.
  • The test runs in about 4.4 s instead of 9 s.

Verification (local, each test in each project)

  • TestFSMRef: 25/25 normally and 10/10 with every core pegged.
  • Radio silence: 10/10 normally and 5/5 pegged.

…e 500 ms cap

A_TestFSMRef_must_allow_access_to_internal_state failed in the Akka.Hosting
copy with "Block took 00:00:01.82, exceeding 00:00:00.50" on a 2-vCPU Linux
agent. The StateTimeout travels scheduler -> mailbox -> ThreadPool-backed
dispatcher, and the synchronous Within/AwaitCondition blocked a pool thread
while it waited, so a starved agent easily needs more than 500 ms.

Both copies (Akka.Hosting.TestKit.Tests and Akka.TestKit.Tests) now await
the condition with a Dilated(5s) bound and assert the 80 ms lower bound with
a Stopwatch started before SetStateTimeout. Within's clock started after it,
so a stall between the two calls could have made the timeout look early.

@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 enabled auto-merge (squash) September 23, 2026 17:35
…of a 1.5 s margin

WaitForRadioSilenceAsync_should_reset_timer_twice_only returned {1} instead
of {1,2} in the Akka.Hosting copy on a 2-vCPU Windows agent. Message 2 was
sent 1.5 s after message 1 into a 3 s window that message 1 had just opened,
so a 1.5 s oversleep on the test thread pushed it out.

Both copies now send 2 only 400 ms after 1, in a 4 s window, and prove the
reset from the elapsed time: the silence cannot end before max after 2 was
sent, while without a reset it would end about 400 ms sooner. Delays only
lengthen the measured time. Renamed to what it tests; the maxMessages
violation is already covered by WaitForRadioSilenceAsync_should_fail_immediately_with_bad_input.
@Aaronontheweb Aaronontheweb changed the title De-flake TestFSMRefSpec state-timeout check in Akka.TestKit and Akka.Hosting.TestKit De-flake two TestKit timing tests (TestFSMRef state timeout, WaitForRadioSilence reset) in Akka.TestKit and Akka.Hosting.TestKit Sep 23, 2026
@Aaronontheweb
Aaronontheweb merged commit 0ea28f8 into akkadotnet:dev Sep 23, 2026
15 checks passed
@Aaronontheweb
Aaronontheweb deleted the fix/testfsmref-state-timeout-bound branch September 23, 2026 20:38
Aaronontheweb added a commit that referenced this pull request Oct 3, 2026
* De-flake ActorSystemSpec.Log_dead_letters: await DeadLetterListener startup (#8428)

The test intermittently failed with 'Only received 0/1 messages that matched filter [Info when
Message contains "not delivered"]' (Azure build 129444, Windows unit lane) — zero matches, not a
late one, which no timeout increase would fix.

Root cause is a genuine startup race. ActorSystemImpl.Start() creates the listener fire-and-forget
(ActorSystemImpl.cs:219 'SystemActorOf<DeadLetterListener>("deadLetterListener")'), and the
EventStream subscription itself happens inside DeadLetterListener.PreStart()
(DeadLetterListener.cs:40). Unlike the configured akka.loggers, that startup has no synchronous
handshake before ActorSystem.Create returns, so a dead letter published in the window before PreStart
runs has no subscriber, is dropped, and never produces a 'not delivered' log at all.

Resolve /system/deadLetterListener before exercising the dead letter. An actor always processes its
Create system message (which runs PreStart) before any user message, so a successful Identify reply
is proof the DeadLetter subscription is in place — a real happens-before, not a padded wait.

Verified: full ActorSystemSpec green; the dead-letter test 12/12 at 2x core saturation (a
non-regression check — the race is too rare to reproduce locally on demand).

(cherry picked from commit d5993ab)

* De-flake two TestKit timing tests (TestFSMRef state timeout, WaitForRadioSilence reset) in Akka.TestKit and Akka.Hosting.TestKit (#8615)

* TestFSMRefSpec: time the state timeout from before it is set, drop the 500 ms cap

A_TestFSMRef_must_allow_access_to_internal_state failed in the Akka.Hosting
copy with "Block took 00:00:01.82, exceeding 00:00:00.50" on a 2-vCPU Linux
agent. The StateTimeout travels scheduler -> mailbox -> ThreadPool-backed
dispatcher, and the synchronous Within/AwaitCondition blocked a pool thread
while it waited, so a starved agent easily needs more than 500 ms.

Both copies (Akka.Hosting.TestKit.Tests and Akka.TestKit.Tests) now await
the condition with a Dilated(5s) bound and assert the 80 ms lower bound with
a Stopwatch started before SetStateTimeout. Within's clock started after it,
so a stall between the two calls could have made the timeout look early.

* ReceiveTests: prove the radio-silence reset with a stopwatch instead of a 1.5 s margin

WaitForRadioSilenceAsync_should_reset_timer_twice_only returned {1} instead
of {1,2} in the Akka.Hosting copy on a 2-vCPU Windows agent. Message 2 was
sent 1.5 s after message 1 into a 3 s window that message 1 had just opened,
so a 1.5 s oversleep on the test thread pushed it out.

Both copies now send 2 only 400 ms after 1, in a 4 s window, and prove the
reset from the elapsed time: the silence cannot end before max after 2 was
sent, while without a reset it would end about 400 ms sooner. Delays only
lengthen the measured time. Renamed to what it tests; the maxMessages
violation is already covered by WaitForRadioSilenceAsync_should_fail_immediately_with_bad_input.

(cherry picked from commit 0ea28f8)

* De-flake scheduler cancel test and Hosting AwaitAssert test (#8712)

TaskBasedScheduler_TellScheduler_Cancellation_Tests: a repeater tick that fires before Cancel() runs (test thread stalled past the 150ms interval) sits in the TestActor queue and tripped ExpectNoMsg. Drain in-flight ticks, then require two intervals of silence, bounded by a deadline so a broken cancel still fails.

Akka.Hosting.TestKit AwaitAssertTests: convert to AwaitAssertAsync and WithinAsync, and raise the upper bound from 1s (dilated to 2s) to 10s so a starved host cannot trip it.

(cherry picked from commit c01ec35)

* TCK: give each SaveSnapshot spec actor a fresh name (#8734)

The spec stopped an actor, waited for Terminated and recreated it under the same
name. The parent frees the name only after the watcher saw Terminated, so the
recreate failed with InvalidActorNameException in roughly one run in three.

(cherry picked from commit 84461de)
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