af5bf81754
Reviewed-by: sspitsyn, dholmes
330 lines
13 KiB
Java
330 lines
13 KiB
Java
/*
|
|
* 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
|
|
* @bug 8309612 8310584
|
|
* @summary The test verifies that JVMTI GetThreadState function reports expected state
|
|
* for mounted (pinned) virtual thread and its carrier thread
|
|
* @requires vm.jvmti
|
|
* @requires vm.continuations
|
|
* @run main/othervm/native
|
|
* -agentlib:GetThreadStateMountedTest
|
|
* GetThreadStateMountedTest
|
|
*/
|
|
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.locks.LockSupport;
|
|
|
|
/**
|
|
* The test implements different scenarios to get desired JVMTI thread states.
|
|
* For each scenario the test also checks states after carrier and virtual threads suspend/resume
|
|
* and after virtual thread interrupt.
|
|
* Special handling is required for WAITING state scenarios:
|
|
* Spurious wakeups may cause unexpected thread state change and this causes test failure.
|
|
* To avoid this, the test thread should be suspended (i.e. carrier and/or mounted virtual thread is suspended).
|
|
*/
|
|
public class GetThreadStateMountedTest {
|
|
|
|
static final int JVMTI_THREAD_STATE_RUNNABLE = 0x0004;
|
|
static final int JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400;
|
|
static final int JVMTI_THREAD_STATE_WAITING = 0x0080;
|
|
static final int JVMTI_THREAD_STATE_WAITING_INDEFINITELY = 0x0010;
|
|
static final int JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT = 0x0020;
|
|
static final int JVMTI_THREAD_STATE_IN_OBJECT_WAIT = 0x0100;
|
|
static final int JVMTI_THREAD_STATE_SLEEPING = 0x0040;
|
|
static final int JVMTI_THREAD_STATE_PARKED = 0x0200;
|
|
static final int JVMTI_THREAD_STATE_IN_NATIVE = 0x400000;
|
|
|
|
static void runnable() throws Exception {
|
|
TestStatus status = new TestStatus("JVMTI_THREAD_STATE_RUNNABLE");
|
|
CountDownLatch ready = new CountDownLatch(1);
|
|
final boolean[] stopFlag = new boolean[1];
|
|
Thread vthread = createPinnedVThread(() -> {
|
|
ready.countDown();
|
|
int i = 0;
|
|
while (!stopFlag[0]) {
|
|
if (i < 200) {
|
|
i++;
|
|
} else {
|
|
i = 0;
|
|
}
|
|
}
|
|
});
|
|
vthread.start();
|
|
ready.await();
|
|
testThreadStates(vthread, false, true, JVMTI_THREAD_STATE_RUNNABLE);
|
|
stopFlag[0] = true;
|
|
status.print();
|
|
}
|
|
|
|
static void blockedOnMonitorEnter() throws Exception {
|
|
// JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER
|
|
// Thread is waiting to enter a synchronized block/method or,
|
|
// after an Object.wait(), waiting to re-enter a synchronized block/method.
|
|
TestStatus status = new TestStatus("JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER");
|
|
CountDownLatch ready = new CountDownLatch(1);
|
|
final Object syncObj = new Object();
|
|
Thread vthread = createPinnedVThread(() -> {
|
|
ready.countDown();
|
|
synchronized (syncObj) {
|
|
}
|
|
});
|
|
synchronized (syncObj) {
|
|
vthread.start();
|
|
ready.await();
|
|
Thread.sleep(500); // wait some time to ensure the thread is blocked on monitor
|
|
testThreadStates(vthread, false, true, JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER);
|
|
}
|
|
status.print();
|
|
}
|
|
|
|
static void waiting(boolean withTimeout) throws Exception {
|
|
// JVMTI_THREAD_STATE_WAITING
|
|
// Thread is waiting.
|
|
// JVMTI_THREAD_STATE_WAITING_INDEFINITELY
|
|
// Thread is waiting without a timeout. For example, Object.wait().
|
|
// JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT
|
|
// Thread is waiting with a maximum time to wait specified. For example, Object.wait(long).
|
|
TestStatus status = new TestStatus(withTimeout
|
|
? "JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT"
|
|
: "JVMTI_THREAD_STATE_WAITING_INDEFINITELY");
|
|
CountDownLatch ready = new CountDownLatch(1);
|
|
// Test thread exits by InterruptedException,
|
|
// stopFlag is to handle spurious wakeups.
|
|
final boolean[] stopFlag = new boolean[1];
|
|
final Object syncObj = new Object();
|
|
Thread vthread = createPinnedVThread(() -> {
|
|
synchronized (syncObj) {
|
|
try {
|
|
ready.countDown();
|
|
while (!stopFlag[0]) {
|
|
if (withTimeout) {
|
|
syncObj.wait(60000);
|
|
} else {
|
|
syncObj.wait();
|
|
}
|
|
}
|
|
} catch (InterruptedException ex) {
|
|
// expected after testThreadStates
|
|
}
|
|
}
|
|
});
|
|
vthread.start();
|
|
ready.await();
|
|
|
|
// Suspend test thread in "waiting" state.
|
|
suspendWaiting(vthread);
|
|
|
|
int expectedState = JVMTI_THREAD_STATE_WAITING
|
|
| JVMTI_THREAD_STATE_IN_OBJECT_WAIT
|
|
| (withTimeout
|
|
? JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT
|
|
: JVMTI_THREAD_STATE_WAITING_INDEFINITELY);
|
|
testThreadStates(vthread, true, true, expectedState);
|
|
// signal test thread to finish (for safety, Object.wait should throw InterruptedException)
|
|
synchronized (syncObj) {
|
|
stopFlag[0] = true;
|
|
syncObj.notifyAll();
|
|
}
|
|
status.print();
|
|
}
|
|
|
|
static void sleeping() throws Exception {
|
|
// JVMTI_THREAD_STATE_SLEEPING
|
|
// Thread is sleeping -- Thread.sleep.
|
|
// JVMTI_THREAD_STATE_PARKED
|
|
// A virtual thread that is sleeping, in Thread.sleep,
|
|
// may have this state flag set instead of JVMTI_THREAD_STATE_SLEEPING.
|
|
TestStatus status = new TestStatus("JVMTI_THREAD_STATE_SLEEPING");
|
|
CountDownLatch ready = new CountDownLatch(1);
|
|
// Test thread exits by InterruptedException,
|
|
// stopFlag is to handle spurious wakeups.
|
|
final boolean[] stopFlag = new boolean[1];
|
|
Thread vthread = createPinnedVThread(() -> {
|
|
ready.countDown();
|
|
try {
|
|
while (!stopFlag[0]) {
|
|
Thread.sleep(60000);
|
|
}
|
|
} catch (InterruptedException ex) {
|
|
// expected, ignore
|
|
}
|
|
});
|
|
vthread.start();
|
|
ready.await();
|
|
|
|
// Suspend test thread in "waiting" state.
|
|
suspendWaiting(vthread);
|
|
|
|
// vthread is suspended, set stopFlag before testThreadStates
|
|
stopFlag[0] = true;
|
|
|
|
// don't test interrupt() - it causes thread state change for parked thread
|
|
// even if it's suspended
|
|
testThreadStates(vthread, true, false,
|
|
JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT,
|
|
JVMTI_THREAD_STATE_SLEEPING | JVMTI_THREAD_STATE_PARKED);
|
|
status.print();
|
|
}
|
|
|
|
static void parked() throws Exception {
|
|
// JVMTI_THREAD_STATE_PARKED
|
|
// Thread is parked, for example: LockSupport.park, LockSupport.parkUtil and LockSupport.parkNanos.
|
|
TestStatus status = new TestStatus("JVMTI_THREAD_STATE_PARKED");
|
|
CountDownLatch ready = new CountDownLatch(1);
|
|
final boolean[] stopFlag = new boolean[1];
|
|
|
|
Thread vthread = createPinnedVThread(() -> {
|
|
ready.countDown();
|
|
while (!stopFlag[0]) {
|
|
LockSupport.park(Thread.currentThread());
|
|
}
|
|
});
|
|
vthread.start();
|
|
ready.await();
|
|
|
|
// Suspend test thread in "waiting" state.
|
|
suspendWaiting(vthread);
|
|
|
|
// vthread is suspended, set stopFlag before testThreadStates
|
|
stopFlag[0] = true;
|
|
|
|
// don't test interrupt() - it causes thread state change for parked thread
|
|
// even if it's suspended
|
|
testThreadStates(vthread, true, false,
|
|
JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_INDEFINITELY | JVMTI_THREAD_STATE_PARKED);
|
|
// allow test thread to finish
|
|
LockSupport.unpark(vthread);
|
|
status.print();
|
|
}
|
|
|
|
static void inNative() throws Exception {
|
|
TestStatus status = new TestStatus("JVMTI_THREAD_STATE_IN_NATIVE");
|
|
Thread vthread = createPinnedVThread(() -> {
|
|
waitInNative();
|
|
});
|
|
vthread.start();
|
|
while (!waitInNativeReady) {
|
|
Thread.sleep(50);
|
|
}
|
|
testThreadStates(vthread, false, true,
|
|
JVMTI_THREAD_STATE_RUNNABLE | JVMTI_THREAD_STATE_IN_NATIVE,
|
|
0);
|
|
endWait();
|
|
status.print();
|
|
}
|
|
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
runnable();
|
|
blockedOnMonitorEnter();
|
|
waiting(false);
|
|
waiting(true);
|
|
sleeping();
|
|
parked();
|
|
inNative();
|
|
|
|
int errCount = getErrorCount();
|
|
if (errCount > 0) {
|
|
throw new RuntimeException("Test failed, " + errCount + " errors");
|
|
}
|
|
}
|
|
|
|
private static Thread createPinnedVThread(Runnable runnable) {
|
|
final Object syncObj = new Object();
|
|
return Thread.ofVirtual().unstarted(() -> {
|
|
synchronized (syncObj) {
|
|
runnable.run();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Native implementation of suspendWaiting.
|
|
// Returns false if the method is not able to reach the desired state in several tries.
|
|
private static native boolean trySuspendInWaitingState(Thread vthread);
|
|
|
|
// Suspends virtual thread and ensures it's suspended in "waiting" state
|
|
// (to handle possible spurious wakeups).
|
|
// throws an exception if the method is not able to reach the desired state in several tries.
|
|
private static void suspendWaiting(Thread vthread) {
|
|
boolean result = trySuspendInWaitingState(vthread);
|
|
if (!result) {
|
|
throw new RuntimeException("Failed to suspend thread in WAITING state");
|
|
}
|
|
}
|
|
|
|
// Tests thread states (vthread and the carrier thread).
|
|
// expectedStrong specifies value which must be present in vthreat state;
|
|
// expectedWeak is a combination of bits which may be set in vthreat state
|
|
// (at least one of the bit must set, but not all).
|
|
// Note: Last steps of the testing are interrupt/resume the virtual thread,
|
|
// so after the call vthread is interrupted.
|
|
private static native void testThread(Thread vthread, boolean isVThreadSuspended,
|
|
boolean testInterrupt,
|
|
int expectedStrong, int expectedWeak);
|
|
private static native int getErrorCount();
|
|
// To retry test case when spurious wakeup detected.
|
|
private static native int resetErrorCount(int count);
|
|
|
|
private static boolean waitInNativeReady = false;
|
|
|
|
// Sets waitInNativeReady static field to true
|
|
// and then waits until endWait() method is called.
|
|
private static native void waitInNative();
|
|
// Signals waitInNative() to exit.
|
|
private static native void endWait();
|
|
|
|
private static void testThreadStates(Thread vthread, boolean isVThreadSuspended,
|
|
boolean testInterrupt,
|
|
int expectedStrong, int expectedWeak) {
|
|
String name = vthread.toString();
|
|
log("Thread " + name);
|
|
testThread(vthread, isVThreadSuspended, testInterrupt, expectedStrong, expectedWeak);
|
|
}
|
|
|
|
private static void testThreadStates(Thread vthread, boolean isVThreadSuspended,
|
|
boolean testInterrupt, int expectedState) {
|
|
testThreadStates(vthread, isVThreadSuspended, testInterrupt, expectedState, 0);
|
|
}
|
|
|
|
// helper class to print status of each test
|
|
private static class TestStatus {
|
|
private final String name;
|
|
private final int startErrorCount;
|
|
TestStatus(String name) {
|
|
this.name = name;
|
|
startErrorCount = getErrorCount();
|
|
log(">>" + name);
|
|
}
|
|
void print() {
|
|
log("<<" + name + (startErrorCount == getErrorCount() ? " - OK" : " - FAILED"));
|
|
log("");
|
|
}
|
|
}
|
|
|
|
private static void log(Object s) {
|
|
System.out.println(s);
|
|
}
|
|
}
|