7e2bcf6d00
Reviewed-by: kevinw
418 lines
15 KiB
Java
418 lines
15 KiB
Java
/*
|
|
* 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 id=default
|
|
* @bug 8312498
|
|
* @summary Basic test for JVMTI GetThreadState with virtual threads
|
|
* @modules jdk.management
|
|
* @library /test/lib
|
|
* @run junit/othervm/native --enable-native-access=ALL-UNNAMED GetThreadStateTest
|
|
*/
|
|
|
|
/*
|
|
* @test id=no-vmcontinuations
|
|
* @requires vm.continuations
|
|
* @modules jdk.management
|
|
* @library /test/lib
|
|
* @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations --enable-native-access=ALL-UNNAMED GetThreadStateTest
|
|
*/
|
|
|
|
import java.util.StringJoiner;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.locks.LockSupport;
|
|
|
|
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
|
import jdk.test.lib.thread.VThreadPinner;
|
|
import org.junit.jupiter.api.BeforeAll;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.params.ParameterizedTest;
|
|
import org.junit.jupiter.params.provider.ValueSource;
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
class GetThreadStateTest {
|
|
|
|
@BeforeAll
|
|
static void setup() {
|
|
System.loadLibrary("GetThreadStateTest");
|
|
init();
|
|
|
|
// need >=2 carriers for testing pinning when main thread is a virtual thread
|
|
if (Thread.currentThread().isVirtual()) {
|
|
VThreadRunner.ensureParallelism(2);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 started = new AtomicBoolean();
|
|
var done = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
started.set(true);
|
|
|
|
// spin until done
|
|
while (!done.get()) {
|
|
Thread.onSpinWait();
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to start execution
|
|
awaitTrue(started);
|
|
|
|
// 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 when pinned and not pinned.
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testMonitorEnter(boolean pinned) throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
Object lock = new Object();
|
|
var thread = Thread.ofVirtual().unstarted(() -> {
|
|
if (pinned) {
|
|
VThreadPinner.runPinned(() -> {
|
|
ready.set(true);
|
|
synchronized (lock) { }
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
synchronized (lock) { }
|
|
}
|
|
});
|
|
try {
|
|
synchronized (lock) {
|
|
// start thread and wait for it to start execution
|
|
thread.start();
|
|
awaitTrue(ready);
|
|
|
|
// 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() when pinned and not pinned.
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testObjectWait(boolean pinned) throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
Object lock = new Object();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
synchronized (lock) {
|
|
try {
|
|
if (pinned) {
|
|
VThreadPinner.runPinned(() -> {
|
|
ready.set(true);
|
|
lock.wait();
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
lock.wait();
|
|
}
|
|
} catch (InterruptedException e) { }
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to start execution
|
|
awaitTrue(ready);
|
|
|
|
// 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) when pinned and not pinned.
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testObjectWaitMillis(boolean pinned) throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
Object lock = new Object();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
synchronized (lock) {
|
|
synchronized (lock) {
|
|
try {
|
|
if (pinned) {
|
|
VThreadPinner.runPinned(() -> {
|
|
ready.set(true);
|
|
lock.wait(Long.MAX_VALUE);
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
lock.wait(Long.MAX_VALUE);
|
|
}
|
|
} catch (InterruptedException e) { }
|
|
}
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to start execution
|
|
awaitTrue(ready);
|
|
|
|
// 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 when pinned and not pinned.
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testPark(boolean pinned) throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
var done = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
if (pinned) {
|
|
VThreadPinner.runPinned(() -> {
|
|
ready.set(true);
|
|
while (!done.get()) {
|
|
LockSupport.park();
|
|
}
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
while (!done.get()) {
|
|
LockSupport.park();
|
|
}
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to start execution
|
|
awaitTrue(ready);
|
|
|
|
// 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 when pinned and not pinned.
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testParkNanos(boolean pinned) throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
var done = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
if (pinned) {
|
|
VThreadPinner.runPinned(() -> {
|
|
ready.set(true);
|
|
while (!done.get()) {
|
|
LockSupport.parkNanos(Long.MAX_VALUE);
|
|
}
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
while (!done.get()) {
|
|
LockSupport.parkNanos(Long.MAX_VALUE);
|
|
}
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to start execution
|
|
awaitTrue(ready);
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Waits for the boolean value to become true.
|
|
*/
|
|
private static void awaitTrue(AtomicBoolean ref) throws Exception {
|
|
while (!ref.get()) {
|
|
Thread.sleep(20);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|