Skip to content

feat: make ForkJoinPool minimum-runnable configurable and improve pool documentation#2871

Merged
He-Pin merged 2 commits intomainfrom
fix-jdk21-fjp-configuration
Apr 18, 2026
Merged

feat: make ForkJoinPool minimum-runnable configurable and improve pool documentation#2871
He-Pin merged 2 commits intomainfrom
fix-jdk21-fjp-configuration

Conversation

@He-Pin
Copy link
Copy Markdown
Member

@He-Pin He-Pin commented Apr 18, 2026

Summary

Two targeted improvements to the ForkJoinPool executor configuration — no behavior change by default, purely additive.

Changes

1. Configurable minimum-runnable

actor/src/main/resources/reference.conf

fork-join-executor {
  # Default = 1 (matches JDK ForkJoinPool prior behavior)
  minimum-runnable = 1
}

actor/src/main/scala/org/apache/pekko/dispatch/ForkJoinExecutorConfigurator.scala

Binary compatibility: new params carry default values; class is @InternalApi, so MiMa exclusion is not required.

2. Improved maximum-pool-size documentation

The existing comment was minimal. The new comment explains that on JDK 21+:

  • Compensation threads are created up to maximum-pool-size
  • Tuning this alongside minimum-runnable helps under heavy actor-round-trip workloads

What this PR does NOT change

  • Default dispatcher mode remains FIFO (no LIFO change)
  • Stream test dispatcher is unchanged
  • Default minimum-runnable = 1 preserves existing behaviour

Related

He-Pin and others added 2 commits April 19, 2026 03:09
…21+ starvation

Motivation:
PekkoForkJoinPool hard-coded minimumRunnable=1 in the JDK 9+ ForkJoinPool
constructor. This is the threshold below which the pool creates compensation
threads to maintain progress. On JDK 21+ (see JDK-8300995 / JDK-8321335),
the compensation-thread mechanism for asyncMode=FIFO pools became more
conservative, making it harder to recover from actor-heavy workloads where
the single "runnable" threshold is insufficient under load.

Modification:
- Added minimum-runnable = 1 to fork-join-executor in reference.conf (default
  preserves existing behaviour; documented relationship to JDK-8300995).
- PekkoForkJoinPool now accepts minimumRunnable as a constructor parameter
  (default 1 for binary compatibility).
- ForkJoinExecutorServiceFactory reads minimum-runnable from config and
  forwards it to PekkoForkJoinPool.

Result:
Users experiencing dispatcher starvation on JDK 21+ can now increase
minimum-runnable (e.g. to parallelism/2) to prompt earlier compensation-
thread creation without needing to patch Pekko itself. All existing
actor-tests/dispatch tests pass unchanged (71 passed, 1 pending).

Fixes: #2870

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…on thread behavior

Motivation:
The previous comment was minimal and did not explain how maximum-pool-size
interacts with ForkJoinPool compensation threads on JDK 21+.

Modification:
Expand the maximum-pool-size comment to describe that on JDK 21+ the pool
uses this as a cap for compensation threads. Tuning it closer to the
target parallelism can reduce latency under actor-heavy workloads
(see JDK-8300995 tracked in #2870).

Result:
Users who see JDK 21+ ForkJoinPool scheduling jitter now have actionable
guidance directly in the default configuration.

References: #2870

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@He-Pin He-Pin force-pushed the fix-jdk21-fjp-configuration branch from d0d208f to 75e6540 Compare April 18, 2026 19:09
@He-Pin He-Pin changed the title fix: configurable ForkJoinPool minimum-runnable and LIFO stream dispatcher for JDK 21+ regression feat: make ForkJoinPool minimum-runnable configurable and improve pool documentation Apr 18, 2026
@He-Pin He-Pin marked this pull request as ready for review April 18, 2026 19:13
@He-Pin He-Pin requested a review from pjfanning April 18, 2026 19:15
@He-Pin He-Pin added the performance Related to performance label Apr 18, 2026
@He-Pin He-Pin added this to the 2.0.0-M2 milestone Apr 18, 2026
Copy link
Copy Markdown
Member

@pjfanning pjfanning left a comment

Choose a reason for hiding this comment

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

lgtm

@He-Pin He-Pin merged commit 1ab254a into main Apr 18, 2026
9 checks passed
@He-Pin He-Pin deleted the fix-jdk21-fjp-configuration branch April 18, 2026 20:33
He-Pin added a commit that referenced this pull request Apr 21, 2026
Motivation:
JDK-8300995 causes ForkJoinPool compensation-thread starvation, leading
to sporadic test timeouts when actors block on reply futures. Virtual
threads (JDK 21+) bypass this limitation by unmounting when blocking,
providing equivalent or better performance. See issue #2870.

Modification:
1. stream-testkit/reference.conf: Add PEKKO_VIRTUALIZE_DISPATCHER env var
   support with safe fallback to 'off' for stable local testing
2. .github/workflows/nightly-builds.yml: Set env var conditionally for JDK
   21+, with detailed comments explaining TIMEFACTOR logic
3. CONTRIBUTING.md: Document virtual thread testing with cleanup guidance
   and safe one-liner for developers
4. Virtual thread enablement only affects nightly CI on JDK 21+; local
   development defaults to OFF for stability

Result:
- Virtual threads automatically enabled on JDK 21+ in nightly builds
- Local tests safely default to virtual threads OFF for stability
- Nightly test reliability improved; timeouts addressed
- Binary compatibility maintained; MiMa checks pass

References:
- Issue #2870: Compensation-thread starvation investigation
- Issue #2573: JDK 25 ForkJoinPool scheduling regression
- PR #2871: ForkJoinPool minimum-runnable tuning (JDK < 21 fallback)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Related to performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants