Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 0791a4a

Browse files
authored
Port to 3.1: Fix allocation of RuntimeTypeCache GC handle (#28025)
* Port to 3.1: Fix allocation of RuntimeTypeCache GC handle When there is a race calling RuntimeType.InitializeCache, each of the racing threads creates a new GC handle using new RuntimeTypeHandle(this).GetGCHandle(GCHandleType.WeakTrackResurrection); This ends up calling RuntimeTypeHandle::GetGCHandle native method that adds the allocated handle into the handle cleanup list of the AssemblyLoaderAllocator specific for the runtime type. All but the winning thread then call GCHandle.InternalFree on the just allocated handle. That frees the handle, but leaves it on the cleanup list of the loader allocator. The same handle can be later allocated for some other purpose. When the AssemblyLoaderAllocator is being destroyed, all the handles on the cleanup list are destroyed too. So it destroys also the handle that was left on the cleanup list incorrectly. That can cause all kinds of hard to diagnose issues, like the dotnet/runtime#32171. This change fixes it by adding a FreeGCHandle method on the RuntimeTypeHandle that besides freeing the handle also removes it from the cleanup list of the related AssemblyLoadContext. ## Customer impact Hard to diagnose crashes in the runtime caused by closing of random GC handles. The customer that has reported this issue was using collectible assemblies and it was resulting in collecting LoaderAllocator that was still in use and it lead to crashes at various places. ## Regression? Yes, it was introduced in 3.0. In 2.1 and 2.2, the thread that loses the race destroys the handle only if the type was not in a collectible assembly. Since the non-collectible assemblies LoaderAllocator is never destroyed, the handles were never cleaned up and so no problem could occur. It was introduced in #21737 ##Testing Customer affected by the issue heavily has tested a fixed version and reported the issue doesn't occur anymore. ## Risk Low, the new code is executed at single place once per process runtine only when a thread races for allocating the GC handle with another one and loses the race. * Fix build break - subtle differences from runtime master
1 parent 46e980e commit 0791a4a

File tree

7 files changed

+58
-2
lines changed

7 files changed

+58
-2
lines changed

src/System.Private.CoreLib/src/System/RuntimeHandles.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,15 @@ internal IntPtr GetGCHandle(GCHandleType type)
339339
return GetGCHandle(JitHelpers.GetQCallTypeHandleOnStack(ref nativeHandle), type);
340340
}
341341

342+
[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
343+
private static extern IntPtr FreeGCHandle(QCallTypeHandle typeHandle, IntPtr objHandle);
344+
345+
internal IntPtr FreeGCHandle(IntPtr objHandle)
346+
{
347+
RuntimeTypeHandle nativeHandle = GetNativeHandle();
348+
return FreeGCHandle(JitHelpers.GetQCallTypeHandleOnStack(ref nativeHandle), objHandle);
349+
}
350+
342351
[MethodImplAttribute(MethodImplOptions.InternalCall)]
343352
internal static extern int GetNumVirtuals(RuntimeType type);
344353

src/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2414,10 +2414,11 @@ private RuntimeTypeCache InitializeCache()
24142414
{
24152415
if (m_cache == IntPtr.Zero)
24162416
{
2417-
IntPtr newgcHandle = new RuntimeTypeHandle(this).GetGCHandle(GCHandleType.WeakTrackResurrection);
2417+
RuntimeTypeHandle th = new RuntimeTypeHandle(this);
2418+
IntPtr newgcHandle = th.GetGCHandle(GCHandleType.WeakTrackResurrection);
24182419
IntPtr gcHandle = Interlocked.CompareExchange(ref m_cache, newgcHandle, IntPtr.Zero);
24192420
if (gcHandle != IntPtr.Zero)
2420-
GCHandle.InternalFree(newgcHandle);
2421+
th.FreeGCHandle(newgcHandle);
24212422
}
24222423

24232424
RuntimeTypeCache cache = (RuntimeTypeCache)GCHandle.InternalGet(m_cache);

src/vm/ecalllist.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ FCFuncStart(gCOMTypeHandleFuncs)
225225
FCFuncElement("CreateCaInstance", RuntimeTypeHandle::CreateCaInstance)
226226
FCFuncElement("CreateInstanceForAnotherGenericParameter", RuntimeTypeHandle::CreateInstanceForGenericType)
227227
QCFuncElement("GetGCHandle", RuntimeTypeHandle::GetGCHandle)
228+
QCFuncElement("FreeGCHandle", RuntimeTypeHandle::FreeGCHandle)
228229

229230
FCFuncElement("IsInstanceOfType", RuntimeTypeHandle::IsInstanceOfType)
230231
FCFuncElement("GetDeclaringMethod", RuntimeTypeHandle::GetDeclaringMethod)

src/vm/loaderallocator.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1838,6 +1838,31 @@ void AssemblyLoaderAllocator::RegisterHandleForCleanup(OBJECTHANDLE objHandle)
18381838
m_handleCleanupList.InsertTail(new (pItem) HandleCleanupListItem(objHandle));
18391839
}
18401840

1841+
void AssemblyLoaderAllocator::UnregisterHandleFromCleanup(OBJECTHANDLE objHandle)
1842+
{
1843+
CONTRACTL
1844+
{
1845+
MODE_ANY;
1846+
CAN_TAKE_LOCK;
1847+
PRECONDITION(CheckPointer(objHandle));
1848+
}
1849+
CONTRACTL_END;
1850+
1851+
// FindAndRemove must be protected by a lock. Just use the loader allocator lock
1852+
CrstHolder ch(&m_crstLoaderAllocator);
1853+
1854+
for (HandleCleanupListItem* item = m_handleCleanupList.GetHead(); item != NULL; item = SList<HandleCleanupListItem>::GetNext(item))
1855+
{
1856+
if (item->m_handle == objHandle)
1857+
{
1858+
m_handleCleanupList.FindAndRemove(item);
1859+
return;
1860+
}
1861+
}
1862+
1863+
_ASSERTE(!"Trying to unregister a handle that was never registered");
1864+
}
1865+
18411866
void AssemblyLoaderAllocator::CleanupHandles()
18421867
{
18431868
CONTRACTL

src/vm/loaderallocator.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ class LoaderAllocator
467467

468468
// The default implementation is a no-op. Only collectible loader allocators implement this method.
469469
virtual void RegisterHandleForCleanup(OBJECTHANDLE /* objHandle */) { }
470+
virtual void UnregisterHandleFromCleanup(OBJECTHANDLE /* objHandle */) { }
470471
virtual void CleanupHandles() { }
471472

472473
void RegisterFailedTypeInitForCleanup(ListLockEntry *pListLockEntry);
@@ -671,6 +672,7 @@ class AssemblyLoaderAllocator : public LoaderAllocator
671672

672673
#if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
673674
virtual void RegisterHandleForCleanup(OBJECTHANDLE objHandle);
675+
virtual void UnregisterHandleFromCleanup(OBJECTHANDLE /* objHandle */);
674676
virtual void CleanupHandles();
675677
CLRPrivBinderAssemblyLoadContext* GetBinder()
676678
{

src/vm/runtimehandles.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,21 @@ PVOID QCALLTYPE RuntimeTypeHandle::GetGCHandle(QCall::TypeHandle pTypeHandle, IN
11091109
return objHandle;
11101110
}
11111111

1112+
void QCALLTYPE RuntimeTypeHandle::FreeGCHandle(QCall::TypeHandle pTypeHandle, OBJECTHANDLE objHandle)
1113+
{
1114+
QCALL_CONTRACT;
1115+
1116+
BEGIN_QCALL;
1117+
1118+
GCX_COOP();
1119+
1120+
TypeHandle th = pTypeHandle.AsTypeHandle();
1121+
th.GetLoaderAllocator()->UnregisterHandleFromCleanup(objHandle);
1122+
DestroyTypedHandle(objHandle);
1123+
1124+
END_QCALL;
1125+
}
1126+
11121127
void QCALLTYPE RuntimeTypeHandle::VerifyInterfaceIsImplemented(QCall::TypeHandle pTypeHandle, QCall::TypeHandle pIFaceHandle)
11131128
{
11141129
QCALL_CONTRACT;

src/vm/runtimehandles.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ class RuntimeTypeHandle {
241241
static
242242
PVOID QCALLTYPE GetGCHandle(QCall::TypeHandle pTypeHandle, INT32 handleType);
243243

244+
static
245+
void QCALLTYPE FreeGCHandle(QCall::TypeHandle pTypeHandle, OBJECTHANDLE objHandle);
246+
244247
static FCDECL1(INT32, GetCorElementType, PTR_ReflectClassBaseObject pType);
245248
static FCDECL1(ReflectClassBaseObject*, GetElementType, ReflectClassBaseObject* pType);
246249

0 commit comments

Comments
 (0)