ADDITIONAL SYSTEM INFORMATION :
Ubunu 22.10 x86_64
openjdk version "20-ea" 2023-03-21
OpenJDK Runtime Environment (build 20-ea+20-1466)
OpenJDK 64-Bit Server VM (build 20-ea+20-1466, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
GetThreadListStackTraces returns JVMTI_THREAD_STATE_RUNNABLE for a VirtualThread blocked on an intrinsic monitor when called for more than one thread. When called for a single VirtualThread it correctly returns a state that includes the JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER flag.
It looks like the carrier thread state is not checked in this code path. Maybe VM_GetThreadListStackTraces::doit should call get_threadOop_and_JavaThread instead of cv_external_thread_to_JavaThread.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
With the attached sources and JAVA_HOME set to a JDK 20:
gcc -shared -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC VirtualThreadStateTest.c -o libVirtualThreadStateTest.so
$JAVA_HOME/bin/javac --enable-preview --release=20 VirtualThreadStateTest.java
LD_LIBRARY_PATH=. $JAVA_HOME/bin/java --enable-preview -agentlib:VirtualThreadStateTest VirtualThreadStateTest
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Test process should return 0.
ACTUAL -
Test process prints "Multiple state wrong" and returns 1.
---------- BEGIN SOURCE ----------
-- VirtualThreadStateTest.java -------------------------------------------------------------
import java.util.concurrent.locks.ReentrantLock;
public class VirtualThreadStateTest {
private static final int JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400;
private static final int JVMTI_THREAD_STATE_WAITING = 0x0080;
private static native int getStateSingle(Thread thread);
private static native int getStateMultiple(Thread thread, Thread other);
public static void main(String[] args) throws InterruptedException {
System.loadLibrary("VirtualThreadStateTest");
checkReentrantLock();
checkSynchronized();
}
private static void checkReentrantLock() throws InterruptedException {
final ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
Thread virtualThread = Thread.ofVirtual().start(() -> {
System.out.println("trying to get lock");
reentrantLock.lock();
System.out.println("got lock");
});
Thread.sleep(1000);
checkStates(virtualThread, Thread.State.WAITING);
}
private static void checkSynchronized() throws InterruptedException {
final Object monitor = new Object();
synchronized (monitor) {
Thread virtualThread = Thread.ofVirtual().start(() -> {
System.out.println("trying to get monitor");
synchronized (monitor) {
System.out.println("got monitor");
}
});
Thread.sleep(1000);
checkStates(virtualThread, Thread.State.BLOCKED);
}
}
private static void checkStates(Thread virtualThread, Thread.State expected) {
int stateSingle = getStateSingle(virtualThread);
int stateMultiple = getStateMultiple(virtualThread, Thread.currentThread());
System.out.println("State: " + virtualThread.getState() + ", Single: " +
Integer.toHexString(stateSingle) + ", Multiple: " + Integer.toHexString(stateMultiple));
if (virtualThread.getState() != expected) {
System.out.println("Java state wrong");
System.exit(1);
}
int jvmtiExpected = expected == Thread.State.WAITING ? JVMTI_THREAD_STATE_WAITING : JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
if ((stateSingle & jvmtiExpected) == 0) {
System.out.println("Single state wrong");
System.exit(1);
}
if ((stateMultiple & jvmtiExpected) == 0) {
System.out.println("Multiple state wrong");
System.exit(1);
}
}
}
--------------------------------------------------------------------------------------------
-- VirtualThreadStateTest.c -------------------------------------------------------------
#include <jvmti.h>
#include <stdlib.h>
static jvmtiEnv *jvmti = NULL;
static void checkJvmti(int code, const char* message) {
if (code != JVMTI_ERROR_NONE) {
printf("Error %s: %d\n", message, code);
abort();
}
}
JNIEXPORT jint JNICALL Java_VirtualThreadStateTest_getStateSingle(JNIEnv* jni_env, jclass clazz, jthread virtualThread) {
jvmtiStackInfo* stackInfo = NULL;
checkJvmti((*jvmti)->GetThreadListStackTraces(jvmti, 1, &virtualThread, 1000, &stackInfo), "getStateSingle");
return stackInfo[0].state;
}
JNIEXPORT jint JNICALL Java_VirtualThreadStateTest_getStateMultiple
(JNIEnv* jni_env, jclass clazz, jthread virtualThread, jthread otherThread)
{
jthread threads[2];
threads[0] = virtualThread;
threads[1] = otherThread;
jvmtiStackInfo* stackInfo = NULL;
checkJvmti((*jvmti)->GetThreadListStackTraces(jvmti, 2, threads, 1000, &stackInfo), "getStateMultiple");
return stackInfo[0].state;
}
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
(*vm)->GetEnv(vm, (void**)&jvmti, JVMTI_VERSION);
if (jvmti == NULL) {
printf("could not initialize JVMTI\n");
abort();
}
return JVMTI_ERROR_NONE;
}
--------------------------------------------------------------------------------------------
---------- END SOURCE ----------
FREQUENCY : always
Ubunu 22.10 x86_64
openjdk version "20-ea" 2023-03-21
OpenJDK Runtime Environment (build 20-ea+20-1466)
OpenJDK 64-Bit Server VM (build 20-ea+20-1466, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
GetThreadListStackTraces returns JVMTI_THREAD_STATE_RUNNABLE for a VirtualThread blocked on an intrinsic monitor when called for more than one thread. When called for a single VirtualThread it correctly returns a state that includes the JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER flag.
It looks like the carrier thread state is not checked in this code path. Maybe VM_GetThreadListStackTraces::doit should call get_threadOop_and_JavaThread instead of cv_external_thread_to_JavaThread.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
With the attached sources and JAVA_HOME set to a JDK 20:
gcc -shared -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC VirtualThreadStateTest.c -o libVirtualThreadStateTest.so
$JAVA_HOME/bin/javac --enable-preview --release=20 VirtualThreadStateTest.java
LD_LIBRARY_PATH=. $JAVA_HOME/bin/java --enable-preview -agentlib:VirtualThreadStateTest VirtualThreadStateTest
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Test process should return 0.
ACTUAL -
Test process prints "Multiple state wrong" and returns 1.
---------- BEGIN SOURCE ----------
-- VirtualThreadStateTest.java -------------------------------------------------------------
import java.util.concurrent.locks.ReentrantLock;
public class VirtualThreadStateTest {
private static final int JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400;
private static final int JVMTI_THREAD_STATE_WAITING = 0x0080;
private static native int getStateSingle(Thread thread);
private static native int getStateMultiple(Thread thread, Thread other);
public static void main(String[] args) throws InterruptedException {
System.loadLibrary("VirtualThreadStateTest");
checkReentrantLock();
checkSynchronized();
}
private static void checkReentrantLock() throws InterruptedException {
final ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
Thread virtualThread = Thread.ofVirtual().start(() -> {
System.out.println("trying to get lock");
reentrantLock.lock();
System.out.println("got lock");
});
Thread.sleep(1000);
checkStates(virtualThread, Thread.State.WAITING);
}
private static void checkSynchronized() throws InterruptedException {
final Object monitor = new Object();
synchronized (monitor) {
Thread virtualThread = Thread.ofVirtual().start(() -> {
System.out.println("trying to get monitor");
synchronized (monitor) {
System.out.println("got monitor");
}
});
Thread.sleep(1000);
checkStates(virtualThread, Thread.State.BLOCKED);
}
}
private static void checkStates(Thread virtualThread, Thread.State expected) {
int stateSingle = getStateSingle(virtualThread);
int stateMultiple = getStateMultiple(virtualThread, Thread.currentThread());
System.out.println("State: " + virtualThread.getState() + ", Single: " +
Integer.toHexString(stateSingle) + ", Multiple: " + Integer.toHexString(stateMultiple));
if (virtualThread.getState() != expected) {
System.out.println("Java state wrong");
System.exit(1);
}
int jvmtiExpected = expected == Thread.State.WAITING ? JVMTI_THREAD_STATE_WAITING : JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
if ((stateSingle & jvmtiExpected) == 0) {
System.out.println("Single state wrong");
System.exit(1);
}
if ((stateMultiple & jvmtiExpected) == 0) {
System.out.println("Multiple state wrong");
System.exit(1);
}
}
}
--------------------------------------------------------------------------------------------
-- VirtualThreadStateTest.c -------------------------------------------------------------
#include <jvmti.h>
#include <stdlib.h>
static jvmtiEnv *jvmti = NULL;
static void checkJvmti(int code, const char* message) {
if (code != JVMTI_ERROR_NONE) {
printf("Error %s: %d\n", message, code);
abort();
}
}
JNIEXPORT jint JNICALL Java_VirtualThreadStateTest_getStateSingle(JNIEnv* jni_env, jclass clazz, jthread virtualThread) {
jvmtiStackInfo* stackInfo = NULL;
checkJvmti((*jvmti)->GetThreadListStackTraces(jvmti, 1, &virtualThread, 1000, &stackInfo), "getStateSingle");
return stackInfo[0].state;
}
JNIEXPORT jint JNICALL Java_VirtualThreadStateTest_getStateMultiple
(JNIEnv* jni_env, jclass clazz, jthread virtualThread, jthread otherThread)
{
jthread threads[2];
threads[0] = virtualThread;
threads[1] = otherThread;
jvmtiStackInfo* stackInfo = NULL;
checkJvmti((*jvmti)->GetThreadListStackTraces(jvmti, 2, threads, 1000, &stackInfo), "getStateMultiple");
return stackInfo[0].state;
}
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
(*vm)->GetEnv(vm, (void**)&jvmti, JVMTI_VERSION);
if (jvmti == NULL) {
printf("could not initialize JVMTI\n");
abort();
}
return JVMTI_ERROR_NONE;
}
--------------------------------------------------------------------------------------------
---------- END SOURCE ----------
FREQUENCY : always