// 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