Skip to content

Commit

Permalink
Initialize J9JavaVM->liveVirtualThreadList at startup
Browse files Browse the repository at this point in the history
This change fixes the below deadlock.

Thread 1 wants exclusive VM access but it is unable to acquire
exclusive VM access because Thread 2 also holds VM access.
Thread 2 is waiting to acquire liveVirtualThreadListMutex and
unable to proceed because Thread 1 holds liveVirtualThreadListMutex.
This prevents both threads to proceed and leads to a deadlock.

Thread 1: JVM_VirtualThreadMountEnd
           -> Acquires VM Access and liveVirtualThreadListMutex
           -> J9AllocateObject
           -> Multiple GC frames
           -> acquireExclusiveVMAccess

Thread 2: JVM_VirtualThreadMountEnd
           -> Acquires VM Access
           -> Blocked to enter liveVirtualThreadListMutex

Related: #16340

Signed-off-by: Babneet Singh <sbabneet@ca.ibm.com>
  • Loading branch information
babsingh committed Nov 30, 2022
1 parent 3f2deca commit 7121794
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 60 deletions.
97 changes: 37 additions & 60 deletions runtime/j9vm/javanextvmi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,49 +325,25 @@ JVM_VirtualThreadMountEnd(JNIEnv *env, jobject thread, jboolean firstMount)
}

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.
*/
j9object_t rootVirtualThread = mmFuncs->J9AllocateObject(currentThread, virtualThreadClass, J9_GC_ALLOCATE_OBJECT_NON_INSTRUMENTABLE);
if (NULL != rootVirtualThread) {
/* The global ref will be freed at vm death. */
jobject globalRef = vmFuncs->j9jni_createGlobalRef((JNIEnv *)currentThread, rootVirtualThread, JNI_FALSE);
if (NULL != globalRef) {
vm->liveVirtualThreadList = (j9object_t *)globalRef;

/* Set linkNext/linkPrevious to itself. */
J9OBJECT_OBJECT_STORE(currentThread, rootVirtualThread, vm->virtualThreadLinkNextOffset, rootVirtualThread);
J9OBJECT_OBJECT_STORE(currentThread, rootVirtualThread, vm->virtualThreadLinkPreviousOffset, rootVirtualThread);
} else {
vmFuncs->setNativeOutOfMemoryError(currentThread, 0, 0);
}
} else {
vmFuncs->setHeapOutOfMemoryError(currentThread);
}
}

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

/* Non-null previous and next elements in the list suggest that the thread has
* already been added to the list. Only add to the list if the previous and
* next elements in the list are null.
*/
Assert_SC_true((NULL == threadPrev) && (NULL == threadNext));
j9object_t root = *(vm->liveVirtualThreadList);
j9object_t rootPrev = J9OBJECT_OBJECT_LOAD(currentThread, root, vm->virtualThreadLinkPreviousOffset);

/* Add thread to the end of the list. */
J9OBJECT_OBJECT_STORE(currentThread, threadObj, vm->virtualThreadLinkNextOffset, root);
J9OBJECT_OBJECT_STORE(currentThread, threadObj, vm->virtualThreadLinkPreviousOffset, rootPrev);
J9OBJECT_OBJECT_STORE(currentThread, rootPrev, vm->virtualThreadLinkNextOffset, threadObj);
J9OBJECT_OBJECT_STORE(currentThread, root, vm->virtualThreadLinkPreviousOffset, threadObj);
}
/* vm->liveVirtualThreadList should be initialized in stdinit.c::standardInit. */
Assert_SC_true(NULL != vm->liveVirtualThreadList);

j9object_t threadPrev = J9OBJECT_OBJECT_LOAD(currentThread, threadObj, vm->virtualThreadLinkPreviousOffset);
j9object_t threadNext = J9OBJECT_OBJECT_LOAD(currentThread, threadObj, vm->virtualThreadLinkNextOffset);

/* Non-null previous and next elements in the list suggest that the thread has
* already been added to the list. Only add to the list if the previous and
* next elements in the list are null.
*/
Assert_SC_true((NULL == threadPrev) && (NULL == threadNext));
j9object_t root = *(vm->liveVirtualThreadList);
j9object_t rootPrev = J9OBJECT_OBJECT_LOAD(currentThread, root, vm->virtualThreadLinkPreviousOffset);

/* Add thread to the end of the list. */
J9OBJECT_OBJECT_STORE(currentThread, threadObj, vm->virtualThreadLinkNextOffset, root);
J9OBJECT_OBJECT_STORE(currentThread, threadObj, vm->virtualThreadLinkPreviousOffset, rootPrev);
J9OBJECT_OBJECT_STORE(currentThread, rootPrev, vm->virtualThreadLinkNextOffset, threadObj);
J9OBJECT_OBJECT_STORE(currentThread, root, vm->virtualThreadLinkPreviousOffset, threadObj);

TRIGGER_J9HOOK_VM_VIRTUAL_THREAD_STARTED(vm->hookInterface, currentThread);
}
Expand Down Expand Up @@ -439,23 +415,24 @@ JVM_VirtualThreadUnmountBegin(JNIEnv *env, jobject thread, jboolean lastUnmount)
J9OBJECT_I64_STORE(currentThread, threadObj, vm->virtualThreadInspectorCountOffset, -1);

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

/* Non-null previous and next elements in the list suggest that the thread has
* been added to the list. Only remove from the list if the previous and next
* elements in the list are non-null.
*/
Assert_SC_true((NULL != threadPrev) && (NULL != threadNext));
/* Remove thread from the list. The root will never be removed. */
J9OBJECT_OBJECT_STORE(currentThread, threadPrev, vm->virtualThreadLinkNextOffset, threadNext);
J9OBJECT_OBJECT_STORE(currentThread, threadNext, vm->virtualThreadLinkPreviousOffset, threadPrev);

/* Reset previous and next fields in the thread object to null. */
J9OBJECT_OBJECT_STORE(currentThread, threadObj, vm->virtualThreadLinkNextOffset, NULL);
J9OBJECT_OBJECT_STORE(currentThread, threadObj, vm->virtualThreadLinkPreviousOffset, NULL);
}
/* vm->liveVirtualThreadList should be initialized in stdinit.c::standardInit. */
Assert_SC_true(NULL != vm->liveVirtualThreadList);

j9object_t threadPrev = J9OBJECT_OBJECT_LOAD(currentThread, threadObj, vm->virtualThreadLinkPreviousOffset);
j9object_t threadNext = J9OBJECT_OBJECT_LOAD(currentThread, threadObj, vm->virtualThreadLinkNextOffset);

/* Non-null previous and next elements in the list suggest that the thread has
* been added to the list. Only remove from the list if the previous and next
* elements in the list are non-null.
*/
Assert_SC_true((NULL != threadPrev) && (NULL != threadNext));
/* Remove thread from the list. The root will never be removed. */
J9OBJECT_OBJECT_STORE(currentThread, threadPrev, vm->virtualThreadLinkNextOffset, threadNext);
J9OBJECT_OBJECT_STORE(currentThread, threadNext, vm->virtualThreadLinkPreviousOffset, threadPrev);

/* Reset previous and next fields in the thread object to null. */
J9OBJECT_OBJECT_STORE(currentThread, threadObj, vm->virtualThreadLinkNextOffset, NULL);
J9OBJECT_OBJECT_STORE(currentThread, threadObj, vm->virtualThreadLinkPreviousOffset, NULL);

TRIGGER_J9HOOK_VM_VIRTUAL_THREAD_END(vm->hookInterface, currentThread);
}
Expand Down
33 changes: 33 additions & 0 deletions runtime/jcl/common/stdinit.c
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,39 @@ standardInit(J9JavaVM *vm, char *dllName)
} else {
goto _fail;
}

/* vm->liveVirtualThreadList should not be set at this point. */
if (NULL != vm->liveVirtualThreadList) {
goto _fail;
}

/* vm->liveVirtualThreadList is maintained in javanextvmi.cpp::JVM_VirtualThread* functions. */
clazz = (*(JNIEnv*)vmThread)->FindClass((JNIEnv*)vmThread, "java/lang/VirtualThread");
if (NULL != clazz) {
J9Class *virtualThreadClass = J9VM_J9CLASS_FROM_JCLASS(vmThread, clazz);

/* 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.
*/
j9object_t rootVirtualThread = vm->memoryManagerFunctions->J9AllocateObject(vmThread, virtualThreadClass, J9_GC_ALLOCATE_OBJECT_NON_INSTRUMENTABLE);
if (NULL != rootVirtualThread) {
/* The global ref will be freed at VM death. */
jobject globalRef = vmFuncs->j9jni_createGlobalRef((JNIEnv*)vmThread, rootVirtualThread, JNI_FALSE);
if (NULL != globalRef) {
vm->liveVirtualThreadList = (j9object_t*)globalRef;

/* Set linkNext/linkPrevious to itself. */
J9OBJECT_OBJECT_STORE(vmThread, rootVirtualThread, vm->virtualThreadLinkNextOffset, rootVirtualThread);
J9OBJECT_OBJECT_STORE(vmThread, rootVirtualThread, vm->virtualThreadLinkPreviousOffset, rootVirtualThread);
}
}
(*(JNIEnv*)vmThread)->DeleteLocalRef((JNIEnv*)vmThread, clazz);
if (NULL == vm->liveVirtualThreadList) {
goto _fail;
}
} else {
goto _fail;
}
#endif /* JAVA_SPEC_VERSION >= 19 */
#endif /* !J9VM_IVE_RAW_BUILD */
}
Expand Down

0 comments on commit 7121794

Please sign in to comment.