-
Notifications
You must be signed in to change notification settings - Fork 737
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
Synchronization between continuation mounting and concurrent scanning #16290
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,6 +41,7 @@ | |
#include "j9modifiers_api.h" | ||
#include "j9cp.h" | ||
#include "ute.h" | ||
#include "AtomicSupport.hpp" | ||
#include "ObjectAllocationAPI.hpp" | ||
|
||
typedef enum { | ||
|
@@ -2048,18 +2049,19 @@ class VM_VMHelpers | |
#endif /* JAVA_SPEC_VERSION > 11 */ | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A general comment about these Continuation specific helpers in this generic VMHelpers.hpp file.... Firstly, they should probably be in ContinuationHelpers.cpp file (or *.hpp for inline purposes) . But I guess, there is C style deeply rooted into most of VM structs, that I don't want to deal with (especially not as a part of these changes). |
||
static VMINLINE UDATA | ||
walkContinuationStackFramesWrapper(J9VMThread *vmThread, j9object_t continuationObject, J9StackWalkState *walkState) | ||
{ | ||
UDATA rc = J9_STACKWALK_RC_NONE; | ||
#if JAVA_SPEC_VERSION >= 19 | ||
J9VMContinuation *continuation = J9VMJDKINTERNALVMCONTINUATION_VMREF(vmThread, continuationObject); | ||
rc = vmThread->javaVM->internalVMFunctions->walkContinuationStackFrames(vmThread, continuation, walkState); | ||
#endif /* JAVA_SPEC_VERSION >= 19 */ | ||
return rc; | ||
static VMINLINE J9VMThread * | ||
getCarrierThreadFromContinuationState(uintptr_t continuationState) | ||
{ | ||
return (J9VMThread *)(continuationState & (~(uintptr_t)J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN)); | ||
} | ||
|
||
static VMINLINE bool | ||
isConcurrentlyScannedFromContinuationState(uintptr_t continuationState) | ||
{ | ||
return J9_ARE_ANY_BITS_SET(continuationState, J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN); | ||
} | ||
|
||
#if JAVA_SPEC_VERSION >= 19 | ||
/** | ||
* Check if the related J9VMContinuation is mounted to carrier thread | ||
* @param[in] continuation the related J9VMContinuation | ||
|
@@ -2068,25 +2070,89 @@ class VM_VMHelpers | |
static VMINLINE bool | ||
isContinuationMounted(J9VMContinuation *continuation) | ||
{ | ||
return (NULL != continuation->carrierThread); | ||
return J9_ARE_ANY_BITS_SET(continuation->state, ~(uintptr_t)J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN); | ||
} | ||
|
||
static VMINLINE bool | ||
isContinuationMountedOrConcurrentlyScanned(J9VMContinuation *continuation) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider moving those 2 new helpers to more specific ContinuationHelper.hpp. As I said before, even the other existing ones that are continuation specific belong there, but for easier code review, it's probably better to leave them here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider same for any other NEW method in this file |
||
{ | ||
return isContinuationMounted(continuation) || isConcurrentlyScannedFromContinuationState(continuation->state); | ||
} | ||
|
||
/* | ||
* If low tagging failed due to either | ||
* | ||
* a carrier thread winning to mount, we don't need to do anything, since it will be compensated by pre/post mount actions | ||
* another GC thread winning to scan, again don't do anything, and let the winning thread do the work, instead | ||
*/ | ||
static VMINLINE bool | ||
tryWinningConcurrentGCScan(J9VMContinuation *continuation) | ||
{ | ||
return J9_GC_CONTINUATION_STATE_INITIAL == VM_AtomicSupport::lockCompareExchange(&continuation->state, J9_GC_CONTINUATION_STATE_INITIAL, J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN); | ||
} | ||
|
||
static VMINLINE void | ||
exitConcurrentGCScan(J9VMContinuation *continuation) | ||
{ | ||
/* clear CONCURRENTSCANNING flag */ | ||
uintptr_t oldContinuationState = VM_AtomicSupport::bitAnd(&continuation->state, ~(uintptr_t)J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN); | ||
J9VMThread *carrierThread = getCarrierThreadFromContinuationState(oldContinuationState); | ||
if (NULL != carrierThread) { | ||
omrthread_monitor_enter(carrierThread->publicFlagsMutex); | ||
/* notify the waiting carrierThread that we just finished scanning, and it can proceed with mounting. */ | ||
omrthread_monitor_notify_all(carrierThread->publicFlagsMutex); | ||
omrthread_monitor_exit(carrierThread->publicFlagsMutex); | ||
} | ||
} | ||
#endif /* JAVA_SPEC_VERSION >= 19 */ | ||
|
||
static VMINLINE UDATA | ||
walkContinuationStackFramesWrapper(J9VMThread *vmThread, j9object_t continuationObject, J9StackWalkState *walkState, bool syncWithContinuationMounting) | ||
{ | ||
UDATA rc = J9_STACKWALK_RC_NONE; | ||
#if JAVA_SPEC_VERSION >= 19 | ||
J9VMContinuation *continuation = J9VMJDKINTERNALVMCONTINUATION_VMREF(vmThread, continuationObject); | ||
if (syncWithContinuationMounting && (NULL != continuation)) { | ||
if (!tryWinningConcurrentGCScan(continuation)) { | ||
/* If continuation is mounted or already being scanned by another GC thread, we do nothing */ | ||
return rc; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GAC found it counterintuitive that failing to 'enter monitor' ends up returning 'success' (RC_NONE). You could even repeat a lighter version of the fat comment associated with this method, just prior to return statement: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this going to result in missing stack scans? A continuation always has a stack that needs walking, whether it's the stack for an unmounted continuation, or the stack of carrier thread for a mounted continuation. Every continuation and every thread stack needs processing somewhere. I don't think it's valid to just do nothing in the case where the mount state is changing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Valid concern, but it should not result in missing scans. If continuation is not scanned by this GC thread now, it will be done during pre-mount or post-unmount (depending of concurrent style, SATB or incremental-update). This is why we use atomic: we must conclude that either we do here or it's done somewhere else, based on whether it's mounted or not. In this change we have to make sure this decision is made right (while we already made sure 'somewhere else' really does occur, as just explained). Carrier thread are always scanned during root scanning, regardless if mounted something or not. |
||
} | ||
} | ||
rc = vmThread->javaVM->internalVMFunctions->walkContinuationStackFrames(vmThread, continuation, walkState); | ||
if (syncWithContinuationMounting && (NULL != continuation)) { | ||
exitConcurrentGCScan(continuation); | ||
} | ||
#endif /* JAVA_SPEC_VERSION >= 19 */ | ||
return rc; | ||
} | ||
|
||
/** | ||
* Check if we need to scan the java stack for the Continuation Object | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment: |
||
* Used during main scan phase of GC (object graph traversal) or heap object iteration (in sliding compact). | ||
* Not meant to be used during root scanning (neither strong roots nor weak roots)! | ||
* @param[in] vmThread the current J9VMThread | ||
* @param[in] continuationObject the continuation object | ||
* @param[in] scanOnlyUnmounted if it is true, only scan unmounted continuation object, default is false | ||
* @return true if we need to scan the java stack | ||
*/ | ||
static VMINLINE bool | ||
needScanStacksForContinuation(J9VMThread *vmThread, j9object_t continuationObject, bool scanOnlyUnmounted = false) | ||
needScanStacksForContinuation(J9VMThread *vmThread, j9object_t continuationObject) | ||
{ | ||
bool needScan = false; | ||
#if JAVA_SPEC_VERSION >= 19 | ||
jboolean started = J9VMJDKINTERNALVMCONTINUATION_STARTED(vmThread, continuationObject); | ||
J9VMContinuation *continuation = J9VMJDKINTERNALVMCONTINUATION_VMREF(vmThread, continuationObject); | ||
needScan = started && (NULL != continuation) && (!scanOnlyUnmounted || !isContinuationMounted(continuation)); | ||
/** | ||
* We don't scan mounted continuations: | ||
* | ||
* for concurrent GCs, since stack is actively changing. Instead, we scan them during preMount or during root scanning if already mounted at cycle start or during postUnmount (might be indirectly via card cleaning) or during final STW (via root re-scan) if still mounted at cycle end | ||
* for sliding compacts to avoid double slot fixups | ||
* | ||
* For fully STW GCs, there is no harm to scan them, but it's a waste of time since they are scanned during root scanning already. | ||
* | ||
* We don't scan currently scanned either - one scan is enough. | ||
*/ | ||
needScan = started && (NULL != continuation) && (!isContinuationMountedOrConcurrentlyScanned(continuation)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's add a comment We don't scan mounted continuations:
For fully STW GCs, there is no harm to scan them, but it's a waste of time since they are scanned during root scanning already. We don't scan currently scanned either - one scan is enough. |
||
#endif /* JAVA_SPEC_VERSION >= 19 */ | ||
return needScan; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -88,19 +88,44 @@ createContinuation(J9VMThread *currentThread, j9object_t continuationObject) | |
return result; | ||
} | ||
|
||
void | ||
synchronizeWithConcurrentGCScan(J9VMThread *currentThread, J9VMContinuation *continuation) | ||
{ | ||
volatile uintptr_t *localAddr = &continuation->state; | ||
/* atomically 'or' (not 'set') continuation->state with currentThread */ | ||
uintptr_t oldContinuationState = VM_AtomicSupport::bitOr(localAddr, (uintptr_t)currentThread); | ||
|
||
Assert_VM_Null(VM_VMHelpers::getCarrierThreadFromContinuationState(oldContinuationState)); | ||
|
||
if (VM_VMHelpers::isConcurrentlyScannedFromContinuationState(oldContinuationState)) { | ||
/* currentThread was low tagged (GC was already in progress), but by 'or'-ing our ID, we let GC know there is a pending mount */ | ||
internalReleaseVMAccess(currentThread); | ||
|
||
omrthread_monitor_enter(currentThread->publicFlagsMutex); | ||
while (VM_VMHelpers::isConcurrentlyScannedFromContinuationState(*localAddr)) { | ||
/* GC is still concurrently scanning the continuation(currentThread was still low tagged), wait for GC thread to notify us when it's done. */ | ||
omrthread_monitor_wait(currentThread->publicFlagsMutex); | ||
} | ||
omrthread_monitor_exit(currentThread->publicFlagsMutex); | ||
|
||
internalAcquireVMAccess(currentThread); | ||
} | ||
} | ||
|
||
BOOLEAN | ||
enterContinuation(J9VMThread *currentThread, j9object_t continuationObject) | ||
{ | ||
BOOLEAN result = TRUE; | ||
jboolean started = J9VMJDKINTERNALVMCONTINUATION_STARTED(currentThread, continuationObject); | ||
J9VMContinuation *continuation = J9VMJDKINTERNALVMCONTINUATION_VMREF(currentThread, continuationObject); | ||
|
||
Assert_VM_Null(currentThread->currentContinuation); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. restore this assert |
||
Assert_VM_notNull(continuation); | ||
|
||
/* let GC know we are mounting, so they don't need to scan us, or if there is already ongoing scan wait till it's complete. */ | ||
synchronizeWithConcurrentGCScan(currentThread, continuation); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can add a comment: |
||
|
||
VM_ContinuationHelpers::swapFieldsWithContinuation(currentThread, continuation, started); | ||
|
||
continuation->carrierThread = currentThread; | ||
currentThread->currentContinuation = continuation; | ||
|
||
/* Reset counters which determine if the current continuation is pinned. */ | ||
|
@@ -141,19 +166,29 @@ yieldContinuation(J9VMThread *currentThread) | |
{ | ||
BOOLEAN result = TRUE; | ||
J9VMContinuation *continuation = currentThread->currentContinuation; | ||
|
||
Assert_VM_notNull(currentThread->currentContinuation); | ||
|
||
VM_ContinuationHelpers::swapFieldsWithContinuation(currentThread, continuation); | ||
continuation->carrierThread = NULL; | ||
currentThread->currentContinuation = NULL; | ||
VM_ContinuationHelpers::swapFieldsWithContinuation(currentThread, continuation); | ||
|
||
/* We need a full fence here to preserve happens-before relationship on PPC and other weakly | ||
* ordered architectures since learning/reservation is turned on by default. Since we have the | ||
* global pin lock counters we only need to need to address yield points, as thats the | ||
* only time a different virtualThread can run on the underlying j9vmthread. | ||
*/ | ||
VM_AtomicSupport::readWriteBarrier(); | ||
/* we don't need atomic here, since no GC thread should be able to start scanning while continuation is mounted, | ||
* nor should another carrier thread be able to mount before we complete the unmount (hence no risk to overwrite anything in a race). | ||
* Order | ||
* | ||
* swap-stacks | ||
* writeBarrier | ||
* state initial | ||
* | ||
* must be maintained for weakly ordered CPUs, to unsure that once the continuation is again available for GC scan (on potentially remote CPUs), all CPUs see up-to-date stack . | ||
*/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @LinHu2016, you can expand this comment with:
|
||
Assert_VM_true((uintptr_t)currentThread == continuation->state); | ||
continuation->state = J9_GC_CONTINUATION_STATE_INITIAL; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. assert before this that carrier ID in the state is current thread |
||
|
||
return result; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can be either concurrent or STW and we should distinguish
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
look for _globalMarkIncrementType