Skip to content

fix: prevent carrier starvation in ForkJoinTask context propagation - #628

Closed
zhengziyi0117 wants to merge 1 commit into
arextest:mainfrom
zhengziyi0117:fix/carrier-reference-queue-deadlock
Closed

fix: prevent carrier starvation in ForkJoinTask context propagation#628
zhengziyi0117 wants to merge 1 commit into
arextest:mainfrom
zhengziyi0117:fix/carrier-reference-queue-deadlock

Conversation

@zhengziyi0117

Copy link
Copy Markdown

ForkJoinTask advice can exhaust the default virtual-thread scheduler on JDK 21 by polling CAPTURED_CACHE from carrier threads. If a virtual thread is the first waiter for the cache's ReferenceQueue lock and every carrier then blocks behind it, releasing the lock leaves the virtual thread runnable with no carrier available to resume it.

Skip context capture and replay on jdk.internal.misc.CarrierThread before either ForkJoinTask advice accesses the shared cache. Mounted virtual threads and ordinary ForkJoinPool workers retain the existing behavior. The helper remains Java 8 compatible and targets the JDK's default scheduler.

Regression coverage

ForkJoinCarrierThreadTest starts isolated JVMs with two real carriers and a Byte Buddy test agent weaving ForkJoinTask constructors and execution methods. Explicit reference enqueueing and controlled lock acquisition put the virtual thread first in the AQS queue without relying on GC timing.

  • The unguarded advice reproduces the old failure: after unlock, lockHeld=false, the VT is RUNNABLE, all three waiters remain queued, and neither carrier probe completes.
  • The production advice allows both carrier probes and the VT to complete. It also checks the constructor guard with non-null carrier context, VT context preservation, and ordinary ForkJoinPool context propagation.
  • Child processes have bounded waits and forced cleanup. The reproducer runs on JDK 21 with a ReentrantLock-based ReferenceQueue and skips other implementations.

Validation

On OpenJDK 21.0.2:

mvn -pl arex-instrumentation/internal/arex-executors -am test

364 tests passed (137 bootstrap, 203 instrumentation API, 24 executors), with no failures, errors, or skips. All seven changed Java files also compiled with JDK 8 (Zulu 8.0.422).

To run only the before/after reproducer:

mvn -pl arex-instrumentation/internal/arex-executors -am \
  -Dtest=ForkJoinCarrierThreadTest \
  -Dsurefire.failIfNoSpecifiedTests=false test

The test agent validates the actual advice in the JDK scheduling path; it does not start the full AREX agent or require backend services.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant