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 java_lang_VirtualThread::map_state_to_thread_status(int state) {
|
||||||
JavaThreadStatus status = JavaThreadStatus::NEW;
|
JavaThreadStatus status = JavaThreadStatus::NEW;
|
||||||
switch (state) {
|
switch (state & ~SUSPENDED) {
|
||||||
case NEW :
|
case NEW :
|
||||||
status = JavaThreadStatus::NEW;
|
status = JavaThreadStatus::NEW;
|
||||||
break;
|
break;
|
||||||
case STARTED :
|
case STARTED :
|
||||||
case RUNNABLE :
|
case RUNNABLE :
|
||||||
case RUNNABLE_SUSPENDED :
|
|
||||||
case RUNNING :
|
case RUNNING :
|
||||||
case PARKING :
|
case PARKING :
|
||||||
|
case TIMED_PARKING:
|
||||||
case YIELDING :
|
case YIELDING :
|
||||||
status = JavaThreadStatus::RUNNABLE;
|
status = JavaThreadStatus::RUNNABLE;
|
||||||
break;
|
break;
|
||||||
case PARKED :
|
case PARKED :
|
||||||
case PARKED_SUSPENDED :
|
|
||||||
case PINNED :
|
case PINNED :
|
||||||
status = JavaThreadStatus::PARKED;
|
status = JavaThreadStatus::PARKED;
|
||||||
break;
|
break;
|
||||||
|
case TIMED_PARKED:
|
||||||
|
case TIMED_PINNED:
|
||||||
|
status = JavaThreadStatus::PARKED_TIMED;
|
||||||
|
break;
|
||||||
case TERMINATED :
|
case TERMINATED :
|
||||||
status = JavaThreadStatus::TERMINATED;
|
status = JavaThreadStatus::TERMINATED;
|
||||||
break;
|
break;
|
||||||
|
@ -521,20 +521,21 @@ class java_lang_VirtualThread : AllStatic {
|
|||||||
JFR_ONLY(static int _jfr_epoch_offset;)
|
JFR_ONLY(static int _jfr_epoch_offset;)
|
||||||
public:
|
public:
|
||||||
enum {
|
enum {
|
||||||
NEW = 0,
|
NEW = 0,
|
||||||
STARTED = 1,
|
STARTED = 1,
|
||||||
RUNNABLE = 2,
|
RUNNABLE = 2,
|
||||||
RUNNING = 3,
|
RUNNING = 3,
|
||||||
PARKING = 4,
|
PARKING = 4,
|
||||||
PARKED = 5,
|
PARKED = 5,
|
||||||
PINNED = 6,
|
PINNED = 6,
|
||||||
YIELDING = 7,
|
TIMED_PARKING = 7,
|
||||||
TERMINATED = 99,
|
TIMED_PARKED = 8,
|
||||||
|
TIMED_PINNED = 9,
|
||||||
|
YIELDING = 10,
|
||||||
|
TERMINATED = 99,
|
||||||
|
|
||||||
// can be suspended from scheduling when unmounted
|
// additional state bits
|
||||||
SUSPENDED = 1 << 8,
|
SUSPENDED = 1 << 8, // suspended when unmounted
|
||||||
RUNNABLE_SUSPENDED = (RUNNABLE | SUSPENDED),
|
|
||||||
PARKED_SUSPENDED = (PARKED | SUSPENDED)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void compute_offsets();
|
static void compute_offsets();
|
||||||
|
@ -86,41 +86,52 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
private volatile int state;
|
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 -> TERMINATED // failed to start
|
||||||
* STARTED -> RUNNING // first run
|
* STARTED -> RUNNING // first run
|
||||||
|
* RUNNING -> TERMINATED // done
|
||||||
*
|
*
|
||||||
* RUNNING -> PARKING // Thread attempts to park
|
* RUNNING -> PARKING // Thread parking with LockSupport.park
|
||||||
* PARKING -> PARKED // cont.yield successful, thread is parked
|
* PARKING -> PARKED // cont.yield successful, parked indefinitely
|
||||||
* PARKING -> PINNED // cont.yield failed, thread is pinned
|
* 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
|
* RUNNING -> TIMED_PARKING // Thread parking with LockSupport.parkNanos
|
||||||
* PINNED -> RUNNABLE // unpark or interrupted
|
* 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
|
* RUNNABLE -> RUNNING // continue execution
|
||||||
*
|
|
||||||
* RUNNING -> YIELDING // Thread.yield
|
* RUNNING -> YIELDING // Thread.yield
|
||||||
* YIELDING -> RUNNABLE // yield successful
|
* YIELDING -> RUNNABLE // yield successful
|
||||||
* YIELDING -> RUNNING // yield failed
|
* YIELDING -> RUNNING // yield failed
|
||||||
*
|
|
||||||
* RUNNING -> TERMINATED // done
|
|
||||||
*/
|
*/
|
||||||
private static final int NEW = 0;
|
private static final int NEW = 0;
|
||||||
private static final int STARTED = 1;
|
private static final int STARTED = 1;
|
||||||
private static final int RUNNABLE = 2; // runnable-unmounted
|
private static final int RUNNABLE = 2; // runnable-unmounted
|
||||||
private static final int RUNNING = 3; // runnable-mounted
|
private static final int RUNNING = 3; // runnable-mounted
|
||||||
|
|
||||||
|
// untimed parking
|
||||||
private static final int PARKING = 4;
|
private static final int PARKING = 4;
|
||||||
private static final int PARKED = 5; // unmounted
|
private static final int PARKED = 5; // unmounted
|
||||||
private static final int PINNED = 6; // mounted
|
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
|
private static final int TERMINATED = 99; // final state
|
||||||
|
|
||||||
// can be suspended from scheduling when unmounted
|
// can be suspended from scheduling when unmounted
|
||||||
private static final int SUSPENDED = 1 << 8;
|
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
|
// parking permit
|
||||||
private volatile boolean parkPermit;
|
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.
|
* If yielding due to Thread.yield then it just submits the task to continue.
|
||||||
*/
|
*/
|
||||||
private void afterYield() {
|
private void afterYield() {
|
||||||
int s = state();
|
assert carrierThread == null;
|
||||||
assert (s == PARKING || s == YIELDING) && (carrierThread == null);
|
|
||||||
|
|
||||||
if (s == PARKING) {
|
int s = state();
|
||||||
setState(PARKED);
|
if (s == PARKING || s == TIMED_PARKING) {
|
||||||
|
int newState = (s == PARKING) ? PARKED : TIMED_PARKED;
|
||||||
|
setState(newState);
|
||||||
|
|
||||||
// notify JVMTI that unmount has completed, thread is parked
|
// notify JVMTI that unmount has completed, thread is parked
|
||||||
notifyJvmtiUnmount(/*hide*/false);
|
notifyJvmtiUnmount(/*hide*/false);
|
||||||
|
|
||||||
// may have been unparked while parking
|
// 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
|
// lazy submit to continue on the current thread as carrier if possible
|
||||||
if (currentThread() instanceof CarrierThread ct) {
|
if (currentThread() instanceof CarrierThread ct) {
|
||||||
lazySubmitRunContinuation(ct.getPool());
|
lazySubmitRunContinuation(ct.getPool());
|
||||||
@ -465,6 +477,8 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
} else {
|
} else {
|
||||||
submitRunContinuation();
|
submitRunContinuation();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
assert false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -607,13 +621,13 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
|
|
||||||
boolean yielded = false;
|
boolean yielded = false;
|
||||||
Future<?> unparker = scheduleUnpark(this::unpark, nanos);
|
Future<?> unparker = scheduleUnpark(this::unpark, nanos);
|
||||||
setState(PARKING);
|
setState(TIMED_PARKING);
|
||||||
try {
|
try {
|
||||||
yielded = yieldContinuation(); // may throw
|
yielded = yieldContinuation(); // may throw
|
||||||
} finally {
|
} finally {
|
||||||
assert (Thread.currentThread() == this) && (yielded == (state() == RUNNING));
|
assert (Thread.currentThread() == this) && (yielded == (state() == RUNNING));
|
||||||
if (!yielded) {
|
if (!yielded) {
|
||||||
assert state() == PARKING;
|
assert state() == TIMED_PARKING;
|
||||||
setState(RUNNING);
|
setState(RUNNING);
|
||||||
}
|
}
|
||||||
cancel(unparker);
|
cancel(unparker);
|
||||||
@ -645,7 +659,7 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
event = null;
|
event = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(PINNED);
|
setState(timed ? TIMED_PINNED : PINNED);
|
||||||
try {
|
try {
|
||||||
if (!parkPermit) {
|
if (!parkPermit) {
|
||||||
if (!timed) {
|
if (!timed) {
|
||||||
@ -713,7 +727,8 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
Thread currentThread = Thread.currentThread();
|
Thread currentThread = Thread.currentThread();
|
||||||
if (!getAndSetParkPermit(true) && currentThread != this) {
|
if (!getAndSetParkPermit(true) && currentThread != this) {
|
||||||
int s = state();
|
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) {
|
if (currentThread instanceof VirtualThread vthread) {
|
||||||
vthread.switchToCarrierThread();
|
vthread.switchToCarrierThread();
|
||||||
try {
|
try {
|
||||||
@ -724,11 +739,11 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
} else {
|
} else {
|
||||||
submitRunContinuation();
|
submitRunContinuation();
|
||||||
}
|
}
|
||||||
} else if (s == PINNED) {
|
} else if ((s == PINNED) || (s == TIMED_PINNED)) {
|
||||||
// unpark carrier thread when pinned.
|
// unpark carrier thread when pinned.
|
||||||
synchronized (carrierThreadAccessLock()) {
|
synchronized (carrierThreadAccessLock()) {
|
||||||
Thread carrier = carrierThread;
|
Thread carrier = carrierThread;
|
||||||
if (carrier != null && state() == PINNED) {
|
if (carrier != null && ((s = state()) == PINNED || s == TIMED_PINNED)) {
|
||||||
U.unpark(carrier);
|
U.unpark(carrier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -865,7 +880,8 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
Thread.State threadState() {
|
Thread.State threadState() {
|
||||||
switch (state()) {
|
int s = state();
|
||||||
|
switch (s & ~SUSPENDED) {
|
||||||
case NEW:
|
case NEW:
|
||||||
return Thread.State.NEW;
|
return Thread.State.NEW;
|
||||||
case STARTED:
|
case STARTED:
|
||||||
@ -876,7 +892,6 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
return Thread.State.RUNNABLE;
|
return Thread.State.RUNNABLE;
|
||||||
}
|
}
|
||||||
case RUNNABLE:
|
case RUNNABLE:
|
||||||
case RUNNABLE_SUSPENDED:
|
|
||||||
// runnable, not mounted
|
// runnable, not mounted
|
||||||
return Thread.State.RUNNABLE;
|
return Thread.State.RUNNABLE;
|
||||||
case RUNNING:
|
case RUNNING:
|
||||||
@ -890,13 +905,16 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
// runnable, mounted
|
// runnable, mounted
|
||||||
return Thread.State.RUNNABLE;
|
return Thread.State.RUNNABLE;
|
||||||
case PARKING:
|
case PARKING:
|
||||||
|
case TIMED_PARKING:
|
||||||
case YIELDING:
|
case YIELDING:
|
||||||
// runnable, mounted, not yet waiting
|
// runnable, mounted, not yet waiting
|
||||||
return Thread.State.RUNNABLE;
|
return Thread.State.RUNNABLE;
|
||||||
case PARKED:
|
case PARKED:
|
||||||
case PARKED_SUSPENDED:
|
|
||||||
case PINNED:
|
case PINNED:
|
||||||
return Thread.State.WAITING;
|
return State.WAITING;
|
||||||
|
case TIMED_PARKED:
|
||||||
|
case TIMED_PINNED:
|
||||||
|
return State.TIMED_WAITING;
|
||||||
case TERMINATED:
|
case TERMINATED:
|
||||||
return Thread.State.TERMINATED;
|
return Thread.State.TERMINATED;
|
||||||
default:
|
default:
|
||||||
@ -936,7 +954,7 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
private StackTraceElement[] tryGetStackTrace() {
|
private StackTraceElement[] tryGetStackTrace() {
|
||||||
int initialState = state();
|
int initialState = state();
|
||||||
return switch (initialState) {
|
return switch (initialState) {
|
||||||
case RUNNABLE, PARKED -> {
|
case RUNNABLE, PARKED, TIMED_PARKED -> {
|
||||||
int suspendedState = initialState | SUSPENDED;
|
int suspendedState = initialState | SUSPENDED;
|
||||||
if (compareAndSetState(initialState, suspendedState)) {
|
if (compareAndSetState(initialState, suspendedState)) {
|
||||||
try {
|
try {
|
||||||
@ -948,7 +966,7 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
// re-submit if runnable
|
// re-submit if runnable
|
||||||
// re-submit if unparked while suspended
|
// re-submit if unparked while suspended
|
||||||
if (initialState == RUNNABLE
|
if (initialState == RUNNABLE
|
||||||
|| (parkPermit && compareAndSetState(PARKED, RUNNABLE))) {
|
|| (parkPermit && compareAndSetState(initialState, RUNNABLE))) {
|
||||||
try {
|
try {
|
||||||
submitRunContinuation();
|
submitRunContinuation();
|
||||||
} catch (RejectedExecutionException ignore) { }
|
} 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);
|
await(ready0);
|
||||||
mready.countDown();
|
mready.countDown();
|
||||||
await(ready1); // to guaranty state is not State.WAITING after await(mready) in test1()
|
await(ready1); // to guarantee state is not State.TIMED_WAITING after await(mready) in test1()
|
||||||
// wait for test1 threads to reach WAITING state in sleep()
|
// wait for test1 threads to reach TIMED_WAITING state in sleep()
|
||||||
for (Thread t : test1Threads) {
|
for (Thread t : test1Threads) {
|
||||||
Thread.State state = t.getState();
|
Thread.State state = t.getState();
|
||||||
log("DBG: state: " + state);
|
log("DBG: state: " + state);
|
||||||
while (state != Thread.State.WAITING) {
|
while (state != Thread.State.TIMED_WAITING) {
|
||||||
Thread.sleep(10);
|
Thread.sleep(10);
|
||||||
state = t.getState();
|
state = t.getState();
|
||||||
log("DBG: state: " + state);
|
log("DBG: state: " + state);
|
||||||
|
@ -45,7 +45,6 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
import static org.junit.jupiter.api.Assumptions.*;
|
import static org.junit.jupiter.api.Assumptions.*;
|
||||||
|
|
||||||
class CustomScheduler {
|
class CustomScheduler {
|
||||||
private static final Executor DEFAULT_SCHEDULER = defaultScheduler();
|
|
||||||
private static ExecutorService scheduler1;
|
private static ExecutorService scheduler1;
|
||||||
private static ExecutorService scheduler2;
|
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.
|
* Returns the scheduler for the given virtual thread.
|
||||||
*/
|
*/
|
||||||
|
@ -106,7 +106,7 @@ class ThreadAPI {
|
|||||||
LockSupport.park();
|
LockSupport.park();
|
||||||
after.set(Thread.currentThread());
|
after.set(Thread.currentThread());
|
||||||
});
|
});
|
||||||
awaitParked(thread);
|
await(thread, Thread.State.WAITING);
|
||||||
LockSupport.unpark(thread);
|
LockSupport.unpark(thread);
|
||||||
thread.join();
|
thread.join();
|
||||||
assertTrue(before.get() == thread);
|
assertTrue(before.get() == thread);
|
||||||
@ -130,7 +130,7 @@ class ThreadAPI {
|
|||||||
});
|
});
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
thread.start();
|
thread.start();
|
||||||
awaitBlocked(thread);
|
await(thread, Thread.State.BLOCKED);
|
||||||
}
|
}
|
||||||
thread.join();
|
thread.join();
|
||||||
assertTrue(ref1.get() == thread);
|
assertTrue(ref1.get() == thread);
|
||||||
@ -160,7 +160,7 @@ class ThreadAPI {
|
|||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
thread.start();
|
thread.start();
|
||||||
awaitParked(thread);
|
await(thread, Thread.State.WAITING);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@ -765,6 +765,7 @@ class ThreadAPI {
|
|||||||
assertFalse(thread.join(Duration.ofMillis(100)));
|
assertFalse(thread.join(Duration.ofMillis(100)));
|
||||||
} finally {
|
} finally {
|
||||||
done.set(true);
|
done.set(true);
|
||||||
|
thread.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -897,7 +898,7 @@ class ThreadAPI {
|
|||||||
exception.set(e);
|
exception.set(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
awaitParked(thread);
|
await(thread, Thread.State.TIMED_WAITING);
|
||||||
thread.interrupt();
|
thread.interrupt();
|
||||||
thread.join();
|
thread.join();
|
||||||
assertNull(exception.get());
|
assertNull(exception.get());
|
||||||
@ -917,7 +918,7 @@ class ThreadAPI {
|
|||||||
exception.set(e);
|
exception.set(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
awaitParked(thread);
|
await(thread, Thread.State.WAITING);
|
||||||
thread.interrupt();
|
thread.interrupt();
|
||||||
thread.join();
|
thread.join();
|
||||||
assertNull(exception.get());
|
assertNull(exception.get());
|
||||||
@ -1032,16 +1033,16 @@ class ThreadAPI {
|
|||||||
void testSetPriority1() throws Exception {
|
void testSetPriority1() throws Exception {
|
||||||
VThreadRunner.run(() -> {
|
VThreadRunner.run(() -> {
|
||||||
Thread me = Thread.currentThread();
|
Thread me = Thread.currentThread();
|
||||||
assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
|
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
|
||||||
|
|
||||||
me.setPriority(Thread.MAX_PRIORITY);
|
me.setPriority(Thread.MAX_PRIORITY);
|
||||||
assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
|
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
|
||||||
|
|
||||||
me.setPriority(Thread.NORM_PRIORITY);
|
me.setPriority(Thread.NORM_PRIORITY);
|
||||||
assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
|
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
|
||||||
|
|
||||||
me.setPriority(Thread.MIN_PRIORITY);
|
me.setPriority(Thread.MIN_PRIORITY);
|
||||||
assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
|
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
|
||||||
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> me.setPriority(-1));
|
assertThrows(IllegalArgumentException.class, () -> me.setPriority(-1));
|
||||||
});
|
});
|
||||||
@ -1055,33 +1056,33 @@ class ThreadAPI {
|
|||||||
var thread = Thread.ofVirtual().unstarted(LockSupport::park);
|
var thread = Thread.ofVirtual().unstarted(LockSupport::park);
|
||||||
|
|
||||||
// not started
|
// not started
|
||||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||||
|
|
||||||
thread.setPriority(Thread.MAX_PRIORITY);
|
thread.setPriority(Thread.MAX_PRIORITY);
|
||||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||||
|
|
||||||
thread.setPriority(Thread.NORM_PRIORITY);
|
thread.setPriority(Thread.NORM_PRIORITY);
|
||||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||||
|
|
||||||
thread.setPriority(Thread.MIN_PRIORITY);
|
thread.setPriority(Thread.MIN_PRIORITY);
|
||||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||||
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
|
assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
|
||||||
|
|
||||||
// running
|
// running
|
||||||
thread.start();
|
thread.start();
|
||||||
try {
|
try {
|
||||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||||
thread.setPriority(Thread.NORM_PRIORITY);
|
thread.setPriority(Thread.NORM_PRIORITY);
|
||||||
|
|
||||||
thread.setPriority(Thread.MAX_PRIORITY);
|
thread.setPriority(Thread.MAX_PRIORITY);
|
||||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||||
|
|
||||||
thread.setPriority(Thread.NORM_PRIORITY);
|
thread.setPriority(Thread.NORM_PRIORITY);
|
||||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||||
|
|
||||||
thread.setPriority(Thread.MIN_PRIORITY);
|
thread.setPriority(Thread.MIN_PRIORITY);
|
||||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||||
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
|
assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
|
||||||
|
|
||||||
@ -1091,7 +1092,7 @@ class ThreadAPI {
|
|||||||
thread.join();
|
thread.join();
|
||||||
|
|
||||||
// terminated
|
// 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
|
@Test
|
||||||
void testGetState1() {
|
void testGetState1() {
|
||||||
var thread = Thread.ofVirtual().unstarted(() -> { });
|
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 Thread::getState when thread is runnable (mounted).
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testGetState2() throws Exception {
|
void testGetState3() throws Exception {
|
||||||
VThreadRunner.run(() -> {
|
var started = new CountDownLatch(1);
|
||||||
Thread.State state = Thread.currentThread().getState();
|
var done = new AtomicBoolean();
|
||||||
assertTrue(state == Thread.State.RUNNABLE);
|
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 Thread::getState when thread is runnable (not mounted).
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testGetState3() throws Exception {
|
void testGetState4() throws Exception {
|
||||||
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
|
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
|
||||||
AtomicBoolean completed = new AtomicBoolean();
|
AtomicBoolean completed = new AtomicBoolean();
|
||||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||||
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
|
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
|
||||||
Thread t1 = builder.start(() -> {
|
Thread t1 = builder.start(() -> {
|
||||||
Thread t2 = builder.unstarted(LockSupport::park);
|
Thread t2 = builder.unstarted(LockSupport::park);
|
||||||
assertTrue(t2.getState() == Thread.State.NEW);
|
assertEquals(Thread.State.NEW, t2.getState());
|
||||||
|
|
||||||
// start t2 to make it runnable
|
// start t2 to make it runnable
|
||||||
t2.start();
|
t2.start();
|
||||||
try {
|
try {
|
||||||
assertTrue(t2.getState() == Thread.State.RUNNABLE);
|
assertEquals(Thread.State.RUNNABLE, t2.getState());
|
||||||
|
|
||||||
// yield to allow t2 to run and park
|
// yield to allow t2 to run and park
|
||||||
Thread.yield();
|
Thread.yield();
|
||||||
assertTrue(t2.getState() == Thread.State.WAITING);
|
assertEquals(Thread.State.WAITING, t2.getState());
|
||||||
} finally {
|
} finally {
|
||||||
// unpark t2 to make it runnable again
|
// unpark t2 to make it runnable again
|
||||||
LockSupport.unpark(t2);
|
LockSupport.unpark(t2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// t2 should be runnable (not mounted)
|
// t2 should be runnable (not mounted)
|
||||||
assertTrue(t2.getState() == Thread.State.RUNNABLE);
|
assertEquals(Thread.State.RUNNABLE, t2.getState());
|
||||||
|
|
||||||
completed.set(true);
|
completed.set(true);
|
||||||
});
|
});
|
||||||
@ -1706,48 +1733,21 @@ class ThreadAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test Thread::getState when thread is parked.
|
* Test Thread::getState when thread is waiting to enter a monitor.
|
||||||
*/
|
|
||||||
@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
|
@Test
|
||||||
void testGetState5() throws Exception {
|
void testGetState5() throws Exception {
|
||||||
var thread = Thread.ofVirtual().start(() -> {
|
var started = new CountDownLatch(1);
|
||||||
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 thread = Thread.ofVirtual().unstarted(() -> {
|
var thread = Thread.ofVirtual().unstarted(() -> {
|
||||||
|
started.countDown();
|
||||||
synchronized (lock) { }
|
synchronized (lock) { }
|
||||||
});
|
});
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
thread.start();
|
thread.start();
|
||||||
while (thread.getState() != Thread.State.BLOCKED) {
|
started.await();
|
||||||
Thread.sleep(20);
|
|
||||||
}
|
// wait for thread to block
|
||||||
|
await(thread, Thread.State.BLOCKED);
|
||||||
}
|
}
|
||||||
thread.join();
|
thread.join();
|
||||||
}
|
}
|
||||||
@ -1756,27 +1756,124 @@ class ThreadAPI {
|
|||||||
* Test Thread::getState when thread is waiting in Object.wait.
|
* Test Thread::getState when thread is waiting in Object.wait.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testGetState7() throws Exception {
|
void testGetState6() throws Exception {
|
||||||
var thread = Thread.ofVirtual().start(() -> {
|
var thread = Thread.ofVirtual().start(() -> {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
try { lock.wait(); } catch (InterruptedException e) { }
|
try { lock.wait(); } catch (InterruptedException e) { }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
while (thread.getState() != Thread.State.WAITING) {
|
try {
|
||||||
Thread.sleep(20);
|
// 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
|
@Test
|
||||||
void testGetState8() throws Exception {
|
void testGetState8() throws Exception {
|
||||||
var thread = Thread.ofVirtual().start(() -> { });
|
var thread = Thread.ofVirtual().start(LockSupport::park);
|
||||||
thread.join();
|
try {
|
||||||
assertTrue(thread.getState() == Thread.State.TERMINATED);
|
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
|
// wait for virtual thread to block in wait
|
||||||
while (vthread.getState() != Thread.State.WAITING) {
|
await(vthread, Thread.State.WAITING);
|
||||||
Thread.sleep(20);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get stack trace of both carrier and virtual thread
|
// get stack trace of both carrier and virtual thread
|
||||||
StackTraceElement[] carrierStackTrace = carrier.getStackTrace();
|
StackTraceElement[] carrierStackTrace = carrier.getStackTrace();
|
||||||
@ -1928,12 +2023,7 @@ class ThreadAPI {
|
|||||||
@Test
|
@Test
|
||||||
void testGetStackTrace5() throws Exception {
|
void testGetStackTrace5() throws Exception {
|
||||||
var thread = Thread.ofVirtual().start(LockSupport::park);
|
var thread = Thread.ofVirtual().start(LockSupport::park);
|
||||||
|
await(thread, Thread.State.WAITING);
|
||||||
// wait for thread to park
|
|
||||||
while (thread.getState() != Thread.State.WAITING) {
|
|
||||||
Thread.sleep(20);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
StackTraceElement[] stack = thread.getStackTrace();
|
StackTraceElement[] stack = thread.getStackTrace();
|
||||||
assertTrue(contains(stack, "LockSupport.park"));
|
assertTrue(contains(stack, "LockSupport.park"));
|
||||||
@ -1996,9 +2086,7 @@ class ThreadAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wait for virtual thread to block in wait
|
// wait for virtual thread to block in wait
|
||||||
while (vthread.getState() != Thread.State.WAITING) {
|
await(vthread, Thread.State.WAITING);
|
||||||
Thread.sleep(20);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all stack traces
|
// get all stack traces
|
||||||
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
|
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
|
||||||
@ -2034,7 +2122,7 @@ class ThreadAPI {
|
|||||||
var vgroup = thread.getThreadGroup();
|
var vgroup = thread.getThreadGroup();
|
||||||
thread.start();
|
thread.start();
|
||||||
try {
|
try {
|
||||||
assertTrue(thread.getThreadGroup() == vgroup);
|
assertEquals(vgroup, thread.getThreadGroup());
|
||||||
} finally {
|
} finally {
|
||||||
LockSupport.unpark(thread);
|
LockSupport.unpark(thread);
|
||||||
thread.join();
|
thread.join();
|
||||||
@ -2051,7 +2139,7 @@ class ThreadAPI {
|
|||||||
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
|
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
|
||||||
Thread child = new Thread(() -> { });
|
Thread child = new Thread(() -> { });
|
||||||
ThreadGroup group = child.getThreadGroup();
|
ThreadGroup group = child.getThreadGroup();
|
||||||
assertTrue(group == vgroup);
|
assertEquals(vgroup, group);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2068,19 +2156,19 @@ class ThreadAPI {
|
|||||||
thread.join();
|
thread.join();
|
||||||
|
|
||||||
ThreadGroup vgroup = ref.get();
|
ThreadGroup vgroup = ref.get();
|
||||||
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
|
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
||||||
|
|
||||||
ThreadGroup group = new ThreadGroup(vgroup, "group");
|
ThreadGroup group = new ThreadGroup(vgroup, "group");
|
||||||
assertTrue(group.getParent() == vgroup);
|
assertTrue(group.getParent() == vgroup);
|
||||||
assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY);
|
assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority());
|
||||||
|
|
||||||
vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
|
vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
|
||||||
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
|
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
||||||
assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY - 1);
|
assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority());
|
||||||
|
|
||||||
vgroup.setMaxPriority(Thread.MIN_PRIORITY);
|
vgroup.setMaxPriority(Thread.MIN_PRIORITY);
|
||||||
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
|
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
||||||
assertTrue(group.getMaxPriority() == Thread.MIN_PRIORITY);
|
assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2091,20 +2179,19 @@ class ThreadAPI {
|
|||||||
void testThreadGroup4() throws Exception {
|
void testThreadGroup4() throws Exception {
|
||||||
VThreadRunner.run(() -> {
|
VThreadRunner.run(() -> {
|
||||||
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
|
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
|
||||||
|
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
||||||
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
|
|
||||||
|
|
||||||
ThreadGroup group = new ThreadGroup("group");
|
ThreadGroup group = new ThreadGroup("group");
|
||||||
assertTrue(group.getParent() == vgroup);
|
assertEquals(vgroup, group.getParent());
|
||||||
assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY);
|
assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority());
|
||||||
|
|
||||||
vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
|
vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
|
||||||
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
|
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
||||||
assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY - 1);
|
assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority());
|
||||||
|
|
||||||
vgroup.setMaxPriority(Thread.MIN_PRIORITY);
|
vgroup.setMaxPriority(Thread.MIN_PRIORITY);
|
||||||
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
|
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
||||||
assertTrue(group.getMaxPriority() == Thread.MIN_PRIORITY);
|
assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2208,9 +2295,7 @@ class ThreadAPI {
|
|||||||
me.setName("fred");
|
me.setName("fred");
|
||||||
LockSupport.park();
|
LockSupport.park();
|
||||||
});
|
});
|
||||||
while (thread.getState() != Thread.State.WAITING) {
|
await(thread, Thread.State.WAITING);
|
||||||
Thread.sleep(10);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
assertTrue(thread.toString().contains("fred"));
|
assertTrue(thread.toString().contains("fred"));
|
||||||
} finally {
|
} 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();
|
Thread.State state = thread.getState();
|
||||||
while (state != Thread.State.WAITING && state != Thread.State.TIMED_WAITING) {
|
while (state != expectedState) {
|
||||||
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) {
|
|
||||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||||
Thread.sleep(10);
|
Thread.sleep(10);
|
||||||
state = thread.getState();
|
state = thread.getState();
|
||||||
|
@ -51,7 +51,6 @@ class ThreadBuilders {
|
|||||||
* @throws UnsupportedOperationException if custom schedulers are not supported
|
* @throws UnsupportedOperationException if custom schedulers are not supported
|
||||||
*/
|
*/
|
||||||
static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
|
static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
|
||||||
Thread.Builder.OfVirtual builder = Thread.ofVirtual();
|
|
||||||
try {
|
try {
|
||||||
return (Thread.Builder.OfVirtual) VTBUILDER_CTOR.newInstance(scheduler);
|
return (Thread.Builder.OfVirtual) VTBUILDER_CTOR.newInstance(scheduler);
|
||||||
} catch (InvocationTargetException e) {
|
} 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