|
|
//+-------------------------------------------------------------------
//
// File: RWLock.cxx
//
// 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
// CStaticRWLock
//
// History: 19-Aug-98 Gopalk Created
//
//--------------------------------------------------------------------
#include <ole2int.h>
#include "RWLock.hxx"
// 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)
// Reader lock was upgraded
#define INVALID_COOKIE 0x01
#define UPGRADE_COOKIE 0x02
#define RELEASE_COOKIE 0x04
#define COOKIE_NONE 0x10
#define COOKIE_WRITER 0x20
#define COOKIE_READER 0x40
DWORD gdwDefaultTimeout = INFINITE; DWORD gdwDefaultSpinCount = 0; DWORD gdwNumberOfProcessors = 1; DWORD gdwLockSeqNum = 0; const DWORD gdwReasonableTimeout = 120000; const DWORD gdwMaxReaders = READERS_MASK; const DWORD gdwMaxWaitingReaders = (WAITING_READERS_MASK >> WAITING_READERS_SHIFT);
BOOL IsKDPresent() { return USER_SHARED_DATA->KdDebuggerEnabled; }
BOOL fBreakOnErrors () { return (IsDebuggerPresent() || IsKDPresent()); }
#ifdef __NOOLETLS__
DWORD gLockTlsIdx = -1; #endif
#define RWLOCK_FATALFAILURE 1000
#define HEAP_SERIALIZE 0
//+-------------------------------------------------------------------
//
// Method: CRWLock::InitDefaults public
//
// Synopsis: Reads default values from registry
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
void CRWLock::InitDefaults() { SYSTEM_INFO system;
// Obtain number of processors on the system
GetSystemInfo(&system); gdwNumberOfProcessors = system.dwNumberOfProcessors; gdwDefaultSpinCount = (gdwNumberOfProcessors > 1) ? 500 : 0;
// Obtain system wide timeout value
HKEY hKey; LONG lRetVal = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session Manager", NULL, KEY_READ, &hKey); if(lRetVal == ERROR_SUCCESS) { DWORD dwTimeout, dwSize = sizeof(dwTimeout);
lRetVal = RegQueryValueExA(hKey, "CriticalSectionTimeout", NULL, NULL, (LPBYTE) &dwTimeout, &dwSize); if(lRetVal == ERROR_SUCCESS) { if (dwTimeout <= 1*24*60*60) // less than a day, usually ntstress case
{ gdwDefaultTimeout = dwTimeout * 20000; // 20 times the critsec timeout
} // otherwise lock timeout is INFINITE.
} RegCloseKey(hKey); }
return; }
//+-------------------------------------------------------------------
//
// Method: CRWLock::Cleanup public
//
// Synopsis: Cleansup state
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
void CRWLock::Cleanup() {
#if DBG==1
if (g_fDllState != DLL_STATE_PROCESS_DETACH) { // Perform sanity checks if we're not shutting down
Win4Assert(_dwState == 0); Win4Assert(_dwWriterID == 0); Win4Assert(_wWriterLevel == 0); } #endif
if(_hWriterEvent) CloseHandle(_hWriterEvent); if(_hReaderEvent) CloseHandle(_hReaderEvent);
#if LOCK_PERF==1
gLockTracker.ReportContention(this, _dwWriterEntryCount, _dwWriterContentionCount, _dwReaderEntryCount, _dwReaderContentionCount); #endif
return; }
//+-------------------------------------------------------------------
//
// Method: CRWLock::AssertWriterLockHeld public
//
// Synopsis: Asserts that writer lock is held
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
#if DBG==1
BOOL CRWLock::AssertWriterLockHeld() { DWORD dwThreadID = GetCurrentThreadId();
if(_dwWriterID != dwThreadID) Win4Assert(!"Writer lock not held by the current thread");
return(_dwWriterID == dwThreadID); } #endif
//+-------------------------------------------------------------------
//
// Method: CRWLock::AssertWriterLockNotHeld public
//
// Synopsis: Asserts that writer lock is not held
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
#if DBG==1
BOOL CRWLock::AssertWriterLockNotHeld() { DWORD dwThreadID = GetCurrentThreadId();
if(_dwWriterID == dwThreadID) Win4Assert(!"Writer lock held by the current thread");
return(_dwWriterID != dwThreadID); } #endif
//+-------------------------------------------------------------------
//
// Method: CRWLock::AssertReaderLockHeld public
//
// Synopsis: Asserts that reader lock is held
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
#if DBG==1
BOOL CRWLock::AssertReaderLockHeld() { HRESULT hr; WORD *pwReaderLevel; BOOL fLockHeld = FALSE;
hr = GetTLSLockData(&pwReaderLevel); if((hr == S_OK) && (*pwReaderLevel != 0)) fLockHeld = TRUE;
if(fLockHeld == FALSE) Win4Assert(!"Reader lock not held by the current thread");
return(fLockHeld); } #endif
//+-------------------------------------------------------------------
//
// Method: CRWLock::AssertReaderLockNotHeld public
//
// Synopsis: Asserts that writer lock is not held
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
#if DBG==1
BOOL CRWLock::AssertReaderLockNotHeld() { HRESULT hr; WORD *pwReaderLevel; BOOL fLockHeld = FALSE;
hr = GetTLSLockData(&pwReaderLevel); if((hr == S_OK) && (*pwReaderLevel != 0)) fLockHeld = TRUE;
if(fLockHeld == TRUE) Win4Assert(!"Reader lock held by the current thread");
return(fLockHeld == FALSE); } #endif
//+-------------------------------------------------------------------
//
// Method: CRWLock::AssertReaderOrWriterLockHeld public
//
// Synopsis: Asserts that writer lock is not held
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
#if DBG==1
BOOL CRWLock::AssertReaderOrWriterLockHeld() { BOOL fLockHeld = FALSE;
if(_dwWriterID == GetCurrentThreadId()) { fLockHeld = TRUE; } else { HRESULT hr; WORD *pwReaderLevel;
hr = GetTLSLockData(&pwReaderLevel); if((hr == S_OK) && (*pwReaderLevel != 0)) fLockHeld = TRUE; }
Win4Assert(fLockHeld && "Neither Reader nor Writer lock held");
return(fLockHeld); } #endif
//+-------------------------------------------------------------------
//
// Method: CRWLock::ModifyState public
//
// Synopsis: Helper function for updating the state inside the lock
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
inline DWORD CRWLock::ModifyState(DWORD dwModifyState) { return(InterlockedExchangeAdd((LONG *) &_dwState, dwModifyState)); }
//+-------------------------------------------------------------------
//
// Method: CRWLock::RWSetEvent public
//
// Synopsis: Helper function for setting an event
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
inline void CRWLock::RWSetEvent(HANDLE event) { if(!SetEvent(event)) { Win4Assert(!"SetEvent failed"); if(fBreakOnErrors()) DebugBreak(); TerminateProcess(GetCurrentProcess(), RWLOCK_FATALFAILURE); } }
//+-------------------------------------------------------------------
//
// Method: CRWLock::RWResetEvent public
//
// Synopsis: Helper function for resetting an event
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
inline void CRWLock::RWResetEvent(HANDLE event) { if(!ResetEvent(event)) { Win4Assert(!"ResetEvent failed"); if(fBreakOnErrors()) DebugBreak(); TerminateProcess(GetCurrentProcess(), RWLOCK_FATALFAILURE); } }
//+-------------------------------------------------------------------
//
// Method: CRWLock::RWSleep public
//
// Synopsis: Helper function for calling Sleep. Useful for debugging
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
inline void CRWLock::RWSleep(DWORD dwTime) { Sleep(dwTime); }
//+-------------------------------------------------------------------
//
// Method: CRWLock::ReleaseEvents public
//
// Synopsis: Helper function for caching events
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
#ifdef RWLOCK_FULL_FUNCTIONALITY
void CRWLock::ReleaseEvents() { // Sanity check
Win4Assert(_wFlags & RWLOCKFLAG_CACHEEVENTS);
// Ensure that reader and writers have been stalled
Win4Assert((_dwState & CACHING_EVENTS) == CACHING_EVENTS);
// Save writer event
HANDLE hWriterEvent = _hWriterEvent; _hWriterEvent = NULL;
// Save reader event
HANDLE hReaderEvent = _hReaderEvent; _hReaderEvent = NULL;
// Allow readers and writers to continue
ModifyState(-(CACHING_EVENTS));
// Cache events
// REVIEW: I am closing events for now. What is needed
// is an event cache to which the events are
// released using InterlockedCompareExchange64
if(hWriterEvent) { ComDebOut((DEB_TRACE, "Releasing writer event\n")); CloseHandle(hWriterEvent); } if(hReaderEvent) { ComDebOut((DEB_TRACE, "Releasing reader event\n")); CloseHandle(hReaderEvent); }
return; } #endif
//+-------------------------------------------------------------------
//
// Method: CRWLock::GetWriterEvent public
//
// Synopsis: Helper function for obtaining a auto reset event used
// for serializing writers. It utilizes event cache
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
HANDLE CRWLock::GetWriterEvent() { if(_hWriterEvent == NULL) { HANDLE hWriterEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if(hWriterEvent) { if(InterlockedCompareExchangePointer(&_hWriterEvent, hWriterEvent, NULL)) { CloseHandle(hWriterEvent); } } }
return(_hWriterEvent); }
//+-------------------------------------------------------------------
//
// 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
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
HANDLE CRWLock::GetReaderEvent() { if(_hReaderEvent == NULL) { HANDLE hReaderEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if(hReaderEvent) { if(InterlockedCompareExchangePointer(&_hReaderEvent, hReaderEvent, NULL)) { CloseHandle(hReaderEvent); } } }
return(_hReaderEvent); }
//+-------------------------------------------------------------------
//
// Method: CRWLock::AcquireReaderLock public
//
// Synopsis: Makes the thread a reader. Supports nested reader locks.
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
HRESULT CRWLock::AcquireReaderLock( #ifdef RWLOCK_FULL_FUNCTIONALITY
BOOL fReturnErrors, DWORD dwDesiredTimeout #if LOCK_PERF==1
, #endif
#endif
#if LOCK_PERF==1
const char *pszFile, DWORD dwLine, const char *pszLockName #endif
) { HRESULT hr; #ifndef RWLOCK_FULL_FUNCTIONALITY
DWORD dwDesiredTimeout = gdwDefaultTimeout; #endif
// Ensure that the lock was initialized
if(!IsInitialized()) Initialize();
// Check if the thread already has writer lock
if(_dwWriterID == GetCurrentThreadId()) { hr = AcquireWriterLock(); } else { WORD *pwReaderLevel;
hr = GetTLSLockData(&pwReaderLevel); if(SUCCEEDED(hr)) { if(*pwReaderLevel != 0) { ++(*pwReaderLevel); } else { DWORD dwCurrentState, dwKnownState; DWORD dwSpinCount;
// Initialize
hr = S_OK; dwSpinCount = 0; dwCurrentState = _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 = InterlockedCompareExchange((LONG *) &_dwState, (dwKnownState + READER), dwKnownState); if(dwCurrentState == dwKnownState) { // One more reader
break; } } // Check for too many Readers, or waiting readers
else if(((dwKnownState & READERS_MASK) == READERS_MASK) || ((dwKnownState & WAITING_READERS_MASK) == WAITING_READERS_MASK) || ((dwKnownState & CACHING_EVENTS) == READER_SIGNALED)) { RWSleep(1000); dwSpinCount = 0; dwCurrentState = _dwState; } // Check if events are being cached
#ifdef RWLOCK_FULL_FUNCTIONALITY
else if((dwKnownState & CACHING_EVENTS) == CACHING_EVENTS) { if(++dwSpinCount > gdwDefaultSpinCount) { RWSleep(10); dwSpinCount = 0; } dwCurrentState = _dwState; } #endif
// Check spin count
else if(++dwSpinCount > gdwDefaultSpinCount) { // Add to waiting readers
dwCurrentState = InterlockedCompareExchange((LONG *) &_dwState, (dwKnownState + WAITING_READER), dwKnownState); if(dwCurrentState == dwKnownState) { HANDLE hReaderEvent; DWORD dwStatus; DWORD dwModifyState; // One more waiting reader
#ifdef RWLOCK_STATISTICS
InterlockedIncrement((LONG *) &_dwReaderContentionCount); #endif
#if LOCK_PERF==1
gLockTracker.ReaderWaiting(pszFile, dwLine, pszLockName, this); #endif
hReaderEvent = GetReaderEvent(); if(hReaderEvent) { dwStatus = WaitForSingleObject(hReaderEvent, dwDesiredTimeout); } else { ComDebOut((DEB_WARN, "AcquireReaderLock failed to create reader " "event for RWLock 0x%x\n", this)); dwStatus = WAIT_FAILED; hr = RPC_E_OUT_OF_RESOURCES; }
if(dwStatus == WAIT_OBJECT_0) { Win4Assert(_dwState & READER_SIGNALED); Win4Assert((_dwState & READERS_MASK) < READERS_MASK); dwModifyState = READER - WAITING_READER; } else { dwModifyState = -WAITING_READER; if(dwStatus == WAIT_TIMEOUT) { ComDebOut((DEB_WARN, "Timed out trying to acquire reader lock " "for RWLock 0x%x\n", this)); hr = RPC_E_TIMEOUT; } else if(SUCCEEDED(hr)) { ComDebOut((DEB_ERROR, "WaitForSingleObject Failed for " "RWLock 0x%x\n", this)); hr = HRESULT_FROM_WIN32(GetLastError()); } }
// One less waiting reader and he may have become a reader
dwKnownState = ModifyState(dwModifyState);
// Check for last signaled waiting reader
if(((dwKnownState & WAITING_READERS_MASK) == WAITING_READER) && (dwKnownState & READER_SIGNALED)) { dwModifyState = -READER_SIGNALED; if(dwStatus != WAIT_OBJECT_0) { if(hReaderEvent == NULL) hReaderEvent = GetReaderEvent(); Win4Assert(hReaderEvent); dwStatus = WaitForSingleObject(hReaderEvent, INFINITE); Win4Assert(dwStatus == WAIT_OBJECT_0); Win4Assert((_dwState & READERS_MASK) < READERS_MASK); dwModifyState += READER; hr = S_OK; }
RWResetEvent(hReaderEvent); dwKnownState = ModifyState(dwModifyState); }
// Check if the thread became a reader
if(dwStatus == WAIT_OBJECT_0) { break; } } } else { dwCurrentState = _dwState; }
if (RPC_E_TIMEOUT == hr) { DbgPrint("%x:%x> Timed out trying to acquire reader lock 0x%p, WriterID = %x. Switch to the WriterID thread and examine why it is blocked.\n", GetCurrentProcessId(), GetCurrentThreadId(), this, _dwWriterID); #if DBG==1
_dwDeadLockCounter++; #endif
hr = S_OK; if(fBreakOnErrors()) DebugBreak(); } #ifdef _X86_
_asm { pause } #endif
} while(SUCCEEDED(hr));
// Sanity checks
if(SUCCEEDED(hr)) { Win4Assert((_dwState & WRITER) == 0); Win4Assert(_dwState & READERS_MASK); *pwReaderLevel = 1; #ifdef RWLOCK_STATISTICS
InterlockedIncrement((LONG *) &_dwReaderEntryCount); #endif
#if LOCK_PERF==1
gLockTracker.ReaderEntered(pszFile, dwLine, pszLockName, this); #endif
} } } // Check failure return
#if RWLOCK_FULL_FUNCTIONALITY
if(FAILED(hr) && (fReturnErrors == FALSE)) #else
if(FAILED(hr)) #endif
{ Win4Assert(!"Failed to acquire reader lock"); if(fBreakOnErrors()) DebugBreak(); TerminateProcess(GetCurrentProcess(), RWLOCK_FATALFAILURE); } } return(hr); }
//+-------------------------------------------------------------------
//
// Method: CRWLock::AcquireWriterLock public
//
// Synopsis: Makes the thread a writer. Supports nested writer
// locks
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
HRESULT CRWLock::AcquireWriterLock( #ifdef RWLOCK_FULL_FUNCTIONALITY
BOOL fReturnErrors, DWORD dwDesiredTimeout #if LOCK_PERF==1
, #endif
#endif
#if LOCK_PERF==1
const char *pszFile, DWORD dwLine, const char *pszLockName #endif
) { HRESULT hr = S_OK; DWORD dwThreadID = GetCurrentThreadId(); #ifndef RWLOCK_FULL_FUNCTIONALITY
DWORD dwDesiredTimeout = gdwDefaultTimeout; #endif
// Ensure that the lock was initialized
if(!IsInitialized()) Initialize();
// Check if the thread already has writer lock
if(_dwWriterID == dwThreadID) { ++_wWriterLevel; } else { DWORD dwCurrentState, dwKnownState; DWORD dwSpinCount = 0; dwCurrentState = _dwState; do { dwKnownState = dwCurrentState; // Writer need not wait if there are no readers and writer
if(dwKnownState == 0) { // Can be a writer
dwCurrentState = InterlockedCompareExchange((LONG *) &_dwState, WRITER, dwKnownState); if(dwCurrentState == dwKnownState) { // Only writer
break; } } // Check for too many waiting writers
else if(((dwKnownState & WAITING_WRITERS_MASK) == WAITING_WRITERS_MASK)) { RWSleep(1000); dwSpinCount = 0; dwCurrentState = _dwState; } // Check if events are being cached
#ifdef RWLOCK_FULL_FUNCTIONALITY
else if((dwKnownState & CACHING_EVENTS) == CACHING_EVENTS) { if(++dwSpinCount > gdwDefaultSpinCount) { RWSleep(10); dwSpinCount = 0; } dwCurrentState = _dwState; } #endif
// Check spin count
else if(++dwSpinCount > gdwDefaultSpinCount) { // Add to waiting writers
dwCurrentState = InterlockedCompareExchange((LONG *) &_dwState, (dwKnownState + WAITING_WRITER), dwKnownState); if(dwCurrentState == dwKnownState) { HANDLE hWriterEvent; DWORD dwStatus; DWORD dwModifyState; BOOL fLoopback; // One more waiting writer
#ifdef RWLOCK_STATISTICS
InterlockedIncrement((LONG *) &_dwWriterContentionCount); #endif
#if LOCK_PERF==1
gLockTracker.WriterWaiting(pszFile, dwLine, pszLockName, this); #endif
do { fLoopback = FALSE; hr = S_OK; hWriterEvent = GetWriterEvent(); if(hWriterEvent) { dwStatus = WaitForSingleObject(hWriterEvent, dwDesiredTimeout); } else { ComDebOut((DEB_WARN, "AcquireWriterLock failed to create writer " "event for RWLock 0x%x\n", this)); dwStatus = WAIT_FAILED; hr = RPC_E_OUT_OF_RESOURCES; } if(dwStatus == WAIT_OBJECT_0) { Win4Assert(_dwState & WRITER_SIGNALED); dwModifyState = WRITER - WAITING_WRITER - WRITER_SIGNALED; } else { dwModifyState = -WAITING_WRITER; if(dwStatus == WAIT_TIMEOUT) { ComDebOut((DEB_WARN, "Timed out trying to acquire writer " "lock for RWLock 0x%x\n", this)); hr = RPC_E_TIMEOUT; } else if(SUCCEEDED(hr)) { ComDebOut((DEB_ERROR, "WaitForSingleObject Failed for " "RWLock 0x%x\n", this)); hr = HRESULT_FROM_WIN32(GetLastError()); } }
// One less waiting writer and he may have become a writer
dwKnownState = ModifyState(dwModifyState); // Check for last timing out signaled waiting writer
if((dwStatus != WAIT_OBJECT_0) && (dwKnownState & WRITER_SIGNALED) && ((dwKnownState & WAITING_WRITERS_MASK) == WAITING_WRITER)) { fLoopback = TRUE; dwCurrentState = _dwState; do { dwKnownState = dwCurrentState; if(((dwKnownState & WAITING_WRITERS_MASK) == 0) && (dwKnownState & WRITER_SIGNALED)) { dwCurrentState = InterlockedCompareExchange((LONG *) &_dwState, (dwKnownState + WAITING_WRITER), dwKnownState); } else { Win4Assert(FAILED(hr)); fLoopback = FALSE; break; } #ifdef _X86_
// Try to avoid pausing when we won't loop.
if (dwCurrentState != dwKnownState) { _asm { pause } } #endif
} while(dwCurrentState != dwKnownState); }
if(fLoopback) { ComDebOut((DEB_WARN, "Retry of timing out writer for RWLock 0x%x\n", this)); // Reduce the timeout value for retries
dwDesiredTimeout = 100; } } while(fLoopback);
// Check if the thread became a writer
if(dwStatus == WAIT_OBJECT_0) break; } } else { dwCurrentState = _dwState; }
if (RPC_E_TIMEOUT == hr) { DbgPrint("%x:%x> Timed out trying to acquire writer lock 0x%p, WriterID = %x. If WriterID is not zero, switch to the WriterID thread and examine why it is blocked.\n ", GetCurrentProcessId(), GetCurrentThreadId(), this, _dwWriterID); #if DBG==1
_dwDeadLockCounter++; #endif
hr = S_OK; if(fBreakOnErrors()) DebugBreak(); }
#ifdef _X86_
_asm { pause } #endif
} while(SUCCEEDED(hr)); // Sanity checks
if(SUCCEEDED(hr)) { Win4Assert(_dwState & WRITER); Win4Assert((_dwState & WRITER_SIGNALED) == 0); Win4Assert((_dwState & READERS_MASK) == 0); Win4Assert(_dwWriterID == 0); // Save threadid of the writer
_dwWriterID = dwThreadID; _wWriterLevel = 1; ++_dwWriterSeqNum; #ifdef RWLOCK_STATISTICS
++_dwWriterEntryCount; #endif
#if LOCK_PERF==1
gLockTracker.WriterEntered(pszFile, dwLine, pszLockName, this); #endif
} #ifdef RWLOCK_FULL_FUNCTIONALITY
else if(fReturnErrors == FALSE) #else
else #endif
{ Win4Assert(!"Failed to acquire writer lock"); if(fBreakOnErrors()) DebugBreak(); TerminateProcess(GetCurrentProcess(), RWLOCK_FATALFAILURE); } }
return(hr); }
//+-------------------------------------------------------------------
//
// Method: CRWLock::ReleaseWriterLock public
//
// Synopsis: Removes the thread as a writer if not a nested
// call to release the lock
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
HRESULT CRWLock::ReleaseWriterLock() { HRESULT hr = S_OK; DWORD dwThreadID = GetCurrentThreadId();
// Check validity of caller
if(_dwWriterID == dwThreadID) { // Sanity check
Win4Assert(IsInitialized());
// Check for nested release
if(--_wWriterLevel == 0) { DWORD dwCurrentState, dwKnownState, dwModifyState; BOOL fCacheEvents; HANDLE hReaderEvent = NULL, hWriterEvent = NULL;
// Not a writer any more
#if LOCK_PERF==1
gLockTracker.WriterLeaving(this); #endif
_dwWriterID = 0; dwCurrentState = _dwState; do { dwKnownState = dwCurrentState; dwModifyState = -WRITER; fCacheEvents = FALSE; if(dwKnownState & WAITING_READERS_MASK) { hReaderEvent = GetReaderEvent(); if(hReaderEvent == NULL) { ComDebOut((DEB_WARN, "ReleaseWriterLock failed to create " "reader event for RWLock 0x%x\n", this)); RWSleep(100); dwCurrentState = _dwState; dwKnownState = 0; Win4Assert(dwCurrentState != dwKnownState); #ifdef _X86_
_asm { pause } #endif
continue; } dwModifyState += READER_SIGNALED; } else if(dwKnownState & WAITING_WRITERS_MASK) { hWriterEvent = GetWriterEvent(); if(hWriterEvent == NULL) { ComDebOut((DEB_WARN, "ReleaseWriterLock failed to create " "writer event for RWLock 0x%x\n", this)); RWSleep(100); dwCurrentState = _dwState; dwKnownState = 0; Win4Assert(dwCurrentState != dwKnownState); #ifdef _X86_
_asm { pause } #endif
continue; } dwModifyState += WRITER_SIGNALED; } #ifdef RWLOCK_FULL_FUNCTIONALITY
else if((_wFlags & RWLOCKFLAG_CACHEEVENTS) && (dwKnownState == WRITER) && (_hReaderEvent || _hWriterEvent) && ((dwKnownState & CACHING_EVENTS) == 0)) { fCacheEvents = TRUE; dwModifyState += CACHING_EVENTS; } #endif
// Sanity checks
Win4Assert((dwKnownState & CACHING_EVENTS) == 0); Win4Assert((dwKnownState & READERS_MASK) == 0);
dwCurrentState = InterlockedCompareExchange((LONG *) &_dwState, (dwKnownState + dwModifyState), dwKnownState); #ifdef _X86_
_asm { pause } #endif
} while(dwCurrentState != dwKnownState);
// Check for waiting readers
if(dwKnownState & WAITING_READERS_MASK) { Win4Assert(_dwState & READER_SIGNALED); Win4Assert(hReaderEvent); RWSetEvent(hReaderEvent); } // Check for waiting writers
else if(dwKnownState & WAITING_WRITERS_MASK) { Win4Assert(_dwState & WRITER_SIGNALED); Win4Assert(hWriterEvent); RWSetEvent(hWriterEvent); } #ifdef RWLOCK_FULL_FUNCTIONALITY
// Check for the need to release events
else if(fCacheEvents) { ReleaseEvents(); } #endif
} } else { hr = HRESULT_FROM_WIN32(ERROR_NOT_OWNER); Win4Assert(!"Attempt to release writer lock on a wrong thread"); #ifdef RWLOCK_FULL_FUNCTIONALITY
if((_wFlags & RWLOCKFLAG_RETURNERRORS) == 0) #endif
{ if(fBreakOnErrors()) DebugBreak(); TerminateProcess(GetCurrentProcess(), RWLOCK_FATALFAILURE); } }
return(hr); }
//+-------------------------------------------------------------------
//
// Method: CRWLock::ReleaseReaderLock public
//
// Synopsis: Removes the thread as a reader
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
HRESULT CRWLock::ReleaseReaderLock() { HRESULT hr = S_OK;
// Check if the thread has writer lock
if(_dwWriterID == GetCurrentThreadId()) { hr = ReleaseWriterLock(); } else { WORD *pwReaderLevel;
hr = GetTLSLockData(&pwReaderLevel); if(SUCCEEDED(hr)) { if(*pwReaderLevel > 1) { --(*pwReaderLevel); } else if(*pwReaderLevel == 1) { DWORD dwCurrentState, dwKnownState, dwModifyState; BOOL fLastReader, fCacheEvents; HANDLE hReaderEvent = NULL, hWriterEvent = NULL;
// Sanity checks
Win4Assert((_dwState & WRITER) == 0); Win4Assert(_dwState & READERS_MASK);
// Not a reader any more
*pwReaderLevel = 0; dwCurrentState = _dwState; do { dwKnownState = dwCurrentState; dwModifyState = -READER; if((dwKnownState & (READERS_MASK | READER_SIGNALED)) == READER) { fLastReader = TRUE; fCacheEvents = FALSE; if(dwKnownState & WAITING_WRITERS_MASK) { hWriterEvent = GetWriterEvent(); if(hWriterEvent == NULL) { ComDebOut((DEB_WARN, "ReleaseReaderLock failed to create " "writer event for RWLock 0x%x\n", this)); RWSleep(100); dwCurrentState = _dwState; dwKnownState = 0; Win4Assert(dwCurrentState != dwKnownState); #ifdef _X86_
_asm { pause } #endif
continue; } dwModifyState += WRITER_SIGNALED; } else if(dwKnownState & WAITING_READERS_MASK) { hReaderEvent = GetReaderEvent(); if(hReaderEvent == NULL) { ComDebOut((DEB_WARN, "ReleaseReaderLock failed to create " "reader event\n", this)); RWSleep(100); dwCurrentState = _dwState; dwKnownState = 0; Win4Assert(dwCurrentState != dwKnownState); #ifdef _X86_
_asm { pause } #endif
continue; } dwModifyState += READER_SIGNALED; } #ifdef RWLOCK_FULL_FUNCTIONALITY
else if((_wFlags & RWLOCKFLAG_CACHEEVENTS) && (dwKnownState == READER) && (_hReaderEvent || _hWriterEvent)) { fCacheEvents = TRUE; dwModifyState += (WRITER_SIGNALED + READER_SIGNALED); } #endif
} else { fLastReader = FALSE; }
// Sanity checks
Win4Assert((dwKnownState & WRITER) == 0); Win4Assert(dwKnownState & READERS_MASK);
dwCurrentState = InterlockedCompareExchange((LONG *) &_dwState, (dwKnownState + dwModifyState), dwKnownState);
#ifdef _X86_
// Try to avoid pausing when we won't loop.
if (dwCurrentState != dwKnownState) { _asm { pause } } #endif
} while(dwCurrentState != dwKnownState);
#if LOCK_PERF==1
gLockTracker.ReaderLeaving(this); #endif
// Check for last reader
if(fLastReader) { // Check for waiting writers
if(dwKnownState & WAITING_WRITERS_MASK) { Win4Assert(_dwState & WRITER_SIGNALED); Win4Assert(hWriterEvent); RWSetEvent(hWriterEvent); } // Check for waiting readers
else if(dwKnownState & WAITING_READERS_MASK) { Win4Assert(_dwState & READER_SIGNALED); Win4Assert(hReaderEvent); RWSetEvent(hReaderEvent); } #ifdef RWLOCK_FULL_FUNCTIONALITY
// Check for the need to release events
else if(fCacheEvents) { ReleaseEvents(); } #endif
} } else { hr = HRESULT_FROM_WIN32(ERROR_NOT_OWNER); } } else { hr = HRESULT_FROM_WIN32(ERROR_NOT_OWNER); }
if(FAILED(hr)) { #ifdef RWLOCK_FULL_FUNCTIONALITY
if((_wFlags & RWLOCKFLAG_RETURNERRORS) == 0) #endif
{ Win4Assert(!"Attempt to release reader lock on a wrong thread"); if(fBreakOnErrors()) DebugBreak(); TerminateProcess(GetCurrentProcess(), RWLOCK_FATALFAILURE); } } }
return(hr); }
//+-------------------------------------------------------------------
//
// Method: CRWLock::UpgradeToWriterLock public
//
// Synopsis: Upgrades to a writer lock. It returns a BOOL that
// indicates intervening writes.
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
HRESULT CRWLock::UpgradeToWriterLock(LockCookie *pLockCookie, BOOL *pfInterveningWrites #ifdef RWLOCK_FULL_FUNCTIONALITY
, BOOL fReturnErrors, DWORD dwDesiredTimeout #endif
#if LOCK_PERF==1
, const char *pszFile, DWORD dwLine, const char *pszLockName #endif
) { HRESULT hr; DWORD dwThreadID;
// Initialize the cookie
memset(pLockCookie, 0, sizeof(LockCookie)); if(pfInterveningWrites) *pfInterveningWrites = TRUE; dwThreadID = GetCurrentThreadId();
// Check if the thread is already a writer
if(_dwWriterID == dwThreadID) { // Update cookie state
pLockCookie->dwFlags = UPGRADE_COOKIE | COOKIE_WRITER; pLockCookie->wWriterLevel = _wWriterLevel; // No intevening writes
if(pfInterveningWrites) *pfInterveningWrites = FALSE; // Acquire the writer lock again
hr = AcquireWriterLock(); } else { WORD *pwReaderLevel;
// Ensure that the lock was initialized
if(!IsInitialized()) Initialize();
hr = GetTLSLockData(&pwReaderLevel); if(SUCCEEDED(hr)) { BOOL fAcquireWriterLock; DWORD dwWriterSeqNum = 0;
// Check if the thread is a reader
if(*pwReaderLevel != 0) { // Sanity check
Win4Assert(_dwState & READERS_MASK);
// Save lock state in the cookie
pLockCookie->dwFlags = UPGRADE_COOKIE | COOKIE_READER; pLockCookie->pwReaderLevel = pwReaderLevel; pLockCookie->wReaderLevel = *pwReaderLevel;
// If there is only one reader, try to convert reader to a writer
DWORD dwKnownState = InterlockedCompareExchange((LONG *) &_dwState, WRITER, READER); if(dwKnownState == READER) { // Thread is no longer a reader
*pwReaderLevel = 0;
// Save threadid of the writer
_dwWriterID = dwThreadID; _wWriterLevel = 1; ++_dwWriterSeqNum; fAcquireWriterLock = FALSE;
// No intevening writes
if(pfInterveningWrites) *pfInterveningWrites = FALSE; #if RWLOCK_STATISTICS
++_dwWriterEntryCount; #endif
#if LOCK_PERF==1
gLockTracker.ReaderLeaving(this); gLockTracker.WriterEntered(pszFile, dwLine, pszLockName, this); #endif
} else { // Note the current sequence number of the writer lock
dwWriterSeqNum = _dwWriterSeqNum;
// Release the reader lock
*pwReaderLevel = 1; hr = ReleaseReaderLock(); Win4Assert(SUCCEEDED(hr)); fAcquireWriterLock = TRUE; } } else { fAcquireWriterLock = TRUE; pLockCookie->dwFlags = UPGRADE_COOKIE | COOKIE_NONE; }
// Check for the need to acquire the writer lock
if(fAcquireWriterLock) { hr = AcquireWriterLock( #ifdef RWLOCK_FULL_FUNCTIONALITY
TRUE, dwDesiredTimeout #if LOCK_PERF==1
, #endif
#endif
#if LOCK_PERF==1
pszFile, dwLine, pszLockName #endif
); if(SUCCEEDED(hr)) { // Check for intevening writes
if((_dwWriterSeqNum == (dwWriterSeqNum + 1)) && pfInterveningWrites) *pfInterveningWrites = FALSE; } else { if(pLockCookie->dwFlags & COOKIE_READER) { #ifdef RWLOCK_FULL_FUNCTIONALITY
DWORD dwTimeout = (dwDesiredTimeout > gdwReasonableTimeout) ? dwDesiredTimeout : gdwReasonableTimeout; #endif
HRESULT hr1 = AcquireReaderLock( #ifdef RWLOCK_FULL_FUNCTIONALITY
FALSE, dwTimeout #if LOCK_PERF==1
, #endif
#endif
#if LOCK_PERF==1
pszFile, dwLine, pszLockName #endif
); if(SUCCEEDED(hr1)) { *pwReaderLevel = pLockCookie->wReaderLevel; } else { Win4Assert(!"Failed to reacquire reader lock"); if(fBreakOnErrors()) DebugBreak(); TerminateProcess(GetCurrentProcess(), RWLOCK_FATALFAILURE); } } } } } }
// Check failure return
if(FAILED(hr)) { pLockCookie->dwFlags = INVALID_COOKIE; #ifdef RWLOCK_FULL_FUNCTIONALITY
if(fReturnErrors == FALSE) { Win4Assert(!"Failed to upgrade the lock"); if(fBreakOnErrors()) DebugBreak(); TerminateProcess(GetCurrentProcess(), RWLOCK_FATALFAILURE); } #endif
}
return(hr); }
//+-------------------------------------------------------------------
//
// Method: CRWLock::DowngradeFromWriterLock public
//
// Synopsis: Downgrades from a writer lock.
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
HRESULT CRWLock::DowngradeFromWriterLock(LockCookie *pLockCookie #if LOCK_PERF==1
, const char *pszFile, DWORD dwLine, const char *pszLockName #endif
) { HRESULT hr = S_OK;
// Ensure that the cookie is valid
if(pLockCookie->dwFlags & UPGRADE_COOKIE) { DWORD dwThreadID = GetCurrentThreadId();
// Sanity check
Win4Assert((pLockCookie->pwReaderLevel == NULL) || (*pLockCookie->pwReaderLevel == 0));
// Ensure that the thread is a writer
if(_dwWriterID == dwThreadID) { // Release the writer lock
hr = ReleaseWriterLock(); if(SUCCEEDED(hr)) { // Check if the thread was a writer
if(_dwWriterID == dwThreadID) { // Ensure that the thread was a writer and that
// nesting level was restored to the previous
// value
if(((pLockCookie->dwFlags & COOKIE_WRITER) == 0) || (pLockCookie->wWriterLevel != _wWriterLevel)) { Win4Assert(!"Writer lock incorrectly nested"); hr = E_FAIL; } } // Check if the thread was a reader
else if(pLockCookie->dwFlags & COOKIE_READER) { #ifdef RWLOCK_FULL_FUNCTIONALITY
DWORD dwTimeout = (gdwDefaultTimeout > gdwReasonableTimeout) ? gdwDefaultTimeout : gdwReasonableTimeout; #endif
hr = AcquireReaderLock( #ifdef RWLOCK_FULL_FUNCTIONALITY
TRUE, dwTimeout #if LOCK_PERF==1
, #endif
#endif
#if LOCK_PERF==1
pszFile, dwLine, pszLockName #endif
); if(SUCCEEDED(hr)) { *pLockCookie->pwReaderLevel = pLockCookie->wReaderLevel; } else { Win4Assert(!"Failed to reacquire reader lock"); } } else { Win4Assert(pLockCookie->dwFlags & COOKIE_NONE); } } } else { Win4Assert(!"Attempt to downgrade writer lock on a wrong thread"); hr = HRESULT_FROM_WIN32(ERROR_NOT_OWNER); }
// Check failure return
if(FAILED(hr)) { if(fBreakOnErrors()) DebugBreak(); TerminateProcess(GetCurrentProcess(), RWLOCK_FATALFAILURE); } }
return(hr); }
//+-------------------------------------------------------------------
//
// Method: CRWLock::ReleaseLock public
//
// Synopsis: Releases the lock held by the current thread
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
HRESULT CRWLock::ReleaseLock(LockCookie *pLockCookie) { HRESULT hr;
// Initialize the cookie
memset(pLockCookie, 0, sizeof(LockCookie));
// Check if the thread is a writer
if(_dwWriterID == GetCurrentThreadId()) { // Save lock state in the cookie
pLockCookie->dwFlags = RELEASE_COOKIE | COOKIE_WRITER; pLockCookie->dwWriterSeqNum = _dwWriterSeqNum; pLockCookie->wWriterLevel = _wWriterLevel;
// Release the writer lock
_wWriterLevel = 1; hr = ReleaseWriterLock(); Win4Assert(SUCCEEDED(hr)); } else { WORD *pwReaderLevel;
// Ensure that the lock was initialized
if(!IsInitialized()) Initialize();
hr = GetTLSLockData(&pwReaderLevel); if(SUCCEEDED(hr)) { // Check if the thread is a reader
if(*pwReaderLevel != 0) { // Save lock state in the cookie
pLockCookie->dwFlags = RELEASE_COOKIE | COOKIE_READER; pLockCookie->pwReaderLevel = pwReaderLevel; pLockCookie->wReaderLevel = *pwReaderLevel; pLockCookie->dwWriterSeqNum = _dwWriterSeqNum;
// Release the reader lock
*pwReaderLevel = 1; hr = ReleaseReaderLock(); Win4Assert(SUCCEEDED(hr)); } else { pLockCookie->dwFlags = RELEASE_COOKIE | COOKIE_NONE; } } else { hr = S_OK; pLockCookie->dwFlags = RELEASE_COOKIE | COOKIE_NONE; } }
return(hr); }
//+-------------------------------------------------------------------
//
// Method: CRWLock::RestoreLock public
//
// Synopsis: Restore the lock held by the current thread
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
HRESULT CRWLock::RestoreLock(LockCookie *pLockCookie, BOOL *pfInterveningWrites #if LOCK_PERF==1
, const char *pszFile, DWORD dwLine, const char *pszLockName #endif
) { HRESULT hr = S_OK;
// Initialize
if(pfInterveningWrites) *pfInterveningWrites = TRUE;
// Ensure that the cookie is valid
if(pLockCookie->dwFlags & RELEASE_COOKIE) { DWORD dwThreadID = GetCurrentThreadId(); #ifdef RWLOCK_FULL_FUNCTIONALITY
DWORD dwTimeout = (gdwDefaultTimeout > gdwReasonableTimeout) ? gdwDefaultTimeout : gdwReasonableTimeout; #endif
// Check if the thread holds reader or writer lock
if(((pLockCookie->pwReaderLevel != NULL) && (*pLockCookie->pwReaderLevel > 0)) || (_dwWriterID == dwThreadID)) { Win4Assert(!"Thread holds reader or writer lock"); if(fBreakOnErrors()) DebugBreak(); TerminateProcess(GetCurrentProcess(), RWLOCK_FATALFAILURE); } // Check if the thread was a writer
else if(pLockCookie->dwFlags & COOKIE_WRITER) { // Acquire writer lock
hr = AcquireWriterLock( #ifdef RWLOCK_FULL_FUNCTIONALITY
FALSE, dwTimeout #if LOCK_PERF==1
, #endif
#endif
#if LOCK_PERF==1
pszFile, dwLine, pszLockName #endif
); Win4Assert(SUCCEEDED(hr)); _wWriterLevel = pLockCookie->wWriterLevel; if((_dwWriterSeqNum == (pLockCookie->dwWriterSeqNum + 1)) && pfInterveningWrites) *pfInterveningWrites = FALSE; } // Check if the thread was a reader
else if(pLockCookie->dwFlags & COOKIE_READER) { hr = AcquireReaderLock( #ifdef RWLOCK_FULL_FUNCTIONALITY
FALSE, dwTimeout #if LOCK_PERF==1
, #endif
#endif
#if LOCK_PERF==1
pszFile, dwLine, pszLockName #endif
); Win4Assert(SUCCEEDED(hr)); *pLockCookie->pwReaderLevel = pLockCookie->wReaderLevel; if((_dwWriterSeqNum == (pLockCookie->dwWriterSeqNum + 1)) && pfInterveningWrites) *pfInterveningWrites = FALSE; } else { Win4Assert(pLockCookie->dwFlags & COOKIE_NONE); } }
return(hr); }
//+-------------------------------------------------------------------
//
// Method: CStaticRWLock::Initialize public
//
// Synopsis: Initializes state. It is important that the
// default constructor only Zero out the memory
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
extern CRITICAL_SECTION g_OleMutexCreationSem; void CStaticRWLock::Initialize() { // Acquire lock creation critical section
EnterCriticalSection (&g_OleMutexCreationSem);
// Prevent second initialization
if(!IsInitialized()) { _dwLockNum = gdwLockSeqNum++; #if LOCK_PERF==1
gLockTracker.RegisterLock(this, TRUE); #endif
// The initialization should be complete
// before delegating to the base class
CRWLock::Initialize(); }
// Release lock creation critical section
LeaveCriticalSection (&g_OleMutexCreationSem);
return; }
LockEntry * GetLockEntryFromTLS() { LockEntry *pLockEntry = NULL; #ifdef __NOOLETLS__
pLockEntry = (LockEntry *) TlsGetValue(gLockTlsIdx); if (!pLockEntry) { pLockEntry = (LockEntry *) PrivMemAlloc(sizeof(LockEntry)); if (pLockEntry) { memset(pLockEntry, 0, sizeof(LockEntry)); TlsSetValue(gLockTlsIdx, pLockEntry); }
} #else
HRESULT hr; COleTls Tls(hr);
if(SUCCEEDED(hr)) { pLockEntry = &(Tls->lockEntry); } #endif
return pLockEntry; }
//+-------------------------------------------------------------------
//
// Method: CStaticRWLock::GetTLSLockData private
//
// Synopsis: Obtains the data mainitained in TLS for the lock
//
// History: 21-Aug-98 Gopalk Created
//
//+-------------------------------------------------------------------
HRESULT CStaticRWLock::GetTLSLockData(WORD **ppwReaderLevel) { HRESULT hr = E_OUTOFMEMORY;
// Ensure that the lock was initialized
if(IsInitialized()) { LockEntry *pLockEntry = GetLockEntryFromTLS(); if (pLockEntry) { // Compute the quotient and remainder
DWORD dwSkip = _dwLockNum / LOCKS_PER_ENTRY; DWORD dwIndex = _dwLockNum % LOCKS_PER_ENTRY;
// Skip quotient entries
while(dwSkip && pLockEntry) { // Allocate the lock entries if needed
if(pLockEntry->pNext == NULL) { LockEntry *pEntry; pEntry = (LockEntry *) PrivMemAlloc(sizeof(LockEntry)); if(pEntry) { memset(pEntry, 0 , sizeof(LockEntry)); pEntry = (LockEntry *) InterlockedCompareExchangePointer((void **) &(pLockEntry->pNext), pEntry, NULL); if(pEntry) PrivMemFree(pEntry); } }
// Skip to next lock entry
pLockEntry = pLockEntry->pNext; --dwSkip; }
// Check for OOM
if(pLockEntry) { *ppwReaderLevel = &(pLockEntry->wReaderLevel[dwIndex]); hr = S_OK; } else { *ppwReaderLevel = NULL; hr = E_OUTOFMEMORY; } } } else { *ppwReaderLevel = NULL; hr = S_FALSE; }
return(hr); }
|