He-Pin opened a new issue, #3235:
URL: https://github.com/apache/pekko/issues/3235

   ### Motivation
   `ManualTime.expectNoMessageFor` uses `Duration.Zero` when checking test 
probes for unexpected messages after advancing the clock. Because scheduled 
tasks run synchronously on the calling thread inside `timePasses`, but messages 
they send to test probe actors are delivered asynchronously through the actor 
system's dispatcher/mailbox, the zero-timeout poll can miss messages that are 
still in flight. This produces false-negative test results: assertions that "no 
messages were received" pass when they should fail.
   
   ### Current behavior
   In 
`actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/scaladsl/ManualTime.scala:76-79`:
   
   ```scala
   def expectNoMessageFor(duration: FiniteDuration, on: TestProbe[?]*): Unit = {
       delegate.timePasses(duration)
       on.foreach(_.expectNoMessage(Duration.Zero))
   }
   ```
   
   The call chain is:
   1. `delegate.timePasses(duration)` runs scheduled `Runnable`s synchronously 
on the calling thread (`ExplicitlyTriggeredScheduler.scala:98`).
   2. Those runnables may send messages to test probe actors via `!`, enqueuing 
them in the actor's mailbox.
   3. The test probe actor processes messages on a separate dispatcher thread, 
calling `queue.offerLast(other)` (`TestProbeImpl.scala:66`).
   4. `expectNoMessage(Duration.Zero)` calls 
`receiveOne_internal(Duration.Zero)`, which does a non-blocking 
`queue.pollFirst()` (`TestProbeImpl.scala:202-203`) with zero wait.
   
   Between steps 2 and 4, there is no synchronization or delay. Messages 
dispatched in step 2 may not yet be in the probe's `LinkedBlockingDeque` when 
step 4 polls.
   
   The same issue exists in the Java DSL at 
`actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/javadsl/ManualTime.scala:76-79`.
   
   Notably, `ExplicitlyTriggeredScheduler.timePasses` itself includes 
`Thread.sleep(100)` at its start (`ExplicitlyTriggeredScheduler.scala:72`) with 
a comment about giving "dispatchers time to clear," acknowledging that async 
message delivery races exist in this context. However, no equivalent delay 
exists between task execution and the `Duration.Zero` check.
   
   ### Expected behavior
   After `timePasses` completes, the `expectNoMessage` check should allow 
sufficient time for any messages dispatched by the scheduled tasks to be 
delivered to the test probe actors. The idiomatic fix is to use the probe's 
default no-message timeout via `expectNoMessage()` (no arguments), which uses 
`settings.ExpectNoMessageDefaultTimeout` (defined in 
`TestKitSettings.scala:77`). This timeout is dilated by the test time factor 
and is specifically designed for "expect no message" assertions.
   
   ### Code evidence
   - 
`actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/scaladsl/ManualTime.scala:78`
 — `Duration.Zero` in Scala DSL
   - 
`actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/javadsl/ManualTime.scala:78`
 — `Duration.ZERO` in Java DSL
   - 
`actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/internal/TestProbeImpl.scala:202-203`
 — zero duration triggers non-blocking `pollFirst`
   - 
`actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/internal/TestProbeImpl.scala:217-218`
 — `expectNoMessage()` (no args) uses `ExpectNoMessageDefaultTimeout`
   - 
`testkit/src/main/scala/org/apache/pekko/testkit/ExplicitlyTriggeredScheduler.scala:72`
 — existing `Thread.sleep(100)` acknowledging the race pattern
   
   ### Reproduction
   Create a test using `ManualTime` where a scheduled task sends a message to a 
test probe actor. Call `expectNoMessageFor` with a duration that triggers the 
scheduled task. Due to the race, the test may intermittently pass (no message 
detected) even though the probe should have received a message during the time 
advance.
   
   A deterministic reproduction would require a slow dispatcher or deliberate 
scheduling to widen the race window, but the fundamental issue is that the 
zero-timeout check is unsound.
   
   ### Impact
   - **Modules affected:** `pekko-actor-testkit-typed` (both Scala and Java 
DSLs)
   - **Use cases affected:** Any test using `ManualTime.expectNoMessageFor` to 
verify that no messages are delivered during a time advance. This is a core 
testing utility documented in the official Pekko testing examples 
(`ManualTimerExampleSpec.scala`).
   - **Severity:** Intermittent false-negative test results. Tests may 
incorrectly pass when a bug exists, reducing confidence in the test suite.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to