Skip to content

Commit

Permalink
Merge pull request #15766 from EricYangIBM/getVMThread
Browse files Browse the repository at this point in the history
Block Virtual Threads for JVMTI Inspection
  • Loading branch information
tajila authored Aug 26, 2022
2 parents 861ea61 + 54c65d0 commit fbfa619
Show file tree
Hide file tree
Showing 17 changed files with 278 additions and 112 deletions.
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
106 changes: 82 additions & 24 deletions runtime/jcl/common/thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -527,17 +527,32 @@ 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);
omrthread_monitor_enter(vm->liveVirtualThreadListMutex);
j9object_t threadObj = J9_JNI_UNWRAP_REFERENCE(thread);

while (J9OBJECT_I64_LOAD(currentThread, threadObj, vm->virtualThreadInspectorCountOffset) > 0) {
/* Thread is being inspected, wait. */
VM_VMHelpers::pushObjectInSpecialFrame(currentThread, threadObj);
vmFuncs->internalExitVMToJNI(currentThread);
omrthread_monitor_wait(vm->liveVirtualThreadListMutex);
vmFuncs->internalEnterVMFromJNI(currentThread);
threadObj = VM_VMHelpers::popObjectInSpecialFrame(currentThread);
}

/* Prevent inspectors from inspecting this thread during stack swap and mount by locking from notifyJvmtiMountBegin
* to notifyJvmtiMountEnd. See getVMThread() in jvmtiHelpers.c.
*/
J9OBJECT_I64_STORE(currentThread, threadObj, vm->virtualThreadInspectorCountOffset, -1);

omrthread_monitor_enter(vm->liveVirtualThreadListMutex);
if (firstMount) {
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 +577,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,49 +585,93 @@ 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);
}

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

/* private native void notifyJvmtiMountEnd(boolean firstMount); */
void JNICALL
Java_java_lang_VirtualThread_notifyJvmtiMountEnd(JNIEnv *env, jobject thread, jboolean firstMount)
{
J9VMThread *currentThread = (J9VMThread *)env;
J9JavaVM *vm = currentThread->javaVM;
J9InternalVMFunctions *vmFuncs = vm->internalVMFunctions;

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

/* Allow thread to be inspected again. */
Assert_JCL_true(-1 == J9OBJECT_I64_LOAD(currentThread, threadObj, vm->virtualThreadInspectorCountOffset));
J9OBJECT_I64_STORE(currentThread, threadObj, vm->virtualThreadInspectorCountOffset, 0);
omrthread_monitor_notify_all(vm->liveVirtualThreadListMutex);

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

/* private native void notifyJvmtiUnmountBegin(boolean lastUnmount); */
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);
j9object_t threadObj = J9_JNI_UNWRAP_REFERENCE(thread);

while (J9OBJECT_I64_LOAD(currentThread, threadObj, vm->virtualThreadInspectorCountOffset) > 0) {
/* Thread is being inspected, wait. */
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);
/* Prevent inspectors from inspecting this thread during stack swap and unmount by locking from notifyJvmtiUnmountBegin
* to notifyJvmtiUnmountEnd. See getVMThread() in jvmtiHelpers.c.
*/
J9OBJECT_I64_STORE(currentThread, threadObj, vm->virtualThreadInspectorCountOffset, -1);

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)
{
J9VMThread *currentThread = (J9VMThread *)env;
J9JavaVM *vm = currentThread->javaVM;
J9InternalVMFunctions *vmFuncs = vm->internalVMFunctions;

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

/* Allow thread to be inspected again. */
Assert_JCL_true(-1 == J9OBJECT_I64_LOAD(currentThread, threadObj, vm->virtualThreadInspectorCountOffset));
J9OBJECT_I64_STORE(currentThread, threadObj, vm->virtualThreadInspectorCountOffset, 0);
omrthread_monitor_notify_all(vm->liveVirtualThreadListMutex);

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

/* 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
99 changes: 79 additions & 20 deletions runtime/jvmti/jvmtiHelpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,56 +95,116 @@ 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) {
omrthread_monitor_enter(vm->liveVirtualThreadListMutex);

while (J9OBJECT_I64_LOAD(currentThread, threadObject, vm->virtualThreadInspectorCountOffset) < 0) {
/* Thread is currently in the process of mounting/unmounting, wait. */
vm->internalVMFunctions->internalExitVMToJNI(currentThread);
omrthread_monitor_wait(vm->liveVirtualThreadListMutex);
vm->internalVMFunctions->internalEnterVMFromJNI(currentThread);
threadObject = J9_JNI_UNWRAP_REFERENCE(thread);
}

jint vthreadState = J9VMJAVALANGVIRTUALTHREAD_STATE(currentThread, threadObject);
j9object_t carrierThread = (j9object_t)J9VMJAVALANGVIRTUALTHREAD_CARRIERTHREAD(currentThread, threadObject);
if (NULL != carrierThread) {
targetThread = J9VMJAVALANGTHREAD_THREADREF(currentThread, carrierThread);
}
isThreadAlive = (JVMTI_VTHREAD_STATE_NEW != vthreadState) && (JVMTI_VTHREAD_STATE_TERMINATED != vthreadState);
} 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) {
I_64 vthreadInspectorCount = J9OBJECT_I64_LOAD(currentThread, threadObject, vm->virtualThreadInspectorCountOffset) + 1;
Assert_JVMTI_true(vthreadInspectorCount > 0);
J9OBJECT_I64_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 ((currentThread->threadObject != threadObject) && IS_VIRTUAL_THREAD(currentThread, threadObject)) {
J9JavaVM *vm = currentThread->javaVM;
I_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_I64_LOAD(currentThread, threadObject, vm->virtualThreadInspectorCountOffset);
Assert_JVMTI_true(vthreadInspectorCount > 0);
vthreadInspectorCount -= 1;
J9OBJECT_I64_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 +1528,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
Loading

0 comments on commit fbfa619

Please sign in to comment.