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:
parent
670b4567cf
commit
4461eeb31d
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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) { }
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
94
test/jdk/java/lang/Thread/virtual/stress/ParkALot.java
Normal file
94
test/jdk/java/lang/Thread/virtual/stress/ParkALot.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user