Skip to content

Commit 02bea82

Browse files
authored
Use spin lock on SyncBlock Lock upgrade path (#121355)
1 parent 4d4c8fb commit 02bea82

File tree

3 files changed

+119
-114
lines changed

3 files changed

+119
-114
lines changed

src/coreclr/gc/gc.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,6 @@ extern size_t gc_global_mechanisms[MAX_GLOBAL_GC_MECHANISMS_COUNT];
141141
class DacHeapWalker;
142142
#endif
143143

144-
#define MP_LOCKS
145-
146144
#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES
147145
extern "C" uint32_t* g_gc_card_bundle_table;
148146
#endif
@@ -243,11 +241,11 @@ struct alloc_context : gc_alloc_context
243241
}
244242

245243
// How the alloc_count field is organized -
246-
//
244+
//
247245
// high 16-bits are for the handle info, out of which
248-
// high 10 bits store the cpu index.
246+
// high 10 bits store the cpu index.
249247
// low 6 bits store the number of handles allocated so far (before the next reset).
250-
//
248+
//
251249
// low 16-bits are for the actual alloc_count used by balance_heaps
252250
inline void init_alloc_count()
253251
{

src/coreclr/vm/syncblk.cpp

Lines changed: 115 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ void SyncBlockCache::DeleteSyncBlock(SyncBlock *psb)
786786
#endif // FEATURE_METADATA_UPDATER
787787

788788
// Cleanup lock info
789-
psb->m_thinLock = 0;
789+
psb->m_thinLock.StoreWithoutBarrier(0);
790790
if (psb->m_Lock)
791791
{
792792
DestroyHandle(psb->m_Lock);
@@ -1307,117 +1307,113 @@ void DumpSyncBlockCache()
13071307

13081308
// ***************************************************************************
13091309
//
1310-
// ObjHeader class implementation
1310+
// SpinLock implementation
13111311
//
13121312
// ***************************************************************************
13131313

1314-
#ifdef MP_LOCKS
1315-
DEBUG_NOINLINE void ObjHeader::EnterSpinLock()
1314+
namespace
13161315
{
1317-
// NOTE: This function cannot have a dynamic contract. If it does, the contract's
1318-
// destructor will reset the CLR debug state to what it was before entering the
1319-
// function, which will undo the BeginNoTriggerGC() call below.
1320-
STATIC_CONTRACT_GC_NOTRIGGER;
1321-
1322-
#ifdef _DEBUG
1323-
int i = 0;
1324-
#endif
1325-
1326-
DWORD dwSwitchCount = 0;
1327-
1328-
while (TRUE)
1316+
void EnterSpinLock(Volatile<DWORD>* pLock)
13291317
{
1330-
#ifdef _DEBUG
1331-
#ifdef HOST_64BIT
1332-
// Give 64bit more time because there isn't a remoting fast path now, and we've hit this assert
1333-
// needlessly in CLRSTRESS.
1334-
if (i++ > 30000)
1335-
#else
1336-
if (i++ > 10000)
1337-
#endif // HOST_64BIT
1338-
_ASSERTE(!"ObjHeader::EnterLock timed out");
1339-
#endif
1340-
// get the value so that it doesn't get changed under us.
1341-
LONG curValue = m_SyncBlockValue.LoadWithoutBarrier();
1318+
STATIC_CONTRACT_GC_NOTRIGGER;
13421319

1343-
// check if lock taken
1344-
if (! (curValue & BIT_SBLK_SPIN_LOCK))
1345-
{
1346-
// try to take the lock
1347-
LONG newValue = curValue | BIT_SBLK_SPIN_LOCK;
1348-
LONG result = InterlockedCompareExchange((LONG*)&m_SyncBlockValue, newValue, curValue);
1349-
if (result == curValue)
1350-
break;
1351-
}
1352-
if (g_SystemInfo.dwNumberOfProcessors > 1)
1320+
DWORD dwSwitchCount = 0;
1321+
1322+
while (TRUE)
13531323
{
1354-
for (int spinCount = 0; spinCount < BIT_SBLK_SPIN_COUNT; spinCount++)
1324+
// get the value so that it doesn't get changed under us.
1325+
LONG curValue = pLock->LoadWithoutBarrier();
1326+
1327+
// check if lock taken
1328+
if (! (curValue & BIT_SBLK_SPIN_LOCK))
13551329
{
1356-
if (! (m_SyncBlockValue & BIT_SBLK_SPIN_LOCK))
1330+
// try to take the lock
1331+
LONG newValue = curValue | BIT_SBLK_SPIN_LOCK;
1332+
LONG result = InterlockedCompareExchange((LONG*)pLock, newValue, curValue);
1333+
if (result == curValue)
13571334
break;
1358-
YieldProcessorNormalized(); // indicate to the processor that we are spinning
13591335
}
1360-
if (m_SyncBlockValue & BIT_SBLK_SPIN_LOCK)
1336+
if (g_SystemInfo.dwNumberOfProcessors > 1)
1337+
{
1338+
for (int spinCount = 0; spinCount < BIT_SBLK_SPIN_COUNT; spinCount++)
1339+
{
1340+
if (! (*pLock & BIT_SBLK_SPIN_LOCK))
1341+
break;
1342+
YieldProcessorNormalized(); // indicate to the processor that we are spinning
1343+
}
1344+
if (*pLock & BIT_SBLK_SPIN_LOCK)
1345+
__SwitchToThread(0, ++dwSwitchCount);
1346+
}
1347+
else
13611348
__SwitchToThread(0, ++dwSwitchCount);
13621349
}
1363-
else
1364-
__SwitchToThread(0, ++dwSwitchCount);
13651350
}
13661351

1367-
INCONTRACT(Thread* pThread = GetThreadNULLOk());
1368-
INCONTRACT(if (pThread != NULL) pThread->BeginNoTriggerGC(__FILE__, __LINE__));
1369-
}
1370-
#else
1371-
DEBUG_NOINLINE void ObjHeader::EnterSpinLock()
1372-
{
1373-
STATIC_CONTRACT_GC_NOTRIGGER;
1374-
1375-
#ifdef _DEBUG
1376-
int i = 0;
1377-
#endif
1352+
void ReleaseSpinLock(Volatile<DWORD>* pLock)
1353+
{
1354+
LIMITED_METHOD_CONTRACT;
13781355

1379-
DWORD dwSwitchCount = 0;
1356+
InterlockedAnd((LONG*)pLock, ~BIT_SBLK_SPIN_LOCK);
1357+
}
13801358

1381-
while (TRUE)
1359+
struct HeaderSpinLockHolder
13821360
{
1383-
#ifdef _DEBUG
1384-
if (i++ > 10000)
1385-
_ASSERTE(!"ObjHeader::EnterLock timed out");
1386-
#endif
1387-
// get the value so that it doesn't get changed under us.
1388-
LONG curValue = m_SyncBlockValue.LoadWithoutBarrier();
1361+
Volatile<DWORD>* m_pLock;
13891362

1390-
// check if lock taken
1391-
if (! (curValue & BIT_SBLK_SPIN_LOCK))
1363+
HeaderSpinLockHolder(Volatile<DWORD>* pLock)
1364+
: m_pLock(pLock)
13921365
{
1393-
// try to take the lock
1394-
LONG newValue = curValue | BIT_SBLK_SPIN_LOCK;
1395-
LONG result = InterlockedCompareExchange((LONG*)&m_SyncBlockValue, newValue, curValue);
1396-
if (result == curValue)
1397-
break;
1366+
// Acquire the spin-lock in preemptive mode with GC_NOTRIGGER
1367+
// to avoid deadlocks with the GC.
1368+
CONTRACTL
1369+
{
1370+
GC_NOTRIGGER;
1371+
NOTHROW;
1372+
MODE_PREEMPTIVE;
1373+
}
1374+
CONTRACTL_END;
1375+
EnterSpinLock(m_pLock);
13981376
}
1399-
__SwitchToThread(0, ++dwSwitchCount);
1400-
}
1377+
1378+
~HeaderSpinLockHolder()
1379+
{
1380+
LIMITED_METHOD_CONTRACT;
1381+
ReleaseSpinLock(m_pLock);
1382+
}
1383+
};
1384+
}
1385+
#endif //!DACCESS_COMPILE
1386+
1387+
1388+
// ***************************************************************************
1389+
//
1390+
// ObjHeader class implementation
1391+
//
1392+
// ***************************************************************************
1393+
1394+
#ifndef DACCESS_COMPILE
1395+
1396+
1397+
DEBUG_NOINLINE void ObjHeader::EnterSpinLock()
1398+
{
1399+
// NOTE: This function cannot have a dynamic contract. If it does, the contract's
1400+
// destructor will reset the CLR debug state to what it was before entering the
1401+
// function, which will undo the BeginNoTriggerGC() call below.
1402+
STATIC_CONTRACT_GC_NOTRIGGER;
1403+
1404+
::EnterSpinLock(std::addressof(m_SyncBlockValue));
14011405

14021406
INCONTRACT(Thread* pThread = GetThreadNULLOk());
14031407
INCONTRACT(if (pThread != NULL) pThread->BeginNoTriggerGC(__FILE__, __LINE__));
14041408
}
1405-
#endif //MP_LOCKS
1406-
14071409
DEBUG_NOINLINE void ObjHeader::ReleaseSpinLock()
14081410
{
1409-
LIMITED_METHOD_CONTRACT;
1410-
14111411
INCONTRACT(Thread* pThread = GetThreadNULLOk());
14121412
INCONTRACT(if (pThread != NULL) pThread->EndNoTriggerGC());
14131413

1414-
InterlockedAnd((LONG*)&m_SyncBlockValue, ~BIT_SBLK_SPIN_LOCK);
1414+
::ReleaseSpinLock(std::addressof(m_SyncBlockValue));
14151415
}
14161416

1417-
#endif //!DACCESS_COMPILE
1418-
1419-
#ifndef DACCESS_COMPILE
1420-
14211417
DWORD ObjHeader::GetSyncBlockIndex()
14221418
{
14231419
CONTRACTL
@@ -1442,7 +1438,7 @@ DWORD ObjHeader::GetSyncBlockIndex()
14421438
//Try one more time
14431439
if (GetHeaderSyncBlockIndex() == 0)
14441440
{
1445-
ENTER_SPIN_LOCK(this);
1441+
EnterSpinLock();
14461442
// Now the header will be stable - check whether hashcode, appdomain index or lock information is stored in it.
14471443
DWORD bits = GetBits();
14481444
if (((bits & (BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE)) == (BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE)) ||
@@ -1455,7 +1451,7 @@ DWORD ObjHeader::GetSyncBlockIndex()
14551451
{
14561452
SetIndex(BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | SyncBlockCache::GetSyncBlockCache()->NewSyncBlockSlot(GetBaseObject()));
14571453
}
1458-
LEAVE_SPIN_LOCK(this);
1454+
ReleaseSpinLock();
14591455
}
14601456
// SyncBlockCache::LockHolder goes out of scope here
14611457
}
@@ -1621,7 +1617,7 @@ SyncBlock *ObjHeader::GetSyncBlock()
16211617

16221618
{
16231619
// after this point, nobody can update the index in the header
1624-
ENTER_SPIN_LOCK(this);
1620+
EnterSpinLock();
16251621

16261622
{
16271623
// If the thin lock in the header is in use, transfer the information to the syncblock
@@ -1661,7 +1657,7 @@ SyncBlock *ObjHeader::GetSyncBlock()
16611657
if (indexHeld)
16621658
syncBlock->SetPrecious();
16631659

1664-
LEAVE_SPIN_LOCK(this);
1660+
ReleaseSpinLock();
16651661
}
16661662
}
16671663
// SyncBlockCache::LockHolder goes out of scope here
@@ -1716,7 +1712,7 @@ void SyncBlock::InitializeThinLock(DWORD recursionLevel, DWORD threadId)
17161712

17171713
_ASSERTE(m_Lock == (OBJECTHANDLE)NULL);
17181714
_ASSERTE(m_thinLock == 0u);
1719-
m_thinLock = (threadId & SBLK_MASK_LOCK_THREADID) | (recursionLevel << SBLK_RECLEVEL_SHIFT);
1715+
m_thinLock.StoreWithoutBarrier((threadId & SBLK_MASK_LOCK_THREADID) | (recursionLevel << SBLK_RECLEVEL_SHIFT));
17201716
}
17211717

17221718
OBJECTHANDLE SyncBlock::GetOrCreateLock(OBJECTREF lockObj)
@@ -1729,48 +1725,66 @@ OBJECTHANDLE SyncBlock::GetOrCreateLock(OBJECTREF lockObj)
17291725
}
17301726
CONTRACTL_END;
17311727

1732-
if (m_Lock != (OBJECTHANDLE)NULL)
1728+
OBJECTHANDLE existingLock = VolatileLoad(&m_Lock);
1729+
if (existingLock != (OBJECTHANDLE)NULL)
17331730
{
1734-
return m_Lock;
1731+
return existingLock;
17351732
}
17361733

17371734
SetPrecious();
17381735

1739-
// We need to create a new lock
1740-
DWORD thinLock = m_thinLock;
1736+
// We'll likely need to put this lock object into the sync block.
1737+
// Create the handle here.
17411738
OBJECTHANDLEHolder lockHandle = GetAppDomain()->CreateHandle(lockObj);
17421739

1740+
// Switch to preemptive so we can grab the spin-lock.
1741+
// Use the NO_DTOR version so we don't do a coop->preemptive->coop transition on return.
1742+
GCX_PREEMP_NO_DTOR();
1743+
1744+
HeaderSpinLockHolder lock(std::addressof(m_thinLock));
1745+
1746+
// We don't need to be in preemptive any more here.
1747+
GCX_PREEMP_NO_DTOR_END();
1748+
1749+
// Check again now that we hold the spin-lock
1750+
existingLock = m_Lock;
1751+
if (existingLock != (OBJECTHANDLE)NULL)
1752+
{
1753+
return existingLock;
1754+
}
1755+
1756+
// We need to create a new lock
1757+
// Grab the bits that are interesting for thin-lock info.
1758+
// This way we only call back into managed code
1759+
// to initialize the lock when necessary.
1760+
DWORD thinLock = (m_thinLock.LoadWithoutBarrier() & ((SBLK_MASK_LOCK_THREADID) | (SBLK_MASK_LOCK_RECLEVEL)));
1761+
17431762
if (thinLock != 0)
17441763
{
1745-
GCPROTECT_BEGIN(lockObj);
17461764

17471765
// We have thin-lock info that needs to be transferred to the lock object.
17481766
DWORD lockThreadId = thinLock & SBLK_MASK_LOCK_THREADID;
17491767
DWORD recursionLevel = (thinLock & SBLK_MASK_LOCK_RECLEVEL) >> SBLK_RECLEVEL_SHIFT;
17501768
_ASSERTE(lockThreadId != 0);
17511769
PREPARE_NONVIRTUAL_CALLSITE(METHOD__LOCK__INITIALIZE_FOR_MONITOR);
17521770
DECLARE_ARGHOLDER_ARRAY(args, 3);
1753-
args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(lockObj);
1771+
args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(ObjectFromHandle(lockHandle));
17541772
args[ARGNUM_1] = DWORD_TO_ARGHOLDER(lockThreadId);
17551773
args[ARGNUM_2] = DWORD_TO_ARGHOLDER(recursionLevel);
17561774
CALL_MANAGED_METHOD_NORET(args);
1757-
1758-
GCPROTECT_END();
17591775
}
17601776

1761-
OBJECTHANDLE existingHandle = InterlockedCompareExchangeT(&m_Lock, lockHandle.GetValue(), NULL);
1762-
1763-
if (existingHandle != NULL)
1764-
{
1765-
return existingHandle;
1766-
}
1777+
VolatileStore(&m_Lock, lockHandle.GetValue());
17671778

17681779
// Our lock instance is in the sync block now.
17691780
// Don't release it.
17701781
lockHandle.SuppressRelease();
1782+
17711783
// Also, clear the thin lock info.
17721784
// It won't be used any more, but it will look out of date.
1773-
m_thinLock = 0u;
1785+
// Only clear the relevant bits, as the spin-lock bit is used to lock this method.
1786+
// That bit will be reset upon return.
1787+
m_thinLock.StoreWithoutBarrier(m_thinLock.LoadWithoutBarrier() & ~((SBLK_MASK_LOCK_THREADID) | (SBLK_MASK_LOCK_RECLEVEL)));
17741788

17751789
return lockHandle;
17761790
}

src/coreclr/vm/syncblk.h

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ class SyncBlock
444444
public:
445445
SyncBlock(DWORD indx)
446446
: m_Lock((OBJECTHANDLE)NULL)
447-
, m_thinLock(0)
447+
, m_thinLock()
448448
, m_dwSyncIndex(indx)
449449
#ifdef FEATURE_METADATA_UPDATER
450450
, m_pEnCInfo(PTR_NULL)
@@ -967,13 +967,6 @@ struct cdac_data<ObjHeader>
967967

968968
typedef DPTR(class ObjHeader) PTR_ObjHeader;
969969

970-
971-
#define ENTER_SPIN_LOCK(pOh) \
972-
pOh->EnterSpinLock();
973-
974-
#define LEAVE_SPIN_LOCK(pOh) \
975-
pOh->ReleaseSpinLock();
976-
977970
#ifdef TARGET_X86
978971
#include <poppack.h>
979972
#endif // TARGET_X86

0 commit comments

Comments
 (0)