// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//
// 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"
#include "minipal/time.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)];
};
SyncBlockCache g_SyncBlockCacheInstance;
SPTR_IMPL (SyncBlockCache, SyncBlockCache, s_pSyncBlockCache);
#ifndef DACCESS_COMPILE
InteropSyncBlockInfo::~InteropSyncBlockInfo()
{
CONTRACTL
{
NOTHROW;
DESTRUCTOR_CHECK;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
#ifndef FEATURE_PORTABLE_ENTRYPOINTS
FreeUMEntryThunk();
#endif // !FEATURE_PORTABLE_ENTRYPOINTS
}
#ifndef FEATURE_PORTABLE_ENTRYPOINTS
void InteropSyncBlockInfo::FreeUMEntryThunk()
{
CONTRACTL
{
NOTHROW;
DESTRUCTOR_CHECK;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END
UMEntryThunkData *pUMEntryThunk = m_pUMEntryThunk;
if (pUMEntryThunk != NULL)
{
UMEntryThunkData::FreeUMEntryThunk(pUMEntryThunk);
m_pUMEntryThunk = NULL;
}
}
#endif // !FEATURE_PORTABLE_ENTRYPOINTS
#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;
}
#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
//
// ***************************************************************************
void SyncBlockCache::Init()
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
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.Init(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;
}
void SyncBlockCache::Destroy()
{
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;
m_CacheLock.Destroy();
// 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(FinalizerThread::IsCurrentThreadFinalizer());
// 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()->CatchAtSafePoint())
{
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::Start()
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
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;
}
// 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())
{
SyncBlockCache::GetSyncBlockCache()->Destroy();
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;
#if defined(FEATURE_COMINTEROP)
if (psb->m_pInteropInfo)
{
// called during GC
// so do only minorcleanup
MinorCleanupSyncBlockComData(psb->m_pInteropInfo);
}
#endif // FEATURE_COMINTEROP || FEATURE_COMWRAPPERS
// 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_pNext = m_pCleanupBlockList;
m_pCleanupBlockList = psb;
// 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 = m_pCleanupBlockList;
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
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
#ifdef _DEBUG // Instrumentation for OOM fault injection testing
delete new char;
#endif
SyncBlock *psb;
SyncBlock *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 = plst;
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;
}
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!
{
EX_THROW(EEMessageException, (kOutOfMemoryException, IDS_EE_OUT_OF_SYNCBLOCKS));
}
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();
newSyncTable.SuppressRelease();
newBitMap.SuppressRelease();
// We chain old table because we can't delete
// them before all the threads are stopped
// (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 ?
InterlockedExchangeT(&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;
}
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;
}
CONTRACTL_END;
// clean up comdata
if (psb->m_pInteropInfo)
{
#if defined(FEATURE_COMINTEROP)
CleanupSyncBlockComData(psb->m_pInteropInfo);
#endif // FEATURE_COMINTEROP
delete psb->m_pInteropInfo;
}
#ifdef FEATURE_METADATA_UPDATER
// clean up EnC info
if (psb->m_pEnCInfo)
psb->m_pEnCInfo->Cleanup();
#endif // FEATURE_METADATA_UPDATER
// Cleanup lock info
psb->m_thinLock.StoreWithoutBarrier(0);
if (psb->m_Lock)
{
DestroyHandle(psb->m_Lock);
psb->m_Lock = NULL;
}
// 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;
}
CONTRACTL_END
m_ActiveCount--;
m_FreeCount++;
psb->m_pNext = m_FreeBlockList;
m_FreeBlockList = psb;
}
// 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_pNext = m_FreeBlockList;
m_FreeBlockList = psb;
}
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 freeSyncTableIndexCopy = m_FreeSyncTableIndex;
SyncTableEntry * syncTableShadow = NULL;
if ((g_pConfig->GetHeapVerifyLevel()& EEConfig::HEAPVERIFY_SYNCBLK) && !((ScanContext*)lp1)->promotion)
{
syncTableShadow = new(nothrow) SyncTableEntry [m_FreeSyncTableIndex];
if (syncTableShadow)
{
memcpy ((void*)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 (freeSyncTableIndexCopy != 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 (o && ((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,
(void*)(size_t)SyncTableEntry::GetSyncTableEntry()[nb].m_SyncBlock,
(void*)(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,
(void*)(size_t)pSB, (void*)(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 %u\n", pCache->m_FreeSyncTableIndex);
static int dumpSBStyle = -1;
if (dumpSBStyle == -1)
dumpSBStyle = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SBDumpStyle);
if (dumpSBStyle == 0)
return;
DWORD objectCount = 0;
DWORD slotCount = 0;
for (DWORD nb = 1; nb < pCache->m_FreeSyncTableIndex; nb++)
{
char buffer[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;
} param;
param.descrip = descrip;
param.oref = oref;
PAL_TRY(Param *, pParam, ¶m)
{
pParam->descrip = pParam->oref->GetMethodTable()->GetDebugClassName();
if (strlen(pParam->descrip) == 0)
pParam->descrip = "";
}
PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
param.descrip = "";
}
PAL_ENDTRY
descrip = param.descrip;
}
sprintf_s(buffer, ARRAY_SIZE(buffer), "%s", descrip);
descrip = buffer;
}
if (dumpSBStyle < 2)
LogSpewAlways("[%4.4d]: %p %s\n", nb, (void*)oref, descrip);
else if (dumpSBStyle == 2)
LogSpewAlways("[%4.4d]: %s\n", nb, descrip);
}
LogSpewAlways("Done dumping SyncBlockCache used slots: %d, objects: %d\n", slotCount, objectCount);
}
#endif
// ***************************************************************************
//
// SpinLock implementation
//
// ***************************************************************************
namespace
{
void EnterSpinLock(Volatile* pLock)
{
STATIC_CONTRACT_GC_NOTRIGGER;
DWORD dwSwitchCount = 0;
while (TRUE)
{
// get the value so that it doesn't get changed under us.
LONG curValue = pLock->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 = InterlockedCompareExchange((LONG*)pLock, newValue, curValue);
if (result == curValue)
break;
}
if (g_SystemInfo.dwNumberOfProcessors > 1)
{
for (int spinCount = 0; spinCount < BIT_SBLK_SPIN_COUNT; spinCount++)
{
if (! (*pLock & BIT_SBLK_SPIN_LOCK))
break;
YieldProcessorNormalized(); // indicate to the processor that we are spinning
}
if (*pLock & BIT_SBLK_SPIN_LOCK)
__SwitchToThread(0, ++dwSwitchCount);
}
else
__SwitchToThread(0, ++dwSwitchCount);
}
}
void ReleaseSpinLock(Volatile* pLock)
{
LIMITED_METHOD_CONTRACT;
InterlockedAnd((LONG*)pLock, ~BIT_SBLK_SPIN_LOCK);
}
struct HeaderSpinLockHolder
{
Volatile* m_pLock;
HeaderSpinLockHolder(Volatile* pLock)
: m_pLock(pLock)
{
// Acquire the spin-lock in preemptive mode with GC_NOTRIGGER
// to avoid deadlocks with the GC.
CONTRACTL
{
GC_NOTRIGGER;
NOTHROW;
MODE_PREEMPTIVE;
}
CONTRACTL_END;
EnterSpinLock(m_pLock);
}
~HeaderSpinLockHolder()
{
LIMITED_METHOD_CONTRACT;
ReleaseSpinLock(m_pLock);
}
};
}
#endif //!DACCESS_COMPILE
// ***************************************************************************
//
// ObjHeader class implementation
//
// ***************************************************************************
#ifndef DACCESS_COMPILE
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.
STATIC_CONTRACT_GC_NOTRIGGER;
::EnterSpinLock(std::addressof(m_SyncBlockValue));
INCONTRACT(Thread* pThread = GetThreadNULLOk());
INCONTRACT(if (pThread != NULL) pThread->BeginNoTriggerGC(__FILE__, __LINE__));
}
DEBUG_NOINLINE void ObjHeader::ReleaseSpinLock()
{
INCONTRACT(Thread* pThread = GetThreadNULLOk());
INCONTRACT(if (pThread != NULL) pThread->EndNoTriggerGC());
::ReleaseSpinLock(std::addressof(m_SyncBlockValue));
}
DWORD ObjHeader::GetSyncBlockIndex()
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
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)
{
EnterSpinLock();
// 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()));
}
ReleaseSpinLock();
}
// 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
// BIT_UNUSED 0x80000000
// BIT_SBLK_FINALIZER_RUN 0x40000000
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 ())
{
ASSERT_AND_CHECK (GCHeapUtilities::GetGCHeap()->IsInFrozenSegment(obj));
}
}
//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 requirements. 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, 0> SyncBlockMemoryHolder;
// get the sync block for an existing object
SyncBlock *ObjHeader::GetSyncBlock()
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_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();
syncBlockMemoryHolder.SuppressRelease();
new (syncBlock) SyncBlock(indx);
{
// after this point, nobody can update the index in the header
EnterSpinLock();
{
// 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);
syncBlock->InitializeThinLock(recursionLevel, lockThreadId);
}
}
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();
ReleaseSpinLock();
}
}
// SyncBlockCache::LockHolder goes out of scope here
}
return syncBlock;
}
// ***************************************************************************
//
// SyncBlock class implementation
//
// ***************************************************************************
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 (InterlockedCompareExchangeT(&m_pInteropInfo,
pInteropInfo,
NULL) == NULL);
}
#ifdef FEATURE_METADATA_UPDATER
// 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 // FEATURE_METADATA_UPDATER
void SyncBlock::InitializeThinLock(DWORD recursionLevel, DWORD threadId)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(m_Lock == (OBJECTHANDLE)NULL);
_ASSERTE(m_thinLock == 0u);
m_thinLock.StoreWithoutBarrier((threadId & SBLK_MASK_LOCK_THREADID) | (recursionLevel << SBLK_RECLEVEL_SHIFT));
}
OBJECTHANDLE SyncBlock::GetOrCreateLock(OBJECTREF lockObj)
{
CONTRACTL
{
GC_TRIGGERS;
THROWS;
MODE_COOPERATIVE;
}
CONTRACTL_END;
OBJECTHANDLE existingLock = VolatileLoad(&m_Lock);
if (existingLock != (OBJECTHANDLE)NULL)
{
return existingLock;
}
SetPrecious();
// We'll likely need to put this lock object into the sync block.
// Create the handle here.
OBJECTHANDLEHolder lockHandle(GetAppDomain()->CreateHandle(lockObj));
if (TryUpgradeThinLockToFullLock(lockHandle))
{
// Our lock instance is the one in the sync block now.
return lockHandle.Detach();
}
return VolatileLoad(&m_Lock);
}
bool SyncBlock::TryUpgradeThinLockToFullLock(OBJECTHANDLE lockHandle)
{
CONTRACTL
{
GC_TRIGGERS;
THROWS;
MODE_COOPERATIVE;
}
CONTRACTL_END;
// Switch to preemptive so we can grab the spin-lock.
// Use the NO_DTOR version so we don't do a coop->preemptive->coop transition on return.
GCX_PREEMP_NO_DTOR();
HeaderSpinLockHolder lock(std::addressof(m_thinLock));
// We don't need to be in preemptive any more here.
GCX_PREEMP_NO_DTOR_END();
// Check again now that we hold the spin-lock
if (m_Lock != (OBJECTHANDLE)NULL)
{
return false;
}
// We need to create a new lock
// Grab the bits that are interesting for thin-lock info.
// This way we only call back into managed code
// to initialize the lock when necessary.
DWORD thinLock = (m_thinLock.LoadWithoutBarrier() & ((SBLK_MASK_LOCK_THREADID) | (SBLK_MASK_LOCK_RECLEVEL)));
if (thinLock != 0)
{
DWORD lockThreadId = thinLock & SBLK_MASK_LOCK_THREADID;
DWORD recursionLevel = (thinLock & SBLK_MASK_LOCK_RECLEVEL) >> SBLK_RECLEVEL_SHIFT;
_ASSERTE(lockThreadId != 0);
// We have thin-lock info that needs to be transferred to the lock object.
OBJECTREF lockObj = ObjectFromHandle(lockHandle);
GCPROTECT_BEGIN(lockObj);
UnmanagedCallersOnlyCaller initializeForMonitor(METHOD__LOCK__INITIALIZE_FOR_MONITOR);
initializeForMonitor.InvokeThrowing(&lockObj, (int32_t)lockThreadId, (uint32_t)recursionLevel);
GCPROTECT_END();
}
VolatileStore(&m_Lock, lockHandle);
// Clear the thin lock info.
// It won't be used any more, but it will look out of date.
// Only clear the relevant bits, as the spin-lock bit is used to lock this method.
// That bit will be reset upon return.
m_thinLock.StoreWithoutBarrier(m_thinLock.LoadWithoutBarrier() & ~((SBLK_MASK_LOCK_THREADID) | (SBLK_MASK_LOCK_RECLEVEL)));
// Our lock instance is in the sync block now.
// Don't release it.
return true;
}
#endif // !DACCESS_COMPILE
BOOL SyncBlock::TryGetLockInfo(DWORD *pThreadId, DWORD *pRecursionLevel)
{
WRAPPER_NO_CONTRACT;
if (m_Lock != (OBJECTHANDLE)NULL)
{
GCX_COOP();
// Extract info from the lock object
OBJECTREF lockObj = ObjectFromHandle(m_Lock);
GCPROTECT_BEGIN(lockObj);
DWORD state = CoreLibBinder::GetField(FIELD__LOCK__STATE)->GetValue32(lockObj);
*pThreadId = CoreLibBinder::GetField(FIELD__LOCK__OWNING_THREAD_ID)->GetValue32(lockObj);
*pRecursionLevel = CoreLibBinder::GetField(FIELD__LOCK__RECURSION_COUNT)->GetValue32(lockObj);
return state & 1;
GCPROTECT_END();
}
else if (m_thinLock != 0u)
{
// Extract info from the thin lock
DWORD threadId = m_thinLock & SBLK_MASK_LOCK_THREADID;
*pThreadId = threadId;
*pRecursionLevel = (m_thinLock & SBLK_MASK_LOCK_RECLEVEL) >> SBLK_RECLEVEL_SHIFT;
return threadId != 0;
}
else
{
// No lock info available
*pThreadId = 0;
*pRecursionLevel = 0;
return FALSE;
}
}
#if defined(HOST_64BIT) && defined(_DEBUG)
void ObjHeader::IllegalAlignPad()
{
WRAPPER_NO_CONTRACT;
#ifdef LOGGING
void** object = ((void**) this) + 1;
STRESS_LOG1(LF_ASSERT, LL_ALWAYS, "\n\n******** Illegal ObjHeader m_alignpad not 0, m_alignpad value: %d\n", m_alignpad);
#endif
_ASSERTE(m_alignpad == 0);
}
#endif // HOST_64BIT && _DEBUG