mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
8369227: Virtual thread stuck in PARKED state
Reviewed-by: pchilanomate
This commit is contained in:
parent
2074b975c3
commit
f83918c692
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
120
test/jdk/java/lang/Thread/virtual/stress/ParkAfterTimedPark.java
Normal file
120
test/jdk/java/lang/Thread/virtual/stress/ParkAfterTimedPark.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user