diff --git a/src/coreclr/gc/env/gcenv.ee.h b/src/coreclr/gc/env/gcenv.ee.h index 94b915bedb1b53..000d43d673c864 100644 --- a/src/coreclr/gc/env/gcenv.ee.h +++ b/src/coreclr/gc/env/gcenv.ee.h @@ -41,6 +41,8 @@ class GCToEEInterface static void TriggerClientBridgeProcessing(MarkCrossReferencesArgs* args); + static bool IsClientBridgeProcessingActive(); + // Sync block cache management static void SyncBlockCacheWeakPtrScan(HANDLESCANPROC scanProc, uintptr_t lp1, uintptr_t lp2); static void SyncBlockCacheDemote(int max_gen); diff --git a/src/coreclr/gc/env/gctoeeinterface.standalone.inl b/src/coreclr/gc/env/gctoeeinterface.standalone.inl index 0bde5e308a70da..8ef87b29fce95a 100644 --- a/src/coreclr/gc/env/gctoeeinterface.standalone.inl +++ b/src/coreclr/gc/env/gctoeeinterface.standalone.inl @@ -54,6 +54,11 @@ namespace standalone return ::GCToEEInterface::TriggerClientBridgeProcessing(args); } + bool IsClientBridgeProcessingActive() + { + return ::GCToEEInterface::IsClientBridgeProcessingActive(); + } + void SyncBlockCacheWeakPtrScan(HANDLESCANPROC scanProc, uintptr_t lp1, uintptr_t lp2) { ::GCToEEInterface::SyncBlockCacheWeakPtrScan(scanProc, lp1, lp2); diff --git a/src/coreclr/gc/gcbridge.cpp b/src/coreclr/gc/gcbridge.cpp index ea8801cba9eda5..548f4dabf29be7 100644 --- a/src/coreclr/gc/gcbridge.cpp +++ b/src/coreclr/gc/gcbridge.cpp @@ -1065,6 +1065,9 @@ static void ResetXRefs(ColorData* color) static uint64_t g_startTime; static uint64_t g_afterTarjanTime; +// Timestamp, in microseconds, of the last set of cross references handed to the client. +static uint64_t g_lastBridgeRequestTime; + static void BridgeFinish() { #if DUMP_GRAPH @@ -1111,6 +1114,28 @@ uint8_t** GetRegisteredBridges(size_t* pNumBridges) return (uint8_t**)g_registeredBridges.data; } +bool ShouldProcessBridgeObjects(uint32_t condemned) +{ + // The client discards any set of cross references handed to it while it is still + // processing a previous one, so computing it would be pure waste. + if (GCToEEInterface::IsClientBridgeProcessingActive()) + return false; + + // Requests are only throttled for gen0 collections. Bridge objects that are not handed to + // the client are still promoted, so a deferred object cannot be registered again by a later + // gen0 collection; keeping gen1 and gen2 unthrottled bounds how long a dead peer can go + // unreported to the next gen1 collection. + if (condemned > 0) + return true; + + int64_t minIntervalMs = GCConfig::GetGCBridgeMinIntervalMs(); + if (minIntervalMs <= 0) + return true; + + uint64_t now = GetHighPrecisionTimeStamp(); + return (now - g_lastBridgeRequestTime) >= ((uint64_t)minIntervalMs * 1000); +} + static bool TarjanSccAlgorithm() { int i; @@ -1293,6 +1318,8 @@ MarkCrossReferencesArgs* ProcessBridgeObjects() BridgeFinish(); + g_lastBridgeRequestTime = GetHighPrecisionTimeStamp(); + return args; } diff --git a/src/coreclr/gc/gcbridge.h b/src/coreclr/gc/gcbridge.h index f8d7fe0ddf93f5..c1305d77a6bdc1 100644 --- a/src/coreclr/gc/gcbridge.h +++ b/src/coreclr/gc/gcbridge.h @@ -12,6 +12,13 @@ void BridgeResetData(); MarkCrossReferencesArgs* ProcessBridgeObjects(); +// Decides whether this collection should hand a fresh set of cross references to the client. +// Returns false when the client is still processing a previous set (the new one would just be +// discarded) or when the request would arrive too soon after the previous one. Only gen0 +// collections are ever throttled, so a deferred object is guaranteed to be reconsidered by the +// next gen1 or gen2 collection. +bool ShouldProcessBridgeObjects(uint32_t condemned); + void RegisterBridgeObject(Object *object, uintptr_t context); uint8_t** GetRegisteredBridges(size_t *pNumBridges); diff --git a/src/coreclr/gc/gcconfig.h b/src/coreclr/gc/gcconfig.h index 1f9ad6a834d9a6..6aca7cadd1ee9a 100644 --- a/src/coreclr/gc/gcconfig.h +++ b/src/coreclr/gc/gcconfig.h @@ -108,6 +108,10 @@ class GCConfigStringHolder INT_CONFIG (GCRegionRange, "GCRegionRange", "System.GC.RegionRange", 0, "Specifies the range for the GC heap") \ INT_CONFIG (GCRegionSize, "GCRegionSize", "System.GC.RegionSize", 0, "Specifies the size for a basic GC region") \ INT_CONFIG (GCEnableSpecialRegions, "GCEnableSpecialRegions", NULL, 0, "Specifies to enable special handling some regions like SIP") \ + INT_CONFIG (GCBridgeMinIntervalMs, "GCBridgeMinIntervalMs", "System.GC.BridgeMinIntervalMs", 50, "Minimum number of milliseconds between two consecutive cross-reference (Java bridge) " \ + "processing requests handed to the client. Ephemeral collections that occur inside that " \ + "window skip the request; collections of gen1 and above are never skipped. 0 disables " \ + "throttling.") \ STRING_CONFIG(LogFile, "GCLogFile", NULL, "Specifies the name of the GC log file") \ STRING_CONFIG(ConfigLogFile, "GCConfigLogFile", NULL, "Specifies the name of the GC config log file") \ INT_CONFIG (BGCFLTuningEnabled, "BGCFLTuningEnabled", NULL, 0, "Enables FL tuning") \ diff --git a/src/coreclr/gc/gcenv.ee.standalone.inl b/src/coreclr/gc/gcenv.ee.standalone.inl index 9d3fcf16f9dc81..5264d1c7df0e54 100644 --- a/src/coreclr/gc/gcenv.ee.standalone.inl +++ b/src/coreclr/gc/gcenv.ee.standalone.inl @@ -81,6 +81,17 @@ inline void GCToEEInterface::TriggerClientBridgeProcessing(MarkCrossReferencesAr } } +inline bool GCToEEInterface::IsClientBridgeProcessingActive() +{ + assert(g_theGCToCLR != nullptr); + if (g_runtimeSupportedVersion.MajorVersion >= 5) + { + return g_theGCToCLR->IsClientBridgeProcessingActive(); + } + + return false; +} + inline void GCToEEInterface::SyncBlockCacheWeakPtrScan(HANDLESCANPROC scanProc, uintptr_t lp1, uintptr_t lp2) { assert(g_theGCToCLR != nullptr); diff --git a/src/coreclr/gc/gcinterface.ee.h b/src/coreclr/gc/gcinterface.ee.h index 9f0402888d4d84..e0947a91b85a17 100644 --- a/src/coreclr/gc/gcinterface.ee.h +++ b/src/coreclr/gc/gcinterface.ee.h @@ -468,6 +468,14 @@ class IGCToCLR { virtual void TriggerClientBridgeProcessing(MarkCrossReferencesArgs* args) PURE_VIRTUAL + + // The following method is available only with EE_INTERFACE_MAJOR_VERSION >= 5 + + // Returns true when the client is still processing cross references handed to it by a + // previous call to TriggerClientBridgeProcessing. While that is the case any new set of + // cross references would be discarded by the client, so the GC can skip computing it. + virtual + bool IsClientBridgeProcessingActive() PURE_VIRTUAL }; #endif // _GCINTERFACE_EE_H_ diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h index a5a0002671c972..7e2d74f4d81728 100644 --- a/src/coreclr/gc/gcinterface.h +++ b/src/coreclr/gc/gcinterface.h @@ -15,7 +15,7 @@ // The major version of the IGCToCLR interface. Breaking changes to this interface // require bumps in the major version number. -#define EE_INTERFACE_MAJOR_VERSION 4 +#define EE_INTERFACE_MAJOR_VERSION 5 struct ScanContext; struct gc_alloc_context; diff --git a/src/coreclr/gc/objecthandle.cpp b/src/coreclr/gc/objecthandle.cpp index 45054e04941d8f..8769ddb76c75e9 100644 --- a/src/coreclr/gc/objecthandle.cpp +++ b/src/coreclr/gc/objecthandle.cpp @@ -1535,13 +1535,18 @@ uint8_t** Ref_ScanBridgeObjects(uint32_t condemned, uint32_t maxgen, ScanContext } // The callee here will free the allocated memory. - MarkCrossReferencesArgs *args = ProcessBridgeObjects(); - - if (args != NULL) + if (ShouldProcessBridgeObjects(condemned)) { - GCToEEInterface::TriggerClientBridgeProcessing(args); + MarkCrossReferencesArgs *args = ProcessBridgeObjects(); + + if (args != NULL) + { + GCToEEInterface::TriggerClientBridgeProcessing(args); + } } + // Every registered bridge object is promoted whether or not it was handed to the client, + // so skipping the request above only delays reporting it, it never collects it early. return GetRegisteredBridges(numObjs); } #endif // FEATURE_JAVAMARSHAL diff --git a/src/coreclr/gc/sample/gcenv.ee.cpp b/src/coreclr/gc/sample/gcenv.ee.cpp index f0aabc78183404..aa4454b392d7fb 100644 --- a/src/coreclr/gc/sample/gcenv.ee.cpp +++ b/src/coreclr/gc/sample/gcenv.ee.cpp @@ -168,6 +168,11 @@ void GCToEEInterface::TriggerClientBridgeProcessing(MarkCrossReferencesArgs* arg { } +bool GCToEEInterface::IsClientBridgeProcessingActive() +{ + return false; +} + bool GCToEEInterface::IsPreemptiveGCDisabled() { Thread* pThread = ::GetThread(); diff --git a/src/coreclr/nativeaot/Runtime/gcenv.ee.cpp b/src/coreclr/nativeaot/Runtime/gcenv.ee.cpp index 7cbad8bf616384..f16761c5a6d5b9 100644 --- a/src/coreclr/nativeaot/Runtime/gcenv.ee.cpp +++ b/src/coreclr/nativeaot/Runtime/gcenv.ee.cpp @@ -826,4 +826,13 @@ void GCToEEInterface::TriggerClientBridgeProcessing(MarkCrossReferencesArgs* arg #endif } +bool GCToEEInterface::IsClientBridgeProcessingActive() +{ +#ifdef FEATURE_JAVAMARSHAL + return JavaMarshalNative::IsGCBridgeActive(); +#else + return false; +#endif +} + #endif // !DACCESS_COMPILE diff --git a/src/coreclr/nativeaot/Runtime/interoplibinterface.h b/src/coreclr/nativeaot/Runtime/interoplibinterface.h index c89281d152d8cc..b782d35fdc11cf 100644 --- a/src/coreclr/nativeaot/Runtime/interoplibinterface.h +++ b/src/coreclr/nativeaot/Runtime/interoplibinterface.h @@ -32,5 +32,7 @@ class JavaMarshalNative public: static void TriggerClientBridgeProcessing( MarkCrossReferencesArgs* args); + + static bool IsGCBridgeActive(); }; #endif // FEATURE_JAVAMARSHAL diff --git a/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp b/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp index 1fcda8be6089d8..54aee68af28527 100644 --- a/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp +++ b/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp @@ -45,6 +45,11 @@ namespace } } +bool JavaMarshalNative::IsGCBridgeActive() +{ + return g_GCBridgeActive; +} + void JavaMarshalNative::TriggerClientBridgeProcessing( _In_ MarkCrossReferencesArgs* args) { diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 0dbc7caee8c0e7..83dadccf5bfa8c 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -422,6 +422,22 @@ void GCToEEInterface::TriggerClientBridgeProcessing(MarkCrossReferencesArgs* arg #endif // FEATURE_JAVAMARSHAL } +bool GCToEEInterface::IsClientBridgeProcessingActive() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#ifdef FEATURE_JAVAMARSHAL + return Interop::IsGCBridgeActive(); +#else + return false; +#endif // FEATURE_JAVAMARSHAL +} + void GCToEEInterface::SyncBlockCacheDemote(int max_gen) { CONTRACTL