Skip to content

Commit 9d39aa6

Browse files
[Release/5.0] - Fix internal cache clean up for ComWrappers (#53203)
* Cleanup internal ComWrappers cache when object enters Finalization queue * Object cleanup scenario. * Narrow usage to the COMINTEROP feature. * Feedback from main review
1 parent e917853 commit 9d39aa6

6 files changed

Lines changed: 135 additions & 2 deletions

File tree

src/coreclr/src/interop/comwrappers.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,7 @@ void* NativeObjectWrapperContext::GetRuntimeContext() const noexcept
856856

857857
IReferenceTracker* NativeObjectWrapperContext::GetReferenceTracker() const noexcept
858858
{
859-
return ((_trackerObjectState == TrackerObjectState_NotSet) ? nullptr : _trackerObject);
859+
return ((_trackerObjectState == TrackerObjectState_NotSet || _trackerObjectDisconnected) ? nullptr : _trackerObject);
860860
}
861861

862862
// See TrackerObjectManager::AfterWrapperCreated() for AddRefFromTrackerSource() usage.

src/coreclr/src/vm/gcenv.ee.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ VOID GCToEEInterface::AfterGcScanRoots (int condemned, int max_gen,
6565
// Go through all the only app domain and detach all the *unmarked* RCWs to prevent
6666
// the RCW cache from resurrecting them.
6767
::GetAppDomain()->DetachRCWs();
68+
69+
Interop::OnAfterGCScanRoots();
6870
#endif // FEATURE_COMINTEROP
6971
}
7072

src/coreclr/src/vm/interoplibinterface.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,16 @@ namespace
3030
enum
3131
{
3232
Flags_None = 0,
33+
34+
// The EOC has been collected and is no longer visible from managed code.
3335
Flags_Collected = 1,
36+
3437
Flags_ReferenceTracker = 2,
3538
Flags_InCache = 4,
39+
40+
// The EOC is "detached" and no longer used to map between identity and a managed object.
41+
// This will only be set if the EOC was inserted into the cache.
42+
Flags_Detached = 8,
3643
};
3744
DWORD Flags;
3845

@@ -80,6 +87,17 @@ namespace
8087
Flags |= Flags_Collected;
8188
}
8289

90+
void MarkDetached()
91+
{
92+
_ASSERTE(GCHeapUtilities::IsGCInProgress());
93+
Flags |= Flags_Detached;
94+
}
95+
96+
void MarkNotInCache()
97+
{
98+
::InterlockedAnd((LONG*)&Flags, (~Flags_InCache));
99+
}
100+
83101
OBJECTREF GetObjectRef()
84102
{
85103
CONTRACTL
@@ -432,6 +450,32 @@ namespace
432450

433451
_hashMap.Remove(cxt->GetKey());
434452
}
453+
454+
void DetachNotPromotedEOCs()
455+
{
456+
CONTRACTL
457+
{
458+
NOTHROW;
459+
GC_NOTRIGGER;
460+
MODE_ANY;
461+
PRECONDITION(GCHeapUtilities::IsGCInProgress()); // GC is in progress and the runtime is suspended
462+
}
463+
CONTRACTL_END;
464+
465+
Iterator curr = _hashMap.Begin();
466+
Iterator end = _hashMap.End();
467+
468+
ExternalObjectContext* cxt;
469+
for (; curr != end; ++curr)
470+
{
471+
cxt = *curr;
472+
if (!cxt->IsSet(ExternalObjectContext::Flags_Detached)
473+
&& !GCHeapUtilities::GetGCHeap()->IsPromoted(OBJECTREFToObject(cxt->GetObjectRef())))
474+
{
475+
cxt->MarkDetached();
476+
}
477+
}
478+
}
435479
};
436480

437481
// Global instance of the external object cache
@@ -731,6 +775,15 @@ namespace
731775
handle = handleLocal;
732776
}
733777
}
778+
else if (extObjCxt != NULL && extObjCxt->IsSet(ExternalObjectContext::Flags_Detached))
779+
{
780+
// If an EOC has been found but is marked detached, then we will remove it from the
781+
// cache here instead of letting the GC do it later and pretend like it wasn't found.
782+
STRESS_LOG1(LF_INTEROP, LL_INFO10, "Detached EOC requested: 0x%p\n", extObjCxt);
783+
cache->Remove(extObjCxt);
784+
extObjCxt->MarkNotInCache();
785+
extObjCxt = NULL;
786+
}
734787
}
735788

736789
STRESS_LOG2(LF_INTEROP, LL_INFO1000, "EOC: 0x%p or Handle: 0x%p\n", extObjCxt, handle);
@@ -1827,3 +1880,19 @@ void Interop::OnGCFinished(_In_ int nCondemnedGeneration)
18271880
}
18281881
#endif // FEATURE_COMWRAPPERS
18291882
}
1883+
1884+
void Interop::OnAfterGCScanRoots()
1885+
{
1886+
CONTRACTL
1887+
{
1888+
NOTHROW;
1889+
GC_NOTRIGGER;
1890+
}
1891+
CONTRACTL_END;
1892+
1893+
#ifdef FEATURE_COMWRAPPERS
1894+
ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow();
1895+
if (cache != NULL)
1896+
cache->DetachNotPromotedEOCs();
1897+
#endif // FEATURE_COMWRAPPERS
1898+
}

src/coreclr/src/vm/interoplibinterface.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,6 @@ class Interop
9999

100100
// Notify when GC finished
101101
static void OnGCFinished(_In_ int nCondemnedGeneration);
102+
103+
static void OnAfterGCScanRoots();
102104
};

src/tests/Interop/COM/ComWrappers/API/Program.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,56 @@ static void ValidatePrecreatedExternalWrapper()
261261
});
262262
}
263263

264+
static void ValidateExternalWrapperCacheCleanUp()
265+
{
266+
Console.WriteLine($"Running {nameof(ValidateExternalWrapperCacheCleanUp)}...");
267+
268+
var cw = new TestComWrappers();
269+
270+
// Get an object from a tracker runtime.
271+
IntPtr trackerObjRaw = MockReferenceTrackerRuntime.CreateTrackerObject();
272+
273+
// Create a wrapper for the object instance.
274+
var weakRef1 = CreateAndRegisterWrapper(cw, trackerObjRaw);
275+
276+
// Run the GC to have the wrapper marked for collection.
277+
ForceGC();
278+
279+
// Create a new wrapper for the same external object.
280+
var weakRef2 = CreateAndRegisterWrapper(cw, trackerObjRaw);
281+
282+
// We are using a tracking resurrection WeakReference<T> so we should be able
283+
// to get back the objects as they are all continually re-registering for Finalization.
284+
Assert.IsTrue(weakRef1.TryGetTarget(out ITrackerObjectWrapper wrapper1));
285+
Assert.IsTrue(weakRef2.TryGetTarget(out ITrackerObjectWrapper wrapper2));
286+
287+
// Check that the two wrappers aren't equal, meaning we created a new wrapper since
288+
// the first wrapper was removed from the internal cache.
289+
Assert.AreNotEqual(wrapper1, wrapper2);
290+
291+
// Let the wrappers Finalize.
292+
wrapper1.ReregisterForFinalize = false;
293+
wrapper2.ReregisterForFinalize = false;
294+
295+
static WeakReference<ITrackerObjectWrapper> CreateAndRegisterWrapper(ComWrappers cw, IntPtr trackerObjRaw)
296+
{
297+
// Manually create a wrapper
298+
var iid = typeof(ITrackerObject).GUID;
299+
IntPtr iTestComObject;
300+
int hr = Marshal.QueryInterface(trackerObjRaw, ref iid, out iTestComObject);
301+
Assert.AreEqual(0, hr);
302+
var nativeWrapper = new ITrackerObjectWrapper(iTestComObject);
303+
304+
nativeWrapper = (ITrackerObjectWrapper)cw.GetOrRegisterObjectForComInstance(trackerObjRaw, CreateObjectFlags.None, nativeWrapper);
305+
306+
// Set this on the return instead of during creation since the returned wrapper may be the one from
307+
// the internal cache and not the one passed in above.
308+
nativeWrapper.ReregisterForFinalize = true;
309+
310+
return new WeakReference<ITrackerObjectWrapper>(nativeWrapper, trackResurrection: true);
311+
}
312+
}
313+
264314
static void ValidateIUnknownImpls()
265315
=> TestComWrappers.ValidateIUnknownImpls();
266316

@@ -477,6 +527,7 @@ static int Main(string[] doNotUse)
477527
ValidateCreateObjectCachingScenario();
478528
ValidateWrappersInstanceIsolation();
479529
ValidatePrecreatedExternalWrapper();
530+
ValidateExternalWrapperCacheCleanUp();
480531
ValidateIUnknownImpls();
481532
ValidateBadComWrapperImpl();
482533
ValidateRuntimeTrackerScenario();

src/tests/Interop/COM/ComWrappers/Common.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,18 @@ static IntPtr CreateInstance(IntPtr outer, out IntPtr inner)
206206

207207
~ITrackerObjectWrapper()
208208
{
209-
ComWrappersHelper.Cleanup(ref this.classNative);
209+
if (this.ReregisterForFinalize)
210+
{
211+
GC.ReRegisterForFinalize(this);
212+
}
213+
else
214+
{
215+
ComWrappersHelper.Cleanup(ref this.classNative);
216+
}
210217
}
211218

219+
public bool ReregisterForFinalize { get; set; } = false;
220+
212221
public int AddObjectRef(IntPtr obj)
213222
{
214223
int id;

0 commit comments

Comments
 (0)