Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Virtual Threads for JVMTI Inspection #15690

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion runtime/jcl/common/jclcinit.c
Original file line number Diff line number Diff line change
Expand Up @@ -644,13 +644,20 @@ initializeRequiredClasses(J9VMThread *vmThread, char* dllName)
#endif /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */

#if JAVA_SPEC_VERSION >= 19
/* These fields point to the next and previous VirtualThreads in the liveVirtualThreadList. */
/* Points to the next VirtualThread in the liveVirtualThreadList. */
if (0 != vmFuncs->addHiddenInstanceField(vm, "java/lang/VirtualThread", "linkNext", "Ljava/lang/VirtualThread;", &vm->virtualThreadLinkNextOffset)) {
return 1;
}

/* Points to the previous VirtualThread in the liveVirtualThreadList. */
if (0 != vmFuncs->addHiddenInstanceField(vm, "java/lang/VirtualThread", "linkPrevious", "Ljava/lang/VirtualThread;", &vm->virtualThreadLinkPreviousOffset)) {
return 1;
}

/* Counter to track if the virtual thread is being inspected by JVMTI. */
if (0 != vmFuncs->addHiddenInstanceField(vm, "java/lang/VirtualThread", "inspectorCount", "J", &vm->virtualThreadInspectorCountOffset)) {
return 1;
}
#endif /* JAVA_SPEC_VERSION >= 19 */

vmThread->privateFlags |= J9_PRIVATE_FLAGS_REPORT_ERROR_LOADING_CLASS;
Expand Down
68 changes: 43 additions & 25 deletions runtime/jcl/common/thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -527,17 +527,18 @@ Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass clazz)
void JNICALL
Java_java_lang_VirtualThread_notifyJvmtiMountBegin(JNIEnv *env, jobject thread, jboolean firstMount)
{
if (firstMount) {
J9VMThread *currentThread = (J9VMThread *)env;
J9JavaVM *vm = currentThread->javaVM;
J9InternalVMFunctions *vmFuncs = vm->internalVMFunctions;
J9MemoryManagerFunctions *mmFuncs = vm->memoryManagerFunctions;
J9VMThread *currentThread = (J9VMThread *)env;
J9JavaVM *vm = currentThread->javaVM;
J9InternalVMFunctions *vmFuncs = vm->internalVMFunctions;

vmFuncs->internalEnterVMFromJNI(currentThread);
vmFuncs->internalEnterVMFromJNI(currentThread);
omrthread_monitor_enter(vm->liveVirtualThreadListMutex);
j9object_t threadObj = J9_JNI_UNWRAP_REFERENCE(thread);

omrthread_monitor_enter(vm->liveVirtualThreadListMutex);
if (firstMount) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is the count not checked in this case?

if (NULL == vm->liveVirtualThreadList) {
J9Class *virtualThreadClass = J9OBJECT_CLAZZ(currentThread, J9_JNI_UNWRAP_REFERENCE(thread));
J9MemoryManagerFunctions *mmFuncs = vm->memoryManagerFunctions;

/* Allocate empty virtual thread and create a global reference to it as root for the linked list.
* This prevents the root reference from becoming stale if the GC moves the object.
Expand All @@ -562,7 +563,6 @@ Java_java_lang_VirtualThread_notifyJvmtiMountBegin(JNIEnv *env, jobject thread,

if (NULL != vm->liveVirtualThreadList) {
j9object_t root = *(vm->liveVirtualThreadList);
j9object_t threadObj = J9_JNI_UNWRAP_REFERENCE(thread);
j9object_t rootPrev = J9OBJECT_OBJECT_LOAD(currentThread, root, vm->virtualThreadLinkPreviousOffset);

/* Add thread to the end of the list. */
Expand All @@ -571,10 +571,19 @@ Java_java_lang_VirtualThread_notifyJvmtiMountBegin(JNIEnv *env, jobject thread,
J9OBJECT_OBJECT_STORE(currentThread, rootPrev, vm->virtualThreadLinkNextOffset, threadObj);
J9OBJECT_OBJECT_STORE(currentThread, root, vm->virtualThreadLinkPreviousOffset, threadObj);
}
omrthread_monitor_exit(vm->liveVirtualThreadListMutex);

vmFuncs->internalExitVMToJNI(currentThread);
} else {
/* If this virtual thread is being inspected, do not allow a yielded thread to mount. */
while (0 != J9OBJECT_U64_LOAD(currentThread, threadObj, vm->virtualThreadInspectorCountOffset)) {
VM_VMHelpers::pushObjectInSpecialFrame(currentThread, threadObj);
vmFuncs->internalExitVMToJNI(currentThread);
omrthread_monitor_wait(vm->liveVirtualThreadListMutex);
babsingh marked this conversation as resolved.
Show resolved Hide resolved
vmFuncs->internalEnterVMFromJNI(currentThread);
threadObj = VM_VMHelpers::popObjectInSpecialFrame(currentThread);
}
}

omrthread_monitor_exit(vm->liveVirtualThreadListMutex);
vmFuncs->internalExitVMToJNI(currentThread);
}

/* private native void notifyJvmtiMountEnd(boolean firstMount); */
Expand All @@ -587,33 +596,42 @@ Java_java_lang_VirtualThread_notifyJvmtiMountEnd(JNIEnv *env, jobject thread, jb
void JNICALL
Java_java_lang_VirtualThread_notifyJvmtiUnmountBegin(JNIEnv *env, jobject thread, jboolean lastUnmount)
{
}
J9VMThread *currentThread = (J9VMThread *)env;
J9JavaVM *vm = currentThread->javaVM;
J9InternalVMFunctions *vmFuncs = vm->internalVMFunctions;

/* private native void notifyJvmtiUnmountEnd(boolean lastUnmount); */
void JNICALL
Java_java_lang_VirtualThread_notifyJvmtiUnmountEnd(JNIEnv *env, jobject thread, jboolean lastUnmount)
{
if (lastUnmount) {
J9VMThread *currentThread = (J9VMThread *)env;
J9JavaVM *vm = currentThread->javaVM;
J9InternalVMFunctions *vmFuncs = vm->internalVMFunctions;
vmFuncs->internalEnterVMFromJNI(currentThread);

omrthread_monitor_enter(vm->liveVirtualThreadListMutex);
/* If this virtual thread is being inspected, do not allow it to unmount. */
j9object_t threadObj = J9_JNI_UNWRAP_REFERENCE(thread);
while (0 != J9OBJECT_U64_LOAD(currentThread, threadObj, vm->virtualThreadInspectorCountOffset)) {
VM_VMHelpers::pushObjectInSpecialFrame(currentThread, threadObj);
vmFuncs->internalExitVMToJNI(currentThread);
omrthread_monitor_wait(vm->liveVirtualThreadListMutex);
vmFuncs->internalEnterVMFromJNI(currentThread);
threadObj = VM_VMHelpers::popObjectInSpecialFrame(currentThread);
}

omrthread_monitor_enter(vm->liveVirtualThreadListMutex);
if (lastUnmount) {
if (NULL != vm->liveVirtualThreadList) {
j9object_t threadObj = J9_JNI_UNWRAP_REFERENCE(thread);
j9object_t threadPrev = J9OBJECT_OBJECT_LOAD(currentThread, threadObj, vm->virtualThreadLinkPreviousOffset);
j9object_t threadNext = J9OBJECT_OBJECT_LOAD(currentThread, threadObj, vm->virtualThreadLinkNextOffset);

/* Remove thread from list. The root will never be removed. */
J9OBJECT_OBJECT_STORE(currentThread, threadPrev, vm->virtualThreadLinkNextOffset, threadNext);
J9OBJECT_OBJECT_STORE(currentThread, threadNext, vm->virtualThreadLinkPreviousOffset, threadPrev);
}
omrthread_monitor_exit(vm->liveVirtualThreadListMutex);

vmFuncs->internalExitVMToJNI(currentThread);
}

omrthread_monitor_exit(vm->liveVirtualThreadListMutex);
vmFuncs->internalExitVMToJNI(currentThread);
}

/* private native void notifyJvmtiUnmountEnd(boolean lastUnmount); */
void JNICALL
Java_java_lang_VirtualThread_notifyJvmtiUnmountEnd(JNIEnv *env, jobject thread, jboolean lastUnmount)
{
}

/* private static native void registerNatives(); */
Expand Down
12 changes: 6 additions & 6 deletions runtime/jvmti/jvmtiExtensionMechanism.c
Original file line number Diff line number Diff line change
Expand Up @@ -1458,7 +1458,7 @@ jvmtiGetOSThreadID(jvmtiEnv* jvmti_env, ...)

rc = getCurrentVMThread(vm, &currentThread);
if (rc == JVMTI_ERROR_NONE) {
J9VMThread * targetThread;
J9VMThread *targetThread = NULL;

vm->internalVMFunctions->internalEnterVMFromJNI(currentThread);

Expand All @@ -1468,7 +1468,7 @@ jvmtiGetOSThreadID(jvmtiEnv* jvmti_env, ...)
rc = getVMThread(currentThread, thread, &targetThread, TRUE, TRUE);
if (rc == JVMTI_ERROR_NONE) {
rv_threadid = (jlong) omrthread_get_osId(targetThread->osThread);
releaseVMThread(currentThread, targetThread);
releaseVMThread(currentThread, targetThread, thread);
}
done:
vm->internalVMFunctions->internalExitVMToJNI(currentThread);
Expand Down Expand Up @@ -1508,7 +1508,7 @@ jvmtiGetStackTraceExtended(jvmtiEnv* env, ...)

rc = getCurrentVMThread(vm, &currentThread);
if (rc == JVMTI_ERROR_NONE) {
J9VMThread * targetThread;
J9VMThread *targetThread = NULL;

vm->internalVMFunctions->internalEnterVMFromJNI(currentThread);

Expand All @@ -1525,7 +1525,7 @@ jvmtiGetStackTraceExtended(jvmtiEnv* env, ...)
rc = jvmtiInternalGetStackTraceExtended(env, type, currentThread, targetThread, start_depth, (UDATA) max_frame_count, frame_buffer, &rv_count);

vm->internalVMFunctions->resumeThreadForInspection(currentThread, targetThread);
releaseVMThread(currentThread, targetThread);
releaseVMThread(currentThread, targetThread, thread);
}
done:
vm->internalVMFunctions->internalExitVMToJNI(currentThread);
Expand Down Expand Up @@ -3444,7 +3444,7 @@ jvmtiGetJ9vmThread(jvmtiEnv *env, ...)

rc = getCurrentVMThread(vm, &currentThread);
if (rc == JVMTI_ERROR_NONE) {
J9VMThread * targetThread;
J9VMThread *targetThread = NULL;

vm->internalVMFunctions->internalEnterVMFromJNI(currentThread);

Expand All @@ -3455,7 +3455,7 @@ jvmtiGetJ9vmThread(jvmtiEnv *env, ...)
rc = getVMThread(currentThread, thread, &targetThread, TRUE, TRUE);
if (rc == JVMTI_ERROR_NONE) {
rv_vmThread = targetThread;
releaseVMThread(currentThread, targetThread);
releaseVMThread(currentThread, targetThread, thread);
}
done:
vm->internalVMFunctions->internalExitVMToJNI(currentThread);
Expand Down
4 changes: 2 additions & 2 deletions runtime/jvmti/jvmtiForceEarlyReturn.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ jvmtiForceEarlyReturn(jvmtiEnv* env,

rc = getCurrentVMThread(vm, &currentThread);
if (rc == JVMTI_ERROR_NONE) {
J9VMThread * targetThread;
J9VMThread *targetThread = NULL;

vm->internalVMFunctions->internalEnterVMFromJNI(currentThread);

Expand Down Expand Up @@ -238,7 +238,7 @@ jvmtiForceEarlyReturn(jvmtiEnv* env,
}
resume:
vm->internalVMFunctions->resumeThreadForInspection(currentThread, targetThread);
releaseVMThread(currentThread, targetThread);
releaseVMThread(currentThread, targetThread, thread);
}
done:
vm->internalVMFunctions->internalExitVMToJNI(currentThread);
Expand Down
91 changes: 71 additions & 20 deletions runtime/jvmti/jvmtiHelpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,56 +95,108 @@ static UDATA watchedClassEqual (void *lhsEntry, void *rhsEntry, void *userData);


jvmtiError
getVMThread(J9VMThread * currentThread, jthread thread, J9VMThread ** vmThreadPtr, UDATA allowNull, UDATA mustBeAlive)
getVMThread(J9VMThread *currentThread, jthread thread, J9VMThread **vmThreadPtr, UDATA allowNull, UDATA mustBeAlive)
{
J9JavaVM * vm = currentThread->javaVM;
j9object_t threadObject;
J9VMThread * targetThread = NULL;
J9JavaVM *vm = currentThread->javaVM;
j9object_t threadObject = NULL;
J9VMThread *targetThread = NULL;
BOOLEAN isThreadAlive = FALSE;
#if JAVA_SPEC_VERSION >= 19
BOOLEAN isVirtualThread = FALSE;
#endif /* JAVA_SPEC_VERSION >= 19 */

if (thread == NULL) {
if (NULL == thread) {
if (allowNull) {
*vmThreadPtr = currentThread;
return JVMTI_ERROR_NONE;
}
return JVMTI_ERROR_INVALID_THREAD;
} else {
threadObject = *((j9object_t*) thread);
threadObject = J9_JNI_UNWRAP_REFERENCE(thread);
if (currentThread->threadObject == threadObject) {
*vmThreadPtr = currentThread;
return JVMTI_ERROR_NONE;
}
}

/* Make sure the vmThread stays alive while it is being used */

/* Make sure the vmThread stays alive while it is being used. */
omrthread_monitor_enter(vm->vmThreadListMutex);
if (!J9VMJAVALANGTHREAD_STARTED(currentThread, threadObject) || ((targetThread = J9VMJAVALANGTHREAD_THREADREF(currentThread, threadObject)) == NULL)) {
#if JAVA_SPEC_VERSION >= 19
isVirtualThread = IS_VIRTUAL_THREAD(currentThread, threadObject);
if (isVirtualThread) {
/* Make sure the virtual thread stays alive while it is being used. */
omrthread_monitor_enter(vm->liveVirtualThreadListMutex);
jint vthreadState = J9VMJAVALANGVIRTUALTHREAD_STATE(currentThread, threadObject);
j9object_t carrierThread = (j9object_t)J9VMJAVALANGVIRTUALTHREAD_CARRIERTHREAD(currentThread, threadObject);
if (NULL != carrierThread) {
targetThread = J9VMJAVALANGTHREAD_THREADREF(currentThread, carrierThread);
}
isThreadAlive = (vthreadState != JVMTI_VTHREAD_STATE_NEW) && (vthreadState != JVMTI_VTHREAD_STATE_TERMINATED);
} else
#endif /* JAVA_SPEC_VERSION >= 19 */
{
targetThread = J9VMJAVALANGTHREAD_THREADREF(currentThread, threadObject);
isThreadAlive = J9VMJAVALANGTHREAD_STARTED(currentThread, threadObject) && (NULL != targetThread);
}

if (!isThreadAlive) {
if (mustBeAlive) {
#if JAVA_SPEC_VERSION >= 19
if (isVirtualThread) {
omrthread_monitor_exit(vm->liveVirtualThreadListMutex);
}
#endif /* JAVA_SPEC_VERSION >= 19 */
omrthread_monitor_exit(vm->vmThreadListMutex);
return JVMTI_ERROR_THREAD_NOT_ALIVE;
}
}

*vmThreadPtr = targetThread;
if (targetThread != NULL) {
++(targetThread->inspectorCount);
if (NULL != targetThread) {
targetThread->inspectorCount += 1;
}
#if JAVA_SPEC_VERSION >= 19
if (isVirtualThread) {
U_64 vthreadInspectorCount = J9OBJECT_U64_LOAD(currentThread, threadObject, vm->virtualThreadInspectorCountOffset) + 1;
Assert_JVMTI_true(vthreadInspectorCount > 0);
J9OBJECT_U64_STORE(currentThread, threadObject, vm->virtualThreadInspectorCountOffset, vthreadInspectorCount);
omrthread_monitor_exit(vm->liveVirtualThreadListMutex);
}
#endif /* JAVA_SPEC_VERSION >= 19 */
omrthread_monitor_exit(vm->vmThreadListMutex);
return JVMTI_ERROR_NONE;
}



void
releaseVMThread(J9VMThread * currentThread, J9VMThread * targetThread)
releaseVMThread(J9VMThread *currentThread, J9VMThread *targetThread, jthread thread)
{
if ((currentThread != targetThread) && (targetThread != NULL)) {
J9JavaVM * vm = targetThread->javaVM;

/* Release this thread (allow it to die) now that we are no longer inspecting it */
#if JAVA_SPEC_VERSION >= 19
if (NULL != thread) {
j9object_t threadObject = J9_JNI_UNWRAP_REFERENCE(thread);
if ((NULL != threadObject) && IS_VIRTUAL_THREAD(currentThread, threadObject)) {
J9JavaVM *vm = targetThread->javaVM;
U_64 vthreadInspectorCount = 0;
/* Release the virtual thread (allow it to die) now that we are no longer inspecting it. */
omrthread_monitor_enter(vm->liveVirtualThreadListMutex);
vthreadInspectorCount = J9OBJECT_U64_LOAD(currentThread, threadObject, vm->virtualThreadInspectorCountOffset);
Assert_JVMTI_true(vthreadInspectorCount > 0);
vthreadInspectorCount -= 1;
J9OBJECT_U64_STORE(currentThread, threadObject, vm->virtualThreadInspectorCountOffset, vthreadInspectorCount);
if (0 == vthreadInspectorCount) {
omrthread_monitor_notify_all(vm->liveVirtualThreadListMutex);
}
omrthread_monitor_exit(vm->liveVirtualThreadListMutex);
}
}
#endif /* JAVA_SPEC_VERSION >= 19 */

if ((NULL != targetThread) && (currentThread != targetThread)) {
J9JavaVM *vm = targetThread->javaVM;
/* Release the J9VMThread (allow it to die) now that we are no longer inspecting it. */
omrthread_monitor_enter(vm->vmThreadListMutex);
if (--(targetThread->inspectorCount) == 0) {
if (0 == --(targetThread->inspectorCount)) {
omrthread_monitor_notify_all(vm->vmThreadListMutex);
}
omrthread_monitor_exit(vm->vmThreadListMutex);
Expand Down Expand Up @@ -1468,10 +1520,9 @@ setEventNotificationMode(J9JVMTIEnv * j9env, J9VMThread * currentThread, jint mo
}
}

if (targetThread != NULL) {
releaseVMThread(currentThread, targetThread);
if (NULL != event_thread) {
releaseVMThread(currentThread, targetThread, event_thread);
}

done:
return rc;
}
Expand Down
8 changes: 7 additions & 1 deletion runtime/jvmti/jvmtiHelpers.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 1991, 2014 IBM Corp. and others
* Copyright (c) 1991, 2022 IBM Corp. and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which accompanies this
Expand Down Expand Up @@ -39,5 +39,11 @@
#include "omrlinkedlist.h"
#include "jvmti_internal.h"

#if JAVA_SPEC_VERSION >= 19
/* See VirtualThreadTests.test_verifyJVMTIMacros for validation of below macro values. */
#define JVMTI_VTHREAD_STATE_NEW 0
#define JVMTI_VTHREAD_STATE_TERMINATED 99
#endif /* JAVA_SPEC_VERSION >= 19 */

#endif /* jvmtiHelpers_h */

6 changes: 3 additions & 3 deletions runtime/jvmti/jvmtiLocalVariable.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 1991, 2018 IBM Corp. and others
* Copyright (c) 1991, 2022 IBM Corp. and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which accompanies this
Expand Down Expand Up @@ -309,7 +309,7 @@ jvmtiGetOrSetLocal(jvmtiEnv* env,

rc = getCurrentVMThread(vm, &currentThread);
if (rc == JVMTI_ERROR_NONE) {
J9VMThread * targetThread;
J9VMThread *targetThread = NULL;

vm->internalVMFunctions->internalEnterVMFromJNI(currentThread);
rc = getVMThread(currentThread, thread, &targetThread, TRUE, TRUE);
Expand Down Expand Up @@ -425,7 +425,7 @@ jvmtiGetOrSetLocal(jvmtiEnv* env,

*((jobject *) value_ptr) = vm->internalVMFunctions->j9jni_createLocalRef((JNIEnv *) currentThread, obj);
}
releaseVMThread(currentThread, targetThread);
releaseVMThread(currentThread, targetThread, thread);
}
vm->internalVMFunctions->internalExitVMToJNI(currentThread);
}
Expand Down
Loading