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

Cleanup internal ComWrappers cache when object enters Finalization queue #52771

Merged
merged 9 commits into from
May 25, 2021
5 changes: 3 additions & 2 deletions src/coreclr/interop/comwrappers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,8 @@ ManagedObjectWrapper* ManagedObjectWrapper::MapFromIUnknown(_In_ IUnknown* pUnk)
// If the first Vtable entry is part of the ManagedObjectWrapper IUnknown impl,
// we know how to interpret the IUnknown.
void** vtable = *reinterpret_cast<void***>(pUnk);
if (*vtable != ManagedObjectWrapper_IUnknownImpl.QueryInterface)
if (*vtable != ManagedObjectWrapper_IUnknownImpl.QueryInterface
&& *vtable != ManagedObjectWrapper_IReferenceTrackerTargetImpl.QueryInterface)
return nullptr;

ABI::ComInterfaceDispatch* disp = reinterpret_cast<ABI::ComInterfaceDispatch*>(pUnk);
Expand Down Expand Up @@ -841,7 +842,7 @@ void* NativeObjectWrapperContext::GetRuntimeContext() const noexcept

IReferenceTracker* NativeObjectWrapperContext::GetReferenceTracker() const noexcept
{
return ((_trackerObjectState == TrackerObjectState::NotSet) ? nullptr : _trackerObject);
return ((_trackerObjectState == TrackerObjectState::NotSet || _trackerObjectDisconnected) ? nullptr : _trackerObject);
}

// See TrackerObjectManager::AfterWrapperCreated() for AddRefFromTrackerSource() usage.
Expand Down
3 changes: 1 addition & 2 deletions src/coreclr/vm/gcenv.ee.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ VOID GCToEEInterface::AfterGcScanRoots (int condemned, int max_gen,
::GetAppDomain()->DetachRCWs();
#endif // FEATURE_COMINTEROP

if (!sc->concurrent)
Interop::OnAfterGCScanRoots();
Interop::OnAfterGCScanRoots(sc->concurrent);
}

/*
Expand Down
69 changes: 68 additions & 1 deletion src/coreclr/vm/interoplibinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ namespace
enum
{
Flags_None = 0,

// The EOC has been collected and is no longer visible from managed code.
Flags_Collected = 1,

Flags_ReferenceTracker = 2,
Flags_InCache = 4,

// The EOC is "detached" and no longer used to map between identity and a managed object.
Flags_Detached = 8,
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
};
DWORD Flags;

Expand Down Expand Up @@ -69,7 +75,7 @@ namespace

bool IsActive() const
{
return !IsSet(Flags_Collected)
return !IsSet(Flags_Collected | Flags_Detached)
&& (SyncBlockIndex != InvalidSyncBlockIndex);
}

Expand All @@ -80,6 +86,17 @@ namespace
Flags |= Flags_Collected;
}

void MarkDetached()
{
_ASSERTE(GCHeapUtilities::IsGCInProgress());
Flags |= Flags_Detached;
}

void MarkNotInCache()
{
::InterlockedAnd((LONG*)&Flags, (~Flags_InCache));
}

OBJECTREF GetObjectRef()
{
CONTRACTL
Expand Down Expand Up @@ -428,6 +445,32 @@ namespace

_hashMap.Remove(cxt->GetKey());
}

void DetachNotPromotedEOCs()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(GCHeapUtilities::IsGCInProgress()); // GC is in progress and the runtime is suspended
}
CONTRACTL_END;

Iterator curr = _hashMap.Begin();
Iterator end = _hashMap.End();

ExternalObjectContext* cxt;
for (; curr != end; ++curr)
{
cxt = *curr;
if (cxt->IsActive()
&& !GCHeapUtilities::GetGCHeap()->IsPromoted(OBJECTREFToObject(cxt->GetObjectRef())))
{
cxt->MarkDetached();
}
}
}
};

// Global instance of the external object cache
Expand Down Expand Up @@ -727,6 +770,15 @@ namespace
handle = handleLocal;
}
}
else if (extObjCxt != NULL && extObjCxt->IsSet(ExternalObjectContext::Flags_Detached))
{
// If an EOC has been found but is marked detached, then we will remove it from the
// cache here instead of letting the GC do it later and pretend like it wasn't found.
STRESS_LOG1(LF_INTEROP, LL_INFO10, "Detached EOC requested: 0x%p\n", extObjCxt);
cache->Remove(extObjCxt);
extObjCxt->MarkNotInCache();
extObjCxt = NULL;
}
}

STRESS_LOG2(LF_INTEROP, LL_INFO1000, "EOC: 0x%p or Handle: 0x%p\n", extObjCxt, handle);
Expand Down Expand Up @@ -1796,4 +1848,19 @@ void ComWrappersNative::OnFullGCFinished()
}
}

void ComWrappersNative::AfterRefCountedHandleCallbacks()
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;

ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow();
if (cache != NULL)
cache->DetachNotPromotedEOCs();
}

#endif // FEATURE_COMWRAPPERS
3 changes: 2 additions & 1 deletion src/coreclr/vm/interoplibinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class ComWrappersNative
public: // GC interaction
static void OnFullGCStarted();
static void OnFullGCFinished();
static void AfterRefCountedHandleCallbacks();
};

class GlobalComWrappersForMarshalling
Expand Down Expand Up @@ -187,7 +188,7 @@ class Interop
// Notify before/after when GC is scanning roots.
// Present assumption is that calls will never be nested.
static void OnBeforeGCScanRoots();
static void OnAfterGCScanRoots();
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
static void OnAfterGCScanRoots(_In_ bool is_concurrent);
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
};

#endif // _INTEROPLIBINTERFACE_H_
9 changes: 7 additions & 2 deletions src/coreclr/vm/interoplibinterface_shared.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ void Interop::OnBeforeGCScanRoots()
#endif // FEATURE_OBJCMARSHAL
}

void Interop::OnAfterGCScanRoots()
void Interop::OnAfterGCScanRoots(_In_ bool is_concurrent)
{
CONTRACTL
{
Expand All @@ -139,7 +139,12 @@ void Interop::OnAfterGCScanRoots()
}
CONTRACTL_END;

#ifdef FEATURE_COMWRAPPERS
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
ComWrappersNative::AfterRefCountedHandleCallbacks();
#endif // FEATURE_COMWRAPPERS

#ifdef FEATURE_OBJCMARSHAL
ObjCMarshalNative::AfterRefCountedHandleCallbacks();
if (!is_concurrent)
ObjCMarshalNative::AfterRefCountedHandleCallbacks();
#endif // FEATURE_OBJCMARSHAL
}
51 changes: 51 additions & 0 deletions src/tests/Interop/COM/ComWrappers/API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,56 @@ static void ValidatePrecreatedExternalWrapper()
});
}

static void ValidateExternalWrapperCacheCleanUp()
{
Console.WriteLine($"Running {nameof(ValidateExternalWrapperCacheCleanUp)}...");

var cw = new TestComWrappers();

// Get an object from a tracker runtime.
IntPtr trackerObjRaw = MockReferenceTrackerRuntime.CreateTrackerObject();

// Create a wrapper for the object instance.
var weakRef1 = CreateAndRegisterWrapper(cw, trackerObjRaw);

// Run the GC to have the wrapper marked for collection.
ForceGC();

// Create a new wrapper for the same external object.
var weakRef2 = CreateAndRegisterWrapper(cw, trackerObjRaw);

// We are using a tracking resurrection WeakReference<T> so we should be able
// to get back the objects as they are all continually re-registering for Finalization.
Assert.IsTrue(weakRef1.TryGetTarget(out ITrackerObjectWrapper wrapper1));
Assert.IsTrue(weakRef2.TryGetTarget(out ITrackerObjectWrapper wrapper2));

// Check that the two wrappers aren't equal, meaning we created a new wrapper since
// the first wrapper was removed from the internal cache.
Assert.AreNotEqual(wrapper1, wrapper2);

// Let the wrappers Finalize.
wrapper1.ReregisterForFinalize = false;
wrapper2.ReregisterForFinalize = false;

static WeakReference<ITrackerObjectWrapper> CreateAndRegisterWrapper(ComWrappers cw, IntPtr trackerObjRaw)
{
// Manually create a wrapper
var iid = typeof(ITrackerObject).GUID;
IntPtr iTestComObject;
int hr = Marshal.QueryInterface(trackerObjRaw, ref iid, out iTestComObject);
Assert.AreEqual(0, hr);
var nativeWrapper = new ITrackerObjectWrapper(iTestComObject);

nativeWrapper = (ITrackerObjectWrapper)cw.GetOrRegisterObjectForComInstance(trackerObjRaw, CreateObjectFlags.None, nativeWrapper);

// Set this on the return instead of during creation since the returned wrapper may be the one from
// the internal cache and not the one passed in above.
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
nativeWrapper.ReregisterForFinalize = true;

return new WeakReference<ITrackerObjectWrapper>(nativeWrapper, trackResurrection: true);
}
}

static void ValidateSuppliedInnerNotAggregation()
{
Console.WriteLine($"Running {nameof(ValidateSuppliedInnerNotAggregation)}...");
Expand Down Expand Up @@ -566,6 +616,7 @@ static int Main(string[] doNotUse)
ValidateCreateObjectCachingScenario();
ValidateWrappersInstanceIsolation();
ValidatePrecreatedExternalWrapper();
ValidateExternalWrapperCacheCleanUp();
ValidateSuppliedInnerNotAggregation();
ValidateIUnknownImpls();
ValidateBadComWrapperImpl();
Expand Down
11 changes: 10 additions & 1 deletion src/tests/Interop/COM/ComWrappers/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,18 @@ static IntPtr CreateInstance(IntPtr outer, out IntPtr inner)

~ITrackerObjectWrapper()
{
ComWrappersHelper.Cleanup(ref this.classNative);
if (this.ReregisterForFinalize)
{
GC.ReRegisterForFinalize(this);
}
else
{
ComWrappersHelper.Cleanup(ref this.classNative);
}
}

public bool ReregisterForFinalize { get; set; } = false;

public int AddObjectRef(IntPtr obj)
{
int id;
Expand Down