Skip to content

Commit

Permalink
8310066: Improve test coverage for JVMTI GetThreadState on carrier an…
Browse files Browse the repository at this point in the history
…d mounted vthread

Reviewed-by: sspitsyn, cjplummer
  • Loading branch information
Alex Menkov committed Jul 11, 2023
1 parent 6cb9ec3 commit 15195e6
Show file tree
Hide file tree
Showing 2 changed files with 543 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
/*
* 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();
/* "waiting" test cases fail due JDK-8310584
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);
}
}

1 comment on commit 15195e6

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.