diff --git a/src/java.base/share/classes/java/lang/VirtualThread.java b/src/java.base/share/classes/java/lang/VirtualThread.java index 514bf07e665..93862db9105 100644 --- a/src/java.base/share/classes/java/lang/VirtualThread.java +++ b/src/java.base/share/classes/java/lang/VirtualThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -89,15 +89,19 @@ final class VirtualThread extends BaseVirtualThread { * * RUNNING -> PARKING // Thread parking with LockSupport.park * PARKING -> PARKED // cont.yield successful, parked indefinitely - * PARKING -> PINNED // cont.yield failed, parked indefinitely on carrier * PARKED -> UNPARKED // unparked, may be scheduled to continue - * PINNED -> RUNNING // unparked, continue execution on same carrier * UNPARKED -> RUNNING // continue execution after park * + * PARKING -> RUNNING // cont.yield failed, need to park on carrier + * RUNNING -> PINNED // park on carrier + * PINNED -> RUNNING // unparked, continue execution on same carrier + * * RUNNING -> TIMED_PARKING // Thread parking with LockSupport.parkNanos * TIMED_PARKING -> TIMED_PARKED // cont.yield successful, timed-parked - * TIMED_PARKING -> TIMED_PINNED // cont.yield failed, timed-parked on carrier * TIMED_PARKED -> UNPARKED // unparked, may be scheduled to continue + * + * TIMED_PARKING -> RUNNING // cont.yield failed, need to park on carrier + * RUNNING -> TIMED_PINNED // park on carrier * TIMED_PINNED -> RUNNING // unparked, continue execution on same carrier * * RUNNING -> BLOCKING // blocking on monitor enter @@ -108,7 +112,7 @@ final class VirtualThread extends BaseVirtualThread { * RUNNING -> WAITING // transitional state during wait on monitor * WAITING -> WAIT // waiting on monitor * WAIT -> BLOCKED // notified, waiting to be unblocked by monitor owner - * WAIT -> UNBLOCKED // timed-out/interrupted + * WAIT -> UNBLOCKED // interrupted * * RUNNING -> TIMED_WAITING // transition state during timed-waiting on monitor * TIMED_WAITING -> TIMED_WAIT // timed-waiting on monitor @@ -856,16 +860,20 @@ final class VirtualThread extends BaseVirtualThread { * Re-enables this virtual thread for scheduling. If this virtual thread is parked * then its task is scheduled to continue, otherwise its next call to {@code park} or * {@linkplain #parkNanos(long) parkNanos} is guaranteed not to block. + * @param lazySubmit to use lazySubmit if possible * @throws RejectedExecutionException if the scheduler cannot accept a task */ - @Override - void unpark() { + private void unpark(boolean lazySubmit) { if (!getAndSetParkPermit(true) && currentThread() != this) { int s = state(); // unparked while parked if ((s == PARKED || s == TIMED_PARKED) && compareAndSetState(s, UNPARKED)) { - submitRunContinuation(); + if (lazySubmit) { + lazySubmitRunContinuation(); + } else { + submitRunContinuation(); + } return; } @@ -888,6 +896,11 @@ final class VirtualThread extends BaseVirtualThread { } } + @Override + void unpark() { + unpark(false); + } + /** * Invoked by unblocker thread to unblock this virtual thread. */ @@ -904,11 +917,7 @@ final class VirtualThread extends BaseVirtualThread { */ private void parkTimeoutExpired() { assert !VirtualThread.currentThread().isVirtual(); - if (!getAndSetParkPermit(true) - && (state() == TIMED_PARKED) - && compareAndSetState(TIMED_PARKED, UNPARKED)) { - lazySubmitRunContinuation(); - } + unpark(true); } /** diff --git a/test/jdk/java/lang/Thread/virtual/stress/ParkAfterTimedPark.java b/test/jdk/java/lang/Thread/virtual/stress/ParkAfterTimedPark.java new file mode 100644 index 00000000000..1b173271a79 --- /dev/null +++ b/test/jdk/java/lang/Thread/virtual/stress/ParkAfterTimedPark.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2025, 2026, 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 id=parked + * @bug 8369227 + * @summary Stress test untimed park after a timed park when a thread is unparked around the + * same time that the timeout expires. + * @library /test/lib + * @run main/othervm --enable-native-access=ALL-UNNAMED ParkAfterTimedPark 200 false + */ + +/* + * @test id=pinned + * @summary Stress test untimed park, while pinned, and after a timed park when a thread is + * unparked around the same time that the timeout expires. + * @library /test/lib + * @run main/othervm --enable-native-access=ALL-UNNAMED ParkAfterTimedPark 200 true + */ + +import java.time.Instant; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; +import jdk.test.lib.thread.VThreadPinner; + +public class ParkAfterTimedPark { + public static void main(String[] args) throws Exception { + int iterations = (args.length > 0) ? Integer.parseInt(args[0]) : 100; + boolean pinned = (args.length > 1) ? Boolean.parseBoolean(args[1]) : false; + + for (int i = 1; i <= iterations; i++) { + System.out.println(Instant.now() + " => " + i + " of " + iterations); + for (int timeout = 1; timeout <= 10; timeout++) { + test(timeout, true); + } + } + } + + /** + * Creates two virtual threads. The first does a timed-park for the given time, + * then parks in CountDownLatch.await. A second virtual thread unparks the first + * around the same time that the timeout for the first expires. + */ + private static void test(int millis, boolean pinned) throws Exception { + long nanos = TimeUnit.MILLISECONDS.toNanos(millis); + + var finish = new CountDownLatch(1); + + Thread thread1 = Thread.startVirtualThread(() -> { + LockSupport.parkNanos(nanos); + boolean done = false; + while (!done) { + try { + if (pinned) { + VThreadPinner.runPinned(() -> { + finish.await(); + }); + } else { + finish.await(); + } + done = true; + } catch (InterruptedException e) { } + } + }); + + Thread thread2 = Thread.startVirtualThread(() -> { + int delta = ThreadLocalRandom.current().nextInt(millis); + boolean done = false; + while (!done) { + try { + Thread.sleep(millis - delta); + done = true; + } catch (InterruptedException e) { } + } + LockSupport.unpark(thread1); + }); + + // wait for first thread to park before count down + await(thread1, Thread.State.WAITING); + finish.countDown(); + + thread1.join(); + thread2.join(); + } + + /** + * Waits for the given thread to reach a given state. + */ + private static void await(Thread thread, Thread.State expectedState) throws Exception { + Thread.State state = thread.getState(); + while (state != expectedState) { + if (state == Thread.State.TERMINATED) + throw new RuntimeException("Thread has terminated"); + Thread.sleep(10); + state = thread.getState(); + } + } +} diff --git a/test/jdk/java/lang/Thread/virtual/stress/TimedWaitALot.java b/test/jdk/java/lang/Thread/virtual/stress/TimedWaitALot.java index 6a81a7c5fee..704e299ad8b 100644 --- a/test/jdk/java/lang/Thread/virtual/stress/TimedWaitALot.java +++ b/test/jdk/java/lang/Thread/virtual/stress/TimedWaitALot.java @@ -94,17 +94,20 @@ public class TimedWaitALot { // start thread to Object.notifyAll at around time that the timeout expires if (notify) { - if (ThreadLocalRandom.current().nextBoolean()) { - synchronized (lock) { + executor.submit(() -> { + if (ThreadLocalRandom.current().nextBoolean()) { + synchronized (lock) { + sleepLessThan(timeout); + lock.notifyAll(); + } + } else { sleepLessThan(timeout); - lock.notifyAll(); + synchronized (lock) { + lock.notifyAll(); + } } - } else { - sleepLessThan(timeout); - synchronized (lock) { - lock.notifyAll(); - } - } + return null; + }); } // start thread to interrupt first thread at around time that the timeout expires