Skip to content

Commit

Permalink
Fix assertion/possible deadlock in thread shutdown in the presence of…
Browse files Browse the repository at this point in the history
… ThreadStatics on collectible assemblies (#104003)

* Fix assertion/possible deadlock in thread shutdown in the presence of ThreadStatics on collectible assemblies

* Add contracts and preconditions to the various methods that manipulate the LoaderAllocator handle table

* Update src/coreclr/vm/loaderallocator.cpp

Co-authored-by: Aaron Robinson <arobins@microsoft.com>

* Make assertion check safe for running during LoaderAllocator initialization scenarios

---------

Co-authored-by: Aaron Robinson <arobins@microsoft.com>
  • Loading branch information
davidwrighton and AaronRobinsonMSFT authored Jun 27, 2024
1 parent 55f2bc6 commit 0015867
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 28 deletions.
12 changes: 6 additions & 6 deletions src/coreclr/vm/loaderallocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ LOADERHANDLE LoaderAllocator::AllocateHandle(OBJECTREF value)
do
{
{
CrstHolder ch(&m_crstLoaderAllocator);
CrstHolder ch(&m_crstLoaderAllocatorHandleTable);

gc.handleTable = gc.loaderAllocator->GetHandleTable();

Expand All @@ -781,7 +781,6 @@ LOADERHANDLE LoaderAllocator::AllocateHandle(OBJECTREF value)
retVal = (UINT_PTR)((freeHandleIndex + 1) << 1);
break;
}

slotsUsed = gc.loaderAllocator->GetSlotsUsed();

if (slotsUsed > MAX_LOADERALLOCATOR_HANDLE)
Expand All @@ -808,7 +807,7 @@ LOADERHANDLE LoaderAllocator::AllocateHandle(OBJECTREF value)
gc.handleTable = (PTRARRAYREF)AllocateObjectArray(newSize, g_pObjectClass);

{
CrstHolder ch(&m_crstLoaderAllocator);
CrstHolder ch(&m_crstLoaderAllocatorHandleTable);

if (gc.loaderAllocator->GetHandleTable() == gc.handleTableOld)
{
Expand Down Expand Up @@ -886,7 +885,7 @@ void LoaderAllocator::FreeHandle(LOADERHANDLE handle)
// The slot value doesn't have the low bit set, so it is an index to the handle table.
// In this case, push the index of the handle to the stack of freed indexes for
// reuse.
CrstHolder ch(&m_crstLoaderAllocator);
CrstHolder ch(&m_crstLoaderAllocatorHandleTable);

UINT_PTR index = (((UINT_PTR)handle) >> 1) - 1;
// The Push can fail due to OOM. Ignore this failure, it is better than crashing. The
Expand Down Expand Up @@ -937,7 +936,7 @@ OBJECTREF LoaderAllocator::CompareExchangeValueInHandle(LOADERHANDLE handle, OBJ
else
{
/* The handle table is read locklessly, be careful */
CrstHolder ch(&m_crstLoaderAllocator);
CrstHolder ch(&m_crstLoaderAllocatorHandleTable);

_ASSERTE(!ObjectHandleIsNull(m_hLoaderAllocatorObjectHandle));

Expand Down Expand Up @@ -983,7 +982,7 @@ void LoaderAllocator::SetHandleValue(LOADERHANDLE handle, OBJECTREF value)
else
{
// The handle table is read locklessly, be careful
CrstHolder ch(&m_crstLoaderAllocator);
CrstHolder ch(&m_crstLoaderAllocatorHandleTable);

_ASSERTE(!ObjectHandleIsNull(m_hLoaderAllocatorObjectHandle));

Expand Down Expand Up @@ -1065,6 +1064,7 @@ void LoaderAllocator::Init(BaseDomain *pDomain, BYTE *pExecutableHeapMemory)
m_pDomain = pDomain;

m_crstLoaderAllocator.Init(CrstLoaderAllocator, (CrstFlags)CRST_UNSAFE_COOPGC);
m_crstLoaderAllocatorHandleTable.Init(CrstLeafLock, (CrstFlags)CRST_UNSAFE_COOPGC);
m_InteropDataCrst.Init(CrstInteropData, CRST_REENTRANCY);
#ifdef FEATURE_COMINTEROP
m_ComCallWrapperCrst.Init(CrstCOMCallWrapper);
Expand Down
12 changes: 12 additions & 0 deletions src/coreclr/vm/loaderallocator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ class LoaderAllocator
// The LoaderAllocator specific string literal map.
StringLiteralMap *m_pStringLiteralMap;
CrstExplicitInit m_crstLoaderAllocator;

// Protect the handle table data structures, seperated from m_crstLoaderAllocator to allow thread cleanup to use the lock
CrstExplicitInit m_crstLoaderAllocatorHandleTable;
bool m_fGCPressure;
bool m_fUnloaded;
bool m_fTerminated;
Expand Down Expand Up @@ -649,6 +652,15 @@ class LoaderAllocator
LOADERALLOCATORREF GetExposedObject();
bool IsExposedObjectLive();

#ifdef _DEBUG
bool HasHandleTableLock()
{
WRAPPER_NO_CONTRACT;
if (this == NULL) return true; // During initialization of the LoaderAllocator object, callers may call this with a null this pointer.
return m_crstLoaderAllocatorHandleTable.OwnedByCurrentThread();
}
#endif

#ifndef DACCESS_COMPILE
bool InsertObjectIntoFieldWithLifetimeOfCollectibleLoaderAllocator(OBJECTREF value, Object** pField);
LOADERHANDLE AllocateHandle(OBJECTREF value);
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/vm/loaderallocator.inl
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ FORCEINLINE BOOL LoaderAllocator::GetHandleValueFastPhase2(LOADERHANDLE handle,
return FALSE;

LOADERALLOCATORREF loaderAllocator = dac_cast<LOADERALLOCATORREF>(loaderAllocatorAsObjectRef);
PTRARRAYREF handleTable = loaderAllocator->GetHandleTable();
PTRARRAYREF handleTable = loaderAllocator->DangerousGetHandleTable();
UINT_PTR index = (((UINT_PTR)handle) >> 1) - 1;
*pValue = handleTable->GetAt(index);

Expand All @@ -149,7 +149,7 @@ FORCEINLINE OBJECTREF LoaderAllocator::GetHandleValueFastCannotFailType2(LOADERH
/* This is lockless access to the handle table, be careful */
OBJECTREF loaderAllocatorAsObjectRef = ObjectFromHandle(m_hLoaderAllocatorObjectHandle);
LOADERALLOCATORREF loaderAllocator = dac_cast<LOADERALLOCATORREF>(loaderAllocatorAsObjectRef);
PTRARRAYREF handleTable = loaderAllocator->GetHandleTable();
PTRARRAYREF handleTable = loaderAllocator->DangerousGetHandleTable();
UINT_PTR index = (((UINT_PTR)handle) >> 1) - 1;

return handleTable->GetAt(index);
Expand Down
58 changes: 58 additions & 0 deletions src/coreclr/vm/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1952,3 +1952,61 @@ void ExceptionObject::GetStackTrace(StackTraceArray & stackTrace, PTRARRAYREF *
#endif // !defined(DACCESS_COMPILE)

}

#ifndef DACCESS_COMPILE
void LoaderAllocatorObject::SetSlotsUsed(INT32 newSlotsUsed)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
PRECONDITION(m_pLoaderAllocatorScout->m_nativeLoaderAllocator->HasHandleTableLock());
}
CONTRACTL_END;

m_slotsUsed = newSlotsUsed;
}

PTRARRAYREF LoaderAllocatorObject::GetHandleTable()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
PRECONDITION(m_pLoaderAllocatorScout->m_nativeLoaderAllocator->HasHandleTableLock());
}
CONTRACTL_END;

return (PTRARRAYREF)m_pSlots;
}

void LoaderAllocatorObject::SetHandleTable(PTRARRAYREF handleTable)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
PRECONDITION(m_pLoaderAllocatorScout->m_nativeLoaderAllocator->HasHandleTableLock());
}
CONTRACTL_END;

SetObjectReference(&m_pSlots, (OBJECTREF)handleTable);
}

INT32 LoaderAllocatorObject::GetSlotsUsed()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
PRECONDITION(m_pLoaderAllocatorScout->m_nativeLoaderAllocator->HasHandleTableLock());
}
CONTRACTL_END;

return m_slotsUsed;
}
#endif // DACCESS_COMPILE
32 changes: 12 additions & 20 deletions src/coreclr/vm/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -2081,30 +2081,22 @@ class LoaderAllocatorObject : public Object
friend class CoreLibBinder;

public:
PTRARRAYREF GetHandleTable()
// All uses of this api must be safe lock-free reads used only for reading from the handle table
// The normal GetHandleTable can only be called while holding the handle table lock, but
// this is for use in lock-free scenarios
PTRARRAYREF DangerousGetHandleTable()
{
LIMITED_METHOD_DAC_CONTRACT;
return (PTRARRAYREF)m_pSlots;
}

void SetHandleTable(PTRARRAYREF handleTable)
{
LIMITED_METHOD_CONTRACT;
SetObjectReference(&m_pSlots, (OBJECTREF)handleTable);
}

INT32 GetSlotsUsed()
{
LIMITED_METHOD_CONTRACT;
return m_slotsUsed;
}

void SetSlotsUsed(INT32 newSlotsUsed)
{
LIMITED_METHOD_CONTRACT;
m_slotsUsed = newSlotsUsed;
return (PTRARRAYREF)ObjectToOBJECTREF(VolatileLoadWithoutBarrier((Object**)&m_pSlots));
}

#ifndef DACCESS_COMPILE
PTRARRAYREF GetHandleTable();
void SetHandleTable(PTRARRAYREF handleTable);
INT32 GetSlotsUsed();
void SetSlotsUsed(INT32 newSlotsUsed);
#endif // DACCESS_COMPILE

void SetNativeLoaderAllocator(LoaderAllocator * pLoaderAllocator)
{
LIMITED_METHOD_CONTRACT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Threading;
using Xunit;

public class Program
Expand Down Expand Up @@ -65,6 +66,48 @@ public static int TestEntryPoint()
if (val5Obj != obj5)
return 15;

int otherThreadResult = 0;
Thread t = new ((ThreadStart)delegate {

object obj1 = new object();
object obj2 = new object();
object obj3 = new object();
object obj4 = new object();
object obj5 = new object();
accessor.SetStaticObject(obj1, obj2, obj3, obj4, obj5);
accessor.GetStaticObject(out object val1Obj, out object val2Obj, out object val3Obj, out object val4Obj, out object val5Obj);
if (val1Obj != obj1)
{
otherThreadResult = 111;
return;
}
if (val2Obj != obj2)
{
otherThreadResult = 112;
return;
}
if (val3Obj != obj3)
{
otherThreadResult = 113;
return;
}
if (val4Obj != obj4)
{
otherThreadResult = 114;
return;
}
if (val5Obj != obj5)
{
otherThreadResult = 115;
return;
}
});

t.Start();
t.Join();
if (otherThreadResult != 0)
return otherThreadResult;

GC.KeepAlive(accessor);
return 100;
}
Expand Down

0 comments on commit 0015867

Please sign in to comment.