mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 09:29:38 +01:00
8315373: Change VirtualThread to unmount after freezing, re-mount before thawing
8312498: Thread::getState and JVM TI GetThreadState should return TIMED_WAITING virtual thread is timed parked
8312777: notifyJvmtiMount before notifyJvmtiUnmount
8321270: Virtual Thread.yield consumes parking permit
8322818: Thread::getStackTrace can fail with InternalError if virtual thread is timed-parked when pinned
8323002: test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenPinned.java times out on macosx-x64
8323296: java/lang/Thread/virtual/stress/GetStackTraceALotWhenPinned.java#id1 timed out
8316924: java/lang/Thread/virtual/stress/ParkALot.java times out
Backport-of: 9a83d55887
This commit is contained in:
committed by
Vitaly Provodin
parent
9d82180fe6
commit
63f9947d78
@@ -1989,24 +1989,28 @@ int java_lang_VirtualThread::state(oop vthread) {
|
||||
|
||||
JavaThreadStatus java_lang_VirtualThread::map_state_to_thread_status(int state) {
|
||||
JavaThreadStatus status = JavaThreadStatus::NEW;
|
||||
switch (state) {
|
||||
case NEW :
|
||||
switch (state & ~SUSPENDED) {
|
||||
case NEW:
|
||||
status = JavaThreadStatus::NEW;
|
||||
break;
|
||||
case STARTED :
|
||||
case RUNNABLE :
|
||||
case RUNNABLE_SUSPENDED :
|
||||
case RUNNING :
|
||||
case PARKING :
|
||||
case YIELDING :
|
||||
case STARTED:
|
||||
case RUNNING:
|
||||
case PARKING:
|
||||
case TIMED_PARKING:
|
||||
case UNPARKED:
|
||||
case YIELDING:
|
||||
case YIELDED:
|
||||
status = JavaThreadStatus::RUNNABLE;
|
||||
break;
|
||||
case PARKED :
|
||||
case PARKED_SUSPENDED :
|
||||
case PINNED :
|
||||
case PARKED:
|
||||
case PINNED:
|
||||
status = JavaThreadStatus::PARKED;
|
||||
break;
|
||||
case TERMINATED :
|
||||
case TIMED_PARKED:
|
||||
case TIMED_PINNED:
|
||||
status = JavaThreadStatus::PARKED_TIMED;
|
||||
break;
|
||||
case TERMINATED:
|
||||
status = JavaThreadStatus::TERMINATED;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -521,20 +521,22 @@ class java_lang_VirtualThread : AllStatic {
|
||||
JFR_ONLY(static int _jfr_epoch_offset;)
|
||||
public:
|
||||
enum {
|
||||
NEW = 0,
|
||||
STARTED = 1,
|
||||
RUNNABLE = 2,
|
||||
RUNNING = 3,
|
||||
PARKING = 4,
|
||||
PARKED = 5,
|
||||
PINNED = 6,
|
||||
YIELDING = 7,
|
||||
TERMINATED = 99,
|
||||
NEW = 0,
|
||||
STARTED = 1,
|
||||
RUNNING = 2,
|
||||
PARKING = 3,
|
||||
PARKED = 4,
|
||||
PINNED = 5,
|
||||
TIMED_PARKING = 6,
|
||||
TIMED_PARKED = 7,
|
||||
TIMED_PINNED = 8,
|
||||
UNPARKED = 9,
|
||||
YIELDING = 10,
|
||||
YIELDED = 11,
|
||||
TERMINATED = 99,
|
||||
|
||||
// can be suspended from scheduling when unmounted
|
||||
SUSPENDED = 1 << 8,
|
||||
RUNNABLE_SUSPENDED = (RUNNABLE | SUSPENDED),
|
||||
PARKED_SUSPENDED = (PARKED | SUSPENDED)
|
||||
// additional state bits
|
||||
SUSPENDED = 1 << 8, // suspended when unmounted
|
||||
};
|
||||
|
||||
static void compute_offsets();
|
||||
|
||||
@@ -138,9 +138,9 @@ void JfrStackFrame::write(JfrCheckpointWriter& cpw) const {
|
||||
|
||||
class JfrVframeStream : public vframeStreamCommon {
|
||||
private:
|
||||
bool _vthread;
|
||||
const ContinuationEntry* _cont_entry;
|
||||
bool _async_mode;
|
||||
bool _vthread;
|
||||
bool step_to_sender();
|
||||
void next_frame();
|
||||
public:
|
||||
@@ -165,8 +165,9 @@ JfrVframeStream::JfrVframeStream(JavaThread* jt, const frame& fr, bool stop_at_j
|
||||
RegisterMap::UpdateMap::skip,
|
||||
RegisterMap::ProcessFrames::skip,
|
||||
walk_continuation(jt))),
|
||||
_cont_entry(JfrThreadLocal::is_vthread(jt) ? jt->last_continuation() : nullptr),
|
||||
_async_mode(async_mode), _vthread(JfrThreadLocal::is_vthread(jt)) {
|
||||
_vthread(JfrThreadLocal::is_vthread(jt)),
|
||||
_cont_entry(_vthread ? jt->last_continuation() : nullptr),
|
||||
_async_mode(async_mode) {
|
||||
assert(!_vthread || _cont_entry != nullptr, "invariant");
|
||||
_reg_map.set_async(async_mode);
|
||||
_frame = fr;
|
||||
|
||||
@@ -395,11 +395,14 @@ traceid JfrThreadLocal::thread_id(const Thread* t) {
|
||||
return t->jfr_thread_local()->_thread_id_alias;
|
||||
}
|
||||
JfrThreadLocal* const tl = t->jfr_thread_local();
|
||||
if (!t->is_Java_thread() || !Atomic::load_acquire(&tl->_vthread)) {
|
||||
if (!t->is_Java_thread()) {
|
||||
return jvm_thread_id(t, tl);
|
||||
}
|
||||
const JavaThread* jt = JavaThread::cast(t);
|
||||
if (!is_vthread(jt)) {
|
||||
return jvm_thread_id(t, tl);
|
||||
}
|
||||
// virtual thread
|
||||
const JavaThread* jt = JavaThread::cast(t);
|
||||
const traceid tid = vthread_id(jt);
|
||||
assert(tid != 0, "invariant");
|
||||
if (!tl->is_vthread_excluded()) {
|
||||
@@ -456,7 +459,7 @@ traceid JfrThreadLocal::jvm_thread_id(const Thread* t) {
|
||||
|
||||
bool JfrThreadLocal::is_vthread(const JavaThread* jt) {
|
||||
assert(jt != nullptr, "invariant");
|
||||
return Atomic::load_acquire(&jt->jfr_thread_local()->_vthread);
|
||||
return Atomic::load_acquire(&jt->jfr_thread_local()->_vthread) && jt->last_continuation() != nullptr;
|
||||
}
|
||||
|
||||
inline bool is_virtual(const JavaThread* jt, oop thread) {
|
||||
|
||||
@@ -86,41 +86,52 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
private volatile int state;
|
||||
|
||||
/*
|
||||
* Virtual thread state and transitions:
|
||||
* Virtual thread state transitions:
|
||||
*
|
||||
* NEW -> STARTED // Thread.start
|
||||
* NEW -> STARTED // Thread.start, schedule to run
|
||||
* STARTED -> TERMINATED // failed to start
|
||||
* STARTED -> RUNNING // first run
|
||||
* RUNNING -> TERMINATED // done
|
||||
*
|
||||
* RUNNING -> PARKING // Thread attempts to park
|
||||
* PARKING -> PARKED // cont.yield successful, thread is parked
|
||||
* PARKING -> PINNED // cont.yield failed, thread is pinned
|
||||
* RUNNING -> PARKING // Thread parking with LockSupport.park
|
||||
* PARKING -> PARKED // cont.yield successful, parked indefinitely
|
||||
* PARKING -> PINNED // cont.yield failed, parked indefinitely on carrier
|
||||
* PARKED -> UNPARKED // unparked, may be scheduled to continue
|
||||
* PINNED -> RUNNING // unparked, continue execution on same carrier
|
||||
* UNPARKED -> RUNNING // continue execution after park
|
||||
*
|
||||
* PARKED -> RUNNABLE // unpark or interrupted
|
||||
* PINNED -> RUNNABLE // unpark or interrupted
|
||||
*
|
||||
* RUNNABLE -> RUNNING // continue execution
|
||||
* RUNNING -> TIMED_PARKING // Thread parking with LockSupport.parkNanos
|
||||
* TIMED_PARKING -> TIMED_PARKED // cont.yield successful, timed-parked
|
||||
* TIMED_PARKING -> TIMED_PINNED // cont.yield failed, timed-parked on carrier
|
||||
* TIMED_PARKED -> UNPARKED // unparked, may be scheduled to continue
|
||||
* TIMED_PINNED -> RUNNING // unparked, continue execution on same carrier
|
||||
*
|
||||
* RUNNING -> YIELDING // Thread.yield
|
||||
* YIELDING -> RUNNABLE // yield successful
|
||||
* YIELDING -> RUNNING // yield failed
|
||||
*
|
||||
* RUNNING -> TERMINATED // done
|
||||
* YIELDING -> YIELDED // cont.yield successful, may be scheduled to continue
|
||||
* YIELDING -> RUNNING // cont.yield failed
|
||||
* YIELDED -> RUNNING // continue execution after Thread.yield
|
||||
*/
|
||||
private static final int NEW = 0;
|
||||
private static final int STARTED = 1;
|
||||
private static final int RUNNABLE = 2; // runnable-unmounted
|
||||
private static final int RUNNING = 3; // runnable-mounted
|
||||
private static final int PARKING = 4;
|
||||
private static final int PARKED = 5; // unmounted
|
||||
private static final int PINNED = 6; // mounted
|
||||
private static final int YIELDING = 7; // Thread.yield
|
||||
private static final int RUNNING = 2; // runnable-mounted
|
||||
|
||||
// untimed and timed parking
|
||||
private static final int PARKING = 3;
|
||||
private static final int PARKED = 4; // unmounted
|
||||
private static final int PINNED = 5; // mounted
|
||||
private static final int TIMED_PARKING = 6;
|
||||
private static final int TIMED_PARKED = 7; // unmounted
|
||||
private static final int TIMED_PINNED = 8; // mounted
|
||||
private static final int UNPARKED = 9; // unmounted but runnable
|
||||
|
||||
// Thread.yield
|
||||
private static final int YIELDING = 10;
|
||||
private static final int YIELDED = 11; // unmounted but runnable
|
||||
|
||||
private static final int TERMINATED = 99; // final state
|
||||
|
||||
// can be suspended from scheduling when unmounted
|
||||
private static final int SUSPENDED = 1 << 8;
|
||||
private static final int RUNNABLE_SUSPENDED = (RUNNABLE | SUSPENDED);
|
||||
private static final int PARKED_SUSPENDED = (PARKED | SUSPENDED);
|
||||
|
||||
// parking permit
|
||||
private volatile boolean parkPermit;
|
||||
@@ -194,8 +205,11 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs or continues execution of the continuation on the current thread.
|
||||
* Runs or continues execution on the current thread. The virtual thread is mounted
|
||||
* on the current thread before the task runs or continues. It unmounts when the
|
||||
* task completes or yields.
|
||||
*/
|
||||
@ChangesCurrentThread
|
||||
private void runContinuation() {
|
||||
// the carrier must be a platform thread
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
@@ -204,24 +218,27 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
|
||||
// set state to RUNNING
|
||||
int initialState = state();
|
||||
if (initialState == STARTED && compareAndSetState(STARTED, RUNNING)) {
|
||||
// first run
|
||||
} else if (initialState == RUNNABLE && compareAndSetState(RUNNABLE, RUNNING)) {
|
||||
// consume parking permit
|
||||
setParkPermit(false);
|
||||
if (initialState == STARTED || initialState == UNPARKED || initialState == YIELDED) {
|
||||
// newly started or continue after parking/blocking/Thread.yield
|
||||
if (!compareAndSetState(initialState, RUNNING)) {
|
||||
return;
|
||||
}
|
||||
// consume parking permit when continuing after parking
|
||||
if (initialState == UNPARKED) {
|
||||
setParkPermit(false);
|
||||
}
|
||||
} else {
|
||||
// not runnable
|
||||
return;
|
||||
}
|
||||
|
||||
// notify JVMTI before mount
|
||||
notifyJvmtiMount(/*hide*/true);
|
||||
|
||||
mount();
|
||||
try {
|
||||
cont.run();
|
||||
} finally {
|
||||
unmount();
|
||||
if (cont.isDone()) {
|
||||
afterTerminate();
|
||||
afterDone();
|
||||
} else {
|
||||
afterYield();
|
||||
}
|
||||
@@ -231,8 +248,7 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
/**
|
||||
* Submits the runContinuation task to the scheduler. For the default scheduler,
|
||||
* and calling it on a worker thread, the task will be pushed to the local queue,
|
||||
* otherwise it will be pushed to a submission queue.
|
||||
*
|
||||
* otherwise it will be pushed to an external submission queue.
|
||||
* @throws RejectedExecutionException
|
||||
*/
|
||||
private void submitRunContinuation() {
|
||||
@@ -245,7 +261,7 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the runContinuation task to the scheduler with a lazy submit.
|
||||
* Submits the runContinuation task to given scheduler with a lazy submit.
|
||||
* @throws RejectedExecutionException
|
||||
* @see ForkJoinPool#lazySubmit(ForkJoinTask)
|
||||
*/
|
||||
@@ -259,7 +275,7 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the runContinuation task to the scheduler as an external submit.
|
||||
* Submits the runContinuation task to the given scheduler as an external submit.
|
||||
* @throws RejectedExecutionException
|
||||
* @see ForkJoinPool#externalSubmit(ForkJoinTask)
|
||||
*/
|
||||
@@ -285,16 +301,12 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task in the context of this virtual thread. The virtual thread is
|
||||
* mounted on the current (carrier) thread before the task runs. It unmounts
|
||||
* from its carrier thread when the task completes.
|
||||
* Runs a task in the context of this virtual thread.
|
||||
*/
|
||||
@ChangesCurrentThread
|
||||
private void run(Runnable task) {
|
||||
assert state == RUNNING;
|
||||
assert Thread.currentThread() == this && state == RUNNING;
|
||||
|
||||
// first mount
|
||||
mount();
|
||||
// notify JVMTI, may post VirtualThreadStart event
|
||||
notifyJvmtiStart();
|
||||
|
||||
// emit JFR event if enabled
|
||||
@@ -322,12 +334,8 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
|
||||
} finally {
|
||||
// last unmount
|
||||
// notify JVMTI, may post VirtualThreadEnd event
|
||||
notifyJvmtiEnd();
|
||||
unmount();
|
||||
|
||||
// final state
|
||||
setState(TERMINATED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,6 +347,9 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
@ChangesCurrentThread
|
||||
@ReservedStackAccess
|
||||
private void mount() {
|
||||
// notify JVMTI before mount
|
||||
notifyJvmtiMount(/*hide*/true);
|
||||
|
||||
// sets the carrier thread
|
||||
Thread carrier = Thread.currentCarrierThread();
|
||||
setCarrierThread(carrier);
|
||||
@@ -375,6 +386,9 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
setCarrierThread(null);
|
||||
}
|
||||
carrier.clearInterrupt();
|
||||
|
||||
// notify JVMTI after unmount
|
||||
notifyJvmtiUnmount(/*hide*/false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -417,21 +431,15 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmounts this virtual thread, invokes Continuation.yield, and re-mounts the
|
||||
* thread when continued. When enabled, JVMTI must be notified from this method.
|
||||
* @return true if the yield was successful
|
||||
* Invokes Continuation.yield, notifying JVMTI (if enabled) to hide frames until
|
||||
* the continuation continues.
|
||||
*/
|
||||
@Hidden
|
||||
@ChangesCurrentThread
|
||||
private boolean yieldContinuation() {
|
||||
// unmount
|
||||
notifyJvmtiUnmount(/*hide*/true);
|
||||
unmount();
|
||||
try {
|
||||
return Continuation.yield(VTHREAD_SCOPE);
|
||||
} finally {
|
||||
// re-mount
|
||||
mount();
|
||||
notifyJvmtiMount(/*hide*/false);
|
||||
}
|
||||
}
|
||||
@@ -442,17 +450,17 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
* If yielding due to Thread.yield then it just submits the task to continue.
|
||||
*/
|
||||
private void afterYield() {
|
||||
assert carrierThread == null;
|
||||
|
||||
int s = state();
|
||||
assert (s == PARKING || s == YIELDING) && (carrierThread == null);
|
||||
|
||||
if (s == PARKING) {
|
||||
setState(PARKED);
|
||||
|
||||
// notify JVMTI that unmount has completed, thread is parked
|
||||
notifyJvmtiUnmount(/*hide*/false);
|
||||
// LockSupport.park/parkNanos
|
||||
if (s == PARKING || s == TIMED_PARKING) {
|
||||
int newState = (s == PARKING) ? PARKED : TIMED_PARKED;
|
||||
setState(newState);
|
||||
|
||||
// may have been unparked while parking
|
||||
if (parkPermit && compareAndSetState(PARKED, RUNNABLE)) {
|
||||
if (parkPermit && compareAndSetState(newState, UNPARKED)) {
|
||||
// lazy submit to continue on the current thread as carrier if possible
|
||||
if (currentThread() instanceof CarrierThread ct) {
|
||||
lazySubmitRunContinuation(ct.getPool());
|
||||
@@ -461,11 +469,12 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
|
||||
}
|
||||
} else if (s == YIELDING) { // Thread.yield
|
||||
setState(RUNNABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
// notify JVMTI that unmount has completed, thread is runnable
|
||||
notifyJvmtiUnmount(/*hide*/false);
|
||||
// Thread.yield
|
||||
if (s == YIELDING) {
|
||||
setState(YIELDED);
|
||||
|
||||
// external submit if there are no tasks in the local task queue
|
||||
if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) {
|
||||
@@ -473,30 +482,28 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
} else {
|
||||
submitRunContinuation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after the thread terminates execution. It notifies anyone
|
||||
* waiting for the thread to terminate.
|
||||
* Invoked after the continuation completes.
|
||||
*/
|
||||
private void afterTerminate() {
|
||||
afterTerminate(true, true);
|
||||
private void afterDone() {
|
||||
afterDone(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after the thread terminates (or start failed). This method
|
||||
* notifies anyone waiting for the thread to terminate.
|
||||
* Invoked after the continuation completes (or start failed). Sets the thread
|
||||
* state to TERMINATED and notifies anyone waiting for the thread to terminate.
|
||||
*
|
||||
* @param notifyContainer true if its container should be notified
|
||||
* @param executed true if the thread executed, false if it failed to start
|
||||
*/
|
||||
private void afterTerminate(boolean notifyContainer, boolean executed) {
|
||||
assert (state() == TERMINATED) && (carrierThread == null);
|
||||
|
||||
if (executed) {
|
||||
notifyJvmtiUnmount(/*hide*/false);
|
||||
}
|
||||
private void afterDone(boolean notifyContainer) {
|
||||
assert carrierThread == null;
|
||||
setState(TERMINATED);
|
||||
|
||||
// notify anyone waiting for this virtual thread to terminate
|
||||
CountDownLatch termination = this.termination;
|
||||
@@ -546,8 +553,7 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
started = true;
|
||||
} finally {
|
||||
if (!started) {
|
||||
setState(TERMINATED);
|
||||
afterTerminate(addedToContainer, /*executed*/false);
|
||||
afterDone(addedToContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -615,14 +621,14 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
boolean yielded = false;
|
||||
Future<?> unparker = scheduleUnpark(this::unpark, nanos);
|
||||
setState(PARKING);
|
||||
Future<?> unparker = scheduleUnpark(nanos); // may throw OOME
|
||||
setState(TIMED_PARKING);
|
||||
try {
|
||||
yielded = yieldContinuation(); // may throw
|
||||
} finally {
|
||||
assert (Thread.currentThread() == this) && (yielded == (state() == RUNNING));
|
||||
if (!yielded) {
|
||||
assert state() == PARKING;
|
||||
assert state() == TIMED_PARKING;
|
||||
setState(RUNNING);
|
||||
}
|
||||
cancel(unparker);
|
||||
@@ -654,7 +660,7 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
event = null;
|
||||
}
|
||||
|
||||
setState(PINNED);
|
||||
setState(timed ? TIMED_PINNED : PINNED);
|
||||
try {
|
||||
if (!parkPermit) {
|
||||
if (!timed) {
|
||||
@@ -680,14 +686,15 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule an unpark task to run after a given delay.
|
||||
* Schedule this virtual thread to be unparked after a given delay.
|
||||
*/
|
||||
@ChangesCurrentThread
|
||||
private Future<?> scheduleUnpark(Runnable unparker, long nanos) {
|
||||
private Future<?> scheduleUnpark(long nanos) {
|
||||
assert Thread.currentThread() == this;
|
||||
// need to switch to current carrier thread to avoid nested parking
|
||||
switchToCarrierThread();
|
||||
try {
|
||||
return UNPARKER.schedule(unparker, nanos, NANOSECONDS);
|
||||
return UNPARKER.schedule(this::unpark, nanos, NANOSECONDS);
|
||||
} finally {
|
||||
switchToVirtualThread(this);
|
||||
}
|
||||
@@ -722,7 +729,8 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
Thread currentThread = Thread.currentThread();
|
||||
if (!getAndSetParkPermit(true) && currentThread != this) {
|
||||
int s = state();
|
||||
if (s == PARKED && compareAndSetState(PARKED, RUNNABLE)) {
|
||||
boolean parked = (s == PARKED) || (s == TIMED_PARKED);
|
||||
if (parked && compareAndSetState(s, UNPARKED)) {
|
||||
if (currentThread instanceof VirtualThread vthread) {
|
||||
vthread.switchToCarrierThread();
|
||||
try {
|
||||
@@ -733,11 +741,11 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
} else {
|
||||
submitRunContinuation();
|
||||
}
|
||||
} else if (s == PINNED) {
|
||||
// unpark carrier thread when pinned.
|
||||
} else if ((s == PINNED) || (s == TIMED_PINNED)) {
|
||||
// unpark carrier thread when pinned
|
||||
synchronized (carrierThreadAccessLock()) {
|
||||
Thread carrier = carrierThread;
|
||||
if (carrier != null && state() == PINNED) {
|
||||
if (carrier != null && ((s = state()) == PINNED || s == TIMED_PINNED)) {
|
||||
U.unpark(carrier);
|
||||
}
|
||||
}
|
||||
@@ -874,7 +882,8 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
|
||||
@Override
|
||||
Thread.State threadState() {
|
||||
switch (state()) {
|
||||
int s = state();
|
||||
switch (s & ~SUSPENDED) {
|
||||
case NEW:
|
||||
return Thread.State.NEW;
|
||||
case STARTED:
|
||||
@@ -884,8 +893,8 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
} else {
|
||||
return Thread.State.RUNNABLE;
|
||||
}
|
||||
case RUNNABLE:
|
||||
case RUNNABLE_SUSPENDED:
|
||||
case UNPARKED:
|
||||
case YIELDED:
|
||||
// runnable, not mounted
|
||||
return Thread.State.RUNNABLE;
|
||||
case RUNNING:
|
||||
@@ -899,13 +908,16 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
// runnable, mounted
|
||||
return Thread.State.RUNNABLE;
|
||||
case PARKING:
|
||||
case TIMED_PARKING:
|
||||
case YIELDING:
|
||||
// runnable, mounted, not yet waiting
|
||||
// runnable, in transition
|
||||
return Thread.State.RUNNABLE;
|
||||
case PARKED:
|
||||
case PARKED_SUSPENDED:
|
||||
case PINNED:
|
||||
return Thread.State.WAITING;
|
||||
return State.WAITING;
|
||||
case TIMED_PARKED:
|
||||
case TIMED_PINNED:
|
||||
return State.TIMED_WAITING;
|
||||
case TERMINATED:
|
||||
return Thread.State.TERMINATED;
|
||||
default:
|
||||
@@ -940,35 +952,58 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
|
||||
/**
|
||||
* Returns the stack trace for this virtual thread if it is unmounted.
|
||||
* Returns null if the thread is in another state.
|
||||
* Returns null if the thread is mounted or in transition.
|
||||
*/
|
||||
private StackTraceElement[] tryGetStackTrace() {
|
||||
int initialState = state();
|
||||
return switch (initialState) {
|
||||
case RUNNABLE, PARKED -> {
|
||||
int suspendedState = initialState | SUSPENDED;
|
||||
if (compareAndSetState(initialState, suspendedState)) {
|
||||
try {
|
||||
yield cont.getStackTrace();
|
||||
} finally {
|
||||
assert state == suspendedState;
|
||||
setState(initialState);
|
||||
|
||||
// re-submit if runnable
|
||||
// re-submit if unparked while suspended
|
||||
if (initialState == RUNNABLE
|
||||
|| (parkPermit && compareAndSetState(PARKED, RUNNABLE))) {
|
||||
try {
|
||||
submitRunContinuation();
|
||||
} catch (RejectedExecutionException ignore) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
yield null;
|
||||
int initialState = state() & ~SUSPENDED;
|
||||
switch (initialState) {
|
||||
case NEW, STARTED, TERMINATED -> {
|
||||
return new StackTraceElement[0]; // unmounted, empty stack
|
||||
}
|
||||
case NEW, STARTED, TERMINATED -> new StackTraceElement[0]; // empty stack
|
||||
default -> null;
|
||||
case RUNNING, PINNED, TIMED_PINNED -> {
|
||||
return null; // mounted
|
||||
}
|
||||
case PARKED, TIMED_PARKED -> {
|
||||
// unmounted, not runnable
|
||||
}
|
||||
case UNPARKED, YIELDED -> {
|
||||
// unmounted, runnable
|
||||
}
|
||||
case PARKING, TIMED_PARKING, YIELDING -> {
|
||||
return null; // in transition
|
||||
}
|
||||
default -> throw new InternalError("" + initialState);
|
||||
}
|
||||
|
||||
// thread is unmounted, prevent it from continuing
|
||||
int suspendedState = initialState | SUSPENDED;
|
||||
if (!compareAndSetState(initialState, suspendedState)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// get stack trace and restore state
|
||||
StackTraceElement[] stack;
|
||||
try {
|
||||
stack = cont.getStackTrace();
|
||||
} finally {
|
||||
assert state == suspendedState;
|
||||
setState(initialState);
|
||||
}
|
||||
boolean resubmit = switch (initialState) {
|
||||
case UNPARKED, YIELDED -> {
|
||||
// resubmit as task may have run while suspended
|
||||
yield true;
|
||||
}
|
||||
case PARKED, TIMED_PARKED -> {
|
||||
// resubmit if unparked while suspended
|
||||
yield parkPermit && compareAndSetState(initialState, UNPARKED);
|
||||
}
|
||||
default -> throw new InternalError();
|
||||
};
|
||||
if (resubmit) {
|
||||
submitRunContinuation();
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @bug 8284161 8286788
|
||||
* @bug 8284161 8286788 8321270
|
||||
* @summary Test Thread API with virtual threads
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
@@ -106,7 +106,7 @@ class ThreadAPI {
|
||||
LockSupport.park();
|
||||
after.set(Thread.currentThread());
|
||||
});
|
||||
awaitParked(thread);
|
||||
await(thread, Thread.State.WAITING);
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
assertTrue(before.get() == thread);
|
||||
@@ -130,7 +130,7 @@ class ThreadAPI {
|
||||
});
|
||||
synchronized (lock) {
|
||||
thread.start();
|
||||
awaitBlocked(thread);
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
}
|
||||
thread.join();
|
||||
assertTrue(ref1.get() == thread);
|
||||
@@ -160,7 +160,7 @@ class ThreadAPI {
|
||||
lock.lock();
|
||||
try {
|
||||
thread.start();
|
||||
awaitParked(thread);
|
||||
await(thread, Thread.State.WAITING);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -765,6 +765,7 @@ class ThreadAPI {
|
||||
assertFalse(thread.join(Duration.ofMillis(100)));
|
||||
} finally {
|
||||
done.set(true);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -897,7 +898,7 @@ class ThreadAPI {
|
||||
exception.set(e);
|
||||
}
|
||||
});
|
||||
awaitParked(thread);
|
||||
await(thread, Thread.State.TIMED_WAITING);
|
||||
thread.interrupt();
|
||||
thread.join();
|
||||
assertNull(exception.get());
|
||||
@@ -917,7 +918,7 @@ class ThreadAPI {
|
||||
exception.set(e);
|
||||
}
|
||||
});
|
||||
awaitParked(thread);
|
||||
await(thread, Thread.State.WAITING);
|
||||
thread.interrupt();
|
||||
thread.join();
|
||||
assertNull(exception.get());
|
||||
@@ -1032,16 +1033,16 @@ class ThreadAPI {
|
||||
void testSetPriority1() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread me = Thread.currentThread();
|
||||
assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
|
||||
|
||||
me.setPriority(Thread.MAX_PRIORITY);
|
||||
assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
|
||||
|
||||
me.setPriority(Thread.NORM_PRIORITY);
|
||||
assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
|
||||
|
||||
me.setPriority(Thread.MIN_PRIORITY);
|
||||
assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> me.setPriority(-1));
|
||||
});
|
||||
@@ -1055,33 +1056,33 @@ class ThreadAPI {
|
||||
var thread = Thread.ofVirtual().unstarted(LockSupport::park);
|
||||
|
||||
// not started
|
||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||
|
||||
thread.setPriority(Thread.MAX_PRIORITY);
|
||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||
|
||||
thread.setPriority(Thread.NORM_PRIORITY);
|
||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
|
||||
|
||||
// running
|
||||
thread.start();
|
||||
try {
|
||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||
thread.setPriority(Thread.NORM_PRIORITY);
|
||||
|
||||
thread.setPriority(Thread.MAX_PRIORITY);
|
||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||
|
||||
thread.setPriority(Thread.NORM_PRIORITY);
|
||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
|
||||
|
||||
@@ -1091,7 +1092,7 @@ class ThreadAPI {
|
||||
thread.join();
|
||||
|
||||
// terminated
|
||||
assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
|
||||
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1190,6 +1191,36 @@ class ThreadAPI {
|
||||
assertEquals(List.of("A", "A", "B"), list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Thread.yield does not consume the thread's parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testYield3() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
Thread.yield();
|
||||
LockSupport.park(); // should not park
|
||||
});
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Thread.yield does not make available the thread's parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testYield4() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
Thread.yield();
|
||||
LockSupport.park(); // should park
|
||||
});
|
||||
try {
|
||||
await(thread, Thread.State.WAITING);
|
||||
} finally {
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread.onSpinWait.
|
||||
*/
|
||||
@@ -1650,53 +1681,79 @@ class ThreadAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is not started.
|
||||
* Test Thread::getState when thread is new/unstarted.
|
||||
*/
|
||||
@Test
|
||||
void testGetState1() {
|
||||
var thread = Thread.ofVirtual().unstarted(() -> { });
|
||||
assertTrue(thread.getState() == Thread.State.NEW);
|
||||
assertEquals(Thread.State.NEW, thread.getState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is terminated.
|
||||
*/
|
||||
@Test
|
||||
void testGetState2() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> { });
|
||||
thread.join();
|
||||
assertEquals(Thread.State.TERMINATED, thread.getState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is runnable (mounted).
|
||||
*/
|
||||
@Test
|
||||
void testGetState2() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread.State state = Thread.currentThread().getState();
|
||||
assertTrue(state == Thread.State.RUNNABLE);
|
||||
void testGetState3() throws Exception {
|
||||
var started = new CountDownLatch(1);
|
||||
var done = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
|
||||
// spin until done
|
||||
while (!done.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to start
|
||||
started.await();
|
||||
|
||||
// thread should be runnable
|
||||
assertEquals(Thread.State.RUNNABLE, thread.getState());
|
||||
} finally {
|
||||
done.set(true);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is runnable (not mounted).
|
||||
*/
|
||||
@Test
|
||||
void testGetState3() throws Exception {
|
||||
void testGetState4() throws Exception {
|
||||
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
AtomicBoolean completed = new AtomicBoolean();
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
|
||||
Thread t1 = builder.start(() -> {
|
||||
Thread t2 = builder.unstarted(LockSupport::park);
|
||||
assertTrue(t2.getState() == Thread.State.NEW);
|
||||
assertEquals(Thread.State.NEW, t2.getState());
|
||||
|
||||
// start t2 to make it runnable
|
||||
t2.start();
|
||||
try {
|
||||
assertTrue(t2.getState() == Thread.State.RUNNABLE);
|
||||
assertEquals(Thread.State.RUNNABLE, t2.getState());
|
||||
|
||||
// yield to allow t2 to run and park
|
||||
Thread.yield();
|
||||
assertTrue(t2.getState() == Thread.State.WAITING);
|
||||
assertEquals(Thread.State.WAITING, t2.getState());
|
||||
} finally {
|
||||
// unpark t2 to make it runnable again
|
||||
LockSupport.unpark(t2);
|
||||
}
|
||||
|
||||
// t2 should be runnable (not mounted)
|
||||
assertTrue(t2.getState() == Thread.State.RUNNABLE);
|
||||
assertEquals(Thread.State.RUNNABLE, t2.getState());
|
||||
|
||||
completed.set(true);
|
||||
});
|
||||
@@ -1706,48 +1763,21 @@ class ThreadAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is parked.
|
||||
*/
|
||||
@Test
|
||||
void testGetState4() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(LockSupport::park);
|
||||
while (thread.getState() != Thread.State.WAITING) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is parked while holding a monitor.
|
||||
* Test Thread::getState when thread is waiting to enter a monitor.
|
||||
*/
|
||||
@Test
|
||||
void testGetState5() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
LockSupport.park();
|
||||
}
|
||||
});
|
||||
while (thread.getState() != Thread.State.WAITING) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is waiting for a monitor.
|
||||
*/
|
||||
@Test
|
||||
void testGetState6() throws Exception {
|
||||
var started = new CountDownLatch(1);
|
||||
var thread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) { }
|
||||
});
|
||||
synchronized (lock) {
|
||||
thread.start();
|
||||
while (thread.getState() != Thread.State.BLOCKED) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
started.await();
|
||||
|
||||
// wait for thread to block
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
}
|
||||
thread.join();
|
||||
}
|
||||
@@ -1756,27 +1786,124 @@ class ThreadAPI {
|
||||
* Test Thread::getState when thread is waiting in Object.wait.
|
||||
*/
|
||||
@Test
|
||||
void testGetState7() throws Exception {
|
||||
void testGetState6() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
try { lock.wait(); } catch (InterruptedException e) { }
|
||||
}
|
||||
});
|
||||
while (thread.getState() != Thread.State.WAITING) {
|
||||
Thread.sleep(20);
|
||||
try {
|
||||
// wait for thread to wait
|
||||
await(thread, Thread.State.WAITING);
|
||||
} finally {
|
||||
thread.interrupt();
|
||||
thread.join();
|
||||
}
|
||||
thread.interrupt();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is terminated.
|
||||
* Test Thread::getState when thread is waiting in Object.wait(millis).
|
||||
*/
|
||||
@Test
|
||||
void testGetState7() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to wait
|
||||
await(thread, Thread.State.TIMED_WAITING);
|
||||
} finally {
|
||||
thread.interrupt();
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is parked.
|
||||
*/
|
||||
@Test
|
||||
void testGetState8() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> { });
|
||||
thread.join();
|
||||
assertTrue(thread.getState() == Thread.State.TERMINATED);
|
||||
var thread = Thread.ofVirtual().start(LockSupport::park);
|
||||
try {
|
||||
await(thread, Thread.State.WAITING);
|
||||
} finally {
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is timed parked.
|
||||
*/
|
||||
@Test
|
||||
void testGetState9() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> LockSupport.parkNanos(Long.MAX_VALUE));
|
||||
try {
|
||||
await(thread, Thread.State.TIMED_WAITING);
|
||||
} finally {
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is parked while holding a monitor.
|
||||
*/
|
||||
@Test
|
||||
void testGetState10() throws Exception {
|
||||
var started = new CountDownLatch(1);
|
||||
var done = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
while (!done.get()) {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to start
|
||||
started.await();
|
||||
|
||||
// wait for thread to park
|
||||
await(thread, Thread.State.WAITING);
|
||||
} finally {
|
||||
done.set(true);
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is timed parked while holding a monitor.
|
||||
*/
|
||||
@Test
|
||||
void testGetState11() throws Exception {
|
||||
var started = new CountDownLatch(1);
|
||||
var done = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
while (!done.get()) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to start
|
||||
started.await();
|
||||
|
||||
// wait for thread to park
|
||||
await(thread, Thread.State.TIMED_WAITING);
|
||||
} finally {
|
||||
done.set(true);
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1899,9 +2026,7 @@ class ThreadAPI {
|
||||
}
|
||||
|
||||
// wait for virtual thread to block in wait
|
||||
while (vthread.getState() != Thread.State.WAITING) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
await(vthread, Thread.State.WAITING);
|
||||
|
||||
// get stack trace of both carrier and virtual thread
|
||||
StackTraceElement[] carrierStackTrace = carrier.getStackTrace();
|
||||
@@ -1928,12 +2053,7 @@ class ThreadAPI {
|
||||
@Test
|
||||
void testGetStackTrace5() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(LockSupport::park);
|
||||
|
||||
// wait for thread to park
|
||||
while (thread.getState() != Thread.State.WAITING) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
|
||||
await(thread, Thread.State.WAITING);
|
||||
try {
|
||||
StackTraceElement[] stack = thread.getStackTrace();
|
||||
assertTrue(contains(stack, "LockSupport.park"));
|
||||
@@ -1996,9 +2116,7 @@ class ThreadAPI {
|
||||
}
|
||||
|
||||
// wait for virtual thread to block in wait
|
||||
while (vthread.getState() != Thread.State.WAITING) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
await(vthread, Thread.State.WAITING);
|
||||
|
||||
// get all stack traces
|
||||
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
|
||||
@@ -2034,7 +2152,7 @@ class ThreadAPI {
|
||||
var vgroup = thread.getThreadGroup();
|
||||
thread.start();
|
||||
try {
|
||||
assertTrue(thread.getThreadGroup() == vgroup);
|
||||
assertEquals(vgroup, thread.getThreadGroup());
|
||||
} finally {
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
@@ -2051,7 +2169,7 @@ class ThreadAPI {
|
||||
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
|
||||
Thread child = new Thread(() -> { });
|
||||
ThreadGroup group = child.getThreadGroup();
|
||||
assertTrue(group == vgroup);
|
||||
assertEquals(vgroup, group);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2068,19 +2186,19 @@ class ThreadAPI {
|
||||
thread.join();
|
||||
|
||||
ThreadGroup vgroup = ref.get();
|
||||
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
|
||||
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
||||
|
||||
ThreadGroup group = new ThreadGroup(vgroup, "group");
|
||||
assertTrue(group.getParent() == vgroup);
|
||||
assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY);
|
||||
assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority());
|
||||
|
||||
vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
|
||||
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
|
||||
assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY - 1);
|
||||
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
||||
assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority());
|
||||
|
||||
vgroup.setMaxPriority(Thread.MIN_PRIORITY);
|
||||
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
|
||||
assertTrue(group.getMaxPriority() == Thread.MIN_PRIORITY);
|
||||
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
||||
assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2091,20 +2209,19 @@ class ThreadAPI {
|
||||
void testThreadGroup4() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
|
||||
|
||||
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
|
||||
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
||||
|
||||
ThreadGroup group = new ThreadGroup("group");
|
||||
assertTrue(group.getParent() == vgroup);
|
||||
assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY);
|
||||
assertEquals(vgroup, group.getParent());
|
||||
assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority());
|
||||
|
||||
vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
|
||||
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
|
||||
assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY - 1);
|
||||
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
||||
assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority());
|
||||
|
||||
vgroup.setMaxPriority(Thread.MIN_PRIORITY);
|
||||
assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
|
||||
assertTrue(group.getMaxPriority() == Thread.MIN_PRIORITY);
|
||||
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
||||
assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2208,9 +2325,7 @@ class ThreadAPI {
|
||||
me.setName("fred");
|
||||
LockSupport.park();
|
||||
});
|
||||
while (thread.getState() != Thread.State.WAITING) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
await(thread, Thread.State.WAITING);
|
||||
try {
|
||||
assertTrue(thread.toString().contains("fred"));
|
||||
} finally {
|
||||
@@ -2233,23 +2348,11 @@ class ThreadAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to park.
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
static void awaitParked(Thread thread) throws InterruptedException {
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != Thread.State.WAITING && state != Thread.State.TIMED_WAITING) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to block waiting on a monitor.
|
||||
*/
|
||||
static void awaitBlocked(Thread thread) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != Thread.State.BLOCKED) {
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8322818
|
||||
* @summary Stress test Thread.getStackTrace on a virtual thread that is pinned
|
||||
* @requires vm.debug != true
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run main/othervm GetStackTraceALotWhenPinned 500000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run main/othervm/timeout=300 GetStackTraceALotWhenPinned 200000
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
|
||||
public class GetStackTraceALotWhenPinned {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// need at least two carrier threads when main thread is a virtual thread
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
int iterations = Integer.parseInt(args[0]);
|
||||
var barrier = new Barrier(2);
|
||||
|
||||
// Start a virtual thread that loops doing Thread.yield and parking while pinned.
|
||||
// This loop creates the conditions for the main thread to sample the stack trace
|
||||
// as it transitions from being unmounted to parking while pinned.
|
||||
var thread = Thread.startVirtualThread(() -> {
|
||||
boolean timed = false;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
// wait for main thread to arrive
|
||||
barrier.await();
|
||||
|
||||
Thread.yield();
|
||||
synchronized (GetStackTraceALotWhenPinned.class) {
|
||||
if (timed) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
} else {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
timed = !timed;
|
||||
}
|
||||
});
|
||||
|
||||
long lastTimestamp = System.currentTimeMillis();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
// wait for virtual thread to arrive
|
||||
barrier.await();
|
||||
|
||||
thread.getStackTrace();
|
||||
LockSupport.unpark(thread);
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - lastTimestamp) > 500) {
|
||||
System.out.format("%s %d remaining ...%n", Instant.now(), (iterations - i));
|
||||
lastTimestamp = currentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alow threads wait for each other to reach a common barrier point. This class does
|
||||
* not park threads that are waiting for the barrier to trip, instead it spins. This
|
||||
* makes it suitable for tests that use LockSupport.park or Thread.yield.
|
||||
*/
|
||||
private static class Barrier {
|
||||
private final int parties;
|
||||
private final AtomicInteger count;
|
||||
private volatile int generation;
|
||||
|
||||
Barrier(int parties) {
|
||||
this.parties = parties;
|
||||
this.count = new AtomicInteger(parties);
|
||||
}
|
||||
|
||||
void await() {
|
||||
int g = generation;
|
||||
if (count.decrementAndGet() == 0) {
|
||||
count.set(parties);
|
||||
generation = g + 1;
|
||||
} else {
|
||||
while (generation == g) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
95
test/jdk/java/lang/Thread/virtual/stress/ParkALot.java
Normal file
95
test/jdk/java/lang/Thread/virtual/stress/ParkALot.java
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test parking and unparking
|
||||
* @requires vm.debug != true
|
||||
* @run main/othervm ParkALot 500000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true
|
||||
* @run main/othervm ParkALot 100000
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
public class ParkALot {
|
||||
private static final int ITERATIONS = 1_000_000;
|
||||
|
||||
public static void main(String[] args) {
|
||||
int iterations;
|
||||
if (args.length > 0) {
|
||||
iterations = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
iterations = ITERATIONS;
|
||||
}
|
||||
|
||||
int maxThreads = Math.clamp(Runtime.getRuntime().availableProcessors() / 2, 1, 4);
|
||||
for (int nthreads = 1; nthreads <= maxThreads; nthreads++) {
|
||||
System.out.format("%s %d thread(s) ...%n", Instant.now(), nthreads);
|
||||
ThreadFactory factory = Thread.ofPlatform().factory();
|
||||
try (var executor = Executors.newThreadPerTaskExecutor(factory)) {
|
||||
for (int i = 0; i < nthreads; i++) {
|
||||
executor.submit(() -> parkALot(iterations));
|
||||
}
|
||||
}
|
||||
System.out.format("%s %d thread(s) done%n", Instant.now(), nthreads);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a virtual thread that alternates between untimed and timed parking.
|
||||
* A platform thread spins unparking the virtual thread.
|
||||
*/
|
||||
private static void parkALot(int iterations) {
|
||||
Thread vthread = Thread.ofVirtual().start(() -> {
|
||||
int i = 0;
|
||||
boolean timed = false;
|
||||
while (i < iterations) {
|
||||
if (timed) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
timed = false;
|
||||
} else {
|
||||
LockSupport.park();
|
||||
timed = true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
});
|
||||
|
||||
Thread.State state;
|
||||
while ((state = vthread.getState()) != Thread.State.TERMINATED) {
|
||||
if (state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) {
|
||||
LockSupport.unpark(vthread);
|
||||
} else {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user