diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index d51b4ba0770..73fa3eea1bd 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -610,12 +610,11 @@ public class ForkJoinPool extends AbstractExecutorService { * it tries to deactivate()), giving up (and rescanning) on "ctl" * contention. To avoid missed signals during deactivation, the * method rescans and reactivates if there may have been a missed - * (external) signal during deactivation. To reduce false-alarm - * reactivations while doing so, we scan multiple times - * (analogously to method quiescent()) before trying to - * reactivate. Because idle workers are often not yet blocked - * (parked), we use a WorkQueue field to advertise that a waiter - * actually needs unparking upon signal. + * signal during deactivation. To reduce false-alarm reactivations + * while doing so, we scan multiple times (analogously to method + * quiescent()) before trying to reactivate. Because idle workers + * are often not yet blocked (parked), we use a WorkQueue field to + * advertise that a waiter actually needs unparking upon signal. * * Quiescence. Workers scan looking for work, giving up when they * don't find any, without being sure that none are available. @@ -1996,7 +1995,7 @@ public class ForkJoinPool extends AbstractExecutorService { return IDLE; int p = phase | IDLE, activePhase = phase + (IDLE << 1); long pc = ctl, qc = (activePhase & LMASK) | ((pc - RC_UNIT) & UMASK); - w.stackPred = (int)pc; // set ctl stack link + int sp = w.stackPred = (int)pc; // set ctl stack link w.phase = p; if (!compareAndSetCtl(pc, qc)) // try to enqueue return w.phase = phase; // back out on possible signal @@ -2006,18 +2005,18 @@ public class ForkJoinPool extends AbstractExecutorService { (qs = queues) == null || (n = qs.length) <= 0) return IDLE; // terminating int prechecks = Math.min(ac, 2); // reactivation threshold - for (int k = Math.max(n + (n << 1), SPIN_WAITS << 1);;) { - WorkQueue q; int cap; ForkJoinTask[] a; + for (int k = Math.max(n << 2, SPIN_WAITS << 1);;) { + WorkQueue q; int cap; ForkJoinTask[] a; long c; if (w.phase == activePhase) return activePhase; if (--k < 0) return awaitWork(w, p); // block, drop, or exit - if ((k & 1) != 0) - Thread.onSpinWait(); // interleave spins and rechecks - else if ((q = qs[k & (n - 1)]) != null && - (a = q.array) != null && (cap = a.length) > 0 && + if ((q = qs[k & (n - 1)]) == null) + Thread.onSpinWait(); + else if ((a = q.array) != null && (cap = a.length) > 0 && a[q.base & (cap - 1)] != null && --prechecks < 0 && - ctl == qc && compareAndSetCtl(qc, pc)) + (int)(c = ctl) == activePhase && + compareAndSetCtl(c, (sp & LMASK) | ((c + RC_UNIT) & UMASK))) return w.phase = activePhase; // reactivate } } diff --git a/test/jdk/java/lang/Thread/virtual/Starvation.java b/test/jdk/java/lang/Thread/virtual/Starvation.java new file mode 100644 index 00000000000..c767cffbdf5 --- /dev/null +++ b/test/jdk/java/lang/Thread/virtual/Starvation.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @library /test/lib + * @bug 8345294 + * @run main/othervm --enable-native-access=ALL-UNNAMED Starvation 100000 + */ + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import jdk.test.lib.thread.VThreadPinner; + +public class Starvation { + public static void main(String[] args) throws Exception { + int iterations = Integer.parseInt(args[0]); + + for (int i = 0; i < iterations; i++) { + var exRef = new AtomicReference(); + Thread thread = Thread.startVirtualThread(() -> { + try { + runTest(); + } catch (Exception e) { + exRef.set(e); + } + }); + while (!thread.join(Duration.ofSeconds(1))) { + System.out.format("%s iteration %d waiting for %s%n", Instant.now(), i, thread); + } + Exception ex = exRef.get(); + if (ex != null) { + throw ex; + } + } + } + + static void runTest() throws InterruptedException { + int nprocs = Runtime.getRuntime().availableProcessors(); + + var threads = new ArrayList(); + Object lock = new Object(); + synchronized (lock) { + for (int i = 0; i < nprocs - 1; i++) { + var started = new CountDownLatch(1); + Thread thread = Thread.startVirtualThread(() -> { + started.countDown(); + VThreadPinner.runPinned(() -> { + synchronized (lock) { + } + }); + }); + started.await(); + threads.add(thread); + } + } + + for (Thread t : threads) { + t.join(); + } + } +}