diff options
Diffstat (limited to 'src/vm/rwlock.cpp')
-rw-r--r-- | src/vm/rwlock.cpp | 2952 |
1 files changed, 2952 insertions, 0 deletions
diff --git a/src/vm/rwlock.cpp b/src/vm/rwlock.cpp new file mode 100644 index 0000000000..9d8233d140 --- /dev/null +++ b/src/vm/rwlock.cpp @@ -0,0 +1,2952 @@ +// 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. +// + +// +//+------------------------------------------------------------------- +// +// File: RWLock.cpp +// +// Contents: Reader writer lock implementation that supports the +// following features +// 1. Cheap enough to be used in large numbers +// such as per object synchronization. +// 2. Supports timeout. This is a valuable feature +// to detect deadlocks +// 3. Supports caching of events. This allows +// the events to be moved from least contentious +// regions to the most contentious regions. +// In other words, the number of events needed by +// Reader-Writer lockls is bounded by the number +// of threads in the process. +// 4. Supports nested locks by readers and writers +// 5. Supports spin counts for avoiding context switches +// on multi processor machines. +// 6. Supports functionality for upgrading to a writer +// lock with a return argument that indicates +// intermediate writes. Downgrading from a writer +// lock restores the state of the lock. +// 7. Supports functionality to Release Lock for calling +// app code. RestoreLock restores the lock state and +// indicates intermediate writes. +// 8. Recovers from most common failures such as creation of +// events. In other words, the lock mainitains consistent +// internal state and remains usable +// +// +// Classes: CRWLock +// +//-------------------------------------------------------------------- + + +#include "common.h" +#include "rwlock.h" +#include "corhost.h" + +#ifdef FEATURE_RWLOCK + +// Reader increment +#define READER 0x00000001 +// Max number of readers +#define READERS_MASK 0x000003FF +// Reader being signaled +#define READER_SIGNALED 0x00000400 +// Writer being signaled +#define WRITER_SIGNALED 0x00000800 +#define WRITER 0x00001000 +// Waiting reader increment +#define WAITING_READER 0x00002000 +// Note size of waiting readers must be less +// than or equal to size of readers +#define WAITING_READERS_MASK 0x007FE000 +#define WAITING_READERS_SHIFT 13 +// Waiting writer increment +#define WAITING_WRITER 0x00800000 +// Max number of waiting writers +#define WAITING_WRITERS_MASK 0xFF800000 +// Events are being cached +#define CACHING_EVENTS (READER_SIGNALED | WRITER_SIGNALED) + +// Cookie flags +#define UPGRADE_COOKIE 0x02000 +#define RELEASE_COOKIE 0x04000 +#define COOKIE_NONE 0x10000 +#define COOKIE_WRITER 0x20000 +#define COOKIE_READER 0x40000 +#define INVALID_COOKIE (~(UPGRADE_COOKIE | RELEASE_COOKIE | \ + COOKIE_NONE | COOKIE_WRITER | COOKIE_READER)) +#define RWLOCK_MAX_ACQUIRE_COUNT 0xFFFF + +// globals +Volatile<LONGLONG> CRWLock::s_mostRecentLockID = 0; +CrstStatic CRWLock::s_RWLockCrst; + +// Default values +#ifdef _DEBUG +DWORD gdwDefaultTimeout = 120000; +#else //!_DEBUG +DWORD gdwDefaultTimeout = INFINITE; +#endif //_DEBUG +const DWORD gdwReasonableTimeout = 120000; +DWORD gdwDefaultSpinCount = 0; +BOOL fBreakOnErrors = FALSE; // Temporarily break on errors + +// <REVISIT_TODO> REVISIT_TODO: Bad practise</REVISIT_TODO> +#define HEAP_SERIALIZE 0 +#define RWLOCK_RECOVERY_FAILURE (0xC0000227L) + +// Catch GC holes +#if _DEBUG +#define VALIDATE_LOCK(pRWLock) ((Object *) (pRWLock))->Validate(); +#else // !_DEBUG +#define VALIDATE_LOCK(pRWLock) +#endif // _DEBUG + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::ProcessInit public +// +// Synopsis: Reads default values from registry and intializes +// process wide data structures +// +//+------------------------------------------------------------------- +void CRWLock::ProcessInit() +{ + CONTRACTL + { + THROWS; // From Crst.Init() + GC_NOTRIGGER; + PRECONDITION((g_SystemInfo.dwNumberOfProcessors != 0)); + } + CONTRACTL_END; + + gdwDefaultSpinCount = (g_SystemInfo.dwNumberOfProcessors != 1) ? 500 : 0; + + PPEB peb = (PPEB) ClrTeb::GetProcessEnvironmentBlock(); + DWORD dwTimeout = (DWORD)(peb->CriticalSectionTimeout.QuadPart/-10000000); + if (dwTimeout) + { + gdwDefaultTimeout = dwTimeout; + } + + // Initialize the critical section used by the lock + // Can throw out of memory here. + s_RWLockCrst.Init(CrstRWLock, CRST_UNSAFE_ANYMODE); +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::CRWLock public +// +// Synopsis: Constructor +// +//+------------------------------------------------------------------- +CRWLock::CRWLock() +: _hWriterEvent(NULL), + _hReaderEvent(NULL), + _dwState(0), + _dwWriterID(0), + _dwWriterSeqNum(1), + _wWriterLevel(0) +#ifdef RWLOCK_STATISTICS + , + _dwReaderEntryCount(0), + _dwReaderContentionCount(0), + _dwWriterEntryCount(0), + _dwWriterContentionCount(0), + _dwEventsReleasedCount(0) +#endif +{ + + CONTRACT_VOID + { + NOTHROW; + GC_NOTRIGGER; + POSTCONDITION((_dwLLockID > 0)); + } + CONTRACT_END; + + LONGLONG qwLockID = s_mostRecentLockID; + while (true) + { + LONGLONG qwNextLockID = qwLockID + 1; + if (static_cast<LONG>(qwNextLockID) == 0) + { + // A value of zero for the lower half of the ID is reserved to identify that a thread does not own any RW locks, + // regardless of the upper half of the ID + ++qwNextLockID; + } + + LONGLONG qwLockIDBeforeExchange = RWInterlockedCompareExchange64(&s_mostRecentLockID, qwNextLockID, qwLockID); + if (qwLockIDBeforeExchange == qwLockID) + { + qwLockID = qwNextLockID; + break; + } + + qwLockID = qwLockIDBeforeExchange; + } + + _dwLLockID = static_cast<LONG>(qwLockID); + _dwULockID = static_cast<LONG>(qwLockID >> 32); + + RETURN; +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::Cleanup public +// +// Synopsis: Cleansup state +// +//+------------------------------------------------------------------- +void CRWLock::Cleanup() +{ + + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION((_dwState == 0)); // sanity checks + PRECONDITION((_dwWriterID == 0)); + PRECONDITION((_wWriterLevel == 0)); + } + CONTRACTL_END; + + if(_hWriterEvent) { + delete _hWriterEvent; + _hWriterEvent = NULL; + } + if(_hReaderEvent) { + delete _hReaderEvent; + _hReaderEvent = NULL; + } + + return; +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::ChainEntry private +// +// Synopsis: Chains the given lock entry into the chain +// +//+------------------------------------------------------------------- +inline void CRWLock::ChainEntry(Thread *pThread, LockEntry *pLockEntry) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // This is to synchronize with finalizer thread and deadlock detection. + CrstHolder rwl(&s_RWLockCrst); + LockEntry *pHeadEntry = pThread->m_pHead; + pLockEntry->pNext = pHeadEntry; + pLockEntry->pPrev = pHeadEntry->pPrev; + pLockEntry->pPrev->pNext = pLockEntry; + pHeadEntry->pPrev = pLockEntry; + + return; +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::GetLockEntry private +// +// Synopsis: Gets lock entry from TLS +// +//+------------------------------------------------------------------- +inline LockEntry *CRWLock::GetLockEntry(Thread* pThread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + if (pThread == NULL) { + pThread = GetThread(); + } + LockEntry *pHeadEntry = pThread->m_pHead; + LockEntry *pLockEntry = pHeadEntry; + do + { + if((pLockEntry->dwLLockID == _dwLLockID) && (pLockEntry->dwULockID == _dwULockID)) + return(pLockEntry); + pLockEntry = pLockEntry->pNext; + } while(pLockEntry != pHeadEntry); + + return(NULL); +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::FastGetOrCreateLockEntry private +// +// Synopsis: The fast path for getting a lock entry from TLS +// +//+------------------------------------------------------------------- +inline LockEntry *CRWLock::FastGetOrCreateLockEntry() +{ + + CONTRACTL + { + THROWS; // SlowGetOrCreateLockEntry can throw out of memory exception + GC_NOTRIGGER; + } + CONTRACTL_END; + + Thread *pThread = GetThread(); + _ASSERTE(pThread); + LockEntry *pLockEntry = pThread->m_pHead; + if(pLockEntry->dwLLockID == 0) + { + _ASSERTE(pLockEntry->wReaderLevel == 0); + pLockEntry->dwLLockID = _dwLLockID; + pLockEntry->dwULockID = _dwULockID; + return(pLockEntry); + } + else if((pLockEntry->dwLLockID == _dwLLockID) && (pLockEntry->dwULockID == _dwULockID)) + { + // Note, StaticAcquireReaderLock can have reentry via pumping while it's blocking + // so no assertions about pLockEntry->wReaderLevel's state + return(pLockEntry); + } + + return(SlowGetOrCreateLockEntry(pThread)); +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::SlowGetorCreateLockEntry private +// +// Synopsis: The slow path for getting a lock entry from TLS +// +//+------------------------------------------------------------------- +LockEntry *CRWLock::SlowGetOrCreateLockEntry(Thread *pThread) +{ + + CONTRACTL + { + THROWS; // memory allocation can throw out of memory exception + GC_NOTRIGGER; + } + CONTRACTL_END; + + LockEntry *pFreeEntry = NULL; + LockEntry *pHeadEntry = pThread->m_pHead; + + // Search for an empty entry or an entry belonging to this lock + LockEntry *pLockEntry = pHeadEntry->pNext; + while(pLockEntry != pHeadEntry) + { + if(pLockEntry->dwLLockID && + ((pLockEntry->dwLLockID != _dwLLockID) || (pLockEntry->dwULockID != _dwULockID))) + { + // Move to the next entry + pLockEntry = pLockEntry->pNext; + } + else + { + // Prepare to move it to the head + pFreeEntry = pLockEntry; + pLockEntry->pPrev->pNext = pLockEntry->pNext; + pLockEntry->pNext->pPrev = pLockEntry->pPrev; + + break; + } + } + + if(pFreeEntry == NULL) + { + pFreeEntry = new LockEntry; + pFreeEntry->wReaderLevel = 0; + } + + if(pFreeEntry) + { + _ASSERTE((pFreeEntry->dwLLockID != 0) || (pFreeEntry->wReaderLevel == 0)); + _ASSERTE((pFreeEntry->wReaderLevel == 0) || + ((pFreeEntry->dwLLockID == _dwLLockID) && (pFreeEntry->dwULockID == _dwULockID))); + + // Chain back the entry + ChainEntry(pThread, pFreeEntry); + + // Move this entry to the head + pThread->m_pHead = pFreeEntry; + + // Mark the entry as belonging to this lock + pFreeEntry->dwLLockID = _dwLLockID; + pFreeEntry->dwULockID = _dwULockID; + } + + return pFreeEntry; +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::FastRecycleLockEntry private +// +// Synopsis: Fast path for recycling the lock entry that is used +// when the thread is the next few instructions is going +// to call FastGetOrCreateLockEntry again +// +//+------------------------------------------------------------------- +inline void CRWLock::FastRecycleLockEntry(LockEntry *pLockEntry) +{ + + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + + // Sanity checks + PRECONDITION(pLockEntry->wReaderLevel == 0); + PRECONDITION((pLockEntry->dwLLockID == _dwLLockID) && (pLockEntry->dwULockID == _dwULockID)); + PRECONDITION(pLockEntry == GetThread()->m_pHead); + } + CONTRACTL_END; + + + pLockEntry->dwLLockID = 0; +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::RecycleLockEntry private +// +// Synopsis: Fast path for recycling the lock entry +// +//+------------------------------------------------------------------- +inline void CRWLock::RecycleLockEntry(LockEntry *pLockEntry) +{ + + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + + // Sanity check + PRECONDITION(pLockEntry->wReaderLevel == 0); + } + CONTRACTL_END; + + // Move the entry to tail + Thread *pThread = GetThread(); + LockEntry *pHeadEntry = pThread->m_pHead; + if(pLockEntry == pHeadEntry) + { + pThread->m_pHead = pHeadEntry->pNext; + } + else if(pLockEntry->pNext->dwLLockID) + { + // Prepare to move the entry to tail + pLockEntry->pPrev->pNext = pLockEntry->pNext; + pLockEntry->pNext->pPrev = pLockEntry->pPrev; + + // Chain back the entry + ChainEntry(pThread, pLockEntry); + } + + // The entry does not belong to this lock anymore + pLockEntry->dwLLockID = 0; + return; +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticIsWriterLockHeld public +// +// Synopsis: Return TRUE if writer lock is held +// +//+------------------------------------------------------------------- +FCIMPL1(FC_BOOL_RET, CRWLock::StaticIsWriterLockHeld, CRWLock *pRWLock) +{ + FCALL_CONTRACT; + + if (pRWLock == NULL) + { + FCThrow(kNullReferenceException); + } + + if(pRWLock->_dwWriterID == GetThread()->GetThreadId()) + FC_RETURN_BOOL(TRUE); + + FC_RETURN_BOOL(FALSE); +} +FCIMPLEND + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticIsReaderLockHeld public +// +// Synopsis: Return TRUE if reader lock is held +// +//+------------------------------------------------------------------- +FCIMPL1(FC_BOOL_RET, CRWLock::StaticIsReaderLockHeld, CRWLock *pRWLock) +{ + FCALL_CONTRACT; + + if (pRWLock == NULL) + { + FCThrow(kNullReferenceException); + } + + LockEntry *pLockEntry = pRWLock->GetLockEntry(); + if(pLockEntry) + { + FC_RETURN_BOOL(pLockEntry->wReaderLevel > 0); + } + + FC_RETURN_BOOL(FALSE); +} +FCIMPLEND + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::AssertWriterLockHeld public +// +// Synopsis: Asserts that writer lock is held +// +//+------------------------------------------------------------------- +#ifdef _DEBUG +BOOL CRWLock::AssertWriterLockHeld() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + if(_dwWriterID == GetThread()->GetThreadId()) + return(TRUE); + + _ASSERTE(!"Writer lock not held by the current thread"); + return(FALSE); +} +#endif + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::AssertWriterLockNotHeld public +// +// Synopsis: Asserts that writer lock is not held +// +//+------------------------------------------------------------------- +#ifdef _DEBUG +BOOL CRWLock::AssertWriterLockNotHeld() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + if(_dwWriterID != GetThread()->GetThreadId()) + return(TRUE); + + _ASSERTE(!"Writer lock held by the current thread"); + return(FALSE); +} +#endif + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::AssertReaderLockHeld public +// +// Synopsis: Asserts that reader lock is held +// +//+------------------------------------------------------------------- +#ifdef _DEBUG +BOOL CRWLock::AssertReaderLockHeld() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + LockEntry *pLockEntry = GetLockEntry(); + if(pLockEntry) + { + _ASSERTE(pLockEntry->wReaderLevel); + return(TRUE); + } + + _ASSERTE(!"Reader lock not held by the current thread"); + return(FALSE); +} +#endif + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::AssertReaderLockNotHeld public +// +// Synopsis: Asserts that writer lock is not held +// +//+------------------------------------------------------------------- +#ifdef _DEBUG +BOOL CRWLock::AssertReaderLockNotHeld() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + LockEntry *pLockEntry = GetLockEntry(); + if(pLockEntry == NULL) + return(TRUE); + + _ASSERTE(pLockEntry->wReaderLevel); + _ASSERTE(!"Reader lock held by the current thread"); + + return(FALSE); +} +#endif + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::AssertReaderOrWriterLockHeld public +// +// Synopsis: Asserts that writer lock is not held +// +//+------------------------------------------------------------------- +#ifdef _DEBUG +BOOL CRWLock::AssertReaderOrWriterLockHeld() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + if(_dwWriterID == GetThread()->GetThreadId()) + { + return(TRUE); + } + else + { + LockEntry *pLockEntry = GetLockEntry(); + if(pLockEntry) + { + _ASSERTE(pLockEntry->wReaderLevel); + return(TRUE); + } + } + + _ASSERTE(!"Neither Reader nor Writer lock held"); + return(FALSE); +} +#endif + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::RWSetEvent private +// +// Synopsis: Helper function for setting an event +// +//+------------------------------------------------------------------- +inline void CRWLock::RWSetEvent(CLREvent* event) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + if(!event->Set()) + { + _ASSERTE(!"SetEvent failed"); + if(fBreakOnErrors) // fBreakOnErrors == FALSE so will be optimized out. + DebugBreak(); + COMPlusThrowWin32(E_UNEXPECTED); + } +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::RWResetEvent private +// +// Synopsis: Helper function for resetting an event +// +//+------------------------------------------------------------------- +inline void CRWLock::RWResetEvent(CLREvent* event) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + if(!event->Reset()) + { + _ASSERTE(!"ResetEvent failed"); + if(fBreakOnErrors) // fBreakOnErrors == FALSE so will be optimized out. + DebugBreak(); + COMPlusThrowWin32(E_UNEXPECTED); + } +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::RWWaitForSingleObject public +// +// Synopsis: Helper function for waiting on an event +// +//+------------------------------------------------------------------- +inline DWORD CRWLock::RWWaitForSingleObject(CLREvent* event, DWORD dwTimeout) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + } + CONTRACTL_END; + + DWORD status = WAIT_FAILED; + EX_TRY + { + status = event->Wait(dwTimeout,TRUE); + } + EX_CATCH + { + status = GET_EXCEPTION()->GetHR(); + if (status == S_OK) + { + status = WAIT_FAILED; + } + } + EX_END_CATCH(SwallowAllExceptions); // The caller will rethrow the exception + + return status; +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::RWSleep public +// +// Synopsis: Helper function for calling Sleep +// +//+------------------------------------------------------------------- +inline void CRWLock::RWSleep(DWORD dwTime) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + ClrSleepEx(dwTime, TRUE); +} + + +#undef volatile + +//+------------------------------------------------------------------- +// +// Method: CRWLock::RWInterlockedCompareExchange public +// +// Synopsis: Helper function for calling intelockedCompareExchange +// +//+------------------------------------------------------------------- +inline LONG CRWLock::RWInterlockedCompareExchange(LONG volatile *pvDestination, + LONG dwExchange, + LONG dwComparand) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + return FastInterlockCompareExchange(pvDestination, + dwExchange, + dwComparand); +} + +inline LONGLONG CRWLock::RWInterlockedCompareExchange64(LONGLONG volatile *pvDestination, + LONGLONG qwExchange, + LONGLONG qwComparand) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + return FastInterlockCompareExchangeLong(pvDestination, qwExchange, qwComparand); +} + +inline void* CRWLock::RWInterlockedCompareExchangePointer(PVOID volatile *pvDestination, + void* pExchange, + void* pComparand) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + return FastInterlockCompareExchangePointer(pvDestination, + pExchange, + pComparand); +} + +//+------------------------------------------------------------------- +// +// Method: CRWLock::RWInterlockedExchangeAdd public +// +// Synopsis: Helper function for adding state +// +//+------------------------------------------------------------------- +inline LONG CRWLock::RWInterlockedExchangeAdd(LONG volatile *pvDestination, + LONG dwAddToState) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + return FastInterlockExchangeAdd(pvDestination, dwAddToState); +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::RWInterlockedIncrement public +// +// Synopsis: Helper function for incrementing a pointer +// +//+------------------------------------------------------------------- +inline LONG CRWLock::RWInterlockedIncrement(LONG volatile *pdwState) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + return FastInterlockIncrement(pdwState); +} + +#define volatile DoNotUserVolatileKeyword + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::ReleaseEvents public +// +// Synopsis: Helper function for caching events +// +//+------------------------------------------------------------------- +void CRWLock::ReleaseEvents() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + PRECONDITION(((_dwState & CACHING_EVENTS) == CACHING_EVENTS)); // Ensure that reader and writers have been stalled + + } + CONTRACTL_END; + + // Save writer event + CLREvent *hWriterEvent = _hWriterEvent; + _hWriterEvent = NULL; + + // Save reader event + CLREvent *hReaderEvent = _hReaderEvent; + _hReaderEvent = NULL; + + // Allow readers and writers to continue + RWInterlockedExchangeAdd(&_dwState, -(CACHING_EVENTS)); + + // Cache events + // <REVISIT_TODO>: + // I am closing events for now. What is needed + // is an event cache to which the events are + // released using InterlockedCompareExchange64</REVISIT_TODO> + if(hWriterEvent) + { + LOG((LF_SYNC, LL_INFO10, "Releasing writer event\n")); + delete hWriterEvent; + } + if(hReaderEvent) + { + LOG((LF_SYNC, LL_INFO10, "Releasing reader event\n")); + delete hReaderEvent; + } +#ifdef RWLOCK_STATISTICS + RWInterlockedIncrement(&_dwEventsReleasedCount); +#endif + + return; +} + +//+------------------------------------------------------------------- +// +// Method: CRWLock::GetWriterEvent public +// +// Synopsis: Helper function for obtaining a auto reset event used +// for serializing writers. It utilizes event cache +// +//+------------------------------------------------------------------- +CLREvent* CRWLock::GetWriterEvent(HRESULT *pHR) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + } + CONTRACTL_END; + + *pHR = S_OK; + //GC could happen in ~CLREvent or EH. "this" is a GC object so it could be moved + //during GC. So we need to cache the field before GC could happen + CLREvent * result = _hWriterEvent; + + if(_hWriterEvent == NULL) + { + EX_TRY + { + CLREvent *pEvent = new CLREvent(); + NewHolder<CLREvent> hWriterEvent (pEvent); + hWriterEvent->CreateRWLockWriterEvent(FALSE,this); + if(hWriterEvent) + { + if(RWInterlockedCompareExchangePointer((PVOID*) &_hWriterEvent, + hWriterEvent.GetValue(), + NULL) == NULL) + { + hWriterEvent.SuppressRelease(); + } + //GC could happen in ~CLREvent or EH. "this" is a GC object so it could be moved + //during GC. So we need to cache the field before GC could happen. + result = _hWriterEvent; + } + } + EX_CATCH + { + *pHR = GET_EXCEPTION()->GetHR(); + } + EX_END_CATCH(SwallowAllExceptions); + } + + return(result); +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::GetReaderEvent public +// +// Synopsis: Helper function for obtaining a manula reset event used +// by readers to wait when a writer holds the lock. +// It utilizes event cache +// +//+------------------------------------------------------------------- +CLREvent* CRWLock::GetReaderEvent(HRESULT *pHR) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + } + CONTRACTL_END; + + *pHR = S_OK; + //GC could happen in ~CLREvent or EH. "this" is a GC object so it could be moved + //during GC. So we need to cache the field before GC could happen + CLREvent * result = _hReaderEvent; + + if(_hReaderEvent == NULL) + { + EX_TRY + { + CLREvent *pEvent = new CLREvent(); + NewHolder<CLREvent> hReaderEvent (pEvent); + hReaderEvent->CreateRWLockReaderEvent(FALSE, this); + if(hReaderEvent) + { + if(RWInterlockedCompareExchangePointer((PVOID*) &_hReaderEvent, + hReaderEvent.GetValue(), + NULL) == NULL) + { + hReaderEvent.SuppressRelease(); + } + //GC could happen in ~CLREvent or EH. "this" is a GC object so it could be moved + //during GC. So we need to cache the field before GC could happen + result = _hReaderEvent; + } + } + EX_CATCH + { + *pHR = GET_EXCEPTION()->GetHR(); + } + EX_END_CATCH(SwallowAllExceptions); + } + + return(result); +} + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticRecoverLock public +// +// Synopsis: Helper function to restore the lock to +// the original state +// + +// +//+------------------------------------------------------------------- +void CRWLock::StaticRecoverLock( + CRWLock **ppRWLock, + LockCookie *pLockCookie, + DWORD dwFlags) +{ + CONTRACTL + { + THROWS; // StaticAcquireWriterLock can throw exception + GC_TRIGGERS; + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + DWORD dwTimeout = (gdwDefaultTimeout > gdwReasonableTimeout) + ? gdwDefaultTimeout + : gdwReasonableTimeout; + + Thread *pThread = GetThread(); + _ASSERTE (pThread); + + EX_TRY + { + // Check if the thread was a writer + if(dwFlags & COOKIE_WRITER) + { + // Acquire writer lock + StaticAcquireWriterLock(ppRWLock, dwTimeout); + _ASSERTE (pThread->m_dwLockCount >= (*ppRWLock)->_wWriterLevel); + ASSERT_UNLESS_NO_DEBUG_STATE(__pClrDebugState->GetLockCount(kDbgStateLockType_User) >= (*ppRWLock)->_wWriterLevel); + pThread->m_dwLockCount -= (*ppRWLock)->_wWriterLevel; + USER_LOCK_RELEASED_MULTIPLE((*ppRWLock)->_wWriterLevel, GetPtrForLockContract(ppRWLock)); + (*ppRWLock)->_wWriterLevel = pLockCookie->wWriterLevel; + pThread->m_dwLockCount += (*ppRWLock)->_wWriterLevel; + USER_LOCK_TAKEN_MULTIPLE((*ppRWLock)->_wWriterLevel, GetPtrForLockContract(ppRWLock)); + } + // Check if the thread was a reader + else if(dwFlags & COOKIE_READER) + { + StaticAcquireReaderLock(ppRWLock, dwTimeout); + LockEntry *pLockEntry = (*ppRWLock)->GetLockEntry(); + _ASSERTE(pLockEntry); + _ASSERTE (pThread->m_dwLockCount >= pLockEntry->wReaderLevel); + ASSERT_UNLESS_NO_DEBUG_STATE(__pClrDebugState->GetLockCount(kDbgStateLockType_User) >= pLockEntry->wReaderLevel); + pThread->m_dwLockCount -= pLockEntry->wReaderLevel; + USER_LOCK_RELEASED_MULTIPLE(pLockEntry->wReaderLevel, GetPtrForLockContract(ppRWLock)); + pLockEntry->wReaderLevel = pLockCookie->wReaderLevel; + pThread->m_dwLockCount += pLockEntry->wReaderLevel; + USER_LOCK_TAKEN_MULTIPLE(pLockEntry->wReaderLevel, GetPtrForLockContract(ppRWLock)); + } + } + EX_CATCH + { + // Removed an assert here. This error is expected in case of + // ThreadAbort. + COMPlusThrowWin32(RWLOCK_RECOVERY_FAILURE); + } + EX_END_CATCH_UNREACHABLE +} + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticAcquireReaderLockPublic public +// +// Synopsis: Public access to StaticAcquireReaderLock +// +//+------------------------------------------------------------------- +FCIMPL2(void, CRWLock::StaticAcquireReaderLockPublic, CRWLock *pRWLockUNSAFE, DWORD dwDesiredTimeout) +{ + FCALL_CONTRACT; + + if (pRWLockUNSAFE == NULL) + { + FCThrowVoid(kNullReferenceException); + } + + OBJECTREF pRWLock = ObjectToOBJECTREF((Object*)pRWLockUNSAFE); + HELPER_METHOD_FRAME_BEGIN_1(pRWLock); + + StaticAcquireReaderLock((CRWLock**)&pRWLock, dwDesiredTimeout); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticAcquireReaderLock private +// +// Synopsis: Makes the thread a reader. Supports nested reader locks. +// +//+------------------------------------------------------------------- + +void CRWLock::StaticAcquireReaderLock( + CRWLock **ppRWLock, + DWORD dwDesiredTimeout) +{ + + CONTRACTL + { + THROWS; + GC_TRIGGERS; // CLREvent::Wait is GC_TRIGGERS + CAN_TAKE_LOCK; + PRECONDITION(CheckPointer(ppRWLock)); + PRECONDITION(CheckPointer(*ppRWLock)); + } + CONTRACTL_END; + + TESTHOOKCALL(AppDomainCanBeUnloaded(GetThread()->GetDomain()->GetId().m_dwId,FALSE)); + + if (GetThread()->IsAbortRequested()) { + GetThread()->HandleThreadAbort(); + } + + LockEntry *pLockEntry = (*ppRWLock)->FastGetOrCreateLockEntry(); + if (pLockEntry == NULL) + { + COMPlusThrowWin32(STATUS_NO_MEMORY); + } + + DWORD dwStatus = WAIT_OBJECT_0; + // Check for the fast path + if(RWInterlockedCompareExchange(&(*ppRWLock)->_dwState, READER, 0) == 0) + { + _ASSERTE(pLockEntry->wReaderLevel == 0); + } + // Check for nested reader + else if(pLockEntry->wReaderLevel != 0) + { + _ASSERTE((*ppRWLock)->_dwState & READERS_MASK); + + if (pLockEntry->wReaderLevel == RWLOCK_MAX_ACQUIRE_COUNT) { + COMPlusThrow(kOverflowException, W("Overflow_UInt16")); + } + ++pLockEntry->wReaderLevel; + INCTHREADLOCKCOUNT(); + USER_LOCK_TAKEN(GetPtrForLockContract(ppRWLock)); + return; + } + // Check if the thread already has writer lock + else if((*ppRWLock)->_dwWriterID == GetThread()->GetThreadId()) + { + StaticAcquireWriterLock(ppRWLock, dwDesiredTimeout); + (*ppRWLock)->FastRecycleLockEntry(pLockEntry); + return; + } + else + { + DWORD dwSpinCount; + DWORD dwCurrentState, dwKnownState; + + // Initialize + dwSpinCount = 0; + dwCurrentState = (*ppRWLock)->_dwState; + do + { + dwKnownState = dwCurrentState; + + // Reader need not wait if there are only readers and no writer + if((dwKnownState < READERS_MASK) || + (((dwKnownState & READER_SIGNALED) && ((dwKnownState & WRITER) == 0)) && + (((dwKnownState & READERS_MASK) + + ((dwKnownState & WAITING_READERS_MASK) >> WAITING_READERS_SHIFT)) <= + (READERS_MASK - 2)))) + { + // Add to readers + dwCurrentState = RWInterlockedCompareExchange(&(*ppRWLock)->_dwState, + (dwKnownState + READER), + dwKnownState); + if(dwCurrentState == dwKnownState) + { + // One more reader + break; + } + } + // Check for too many Readers or waiting readers or signaling in progress + else if(((dwKnownState & READERS_MASK) == READERS_MASK) || + ((dwKnownState & WAITING_READERS_MASK) == WAITING_READERS_MASK) || + ((dwKnownState & CACHING_EVENTS) == READER_SIGNALED)) + { + // Sleep + GetThread()->UserSleep(1000); + + // Update to latest state + dwSpinCount = 0; + dwCurrentState = (*ppRWLock)->_dwState; + } + // Check if events are being cached + else if((dwKnownState & CACHING_EVENTS) == CACHING_EVENTS) + { + if(++dwSpinCount > gdwDefaultSpinCount) + { + RWSleep(1); + dwSpinCount = 0; + } + dwCurrentState = (*ppRWLock)->_dwState; + } + // Check spin count + else if(++dwSpinCount <= gdwDefaultSpinCount) + { + dwCurrentState = (*ppRWLock)->_dwState; + } + else + { + // Add to waiting readers + dwCurrentState = RWInterlockedCompareExchange(&(*ppRWLock)->_dwState, + (dwKnownState + WAITING_READER), + dwKnownState); + if(dwCurrentState == dwKnownState) + { + CLREvent* hReaderEvent; + DWORD dwModifyState; + + // One more waiting reader +#ifdef RWLOCK_STATISTICS + RWInterlockedIncrement(&(*ppRWLock)->_dwReaderContentionCount); +#endif + HRESULT hr; + hReaderEvent = (*ppRWLock)->GetReaderEvent(&hr); + if(hReaderEvent) + { + dwStatus = RWWaitForSingleObject(hReaderEvent, dwDesiredTimeout); + VALIDATE_LOCK(*ppRWLock); + + // StaticAcquireReaderLock can have reentry via pumping while waiting for + // hReaderEvent, which may change pLockEntry's state from underneath us. + if ((pLockEntry->dwLLockID != (*ppRWLock)->_dwLLockID) || + (pLockEntry->dwULockID != (*ppRWLock)->_dwULockID)) + { + pLockEntry = (*ppRWLock)->FastGetOrCreateLockEntry(); + if (pLockEntry == NULL) + { + COMPlusThrowWin32(STATUS_NO_MEMORY); + } + } + } + else + { + LOG((LF_SYNC, LL_WARNING, + "AcquireReaderLock failed to create reader " + "event for RWLock 0x%x\n", *ppRWLock)); + dwStatus = E_FAIL; + } + + if(dwStatus == WAIT_OBJECT_0) + { + _ASSERTE((*ppRWLock)->_dwState & READER_SIGNALED); + _ASSERTE(((*ppRWLock)->_dwState & READERS_MASK) < READERS_MASK); + dwModifyState = READER - WAITING_READER; + } + else + { + dwModifyState = (DWORD) -WAITING_READER; + if(dwStatus == WAIT_TIMEOUT) + { + LOG((LF_SYNC, LL_WARNING, + "Timed out trying to acquire reader lock " + "for RWLock 0x%x\n", *ppRWLock)); + hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + } + else if(dwStatus == WAIT_IO_COMPLETION) + { + LOG((LF_SYNC, LL_WARNING, + "Thread interrupted while trying to acquire reader lock " + "for RWLock 0x%x\n", *ppRWLock)); + hr = COR_E_THREADINTERRUPTED; + } + else if (dwStatus == WAIT_FAILED) + { + if (SUCCEEDED(hr)) + { + dwStatus = GetLastError(); + if (dwStatus == WAIT_OBJECT_0) + { + dwStatus = WAIT_FAILED; + } + hr = HRESULT_FROM_WIN32(dwStatus); + LOG((LF_SYNC, LL_WARNING, + "WaitForSingleObject on Event 0x%x failed for " + "RWLock 0x%x with status code 0x%x\n", + hReaderEvent, *ppRWLock, dwStatus)); + } + } + } + + // One less waiting reader and he may have become a reader + dwKnownState = RWInterlockedExchangeAdd(&(*ppRWLock)->_dwState, dwModifyState); + + // Check for last signaled waiting reader + if(dwStatus == WAIT_OBJECT_0) + { + _ASSERTE(dwKnownState & READER_SIGNALED); + _ASSERTE((dwKnownState & READERS_MASK) < READERS_MASK); + if((dwKnownState & WAITING_READERS_MASK) == WAITING_READER) + { + // Reset the event and lower reader signaled flag + RWResetEvent(hReaderEvent); + RWInterlockedExchangeAdd(&(*ppRWLock)->_dwState, -READER_SIGNALED); + } + } + else + { + if(((dwKnownState & WAITING_READERS_MASK) == WAITING_READER) && + (dwKnownState & READER_SIGNALED)) + { + HRESULT hr1; + if(hReaderEvent == NULL) + hReaderEvent = (*ppRWLock)->GetReaderEvent(&hr1); + _ASSERTE(hReaderEvent); + + // Ensure the event is signalled before resetting it. + DWORD dwTemp; + dwTemp = hReaderEvent->Wait(INFINITE, FALSE); + _ASSERTE(dwTemp == WAIT_OBJECT_0); + _ASSERTE(((*ppRWLock)->_dwState & READERS_MASK) < READERS_MASK); + + // Reset the event and lower reader signaled flag + RWResetEvent(hReaderEvent); + RWInterlockedExchangeAdd(&(*ppRWLock)->_dwState, (READER - READER_SIGNALED)); + + // Honor the orginal status + ++pLockEntry->wReaderLevel; + INCTHREADLOCKCOUNT(); + USER_LOCK_TAKEN(GetPtrForLockContract(ppRWLock)); + StaticReleaseReaderLock(ppRWLock); + } + else + { + (*ppRWLock)->FastRecycleLockEntry(pLockEntry); + } + + _ASSERTE((pLockEntry == NULL) || + ((pLockEntry->dwLLockID == 0) && + (pLockEntry->wReaderLevel == 0))); + if(fBreakOnErrors) // fBreakOnErrors == FALSE so will be optimized out. + { + _ASSERTE(!"Failed to acquire reader lock"); + DebugBreak(); + } + + // Prepare the frame for throwing an exception + if ((DWORD)HOST_E_DEADLOCK == dwStatus) + { + // So that the error message is in the exception. + RaiseDeadLockException(); + } else if ((DWORD)COR_E_THREADINTERRUPTED == dwStatus) { + COMPlusThrow(kThreadInterruptedException); + } + else + { + COMPlusThrowWin32 (hr); + } + } + + // Sanity check + _ASSERTE(dwStatus == WAIT_OBJECT_0); + break; + } + } + YieldProcessor(); // Indicate to the processor that we are spining + } while(TRUE); + } + + // Success + _ASSERTE(dwStatus == WAIT_OBJECT_0); + _ASSERTE(((*ppRWLock)->_dwState & WRITER) == 0); + _ASSERTE((*ppRWLock)->_dwState & READERS_MASK); + ++pLockEntry->wReaderLevel; + INCTHREADLOCKCOUNT(); + USER_LOCK_TAKEN(GetPtrForLockContract(ppRWLock)); +#ifdef RWLOCK_STATISTICS + RWInterlockedIncrement(&(*ppRWLock)->_dwReaderEntryCount); +#endif + return; +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticAcquireWriterLockPublic public +// +// Synopsis: Public access to StaticAcquireWriterLock +// +//+------------------------------------------------------------------- +FCIMPL2(void, CRWLock::StaticAcquireWriterLockPublic, CRWLock *pRWLockUNSAFE, DWORD dwDesiredTimeout) +{ + FCALL_CONTRACT; + + if (pRWLockUNSAFE == NULL) + { + FCThrowVoid(kNullReferenceException); + } + + OBJECTREF pRWLock = ObjectToOBJECTREF((Object*)pRWLockUNSAFE); + HELPER_METHOD_FRAME_BEGIN_1(pRWLock); + + StaticAcquireWriterLock((CRWLock**)&pRWLock, dwDesiredTimeout); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticAcquireWriterLock private +// +// Synopsis: Makes the thread a writer. Supports nested writer +// locks +// +//+------------------------------------------------------------------- + +void CRWLock::StaticAcquireWriterLock( + CRWLock **ppRWLock, + DWORD dwDesiredTimeout) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; // CLREvent::Wait can trigger GC + CAN_TAKE_LOCK; + PRECONDITION((CheckPointer(ppRWLock))); + PRECONDITION((CheckPointer(*ppRWLock))); + } + CONTRACTL_END; + + TESTHOOKCALL(AppDomainCanBeUnloaded(GetThread()->GetDomain()->GetId().m_dwId,FALSE)); + if (GetThread()->IsAbortRequested()) { + GetThread()->HandleThreadAbort(); + } + + // Declare locals needed for setting up frame + DWORD dwThreadID = GetThread()->GetThreadId(); + DWORD dwStatus; + + // Check for the fast path + if(RWInterlockedCompareExchange(&(*ppRWLock)->_dwState, WRITER, 0) == 0) + { + _ASSERTE(((*ppRWLock)->_dwState & READERS_MASK) == 0); + } + // Check if the thread already has writer lock + else if((*ppRWLock)->_dwWriterID == dwThreadID) + { + if ((*ppRWLock)->_wWriterLevel == RWLOCK_MAX_ACQUIRE_COUNT) { + COMPlusThrow(kOverflowException, W("Overflow_UInt16")); + } + ++(*ppRWLock)->_wWriterLevel; + INCTHREADLOCKCOUNT(); + USER_LOCK_TAKEN(GetPtrForLockContract(ppRWLock)); + return; + } + else + { + DWORD dwCurrentState, dwKnownState; + DWORD dwSpinCount; + + // Initialize + dwSpinCount = 0; + dwCurrentState = (*ppRWLock)->_dwState; + do + { + dwKnownState = dwCurrentState; + + // Writer need not wait if there are no readers and writer + if((dwKnownState == 0) || (dwKnownState == CACHING_EVENTS)) + { + // Can be a writer + dwCurrentState = RWInterlockedCompareExchange(&(*ppRWLock)->_dwState, + (dwKnownState + WRITER), + dwKnownState); + if(dwCurrentState == dwKnownState) + { + // Only writer + break; + } + } + // Check for too many waiting writers + else if(((dwKnownState & WAITING_WRITERS_MASK) == WAITING_WRITERS_MASK)) + { + // Sleep + GetThread()->UserSleep(1000); + + // Update to latest state + dwSpinCount = 0; + dwCurrentState = (*ppRWLock)->_dwState; + } + // Check if events are being cached + else if((dwKnownState & CACHING_EVENTS) == CACHING_EVENTS) + { + if(++dwSpinCount > gdwDefaultSpinCount) + { + RWSleep(1); + dwSpinCount = 0; + } + dwCurrentState = (*ppRWLock)->_dwState; + } + // Check spin count + else if(++dwSpinCount <= gdwDefaultSpinCount) + { + dwCurrentState = (*ppRWLock)->_dwState; + } + else + { + // Add to waiting writers + dwCurrentState = RWInterlockedCompareExchange(&(*ppRWLock)->_dwState, + (dwKnownState + WAITING_WRITER), + dwKnownState); + if(dwCurrentState == dwKnownState) + { + CLREvent* hWriterEvent; + DWORD dwModifyState; + + // One more waiting writer +#ifdef RWLOCK_STATISTICS + RWInterlockedIncrement(&(*ppRWLock)->_dwWriterContentionCount); +#endif + HRESULT hr; + hWriterEvent = (*ppRWLock)->GetWriterEvent(&hr); + if(hWriterEvent) + { + dwStatus = RWWaitForSingleObject(hWriterEvent, dwDesiredTimeout); + VALIDATE_LOCK(*ppRWLock); + } + else + { + LOG((LF_SYNC, LL_WARNING, + "AcquireWriterLock failed to create writer " + "event for RWLock 0x%x\n", *ppRWLock)); + dwStatus = WAIT_FAILED; + } + + if(dwStatus == WAIT_OBJECT_0) + { + _ASSERTE((*ppRWLock)->_dwState & WRITER_SIGNALED); + dwModifyState = WRITER - WAITING_WRITER - WRITER_SIGNALED; + } + else + { + dwModifyState = (DWORD) -WAITING_WRITER; + if(dwStatus == WAIT_TIMEOUT) + { + LOG((LF_SYNC, LL_WARNING, + "Timed out trying to acquire writer " + "lock for RWLock 0x%x\n", *ppRWLock)); + hr = HRESULT_FROM_WIN32 (ERROR_TIMEOUT); + } + else if(dwStatus == WAIT_IO_COMPLETION) + { + LOG((LF_SYNC, LL_WARNING, + "Thread interrupted while trying to acquire writer lock " + "for RWLock 0x%x\n", *ppRWLock)); + hr = COR_E_THREADINTERRUPTED; + } + else if (dwStatus == WAIT_FAILED) + { + if (SUCCEEDED(hr)) + { + dwStatus = GetLastError(); + if (dwStatus == WAIT_OBJECT_0) + { + dwStatus = WAIT_FAILED; + } + hr = HRESULT_FROM_WIN32(dwStatus); + LOG((LF_SYNC, LL_WARNING, + "WaitForSingleObject on Event 0x%x failed for " + "RWLock 0x%x with status code 0x%x", + hWriterEvent, *ppRWLock, dwStatus)); + } + } + } + + // One less waiting writer and he may have become a writer + dwKnownState = RWInterlockedExchangeAdd(&(*ppRWLock)->_dwState, dwModifyState); + + // Check for last timing out signaled waiting writer + if(dwStatus == WAIT_OBJECT_0) + { + // Common case + } + else + { + if((dwKnownState & WRITER_SIGNALED) && + ((dwKnownState & WAITING_WRITERS_MASK) == WAITING_WRITER)) + { + HRESULT hr1; + if(hWriterEvent == NULL) + hWriterEvent = (*ppRWLock)->GetWriterEvent(&hr1); + _ASSERTE(hWriterEvent); + do + { + dwKnownState = (*ppRWLock)->_dwState; + if((dwKnownState & WRITER_SIGNALED) && + ((dwKnownState & WAITING_WRITERS_MASK) == 0)) + { + DWORD dwTemp = hWriterEvent->Wait(10, FALSE); + if(dwTemp == WAIT_OBJECT_0) + { + dwKnownState = RWInterlockedExchangeAdd(&(*ppRWLock)->_dwState, (WRITER - WRITER_SIGNALED)); + _ASSERTE(dwKnownState & WRITER_SIGNALED); + _ASSERTE((dwKnownState & WRITER) == 0); + + // Honor the orginal status + (*ppRWLock)->_dwWriterID = dwThreadID; + Thread *pThread = GetThread(); + _ASSERTE (pThread); + _ASSERTE ((*ppRWLock)->_wWriterLevel == 0); + pThread->m_dwLockCount ++; + USER_LOCK_TAKEN(GetPtrForLockContract(ppRWLock)); + (*ppRWLock)->_wWriterLevel = 1; + StaticReleaseWriterLock(ppRWLock); + break; + } + // else continue; + } + else + break; + }while(TRUE); + } + + if(fBreakOnErrors) // fBreakOnErrors == FALSE so will be optimized out. + { + _ASSERTE(!"Failed to acquire writer lock"); + DebugBreak(); + } + + // Prepare the frame for throwing an exception + if ((DWORD)HOST_E_DEADLOCK == dwStatus) + { + // So that the error message is in the exception. + RaiseDeadLockException(); + } else if ((DWORD)COR_E_THREADINTERRUPTED == dwStatus) { + COMPlusThrow(kThreadInterruptedException); + } + else + { + COMPlusThrowWin32(hr); + } + } + + // Sanity check + _ASSERTE(dwStatus == WAIT_OBJECT_0); + break; + } + } + YieldProcessor(); // indicate to the processor that we are spinning + } while(TRUE); + } + + // Success + _ASSERTE((*ppRWLock)->_dwState & WRITER); + _ASSERTE(((*ppRWLock)->_dwState & READERS_MASK) == 0); + _ASSERTE((*ppRWLock)->_dwWriterID == 0); + + // Save threadid of the writer + (*ppRWLock)->_dwWriterID = dwThreadID; + (*ppRWLock)->_wWriterLevel = 1; + INCTHREADLOCKCOUNT(); + USER_LOCK_TAKEN(GetPtrForLockContract(ppRWLock)); + ++(*ppRWLock)->_dwWriterSeqNum; +#ifdef RWLOCK_STATISTICS + ++(*ppRWLock)->_dwWriterEntryCount; +#endif + return; +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticReleaseWriterLockPublic public +// +// Synopsis: Public access to StaticReleaseWriterLock +// +//+------------------------------------------------------------------- +FCIMPL1(void, CRWLock::StaticReleaseWriterLockPublic, CRWLock *pRWLockUNSAFE) +{ + FCALL_CONTRACT; + + if (pRWLockUNSAFE == NULL) + { + FCThrowVoid(kNullReferenceException); + } + + OBJECTREF pRWLock = ObjectToOBJECTREF((Object*)pRWLockUNSAFE); + + HELPER_METHOD_FRAME_BEGIN_ATTRIB_1(Frame::FRAME_ATTR_NO_THREAD_ABORT, pRWLock); + + // We don't want to block thread abort when we need to construct exception in + // unwind-continue handler. + // note that we cannot use this holder in FCALLs outside our HMF since it breaks the epilog walker on x86! + ThreadPreventAbortHolder preventAbortIn; + + StaticReleaseWriterLock((CRWLock**)&pRWLock); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticReleaseWriterLock private +// +// Synopsis: Removes the thread as a writer if not a nested +// call to release the lock +// +//+------------------------------------------------------------------- +void CRWLock::StaticReleaseWriterLock( + CRWLock **ppRWLock) +{ + + CONTRACTL + { + THROWS; + GC_TRIGGERS; + PRECONDITION((CheckPointer(ppRWLock))); + PRECONDITION((CheckPointer(*ppRWLock))); + } + CONTRACTL_END; + + DWORD dwThreadID = GetThread()->GetThreadId(); + + // Check validity of caller + if((*ppRWLock)->_dwWriterID == dwThreadID) + { + DECTHREADLOCKCOUNT(); + USER_LOCK_RELEASED(GetPtrForLockContract(ppRWLock)); + // Check for nested release + if(--(*ppRWLock)->_wWriterLevel == 0) + { + DWORD dwCurrentState, dwKnownState, dwModifyState; + BOOL fCacheEvents; + CLREvent* hReaderEvent = NULL, *hWriterEvent = NULL; + + // Not a writer any more + (*ppRWLock)->_dwWriterID = 0; + dwCurrentState = (*ppRWLock)->_dwState; + do + { + dwKnownState = dwCurrentState; + dwModifyState = (DWORD) -WRITER; + fCacheEvents = FALSE; + if(dwKnownState & WAITING_READERS_MASK) + { + HRESULT hr; + hReaderEvent = (*ppRWLock)->GetReaderEvent(&hr); + if(hReaderEvent == NULL) + { + LOG((LF_SYNC, LL_WARNING, + "ReleaseWriterLock failed to create " + "reader event for RWLock 0x%x\n", *ppRWLock)); + RWSleep(100); + dwCurrentState = (*ppRWLock)->_dwState; + dwKnownState = 0; + _ASSERTE(dwCurrentState != dwKnownState); + continue; + } + dwModifyState += READER_SIGNALED; + } + else if(dwKnownState & WAITING_WRITERS_MASK) + { + HRESULT hr; + hWriterEvent = (*ppRWLock)->GetWriterEvent(&hr); + if(hWriterEvent == NULL) + { + LOG((LF_SYNC, LL_WARNING, + "ReleaseWriterLock failed to create " + "writer event for RWLock 0x%x\n", *ppRWLock)); + RWSleep(100); + dwCurrentState = (*ppRWLock)->_dwState; + dwKnownState = 0; + _ASSERTE(dwCurrentState != dwKnownState); + continue; + } + dwModifyState += WRITER_SIGNALED; + } + else if(((*ppRWLock)->_hReaderEvent || (*ppRWLock)->_hWriterEvent) && + (dwKnownState == WRITER)) + { + fCacheEvents = TRUE; + dwModifyState += CACHING_EVENTS; + } + + // Sanity checks + _ASSERTE((dwKnownState & READERS_MASK) == 0); + + dwCurrentState = RWInterlockedCompareExchange(&(*ppRWLock)->_dwState, + (dwKnownState + dwModifyState), + dwKnownState); + } while(dwCurrentState != dwKnownState); + + // Check for waiting readers + if(dwKnownState & WAITING_READERS_MASK) + { + _ASSERTE((*ppRWLock)->_dwState & READER_SIGNALED); + _ASSERTE(hReaderEvent); + RWSetEvent(hReaderEvent); + } + // Check for waiting writers + else if(dwKnownState & WAITING_WRITERS_MASK) + { + _ASSERTE((*ppRWLock)->_dwState & WRITER_SIGNALED); + _ASSERTE(hWriterEvent); + RWSetEvent(hWriterEvent); + } + // Check for the need to release events + else if(fCacheEvents) + { + (*ppRWLock)->ReleaseEvents(); + } + + Thread *pThread = GetThread(); + TESTHOOKCALL(AppDomainCanBeUnloaded(pThread->GetDomain()->GetId().m_dwId,FALSE)); + if (pThread->IsAbortRequested()) { + pThread->HandleThreadAbort(); + } + + } + } + else + { + if(fBreakOnErrors) // fBreakOnErrors == FALSE so will be optimized out. + { + _ASSERTE(!"Attempt to release writer lock on a wrong thread"); + DebugBreak(); + } + COMPlusThrowWin32(ERROR_NOT_OWNER); + } + + return; +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticReleaseReaderLockPublic public +// +// Synopsis: Public access to StaticReleaseReaderLock +// +//+------------------------------------------------------------------- +FCIMPL1(void, CRWLock::StaticReleaseReaderLockPublic, CRWLock *pRWLockUNSAFE) +{ + FCALL_CONTRACT; + + if (pRWLockUNSAFE == NULL) + { + FCThrowVoid(kNullReferenceException); + } + + OBJECTREF pRWLock = ObjectToOBJECTREF((Object*)pRWLockUNSAFE); + + HELPER_METHOD_FRAME_BEGIN_ATTRIB_1(Frame::FRAME_ATTR_NO_THREAD_ABORT, pRWLock); + + // note that we cannot use this holder in FCALLs outside our HMF since it breaks the epilog walker on x86! + ThreadPreventAbortHolder preventAbortIn; + + StaticReleaseReaderLock((CRWLock**)&pRWLock); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticReleaseReaderLock private +// +// Synopsis: Removes the thread as a reader +// +//+------------------------------------------------------------------- + +void CRWLock::StaticReleaseReaderLock( + CRWLock **ppRWLock) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + PRECONDITION((CheckPointer(ppRWLock))); + PRECONDITION((CheckPointer(*ppRWLock))); + } + CONTRACTL_END; + + // Check if the thread has writer lock + if((*ppRWLock)->_dwWriterID == GetThread()->GetThreadId()) + { + StaticReleaseWriterLock(ppRWLock); + } + else + { + LockEntry *pLockEntry = (*ppRWLock)->GetLockEntry(); + if(pLockEntry) + { + --pLockEntry->wReaderLevel; + DECTHREADLOCKCOUNT(); + USER_LOCK_RELEASED(GetPtrForLockContract(ppRWLock)); + if(pLockEntry->wReaderLevel == 0) + { + DWORD dwCurrentState, dwKnownState, dwModifyState; + BOOL fLastReader, fCacheEvents = FALSE; + CLREvent* hReaderEvent = NULL, *hWriterEvent = NULL; + + // Sanity checks + _ASSERTE(((*ppRWLock)->_dwState & WRITER) == 0); + _ASSERTE((*ppRWLock)->_dwState & READERS_MASK); + + // Not a reader any more + dwCurrentState = (*ppRWLock)->_dwState; + do + { + dwKnownState = dwCurrentState; + dwModifyState = (DWORD) -READER; + if((dwKnownState & (READERS_MASK | READER_SIGNALED)) == READER) + { + fLastReader = TRUE; + fCacheEvents = FALSE; + if(dwKnownState & WAITING_WRITERS_MASK) + { + HRESULT hr; + hWriterEvent = (*ppRWLock)->GetWriterEvent(&hr); + if(hWriterEvent == NULL) + { + LOG((LF_SYNC, LL_WARNING, + "ReleaseReaderLock failed to create " + "writer event for RWLock 0x%x\n", *ppRWLock)); + RWSleep(100); + dwCurrentState = (*ppRWLock)->_dwState; + dwKnownState = 0; + _ASSERTE(dwCurrentState != dwKnownState); + continue; + } + dwModifyState += WRITER_SIGNALED; + } + else if(dwKnownState & WAITING_READERS_MASK) + { + HRESULT hr; + hReaderEvent = (*ppRWLock)->GetReaderEvent(&hr); + if(hReaderEvent == NULL) + { + LOG((LF_SYNC, LL_WARNING, + "ReleaseReaderLock failed to create " + "reader event\n", *ppRWLock)); + RWSleep(100); + dwCurrentState = (*ppRWLock)->_dwState; + dwKnownState = 0; + _ASSERTE(dwCurrentState != dwKnownState); + continue; + } + dwModifyState += READER_SIGNALED; + } + else if(((*ppRWLock)->_hReaderEvent || (*ppRWLock)->_hWriterEvent) && + (dwKnownState == READER)) + { + fCacheEvents = TRUE; + dwModifyState += CACHING_EVENTS; + } + } + else + { + fLastReader = FALSE; + } + + // Sanity checks + _ASSERTE((dwKnownState & WRITER) == 0); + _ASSERTE(dwKnownState & READERS_MASK); + + dwCurrentState = RWInterlockedCompareExchange(&(*ppRWLock)->_dwState, + (dwKnownState + dwModifyState), + dwKnownState); + } while(dwCurrentState != dwKnownState); + + // Check for last reader + if(fLastReader) + { + // Check for waiting writers + if(dwKnownState & WAITING_WRITERS_MASK) + { + _ASSERTE((*ppRWLock)->_dwState & WRITER_SIGNALED); + _ASSERTE(hWriterEvent); + RWSetEvent(hWriterEvent); + } + // Check for waiting readers + else if(dwKnownState & WAITING_READERS_MASK) + { + _ASSERTE((*ppRWLock)->_dwState & READER_SIGNALED); + _ASSERTE(hReaderEvent); + RWSetEvent(hReaderEvent); + } + // Check for the need to release events + else if(fCacheEvents) + { + (*ppRWLock)->ReleaseEvents(); + } + } + + // Recycle lock entry + RecycleLockEntry(pLockEntry); + + Thread *pThread = GetThread(); + TESTHOOKCALL(AppDomainCanBeUnloaded(pThread->GetDomain()->GetId().m_dwId,FALSE)); + + if (pThread->IsAbortRequested()) { + pThread->HandleThreadAbort(); + } + } + } + else + { + if(fBreakOnErrors) // fBreakOnErrors == FALSE so will be optimized out. + { + _ASSERTE(!"Attempt to release reader lock on a wrong thread"); + DebugBreak(); + } + COMPlusThrowWin32(ERROR_NOT_OWNER); + } + } + + return; +} + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticDoUpgradeToWriterLockPublic private +// +// Synopsis: Public Access to StaticUpgradeToWriterLockPublic +// +// +//+------------------------------------------------------------------- +FCIMPL3(void, CRWLock::StaticDoUpgradeToWriterLockPublic, CRWLock *pRWLockUNSAFE, LockCookie * pLockCookie, DWORD dwDesiredTimeout) +{ + FCALL_CONTRACT; + + if (pRWLockUNSAFE == NULL) + { + FCThrowVoid(kNullReferenceException); + } + + OBJECTREF pRWLock = ObjectToOBJECTREF((Object*)pRWLockUNSAFE); + HELPER_METHOD_FRAME_BEGIN_1(pRWLock); + GCPROTECT_BEGININTERIOR (pLockCookie) + + StaticUpgradeToWriterLock((CRWLock**)&pRWLock, pLockCookie, dwDesiredTimeout); + + GCPROTECT_END (); + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticUpgradeToWriterLock Private +// +// Synopsis: Upgrades to a writer lock. It returns a BOOL that +// indicates intervening writes. +// + +// +//+------------------------------------------------------------------- + +void CRWLock::StaticUpgradeToWriterLock( + CRWLock **ppRWLock, + LockCookie *pLockCookie, + DWORD dwDesiredTimeout) + +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + DWORD dwThreadID = GetThread()->GetThreadId(); + + // Check if the thread is already a writer + if((*ppRWLock)->_dwWriterID == dwThreadID) + { + // Update cookie state + pLockCookie->dwFlags = UPGRADE_COOKIE | COOKIE_WRITER; + pLockCookie->wWriterLevel = (*ppRWLock)->_wWriterLevel; + + // Acquire the writer lock again + StaticAcquireWriterLock(ppRWLock, dwDesiredTimeout); + } + else + { + BOOL fAcquireWriterLock; + LockEntry *pLockEntry = (*ppRWLock)->GetLockEntry(); + if(pLockEntry == NULL) + { + fAcquireWriterLock = TRUE; + pLockCookie->dwFlags = UPGRADE_COOKIE | COOKIE_NONE; + } + else + { + // Sanity check + _ASSERTE((*ppRWLock)->_dwState & READERS_MASK); + _ASSERTE(pLockEntry->wReaderLevel); + + // Save lock state in the cookie + pLockCookie->dwFlags = UPGRADE_COOKIE | COOKIE_READER; + pLockCookie->wReaderLevel = pLockEntry->wReaderLevel; + pLockCookie->dwWriterSeqNum = (*ppRWLock)->_dwWriterSeqNum; + + // If there is only one reader, try to convert reader to a writer + DWORD dwKnownState = RWInterlockedCompareExchange(&(*ppRWLock)->_dwState, + WRITER, + READER); + if(dwKnownState == READER) + { + // Thread is no longer a reader + Thread* pThread = GetThread(); + _ASSERTE (pThread); + _ASSERTE (pThread->m_dwLockCount >= pLockEntry->wReaderLevel); + ASSERT_UNLESS_NO_DEBUG_STATE(__pClrDebugState->GetLockCount(kDbgStateLockType_User) >= pLockEntry->wReaderLevel); + pThread->m_dwLockCount -= pLockEntry->wReaderLevel; + USER_LOCK_RELEASED_MULTIPLE(pLockEntry->wReaderLevel, GetPtrForLockContract(ppRWLock)); + pLockEntry->wReaderLevel = 0; + RecycleLockEntry(pLockEntry); + + // Thread is a writer + (*ppRWLock)->_dwWriterID = dwThreadID; + (*ppRWLock)->_wWriterLevel = 1; + INCTHREADLOCKCOUNT(); + USER_LOCK_TAKEN(GetPtrForLockContract(ppRWLock)); + ++(*ppRWLock)->_dwWriterSeqNum; + fAcquireWriterLock = FALSE; + + // No intevening writes +#if RWLOCK_STATISTICS + ++(*ppRWLock)->_dwWriterEntryCount; +#endif + } + else + { + // Release the reader lock + Thread *pThread = GetThread(); + _ASSERTE (pThread); + _ASSERTE (pThread->m_dwLockCount >= (DWORD)(pLockEntry->wReaderLevel - 1)); + ASSERT_UNLESS_NO_DEBUG_STATE(__pClrDebugState->GetLockCount(kDbgStateLockType_User) >= + (DWORD)(pLockEntry->wReaderLevel - 1)); + pThread->m_dwLockCount -= (pLockEntry->wReaderLevel - 1); + USER_LOCK_RELEASED_MULTIPLE(pLockEntry->wReaderLevel - 1, GetPtrForLockContract(ppRWLock)); + pLockEntry->wReaderLevel = 1; + StaticReleaseReaderLock(ppRWLock); + fAcquireWriterLock = TRUE; + } + } + + // Check for the need to acquire the writer lock + if(fAcquireWriterLock) + { + + // Declare and Setup the frame as we are aware of the contention + // on the lock and the thread will most probably block + // to acquire writer lock + + EX_TRY + { + StaticAcquireWriterLock(ppRWLock, dwDesiredTimeout); + } + EX_CATCH + { + // Invalidate cookie + DWORD dwFlags = pLockCookie->dwFlags; + pLockCookie->dwFlags = INVALID_COOKIE; + + StaticRecoverLock(ppRWLock, pLockCookie, dwFlags & COOKIE_READER); + + EX_RETHROW; + } + EX_END_CATCH_UNREACHABLE + } + } + + + // Update the validation fields of the cookie + pLockCookie->dwThreadID = dwThreadID; + + return; +} + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticDowngradeFromWriterLock public +// +// Synopsis: Downgrades from a writer lock. +// +//+------------------------------------------------------------------- + +inline CRWLock* GetLock(OBJECTREF orLock) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + return (CRWLock*)OBJECTREFToObject(orLock); +} + +FCIMPL2(void, CRWLock::StaticDowngradeFromWriterLock, CRWLock *pRWLockUNSAFE, LockCookie* pLockCookie) +{ + FCALL_CONTRACT; + STATIC_CONTRACT_CAN_TAKE_LOCK; + + DWORD dwThreadID = GetThread()->GetThreadId(); + + if (pRWLockUNSAFE == NULL) + { + FCThrowVoid(kNullReferenceException); + } + + if( NULL == pLockCookie) { + FCThrowVoid(kNullReferenceException); + } + + OBJECTREF pRWLock = ObjectToOBJECTREF((Object*)pRWLockUNSAFE); + HELPER_METHOD_FRAME_BEGIN_1(pRWLock); + + if (GetLock(pRWLock)->_dwWriterID != dwThreadID) + { + COMPlusThrowWin32(ERROR_NOT_OWNER); + } + + // Validate cookie + DWORD dwStatus; + if(((pLockCookie->dwFlags & INVALID_COOKIE) == 0) && + (pLockCookie->dwThreadID == dwThreadID)) + { + DWORD dwFlags = pLockCookie->dwFlags; + pLockCookie->dwFlags = INVALID_COOKIE; + + // Check if the thread was a reader + if(dwFlags & COOKIE_READER) + { + // Sanity checks + _ASSERTE(GetLock(pRWLock)->_wWriterLevel == 1); + + LockEntry *pLockEntry = GetLock(pRWLock)->FastGetOrCreateLockEntry(); + if(pLockEntry) + { + DWORD dwCurrentState, dwKnownState, dwModifyState; + CLREvent* hReaderEvent = NULL; + + // Downgrade to a reader + GetLock(pRWLock)->_dwWriterID = 0; + GetLock(pRWLock)->_wWriterLevel = 0; + DECTHREADLOCKCOUNT (); + USER_LOCK_RELEASED(GetPtrForLockContract((CRWLock**)&pRWLock)); + dwCurrentState = GetLock(pRWLock)->_dwState; + do + { + dwKnownState = dwCurrentState; + dwModifyState = READER - WRITER; + if(dwKnownState & WAITING_READERS_MASK) + { + HRESULT hr; + hReaderEvent = GetLock(pRWLock)->GetReaderEvent(&hr); + if(hReaderEvent == NULL) + { + LOG((LF_SYNC, LL_WARNING, + "DowngradeFromWriterLock failed to create " + "reader event for RWLock 0x%x\n", GetLock(pRWLock))); + RWSleep(100); + dwCurrentState = GetLock(pRWLock)->_dwState; + dwKnownState = 0; + _ASSERTE(dwCurrentState != dwKnownState); + continue; + } + dwModifyState += READER_SIGNALED; + } + + // Sanity checks + _ASSERTE((dwKnownState & READERS_MASK) == 0); + + dwCurrentState = RWInterlockedCompareExchange(&GetLock(pRWLock)->_dwState, + (dwKnownState + dwModifyState), + dwKnownState); + } while(dwCurrentState != dwKnownState); + + // Check for waiting readers + if(dwKnownState & WAITING_READERS_MASK) + { + _ASSERTE(GetLock(pRWLock)->_dwState & READER_SIGNALED); + _ASSERTE(hReaderEvent); + RWSetEvent(hReaderEvent); + } + + // Restore reader nesting level + Thread *pThread = GetThread(); + _ASSERTE (pThread); + _ASSERTE (pThread->m_dwLockCount >= pLockEntry->wReaderLevel); + ASSERT_UNLESS_NO_DEBUG_STATE(__pClrDebugState->GetLockCount(kDbgStateLockType_User) >= + pLockEntry->wReaderLevel); + pThread->m_dwLockCount -= pLockEntry->wReaderLevel; + USER_LOCK_RELEASED_MULTIPLE(pLockEntry->wReaderLevel, GetPtrForLockContract((CRWLock**)&pRWLock)); + pLockEntry->wReaderLevel = pLockCookie->wReaderLevel; + pThread->m_dwLockCount += pLockEntry->wReaderLevel; + USER_LOCK_TAKEN_MULTIPLE(pLockEntry->wReaderLevel, GetPtrForLockContract((CRWLock**)&pRWLock)); + #ifdef RWLOCK_STATISTICS + RWInterlockedIncrement(&GetLock(pRWLock)->_dwReaderEntryCount); + #endif + } + else + { + // Removed assert, as thread abort can occur normally + dwStatus = RWLOCK_RECOVERY_FAILURE; + goto ThrowException; + } + } + else if(dwFlags & (COOKIE_WRITER | COOKIE_NONE)) + { + // Release the writer lock + StaticReleaseWriterLock((CRWLock**)&pRWLock); + _ASSERTE((GetLock(pRWLock)->_dwWriterID != GetThread()->GetThreadId()) || + (dwFlags & COOKIE_WRITER)); + } + } + else + { + dwStatus = E_INVALIDARG; +ThrowException: + COMPlusThrowWin32(dwStatus); + } + + HELPER_METHOD_FRAME_END(); + + // Update the validation fields of the cookie + pLockCookie->dwThreadID = dwThreadID; + +} +FCIMPLEND + + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticDoReleaseLock private +// +// Synopsis: Releases the lock held by the current thread +// +//+------------------------------------------------------------------- + +FCIMPL2(void, CRWLock::StaticDoReleaseLock, CRWLock *pRWLockUNSAFE, LockCookie * pLockCookie) +{ + FCALL_CONTRACT; + + if (pRWLockUNSAFE == NULL) + { + FCThrowVoid(kNullReferenceException); + } + + DWORD dwThreadID = GetThread()->GetThreadId(); + + OBJECTREF pRWLock = ObjectToOBJECTREF((Object*)pRWLockUNSAFE); + + HELPER_METHOD_FRAME_BEGIN_ATTRIB_1(Frame::FRAME_ATTR_NO_THREAD_ABORT, pRWLock); + + // note that we cannot use this holder in FCALLs outside our HMF since it breaks the epilog walker on x86! + ThreadPreventAbortHolder preventAbortIn; + + GCPROTECT_BEGININTERIOR (pLockCookie) + + // Check if the thread is a writer + if(GetLock(pRWLock)->_dwWriterID == dwThreadID) + { + // Save lock state in the cookie + pLockCookie->dwFlags = RELEASE_COOKIE | COOKIE_WRITER; + pLockCookie->dwWriterSeqNum = GetLock(pRWLock)->_dwWriterSeqNum; + pLockCookie->wWriterLevel = GetLock(pRWLock)->_wWriterLevel; + + // Release the writer lock + Thread *pThread = GetThread(); + _ASSERTE (pThread); + _ASSERTE (pThread->m_dwLockCount >= (DWORD)(GetLock(pRWLock)->_wWriterLevel - 1)); + ASSERT_UNLESS_NO_DEBUG_STATE(__pClrDebugState->GetLockCount(kDbgStateLockType_User) >= + (DWORD)(GetLock(pRWLock)->_wWriterLevel - 1)); + pThread->m_dwLockCount -= (GetLock(pRWLock)->_wWriterLevel - 1); + USER_LOCK_RELEASED_MULTIPLE(GetLock(pRWLock)->_wWriterLevel - 1, GetPtrForLockContract((CRWLock**)&pRWLock)); + GetLock(pRWLock)->_wWriterLevel = 1; + StaticReleaseWriterLock((CRWLock**)&pRWLock); + } + else + { + LockEntry *pLockEntry = GetLock(pRWLock)->GetLockEntry(); + if(pLockEntry) + { + // Sanity check + _ASSERTE(GetLock(pRWLock)->_dwState & READERS_MASK); + _ASSERTE(pLockEntry->wReaderLevel); + + // Save lock state in the cookie + pLockCookie->dwFlags = RELEASE_COOKIE | COOKIE_READER; + pLockCookie->wReaderLevel = pLockEntry->wReaderLevel; + pLockCookie->dwWriterSeqNum = GetLock(pRWLock)->_dwWriterSeqNum; + + // Release the reader lock + Thread *pThread = GetThread(); + _ASSERTE (pThread); + _ASSERTE (pThread->m_dwLockCount >= (DWORD)(pLockEntry->wReaderLevel - 1)); + ASSERT_UNLESS_NO_DEBUG_STATE(__pClrDebugState->GetLockCount(kDbgStateLockType_User) >= + (DWORD)(pLockEntry->wReaderLevel - 1)); + pThread->m_dwLockCount -= (pLockEntry->wReaderLevel - 1); + USER_LOCK_RELEASED_MULTIPLE(pLockEntry->wReaderLevel - 1, GetPtrForLockContract((CRWLock**)&pRWLock)); + pLockEntry->wReaderLevel = 1; + StaticReleaseReaderLock((CRWLock**)&pRWLock); + } + else + { + pLockCookie->dwFlags = RELEASE_COOKIE | COOKIE_NONE; + } + } + + GCPROTECT_END (); + + HELPER_METHOD_FRAME_END(); + + // Update the validation fields of the cookie + pLockCookie->dwThreadID = dwThreadID; +} +FCIMPLEND + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticRestoreLockPublic public +// +// Synopsis: Public Access to StaticRestoreLock +// +// +//+------------------------------------------------------------------- + +FCIMPL2(void, CRWLock::StaticRestoreLockPublic, CRWLock *pRWLockUNSAFE, LockCookie* pLockCookie) +{ + FCALL_CONTRACT; + + if (pRWLockUNSAFE == NULL) { + FCThrowVoid(kNullReferenceException); + } + + if( NULL == pLockCookie) { + FCThrowVoid(kNullReferenceException); + } + + OBJECTREF pRWLock = ObjectToOBJECTREF((Object*)pRWLockUNSAFE); + HELPER_METHOD_FRAME_BEGIN_1(pRWLock); + + StaticRestoreLock((CRWLock**)&pRWLock, pLockCookie); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +//+------------------------------------------------------------------- +// +// Method: CRWLock::StaticRestoreLock Private +// +// Synopsis: Restore the lock held by the current thread +// + +// +//+------------------------------------------------------------------- + +void CRWLock::StaticRestoreLock( + CRWLock **ppRWLock, + LockCookie *pLockCookie) +{ + CONTRACTL + { + THROWS; + CAN_TAKE_LOCK; + GC_TRIGGERS; // CRWLock::StaticAquireWriterLock can trigger GC + } + CONTRACTL_END; + + // Validate cookie + DWORD dwThreadID = GetThread()->GetThreadId(); + DWORD dwFlags = pLockCookie->dwFlags; + if(pLockCookie->dwThreadID == dwThreadID) + { + if (((*ppRWLock)->_dwWriterID == dwThreadID) || ((*ppRWLock)->GetLockEntry() != NULL)) + { + COMPlusThrow(kSynchronizationLockException, W("Arg_RWLockRestoreException")); + } + + // Check for the no contention case + pLockCookie->dwFlags = INVALID_COOKIE; + if(dwFlags & COOKIE_WRITER) + { + if(RWInterlockedCompareExchange(&(*ppRWLock)->_dwState, WRITER, 0) == 0) + { + // Restore writer nesting level + (*ppRWLock)->_dwWriterID = dwThreadID; + Thread *pThread = GetThread(); + _ASSERTE (pThread); + _ASSERTE (pThread->m_dwLockCount >= (*ppRWLock)->_wWriterLevel); + ASSERT_UNLESS_NO_DEBUG_STATE(__pClrDebugState->GetLockCount(kDbgStateLockType_User) >= + (*ppRWLock)->_wWriterLevel); + pThread->m_dwLockCount -= (*ppRWLock)->_wWriterLevel; + USER_LOCK_RELEASED_MULTIPLE((*ppRWLock)->_wWriterLevel, GetPtrForLockContract(ppRWLock)); + (*ppRWLock)->_wWriterLevel = pLockCookie->wWriterLevel; + pThread->m_dwLockCount += (*ppRWLock)->_wWriterLevel; + USER_LOCK_TAKEN_MULTIPLE((*ppRWLock)->_wWriterLevel, GetPtrForLockContract(ppRWLock)); + ++(*ppRWLock)->_dwWriterSeqNum; +#ifdef RWLOCK_STATISTICS + ++(*ppRWLock)->_dwWriterEntryCount; +#endif + goto LNormalReturn; + } + } + else if(dwFlags & COOKIE_READER) + { + LockEntry *pLockEntry = (*ppRWLock)->FastGetOrCreateLockEntry(); + if(pLockEntry) + { + // This thread should not already be a reader + // else bad things can happen + _ASSERTE(pLockEntry->wReaderLevel == 0); + DWORD dwKnownState = (*ppRWLock)->_dwState; + if(dwKnownState < READERS_MASK) + { + DWORD dwCurrentState = RWInterlockedCompareExchange(&(*ppRWLock)->_dwState, + (dwKnownState + READER), + dwKnownState); + if(dwCurrentState == dwKnownState) + { + // Restore reader nesting level + Thread *pThread = GetThread(); + _ASSERTE (pThread); + pLockEntry->wReaderLevel = pLockCookie->wReaderLevel; + pThread->m_dwLockCount += pLockEntry->wReaderLevel; + USER_LOCK_TAKEN_MULTIPLE(pLockEntry->wReaderLevel, GetPtrForLockContract(ppRWLock)); +#ifdef RWLOCK_STATISTICS + RWInterlockedIncrement(&(*ppRWLock)->_dwReaderEntryCount); +#endif + goto LNormalReturn; + } + } + + // Recycle the lock entry for the slow case + (*ppRWLock)->FastRecycleLockEntry(pLockEntry); + } + else + { + // Ignore the error and try again below. May be thread will luck + // out the second time + } + } + else if(dwFlags & COOKIE_NONE) + { + goto LNormalReturn; + } + + // Declare and Setup the frame as we are aware of the contention + // on the lock and the thread will most probably block + // to acquire lock below +ThrowException: + if((dwFlags & INVALID_COOKIE) == 0) + { + StaticRecoverLock(ppRWLock, pLockCookie, dwFlags); + } + else + { + COMPlusThrowWin32(E_INVALIDARG); + } + + goto LNormalReturn; + } + else + { + dwFlags = INVALID_COOKIE; + goto ThrowException; + } + +LNormalReturn: + return; +} + + +//+------------------------------------------------------------------- +// +// Class: CRWLock::StaticPrivateInitialize +// +// Synopsis: Initialize lock +// +//+------------------------------------------------------------------- +FCIMPL1(void, CRWLock::StaticPrivateInitialize, CRWLock *pRWLock) +{ + FCALL_CONTRACT; + + HELPER_METHOD_FRAME_BEGIN_1(pRWLock); + + // Run the constructor on the GC allocated space + // CRWLock's constructor can throw exception +#ifndef _PREFAST_ + // Prefast falsely complains of memory leak. + CRWLock *pTemp; + pTemp = new (pRWLock) CRWLock(); + _ASSERTE(pTemp == pRWLock); +#endif + + // Catch GC holes + VALIDATE_LOCK(pRWLock); + + HELPER_METHOD_FRAME_END(); + return; +} +FCIMPLEND + + +//+------------------------------------------------------------------- +// +// Class: CRWLock::StaticPrivateDestruct +// +// Synopsis: Destruct lock +//+------------------------------------------------------------------- +FCIMPL1(void, CRWLock::StaticPrivateDestruct, CRWLock *pRWLock) +{ + FCALL_CONTRACT; + + HELPER_METHOD_FRAME_BEGIN_ATTRIB_1(Frame::FRAME_ATTR_NO_THREAD_ABORT, pRWLock); + + // Fixing one handle recycling security hole by + // ensuring we don't delete the events more than once. + // After deletion (for now, assuming ONE FINALIZER THREAD) + // make the object essentially unusable by setting handle to + // INVALID_HANDLE_VALUE (unusable) versus NULL (uninitialized) + + if ((pRWLock->_hWriterEvent != INVALID_HANDLE_VALUE) && (pRWLock->_hReaderEvent != INVALID_HANDLE_VALUE)) + { + // Note, this still allows concurrent event consumers (such as StaticAcquireReaderLock) + // to Set and/or Wait on non-events. There still exists a security hole here. + if(pRWLock->_hWriterEvent) + { + CLREvent *h = (CLREvent *) FastInterlockExchangePointer((PVOID *)&(pRWLock->_hWriterEvent), INVALID_HANDLE_VALUE); + delete h; + } + if(pRWLock->_hReaderEvent) + { + CLREvent *h = (CLREvent *) FastInterlockExchangePointer((PVOID *)&(pRWLock->_hReaderEvent), INVALID_HANDLE_VALUE); + delete h; + } + + // There is no LockEntry for this lock. + if (pRWLock->_dwState != 0) + { + // Recycle LockEntry on threads + ThreadStoreLockHolder tsl; + + // Take ThreadStore lock and walk over every thread in the process + Thread *thread = NULL; + while ((thread = ThreadStore::s_pThreadStore->GetAllThreadList(thread, + Thread::TS_Unstarted|Thread::TS_Dead|Thread::TS_Detached, 0)) + != NULL) + { + LockEntry *pLockEntry; + { + CrstHolder rwl(&s_RWLockCrst); + pLockEntry = pRWLock->GetLockEntry(thread); + } + if (pLockEntry) + { + // The entry does not belong to this lock anymore + pLockEntry->dwLLockID = 0; + pLockEntry->wReaderLevel = 0; + } + } + } + } + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + + +//+------------------------------------------------------------------- +// +// Class: CRWLock::StaticGetWriterSeqNum +// +// Synopsis: Returns the current sequence number +// +//+------------------------------------------------------------------- +FCIMPL1(INT32, CRWLock::StaticGetWriterSeqNum, CRWLock *pRWLock) +{ + FCALL_CONTRACT; + + if (pRWLock == NULL) + { + FCThrow(kNullReferenceException); + } + + return(pRWLock->_dwWriterSeqNum); +} +FCIMPLEND + + +//+------------------------------------------------------------------- +// +// Class: CRWLock::StaticAnyWritersSince +// +// Synopsis: Returns TRUE if there were writers since the given +// sequence number +// +//+------------------------------------------------------------------- +FCIMPL2(FC_BOOL_RET, CRWLock::StaticAnyWritersSince, CRWLock *pRWLock, DWORD dwSeqNum) +{ + FCALL_CONTRACT; + + if (pRWLock == NULL) + { + FCThrow(kNullReferenceException); + } + + + if(pRWLock->_dwWriterID == GetThread()->GetThreadId()) + ++dwSeqNum; + + FC_RETURN_BOOL(pRWLock->_dwWriterSeqNum > dwSeqNum); +} +FCIMPLEND + +struct RWLockIterator +{ + IHostTask **m_Owner; + DWORD m_Capacity; + DWORD m_index; +}; + +OBJECTHANDLE CRWLock::GetObjectHandle() +{ + CONTRACTL + { + THROWS; + MODE_COOPERATIVE; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (_hObjectHandle == NULL) + { + OBJECTREF obj = ObjectToOBJECTREF((Object*)this); + OBJECTHANDLE handle = GetAppDomain()->CreateLongWeakHandle(obj); + if (RWInterlockedCompareExchangePointer((PVOID*)&_hObjectHandle, handle, NULL) != NULL) + { + DestroyLongWeakHandle(handle); + } + } + return _hObjectHandle; +} + +// CRWLock::CreateOwnerIterator can return E_OUTOFMEMORY +// +HRESULT CRWLock::CreateOwnerIterator(SIZE_T *pIterator) +{ + CONTRACTL + { + NOTHROW; + MODE_PREEMPTIVE; + GC_NOTRIGGER; + SO_INTOLERANT; + } + CONTRACTL_END; + + *pIterator = 0; + if (_dwState == 0) { + return S_OK; + } + NewHolder<RWLockIterator> IteratorHolder(new (nothrow) RWLockIterator); + RWLockIterator *pRWLockIterator = IteratorHolder; + if (pRWLockIterator == NULL) { + return E_OUTOFMEMORY; + } + // Writer can be handled fast + if (_dwState & WRITER) { + DWORD writerID = _dwWriterID; + if (writerID != 0) + { + pRWLockIterator->m_Capacity = 1; + pRWLockIterator->m_index = 0; + pRWLockIterator->m_Owner = new (nothrow) IHostTask*[1]; + if (pRWLockIterator->m_Owner == NULL) { + return E_OUTOFMEMORY; + } + Thread *pThread = g_pThinLockThreadIdDispenser->IdToThreadWithValidation(writerID); + if (pThread == NULL) + { + return S_OK; + } + IteratorHolder.SuppressRelease(); + pRWLockIterator->m_Owner[0] = pThread->GetHostTaskWithAddRef(); + *pIterator = (SIZE_T)pRWLockIterator; + return S_OK; + } + } + if (_dwState == 0) { + return S_OK; + } + pRWLockIterator->m_Capacity = 4; + pRWLockIterator->m_index = 0; + pRWLockIterator->m_Owner = new (nothrow) IHostTask*[pRWLockIterator->m_Capacity]; + if (pRWLockIterator->m_Owner == NULL) { + return E_OUTOFMEMORY; + } + + HRESULT hr = S_OK; + + NewArrayHolder<IHostTask*> OwnerHolder(pRWLockIterator->m_Owner); + + // Take ThreadStore lock and walk over every thread in the process + Thread *thread = NULL; + while ((thread = ThreadStore::s_pThreadStore->GetAllThreadList(thread, + Thread::TS_Unstarted|Thread::TS_Dead|Thread::TS_Detached, 0)) + != NULL) + { + LockEntry *pLockEntry; + { + CrstHolder rwl(&s_RWLockCrst); + pLockEntry = GetLockEntry(thread); + } + if (pLockEntry && pLockEntry->wReaderLevel >= 1) { + if (pRWLockIterator->m_index == pRWLockIterator->m_Capacity) { + IHostTask** newArray = new (nothrow) IHostTask*[2*pRWLockIterator->m_Capacity]; + if (newArray == NULL) { + hr = E_OUTOFMEMORY; + break; + } + memcpy (newArray,pRWLockIterator->m_Owner,pRWLockIterator->m_Capacity*sizeof(IHostTask*)); + pRWLockIterator->m_Owner = newArray; + pRWLockIterator->m_Capacity *= 2; + OwnerHolder = pRWLockIterator->m_Owner; + } + IHostTask *pHostTask = thread->GetHostTaskWithAddRef(); + if (pHostTask) + { + pRWLockIterator->m_Owner[pRWLockIterator->m_index++] = pHostTask; + } + } + } + if (FAILED(hr)) + { + for (DWORD i = 0; i < pRWLockIterator->m_index; i ++) + { + if (pRWLockIterator->m_Owner[i]) + { + pRWLockIterator->m_Owner[i]->Release(); + } + } + } + if (SUCCEEDED(hr)) { + IteratorHolder.SuppressRelease(); + OwnerHolder.SuppressRelease(); + pRWLockIterator->m_Capacity = pRWLockIterator->m_index; + pRWLockIterator->m_index = 0; + *pIterator = (SIZE_T)pRWLockIterator; + } + + return hr; +} + +void CRWLock::GetNextOwner(SIZE_T Iterator, IHostTask **ppOwnerHostTask) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + *ppOwnerHostTask = NULL; + if (Iterator) { + RWLockIterator* tmp = (RWLockIterator*)Iterator; + if (tmp->m_index < tmp->m_Capacity) { + *ppOwnerHostTask = tmp->m_Owner[tmp->m_index]; + tmp->m_index ++; + } + } +} + +void CRWLock::DeleteOwnerIterator(SIZE_T Iterator) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + + if (Iterator) { + RWLockIterator* pIterator = (RWLockIterator*)Iterator; + while (pIterator->m_index < pIterator->m_Capacity) { + IHostTask *pHostTask = pIterator->m_Owner[pIterator->m_index]; + if (pHostTask) + { + pHostTask->Release(); + } + pIterator->m_index ++; + } + delete[] pIterator->m_Owner; + delete pIterator; + } +} +#endif // FEATURE_RWLOCK |