// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // Callouts from the unmanaged portion of the runtime to C# helpers made during garbage collections. See // RestrictedCallouts.h for more detail. // #include "common.h" #include "CommonTypes.h" #include "CommonMacros.h" #include "daccess.h" #include "PalRedhawkCommon.h" #include "PalRedhawk.h" #include "rhassert.h" #include "slist.h" #include "holder.h" #include "gcrhinterface.h" #include "shash.h" #include "RWLock.h" #include "module.h" #include "rhbinder.h" #include "Crst.h" #include "RuntimeInstance.h" #include "eetype.h" #include "ObjectLayout.h" #include "event.h" #include "varint.h" #include "regdisplay.h" #include "StackFrameIterator.h" #include "thread.h" #include "threadstore.h" #include "threadstore.inl" #include "RestrictedCallouts.h" // The head of the chains of GC callouts, one per callout type. RestrictedCallouts::GcRestrictedCallout * RestrictedCallouts::s_rgGcRestrictedCallouts[GCRC_Count] = { 0 }; // The head of the chain of HandleTable callouts. RestrictedCallouts::HandleTableRestrictedCallout * RestrictedCallouts::s_pHandleTableRestrictedCallouts = NULL; // Lock protecting access to s_rgGcRestrictedCallouts and s_pHandleTableRestrictedCallouts during registration // and unregistration (not used during actual callbacks since everything is single threaded then). CrstStatic RestrictedCallouts::s_sLock; // One time startup initialization. bool RestrictedCallouts::Initialize() { s_sLock.Init(CrstRestrictedCallouts, CRST_DEFAULT); return true; } // Register callback of the given type to the method with the given address. The most recently registered // callbacks are called first. Returns true on success, false if insufficient memory was available for the // registration. bool RestrictedCallouts::RegisterGcCallout(GcRestrictedCalloutKind eKind, void * pCalloutMethod) { // Validate callout kind. if (eKind >= GCRC_Count) { ASSERT_UNCONDITIONALLY("Invalid GC restricted callout kind."); RhFailFast(); } GcRestrictedCallout * pCallout = new (nothrow) GcRestrictedCallout(); if (pCallout == NULL) return false; pCallout->m_pCalloutMethod = pCalloutMethod; CrstHolder lh(&s_sLock); // Link new callout to head of the chain according to its type. pCallout->m_pNext = s_rgGcRestrictedCallouts[eKind]; s_rgGcRestrictedCallouts[eKind] = pCallout; return true; } // Unregister a previously registered callout. Removes the first registration that matches on both callout // kind and address. Causes a fail fast if the registration doesn't exist. void RestrictedCallouts::UnregisterGcCallout(GcRestrictedCalloutKind eKind, void * pCalloutMethod) { // Validate callout kind. if (eKind >= GCRC_Count) { ASSERT_UNCONDITIONALLY("Invalid GC restricted callout kind."); RhFailFast(); } CrstHolder lh(&s_sLock); GcRestrictedCallout * pCurrCallout = s_rgGcRestrictedCallouts[eKind]; GcRestrictedCallout * pPrevCallout = NULL; while (pCurrCallout) { if (pCurrCallout->m_pCalloutMethod == pCalloutMethod) { // Found a matching entry, remove it from the chain. if (pPrevCallout) pPrevCallout->m_pNext = pCurrCallout->m_pNext; else s_rgGcRestrictedCallouts[eKind] = pCurrCallout->m_pNext; delete pCurrCallout; return; } pPrevCallout = pCurrCallout; pCurrCallout = pCurrCallout->m_pNext; } // If we get here we didn't find a matching registration, indicating a bug on the part of the caller. ASSERT_UNCONDITIONALLY("Attempted to unregister restricted callout that wasn't registered."); RhFailFast(); } // Register callback for the "is alive" property of ref counted handles with objects of the given type (the // type match must be exact). The most recently registered callbacks are called first. Returns true on // success, false if insufficient memory was available for the registration. bool RestrictedCallouts::RegisterRefCountedHandleCallback(void * pCalloutMethod, EEType * pTypeFilter) { HandleTableRestrictedCallout * pCallout = new (nothrow) HandleTableRestrictedCallout(); if (pCallout == NULL) return false; pCallout->m_pCalloutMethod = pCalloutMethod; pCallout->m_pTypeFilter = pTypeFilter; CrstHolder lh(&s_sLock); // Link new callout to head of the chain. pCallout->m_pNext = s_pHandleTableRestrictedCallouts; s_pHandleTableRestrictedCallouts = pCallout; return true; } // Unregister a previously registered callout. Removes the first registration that matches on both callout // address and filter type. Causes a fail fast if the registration doesn't exist. void RestrictedCallouts::UnregisterRefCountedHandleCallback(void * pCalloutMethod, EEType * pTypeFilter) { CrstHolder lh(&s_sLock); HandleTableRestrictedCallout * pCurrCallout = s_pHandleTableRestrictedCallouts; HandleTableRestrictedCallout * pPrevCallout = NULL; while (pCurrCallout) { if ((pCurrCallout->m_pCalloutMethod == pCalloutMethod) && (pCurrCallout->m_pTypeFilter == pTypeFilter)) { // Found a matching entry, remove it from the chain. if (pPrevCallout) pPrevCallout->m_pNext = pCurrCallout->m_pNext; else s_pHandleTableRestrictedCallouts = pCurrCallout->m_pNext; delete pCurrCallout; return; } pPrevCallout = pCurrCallout; pCurrCallout = pCurrCallout->m_pNext; } // If we get here we didn't find a matching registration, indicating a bug on the part of the caller. ASSERT_UNCONDITIONALLY("Attempted to unregister restricted callout that wasn't registered."); RhFailFast(); } // Invoke all the registered GC callouts of the given kind. The condemned generation of the current collection // is passed along to the callouts. void RestrictedCallouts::InvokeGcCallouts(GcRestrictedCalloutKind eKind, UInt32 uiCondemnedGeneration) { ASSERT(eKind < GCRC_Count); // It is illegal for any of the callouts to trigger a GC. Thread * pThread = ThreadStore::GetCurrentThread(); pThread->SetDoNotTriggerGc(); // Due to the above we have better suppress GC stress. bool fGcStressWasSuppressed = pThread->IsSuppressGcStressSet(); if (!fGcStressWasSuppressed) pThread->SetSuppressGcStress(); GcRestrictedCallout * pCurrCallout = s_rgGcRestrictedCallouts[eKind]; while (pCurrCallout) { // Make the callout. ((GcRestrictedCallbackFunction)pCurrCallout->m_pCalloutMethod)(uiCondemnedGeneration); pCurrCallout = pCurrCallout->m_pNext; } // Revert GC stress mode if we changed it. if (!fGcStressWasSuppressed) pThread->ClearSuppressGcStress(); pThread->ClearDoNotTriggerGc(); } // Invoke all the registered ref counted handle callouts for the given object extracted from the handle. The // result is the union of the results for all the handlers that matched the object type (i.e. if one of them // returned true the overall result is true otherwise false is returned (which includes the case where no // handlers matched)). Since there should be no other side-effects of the callout, the invocations cease as // soon as a handler returns true. bool RestrictedCallouts::InvokeRefCountedHandleCallbacks(Object * pObject) { bool fResult = false; // It is illegal for any of the callouts to trigger a GC. Thread * pThread = ThreadStore::GetCurrentThread(); pThread->SetDoNotTriggerGc(); // Due to the above we have better suppress GC stress. bool fGcStressWasSuppressed = pThread->IsSuppressGcStressSet(); if (!fGcStressWasSuppressed) pThread->SetSuppressGcStress(); HandleTableRestrictedCallout * pCurrCallout = s_pHandleTableRestrictedCallouts; while (pCurrCallout) { if (pObject->get_SafeEEType() == pCurrCallout->m_pTypeFilter) { // Make the callout. Return true to our caller as soon as we see a true result here. if (((HandleTableRestrictedCallbackFunction)pCurrCallout->m_pCalloutMethod)(pObject)) { fResult = true; goto Done; } } pCurrCallout = pCurrCallout->m_pNext; } Done: // Revert GC stress mode if we changed it. if (!fGcStressWasSuppressed) pThread->ClearSuppressGcStress(); pThread->ClearDoNotTriggerGc(); return fResult; }