8312498: Thread::getState and JVM TI GetThreadState should return TIMED_WAITING virtual thread is timed parked

Reviewed-by: sspitsyn, rpressler
This commit is contained in:
Alan Bateman 2023-09-19 10:58:12 +00:00
parent 670b4567cf
commit 4461eeb31d
10 changed files with 823 additions and 183 deletions

View File

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

View File

@ -521,20 +521,21 @@ 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,
RUNNABLE = 2,
RUNNING = 3,
PARKING = 4,
PARKED = 5,
PINNED = 6,
TIMED_PARKING = 7,
TIMED_PARKED = 8,
TIMED_PINNED = 9,
YIELDING = 10,
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

@ -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 -> RUNNABLE // unparked, schedule to continue
* PINNED -> RUNNING // unparked, continue execution on same carrier
*
* PARKED -> RUNNABLE // unpark or interrupted
* PINNED -> RUNNABLE // unpark or interrupted
* 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 -> RUNNABLE // unparked, schedule to continue
* TIMED_PINNED -> RUNNING // unparked, continue execution on same carrier
*
* RUNNABLE -> RUNNING // continue execution
*
* RUNNING -> YIELDING // Thread.yield
* YIELDING -> RUNNABLE // yield successful
* YIELDING -> RUNNING // yield failed
*
* RUNNING -> TERMINATED // done
*/
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
// untimed parking
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
// timed parking
private static final int TIMED_PARKING = 7;
private static final int TIMED_PARKED = 8;
private static final int TIMED_PINNED = 9;
private static final int YIELDING = 10; // Thread.yield
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;
@ -434,17 +445,18 @@ final class VirtualThread extends BaseVirtualThread {
* If yielding due to Thread.yield then it just submits the task to continue.
*/
private void afterYield() {
int s = state();
assert (s == PARKING || s == YIELDING) && (carrierThread == null);
assert carrierThread == null;
if (s == PARKING) {
setState(PARKED);
int s = state();
if (s == PARKING || s == TIMED_PARKING) {
int newState = (s == PARKING) ? PARKED : TIMED_PARKED;
setState(newState);
// notify JVMTI that unmount has completed, thread is parked
notifyJvmtiUnmount(/*hide*/false);
// may have been unparked while parking
if (parkPermit && compareAndSetState(PARKED, RUNNABLE)) {
if (parkPermit && compareAndSetState(newState, RUNNABLE)) {
// lazy submit to continue on the current thread as carrier if possible
if (currentThread() instanceof CarrierThread ct) {
lazySubmitRunContinuation(ct.getPool());
@ -465,6 +477,8 @@ final class VirtualThread extends BaseVirtualThread {
} else {
submitRunContinuation();
}
} else {
assert false;
}
}
@ -607,13 +621,13 @@ final class VirtualThread extends BaseVirtualThread {
boolean yielded = false;
Future<?> unparker = scheduleUnpark(this::unpark, nanos);
setState(PARKING);
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);
@ -645,7 +659,7 @@ final class VirtualThread extends BaseVirtualThread {
event = null;
}
setState(PINNED);
setState(timed ? TIMED_PINNED : PINNED);
try {
if (!parkPermit) {
if (!timed) {
@ -713,7 +727,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, RUNNABLE)) {
if (currentThread instanceof VirtualThread vthread) {
vthread.switchToCarrierThread();
try {
@ -724,11 +739,11 @@ final class VirtualThread extends BaseVirtualThread {
} else {
submitRunContinuation();
}
} else if (s == 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);
}
}
@ -865,7 +880,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:
@ -876,7 +892,6 @@ final class VirtualThread extends BaseVirtualThread {
return Thread.State.RUNNABLE;
}
case RUNNABLE:
case RUNNABLE_SUSPENDED:
// runnable, not mounted
return Thread.State.RUNNABLE;
case RUNNING:
@ -890,13 +905,16 @@ final class VirtualThread extends BaseVirtualThread {
// runnable, mounted
return Thread.State.RUNNABLE;
case PARKING:
case TIMED_PARKING:
case YIELDING:
// runnable, mounted, not yet waiting
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:
@ -936,7 +954,7 @@ final class VirtualThread extends BaseVirtualThread {
private StackTraceElement[] tryGetStackTrace() {
int initialState = state();
return switch (initialState) {
case RUNNABLE, PARKED -> {
case RUNNABLE, PARKED, TIMED_PARKED -> {
int suspendedState = initialState | SUSPENDED;
if (compareAndSetState(initialState, suspendedState)) {
try {
@ -948,7 +966,7 @@ final class VirtualThread extends BaseVirtualThread {
// re-submit if runnable
// re-submit if unparked while suspended
if (initialState == RUNNABLE
|| (parkPermit && compareAndSetState(PARKED, RUNNABLE))) {
|| (parkPermit && compareAndSetState(initialState, RUNNABLE))) {
try {
submitRunContinuation();
} catch (RejectedExecutionException ignore) { }

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

@ -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());
}
/**
@ -1650,53 +1651,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 +1733,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 +1756,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 +1996,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 +2023,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 +2086,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 +2122,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 +2139,7 @@ class ThreadAPI {
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
Thread child = new Thread(() -> { });
ThreadGroup group = child.getThreadGroup();
assertTrue(group == vgroup);
assertEquals(vgroup, group);
});
}
@ -2068,19 +2156,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 +2179,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 +2295,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 +2318,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,94 @@
/*
* 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 200000
*/
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.max(Runtime.getRuntime().availableProcessors() / 2, 1);
for (int nthreads = 1; nthreads <= maxThreads; nthreads++) {
System.out.format("%s %d threads ...%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));
}
}
}
}
/**
* 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.onSpinWait();
}
}
}
}