Skip to content

Commit 53d2560

Browse files
github-actions[bot]AaronRobinsonMSFTelinor-fung
authored
[release/6.0-rc2] Check if External Object Context is still active after a possible GC (#59436)
* Check if External Object Context is still active after a possible GC * Update interoplibinterface_comwrappers.cpp Check if `IsActive()` during consideration too. * Issues can occur during the multiple managed allocations. * Add validation of non-null elements in ReleaseObjects() API call. Use detach state instead of IsActive state for EOC. * Apply suggestions from code review Co-authored-by: Elinor Fung <elfung@microsoft.com> * Apply suggestions from code review Co-authored-by: Elinor Fung <elfung@microsoft.com> * Review feedback Co-authored-by: Aaron Robinson <arobins@microsoft.com> Co-authored-by: Elinor Fung <elfung@microsoft.com>
1 parent 212c440 commit 53d2560

File tree

2 files changed

+71
-18
lines changed

2 files changed

+71
-18
lines changed

src/coreclr/vm/interoplibinterface_comwrappers.cpp

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -312,52 +312,100 @@ namespace
312312
GC_TRIGGERS;
313313
MODE_COOPERATIVE;
314314
PRECONDITION(!IsLockHeld());
315+
PRECONDITION(!(withFlags & ExternalObjectContext::Flags_Collected));
316+
PRECONDITION(!(withFlags & ExternalObjectContext::Flags_Detached));
315317
POSTCONDITION(RETVAL != NULL);
316318
}
317319
CONTRACT_END;
318320

319321
struct
320322
{
323+
PTRARRAYREF arrRefTmp;
321324
PTRARRAYREF arrRef;
322325
} gc;
323326
::ZeroMemory(&gc, sizeof(gc));
324327
GCPROTECT_BEGIN(gc);
325328

329+
// Only add objects that are in the correct thread
330+
// context, haven't been detached from the cache, and have
331+
// the appropriate flags set.
332+
// Define a macro predicate since it used in multiple places.
333+
// If an instance is in the hashmap, it is active. This invariant
334+
// holds because the GC is what marks and removes from the cache.
335+
#define SELECT_OBJECT(XX) XX->ThreadContext == threadContext \
336+
&& !XX->IsSet(ExternalObjectContext::Flags_Detached) \
337+
&& (withFlags == ExternalObjectContext::Flags_None || XX->IsSet(withFlags))
338+
339+
// Determine the count of objects to return.
340+
SIZE_T objCountMax = 0;
341+
{
342+
LockHolder lock(this);
343+
Iterator end = _hashMap.End();
344+
for (Iterator curr = _hashMap.Begin(); curr != end; ++curr)
345+
{
346+
ExternalObjectContext* inst = *curr;
347+
if (SELECT_OBJECT(inst))
348+
{
349+
objCountMax++;
350+
}
351+
}
352+
}
353+
354+
// Allocate enumerable type to return.
355+
gc.arrRef = (PTRARRAYREF)AllocateObjectArray((DWORD)objCountMax, g_pObjectClass);
356+
326357
CQuickArrayList<ExternalObjectContext*> localList;
327358

328-
// Determine objects to return
359+
// Iterate over the hashmap again while populating the above array
360+
// using the same predicate as before and holding onto context instances.
361+
SIZE_T objCount = 0;
362+
if (0 < objCountMax)
329363
{
330364
LockHolder lock(this);
331365
Iterator end = _hashMap.End();
332366
for (Iterator curr = _hashMap.Begin(); curr != end; ++curr)
333367
{
334368
ExternalObjectContext* inst = *curr;
335-
336-
// Only add objects that are in the correct thread
337-
// context and have the appropriate flags set.
338-
if (inst->ThreadContext == threadContext
339-
&& (withFlags == ExternalObjectContext::Flags_None || inst->IsSet(withFlags)))
369+
if (SELECT_OBJECT(inst))
340370
{
341371
localList.Push(inst);
342-
STRESS_LOG1(LF_INTEROP, LL_INFO100, "Add EOC to Enumerable: 0x%p\n", inst);
372+
373+
gc.arrRef->SetAt(objCount, inst->GetObjectRef());
374+
objCount++;
375+
376+
STRESS_LOG1(LF_INTEROP, LL_INFO1000, "Add EOC to Enumerable: 0x%p\n", inst);
343377
}
378+
379+
// There is a chance more objects were added to the hash while the
380+
// lock was released during array allocation. Once we hit the computed max
381+
// we stop to avoid looking longer than needed.
382+
if (objCount == objCountMax)
383+
break;
344384
}
345385
}
346386

347-
// Allocate enumerable type to return.
348-
gc.arrRef = (PTRARRAYREF)AllocateObjectArray((DWORD)localList.Size(), g_pObjectClass);
349-
350-
// Insert objects into enumerable.
351-
// The ExternalObjectContexts in the hashmap are only
352-
// removed and associated objects collected during a GC. Since
353-
// this code is running in Cooperative mode they will never
354-
// be null.
355-
for (SIZE_T i = 0; i < localList.Size(); i++)
387+
#undef SELECT_OBJECT
388+
389+
// During the allocation of the array to return, a GC could have
390+
// occurred and objects detached from this cache. In order to avoid
391+
// having null array elements we will allocate a new array.
392+
// This subsequent allocation is okay because the array we are
393+
// replacing extends all object lifetimes.
394+
if (objCount < objCountMax)
356395
{
357-
ExternalObjectContext* inst = localList[i];
358-
gc.arrRef->SetAt(i, inst->GetObjectRef());
396+
gc.arrRefTmp = (PTRARRAYREF)AllocateObjectArray((DWORD)objCount, g_pObjectClass);
397+
398+
void* dest = gc.arrRefTmp->GetDataPtr();
399+
void* src = gc.arrRef->GetDataPtr();
400+
SIZE_T elementSize = gc.arrRef->GetComponentSize();
401+
402+
memmoveGCRefs(dest, src, objCount * elementSize);
403+
gc.arrRef = gc.arrRefTmp;
359404
}
360405

406+
// All objects are now referenced from the array so won't be collected
407+
// at this point. This means we can safely iterate over the ExternalObjectContext
408+
// instances.
361409
{
362410
// Separate the wrapper from the tracker runtime prior to
363411
// passing them onto the caller. This call is okay to make

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ protected override object CreateObject(IntPtr externalComObject, CreateObjectFla
171171

172172
protected override void ReleaseObjects(IEnumerable objects)
173173
{
174+
foreach (object o in objects)
175+
{
176+
Assert.IsNotNull(o);
177+
}
178+
174179
throw new Exception() { HResult = ReleaseObjectsCallAck };
175180
}
176181

0 commit comments

Comments
 (0)