You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1911 lines
64 KiB
1911 lines
64 KiB
//+-------------------------------------------------------------------
|
|
//
|
|
// 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);
|
|
}
|
|
|