8369227: Virtual thread stuck in PARKED state

Reviewed-by: pchilanomate
This commit is contained in:
Alan Bateman 2026-01-07 10:43:11 +00:00
parent 2074b975c3
commit f83918c692
3 changed files with 154 additions and 22 deletions

View File

@ -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);
}
/**

View File

@ -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();
}
}
}

View File

@ -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