8315373: Change VirtualThread to unmount after freezing, re-mount before thawing

8312498: Thread::getState and JVM TI GetThreadState should return TIMED_WAITING virtual thread is timed parked
8312777: notifyJvmtiMount before notifyJvmtiUnmount
8321270: Virtual Thread.yield consumes parking permit
8322818: Thread::getStackTrace can fail with InternalError if virtual thread is timed-parked when pinned
8323002: test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenPinned.java times out on macosx-x64
8323296: java/lang/Thread/virtual/stress/GetStackTraceALotWhenPinned.java#id1 timed out
8316924: java/lang/Thread/virtual/stress/ParkALot.java times out

Backport-of: 9a83d55887
This commit is contained in:
Aleksey Shipilev
2024-02-28 15:36:10 +00:00
committed by Vitaly Provodin
parent 9d82180fe6
commit 63f9947d78
13 changed files with 1109 additions and 292 deletions

View File

@@ -1989,24 +1989,28 @@ int java_lang_VirtualThread::state(oop vthread) {
JavaThreadStatus java_lang_VirtualThread::map_state_to_thread_status(int state) {
JavaThreadStatus status = JavaThreadStatus::NEW;
switch (state) {
case NEW :
switch (state & ~SUSPENDED) {
case NEW:
status = JavaThreadStatus::NEW;
break;
case STARTED :
case RUNNABLE :
case RUNNABLE_SUSPENDED :
case RUNNING :
case PARKING :
case YIELDING :
case STARTED:
case RUNNING:
case PARKING:
case TIMED_PARKING:
case UNPARKED:
case YIELDING:
case YIELDED:
status = JavaThreadStatus::RUNNABLE;
break;
case PARKED :
case PARKED_SUSPENDED :
case PINNED :
case PARKED:
case PINNED:
status = JavaThreadStatus::PARKED;
break;
case TERMINATED :
case TIMED_PARKED:
case TIMED_PINNED:
status = JavaThreadStatus::PARKED_TIMED;
break;
case TERMINATED:
status = JavaThreadStatus::TERMINATED;
break;
default:

View File

@@ -521,20 +521,22 @@ class java_lang_VirtualThread : AllStatic {
JFR_ONLY(static int _jfr_epoch_offset;)
public:
enum {
NEW = 0,
STARTED = 1,
RUNNABLE = 2,
RUNNING = 3,
PARKING = 4,
PARKED = 5,
PINNED = 6,
YIELDING = 7,
TERMINATED = 99,
NEW = 0,
STARTED = 1,
RUNNING = 2,
PARKING = 3,
PARKED = 4,
PINNED = 5,
TIMED_PARKING = 6,
TIMED_PARKED = 7,
TIMED_PINNED = 8,
UNPARKED = 9,
YIELDING = 10,
YIELDED = 11,
TERMINATED = 99,
// can be suspended from scheduling when unmounted
SUSPENDED = 1 << 8,
RUNNABLE_SUSPENDED = (RUNNABLE | SUSPENDED),
PARKED_SUSPENDED = (PARKED | SUSPENDED)
// additional state bits
SUSPENDED = 1 << 8, // suspended when unmounted
};
static void compute_offsets();

View File

@@ -138,9 +138,9 @@ void JfrStackFrame::write(JfrCheckpointWriter& cpw) const {
class JfrVframeStream : public vframeStreamCommon {
private:
bool _vthread;
const ContinuationEntry* _cont_entry;
bool _async_mode;
bool _vthread;
bool step_to_sender();
void next_frame();
public:
@@ -165,8 +165,9 @@ JfrVframeStream::JfrVframeStream(JavaThread* jt, const frame& fr, bool stop_at_j
RegisterMap::UpdateMap::skip,
RegisterMap::ProcessFrames::skip,
walk_continuation(jt))),
_cont_entry(JfrThreadLocal::is_vthread(jt) ? jt->last_continuation() : nullptr),
_async_mode(async_mode), _vthread(JfrThreadLocal::is_vthread(jt)) {
_vthread(JfrThreadLocal::is_vthread(jt)),
_cont_entry(_vthread ? jt->last_continuation() : nullptr),
_async_mode(async_mode) {
assert(!_vthread || _cont_entry != nullptr, "invariant");
_reg_map.set_async(async_mode);
_frame = fr;

View File

@@ -395,11 +395,14 @@ traceid JfrThreadLocal::thread_id(const Thread* t) {
return t->jfr_thread_local()->_thread_id_alias;
}
JfrThreadLocal* const tl = t->jfr_thread_local();
if (!t->is_Java_thread() || !Atomic::load_acquire(&tl->_vthread)) {
if (!t->is_Java_thread()) {
return jvm_thread_id(t, tl);
}
const JavaThread* jt = JavaThread::cast(t);
if (!is_vthread(jt)) {
return jvm_thread_id(t, tl);
}
// virtual thread
const JavaThread* jt = JavaThread::cast(t);
const traceid tid = vthread_id(jt);
assert(tid != 0, "invariant");
if (!tl->is_vthread_excluded()) {
@@ -456,7 +459,7 @@ traceid JfrThreadLocal::jvm_thread_id(const Thread* t) {
bool JfrThreadLocal::is_vthread(const JavaThread* jt) {
assert(jt != nullptr, "invariant");
return Atomic::load_acquire(&jt->jfr_thread_local()->_vthread);
return Atomic::load_acquire(&jt->jfr_thread_local()->_vthread) && jt->last_continuation() != nullptr;
}
inline bool is_virtual(const JavaThread* jt, oop thread) {

View File

@@ -86,41 +86,52 @@ final class VirtualThread extends BaseVirtualThread {
private volatile int state;
/*
* Virtual thread state and transitions:
* Virtual thread state transitions:
*
* NEW -> STARTED // Thread.start
* NEW -> STARTED // Thread.start, schedule to run
* STARTED -> TERMINATED // failed to start
* STARTED -> RUNNING // first run
* RUNNING -> TERMINATED // done
*
* RUNNING -> PARKING // Thread attempts to park
* PARKING -> PARKED // cont.yield successful, thread is parked
* PARKING -> PINNED // cont.yield failed, thread is pinned
* 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
*
* PARKED -> RUNNABLE // unpark or interrupted
* PINNED -> RUNNABLE // unpark or interrupted
*
* RUNNABLE -> RUNNING // continue execution
* 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_PINNED -> RUNNING // unparked, continue execution on same carrier
*
* RUNNING -> YIELDING // Thread.yield
* YIELDING -> RUNNABLE // yield successful
* YIELDING -> RUNNING // yield failed
*
* RUNNING -> TERMINATED // done
* YIELDING -> YIELDED // cont.yield successful, may be scheduled to continue
* YIELDING -> RUNNING // cont.yield failed
* YIELDED -> RUNNING // continue execution after Thread.yield
*/
private static final int NEW = 0;
private static final int STARTED = 1;
private static final int RUNNABLE = 2; // runnable-unmounted
private static final int RUNNING = 3; // runnable-mounted
private static final int PARKING = 4;
private static final int PARKED = 5; // unmounted
private static final int PINNED = 6; // mounted
private static final int YIELDING = 7; // Thread.yield
private static final int RUNNING = 2; // runnable-mounted
// untimed and timed parking
private static final int PARKING = 3;
private static final int PARKED = 4; // unmounted
private static final int PINNED = 5; // mounted
private static final int TIMED_PARKING = 6;
private static final int TIMED_PARKED = 7; // unmounted
private static final int TIMED_PINNED = 8; // mounted
private static final int UNPARKED = 9; // unmounted but runnable
// Thread.yield
private static final int YIELDING = 10;
private static final int YIELDED = 11; // unmounted but runnable
private static final int TERMINATED = 99; // final state
// can be suspended from scheduling when unmounted
private static final int SUSPENDED = 1 << 8;
private static final int RUNNABLE_SUSPENDED = (RUNNABLE | SUSPENDED);
private static final int PARKED_SUSPENDED = (PARKED | SUSPENDED);
// parking permit
private volatile boolean parkPermit;
@@ -194,8 +205,11 @@ final class VirtualThread extends BaseVirtualThread {
}
/**
* Runs or continues execution of the continuation on the current thread.
* Runs or continues execution on the current thread. The virtual thread is mounted
* on the current thread before the task runs or continues. It unmounts when the
* task completes or yields.
*/
@ChangesCurrentThread
private void runContinuation() {
// the carrier must be a platform thread
if (Thread.currentThread().isVirtual()) {
@@ -204,24 +218,27 @@ final class VirtualThread extends BaseVirtualThread {
// set state to RUNNING
int initialState = state();
if (initialState == STARTED && compareAndSetState(STARTED, RUNNING)) {
// first run
} else if (initialState == RUNNABLE && compareAndSetState(RUNNABLE, RUNNING)) {
// consume parking permit
setParkPermit(false);
if (initialState == STARTED || initialState == UNPARKED || initialState == YIELDED) {
// newly started or continue after parking/blocking/Thread.yield
if (!compareAndSetState(initialState, RUNNING)) {
return;
}
// consume parking permit when continuing after parking
if (initialState == UNPARKED) {
setParkPermit(false);
}
} else {
// not runnable
return;
}
// notify JVMTI before mount
notifyJvmtiMount(/*hide*/true);
mount();
try {
cont.run();
} finally {
unmount();
if (cont.isDone()) {
afterTerminate();
afterDone();
} else {
afterYield();
}
@@ -231,8 +248,7 @@ final class VirtualThread extends BaseVirtualThread {
/**
* Submits the runContinuation task to the scheduler. For the default scheduler,
* and calling it on a worker thread, the task will be pushed to the local queue,
* otherwise it will be pushed to a submission queue.
*
* otherwise it will be pushed to an external submission queue.
* @throws RejectedExecutionException
*/
private void submitRunContinuation() {
@@ -245,7 +261,7 @@ final class VirtualThread extends BaseVirtualThread {
}
/**
* Submits the runContinuation task to the scheduler with a lazy submit.
* Submits the runContinuation task to given scheduler with a lazy submit.
* @throws RejectedExecutionException
* @see ForkJoinPool#lazySubmit(ForkJoinTask)
*/
@@ -259,7 +275,7 @@ final class VirtualThread extends BaseVirtualThread {
}
/**
* Submits the runContinuation task to the scheduler as an external submit.
* Submits the runContinuation task to the given scheduler as an external submit.
* @throws RejectedExecutionException
* @see ForkJoinPool#externalSubmit(ForkJoinTask)
*/
@@ -285,16 +301,12 @@ final class VirtualThread extends BaseVirtualThread {
}
/**
* Runs a task in the context of this virtual thread. The virtual thread is
* mounted on the current (carrier) thread before the task runs. It unmounts
* from its carrier thread when the task completes.
* Runs a task in the context of this virtual thread.
*/
@ChangesCurrentThread
private void run(Runnable task) {
assert state == RUNNING;
assert Thread.currentThread() == this && state == RUNNING;
// first mount
mount();
// notify JVMTI, may post VirtualThreadStart event
notifyJvmtiStart();
// emit JFR event if enabled
@@ -322,12 +334,8 @@ final class VirtualThread extends BaseVirtualThread {
}
} finally {
// last unmount
// notify JVMTI, may post VirtualThreadEnd event
notifyJvmtiEnd();
unmount();
// final state
setState(TERMINATED);
}
}
}
@@ -339,6 +347,9 @@ final class VirtualThread extends BaseVirtualThread {
@ChangesCurrentThread
@ReservedStackAccess
private void mount() {
// notify JVMTI before mount
notifyJvmtiMount(/*hide*/true);
// sets the carrier thread
Thread carrier = Thread.currentCarrierThread();
setCarrierThread(carrier);
@@ -375,6 +386,9 @@ final class VirtualThread extends BaseVirtualThread {
setCarrierThread(null);
}
carrier.clearInterrupt();
// notify JVMTI after unmount
notifyJvmtiUnmount(/*hide*/false);
}
/**
@@ -417,21 +431,15 @@ final class VirtualThread extends BaseVirtualThread {
}
/**
* Unmounts this virtual thread, invokes Continuation.yield, and re-mounts the
* thread when continued. When enabled, JVMTI must be notified from this method.
* @return true if the yield was successful
* Invokes Continuation.yield, notifying JVMTI (if enabled) to hide frames until
* the continuation continues.
*/
@Hidden
@ChangesCurrentThread
private boolean yieldContinuation() {
// unmount
notifyJvmtiUnmount(/*hide*/true);
unmount();
try {
return Continuation.yield(VTHREAD_SCOPE);
} finally {
// re-mount
mount();
notifyJvmtiMount(/*hide*/false);
}
}
@@ -442,17 +450,17 @@ final class VirtualThread extends BaseVirtualThread {
* If yielding due to Thread.yield then it just submits the task to continue.
*/
private void afterYield() {
assert carrierThread == null;
int s = state();
assert (s == PARKING || s == YIELDING) && (carrierThread == null);
if (s == PARKING) {
setState(PARKED);
// notify JVMTI that unmount has completed, thread is parked
notifyJvmtiUnmount(/*hide*/false);
// LockSupport.park/parkNanos
if (s == PARKING || s == TIMED_PARKING) {
int newState = (s == PARKING) ? PARKED : TIMED_PARKED;
setState(newState);
// may have been unparked while parking
if (parkPermit && compareAndSetState(PARKED, RUNNABLE)) {
if (parkPermit && compareAndSetState(newState, UNPARKED)) {
// lazy submit to continue on the current thread as carrier if possible
if (currentThread() instanceof CarrierThread ct) {
lazySubmitRunContinuation(ct.getPool());
@@ -461,11 +469,12 @@ final class VirtualThread extends BaseVirtualThread {
}
}
} else if (s == YIELDING) { // Thread.yield
setState(RUNNABLE);
return;
}
// notify JVMTI that unmount has completed, thread is runnable
notifyJvmtiUnmount(/*hide*/false);
// Thread.yield
if (s == YIELDING) {
setState(YIELDED);
// external submit if there are no tasks in the local task queue
if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) {
@@ -473,30 +482,28 @@ final class VirtualThread extends BaseVirtualThread {
} else {
submitRunContinuation();
}
return;
}
assert false;
}
/**
* Invoked after the thread terminates execution. It notifies anyone
* waiting for the thread to terminate.
* Invoked after the continuation completes.
*/
private void afterTerminate() {
afterTerminate(true, true);
private void afterDone() {
afterDone(true);
}
/**
* Invoked after the thread terminates (or start failed). This method
* notifies anyone waiting for the thread to terminate.
* Invoked after the continuation completes (or start failed). Sets the thread
* state to TERMINATED and notifies anyone waiting for the thread to terminate.
*
* @param notifyContainer true if its container should be notified
* @param executed true if the thread executed, false if it failed to start
*/
private void afterTerminate(boolean notifyContainer, boolean executed) {
assert (state() == TERMINATED) && (carrierThread == null);
if (executed) {
notifyJvmtiUnmount(/*hide*/false);
}
private void afterDone(boolean notifyContainer) {
assert carrierThread == null;
setState(TERMINATED);
// notify anyone waiting for this virtual thread to terminate
CountDownLatch termination = this.termination;
@@ -546,8 +553,7 @@ final class VirtualThread extends BaseVirtualThread {
started = true;
} finally {
if (!started) {
setState(TERMINATED);
afterTerminate(addedToContainer, /*executed*/false);
afterDone(addedToContainer);
}
}
}
@@ -615,14 +621,14 @@ final class VirtualThread extends BaseVirtualThread {
long startTime = System.nanoTime();
boolean yielded = false;
Future<?> unparker = scheduleUnpark(this::unpark, nanos);
setState(PARKING);
Future<?> unparker = scheduleUnpark(nanos); // may throw OOME
setState(TIMED_PARKING);
try {
yielded = yieldContinuation(); // may throw
} finally {
assert (Thread.currentThread() == this) && (yielded == (state() == RUNNING));
if (!yielded) {
assert state() == PARKING;
assert state() == TIMED_PARKING;
setState(RUNNING);
}
cancel(unparker);
@@ -654,7 +660,7 @@ final class VirtualThread extends BaseVirtualThread {
event = null;
}
setState(PINNED);
setState(timed ? TIMED_PINNED : PINNED);
try {
if (!parkPermit) {
if (!timed) {
@@ -680,14 +686,15 @@ final class VirtualThread extends BaseVirtualThread {
}
/**
* Schedule an unpark task to run after a given delay.
* Schedule this virtual thread to be unparked after a given delay.
*/
@ChangesCurrentThread
private Future<?> scheduleUnpark(Runnable unparker, long nanos) {
private Future<?> scheduleUnpark(long nanos) {
assert Thread.currentThread() == this;
// need to switch to current carrier thread to avoid nested parking
switchToCarrierThread();
try {
return UNPARKER.schedule(unparker, nanos, NANOSECONDS);
return UNPARKER.schedule(this::unpark, nanos, NANOSECONDS);
} finally {
switchToVirtualThread(this);
}
@@ -722,7 +729,8 @@ final class VirtualThread extends BaseVirtualThread {
Thread currentThread = Thread.currentThread();
if (!getAndSetParkPermit(true) && currentThread != this) {
int s = state();
if (s == PARKED && compareAndSetState(PARKED, RUNNABLE)) {
boolean parked = (s == PARKED) || (s == TIMED_PARKED);
if (parked && compareAndSetState(s, UNPARKED)) {
if (currentThread instanceof VirtualThread vthread) {
vthread.switchToCarrierThread();
try {
@@ -733,11 +741,11 @@ final class VirtualThread extends BaseVirtualThread {
} else {
submitRunContinuation();
}
} else if (s == PINNED) {
// unpark carrier thread when pinned.
} else if ((s == PINNED) || (s == TIMED_PINNED)) {
// unpark carrier thread when pinned
synchronized (carrierThreadAccessLock()) {
Thread carrier = carrierThread;
if (carrier != null && state() == PINNED) {
if (carrier != null && ((s = state()) == PINNED || s == TIMED_PINNED)) {
U.unpark(carrier);
}
}
@@ -874,7 +882,8 @@ final class VirtualThread extends BaseVirtualThread {
@Override
Thread.State threadState() {
switch (state()) {
int s = state();
switch (s & ~SUSPENDED) {
case NEW:
return Thread.State.NEW;
case STARTED:
@@ -884,8 +893,8 @@ final class VirtualThread extends BaseVirtualThread {
} else {
return Thread.State.RUNNABLE;
}
case RUNNABLE:
case RUNNABLE_SUSPENDED:
case UNPARKED:
case YIELDED:
// runnable, not mounted
return Thread.State.RUNNABLE;
case RUNNING:
@@ -899,13 +908,16 @@ final class VirtualThread extends BaseVirtualThread {
// runnable, mounted
return Thread.State.RUNNABLE;
case PARKING:
case TIMED_PARKING:
case YIELDING:
// runnable, mounted, not yet waiting
// runnable, in transition
return Thread.State.RUNNABLE;
case PARKED:
case PARKED_SUSPENDED:
case PINNED:
return Thread.State.WAITING;
return State.WAITING;
case TIMED_PARKED:
case TIMED_PINNED:
return State.TIMED_WAITING;
case TERMINATED:
return Thread.State.TERMINATED;
default:
@@ -940,35 +952,58 @@ final class VirtualThread extends BaseVirtualThread {
/**
* Returns the stack trace for this virtual thread if it is unmounted.
* Returns null if the thread is in another state.
* Returns null if the thread is mounted or in transition.
*/
private StackTraceElement[] tryGetStackTrace() {
int initialState = state();
return switch (initialState) {
case RUNNABLE, PARKED -> {
int suspendedState = initialState | SUSPENDED;
if (compareAndSetState(initialState, suspendedState)) {
try {
yield cont.getStackTrace();
} finally {
assert state == suspendedState;
setState(initialState);
// re-submit if runnable
// re-submit if unparked while suspended
if (initialState == RUNNABLE
|| (parkPermit && compareAndSetState(PARKED, RUNNABLE))) {
try {
submitRunContinuation();
} catch (RejectedExecutionException ignore) { }
}
}
}
yield null;
int initialState = state() & ~SUSPENDED;
switch (initialState) {
case NEW, STARTED, TERMINATED -> {
return new StackTraceElement[0]; // unmounted, empty stack
}
case NEW, STARTED, TERMINATED -> new StackTraceElement[0]; // empty stack
default -> null;
case RUNNING, PINNED, TIMED_PINNED -> {
return null; // mounted
}
case PARKED, TIMED_PARKED -> {
// unmounted, not runnable
}
case UNPARKED, YIELDED -> {
// unmounted, runnable
}
case PARKING, TIMED_PARKING, YIELDING -> {
return null; // in transition
}
default -> throw new InternalError("" + initialState);
}
// thread is unmounted, prevent it from continuing
int suspendedState = initialState | SUSPENDED;
if (!compareAndSetState(initialState, suspendedState)) {
return null;
}
// get stack trace and restore state
StackTraceElement[] stack;
try {
stack = cont.getStackTrace();
} finally {
assert state == suspendedState;
setState(initialState);
}
boolean resubmit = switch (initialState) {
case UNPARKED, YIELDED -> {
// resubmit as task may have run while suspended
yield true;
}
case PARKED, TIMED_PARKED -> {
// resubmit if unparked while suspended
yield parkPermit && compareAndSetState(initialState, UNPARKED);
}
default -> throw new InternalError();
};
if (resubmit) {
submitRunContinuation();
}
return stack;
}
@Override

View File

@@ -0,0 +1,416 @@
/*
* Copyright (c) 2023, 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=default
* @bug 8312498
* @summary Basic test for JVMTI GetThreadState with virtual threads
* @run junit/othervm/native GetThreadStateTest
*/
/*
* @test id=no-vmcontinuations
* @requires vm.continuations
* @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations GetThreadStateTest
*/
import java.util.StringJoiner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class GetThreadStateTest {
@BeforeAll
static void setup() {
System.loadLibrary("GetThreadStateTest");
init();
}
/**
* Test state of new/unstarted thread.
*/
@Test
void testUnstarted() {
var thread = Thread.ofVirtual().unstarted(() -> { });
check(thread, /*new*/ 0);
}
/**
* Test state of terminated thread.
*/
@Test
void testTerminated() throws Exception {
var thread = Thread.ofVirtual().start(() -> { });
thread.join();
check(thread, JVMTI_THREAD_STATE_TERMINATED);
}
/**
* Test state of runnable thread.
*/
@Test
void testRunnable() throws Exception {
var latch = new CountDownLatch(1);
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
latch.countDown();
// spin until done
while (!done.get()) {
Thread.onSpinWait();
}
});
try {
// wait for thread to start execution
latch.await();
// thread should be runnable
int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_RUNNABLE;
check(thread, expected);
// re-test with interrupt status set
thread.interrupt();
check(thread, expected | JVMTI_THREAD_STATE_INTERRUPTED);
} finally {
done.set(true);
thread.join();
}
}
/**
* Test state of thread waiting to enter a monitor.
*/
@Test
void testMonitorEnter() throws Exception {
var latch = new CountDownLatch(1);
Object lock = new Object();
var thread = Thread.ofVirtual().unstarted(() -> {
latch.countDown();
synchronized (lock) { }
});
try {
synchronized (lock) {
// start thread and wait for it to start execution
thread.start();
latch.await();
// thread should block on monitor enter
int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
await(thread, expected);
// re-test with interrupt status set
thread.interrupt();
check(thread, expected | JVMTI_THREAD_STATE_INTERRUPTED);
}
} finally {
thread.join();
}
}
/**
* Test state of thread waiting in Object.wait().
*/
@Test
void testObjectWait() throws Exception {
var latch = new CountDownLatch(1);
Object lock = new Object();
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
latch.countDown();
try {
lock.wait();
} catch (InterruptedException e) { }
}
});
try {
// wait for thread to own monitor
latch.await();
// thread should wait
int expected = JVMTI_THREAD_STATE_ALIVE |
JVMTI_THREAD_STATE_WAITING |
JVMTI_THREAD_STATE_WAITING_INDEFINITELY |
JVMTI_THREAD_STATE_IN_OBJECT_WAIT;
await(thread, expected);
// notify so thread waits to re-enter monitor
synchronized (lock) {
lock.notifyAll();
expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
check(thread, expected);
// re-test with interrupt status set
thread.interrupt();
check(thread, expected | JVMTI_THREAD_STATE_INTERRUPTED);
}
} finally {
thread.interrupt();
thread.join();
}
}
/**
* Test state of thread waiting in Object.wait(millis).
*/
@Test
void testObjectWaitMillis() throws Exception {
var latch = new CountDownLatch(1);
Object lock = new Object();
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
latch.countDown();
try {
lock.wait(Long.MAX_VALUE);
} catch (InterruptedException e) { }
}
});
try {
// wait for thread to own monitor
latch.await();
// thread should wait
int expected = JVMTI_THREAD_STATE_ALIVE |
JVMTI_THREAD_STATE_WAITING |
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT |
JVMTI_THREAD_STATE_IN_OBJECT_WAIT;
await(thread, expected);
// notify so thread waits to re-enter monitor
synchronized (lock) {
lock.notifyAll();
expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
check(thread, expected);
// re-test with interrupt status set
thread.interrupt();
check(thread, expected | JVMTI_THREAD_STATE_INTERRUPTED);
}
} finally {
thread.interrupt();
thread.join();
}
}
/**
* Test state of thread parked with LockSupport.park.
*/
@Test
void testPark() throws Exception {
var latch = new CountDownLatch(1);
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
latch.countDown();
while (!done.get()) {
LockSupport.park();
}
});
try {
// wait for thread to start execution
latch.await();
// thread should park
int expected = JVMTI_THREAD_STATE_ALIVE |
JVMTI_THREAD_STATE_WAITING |
JVMTI_THREAD_STATE_WAITING_INDEFINITELY |
JVMTI_THREAD_STATE_PARKED;
await(thread, expected);
} finally {
done.set(true);
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test state of thread parked with LockSupport.parkNanos.
*/
@Test
void testParkNanos() throws Exception {
var latch = new CountDownLatch(1);
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
latch.countDown();
while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE);
}
});
try {
// wait for thread to start execution
latch.await();
// thread should park
int expected = JVMTI_THREAD_STATE_ALIVE |
JVMTI_THREAD_STATE_WAITING |
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT |
JVMTI_THREAD_STATE_PARKED;
await(thread, expected);
} finally {
done.set(true);
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test state of thread parked with LockSupport.park while holding a monitor.
*/
@Test
void testParkWhenPinned() throws Exception {
var latch = new CountDownLatch(1);
Object lock = new Object();
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
latch.countDown();
while (!done.get()) {
LockSupport.park();
}
}
});
try {
// wait for thread to own monitor
latch.await();
// thread should park
int expected = JVMTI_THREAD_STATE_ALIVE |
JVMTI_THREAD_STATE_WAITING |
JVMTI_THREAD_STATE_WAITING_INDEFINITELY |
JVMTI_THREAD_STATE_PARKED;
await(thread, expected);
} finally {
done.set(true);
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test state of thread parked with LockSupport.parkNanos while holding a monitor.
*/
@Test
void testParkNanosWhenPinned() throws Exception {
var latch = new CountDownLatch(1);
Object lock = new Object();
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
latch.countDown();
while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE);
}
}
});
try {
// wait for thread to own monitor
latch.await();
// thread should park
int expected = JVMTI_THREAD_STATE_ALIVE |
JVMTI_THREAD_STATE_WAITING |
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT |
JVMTI_THREAD_STATE_PARKED;
await(thread, expected);
} finally {
done.set(true);
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Asserts that the given thread has the expected JVMTI state.
*/
private static void check(Thread thread, int expected) {
System.err.format(" expect state=0x%x (%s) ...%n", expected, jvmtiStateToString(expected));
int state = jvmtiState(thread);
System.err.format(" thread state=0x%x (%s)%n", state, jvmtiStateToString(state));
assertEquals(expected, state);
}
/**
* Waits indefinitely for the given thread to get to the target JVMTI state.
*/
private static void await(Thread thread, int targetState) throws Exception {
System.err.format(" await state=0x%x (%s) ...%n", targetState, jvmtiStateToString(targetState));
int state = jvmtiState(thread);
System.err.format(" thread state=0x%x (%s)%n", state, jvmtiStateToString(state));
while (state != targetState) {
assertTrue(thread.isAlive(), "Thread has terminated");
Thread.sleep(20);
state = jvmtiState(thread);
System.err.format(" thread state=0x%x (%s)%n", state, jvmtiStateToString(state));
}
}
private static final int JVMTI_THREAD_STATE_ALIVE = 0x0001;
private static final int JVMTI_THREAD_STATE_TERMINATED = 0x0002;
private static final int JVMTI_THREAD_STATE_RUNNABLE = 0x0004;
private static final int JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400;
private static final int JVMTI_THREAD_STATE_WAITING = 0x0080;
private static final int JVMTI_THREAD_STATE_WAITING_INDEFINITELY = 0x0010;
private static final int JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT = 0x0020;
private static final int JVMTI_THREAD_STATE_SLEEPING = 0x0040;
private static final int JVMTI_THREAD_STATE_IN_OBJECT_WAIT = 0x0100;
private static final int JVMTI_THREAD_STATE_PARKED = 0x0200;
private static final int JVMTI_THREAD_STATE_SUSPENDED = 0x100000;
private static final int JVMTI_THREAD_STATE_INTERRUPTED = 0x200000;
private static final int JVMTI_THREAD_STATE_IN_NATIVE = 0x400000;
private static native void init();
private static native int jvmtiState(Thread thread);
private static String jvmtiStateToString(int state) {
StringJoiner sj = new StringJoiner(" | ");
if ((state & JVMTI_THREAD_STATE_ALIVE) != 0)
sj.add("JVMTI_THREAD_STATE_ALIVE");
if ((state & JVMTI_THREAD_STATE_TERMINATED) != 0)
sj.add("JVMTI_THREAD_STATE_TERMINATED");
if ((state & JVMTI_THREAD_STATE_RUNNABLE) != 0)
sj.add("JVMTI_THREAD_STATE_RUNNABLE");
if ((state & JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER) != 0)
sj.add("JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER");
if ((state & JVMTI_THREAD_STATE_WAITING) != 0)
sj.add("JVMTI_THREAD_STATE_WAITING");
if ((state & JVMTI_THREAD_STATE_WAITING_INDEFINITELY) != 0)
sj.add("JVMTI_THREAD_STATE_WAITING_INDEFINITELY");
if ((state & JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT) != 0)
sj.add("JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT");
if ((state & JVMTI_THREAD_STATE_IN_OBJECT_WAIT) != 0)
sj.add("JVMTI_THREAD_STATE_IN_OBJECT_WAIT");
if ((state & JVMTI_THREAD_STATE_PARKED) != 0)
sj.add("JVMTI_THREAD_STATE_PARKED");
if ((state & JVMTI_THREAD_STATE_SUSPENDED) != 0)
sj.add("JVMTI_THREAD_STATE_SUSPENDED");
if ((state & JVMTI_THREAD_STATE_INTERRUPTED) != 0)
sj.add("JVMTI_THREAD_STATE_INTERRUPTED");
if ((state & JVMTI_THREAD_STATE_IN_NATIVE) != 0)
sj.add("JVMTI_THREAD_STATE_IN_NATIVE");
String s = sj.toString();
return s.isEmpty() ? "<empty>" : s;
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2023, 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.
*/
#include "jni.h"
#include "jvmti.h"
static jvmtiEnv *jvmti;
JNIEXPORT void JNICALL Java_GetThreadStateTest_init(JNIEnv *env, jclass clazz) {
JavaVM* vm;
jint res;
res = (*env)->GetJavaVM(env, &vm);
if (res != 0) {
(*env)->FatalError(env, "GetJavaVM failed");
} else {
res = (*vm)->GetEnv(vm, (void**)&jvmti, JVMTI_VERSION);
if (res != JNI_OK) {
(*env)->FatalError(env, "GetEnv failed");
}
}
}
JNIEXPORT jint JNICALL Java_GetThreadStateTest_jvmtiState(JNIEnv *env, jclass clazz, jobject thread) {
jvmtiError err;
jint state = 0;
err = (*jvmti)->GetThreadState(jvmti, thread, &state);
if (err != JVMTI_ERROR_NONE) {
(*env)->FatalError(env, "GetThreadState failed");
}
return state;
}

View File

@@ -149,12 +149,12 @@ public class VThreadEventTest {
}
await(ready0);
mready.countDown();
await(ready1); // to guaranty state is not State.WAITING after await(mready) in test1()
// wait for test1 threads to reach WAITING state in sleep()
await(ready1); // to guarantee state is not State.TIMED_WAITING after await(mready) in test1()
// wait for test1 threads to reach TIMED_WAITING state in sleep()
for (Thread t : test1Threads) {
Thread.State state = t.getState();
log("DBG: state: " + state);
while (state != Thread.State.WAITING) {
while (state != Thread.State.TIMED_WAITING) {
Thread.sleep(10);
state = t.getState();
log("DBG: state: " + state);

View File

@@ -45,7 +45,6 @@ import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.*;
class CustomScheduler {
private static final Executor DEFAULT_SCHEDULER = defaultScheduler();
private static ExecutorService scheduler1;
private static ExecutorService scheduler2;
@@ -216,20 +215,6 @@ class CustomScheduler {
}
}
/**
* Returns the default scheduler.
*/
private static Executor defaultScheduler() {
try {
Field defaultScheduler = Class.forName("java.lang.VirtualThread")
.getDeclaredField("DEFAULT_SCHEDULER");
defaultScheduler.setAccessible(true);
return (Executor) defaultScheduler.get(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Returns the scheduler for the given virtual thread.
*/

View File

@@ -23,7 +23,7 @@
/*
* @test id=default
* @bug 8284161 8286788
* @bug 8284161 8286788 8321270
* @summary Test Thread API with virtual threads
* @modules java.base/java.lang:+open
* @library /test/lib
@@ -106,7 +106,7 @@ class ThreadAPI {
LockSupport.park();
after.set(Thread.currentThread());
});
awaitParked(thread);
await(thread, Thread.State.WAITING);
LockSupport.unpark(thread);
thread.join();
assertTrue(before.get() == thread);
@@ -130,7 +130,7 @@ class ThreadAPI {
});
synchronized (lock) {
thread.start();
awaitBlocked(thread);
await(thread, Thread.State.BLOCKED);
}
thread.join();
assertTrue(ref1.get() == thread);
@@ -160,7 +160,7 @@ class ThreadAPI {
lock.lock();
try {
thread.start();
awaitParked(thread);
await(thread, Thread.State.WAITING);
} finally {
lock.unlock();
}
@@ -765,6 +765,7 @@ class ThreadAPI {
assertFalse(thread.join(Duration.ofMillis(100)));
} finally {
done.set(true);
thread.join();
}
}
@@ -897,7 +898,7 @@ class ThreadAPI {
exception.set(e);
}
});
awaitParked(thread);
await(thread, Thread.State.TIMED_WAITING);
thread.interrupt();
thread.join();
assertNull(exception.get());
@@ -917,7 +918,7 @@ class ThreadAPI {
exception.set(e);
}
});
awaitParked(thread);
await(thread, Thread.State.WAITING);
thread.interrupt();
thread.join();
assertNull(exception.get());
@@ -1032,16 +1033,16 @@ class ThreadAPI {
void testSetPriority1() throws Exception {
VThreadRunner.run(() -> {
Thread me = Thread.currentThread();
assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
me.setPriority(Thread.MAX_PRIORITY);
assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
me.setPriority(Thread.NORM_PRIORITY);
assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
me.setPriority(Thread.MIN_PRIORITY);
assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
assertThrows(IllegalArgumentException.class, () -> me.setPriority(-1));
});
@@ -1055,33 +1056,33 @@ class ThreadAPI {
var thread = Thread.ofVirtual().unstarted(LockSupport::park);
// not started
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
thread.setPriority(Thread.MAX_PRIORITY);
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
thread.setPriority(Thread.NORM_PRIORITY);
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
thread.setPriority(Thread.MIN_PRIORITY);
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
// running
thread.start();
try {
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
thread.setPriority(Thread.NORM_PRIORITY);
thread.setPriority(Thread.MAX_PRIORITY);
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
thread.setPriority(Thread.NORM_PRIORITY);
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
thread.setPriority(Thread.MIN_PRIORITY);
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
@@ -1091,7 +1092,7 @@ class ThreadAPI {
thread.join();
// terminated
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
}
/**
@@ -1190,6 +1191,36 @@ class ThreadAPI {
assertEquals(List.of("A", "A", "B"), list);
}
/**
* Test that Thread.yield does not consume the thread's parking permit.
*/
@Test
void testYield3() throws Exception {
var thread = Thread.ofVirtual().start(() -> {
LockSupport.unpark(Thread.currentThread());
Thread.yield();
LockSupport.park(); // should not park
});
thread.join();
}
/**
* Test that Thread.yield does not make available the thread's parking permit.
*/
@Test
void testYield4() throws Exception {
var thread = Thread.ofVirtual().start(() -> {
Thread.yield();
LockSupport.park(); // should park
});
try {
await(thread, Thread.State.WAITING);
} finally {
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test Thread.onSpinWait.
*/
@@ -1650,53 +1681,79 @@ class ThreadAPI {
}
/**
* Test Thread::getState when thread is not started.
* Test Thread::getState when thread is new/unstarted.
*/
@Test
void testGetState1() {
var thread = Thread.ofVirtual().unstarted(() -> { });
assertTrue(thread.getState() == Thread.State.NEW);
assertEquals(Thread.State.NEW, thread.getState());
}
/**
* Test Thread::getState when thread is terminated.
*/
@Test
void testGetState2() throws Exception {
var thread = Thread.ofVirtual().start(() -> { });
thread.join();
assertEquals(Thread.State.TERMINATED, thread.getState());
}
/**
* Test Thread::getState when thread is runnable (mounted).
*/
@Test
void testGetState2() throws Exception {
VThreadRunner.run(() -> {
Thread.State state = Thread.currentThread().getState();
assertTrue(state == Thread.State.RUNNABLE);
void testGetState3() throws Exception {
var started = new CountDownLatch(1);
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
started.countDown();
// spin until done
while (!done.get()) {
Thread.onSpinWait();
}
});
try {
// wait for thread to start
started.await();
// thread should be runnable
assertEquals(Thread.State.RUNNABLE, thread.getState());
} finally {
done.set(true);
thread.join();
}
}
/**
* Test Thread::getState when thread is runnable (not mounted).
*/
@Test
void testGetState3() throws Exception {
void testGetState4() throws Exception {
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
AtomicBoolean completed = new AtomicBoolean();
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
Thread t1 = builder.start(() -> {
Thread t2 = builder.unstarted(LockSupport::park);
assertTrue(t2.getState() == Thread.State.NEW);
assertEquals(Thread.State.NEW, t2.getState());
// start t2 to make it runnable
t2.start();
try {
assertTrue(t2.getState() == Thread.State.RUNNABLE);
assertEquals(Thread.State.RUNNABLE, t2.getState());
// yield to allow t2 to run and park
Thread.yield();
assertTrue(t2.getState() == Thread.State.WAITING);
assertEquals(Thread.State.WAITING, t2.getState());
} finally {
// unpark t2 to make it runnable again
LockSupport.unpark(t2);
}
// t2 should be runnable (not mounted)
assertTrue(t2.getState() == Thread.State.RUNNABLE);
assertEquals(Thread.State.RUNNABLE, t2.getState());
completed.set(true);
});
@@ -1706,48 +1763,21 @@ class ThreadAPI {
}
/**
* Test Thread::getState when thread is parked.
*/
@Test
void testGetState4() throws Exception {
var thread = Thread.ofVirtual().start(LockSupport::park);
while (thread.getState() != Thread.State.WAITING) {
Thread.sleep(20);
}
LockSupport.unpark(thread);
thread.join();
}
/**
* Test Thread::getState when thread is parked while holding a monitor.
* Test Thread::getState when thread is waiting to enter a monitor.
*/
@Test
void testGetState5() throws Exception {
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
LockSupport.park();
}
});
while (thread.getState() != Thread.State.WAITING) {
Thread.sleep(20);
}
LockSupport.unpark(thread);
thread.join();
}
/**
* Test Thread::getState when thread is waiting for a monitor.
*/
@Test
void testGetState6() throws Exception {
var started = new CountDownLatch(1);
var thread = Thread.ofVirtual().unstarted(() -> {
started.countDown();
synchronized (lock) { }
});
synchronized (lock) {
thread.start();
while (thread.getState() != Thread.State.BLOCKED) {
Thread.sleep(20);
}
started.await();
// wait for thread to block
await(thread, Thread.State.BLOCKED);
}
thread.join();
}
@@ -1756,27 +1786,124 @@ class ThreadAPI {
* Test Thread::getState when thread is waiting in Object.wait.
*/
@Test
void testGetState7() throws Exception {
void testGetState6() throws Exception {
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
try { lock.wait(); } catch (InterruptedException e) { }
}
});
while (thread.getState() != Thread.State.WAITING) {
Thread.sleep(20);
try {
// wait for thread to wait
await(thread, Thread.State.WAITING);
} finally {
thread.interrupt();
thread.join();
}
thread.interrupt();
thread.join();
}
/**
* Test Thread::getState when thread is terminated.
* Test Thread::getState when thread is waiting in Object.wait(millis).
*/
@Test
void testGetState7() throws Exception {
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
try {
lock.wait(Long.MAX_VALUE);
} catch (InterruptedException e) { }
}
});
try {
// wait for thread to wait
await(thread, Thread.State.TIMED_WAITING);
} finally {
thread.interrupt();
thread.join();
}
}
/**
* Test Thread::getState when thread is parked.
*/
@Test
void testGetState8() throws Exception {
var thread = Thread.ofVirtual().start(() -> { });
thread.join();
assertTrue(thread.getState() == Thread.State.TERMINATED);
var thread = Thread.ofVirtual().start(LockSupport::park);
try {
await(thread, Thread.State.WAITING);
} finally {
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test Thread::getState when thread is timed parked.
*/
@Test
void testGetState9() throws Exception {
var thread = Thread.ofVirtual().start(() -> LockSupport.parkNanos(Long.MAX_VALUE));
try {
await(thread, Thread.State.TIMED_WAITING);
} finally {
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test Thread::getState when thread is parked while holding a monitor.
*/
@Test
void testGetState10() throws Exception {
var started = new CountDownLatch(1);
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
started.countDown();
synchronized (lock) {
while (!done.get()) {
LockSupport.park();
}
}
});
try {
// wait for thread to start
started.await();
// wait for thread to park
await(thread, Thread.State.WAITING);
} finally {
done.set(true);
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test Thread::getState when thread is timed parked while holding a monitor.
*/
@Test
void testGetState11() throws Exception {
var started = new CountDownLatch(1);
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
started.countDown();
synchronized (lock) {
while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE);
}
}
});
try {
// wait for thread to start
started.await();
// wait for thread to park
await(thread, Thread.State.TIMED_WAITING);
} finally {
done.set(true);
LockSupport.unpark(thread);
thread.join();
}
}
/**
@@ -1899,9 +2026,7 @@ class ThreadAPI {
}
// wait for virtual thread to block in wait
while (vthread.getState() != Thread.State.WAITING) {
Thread.sleep(20);
}
await(vthread, Thread.State.WAITING);
// get stack trace of both carrier and virtual thread
StackTraceElement[] carrierStackTrace = carrier.getStackTrace();
@@ -1928,12 +2053,7 @@ class ThreadAPI {
@Test
void testGetStackTrace5() throws Exception {
var thread = Thread.ofVirtual().start(LockSupport::park);
// wait for thread to park
while (thread.getState() != Thread.State.WAITING) {
Thread.sleep(20);
}
await(thread, Thread.State.WAITING);
try {
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "LockSupport.park"));
@@ -1996,9 +2116,7 @@ class ThreadAPI {
}
// wait for virtual thread to block in wait
while (vthread.getState() != Thread.State.WAITING) {
Thread.sleep(20);
}
await(vthread, Thread.State.WAITING);
// get all stack traces
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
@@ -2034,7 +2152,7 @@ class ThreadAPI {
var vgroup = thread.getThreadGroup();
thread.start();
try {
assertTrue(thread.getThreadGroup() == vgroup);
assertEquals(vgroup, thread.getThreadGroup());
} finally {
LockSupport.unpark(thread);
thread.join();
@@ -2051,7 +2169,7 @@ class ThreadAPI {
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
Thread child = new Thread(() -> { });
ThreadGroup group = child.getThreadGroup();
assertTrue(group == vgroup);
assertEquals(vgroup, group);
});
}
@@ -2068,19 +2186,19 @@ class ThreadAPI {
thread.join();
ThreadGroup vgroup = ref.get();
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
ThreadGroup group = new ThreadGroup(vgroup, "group");
assertTrue(group.getParent() == vgroup);
assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY);
assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority());
vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY - 1);
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority());
vgroup.setMaxPriority(Thread.MIN_PRIORITY);
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
assertTrue(group.getMaxPriority() == Thread.MIN_PRIORITY);
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority());
}
/**
@@ -2091,20 +2209,19 @@ class ThreadAPI {
void testThreadGroup4() throws Exception {
VThreadRunner.run(() -> {
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
ThreadGroup group = new ThreadGroup("group");
assertTrue(group.getParent() == vgroup);
assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY);
assertEquals(vgroup, group.getParent());
assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority());
vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY - 1);
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority());
vgroup.setMaxPriority(Thread.MIN_PRIORITY);
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
assertTrue(group.getMaxPriority() == Thread.MIN_PRIORITY);
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority());
});
}
@@ -2208,9 +2325,7 @@ class ThreadAPI {
me.setName("fred");
LockSupport.park();
});
while (thread.getState() != Thread.State.WAITING) {
Thread.sleep(10);
}
await(thread, Thread.State.WAITING);
try {
assertTrue(thread.toString().contains("fred"));
} finally {
@@ -2233,23 +2348,11 @@ class ThreadAPI {
}
/**
* Waits for the given thread to park.
* Waits for the given thread to reach a given state.
*/
static void awaitParked(Thread thread) throws InterruptedException {
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
Thread.State state = thread.getState();
while (state != Thread.State.WAITING && state != Thread.State.TIMED_WAITING) {
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
Thread.sleep(10);
state = thread.getState();
}
}
/**
* Waits for the given thread to block waiting on a monitor.
*/
static void awaitBlocked(Thread thread) throws InterruptedException {
Thread.State state = thread.getState();
while (state != Thread.State.BLOCKED) {
while (state != expectedState) {
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
Thread.sleep(10);
state = thread.getState();

View File

@@ -51,7 +51,6 @@ class ThreadBuilders {
* @throws UnsupportedOperationException if custom schedulers are not supported
*/
static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
Thread.Builder.OfVirtual builder = Thread.ofVirtual();
try {
return (Thread.Builder.OfVirtual) VTBUILDER_CTOR.newInstance(scheduler);
} catch (InvocationTargetException e) {

View File

@@ -0,0 +1,123 @@
/*
* Copyright (c) 2023, 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
* @bug 8322818
* @summary Stress test Thread.getStackTrace on a virtual thread that is pinned
* @requires vm.debug != true
* @modules java.base/java.lang:+open
* @library /test/lib
* @run main/othervm GetStackTraceALotWhenPinned 500000
*/
/*
* @test
* @requires vm.debug == true
* @modules java.base/java.lang:+open
* @library /test/lib
* @run main/othervm/timeout=300 GetStackTraceALotWhenPinned 200000
*/
import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
import jdk.test.lib.thread.VThreadRunner;
public class GetStackTraceALotWhenPinned {
public static void main(String[] args) throws Exception {
// need at least two carrier threads when main thread is a virtual thread
if (Thread.currentThread().isVirtual()) {
VThreadRunner.ensureParallelism(2);
}
int iterations = Integer.parseInt(args[0]);
var barrier = new Barrier(2);
// Start a virtual thread that loops doing Thread.yield and parking while pinned.
// This loop creates the conditions for the main thread to sample the stack trace
// as it transitions from being unmounted to parking while pinned.
var thread = Thread.startVirtualThread(() -> {
boolean timed = false;
for (int i = 0; i < iterations; i++) {
// wait for main thread to arrive
barrier.await();
Thread.yield();
synchronized (GetStackTraceALotWhenPinned.class) {
if (timed) {
LockSupport.parkNanos(Long.MAX_VALUE);
} else {
LockSupport.park();
}
}
timed = !timed;
}
});
long lastTimestamp = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
// wait for virtual thread to arrive
barrier.await();
thread.getStackTrace();
LockSupport.unpark(thread);
long currentTime = System.currentTimeMillis();
if ((currentTime - lastTimestamp) > 500) {
System.out.format("%s %d remaining ...%n", Instant.now(), (iterations - i));
lastTimestamp = currentTime;
}
}
}
/**
* Alow threads wait for each other to reach a common barrier point. This class does
* not park threads that are waiting for the barrier to trip, instead it spins. This
* makes it suitable for tests that use LockSupport.park or Thread.yield.
*/
private static class Barrier {
private final int parties;
private final AtomicInteger count;
private volatile int generation;
Barrier(int parties) {
this.parties = parties;
this.count = new AtomicInteger(parties);
}
void await() {
int g = generation;
if (count.decrementAndGet() == 0) {
count.set(parties);
generation = g + 1;
} else {
while (generation == g) {
Thread.onSpinWait();
}
}
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2023, 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
* @summary Stress test parking and unparking
* @requires vm.debug != true
* @run main/othervm ParkALot 500000
*/
/*
* @test
* @requires vm.debug == true
* @run main/othervm ParkALot 100000
*/
import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.locks.LockSupport;
public class ParkALot {
private static final int ITERATIONS = 1_000_000;
public static void main(String[] args) {
int iterations;
if (args.length > 0) {
iterations = Integer.parseInt(args[0]);
} else {
iterations = ITERATIONS;
}
int maxThreads = Math.clamp(Runtime.getRuntime().availableProcessors() / 2, 1, 4);
for (int nthreads = 1; nthreads <= maxThreads; nthreads++) {
System.out.format("%s %d thread(s) ...%n", Instant.now(), nthreads);
ThreadFactory factory = Thread.ofPlatform().factory();
try (var executor = Executors.newThreadPerTaskExecutor(factory)) {
for (int i = 0; i < nthreads; i++) {
executor.submit(() -> parkALot(iterations));
}
}
System.out.format("%s %d thread(s) done%n", Instant.now(), nthreads);
}
}
/**
* Creates a virtual thread that alternates between untimed and timed parking.
* A platform thread spins unparking the virtual thread.
*/
private static void parkALot(int iterations) {
Thread vthread = Thread.ofVirtual().start(() -> {
int i = 0;
boolean timed = false;
while (i < iterations) {
if (timed) {
LockSupport.parkNanos(Long.MAX_VALUE);
timed = false;
} else {
LockSupport.park();
timed = true;
}
i++;
}
});
Thread.State state;
while ((state = vthread.getState()) != Thread.State.TERMINATED) {
if (state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) {
LockSupport.unpark(vthread);
} else {
Thread.yield();
}
}
}
}