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

Fix missing concurrent continuation scanning issue #16614

Merged
merged 1 commit into from Feb 4, 2023

Conversation

LinHu2016
Copy link
Contributor

@LinHu2016 LinHu2016 commented Jan 26, 2023

The issue is caused by concurrent scavenger scanning and concurrent
marking scanning overlap for the same continuation Object, during
concurrent continuation scanning, the current synchronization control
would block the continuation mounting and ignore another concurrent
scanning, but the concurrent scavenger scanning and concurrent marking
are irrelevant, ignore another could cause missing scanning and the
related "live" object is recycled.

  • updated J9VMContinuation->state, use two low bits for recording
    concurrentScanning(bit0 for concurrentScanningLocal and bit1 for
    concurrentScanningGlobal) instead of only one low bit.

  • pass flag isConcurrentGC and flag isGlobalGC for
    GC_VMThreadStackSlotIterator::scanSlots().

  • handle J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_LOCAL and
    J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_GLOBAL independently

  • only both J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_LOCAL and
    J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_GLOBAL bits has been cleared
    we can notify blocked the continuation mounting thread.

fix:#16591

Signed-off-by: Lin Hu linhu@ca.ibm.com

@dmitripivkine dmitripivkine added comp:gc project:loom Used to track Project Loom related work jdk19 labels Jan 26, 2023
@dmitripivkine dmitripivkine added this to the Java 19 0.37 milestone Jan 26, 2023
@@ -492,8 +492,10 @@ extern "C" {
#define J9_GC_MARK_MAP_LOG_SIZEOF_UDATA 0x5
#define J9_GC_MARK_MAP_UDATA_MASK 0x1F
#endif /* J9VM_ENV_DATA64 */
#define J9_GC_CONTINUATION_STATE_INITIAL 0
#define J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN 0x1
#define J9_GC_CONTINUATION_STATE_CONCURRENT_NONE 0
Copy link
Contributor

Choose a reason for hiding this comment

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

_CONCURRENT_SCAN_NONE

uintptr_t state;
uintptr_t retState;
do {
state = continuation->state & (J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_ALL & (~checkConcurrentState));
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's introduce a small helper, that could help with easier code understanding. Could be used here twice, and in 'exit' method, too.

/**

  • extract concurrent state of local GC, if this GC is global, and vice-versa
  • extract concurrent state of global GC, if this GC is local
    */

UDATA complementGCConcurrentState(UDATA continuationState, UDATA thisGCConcurrentState) {

    return continuationState & (J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_ALL & ~thisGCConcurentState);

}

@@ -67,7 +67,8 @@ class GC_VMThreadStackSlotIterator
J9MODRON_OSLOTITERATOR *oSlotIterator,
bool includeStackFrameClassReferences,
bool trackVisibleFrameDepth,
bool syncWithContinuationMounting = false);
bool syncWithContinuationMounting = false,
Copy link
Contributor

Choose a reason for hiding this comment

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

small naming cleanup: let's rename this to isConcurrentGC (here and anywhere else this flag is used)

then both isConcurrentGC and isGlobalGC represent the facts as known by caller, without assuming how the callee will use them

@@ -802,10 +802,10 @@ MM_GlobalMarkingScheme::scanContinuationNativeSlots(MM_EnvironmentVLHGC *env, J9
stackFrameClassWalkNeeded = isDynamicClassUnloadingEnabled();
#endif /* J9VM_GC_DYNAMIC_CLASS_UNLOADING */


/* In STW GC there are no racing carrier threads doing mount and no need for the synchronization. */
bool syncWithContinuationMounting = (MM_VLHGCIncrementStats::mark_concurrent == static_cast<MM_CycleStateVLHGC*>(env->_cycleState)->_vlhgcIncrementStats._globalMarkIncrementType);
Copy link
Contributor

Choose a reason for hiding this comment

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

as mentioned earlier, let's rename this to isConcurrentGC

@@ -792,7 +792,7 @@ void
MM_GlobalMarkingScheme::scanContinuationNativeSlots(MM_EnvironmentVLHGC *env, J9Object *objectPtr, ScanReason reason)
{
J9VMThread *currentThread = (J9VMThread *)env->getLanguageVMThread();
if (MM_GCExtensions::needScanStacksForContinuationObject(currentThread, objectPtr)) {
if (MM_GCExtensions::needScanStacksForContinuationObject(currentThread, objectPtr, true)) {
Copy link
Contributor

@amicic amicic Feb 1, 2023

Choose a reason for hiding this comment

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

let's introduce a line
const bool isGlobalGC = true;
and instead of passing 'true' pass isGlobalGC at the 2 spots
that will help with undestanding what these flags we pass are about

update it for all other methods that use GC_VMThreadStackSlotIterator::scanSlots()

@amicic
Copy link
Contributor

amicic commented Feb 1, 2023

let's fix other caller's of GC_VMThreadStackSlotIterator::scanSlots() to not rely on default isGlobalGC = true, where really that's not the case (CopyForward for example).
WriteOnesCompactor might be a tricky since it's used for both, so you may need to query cycle state to figure out if it's local or global

uintptr_t concurrentState = J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_LOCAL;
if (isGlobalGC) {
concurrentState = J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_GLOBAL;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

there is an extra emty space of indentation in 3 lines of the 'if' block

static VMINLINE bool
isConcurrentlyScannedFromContinuationState(uintptr_t continuationState, bool isGlobalGC)
{
uintptr_t concurrentState = J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_LOCAL;
Copy link
Contributor

Choose a reason for hiding this comment

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

it's really a mask rather then state, so rename it to concurrentGCMask

#define J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_NONE 0
#define J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_LOCAL 0x1
#define J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_GLOBAL 0x2
#define J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_ALL (J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_LOCAL | J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_GLOBAL)
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer either _ANY or _MASK, rather than _ALL

uintptr_t state;
uintptr_t retState;
do {
state = continuation->state & getGCConcurrentState(!isGlobalGC);
Copy link
Contributor

Choose a reason for hiding this comment

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

let's give this state a more specific name (complementGCConcurrentState?) and provide a comment above the line:
/* preserve the concurrent GC state for the other type of GC */

uintptr_t retState;
do {
state = continuation->state & getGCConcurrentState(!isGlobalGC);
retState = VM_AtomicSupport::lockCompareExchange(&continuation->state, state, state | getGCConcurrentState(isGlobalGC));
Copy link
Contributor

Choose a reason for hiding this comment

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

rename to returnedState

do {
state = continuation->state & getGCConcurrentState(!isGlobalGC);
retState = VM_AtomicSupport::lockCompareExchange(&continuation->state, state, state | getGCConcurrentState(isGlobalGC));
} while (state != (retState & state));
Copy link
Contributor

@amicic amicic Feb 1, 2023

Choose a reason for hiding this comment

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

a comment above this line would be good:
if the other GC happened to change its concurrentGC state since us taking a snapshot of their state, we'll have to retry

{
/* clear CONCURRENTSCANNING flag bit0:LocalConcurrentScanning /bit1:GlobalConcurrentScanning */
uintptr_t oldContinuationState = VM_AtomicSupport::bitAnd(&continuation->state, ~getGCConcurrentState(isGlobalGC));
if (!(oldContinuationState & getGCConcurrentState(!isGlobalGC))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

store this into complementGCConcurrentState

@LinHu2016 LinHu2016 force-pushed the GC_Loom_update_7 branch 2 times, most recently from 2fbc065 to 1d3bbe0 Compare February 2, 2023 18:59
if (isGlobalGC) {
return J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_GLOBAL);
} else {
return J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_LOCAL);
Copy link
Contributor

Choose a reason for hiding this comment

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

spaces are used for indentation, while elsewhere tabs are used

@@ -60,14 +60,16 @@ MM_HeapWalkerDelegate::doContinuationNativeSlots(MM_EnvironmentBase *env, omrobj
{
J9VMThread *currentThread = (J9VMThread *)env->getLanguageVMThread();

if (MM_GCExtensions::needScanStacksForContinuationObject(currentThread, objectPtr)) {
const bool isGlobalGC = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is weird one, since Heap walker is not a GC, to start with.
Either false or true is probably good, but since this it's actually (so far) only instantiated by ParallelGlobalGC and used for whole heap walk, it'd actually mark it as global.
If in future it's used from different GCs, then we would have to resolve this in run-time - similar how we do it for WriteOnceCompactor.

Copy link
Contributor

Choose a reason for hiding this comment

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

seems like you forgot to flip this to true

Copy link
Contributor

Choose a reason for hiding this comment

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

this has not been fixed yet


GC_VMThreadStackSlotIterator::scanSlots(currentThread, objectPtr, (void *)&localData, stackSlotIteratorForRealtimeGC, stackFrameClassWalkNeeded, false, syncWithContinuationMounting);
bool isConcurrentGC = _realtimeGC->isCollectorConcurrentTracing();
GC_VMThreadStackSlotIterator::scanSlots(currentThread, objectPtr, (void *)&localData, stackSlotIteratorForRealtimeGC, stackFrameClassWalkNeeded, false, isConcurrentGC, isGlobalGC);
Copy link
Contributor

Choose a reason for hiding this comment

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

sometimes you have an empty line between the setting concurrent flag and this line, and sometimes not
let's be consistent, and have an empty line (here and check other spots, too)

return isContinuationMounted(continuation) || isConcurrentlyScannedFromContinuationState(continuation->state, isGlobalGC);
}

static VMINLINE uintptr_t getGCConcurrentState(bool isGlobalGC) {
Copy link
Contributor

@amicic amicic Feb 2, 2023

Choose a reason for hiding this comment

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

you could rename it to getConcurrentGCMask, but I'll leave it up to you, if you like it

either way, you could use it at one more spot (where I got a motivation):
sConcurrentlyScannedFromContinuationState(uintptr_t continuationState, bool isGlobalGC)

returnedState = VM_AtomicSupport::lockCompareExchange(&continuation->state, complementGCConcurrentState, complementGCConcurrentState | getGCConcurrentState(isGlobalGC));
/* if the other GC happened to change its concurrentGC state since us taking a snapshot of their state, we'll have to retry */
} while (complementGCConcurrentState != (returnedState & complementGCConcurrentState));

Copy link
Contributor

Choose a reason for hiding this comment

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

add a comment:
if returned state does not contain carrier ID, return that we won

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. */
Copy link
Contributor

Choose a reason for hiding this comment

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

expand this comment:

notify the waiting carrierThread that we just finished scanning and we were the only/last GC to scan it, so that it can proceed with mounting

{
return J9_GC_CONTINUATION_STATE_INITIAL == VM_AtomicSupport::lockCompareExchange(&continuation->state, J9_GC_CONTINUATION_STATE_INITIAL, J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN);
uintptr_t complementGCConcurrentState;
uintptr_t returnedState;
Copy link
Contributor

Choose a reason for hiding this comment

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

to comply with coding standards let's initialize these 2 with _NONE

if (syncWithContinuationMounting && (NULL != continuation)) {
if (!tryWinningConcurrentGCScan(continuation)) {
if (isConcurrentGC && (NULL != continuation)) {
if (!tryWinningConcurrentGCScan(continuation, isGlobalGC)) {
/* If continuation is mounted or already being scanned by another GC thread, we do nothing */
Copy link
Contributor

Choose a reason for hiding this comment

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

adjust this comment:

if continuation is mounted or already being scanned by another GC thread of the same GC type, we do nothing

StackIteratorData4Scavenge localData;
localData.scavengerDelegate = this;
localData.env = env;
localData.reason = reason;
localData.shouldRemember = &shouldRemember;
/* In STW GC there are no racing carrier threads doing mount and no need for the synchronization. */
bool syncWithContinuationMounting = _extensions->isConcurrentScavengerInProgress();
bool isConcurrentGC = _extensions->isConcurrentScavengerInProgress();
Copy link
Contributor

Choose a reason for hiding this comment

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

extra space (or indentation) between these 2: bool isConcurrentGC

@@ -2053,13 +2053,29 @@ class VM_VMHelpers
static VMINLINE J9VMThread *
getCarrierThreadFromContinuationState(uintptr_t continuationState)
Copy link
Contributor

Choose a reason for hiding this comment

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

move this method below next to isContinuationMounted - they are related/similar

return isContinuationMounted(continuation) || isConcurrentlyScannedFromContinuationState(continuation->state, isGlobalGC);
}

static VMINLINE uintptr_t getConcurrentGCMask(bool isGlobalGC) {
Copy link
Contributor

Choose a reason for hiding this comment

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

move this method up before isConcurrentlyScannedFromContinuationState that uses it

getCarrierThreadFromContinuationState(uintptr_t continuationState)
{
return (J9VMThread *)(continuationState & (~(uintptr_t)J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN));
static VMINLINE uintptr_t getConcurrentGCMask(bool isGlobalGC) {
Copy link
Contributor

Choose a reason for hiding this comment

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

move open curly bracket into the next line

@LinHu2016 LinHu2016 force-pushed the GC_Loom_update_7 branch 2 times, most recently from a52672e to e32bd04 Compare February 3, 2023 15:58
static VMINLINE bool
isConcurrentlyScannedFromContinuationState(uintptr_t continuationState)
{
return J9_ARE_ANY_BITS_SET(continuationState, J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN);
return J9_ARE_ANY_BITS_SET(continuationState, J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_ANY);

Copy link
Contributor

Choose a reason for hiding this comment

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

remove empty line

{
uintptr_t concurrentGCMask = getConcurrentGCMask(isGlobalGC);
return J9_ARE_ANY_BITS_SET(continuationState, concurrentGCMask);

Copy link
Contributor

Choose a reason for hiding this comment

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

remove empty line

@amicic
Copy link
Contributor

amicic commented Feb 3, 2023

@tajila pretty much all GC code, but formally a couple of files are VM, so if you want to have a quick look...

@tajila
Copy link
Contributor

tajila commented Feb 3, 2023

Changes look good, but I think the exitConcurrentGCScan and related APIs should be moved to continuationhelpers.hpp. Doesn't need to be done in this PR

@amicic
Copy link
Contributor

amicic commented Feb 3, 2023

agree, it's been suggested already that all/most of continuation specific code moves out of this generic VMHelpers, and has been attempted, but apparently there were some compile dependencies that were not trivial to untangle
should try again, eventually

StackIteratorData4HeapWalker localData;
localData.heapWalker = _heapWalker;
localData.env = env;
localData.fromObject = objectPtr;
localData.function = function;
localData.userData = userData;
/* so far there is no case we need ClassWalk for heapwalker, so we set stackFrameClassWalkNeeded = false */
GC_VMThreadStackSlotIterator::scanSlots(currentThread, objectPtr, (void *)&localData, stackSlotIteratorForHeapWalker, false, false);
const bool isConcurrentGC = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

this is not correct, it should be isGlobalGC that should be set to true

The issue is caused by concurrent scavenger scanning and concurrent
marking scanning overlap for the same continuation Object, during
concurrent continuation scanning, the current synchronization control
would block the continuation mounting and ignore another concurrent
scanning, but the concurrent scavenger scanning and concurrent marking
are irrelevant, ignore another could cause missing scanning and the
related "live" object is recycled.

- updated J9VMContinuation->state, use two low bits for recording
 concurrentScanning(bit0 for concurrentScanningLocal and bit1 for
 concurrentScanningGlobal) instead of only one low bit.

- pass flag isConcurrentGC and flag isGlobalGC for
GC_VMThreadStackSlotIterator::scanSlots().

- handle J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_LOCAL and
J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_GLOBAL independently

- only both J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_LOCAL and
J9_GC_CONTINUATION_STATE_CONCURRENT_SCAN_GLOBAL bits has been cleared
we can notify blocked the continuation mounting thread.

Signed-off-by: Lin Hu <linhu@ca.ibm.com>
@amicic
Copy link
Contributor

amicic commented Feb 3, 2023

Jenkins test sanity aix jdk19

@amicic
Copy link
Contributor

amicic commented Feb 3, 2023

Jenkins compile win jdk11

@amicic
Copy link
Contributor

amicic commented Feb 3, 2023

Jenkins test sanity plinux jdk19

@amicic
Copy link
Contributor

amicic commented Feb 3, 2023

Jenkins test sanity aix jdk19

@amicic amicic merged commit af44f3e into eclipse-openj9:master Feb 4, 2023
@amicic amicic self-requested a review February 6, 2023 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
comp:gc jdk19 project:loom Used to track Project Loom related work
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants