You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Pekko nightly CI has observed intermittent test failures on JDK 21 and JDK 25 that trace to a ForkJoinPool behavioral regression in JDK 21+ (JDK-8300995, JDK-8321335).
This issue documents the root cause and proposed mitigations for Pekko's stream tests. Related: #2573.
Root Cause
Pekko's default fork-join-executor uses asyncMode=true (FIFO, via task-peeking-mode = "FIFO"). In JDK 21+, this mode interacts poorly with actor round-trip workloads:
When actor A sends a message to actor B and waits for a reply, the reply task is placed at the back of the FIFO work queue.
With a fixed-size pool (e.g. 8 threads) under load, the reply waits behind all pending tasks before being executed.
The compensation-thread mechanism (which should unblock this) was regressed in JDK 21 for asyncMode=FIFO pools — compensation threads are not reliably created when needed.
This cascades for workloads with many actor hand-offs (e.g. MergeHub buffer=1: one full round-trip per element).
JDK 25 exacerbates the issue because work-stealing heuristics under high CI load diverge further from expected throughput.
Evidence
ForkJoinPoolStarvationSpec has been marked pending on JDK 17+ acknowledging this class of issue
Stream test dispatcher: Changed task-peeking-mode from FIFO to LIFO in stream-testkit/src/test/resources/reference.conf. With LIFO, response tasks go to the front of each worker's deque and are processed immediately. Actor mailbox ordering is unaffected (UnboundedMailbox is always FIFO).
minimum-runnable now configurable: PekkoForkJoinPool hardcoded minimumRunnable=1 in the JDK 9+ ForkJoinPool constructor. This is now exposed as minimum-runnable in the fork-join-executor config block (default: 1, preserving current behaviour). Users experiencing actor starvation on JDK 21+ can increase this to e.g. parallelism/2.
Investigate whether virtualize = on (JDK 21+, Pekko v1.2.0) should be the recommended mode for actor dispatchers on JDK 21+. Virtual threads avoid the FJP compensation-thread regression entirely because the scheduler is cooperative rather than preemptive.
Consider increasing the default minimum-runnable on JDK 21+ from 1 to something like parallelism/2 to proactively create compensation threads before complete starvation.
Summary
The Pekko nightly CI has observed intermittent test failures on JDK 21 and JDK 25 that trace to a ForkJoinPool behavioral regression in JDK 21+ (JDK-8300995, JDK-8321335).
This issue documents the root cause and proposed mitigations for Pekko's stream tests. Related: #2573.
Root Cause
Pekko's default
fork-join-executorusesasyncMode=true(FIFO, viatask-peeking-mode = "FIFO"). In JDK 21+, this mode interacts poorly with actor round-trip workloads:MergeHub buffer=1: one full round-trip per element).JDK 25 exacerbates the issue because work-stealing heuristics under high CI load diverge further from expected throughput.
Evidence
ForkJoinPoolStarvationSpechas been markedpendingon JDK 17+ acknowledging this class of issuestream-typed-tests/MapAsyncPartitionedSpec.scala:110-116already has a comment referencing nightly tests (CI): java 25 runs have a lot of stream test failures #2573: "JDK 25 makes 1000-sample property checks noticeably more expensive"Mitigations Applied (PR #2869)
Stream test dispatcher: Changed
task-peeking-modefromFIFOtoLIFOinstream-testkit/src/test/resources/reference.conf. With LIFO, response tasks go to the front of each worker's deque and are processed immediately. Actor mailbox ordering is unaffected (UnboundedMailboxis always FIFO).minimum-runnablenow configurable:PekkoForkJoinPoolhardcodedminimumRunnable=1in the JDK 9+ ForkJoinPool constructor. This is now exposed asminimum-runnablein thefork-join-executorconfig block (default: 1, preserving current behaviour). Users experiencing actor starvation on JDK 21+ can increase this to e.g.parallelism/2.HubSpec / AggregateWithBoundarySpec / TCK timeouts / FlowMapAsyncPartitionedSpec: test-level fixes documented in fix: stabilise stream tests on JDK 25 nightly (timeout scaling, element counts) #2869.
Future Work
virtualize = on(JDK 21+, Pekko v1.2.0) should be the recommended mode for actor dispatchers on JDK 21+. Virtual threads avoid the FJP compensation-thread regression entirely because the scheduler is cooperative rather than preemptive.minimum-runnableon JDK 21+ from 1 to something likeparallelism/2to proactively create compensation threads before complete starvation.