Skip to content

Commit 600f6bd

Browse files
Fix thread static cleanup paths (dotnet#107438)
* Fix thread static cleanup paths - Do not destroy GC handles while holding the spin lock - Free the pLoaderHandle array when the thread is terminated * When using a ThreadStatics stress test on collectible assemblies, a few more issues were found - Fix issue where the LoaderAllocator's SegmentedHandleIndex wasn't being freed - Fix issue where the logic to re-use TLSIndex values wasn't working properly
1 parent fe7a52d commit 600f6bd

File tree

4 files changed

+64
-36
lines changed

4 files changed

+64
-36
lines changed

src/coreclr/vm/loaderallocator.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ class SegmentedHandleIndexStack
264264

265265
public:
266266

267+
~SegmentedHandleIndexStack();
268+
267269
// Push the value to the stack. If the push cannot be done due to OOM, return false;
268270
inline bool Push(DWORD value);
269271

src/coreclr/vm/loaderallocator.inl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,19 @@ inline DWORD SegmentedHandleIndexStack::Pop()
208208
return m_TOSSegment->m_data[--m_TOSIndex];
209209
}
210210

211+
inline SegmentedHandleIndexStack::~SegmentedHandleIndexStack()
212+
{
213+
LIMITED_METHOD_CONTRACT;
214+
215+
while (m_TOSSegment != NULL)
216+
{
217+
Segment* prevSegment = m_TOSSegment->m_prev;
218+
delete m_TOSSegment;
219+
m_TOSSegment = prevSegment;
220+
}
221+
m_freeSegment = NULL;
222+
}
223+
211224
inline bool SegmentedHandleIndexStack::IsEmpty()
212225
{
213226
LIMITED_METHOD_CONTRACT;

src/coreclr/vm/threadstatics.cpp

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ static TLSIndexToMethodTableMap *g_pThreadStaticCollectibleTypeIndices;
3333
static TLSIndexToMethodTableMap *g_pThreadStaticNonCollectibleTypeIndices;
3434
static PTR_MethodTable g_pMethodTablesForDirectThreadLocalData[offsetof(ThreadLocalData, ExtendedDirectThreadLocalTLSData) - offsetof(ThreadLocalData, ThreadBlockingInfo_First) + EXTENDED_DIRECT_THREAD_LOCAL_SIZE];
3535

36-
static Volatile<uint8_t> s_GCsWhichDoRelocateAndCanEmptyOutTheTLSIndices = 0;
3736
static uint32_t g_NextTLSSlot = 1;
3837
static uint32_t g_NextNonCollectibleTlsSlot = NUMBER_OF_TLSOFFSETS_NOT_USED_IN_NONCOLLECTIBLE_ARRAY;
3938
static uint32_t g_directThreadLocalTLSBytesAvailable = EXTENDED_DIRECT_THREAD_LOCAL_SIZE;
@@ -277,7 +276,7 @@ void TLSIndexToMethodTableMap::Clear(TLSIndex index, uint8_t whenCleared)
277276
_ASSERTE(IsClearedValue(pMap[index.GetIndexOffset()]));
278277
}
279278

280-
bool TLSIndexToMethodTableMap::FindClearedIndex(uint8_t whenClearedMarkerToAvoid, TLSIndex* pIndex)
279+
bool TLSIndexToMethodTableMap::FindClearedIndex(TLSIndex* pIndex)
281280
{
282281
CONTRACTL
283282
{
@@ -291,15 +290,6 @@ bool TLSIndexToMethodTableMap::FindClearedIndex(uint8_t whenClearedMarkerToAvoid
291290
{
292291
if (entry.IsClearedValue)
293292
{
294-
uint8_t whenClearedMarker = entry.ClearedMarker;
295-
if ((whenClearedMarker == whenClearedMarkerToAvoid) ||
296-
(whenClearedMarker == (whenClearedMarkerToAvoid - 1)) ||
297-
(whenClearedMarker == (whenClearedMarkerToAvoid - 2)))
298-
{
299-
// Make sure we are not within 2 of the marker we are trying to avoid
300-
// Use multiple compares instead of trying to fuss around with the overflow style comparisons
301-
continue;
302-
}
303293
*pIndex = entry.TlsIndex;
304294
return true;
305295
}
@@ -317,7 +307,7 @@ void InitializeThreadStaticData()
317307
}
318308
CONTRACTL_END;
319309

320-
g_pThreadStaticCollectibleTypeIndices = new TLSIndexToMethodTableMap(TLSIndexType::NonCollectible);
310+
g_pThreadStaticCollectibleTypeIndices = new TLSIndexToMethodTableMap(TLSIndexType::Collectible);
321311
g_pThreadStaticNonCollectibleTypeIndices = new TLSIndexToMethodTableMap(TLSIndexType::NonCollectible);
322312
g_TLSCrst.Init(CrstThreadLocalStorageLock, CRST_UNSAFE_ANYMODE);
323313
}
@@ -387,7 +377,7 @@ void FreeLoaderAllocatorHandlesForTLSData(Thread *pThread)
387377
#endif
388378
for (const auto& entry : g_pThreadStaticCollectibleTypeIndices->CollectibleEntries())
389379
{
390-
_ASSERTE((entry.TlsIndex.GetIndexOffset() < pThread->cLoaderHandles) || allRemainingIndicesAreNotValid);
380+
_ASSERTE((entry.TlsIndex.GetIndexOffset() <= pThread->cLoaderHandles) || allRemainingIndicesAreNotValid);
391381
if (entry.TlsIndex.GetIndexOffset() >= pThread->cLoaderHandles)
392382
{
393383
#ifndef _DEBUG
@@ -405,6 +395,13 @@ void FreeLoaderAllocatorHandlesForTLSData(Thread *pThread)
405395
}
406396
}
407397
}
398+
399+
pThread->cLoaderHandles = -1; // Sentinel value indicating that there are no LoaderHandles and the thread is permanently dead.
400+
if (pThread->pLoaderHandles != NULL)
401+
{
402+
delete[] pThread->pLoaderHandles;
403+
pThread->pLoaderHandles = NULL;
404+
}
408405
}
409406
}
410407

@@ -431,34 +428,46 @@ void FreeThreadStaticData(Thread* pThread)
431428
}
432429
CONTRACTL_END;
433430

434-
SpinLockHolder spinLock(&pThread->m_TlsSpinLock);
431+
InFlightTLSData* pOldInFlightData = nullptr;
435432

436-
ThreadLocalData *pThreadLocalData = &t_ThreadStatics;
433+
int32_t oldCollectibleTlsDataCount = 0;
434+
DPTR(OBJECTHANDLE) pOldCollectibleTlsArrayData = nullptr;
437435

438-
for (int32_t iTlsSlot = 0; iTlsSlot < pThreadLocalData->cCollectibleTlsData; ++iTlsSlot)
439436
{
440-
if (!IsHandleNullUnchecked(pThreadLocalData->pCollectibleTlsArrayData[iTlsSlot]))
437+
SpinLockHolder spinLock(&pThread->m_TlsSpinLock);
438+
439+
ThreadLocalData *pThreadLocalData = &t_ThreadStatics;
440+
441+
pOldCollectibleTlsArrayData = pThreadLocalData->pCollectibleTlsArrayData;
442+
oldCollectibleTlsDataCount = pThreadLocalData->cCollectibleTlsData;
443+
444+
pThreadLocalData->pCollectibleTlsArrayData = NULL;
445+
pThreadLocalData->cCollectibleTlsData = 0;
446+
pThreadLocalData->pNonCollectibleTlsArrayData = NULL;
447+
pThreadLocalData->cNonCollectibleTlsData = 0;
448+
449+
pOldInFlightData = pThreadLocalData->pInFlightData;
450+
pThreadLocalData->pInFlightData = NULL;
451+
_ASSERTE(pThreadLocalData->pThread == pThread);
452+
pThreadLocalData->pThread = NULL;
453+
}
454+
455+
for (int32_t iTlsSlot = 0; iTlsSlot < oldCollectibleTlsDataCount; ++iTlsSlot)
456+
{
457+
if (!IsHandleNullUnchecked(pOldCollectibleTlsArrayData[iTlsSlot]))
441458
{
442-
DestroyLongWeakHandle(pThreadLocalData->pCollectibleTlsArrayData[iTlsSlot]);
459+
DestroyLongWeakHandle(pOldCollectibleTlsArrayData[iTlsSlot]);
443460
}
444461
}
445462

446-
delete[] (uint8_t*)pThreadLocalData->pCollectibleTlsArrayData;
447-
448-
pThreadLocalData->pCollectibleTlsArrayData = 0;
449-
pThreadLocalData->cCollectibleTlsData = 0;
450-
pThreadLocalData->pNonCollectibleTlsArrayData = 0;
451-
pThreadLocalData->cNonCollectibleTlsData = 0;
463+
delete[] (uint8_t*)pOldCollectibleTlsArrayData;
452464

453-
while (pThreadLocalData->pInFlightData != NULL)
465+
while (pOldInFlightData != NULL)
454466
{
455-
InFlightTLSData* pInFlightData = pThreadLocalData->pInFlightData;
456-
pThreadLocalData->pInFlightData = pInFlightData->pNext;
467+
InFlightTLSData* pInFlightData = pOldInFlightData;
468+
pOldInFlightData = pInFlightData->pNext;
457469
delete pInFlightData;
458470
}
459-
460-
_ASSERTE(pThreadLocalData->pThread == pThread);
461-
pThreadLocalData->pThread = NULL;
462471
}
463472

464473
void SetTLSBaseValue(TADDR *ppTLSBaseAddress, TADDR pTLSBaseAddress, bool useGCBarrierInsteadOfHandleStore)
@@ -553,6 +562,8 @@ void* GetThreadLocalStaticBase(TLSIndex index)
553562
delete[] pOldArray;
554563
}
555564

565+
_ASSERTE(t_ThreadStatics.pThread->cLoaderHandles != -1); // Check sentinel value indicating that there are no LoaderHandles, the thread has gone through termination and is permanently dead.
566+
556567
if (isCollectible && t_ThreadStatics.pThread->cLoaderHandles <= index.GetIndexOffset())
557568
{
558569
// Grow the underlying TLS array
@@ -594,9 +605,11 @@ void* GetThreadLocalStaticBase(TLSIndex index)
594605
gcBaseAddresses.pTLSBaseAddress = dac_cast<TADDR>(OBJECTREFToObject(ObjectFromHandle(pInFlightData->hTLSData)));
595606
if (pMT->IsClassInited())
596607
{
597-
SpinLockHolder spinLock(&t_ThreadStatics.pThread->m_TlsSpinLock);
598-
SetTLSBaseValue(gcBaseAddresses.ppTLSBaseAddress, gcBaseAddresses.pTLSBaseAddress, staticIsNonCollectible);
599-
*ppOldNextPtr = pInFlightData->pNext;
608+
{
609+
SpinLockHolder spinLock(&t_ThreadStatics.pThread->m_TlsSpinLock);
610+
SetTLSBaseValue(gcBaseAddresses.ppTLSBaseAddress, gcBaseAddresses.pTLSBaseAddress, staticIsNonCollectible);
611+
*ppOldNextPtr = pInFlightData->pNext;
612+
}
600613
delete pInFlightData;
601614
}
602615
break;
@@ -744,7 +757,7 @@ void GetTLSIndexForThreadStatic(MethodTable* pMT, bool gcStatic, TLSIndex* pInde
744757
}
745758
else
746759
{
747-
if (!g_pThreadStaticCollectibleTypeIndices->FindClearedIndex(s_GCsWhichDoRelocateAndCanEmptyOutTheTLSIndices, &newTLSIndex))
760+
if (!g_pThreadStaticCollectibleTypeIndices->FindClearedIndex(&newTLSIndex))
748761
{
749762
uint32_t tlsRawIndex = g_NextTLSSlot;
750763
newTLSIndex = TLSIndex(TLSIndexType::Collectible, tlsRawIndex);
@@ -777,7 +790,7 @@ void FreeTLSIndicesForLoaderAllocator(LoaderAllocator *pLoaderAllocator)
777790

778791
while (current != end)
779792
{
780-
g_pThreadStaticCollectibleTypeIndices->Clear(tlsIndicesToCleanup[current], s_GCsWhichDoRelocateAndCanEmptyOutTheTLSIndices);
793+
g_pThreadStaticCollectibleTypeIndices->Clear(tlsIndicesToCleanup[current], 0);
781794
++current;
782795
}
783796
}

src/coreclr/vm/threadstatics.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ class TLSIndexToMethodTableMap
304304

305305
#ifndef DACCESS_COMPILE
306306
void Set(TLSIndex index, PTR_MethodTable pMT, bool isGCStatic);
307-
bool FindClearedIndex(uint8_t whenClearedMarkerToAvoid, TLSIndex* pIndex);
307+
bool FindClearedIndex(TLSIndex* pIndex);
308308
void Clear(TLSIndex index, uint8_t whenCleared);
309309
#endif // !DACCESS_COMPILE
310310

0 commit comments

Comments
 (0)