// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// SYNCBLK.CPP
//
//
// Definition of a SyncBlock and the SyncBlockCache which manages it
//
#include "common.h"
#include "vars.hpp"
#include "util.hpp"
#include "class.h"
#include "object.h"
#include "threads.h"
#include "excep.h"
#include "threads.h"
#include "syncblk.h"
#include "interoputil.h"
#include "encee.h"
#include "eventtrace.h"
#include "dllimportcallback.h"
#include "comcallablewrapper.h"
#include "eeconfig.h"
#include "corhost.h"
#include "comdelegate.h"
#include "finalizerthread.h"
#ifdef FEATURE_COMINTEROP
#include "runtimecallablewrapper.h"
#endif // FEATURE_COMINTEROP
// Allocate 4K worth. Typically enough
#define MAXSYNCBLOCK (0x1000-sizeof(void*))/sizeof(SyncBlock)
#define SYNC_TABLE_INITIAL_SIZE 250
//#define DUMP_SB
class SyncBlockArray
{
public:
SyncBlockArray *m_Next;
BYTE m_Blocks[MAXSYNCBLOCK * sizeof (SyncBlock)];
};
// For in-place constructor
BYTE g_SyncBlockCacheInstance[sizeof(SyncBlockCache)];
SPTR_IMPL (SyncBlockCache, SyncBlockCache, s_pSyncBlockCache);
#ifndef DACCESS_COMPILE
#ifndef FEATURE_PAL
// static
SLIST_HEADER InteropSyncBlockInfo::s_InteropInfoStandbyList;
#endif // !FEATURE_PAL
InteropSyncBlockInfo::~InteropSyncBlockInfo()
{
CONTRACTL
{
NOTHROW;
DESTRUCTOR_CHECK;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
FreeUMEntryThunkOrInterceptStub();
}
#ifndef FEATURE_PAL
// Deletes all items in code:s_InteropInfoStandbyList.
void InteropSyncBlockInfo::FlushStandbyList()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
PSLIST_ENTRY pEntry = InterlockedFlushSList(&InteropSyncBlockInfo::s_InteropInfoStandbyList);
while (pEntry)
{
PSLIST_ENTRY pNextEntry = pEntry->Next;
// make sure to use the global delete since the destructor has already run
::delete (void *)pEntry;
pEntry = pNextEntry;
}
}
#endif // !FEATURE_PAL
void InteropSyncBlockInfo::FreeUMEntryThunkOrInterceptStub()
{
CONTRACTL
{
NOTHROW;
DESTRUCTOR_CHECK;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END
if (!g_fEEShutDown)
{
void *pUMEntryThunk = GetUMEntryThunk();
if (pUMEntryThunk != NULL)
{
COMDelegate::RemoveEntryFromFPtrHash((UPTR)pUMEntryThunk);
UMEntryThunk::FreeUMEntryThunk((UMEntryThunk *)pUMEntryThunk);
}
else
{
#if defined(_TARGET_X86_)
Stub *pInterceptStub = GetInterceptStub();
if (pInterceptStub != NULL)
{
// There may be multiple chained stubs
pInterceptStub->DecRef();
}
#else // _TARGET_X86_
// Intercept stubs are currently not used on other platforms.
_ASSERTE(GetInterceptStub() == NULL);
#endif // _TARGET_X86_
}
}
m_pUMEntryThunkOrInterceptStub = NULL;
}
#ifdef FEATURE_COMINTEROP
// Returns either NULL or an RCW on which AcquireLock has been called.
RCW* InteropSyncBlockInfo::GetRCWAndIncrementUseCount()
{
LIMITED_METHOD_CONTRACT;
DWORD dwSwitchCount = 0;
while (true)
{
RCW *pRCW = VolatileLoad(&m_pRCW);
if ((size_t)pRCW <= 0x1)
{
// the RCW never existed or has been released
return NULL;
}
if (((size_t)pRCW & 0x1) == 0x0)
{
// it looks like we have a chance, try to acquire the lock
RCW *pLockedRCW = (RCW *)((size_t)pRCW | 0x1);
if (InterlockedCompareExchangeT(&m_pRCW, pLockedRCW, pRCW) == pRCW)
{
// we have the lock on the m_pRCW field, now we can safely "use" the RCW
pRCW->IncrementUseCount();
// release the m_pRCW lock
VolatileStore(&m_pRCW, pRCW);
// and return the RCW
return pRCW;
}
}
// somebody else holds the lock, retry
__SwitchToThread(0, ++dwSwitchCount);
}
}
// Sets the m_pRCW field in a thread-safe manner, pRCW can be NULL.
void InteropSyncBlockInfo::SetRawRCW(RCW* pRCW)
{
LIMITED_METHOD_CONTRACT;
if (pRCW != NULL)
{
// we never set two different RCWs on a single object
_ASSERTE(m_pRCW == NULL);
m_pRCW = pRCW;
}
else
{
DWORD dwSwitchCount = 0;
while (true)
{
RCW *pOldRCW = VolatileLoad(&m_pRCW);
if ((size_t)pOldRCW <= 0x1)
{
// the RCW never existed or has been released
VolatileStore(&m_pRCW, (RCW *)0x1);
return;
}
if (((size_t)pOldRCW & 0x1) == 0x0)
{
// it looks like we have a chance, set the RCW to 0x1
if (InterlockedCompareExchangeT(&m_pRCW, (RCW *)0x1, pOldRCW) == pOldRCW)
{
// we made it
return;
}
}
// somebody else holds the lock, retry
__SwitchToThread(0, ++dwSwitchCount);
}
}
}
#endif // FEATURE_COMINTEROP
#endif // !DACCESS_COMPILE
PTR_SyncTableEntry SyncTableEntry::GetSyncTableEntry()
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
return (PTR_SyncTableEntry)g_pSyncTable;
}
#ifndef DACCESS_COMPILE
SyncTableEntry*& SyncTableEntry::GetSyncTableEntryByRef()
{
LIMITED_METHOD_CONTRACT;
return g_pSyncTable;
}
/* static */
SyncBlockCache*& SyncBlockCache::GetSyncBlockCache()
{
LIMITED_METHOD_CONTRACT;
return s_pSyncBlockCache;
}
//----------------------------------------------------------------------------
//
// ThreadQueue Implementation
//
//----------------------------------------------------------------------------
#endif //!DACCESS_COMPILE
// Given a link in the chain, get the Thread that it represents
/* static */
inline PTR_WaitEventLink ThreadQueue::WaitEventLinkForLink(PTR_SLink pLink)
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
return (PTR_WaitEventLink) (((PTR_BYTE) pLink) - offsetof(WaitEventLink, m_LinkSB));
}
#ifndef DACCESS_COMPILE
// Unlink the head of the Q. We are always in the SyncBlock's critical
// section.
/* static */
inline WaitEventLink *ThreadQueue::DequeueThread(SyncBlock *psb)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
CAN_TAKE_LOCK;
}
CONTRACTL_END;
// Be careful, the debugger inspects the queue from out of process and just looks at the memory...
// it must be valid even if the lock is held. Be careful if you change the way the queue is updated.
SyncBlockCache::LockHolder lh(SyncBlockCache::GetSyncBlockCache());
WaitEventLink *ret = NULL;
SLink *pLink = psb->m_Link.m_pNext;
if (pLink)
{
psb->m_Link.m_pNext = pLink->m_pNext;
#ifdef _DEBUG
pLink->m_pNext = (SLink *)POISONC;
#endif
ret = WaitEventLinkForLink(pLink);
_ASSERTE(ret->m_WaitSB == psb);
}
return ret;
}
// Enqueue is the slow one. We have to find the end of the Q since we don't
// want to burn storage for this in the SyncBlock.
/* static */
inline void ThreadQueue::EnqueueThread(WaitEventLink *pWaitEventLink, SyncBlock *psb)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
CAN_TAKE_LOCK;
}
CONTRACTL_END;
_ASSERTE (pWaitEventLink->m_LinkSB.m_pNext == NULL);
// Be careful, the debugger inspects the queue from out of process and just looks at the memory...
// it must be valid even if the lock is held. Be careful if you change the way the queue is updated.
SyncBlockCache::LockHolder lh(SyncBlockCache::GetSyncBlockCache());
SLink *pPrior = &psb->m_Link;
while (pPrior->m_pNext)
{
// We shouldn't already be in the waiting list!
_ASSERTE(pPrior->m_pNext != &pWaitEventLink->m_LinkSB);
pPrior = pPrior->m_pNext;
}
pPrior->m_pNext = &pWaitEventLink->m_LinkSB;
}
// Wade through the SyncBlock's list of waiting threads and remove the
// specified thread.
/* static */
BOOL ThreadQueue::RemoveThread (Thread *pThread, SyncBlock *psb)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
BOOL res = FALSE;
// Be careful, the debugger inspects the queue from out of process and just looks at the memory...
// it must be valid even if the lock is held. Be careful if you change the way the queue is updated.
SyncBlockCache::LockHolder lh(SyncBlockCache::GetSyncBlockCache());
SLink *pPrior = &psb->m_Link;
SLink *pLink;
WaitEventLink *pWaitEventLink;
while ((pLink = pPrior->m_pNext) != NULL)
{
pWaitEventLink = WaitEventLinkForLink(pLink);
if (pWaitEventLink->m_Thread == pThread)
{
pPrior->m_pNext = pLink->m_pNext;
#ifdef _DEBUG
pLink->m_pNext = (SLink *)POISONC;
#endif
_ASSERTE(pWaitEventLink->m_WaitSB == psb);
res = TRUE;
break;
}
pPrior = pLink;
}
return res;
}
#endif //!DACCESS_COMPILE
#ifdef DACCESS_COMPILE
// Enumerates the threads in the queue from front to back by calling
// pCallbackFunction on each one
/* static */
void ThreadQueue::EnumerateThreads(SyncBlock *psb, FP_TQ_THREAD_ENUMERATION_CALLBACK pCallbackFunction, void* pUserData)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
SUPPORTS_DAC;
PTR_SLink pLink = psb->m_Link.m_pNext;
PTR_WaitEventLink pWaitEventLink;
while (pLink != NULL)
{
pWaitEventLink = WaitEventLinkForLink(pLink);
pCallbackFunction(pWaitEventLink->m_Thread, pUserData);
pLink = pLink->m_pNext;
}
}
#endif //DACCESS_COMPILE
#ifndef DACCESS_COMPILE
// ***************************************************************************
//
// Ephemeral Bitmap Helper
//
// ***************************************************************************
#define card_size 32
#define card_word_width 32
size_t CardIndex (size_t card)
{
LIMITED_METHOD_CONTRACT;
return card_size * card;
}
size_t CardOf (size_t idx)
{
LIMITED_METHOD_CONTRACT;
return idx / card_size;
}
size_t CardWord (size_t card)
{
LIMITED_METHOD_CONTRACT;
return card / card_word_width;
}
inline
unsigned CardBit (size_t card)
{
LIMITED_METHOD_CONTRACT;
return (unsigned)(card % card_word_width);
}
inline
void SyncBlockCache::SetCard (size_t card)
{
WRAPPER_NO_CONTRACT;
m_EphemeralBitmap [CardWord (card)] =
(m_EphemeralBitmap [CardWord (card)] | (1 << CardBit (card)));
}
inline
void SyncBlockCache::ClearCard (size_t card)
{
WRAPPER_NO_CONTRACT;
m_EphemeralBitmap [CardWord (card)] =
(m_EphemeralBitmap [CardWord (card)] & ~(1 << CardBit (card)));
}
inline
BOOL SyncBlockCache::CardSetP (size_t card)
{
WRAPPER_NO_CONTRACT;
return ( m_EphemeralBitmap [ CardWord (card) ] & (1 << CardBit (card)));
}
inline
void SyncBlockCache::CardTableSetBit (size_t idx)
{
WRAPPER_NO_CONTRACT;
SetCard (CardOf (idx));
}
size_t BitMapSize (size_t cacheSize)
{
LIMITED_METHOD_CONTRACT;
return (cacheSize + card_size * card_word_width - 1)/ (card_size * card_word_width);
}
// ***************************************************************************
//
// SyncBlockCache class implementation
//
// ***************************************************************************
SyncBlockCache::SyncBlockCache()
: m_pCleanupBlockList(NULL),
m_FreeBlockList(NULL),
// NOTE: CRST_UNSAFE_ANYMODE prevents a GC mode switch when entering this crst.
// If you remove this flag, we will switch to preemptive mode when entering
// g_criticalSection, which means all functions that enter it will become
// GC_TRIGGERS. (This includes all uses of LockHolder around SyncBlockCache::GetSyncBlockCache().
// So be sure to update the contracts if you remove this flag.
m_CacheLock(CrstSyncBlockCache, (CrstFlags) (CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD)),
m_FreeCount(0),
m_ActiveCount(0),
m_SyncBlocks(0),
m_FreeSyncBlock(0),
m_FreeSyncTableIndex(1),
m_FreeSyncTableList(0),
m_SyncTableSize(SYNC_TABLE_INITIAL_SIZE),
m_OldSyncTables(0),
m_bSyncBlockCleanupInProgress(FALSE),
m_EphemeralBitmap(0)
{
CONTRACTL
{
CONSTRUCTOR_CHECK;
THROWS;
GC_NOTRIGGER;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
}
CONTRACTL_END;
}
// This method is NO longer called.
SyncBlockCache::~SyncBlockCache()
{
CONTRACTL
{
DESTRUCTOR_CHECK;
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
// Clear the list the fast way.
m_FreeBlockList = NULL;
//@todo we can clear this fast too I guess
m_pCleanupBlockList = NULL;
// destruct all arrays
while (m_SyncBlocks)
{
SyncBlockArray *next = m_SyncBlocks->m_Next;
delete m_SyncBlocks;
m_SyncBlocks = next;
}
// Also, now is a good time to clean up all the old tables which we discarded
// when we overflowed them.
SyncTableEntry* arr;
while ((arr = m_OldSyncTables) != 0)
{
m_OldSyncTables = (SyncTableEntry*)arr[0].m_Object.Load();
delete arr;
}
}
// When the GC determines that an object is dead the low bit of the
// m_Object field of SyncTableEntry is set, however it is not
// cleaned up because we cant do the COM interop cleanup at GC time.
// It is put on a cleanup list and at a later time (typically during
// finalization, this list is cleaned up.
//
void SyncBlockCache::CleanupSyncBlocks()
{
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_MODE_COOPERATIVE;
_ASSERTE(GetThread() == FinalizerThread::GetFinalizerThread());
// Set the flag indicating sync block cleanup is in progress.
// IMPORTANT: This must be set before the sync block cleanup bit is reset on the thread.
m_bSyncBlockCleanupInProgress = TRUE;
struct Param
{
SyncBlockCache *pThis;
SyncBlock* psb;
#ifdef FEATURE_COMINTEROP
RCW* pRCW;
#endif
} param;
param.pThis = this;
param.psb = NULL;
#ifdef FEATURE_COMINTEROP
param.pRCW = NULL;
#endif
EE_TRY_FOR_FINALLY(Param *, pParam, ¶m)
{
// reset the flag
FinalizerThread::GetFinalizerThread()->ResetSyncBlockCleanup();
// walk the cleanup list and cleanup 'em up
while ((pParam->psb = pParam->pThis->GetNextCleanupSyncBlock()) != NULL)
{
#ifdef FEATURE_COMINTEROP
InteropSyncBlockInfo* pInteropInfo = pParam->psb->GetInteropInfoNoCreate();
if (pInteropInfo)
{
pParam->pRCW = pInteropInfo->GetRawRCW();
if (pParam->pRCW)
{
// We should have initialized the cleanup list with the
// first RCW cache we created
_ASSERTE(g_pRCWCleanupList != NULL);
g_pRCWCleanupList->AddWrapper(pParam->pRCW);
pParam->pRCW = NULL;
pInteropInfo->SetRawRCW(NULL);
}
}
#endif // FEATURE_COMINTEROP
// Delete the sync block.
pParam->pThis->DeleteSyncBlock(pParam->psb);
pParam->psb = NULL;
// pulse GC mode to allow GC to perform its work
if (FinalizerThread::GetFinalizerThread()->CatchAtSafePointOpportunistic())
{
FinalizerThread::GetFinalizerThread()->PulseGCMode();
}
}
#ifdef FEATURE_COMINTEROP
// Now clean up the rcw's sorted by context
if (g_pRCWCleanupList != NULL)
g_pRCWCleanupList->CleanupAllWrappers();
#endif // FEATURE_COMINTEROP
}
EE_FINALLY
{
// We are finished cleaning up the sync blocks.
m_bSyncBlockCleanupInProgress = FALSE;
#ifdef FEATURE_COMINTEROP
if (param.pRCW)
param.pRCW->Cleanup();
#endif
if (param.psb)
DeleteSyncBlock(param.psb);
} EE_END_FINALLY;
}
// create the sync block cache
/* static */
void SyncBlockCache::Attach()
{
LIMITED_METHOD_CONTRACT;
}
// create the sync block cache
/* static */
void SyncBlockCache::Start()
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
DWORD* bm = new DWORD [BitMapSize(SYNC_TABLE_INITIAL_SIZE+1)];
memset (bm, 0, BitMapSize (SYNC_TABLE_INITIAL_SIZE+1)*sizeof(DWORD));
SyncTableEntry::GetSyncTableEntryByRef() = new SyncTableEntry[SYNC_TABLE_INITIAL_SIZE+1];
#ifdef _DEBUG
for (int i=0; im_EphemeralBitmap = bm;
#ifndef FEATURE_PAL
InitializeSListHead(&InteropSyncBlockInfo::s_InteropInfoStandbyList);
#endif // !FEATURE_PAL
}
// destroy the sync block cache
/* static */
void SyncBlockCache::Stop()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
// cache must be destroyed first, since it can traverse the table to find all the
// sync blocks which are live and thus must have their critical sections destroyed.
if (SyncBlockCache::GetSyncBlockCache())
{
delete SyncBlockCache::GetSyncBlockCache();
SyncBlockCache::GetSyncBlockCache() = 0;
}
if (SyncTableEntry::GetSyncTableEntry())
{
delete SyncTableEntry::GetSyncTableEntry();
SyncTableEntry::GetSyncTableEntryByRef() = 0;
}
}
void SyncBlockCache::InsertCleanupSyncBlock(SyncBlock* psb)
{
CONTRACTL
{
INSTANCE_CHECK;
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
// free up the threads that are waiting before we use the link
// for other purposes
if (psb->m_Link.m_pNext != NULL)
{
while (ThreadQueue::DequeueThread(psb) != NULL)
continue;
}
#ifdef FEATURE_COMINTEROP
if (psb->m_pInteropInfo)
{
// called during GC
// so do only minorcleanup
MinorCleanupSyncBlockComData(psb->m_pInteropInfo);
}
#endif // FEATURE_COMINTEROP
// This method will be called only by the GC thread
//@todo add an assert for the above statement
// we don't need to lock here
//EnterCacheLock();
psb->m_Link.m_pNext = m_pCleanupBlockList;
m_pCleanupBlockList = &psb->m_Link;
// we don't need a lock here
//LeaveCacheLock();
}
SyncBlock* SyncBlockCache::GetNextCleanupSyncBlock()
{
LIMITED_METHOD_CONTRACT;
// we don't need a lock here,
// as this is called only on the finalizer thread currently
SyncBlock *psb = NULL;
if (m_pCleanupBlockList)
{
// get the actual sync block pointer
psb = (SyncBlock *) (((BYTE *) m_pCleanupBlockList) - offsetof(SyncBlock, m_Link));
m_pCleanupBlockList = m_pCleanupBlockList->m_pNext;
}
return psb;
}
// returns and removes the next free syncblock from the list
// the cache lock must be entered to call this
SyncBlock *SyncBlockCache::GetNextFreeSyncBlock()
{
CONTRACTL
{
INJECT_FAULT(COMPlusThrowOM());
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
#ifdef _DEBUG // Instrumentation for OOM fault injection testing
delete new char;
#endif
SyncBlock *psb;
SLink *plst = m_FreeBlockList;
m_ActiveCount++;
if (plst)
{
m_FreeBlockList = m_FreeBlockList->m_pNext;
// shouldn't be 0
m_FreeCount--;
// get the actual sync block pointer
psb = (SyncBlock *) (((BYTE *) plst) - offsetof(SyncBlock, m_Link));
return psb;
}
else
{
if ((m_SyncBlocks == NULL) || (m_FreeSyncBlock >= MAXSYNCBLOCK))
{
#ifdef DUMP_SB
// LogSpewAlways("Allocating new syncblock array\n");
// DumpSyncBlockCache();
#endif
SyncBlockArray* newsyncblocks = new(SyncBlockArray);
if (!newsyncblocks)
COMPlusThrowOM ();
newsyncblocks->m_Next = m_SyncBlocks;
m_SyncBlocks = newsyncblocks;
m_FreeSyncBlock = 0;
}
return &(((SyncBlock*)m_SyncBlocks->m_Blocks)[m_FreeSyncBlock++]);
}
}
void SyncBlockCache::Grow()
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_NOTRIGGER;
MODE_COOPERATIVE;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
STRESS_LOG0(LF_SYNC, LL_INFO10000, "SyncBlockCache::NewSyncBlockSlot growing SyncBlockCache \n");
NewArrayHolder newSyncTable (NULL);
NewArrayHolder newBitMap (NULL);
DWORD * oldBitMap;
// Compute the size of the new synctable. Normally, we double it - unless
// doing so would create slots with indices too high to fit within the
// mask. If so, we create a synctable up to the mask limit. If we're
// already at the mask limit, then caller is out of luck.
DWORD newSyncTableSize;
if (m_SyncTableSize <= (MASK_SYNCBLOCKINDEX >> 1))
{
newSyncTableSize = m_SyncTableSize * 2;
}
else
{
newSyncTableSize = MASK_SYNCBLOCKINDEX;
}
if (!(newSyncTableSize > m_SyncTableSize)) // Make sure we actually found room to grow!
{
COMPlusThrowOM();
}
newSyncTable = new SyncTableEntry[newSyncTableSize];
newBitMap = new DWORD[BitMapSize (newSyncTableSize)];
{
//! From here on, we assume that we will succeed and start doing global side-effects.
//! Any operation that could fail must occur before this point.
CANNOTTHROWCOMPLUSEXCEPTION();
FAULT_FORBID();
newSyncTable.SuppressRelease();
newBitMap.SuppressRelease();
// We chain old table because we can't delete
// them before all the threads are stoppped
// (next GC)
SyncTableEntry::GetSyncTableEntry() [0].m_Object = (Object *)m_OldSyncTables;
m_OldSyncTables = SyncTableEntry::GetSyncTableEntry();
memset (newSyncTable, 0, newSyncTableSize*sizeof (SyncTableEntry));
memset (newBitMap, 0, BitMapSize (newSyncTableSize)*sizeof (DWORD));
CopyMemory (newSyncTable, SyncTableEntry::GetSyncTableEntry(),
m_SyncTableSize*sizeof (SyncTableEntry));
CopyMemory (newBitMap, m_EphemeralBitmap,
BitMapSize (m_SyncTableSize)*sizeof (DWORD));
oldBitMap = m_EphemeralBitmap;
m_EphemeralBitmap = newBitMap;
delete[] oldBitMap;
_ASSERTE((m_SyncTableSize & MASK_SYNCBLOCKINDEX) == m_SyncTableSize);
// note: we do not care if another thread does not see the new size
// however we really do not want it to see the new size without seeing the new array
//@TODO do we still leak here if two threads come here at the same time ?
FastInterlockExchangePointer(&SyncTableEntry::GetSyncTableEntryByRef(), newSyncTable.GetValue());
m_FreeSyncTableIndex++;
m_SyncTableSize = newSyncTableSize;
#ifdef _DEBUG
static int dumpSBOnResize = -1;
if (dumpSBOnResize == -1)
dumpSBOnResize = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SBDumpOnResize);
if (dumpSBOnResize)
{
LogSpewAlways("SyncBlockCache resized\n");
DumpSyncBlockCache();
}
#endif
}
}
DWORD SyncBlockCache::NewSyncBlockSlot(Object *obj)
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_NOTRIGGER;
MODE_COOPERATIVE;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
_ASSERTE(m_CacheLock.OwnedByCurrentThread()); // GetSyncBlock takes the lock, make sure no one else does.
DWORD indexNewEntry;
if (m_FreeSyncTableList)
{
indexNewEntry = (DWORD)(m_FreeSyncTableList >> 1);
_ASSERTE ((size_t)SyncTableEntry::GetSyncTableEntry()[indexNewEntry].m_Object.Load() & 1);
m_FreeSyncTableList = (size_t)SyncTableEntry::GetSyncTableEntry()[indexNewEntry].m_Object.Load() & ~1;
}
else if ((indexNewEntry = (DWORD)(m_FreeSyncTableIndex)) >= m_SyncTableSize)
{
// This is kept out of line to keep stuff like the C++ EH prolog (needed for holders) off
// of the common path.
Grow();
}
else
{
#ifdef _DEBUG
static int dumpSBOnNewIndex = -1;
if (dumpSBOnNewIndex == -1)
dumpSBOnNewIndex = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SBDumpOnNewIndex);
if (dumpSBOnNewIndex)
{
LogSpewAlways("SyncBlockCache index incremented\n");
DumpSyncBlockCache();
}
#endif
m_FreeSyncTableIndex ++;
}
CardTableSetBit (indexNewEntry);
// In debug builds the m_SyncBlock at indexNewEntry should already be null, since we should
// start out with a null table and always null it out on delete.
_ASSERTE(SyncTableEntry::GetSyncTableEntry() [indexNewEntry].m_SyncBlock == NULL);
SyncTableEntry::GetSyncTableEntry() [indexNewEntry].m_SyncBlock = NULL;
SyncTableEntry::GetSyncTableEntry() [indexNewEntry].m_Object = obj;
_ASSERTE(indexNewEntry != 0);
return indexNewEntry;
}
// free a used sync block, only called from CleanupSyncBlocks.
void SyncBlockCache::DeleteSyncBlock(SyncBlock *psb)
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
}
CONTRACTL_END;
// clean up comdata
if (psb->m_pInteropInfo)
{
#ifdef FEATURE_COMINTEROP
CleanupSyncBlockComData(psb->m_pInteropInfo);
#endif // FEATURE_COMINTEROP
#ifndef FEATURE_PAL
if (g_fEEShutDown)
{
delete psb->m_pInteropInfo;
}
else
{
psb->m_pInteropInfo->~InteropSyncBlockInfo();
InterlockedPushEntrySList(&InteropSyncBlockInfo::s_InteropInfoStandbyList, (PSLIST_ENTRY)psb->m_pInteropInfo);
}
#else // !FEATURE_PAL
delete psb->m_pInteropInfo;
#endif // !FEATURE_PAL
}
#ifdef EnC_SUPPORTED
// clean up EnC info
if (psb->m_pEnCInfo)
psb->m_pEnCInfo->Cleanup();
#endif // EnC_SUPPORTED
// Destruct the SyncBlock, but don't reclaim its memory. (Overridden
// operator delete).
delete psb;
//synchronizer with the consumers,
// @todo we don't really need a lock here, we can come up
// with some simple algo to avoid taking a lock
{
SyncBlockCache::LockHolder lh(this);
DeleteSyncBlockMemory(psb);
}
}
// returns the sync block memory to the free pool but does not destruct sync block (must own cache lock already)
void SyncBlockCache::DeleteSyncBlockMemory(SyncBlock *psb)
{
CONTRACTL
{
INSTANCE_CHECK;
NOTHROW;
GC_NOTRIGGER;
FORBID_FAULT;
}
CONTRACTL_END
m_ActiveCount--;
m_FreeCount++;
psb->m_Link.m_pNext = m_FreeBlockList;
m_FreeBlockList = &psb->m_Link;
}
// free a used sync block
void SyncBlockCache::GCDeleteSyncBlock(SyncBlock *psb)
{
CONTRACTL
{
INSTANCE_CHECK;
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
// Destruct the SyncBlock, but don't reclaim its memory. (Overridden
// operator delete).
delete psb;
m_ActiveCount--;
m_FreeCount++;
psb->m_Link.m_pNext = m_FreeBlockList;
m_FreeBlockList = &psb->m_Link;
}
void SyncBlockCache::GCWeakPtrScan(HANDLESCANPROC scanProc, uintptr_t lp1, uintptr_t lp2)
{
CONTRACTL
{
INSTANCE_CHECK;
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
// First delete the obsolete arrays since we have exclusive access
BOOL fSetSyncBlockCleanup = FALSE;
SyncTableEntry* arr;
while ((arr = m_OldSyncTables) != NULL)
{
m_OldSyncTables = (SyncTableEntry*)arr[0].m_Object.Load();
delete arr;
}
#ifdef DUMP_SB
LogSpewAlways("GCWeakPtrScan starting\n");
#endif
#ifdef VERIFY_HEAP
if (g_pConfig->GetHeapVerifyLevel()& EEConfig::HEAPVERIFY_SYNCBLK)
STRESS_LOG0 (LF_GC | LF_SYNC, LL_INFO100, "GCWeakPtrScan starting\n");
#endif
if (GCHeapUtilities::GetGCHeap()->GetCondemnedGeneration() < GCHeapUtilities::GetGCHeap()->GetMaxGeneration())
{
#ifdef VERIFY_HEAP
//for VSW 294550: we saw stale obeject reference in SyncBlkCache, so we want to make sure the card
//table logic above works correctly so that every ephemeral entry is promoted.
//For verification, we make a copy of the sync table in relocation phase and promote it use the
//slow approach and compare the result with the original one
DWORD freeSyncTalbeIndexCopy = m_FreeSyncTableIndex;
SyncTableEntry * syncTableShadow = NULL;
if ((g_pConfig->GetHeapVerifyLevel()& EEConfig::HEAPVERIFY_SYNCBLK) && !((ScanContext*)lp1)->promotion)
{
syncTableShadow = new(nothrow) SyncTableEntry [m_FreeSyncTableIndex];
if (syncTableShadow)
{
memcpy (syncTableShadow, SyncTableEntry::GetSyncTableEntry(), m_FreeSyncTableIndex * sizeof (SyncTableEntry));
}
}
#endif //VERIFY_HEAP
//scan the bitmap
size_t dw = 0;
while (1)
{
while (dw < BitMapSize (m_SyncTableSize) && (m_EphemeralBitmap[dw]==0))
{
dw++;
}
if (dw < BitMapSize (m_SyncTableSize))
{
//found one
for (int i = 0; i < card_word_width; i++)
{
size_t card = i+dw*card_word_width;
if (CardSetP (card))
{
BOOL clear_card = TRUE;
for (int idx = 0; idx < card_size; idx++)
{
size_t nb = CardIndex (card) + idx;
if (( nb < m_FreeSyncTableIndex) && (nb > 0))
{
Object* o = SyncTableEntry::GetSyncTableEntry()[nb].m_Object;
if (o && !((size_t)o & 1))
{
if (GCHeapUtilities::GetGCHeap()->IsEphemeral (o))
{
clear_card = FALSE;
GCWeakPtrScanElement ((int)nb, scanProc,
lp1, lp2, fSetSyncBlockCleanup);
}
}
}
}
if (clear_card)
ClearCard (card);
}
}
dw++;
}
else
break;
}
#ifdef VERIFY_HEAP
//for VSW 294550: we saw stale obeject reference in SyncBlkCache, so we want to make sure the card
//table logic above works correctly so that every ephemeral entry is promoted. To verify, we make a
//copy of the sync table and promote it use the slow approach and compare the result with the real one
if (g_pConfig->GetHeapVerifyLevel()& EEConfig::HEAPVERIFY_SYNCBLK)
{
if (syncTableShadow)
{
for (DWORD nb = 1; nb < m_FreeSyncTableIndex; nb++)
{
Object **keyv = (Object **) &syncTableShadow[nb].m_Object;
if (((size_t) *keyv & 1) == 0)
{
(*scanProc) (keyv, NULL, lp1, lp2);
SyncBlock *pSB = syncTableShadow[nb].m_SyncBlock;
if (*keyv != 0 && (!pSB || !pSB->IsIDisposable()))
{
if (syncTableShadow[nb].m_Object != SyncTableEntry::GetSyncTableEntry()[nb].m_Object)
DebugBreak ();
}
}
}
delete []syncTableShadow;
syncTableShadow = NULL;
}
if (freeSyncTalbeIndexCopy != m_FreeSyncTableIndex)
DebugBreak ();
}
#endif //VERIFY_HEAP
}
else
{
for (DWORD nb = 1; nb < m_FreeSyncTableIndex; nb++)
{
GCWeakPtrScanElement (nb, scanProc, lp1, lp2, fSetSyncBlockCleanup);
}
}
if (fSetSyncBlockCleanup)
{
// mark the finalizer thread saying requires cleanup
FinalizerThread::GetFinalizerThread()->SetSyncBlockCleanup();
FinalizerThread::EnableFinalization();
}
#if defined(VERIFY_HEAP)
if (g_pConfig->GetHeapVerifyLevel() & EEConfig::HEAPVERIFY_GC)
{
if (((ScanContext*)lp1)->promotion)
{
for (int nb = 1; nb < (int)m_FreeSyncTableIndex; nb++)
{
Object* o = SyncTableEntry::GetSyncTableEntry()[nb].m_Object;
if (((size_t)o & 1) == 0)
{
o->Validate();
}
}
}
}
#endif // VERIFY_HEAP
}
/* Scan the weak pointers in the SyncBlockEntry and report them to the GC. If the
reference is dead, then return TRUE */
BOOL SyncBlockCache::GCWeakPtrScanElement (int nb, HANDLESCANPROC scanProc, LPARAM lp1, LPARAM lp2,
BOOL& cleanup)
{
CONTRACTL
{
INSTANCE_CHECK;
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
Object **keyv = (Object **) &SyncTableEntry::GetSyncTableEntry()[nb].m_Object;
#ifdef DUMP_SB
struct Param
{
Object **keyv;
char *name;
} param;
param.keyv = keyv;
PAL_TRY(Param *, pParam, ¶m) {
if (! *pParam->keyv)
pParam->name = "null";
else if ((size_t) *pParam->keyv & 1)
pParam->name = "free";
else {
pParam->name = (*pParam->keyv)->GetClass()->GetDebugClassName();
if (strlen(pParam->name) == 0)
pParam->name = "";
}
} PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
param.name = "";
}
PAL_ENDTRY
LogSpewAlways("[%4.4d]: %8.8x, %s\n", nb, *keyv, param.name);
#endif
if (((size_t) *keyv & 1) == 0)
{
#ifdef VERIFY_HEAP
if (g_pConfig->GetHeapVerifyLevel () & EEConfig::HEAPVERIFY_SYNCBLK)
{
STRESS_LOG3 (LF_GC | LF_SYNC, LL_INFO100000, "scanning syncblk[%d, %p, %p]\n", nb, (size_t)SyncTableEntry::GetSyncTableEntry()[nb].m_SyncBlock, (size_t)*keyv);
}
#endif
(*scanProc) (keyv, NULL, lp1, lp2);
SyncBlock *pSB = SyncTableEntry::GetSyncTableEntry()[nb].m_SyncBlock;
if ((*keyv == 0 ) || (pSB && pSB->IsIDisposable()))
{
#ifdef VERIFY_HEAP
if (g_pConfig->GetHeapVerifyLevel () & EEConfig::HEAPVERIFY_SYNCBLK)
{
STRESS_LOG3 (LF_GC | LF_SYNC, LL_INFO100000, "freeing syncblk[%d, %p, %p]\n", nb, (size_t)pSB, (size_t)*keyv);
}
#endif
if (*keyv)
{
_ASSERTE (pSB);
GCDeleteSyncBlock(pSB);
//clean the object syncblock header
((Object*)(*keyv))->GetHeader()->GCResetIndex();
}
else if (pSB)
{
cleanup = TRUE;
// insert block into cleanup list
InsertCleanupSyncBlock (SyncTableEntry::GetSyncTableEntry()[nb].m_SyncBlock);
#ifdef DUMP_SB
LogSpewAlways(" Cleaning up block at %4.4d\n", nb);
#endif
}
// delete the entry
#ifdef DUMP_SB
LogSpewAlways(" Deleting block at %4.4d\n", nb);
#endif
SyncTableEntry::GetSyncTableEntry()[nb].m_Object = (Object *)(m_FreeSyncTableList | 1);
m_FreeSyncTableList = nb << 1;
SyncTableEntry::GetSyncTableEntry()[nb].m_SyncBlock = NULL;
return TRUE;
}
else
{
#ifdef DUMP_SB
LogSpewAlways(" Keeping block at %4.4d with oref %8.8x\n", nb, *keyv);
#endif
}
}
return FALSE;
}
void SyncBlockCache::GCDone(BOOL demoting, int max_gen)
{
CONTRACTL
{
INSTANCE_CHECK;
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
if (demoting &&
(GCHeapUtilities::GetGCHeap()->GetCondemnedGeneration() ==
GCHeapUtilities::GetGCHeap()->GetMaxGeneration()))
{
//scan the bitmap
size_t dw = 0;
while (1)
{
while (dw < BitMapSize (m_SyncTableSize) &&
(m_EphemeralBitmap[dw]==(DWORD)~0))
{
dw++;
}
if (dw < BitMapSize (m_SyncTableSize))
{
//found one
for (int i = 0; i < card_word_width; i++)
{
size_t card = i+dw*card_word_width;
if (!CardSetP (card))
{
for (int idx = 0; idx < card_size; idx++)
{
size_t nb = CardIndex (card) + idx;
if (( nb < m_FreeSyncTableIndex) && (nb > 0))
{
Object* o = SyncTableEntry::GetSyncTableEntry()[nb].m_Object;
if (o && !((size_t)o & 1))
{
if (GCHeapUtilities::GetGCHeap()->WhichGeneration (o) < (unsigned int)max_gen)
{
SetCard (card);
break;
}
}
}
}
}
}
dw++;
}
else
break;
}
}
}
#if defined (VERIFY_HEAP)
#ifndef _DEBUG
#ifdef _ASSERTE
#undef _ASSERTE
#endif
#define _ASSERTE(c) if (!(c)) DebugBreak()
#endif
void SyncBlockCache::VerifySyncTableEntry()
{
CONTRACTL
{
INSTANCE_CHECK;
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
for (DWORD nb = 1; nb < m_FreeSyncTableIndex; nb++)
{
Object* o = SyncTableEntry::GetSyncTableEntry()[nb].m_Object;
// if the slot was just allocated, the object may still be null
if (o && (((size_t)o & 1) == 0))
{
//there is no need to verify next object's header because this is called
//from verify_heap, which will verify every object anyway
o->Validate(TRUE, FALSE);
//
// This loop is just a heuristic to try to catch errors, but it is not 100%.
// To prevent false positives, we weaken our assert below to exclude the case
// where the index is still NULL, but we've reached the end of our loop.
//
static const DWORD max_iterations = 100;
DWORD loop = 0;
for (; loop < max_iterations; loop++)
{
// The syncblock index may be updating by another thread.
if (o->GetHeader()->GetHeaderSyncBlockIndex() != 0)
{
break;
}
__SwitchToThread(0, CALLER_LIMITS_SPINNING);
}
DWORD idx = o->GetHeader()->GetHeaderSyncBlockIndex();
_ASSERTE(idx == nb || ((0 == idx) && (loop == max_iterations)));
_ASSERTE(!GCHeapUtilities::GetGCHeap()->IsEphemeral(o) || CardSetP(CardOf(nb)));
}
}
}
#ifndef _DEBUG
#undef _ASSERTE
#define _ASSERTE(expr) ((void)0)
#endif // _DEBUG
#endif // VERIFY_HEAP
#ifdef _DEBUG
void DumpSyncBlockCache()
{
STATIC_CONTRACT_NOTHROW;
SyncBlockCache *pCache = SyncBlockCache::GetSyncBlockCache();
LogSpewAlways("Dumping SyncBlockCache size %d\n", pCache->m_FreeSyncTableIndex);
static int dumpSBStyle = -1;
if (dumpSBStyle == -1)
dumpSBStyle = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SBDumpStyle);
if (dumpSBStyle == 0)
return;
BOOL isString = FALSE;
DWORD objectCount = 0;
DWORD slotCount = 0;
for (DWORD nb = 1; nb < pCache->m_FreeSyncTableIndex; nb++)
{
isString = FALSE;
char buffer[1024], buffer2[1024];
LPCUTF8 descrip = "null";
SyncTableEntry *pEntry = &SyncTableEntry::GetSyncTableEntry()[nb];
Object *oref = (Object *) pEntry->m_Object;
if (((size_t) oref & 1) != 0)
{
descrip = "free";
oref = 0;
}
else
{
++slotCount;
if (oref)
{
++objectCount;
struct Param
{
LPCUTF8 descrip;
Object *oref;
char *buffer2;
UINT cch2;
BOOL isString;
} param;
param.descrip = descrip;
param.oref = oref;
param.buffer2 = buffer2;
param.cch2 = COUNTOF(buffer2);
param.isString = isString;
PAL_TRY(Param *, pParam, ¶m)
{
pParam->descrip = pParam->oref->GetMethodTable()->GetDebugClassName();
if (strlen(pParam->descrip) == 0)
pParam->descrip = "";
else if (pParam->oref->GetMethodTable() == g_pStringClass)
{
sprintf_s(pParam->buffer2, pParam->cch2, "%s (%S)", pParam->descrip, ObjectToSTRINGREF((StringObject*)pParam->oref)->GetBuffer());
pParam->descrip = pParam->buffer2;
pParam->isString = TRUE;
}
}
PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
param.descrip = "";
}
PAL_ENDTRY
descrip = param.descrip;
isString = param.isString;
}
sprintf_s(buffer, COUNTOF(buffer), "%s", descrip);
descrip = buffer;
}
if (dumpSBStyle < 2)
LogSpewAlways("[%4.4d]: %8.8x %s\n", nb, oref, descrip);
else if (dumpSBStyle == 2 && ! isString)
LogSpewAlways("[%4.4d]: %s\n", nb, descrip);
}
LogSpewAlways("Done dumping SyncBlockCache used slots: %d, objects: %d\n", slotCount, objectCount);
}
#endif
// ***************************************************************************
//
// ObjHeader class implementation
//
// ***************************************************************************
#if defined(ENABLE_CONTRACTS_IMPL)
// The LOCK_TAKEN/RELEASED macros need a "pointer" to the lock object to do
// comparisons between takes & releases (and to provide debugging info to the
// developer). Ask the syncblock for its lock contract pointer, if the
// syncblock exists. Otherwise, use the MethodTable* from the Object. That's not great,
// as it's not unique, so we might miss unbalanced lock takes/releases from
// different objects of the same type. However, our hands are tied, and we can't
// do much better.
void * ObjHeader::GetPtrForLockContract()
{
if (GetHeaderSyncBlockIndex() == 0)
{
return (void *) GetBaseObject()->GetMethodTable();
}
return PassiveGetSyncBlock()->GetPtrForLockContract();
}
#endif // defined(ENABLE_CONTRACTS_IMPL)
// this enters the monitor of an object
void ObjHeader::EnterObjMonitor()
{
WRAPPER_NO_CONTRACT;
GetSyncBlock()->EnterMonitor();
}
// Non-blocking version of above
BOOL ObjHeader::TryEnterObjMonitor(INT32 timeOut)
{
WRAPPER_NO_CONTRACT;
return GetSyncBlock()->TryEnterMonitor(timeOut);
}
AwareLock::EnterHelperResult ObjHeader::EnterObjMonitorHelperSpin(Thread* pCurThread)
{
CONTRACTL{
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
// Note: EnterObjMonitorHelper must be called before this function (see below)
if (g_SystemInfo.dwNumberOfProcessors == 1)
{
return AwareLock::EnterHelperResult_Contention;
}
YieldProcessorNormalizationInfo normalizationInfo;
const DWORD spinCount = g_SpinConstants.dwMonitorSpinCount;
for (DWORD spinIteration = 0; spinIteration < spinCount; ++spinIteration)
{
AwareLock::SpinWait(normalizationInfo, spinIteration);
LONG oldValue = m_SyncBlockValue.LoadWithoutBarrier();
// Since spinning has begun, chances are good that the monitor has already switched to AwareLock mode, so check for that
// case first
if (oldValue & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX)
{
// If we have a hash code already, we need to create a sync block
if (oldValue & BIT_SBLK_IS_HASHCODE)
{
return AwareLock::EnterHelperResult_UseSlowPath;
}
SyncBlock *syncBlock = g_pSyncTable[oldValue & MASK_SYNCBLOCKINDEX].m_SyncBlock;
_ASSERTE(syncBlock != NULL);
AwareLock *awareLock = &syncBlock->m_Monitor;
AwareLock::EnterHelperResult result = awareLock->TryEnterBeforeSpinLoopHelper(pCurThread);
if (result != AwareLock::EnterHelperResult_Contention)
{
return result;
}
++spinIteration;
if (spinIteration < spinCount)
{
while (true)
{
AwareLock::SpinWait(normalizationInfo, spinIteration);
++spinIteration;
if (spinIteration >= spinCount)
{
// The last lock attempt for this spin will be done after the loop
break;
}
result = awareLock->TryEnterInsideSpinLoopHelper(pCurThread);
if (result == AwareLock::EnterHelperResult_Entered)
{
return AwareLock::EnterHelperResult_Entered;
}
if (result == AwareLock::EnterHelperResult_UseSlowPath)
{
break;
}
}
}
if (awareLock->TryEnterAfterSpinLoopHelper(pCurThread))
{
return AwareLock::EnterHelperResult_Entered;
}
break;
}
DWORD tid = pCurThread->GetThreadId();
if ((oldValue & (BIT_SBLK_SPIN_LOCK +
SBLK_MASK_LOCK_THREADID +
SBLK_MASK_LOCK_RECLEVEL)) == 0)
{
if (tid > SBLK_MASK_LOCK_THREADID)
{
return AwareLock::EnterHelperResult_UseSlowPath;
}
LONG newValue = oldValue | tid;
if (InterlockedCompareExchangeAcquire((LONG*)&m_SyncBlockValue, newValue, oldValue) == oldValue)
{
pCurThread->IncLockCount();
return AwareLock::EnterHelperResult_Entered;
}
continue;
}
// EnterObjMonitorHelper handles the thin lock recursion case. If it's not that case, it won't become that case. If
// EnterObjMonitorHelper failed to increment the recursion level, it will go down the slow path and won't come here. So,
// no need to check the recursion case here.
_ASSERTE(
// The header is transitioning - treat this as if the lock was taken
oldValue & BIT_SBLK_SPIN_LOCK ||
// Here we know we have the "thin lock" layout, but the lock is not free.
// It can't be the recursion case though, because the call to EnterObjMonitorHelper prior to this would have taken
// the slow path in the recursive case.
tid != (DWORD)(oldValue & SBLK_MASK_LOCK_THREADID));
}
return AwareLock::EnterHelperResult_Contention;
}
BOOL ObjHeader::LeaveObjMonitor()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_COOPERATIVE;
}
CONTRACTL_END;
//this function switch to preemp mode so we need to protect the object in some path
OBJECTREF thisObj = ObjectToOBJECTREF (GetBaseObject ());
DWORD dwSwitchCount = 0;
for (;;)
{
AwareLock::LeaveHelperAction action = thisObj->GetHeader ()->LeaveObjMonitorHelper(GetThread());
switch(action)
{
case AwareLock::LeaveHelperAction_None:
// We are done
return TRUE;
case AwareLock::LeaveHelperAction_Signal:
{
// Signal the event
SyncBlock *psb = thisObj->GetHeader ()->PassiveGetSyncBlock();
if (psb != NULL)
psb->QuickGetMonitor()->Signal();
}
return TRUE;
case AwareLock::LeaveHelperAction_Yield:
YieldProcessorNormalized();
continue;
case AwareLock::LeaveHelperAction_Contention:
// Some thread is updating the syncblock value.
{
//protect the object before switching mode
GCPROTECT_BEGIN (thisObj);
GCX_PREEMP();
__SwitchToThread(0, ++dwSwitchCount);
GCPROTECT_END ();
}
continue;
default:
// Must be an error otherwise - ignore it
_ASSERTE(action == AwareLock::LeaveHelperAction_Error);
return FALSE;
}
}
}
// The only difference between LeaveObjMonitor and LeaveObjMonitorAtException is switch
// to preemptive mode around __SwitchToThread
BOOL ObjHeader::LeaveObjMonitorAtException()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
}
CONTRACTL_END;
DWORD dwSwitchCount = 0;
for (;;)
{
AwareLock::LeaveHelperAction action = LeaveObjMonitorHelper(GetThread());
switch(action)
{
case AwareLock::LeaveHelperAction_None:
// We are done
return TRUE;
case AwareLock::LeaveHelperAction_Signal:
{
// Signal the event
SyncBlock *psb = PassiveGetSyncBlock();
if (psb != NULL)
psb->QuickGetMonitor()->Signal();
}
return TRUE;
case AwareLock::LeaveHelperAction_Yield:
YieldProcessorNormalized();
continue;
case AwareLock::LeaveHelperAction_Contention:
// Some thread is updating the syncblock value.
//
// We never toggle GC mode while holding the spinlock (BeginNoTriggerGC/EndNoTriggerGC
// in EnterSpinLock/ReleaseSpinLock ensures it). Thus we do not need to switch to preemptive
// while waiting on the spinlock.
//
{
__SwitchToThread(0, ++dwSwitchCount);
}
continue;
default:
// Must be an error otherwise - ignore it
_ASSERTE(action == AwareLock::LeaveHelperAction_Error);
return FALSE;
}
}
}
#endif //!DACCESS_COMPILE
// Returns TRUE if the lock is owned and FALSE otherwise
// threadId is set to the ID (Thread::GetThreadId()) of the thread which owns the lock
// acquisitionCount is set to the number of times the lock needs to be released before
// it is unowned
BOOL ObjHeader::GetThreadOwningMonitorLock(DWORD *pThreadId, DWORD *pAcquisitionCount)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
#ifndef DACCESS_COMPILE
if (!IsGCSpecialThread ()) {MODE_COOPERATIVE;} else {MODE_ANY;}
#endif
}
CONTRACTL_END;
SUPPORTS_DAC;
DWORD bits = GetBits();
if (bits & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX)
{
if (bits & BIT_SBLK_IS_HASHCODE)
{
//
// This thread does not own the lock.
//
*pThreadId = 0;
*pAcquisitionCount = 0;
return FALSE;
}
else
{
//
// We have a syncblk
//
DWORD index = bits & MASK_SYNCBLOCKINDEX;
SyncBlock* psb = g_pSyncTable[(int)index].m_SyncBlock;
_ASSERTE(psb->GetMonitor() != NULL);
Thread* pThread = psb->GetMonitor()->GetHoldingThread();
if(pThread == NULL)
{
*pThreadId = 0;
*pAcquisitionCount = 0;
return FALSE;
}
else
{
*pThreadId = pThread->GetThreadId();
*pAcquisitionCount = psb->GetMonitor()->GetRecursionLevel();
return TRUE;
}
}
}
else
{
//
// We have a thinlock
//
DWORD lockThreadId, recursionLevel;
lockThreadId = bits & SBLK_MASK_LOCK_THREADID;
recursionLevel = (bits & SBLK_MASK_LOCK_RECLEVEL) >> SBLK_RECLEVEL_SHIFT;
//if thread ID is 0, recursionLevel got to be zero
//but thread ID doesn't have to be valid because the lock could be orphanend
_ASSERTE (lockThreadId != 0 || recursionLevel == 0 );
*pThreadId = lockThreadId;
if(lockThreadId != 0)
{
// in the header, the recursionLevel of 0 means the lock is owned once
// (this differs from m_Recursion in the AwareLock)
*pAcquisitionCount = recursionLevel + 1;
return TRUE;
}
else
{
*pAcquisitionCount = 0;
return FALSE;
}
}
}
#ifndef DACCESS_COMPILE
#ifdef MP_LOCKS
DEBUG_NOINLINE void ObjHeader::EnterSpinLock()
{
// NOTE: This function cannot have a dynamic contract. If it does, the contract's
// destructor will reset the CLR debug state to what it was before entering the
// function, which will undo the BeginNoTriggerGC() call below.
SCAN_SCOPE_BEGIN;
STATIC_CONTRACT_GC_NOTRIGGER;
#ifdef _DEBUG
int i = 0;
#endif
DWORD dwSwitchCount = 0;
while (TRUE)
{
#ifdef _DEBUG
#ifdef _WIN64
// Give 64bit more time because there isn't a remoting fast path now, and we've hit this assert
// needlessly in CLRSTRESS.
if (i++ > 30000)
#else
if (i++ > 10000)
#endif // _WIN64
_ASSERTE(!"ObjHeader::EnterLock timed out");
#endif
// get the value so that it doesn't get changed under us.
LONG curValue = m_SyncBlockValue.LoadWithoutBarrier();
// check if lock taken
if (! (curValue & BIT_SBLK_SPIN_LOCK))
{
// try to take the lock
LONG newValue = curValue | BIT_SBLK_SPIN_LOCK;
LONG result = FastInterlockCompareExchange((LONG*)&m_SyncBlockValue, newValue, curValue);
if (result == curValue)
break;
}
if (g_SystemInfo.dwNumberOfProcessors > 1)
{
for (int spinCount = 0; spinCount < BIT_SBLK_SPIN_COUNT; spinCount++)
{
if (! (m_SyncBlockValue & BIT_SBLK_SPIN_LOCK))
break;
YieldProcessorNormalized(); // indicate to the processor that we are spinning
}
if (m_SyncBlockValue & BIT_SBLK_SPIN_LOCK)
__SwitchToThread(0, ++dwSwitchCount);
}
else
__SwitchToThread(0, ++dwSwitchCount);
}
INCONTRACT(Thread* pThread = GetThread());
INCONTRACT(if (pThread != NULL) pThread->BeginNoTriggerGC(__FILE__, __LINE__));
}
#else
DEBUG_NOINLINE void ObjHeader::EnterSpinLock()
{
SCAN_SCOPE_BEGIN;
STATIC_CONTRACT_GC_NOTRIGGER;
#ifdef _DEBUG
int i = 0;
#endif
DWORD dwSwitchCount = 0;
while (TRUE)
{
#ifdef _DEBUG
if (i++ > 10000)
_ASSERTE(!"ObjHeader::EnterLock timed out");
#endif
// get the value so that it doesn't get changed under us.
LONG curValue = m_SyncBlockValue.LoadWithoutBarrier();
// check if lock taken
if (! (curValue & BIT_SBLK_SPIN_LOCK))
{
// try to take the lock
LONG newValue = curValue | BIT_SBLK_SPIN_LOCK;
LONG result = FastInterlockCompareExchange((LONG*)&m_SyncBlockValue, newValue, curValue);
if (result == curValue)
break;
}
__SwitchToThread(0, ++dwSwitchCount);
}
INCONTRACT(Thread* pThread = GetThread());
INCONTRACT(if (pThread != NULL) pThread->BeginNoTriggerGC(__FILE__, __LINE__));
}
#endif //MP_LOCKS
DEBUG_NOINLINE void ObjHeader::ReleaseSpinLock()
{
SCAN_SCOPE_END;
LIMITED_METHOD_CONTRACT;
INCONTRACT(Thread* pThread = GetThread());
INCONTRACT(if (pThread != NULL) pThread->EndNoTriggerGC());
FastInterlockAnd(&m_SyncBlockValue, ~BIT_SBLK_SPIN_LOCK);
}
#endif //!DACCESS_COMPILE
#ifndef DACCESS_COMPILE
DWORD ObjHeader::GetSyncBlockIndex()
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_NOTRIGGER;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
DWORD indx;
if ((indx = GetHeaderSyncBlockIndex()) == 0)
{
BOOL fMustCreateSyncBlock = FALSE;
{
//Need to get it from the cache
SyncBlockCache::LockHolder lh(SyncBlockCache::GetSyncBlockCache());
//Try one more time
if (GetHeaderSyncBlockIndex() == 0)
{
ENTER_SPIN_LOCK(this);
// Now the header will be stable - check whether hashcode, appdomain index or lock information is stored in it.
DWORD bits = GetBits();
if (((bits & (BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE)) == (BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE)) ||
((bits & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) == 0))
{
// Need a sync block to store this info
fMustCreateSyncBlock = TRUE;
}
else
{
SetIndex(BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | SyncBlockCache::GetSyncBlockCache()->NewSyncBlockSlot(GetBaseObject()));
}
LEAVE_SPIN_LOCK(this);
}
// SyncBlockCache::LockHolder goes out of scope here
}
if (fMustCreateSyncBlock)
GetSyncBlock();
if ((indx = GetHeaderSyncBlockIndex()) == 0)
COMPlusThrowOM();
}
return indx;
}
#if defined (VERIFY_HEAP)
BOOL ObjHeader::Validate (BOOL bVerifySyncBlkIndex)
{
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_NOTRIGGER;
STATIC_CONTRACT_MODE_COOPERATIVE;
DWORD bits = GetBits ();
Object * obj = GetBaseObject ();
BOOL bVerifyMore = g_pConfig->GetHeapVerifyLevel() & EEConfig::HEAPVERIFY_SYNCBLK;
//the highest 2 bits have reloaded meaning
//for string objects:
// BIT_SBLK_STRING_HAS_NO_HIGH_CHARS 0x80000000
// BIT_SBLK_STRING_HIGH_CHARS_KNOWN 0x40000000
// BIT_SBLK_STRING_HAS_SPECIAL_SORT 0xC0000000
//for other objects:
// BIT_SBLK_FINALIZER_RUN 0x40000000
if (bits & BIT_SBLK_STRING_HIGH_CHAR_MASK)
{
if (obj->GetGCSafeMethodTable () == g_pStringClass)
{
if (bVerifyMore)
{
ASSERT_AND_CHECK (((StringObject *)obj)->ValidateHighChars());
}
}
else
{
if (bits & BIT_SBLK_FINALIZER_RUN)
{
ASSERT_AND_CHECK (obj->GetGCSafeMethodTable ()->HasFinalizer ());
}
}
}
//BIT_SBLK_GC_RESERVE (0x20000000) is only set during GC. But for frozen object, we don't clean the bit
if (bits & BIT_SBLK_GC_RESERVE)
{
if (!GCHeapUtilities::IsGCInProgress () && !GCHeapUtilities::GetGCHeap()->IsConcurrentGCInProgress ())
{
#ifdef FEATURE_BASICFREEZE
ASSERT_AND_CHECK (GCHeapUtilities::GetGCHeap()->IsInFrozenSegment(obj));
#else //FEATURE_BASICFREEZE
_ASSERTE(!"Reserve bit not cleared");
return FALSE;
#endif //FEATURE_BASICFREEZE
}
}
//Don't know how to verify BIT_SBLK_SPIN_LOCK (0x10000000)
//BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX (0x08000000)
if (bits & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX)
{
//if BIT_SBLK_IS_HASHCODE (0x04000000) is not set,
//rest of the DWORD is SyncBlk Index
if (!(bits & BIT_SBLK_IS_HASHCODE))
{
if (bVerifySyncBlkIndex && GCHeapUtilities::GetGCHeap()->RuntimeStructuresValid ())
{
DWORD sbIndex = bits & MASK_SYNCBLOCKINDEX;
ASSERT_AND_CHECK(SyncTableEntry::GetSyncTableEntry()[sbIndex].m_Object == obj);
}
}
else
{
// rest of the DWORD is a hash code and we don't have much to validate it
}
}
else
{
//if BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX is clear, rest of DWORD is thin lock thread ID,
//thin lock recursion level and appdomain index
DWORD lockThreadId = bits & SBLK_MASK_LOCK_THREADID;
DWORD recursionLevel = (bits & SBLK_MASK_LOCK_RECLEVEL) >> SBLK_RECLEVEL_SHIFT;
//if thread ID is 0, recursionLeve got to be zero
//but thread ID doesn't have to be valid because the lock could be orphanend
ASSERT_AND_CHECK (lockThreadId != 0 || recursionLevel == 0 );
}
return TRUE;
}
#endif //VERIFY_HEAP
// This holder takes care of the SyncBlock memory cleanup if an OOM occurs inside a call to NewSyncBlockSlot.
//
// Warning: Assumes you already own the cache lock.
// Assumes nothing allocated inside the SyncBlock (only releases the memory, does not destruct.)
//
// This holder really just meets GetSyncBlock()'s special needs. It's not a general purpose holder.
// Do not inline this call. (fyuan)
// SyncBlockMemoryHolder is normally a check for empty pointer and return. Inlining VoidDeleteSyncBlockMemory adds expensive exception handling.
void VoidDeleteSyncBlockMemory(SyncBlock* psb)
{
LIMITED_METHOD_CONTRACT;
SyncBlockCache::GetSyncBlockCache()->DeleteSyncBlockMemory(psb);
}
typedef Wrapper, VoidDeleteSyncBlockMemory, NULL> SyncBlockMemoryHolder;
// get the sync block for an existing object
SyncBlock *ObjHeader::GetSyncBlock()
{
CONTRACT(SyncBlock *)
{
INSTANCE_CHECK;
THROWS;
GC_NOTRIGGER;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM(););
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
PTR_SyncBlock syncBlock = GetBaseObject()->PassiveGetSyncBlock();
DWORD indx = 0;
BOOL indexHeld = FALSE;
if (syncBlock)
{
#ifdef _DEBUG
// Has our backpointer been correctly updated through every GC?
PTR_SyncTableEntry pEntries(SyncTableEntry::GetSyncTableEntry());
_ASSERTE(pEntries[GetHeaderSyncBlockIndex()].m_Object == GetBaseObject());
#endif // _DEBUG
RETURN syncBlock;
}
//Need to get it from the cache
{
SyncBlockCache::LockHolder lh(SyncBlockCache::GetSyncBlockCache());
//Try one more time
syncBlock = GetBaseObject()->PassiveGetSyncBlock();
if (syncBlock)
RETURN syncBlock;
SyncBlockMemoryHolder syncBlockMemoryHolder(SyncBlockCache::GetSyncBlockCache()->GetNextFreeSyncBlock());
syncBlock = syncBlockMemoryHolder;
if ((indx = GetHeaderSyncBlockIndex()) == 0)
{
indx = SyncBlockCache::GetSyncBlockCache()->NewSyncBlockSlot(GetBaseObject());
}
else
{
//We already have an index, we need to hold the syncblock
indexHeld = TRUE;
}
{
//! NewSyncBlockSlot has side-effects that we don't have backout for - thus, that must be the last
//! failable operation called.
CANNOTTHROWCOMPLUSEXCEPTION();
FAULT_FORBID();
syncBlockMemoryHolder.SuppressRelease();
new (syncBlock) SyncBlock(indx);
{
// after this point, nobody can update the index in the header
ENTER_SPIN_LOCK(this);
{
// If the thin lock in the header is in use, transfer the information to the syncblock
DWORD bits = GetBits();
if ((bits & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) == 0)
{
DWORD lockThreadId = bits & SBLK_MASK_LOCK_THREADID;
DWORD recursionLevel = (bits & SBLK_MASK_LOCK_RECLEVEL) >> SBLK_RECLEVEL_SHIFT;
if (lockThreadId != 0 || recursionLevel != 0)
{
// recursionLevel can't be non-zero if thread id is 0
_ASSERTE(lockThreadId != 0);
Thread *pThread = g_pThinLockThreadIdDispenser->IdToThreadWithValidation(lockThreadId);
if (pThread == NULL)
{
// The lock is orphaned.
pThread = (Thread*) -1;
}
syncBlock->InitState(recursionLevel + 1, pThread);
}
}
else if ((bits & BIT_SBLK_IS_HASHCODE) != 0)
{
DWORD hashCode = bits & MASK_HASHCODE;
syncBlock->SetHashCode(hashCode);
}
}
SyncTableEntry::GetSyncTableEntry() [indx].m_SyncBlock = syncBlock;
// in order to avoid a race where some thread tries to get the AD index and we've already zapped it,
// make sure the syncblock etc is all setup with the AD index prior to replacing the index
// in the header
if (GetHeaderSyncBlockIndex() == 0)
{
// We have transferred the AppDomain into the syncblock above.
SetIndex(BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | indx);
}
//If we had already an index, hold the syncblock
//for the lifetime of the object.
if (indexHeld)
syncBlock->SetPrecious();
LEAVE_SPIN_LOCK(this);
}
// SyncBlockCache::LockHolder goes out of scope here
}
}
RETURN syncBlock;
}
BOOL ObjHeader::Wait(INT32 timeOut, BOOL exitContext)
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
// The following code may cause GC, so we must fetch the sync block from
// the object now in case it moves.
SyncBlock *pSB = GetBaseObject()->GetSyncBlock();
// GetSyncBlock throws on failure
_ASSERTE(pSB != NULL);
// make sure we own the crst
if (!pSB->DoesCurrentThreadOwnMonitor())
COMPlusThrow(kSynchronizationLockException);
#ifdef _DEBUG
Thread *pThread = GetThread();
DWORD curLockCount = pThread->m_dwLockCount;
#endif
BOOL result = pSB->Wait(timeOut,exitContext);
_ASSERTE (curLockCount == pThread->m_dwLockCount);
return result;
}
void ObjHeader::Pulse()
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
// The following code may cause GC, so we must fetch the sync block from
// the object now in case it moves.
SyncBlock *pSB = GetBaseObject()->GetSyncBlock();
// GetSyncBlock throws on failure
_ASSERTE(pSB != NULL);
// make sure we own the crst
if (!pSB->DoesCurrentThreadOwnMonitor())
COMPlusThrow(kSynchronizationLockException);
pSB->Pulse();
}
void ObjHeader::PulseAll()
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
// The following code may cause GC, so we must fetch the sync block from
// the object now in case it moves.
SyncBlock *pSB = GetBaseObject()->GetSyncBlock();
// GetSyncBlock throws on failure
_ASSERTE(pSB != NULL);
// make sure we own the crst
if (!pSB->DoesCurrentThreadOwnMonitor())
COMPlusThrow(kSynchronizationLockException);
pSB->PulseAll();
}
// ***************************************************************************
//
// AwareLock class implementation (GC-aware locking)
//
// ***************************************************************************
void AwareLock::AllocLockSemEvent()
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
// Before we switch from cooperative, ensure that this syncblock won't disappear
// under us. For something as expensive as an event, do it permanently rather
// than transiently.
SetPrecious();
GCX_PREEMP();
// No need to take a lock - CLREvent::CreateMonitorEvent is thread safe
m_SemEvent.CreateMonitorEvent((SIZE_T)this);
}
void AwareLock::Enter()
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
Thread *pCurThread = GetThread();
LockState state = m_lockState.VolatileLoadWithoutBarrier();
if (!state.IsLocked() || m_HoldingThread != pCurThread)
{
if (m_lockState.InterlockedTryLock_Or_RegisterWaiter(this, state))
{
// We get here if we successfully acquired the mutex.
m_HoldingThread = pCurThread;
m_Recursion = 1;
pCurThread->IncLockCount();
#if defined(_DEBUG) && defined(TRACK_SYNC)
// The best place to grab this is from the ECall frame
Frame *pFrame = pCurThread->GetFrame();
int caller = (pFrame && pFrame != FRAME_TOP
? (int)pFrame->GetReturnAddress()
: -1);
pCurThread->m_pTrackSync->EnterSync(caller, this);
#endif
return;
}
// Lock was not acquired and the waiter was registered
// Didn't manage to get the mutex, must wait.
// The precondition for EnterEpilog is that the count of waiters be bumped
// to account for this thread, which was done above.
EnterEpilog(pCurThread);
return;
}
// Got the mutex via recursive locking on the same thread.
_ASSERTE(m_Recursion >= 1);
m_Recursion++;
#if defined(_DEBUG) && defined(TRACK_SYNC)
// The best place to grab this is from the ECall frame
Frame *pFrame = pCurThread->GetFrame();
int caller = (pFrame && pFrame != FRAME_TOP ? (int)pFrame->GetReturnAddress() : -1);
pCurThread->m_pTrackSync->EnterSync(caller, this);
#endif
}
BOOL AwareLock::TryEnter(INT32 timeOut)
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_TRIGGERS;
if (timeOut == 0) {MODE_ANY;} else {MODE_COOPERATIVE;}
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
Thread *pCurThread = GetThread();
if (pCurThread->IsAbortRequested())
{
pCurThread->HandleThreadAbort();
}
LockState state = m_lockState.VolatileLoadWithoutBarrier();
if (!state.IsLocked() || m_HoldingThread != pCurThread)
{
if (timeOut == 0
? m_lockState.InterlockedTryLock(state)
: m_lockState.InterlockedTryLock_Or_RegisterWaiter(this, state))
{
// We get here if we successfully acquired the mutex.
m_HoldingThread = pCurThread;
m_Recursion = 1;
pCurThread->IncLockCount();
#if defined(_DEBUG) && defined(TRACK_SYNC)
// The best place to grab this is from the ECall frame
Frame *pFrame = pCurThread->GetFrame();
int caller = (pFrame && pFrame != FRAME_TOP ? (int)pFrame->GetReturnAddress() : -1);
pCurThread->m_pTrackSync->EnterSync(caller, this);
#endif
return true;
}
// Lock was not acquired and the waiter was registered if the timeout is nonzero
// Didn't manage to get the mutex, return failure if no timeout, else wait
// for at most timeout milliseconds for the mutex.
if (timeOut == 0)
{
return false;
}
// The precondition for EnterEpilog is that the count of waiters be bumped
// to account for this thread, which was done above
return EnterEpilog(pCurThread, timeOut);
}
// Got the mutex via recursive locking on the same thread.
_ASSERTE(m_Recursion >= 1);
m_Recursion++;
#if defined(_DEBUG) && defined(TRACK_SYNC)
// The best place to grab this is from the ECall frame
Frame *pFrame = pCurThread->GetFrame();
int caller = (pFrame && pFrame != FRAME_TOP ? (int)pFrame->GetReturnAddress() : -1);
pCurThread->m_pTrackSync->EnterSync(caller, this);
#endif
return true;
}
BOOL AwareLock::EnterEpilog(Thread* pCurThread, INT32 timeOut)
{
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_MODE_COOPERATIVE;
STATIC_CONTRACT_GC_TRIGGERS;
// While we are in this frame the thread is considered blocked on the
// critical section of the monitor lock according to the debugger
DebugBlockingItem blockingMonitorInfo;
blockingMonitorInfo.dwTimeout = timeOut;
blockingMonitorInfo.pMonitor = this;
blockingMonitorInfo.pAppDomain = SystemDomain::GetCurrentDomain();
blockingMonitorInfo.type = DebugBlock_MonitorCriticalSection;
DebugBlockingItemHolder holder(pCurThread, &blockingMonitorInfo);
// We need a separate helper because it uses SEH and the holder has a
// destructor
return EnterEpilogHelper(pCurThread, timeOut);
}
#ifdef _DEBUG
#define _LOGCONTENTION
#endif // _DEBUG
#ifdef _LOGCONTENTION
inline void LogContention()
{
WRAPPER_NO_CONTRACT;
#ifdef LOGGING
if (LoggingOn(LF_SYNC, LL_INFO100))
{
LogSpewAlways("Contention: Stack Trace Begin\n");
void LogStackTrace();
LogStackTrace();
LogSpewAlways("Contention: Stack Trace End\n");
}
#endif
}
#else
#define LogContention()
#endif
double ComputeElapsedTimeInNanosecond(LARGE_INTEGER startTicks, LARGE_INTEGER endTicks)
{
static LARGE_INTEGER freq;
if (freq.QuadPart == 0)
QueryPerformanceFrequency(&freq);
const double NsPerSecond = 1000 * 1000 * 1000;
LONGLONG elapsedTicks = endTicks.QuadPart - startTicks.QuadPart;
return (elapsedTicks * NsPerSecond) / freq.QuadPart;
}
BOOL AwareLock::EnterEpilogHelper(Thread* pCurThread, INT32 timeOut)
{
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_MODE_COOPERATIVE;
STATIC_CONTRACT_GC_TRIGGERS;
// IMPORTANT!!!
// The caller has already registered a waiter. This function needs to unregister the waiter on all paths (exception paths
// included). On runtimes where thread-abort is supported, a thread-abort also needs to unregister the waiter. There may be
// a possibility for preemptive GC toggles below to handle a thread-abort, that should be taken into consideration when
// porting this code back to .NET Framework.
// Require all callers to be in cooperative mode. If they have switched to preemptive
// mode temporarily before calling here, then they are responsible for protecting
// the object associated with this lock.
_ASSERTE(pCurThread->PreemptiveGCDisabled());
BOOLEAN IsContentionKeywordEnabled = ETW_TRACING_CATEGORY_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, TRACE_LEVEL_INFORMATION, CLR_CONTENTION_KEYWORD);
LARGE_INTEGER startTicks = { {0} };
if (IsContentionKeywordEnabled)
{
QueryPerformanceCounter(&startTicks);
// Fire a contention start event for a managed contention
FireEtwContentionStart_V1(ETW::ContentionLog::ContentionStructs::ManagedContention, GetClrInstanceId());
}
LogContention();
Thread::IncrementMonitorLockContentionCount(pCurThread);
OBJECTREF obj = GetOwningObject();
// We cannot allow the AwareLock to be cleaned up underneath us by the GC.
IncrementTransientPrecious();
DWORD ret;
GCPROTECT_BEGIN(obj);
{
if (!m_SemEvent.IsMonitorEventAllocated())
{
AllocLockSemEvent();
}
_ASSERTE(m_SemEvent.IsMonitorEventAllocated());
pCurThread->EnablePreemptiveGC();
for (;;)
{
// We might be interrupted during the wait (Thread.Interrupt), so we need an
// exception handler round the call.
struct Param
{
AwareLock *pThis;
INT32 timeOut;
DWORD ret;
} param;
param.pThis = this;
param.timeOut = timeOut;
// Measure the time we wait so that, in the case where we wake up
// and fail to acquire the mutex, we can adjust remaining timeout
// accordingly.
ULONGLONG start = CLRGetTickCount64();
EE_TRY_FOR_FINALLY(Param *, pParam, ¶m)
{
pParam->ret = pParam->pThis->m_SemEvent.Wait(pParam->timeOut, TRUE);
_ASSERTE((pParam->ret == WAIT_OBJECT_0) || (pParam->ret == WAIT_TIMEOUT));
}
EE_FINALLY
{
if (GOT_EXCEPTION())
{
// It is likely the case that An APC threw an exception, for instance Thread.Interrupt(). The wait subsystem
// guarantees that if a signal to the event being waited upon is observed by the woken thread, that thread's
// wait will return WAIT_OBJECT_0. So in any race between m_SemEvent being signaled and the wait throwing an
// exception, a thread that is woken by an exception would not observe the signal, and the signal would wake
// another thread as necessary.
// We must decrement the waiter count.
m_lockState.InterlockedUnregisterWaiter();
}
} EE_END_FINALLY;
ret = param.ret;
if (ret != WAIT_OBJECT_0)
{
// We timed out, decrement waiter count.
m_lockState.InterlockedUnregisterWaiter();
break;
}
// Spin a bit while trying to acquire the lock. This has a few benefits:
// - Spinning helps to reduce waiter starvation. Since other non-waiter threads can take the lock while there are
// waiters (see LockState::InterlockedTryLock()), once a waiter wakes it will be able to better compete
// with other spinners for the lock.
// - If there is another thread that is repeatedly acquiring and releasing the lock, spinning before waiting again
// helps to prevent a waiter from repeatedly context-switching in and out
// - Further in the same situation above, waking up and waiting shortly thereafter deprioritizes this waiter because
// events release waiters in FIFO order. Spinning a bit helps a waiter to retain its priority at least for one
// spin duration before it gets deprioritized behind all other waiters.
if (g_SystemInfo.dwNumberOfProcessors > 1)
{
bool acquiredLock = false;
YieldProcessorNormalizationInfo normalizationInfo;
const DWORD spinCount = g_SpinConstants.dwMonitorSpinCount;
for (DWORD spinIteration = 0; spinIteration < spinCount; ++spinIteration)
{
if (m_lockState.InterlockedTry_LockAndUnregisterWaiterAndObserveWakeSignal(this))
{
acquiredLock = true;
break;
}
SpinWait(normalizationInfo, spinIteration);
}
if (acquiredLock)
{
break;
}
}
if (m_lockState.InterlockedObserveWakeSignal_Try_LockAndUnregisterWaiter(this))
{
break;
}
// When calculating duration we consider a couple of special cases.
// If the end tick is the same as the start tick we make the
// duration a millisecond, to ensure we make forward progress if
// there's a lot of contention on the mutex. Secondly, we have to
// cope with the case where the tick counter wrapped while we where
// waiting (we can cope with at most one wrap, so don't expect three
// month timeouts to be very accurate). Luckily for us, the latter
// case is taken care of by 32-bit modulo arithmetic automatically.
if (timeOut != (INT32)INFINITE)
{
ULONGLONG end = CLRGetTickCount64();
ULONGLONG duration;
if (end == start)
{
duration = 1;
}
else
{
duration = end - start;
}
duration = min(duration, (DWORD)timeOut);
timeOut -= (INT32)duration;
}
}
pCurThread->DisablePreemptiveGC();
}
GCPROTECT_END();
DecrementTransientPrecious();
if (IsContentionKeywordEnabled)
{
LARGE_INTEGER endTicks;
QueryPerformanceCounter(&endTicks);
double elapsedTimeInNanosecond = ComputeElapsedTimeInNanosecond(startTicks, endTicks);
// Fire a contention end event for a managed contention
FireEtwContentionStop_V1(ETW::ContentionLog::ContentionStructs::ManagedContention, GetClrInstanceId(), elapsedTimeInNanosecond);
}
if (ret == WAIT_TIMEOUT)
{
return false;
}
m_HoldingThread = pCurThread;
m_Recursion = 1;
pCurThread->IncLockCount();
#if defined(_DEBUG) && defined(TRACK_SYNC)
// The best place to grab this is from the ECall frame
Frame *pFrame = pCurThread->GetFrame();
int caller = (pFrame && pFrame != FRAME_TOP ? (int)pFrame->GetReturnAddress() : -1);
pCurThread->m_pTrackSync->EnterSync(caller, this);
#endif
return true;
}
BOOL AwareLock::Leave()
{
CONTRACTL
{
INSTANCE_CHECK;
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
Thread* pThread = GetThread();
AwareLock::LeaveHelperAction action = LeaveHelper(pThread);
switch(action)
{
case AwareLock::LeaveHelperAction_None:
// We are done
return TRUE;
case AwareLock::LeaveHelperAction_Signal:
// Signal the event
Signal();
return TRUE;
default:
// Must be an error otherwise
_ASSERTE(action == AwareLock::LeaveHelperAction_Error);
return FALSE;
}
}
LONG AwareLock::LeaveCompletely()
{
WRAPPER_NO_CONTRACT;
LONG count = 0;
while (Leave()) {
count++;
}
_ASSERTE(count > 0); // otherwise we were never in the lock
return count;
}
BOOL AwareLock::OwnedByCurrentThread()
{
WRAPPER_NO_CONTRACT;
return (GetThread() == m_HoldingThread);
}
// ***************************************************************************
//
// SyncBlock class implementation
//
// ***************************************************************************
// We maintain two queues for SyncBlock::Wait.
// 1. Inside SyncBlock we queue all threads that are waiting on the SyncBlock.
// When we pulse, we pick the thread from this queue using FIFO.
// 2. We queue all SyncBlocks that a thread is waiting for in Thread::m_WaitEventLink.
// When we pulse a thread, we find the event from this queue to set, and we also
// or in a 1 bit in the syncblock value saved in the queue, so that we can return
// immediately from SyncBlock::Wait if the syncblock has been pulsed.
BOOL SyncBlock::Wait(INT32 timeOut, BOOL exitContext)
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
}
CONTRACTL_END;
Thread *pCurThread = GetThread();
BOOL isTimedOut = FALSE;
BOOL isEnqueued = FALSE;
WaitEventLink waitEventLink;
WaitEventLink *pWaitEventLink;
// As soon as we flip the switch, we are in a race with the GC, which could clean
// up the SyncBlock underneath us -- unless we report the object.
_ASSERTE(pCurThread->PreemptiveGCDisabled());
// Does this thread already wait for this SyncBlock?
WaitEventLink *walk = pCurThread->WaitEventLinkForSyncBlock(this);
if (walk->m_Next) {
if (walk->m_Next->m_WaitSB == this) {
// Wait on the same lock again.
walk->m_Next->m_RefCount ++;
pWaitEventLink = walk->m_Next;
}
else if ((SyncBlock*)(((DWORD_PTR)walk->m_Next->m_WaitSB) & ~1)== this) {
// This thread has been pulsed. No need to wait.
return TRUE;
}
}
else {
// First time this thread is going to wait for this SyncBlock.
CLREvent* hEvent;
if (pCurThread->m_WaitEventLink.m_Next == NULL) {
hEvent = &(pCurThread->m_EventWait);
}
else {
hEvent = GetEventFromEventStore();
}
waitEventLink.m_WaitSB = this;
waitEventLink.m_EventWait = hEvent;
waitEventLink.m_Thread = pCurThread;
waitEventLink.m_Next = NULL;
waitEventLink.m_LinkSB.m_pNext = NULL;
waitEventLink.m_RefCount = 1;
pWaitEventLink = &waitEventLink;
walk->m_Next = pWaitEventLink;
// Before we enqueue it (and, thus, before it can be dequeued), reset the event
// that will awaken us.
hEvent->Reset();
// This thread is now waiting on this sync block
ThreadQueue::EnqueueThread(pWaitEventLink, this);
isEnqueued = TRUE;
}
_ASSERTE ((SyncBlock*)((DWORD_PTR)walk->m_Next->m_WaitSB & ~1)== this);
PendingSync syncState(walk);
OBJECTREF obj = m_Monitor.GetOwningObject();
m_Monitor.IncrementTransientPrecious();
// While we are in this frame the thread is considered blocked on the
// event of the monitor lock according to the debugger
DebugBlockingItem blockingMonitorInfo;
blockingMonitorInfo.dwTimeout = timeOut;
blockingMonitorInfo.pMonitor = &m_Monitor;
blockingMonitorInfo.pAppDomain = SystemDomain::GetCurrentDomain();
blockingMonitorInfo.type = DebugBlock_MonitorEvent;
DebugBlockingItemHolder holder(pCurThread, &blockingMonitorInfo);
GCPROTECT_BEGIN(obj);
{
GCX_PREEMP();
// remember how many times we synchronized
syncState.m_EnterCount = LeaveMonitorCompletely();
_ASSERTE(syncState.m_EnterCount > 0);
isTimedOut = pCurThread->Block(timeOut, &syncState);
}
GCPROTECT_END();
m_Monitor.DecrementTransientPrecious();
return !isTimedOut;
}
void SyncBlock::Pulse()
{
CONTRACTL
{
INSTANCE_CHECK;
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
WaitEventLink *pWaitEventLink;
if ((pWaitEventLink = ThreadQueue::DequeueThread(this)) != NULL)
pWaitEventLink->m_EventWait->Set();
}
void SyncBlock::PulseAll()
{
CONTRACTL
{
INSTANCE_CHECK;
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
WaitEventLink *pWaitEventLink;
while ((pWaitEventLink = ThreadQueue::DequeueThread(this)) != NULL)
pWaitEventLink->m_EventWait->Set();
}
bool SyncBlock::SetInteropInfo(InteropSyncBlockInfo* pInteropInfo)
{
WRAPPER_NO_CONTRACT;
SetPrecious();
// We could be agile, but not have noticed yet. We can't assert here
// that we live in any given domain, nor is this an appropriate place
// to re-parent the syncblock.
/* _ASSERTE (m_dwAppDomainIndex.m_dwIndex == 0 ||
m_dwAppDomainIndex == SystemDomain::System()->DefaultDomain()->GetIndex() ||
m_dwAppDomainIndex == GetAppDomain()->GetIndex());
m_dwAppDomainIndex = GetAppDomain()->GetIndex();
*/
return (FastInterlockCompareExchangePointer(&m_pInteropInfo,
pInteropInfo,
NULL) == NULL);
}
#ifdef EnC_SUPPORTED
// Store information about fields added to this object by EnC
// This must be called from a thread in the AppDomain of this object instance
void SyncBlock::SetEnCInfo(EnCSyncBlockInfo *pEnCInfo)
{
WRAPPER_NO_CONTRACT;
// We can't recreate the field contents, so this SyncBlock can never go away
SetPrecious();
// Store the field info (should only ever happen once)
_ASSERTE( m_pEnCInfo == NULL );
m_pEnCInfo = pEnCInfo;
}
#endif // EnC_SUPPORTED
#endif // !DACCESS_COMPILE
#if defined(_WIN64) && defined(_DEBUG)
void ObjHeader::IllegalAlignPad()
{
WRAPPER_NO_CONTRACT;
#ifdef LOGGING
void** object = ((void**) this) + 1;
LogSpewAlways("\n\n******** Illegal ObjHeader m_alignpad not 0, object" FMT_ADDR "\n\n",
DBG_ADDR(object));
#endif
_ASSERTE(m_alignpad == 0);
}
#endif // _WIN64 && _DEBUG