|
|
/*++
Copyright (C) Microsoft Corporation, 2000
Module Name:
SWMR.hxx
Abstract:
Class definitions for Single Writer Multiple Readers lock. See the header in swmr.cxx for more details.
Author:
Kamen Moutafov [KamenM]
Revision History:
KamenM Aug 2000 Created
--*/
#if _MSC_VER >= 1200
#pragma once
#endif
#ifndef __SWMR_HXX_
#define __SWMR_HXX_
#if defined(_TEST_SWMR_)
extern long TestCounter; #endif
// forwards
class SWMRWaiterQueue;
void SpinOrYield ( IN OUT BOOL *fNonFirstIteration, IN SWMRWaiterQueue *Queue );
typedef enum tagSWMRWaiterType { swmrwtReader = 0, swmrwtWriter, swmrwtInvalid } SWMRWaiterType;
class SWMRWaiter { public: const static long OwnLock = 4; const static long OwnLockMask = 4;
// the waiter types are taken from the SWMRWaiterType enum
const static WaiterTypeMask = 3;
// if this constant is set in flags, then another writer waiter
// was inserted before the current writer after the current
// writer was queued. This flag can be raised for writers only.
// Note that only the code that converts the lock to exclusive
// reads this lock even though it can be set to other waiters
// as well.
const static long WriterInsertedBefore = 8; const static long WriterInsertedBeforeMask = 8;
// the flags contain three sets of flags - the type of lock, whether
// the current waiter owns the lock, and whether another waiter was
// inserted before the current one after the current waiter started
// waiting. The flags are protected by the
// queue lock and cannot change on any queued waiter block aside
// from raising flags for writers losing shared lock while
// converting to exclusive
long Flags;
// used only for reader waiter blocks. For writers it is not used.
// Protected by the queue lock
long RefCount;
// NULL if there is no next waiter. Points to the next waiter otherwise.
// Cannot change from NULL->!NULL outside the lock. The other change is
// irrelevant - waiters simply fall off the queue
SWMRWaiter *Next;
// event to wait on
HANDLE hEvent;
SWMRWaiter ( void ) { hEvent = NULL; Initialize(swmrwtInvalid, FALSE); }
RPC_STATUS Initialize ( IN SWMRWaiterType WaiterType, IN BOOL fEventRequired ) { Flags = WaiterType; RefCount = 1; Next = NULL; if (fEventRequired) { return InitializeEventIfNecessary(); } else { return RPC_S_OK; } }
RPC_STATUS InitializeEvent ( void ) { #if defined(_TEST_SWMR_)
int RndNum;
// inject failures for unit tests
RndNum = rand(); if ((RndNum % 20) != 11) { #endif
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!hEvent) { return RPC_S_OUT_OF_MEMORY; }
LogEvent(SU_EVENT, EV_CREATE, hEvent, this, 0, 1, 1); return RPC_S_OK; #if defined(_TEST_SWMR_)
} else { return RPC_S_OUT_OF_MEMORY; } #endif
}
RPC_STATUS InitializeEventIfNecessary ( void ) { if (hEvent) { ResetEvent(hEvent); return RPC_S_OK; } else return InitializeEvent();
}
static void CookupWaiterFromEventAndBuffer ( IN SWMRWaiter *WaiterBlock, SWMRWaiterType WaiterType, IN HANDLE hEvent) { WaiterBlock->Flags = WaiterType; WaiterBlock->RefCount = 1; WaiterBlock->Next = NULL; WaiterBlock->hEvent = hEvent; }
inline void FreeWaiterData ( void ) { if (hEvent) { LogEvent(SU_EVENT, EV_DELETE, hEvent, this, 0, 1, 2); CloseHandle(hEvent); } } };
const size_t SizeOfWaiterBlock = sizeof(SWMRWaiter);
// SWMR - queued implementation
class SWMRWaiterQueue { // not protected - interlocks must be used. When it is locked,
// nobody can queue themselves, and nobody can unqueue themselves
SWMRWaiter *LastWaiter;
// A pointer to the waiter block for the current
// owner(s)
// not protected. If the lock is free, changed
// to something by the first owner. If the lock
// is not free and ownership is handed to somebody,
// the owner sets this to the next owner. It is
// used only during unlocking
SWMRWaiter *FirstWaiter;
// the thread id of the owner in case of exclusive ownership.
// used only for debugging. This is not reset upon leaving ownership
// so in case the lock is not owned, or owned in shared mode, the
// thread id is stale
// Don't use this in real processing!
ULONG LastWriterThreadId;
public: const static ULONG_PTR Free = 0x0; const static ULONG_PTR LastReader = 0x1; const static ULONG_PTR LastWriter = 0x2; const static ULONG_PTR Locked = 0x3; const static ULONG_PTR StateMask = 0x3;
SWMRWaiterQueue ( void ) { LastWaiter = NULL; FirstWaiter = NULL; }
~SWMRWaiterQueue ( void ) { ASSERT(LastWaiter == NULL); }
SWMRWaiter * Lock ( void ) /*++
Routine Description:
Locks the queue.
Arguments:
Return Value:
The old queue lock value. This needs to be passed to Unlock
--*/ { SWMRWaiter *CurrentLastWaiter; BOOL fNonFirstIteration = FALSE;
while (TRUE) { CurrentLastWaiter = LastWaiter;
if (((ULONG_PTR)CurrentLastWaiter & StateMask) != Locked) { if (InterlockedCompareExchangePointer((PVOID *)&LastWaiter, (PVOID)((ULONG_PTR)CurrentLastWaiter | Locked), CurrentLastWaiter) == CurrentLastWaiter) { return CurrentLastWaiter; } }
SpinOrYield(&fNonFirstIteration, this); } }
void UnlockAndCommit ( SWMRWaiter *OldLastWaiter ) /*++
Routine Description:
Unlocks the queue and restores the old value.
Arguments: The last waiter before the queue was locked.
Return Value:
--*/ { SWMRWaiter *CurrentWaiter; SWMRWaiter *PreviousWaiter; char Buffer[256]; char *BufPtr; char WaiterSymbol; BOOL fDoDump;
CurrentWaiter = LastWaiter; ASSERT(((ULONG_PTR)CurrentWaiter & StateMask) == Locked); ASSERT(((ULONG_PTR)OldLastWaiter & StateMask) != Locked);
#if defined(_TEST_SWMR_)
// ensure we leave the chain in consistent state
if (OldLastWaiter) { CurrentWaiter = FirstWaiter; BufPtr = Buffer;
fDoDump = ((TestCounter & 0x3FFF) == 0);
while (CurrentWaiter) { if (fDoDump) { if ((CurrentWaiter->Flags & SWMRWaiter::WaiterTypeMask) == swmrwtReader) WaiterSymbol = 'R'; else WaiterSymbol = 'W';
ASSERT(CurrentWaiter->RefCount != 0); if (CurrentWaiter->RefCount == 1) { *BufPtr = WaiterSymbol; BufPtr ++; } else { ASSERT(WaiterSymbol == 'R'); memset(BufPtr, WaiterSymbol, CurrentWaiter->RefCount); BufPtr += CurrentWaiter->RefCount; } }
PreviousWaiter = CurrentWaiter; CurrentWaiter = CurrentWaiter->Next;
if (CurrentWaiter && ((CurrentWaiter->Flags & SWMRWaiter::WaiterTypeMask) == swmrwtReader)) { // we don't want to have adjacent reader segments
ASSERT((PreviousWaiter->Flags & SWMRWaiter::WaiterTypeMask) != swmrwtReader); }
if (fDoDump) { *BufPtr = '-'; BufPtr ++; } }
if (fDoDump) { *BufPtr = '\0'; Dump("%d: %s\n", TestCounter, Buffer); }
ASSERT(PreviousWaiter == (SWMRWaiter *)((ULONG_PTR)OldLastWaiter & ~StateMask)); } #endif
LastWaiter = OldLastWaiter; }
inline ULONG_PTR GetLastWaiterState ( void ) volatile { return (((ULONG_PTR)LastWaiter) & StateMask); }
inline SWMRWaiter * InterlockCompareExchangeLastWaiter ( IN SWMRWaiter *Comperand, IN SWMRWaiter *Exchange, IN ULONG_PTR State ) { ASSERT((State & ~StateMask) == 0); return (SWMRWaiter *)InterlockedCompareExchangePointer((PVOID *)&LastWaiter, (PVOID)((ULONG_PTR)Exchange | State), Comperand); }
inline BOOL VerifyState ( IN SWMRWaiter *OldWaiter, IN ULONG_PTR ExpectedState ) { return (((ULONG_PTR)OldWaiter & StateMask) == ExpectedState); }
inline SWMRWaiter * GetFirstWaiter ( void ) { return FirstWaiter; }
inline void SetFirstWaiter ( IN SWMRWaiter *NewFirstWaiter ) { FirstWaiter = NewFirstWaiter; }
inline SWMRWaiter * RemoveReaderStateFromWaiterPtr ( IN SWMRWaiter *Waiter ) { ASSERT(((ULONG_PTR)Waiter & StateMask) == LastReader); return (SWMRWaiter *)((ULONG_PTR)Waiter & ~StateMask); }
inline SWMRWaiter * RemoveWriterStateFromWaiterPtr ( IN SWMRWaiter *Waiter ) { ASSERT(((ULONG_PTR)Waiter & StateMask) == LastWriter); return (SWMRWaiter *)((ULONG_PTR)Waiter & ~StateMask); }
inline SWMRWaiter * RemoveStateFromWaiterPtr ( IN SWMRWaiter *Waiter ) { return (SWMRWaiter *)((ULONG_PTR)Waiter & ~StateMask); }
inline SWMRWaiter * AddReaderStateInWaiterPtr ( IN SWMRWaiter *Waiter ) { ASSERT(((ULONG_PTR)Waiter & StateMask) == Free); return (SWMRWaiter *)((ULONG_PTR)Waiter | LastReader); }
inline SWMRWaiter * AddWriterStateInWaiterPtr ( IN SWMRWaiter *Waiter ) { ASSERT(((ULONG_PTR)Waiter & StateMask) == Free); return (SWMRWaiter *)((ULONG_PTR)Waiter | LastWriter); }
inline SWMRWaiter * SetStateInWaiterPtr ( IN SWMRWaiter *Waiter, IN ULONG_PTR DesiredState ) { ASSERT(((ULONG_PTR)Waiter & StateMask) == Free); return (SWMRWaiter *)((ULONG_PTR)Waiter | DesiredState); }
inline void SetLastWriterThreadId ( void ) { LastWriterThreadId = HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread); } };
class SWMRLock : public SWMRWaiterQueue { private: SWMRWaiter CachedWaiter;
// 1 means the cached waiter is available
// 0 means it is not
long CachedWaiterAvailable;
SWMRWaiter * AllocateWaiter ( IN SWMRWaiterType WaiterType, IN BOOL fEventRequired );
static void FreeWaiter ( IN SWMRWaiter *Waiter );
void StoreOrFreeSpareWaiter ( IN OUT SWMRWaiter **WaiterCache OPTIONAL, IN SWMRWaiter *Waiter OPTIONAL );
void LockSharedOnLastReader ( IN OUT SWMRWaiter **WaiterCache OPTIONAL, IN OUT SWMRWaiter *OldWaiter, IN OUT SWMRWaiter *AllocatedWaiter );
void LockSharedOrExclusiveOnLastWriter ( IN OUT SWMRWaiter **WaiterCache OPTIONAL, IN OUT SWMRWaiter *OldWaiter, IN OUT SWMRWaiter *AllocatedWaiter, IN ULONG_PTR DesiredState );
void LockExclusiveOnLastReader ( IN OUT SWMRWaiter **WaiterCache OPTIONAL, IN OUT SWMRWaiter *OldWaiter, IN OUT SWMRWaiter *AllocatedWaiter );
RPC_STATUS LockSharedOrExclusive ( IN OUT SWMRWaiter **WaiterCache OPTIONAL, IN SWMRWaiterType WaiterType, IN ULONG_PTR DesiredState );
public: SWMRLock ( void ) { CachedWaiterAvailable = TRUE; }
~SWMRLock ( void ) { ASSERT(CachedWaiterAvailable); CachedWaiter.FreeWaiterData(); }
inline RPC_STATUS LockShared ( IN OUT SWMRWaiter **WaiterCache OPTIONAL ) /*++
Routine Description:
Obtains a shared access lock.
Arguments: WaiterCache - An optional parameter allowing caching of waiter blocks. If WaiterCache == NULL, no caching will be done. If WaiterCache != NULL, but *WaiterCache == NULL, *WaiterCache may be set to the new Waiter block. If *WaiterCache != NULL, the cached value will be used. Its hEvent must be set
Return Value: RPC_S_OK or RPC_S_* for error
--*/ { return LockSharedOrExclusive(WaiterCache, swmrwtReader, LastReader); }
inline RPC_STATUS LockExclusive ( IN OUT SWMRWaiter **WaiterCache OPTIONAL ) /*++
Routine Description:
Obtains an exclusive writer lock
Arguments: WaiterCache - An optional parameter allowing caching of waiter blocks. If WaiterCache == NULL, no caching will be done. If WaiterCache != NULL, but *WaiterCache == NULL, *WaiterCache may be set to the new Waiter block. If *WaiterCache != NULL, the cached value will be used. Its hEvent must be set
Return Value: RPC_S_OK or RPC_S_* for error
--*/ { return LockSharedOrExclusive(WaiterCache, swmrwtWriter, LastWriter); }
void UnlockShared ( IN OUT SWMRWaiter **WaiterCache OPTIONAL );
void UnlockExclusive ( IN OUT SWMRWaiter **WaiterCache OPTIONAL );
void Unlock ( IN OUT SWMRWaiter **WaiterCache OPTIONAL );
RPC_STATUS ConvertToExclusive ( IN OUT SWMRWaiter **WaiterCache OPTIONAL );
RPC_STATUS ConvertToShared ( IN OUT SWMRWaiter **WaiterCache OPTIONAL, IN BOOL fSyncCacheUsed );
static void FreeWaiterCache ( IN SWMRWaiter **WaiterCache ) /*++
Routine Description:
Allows an external caller to free its cache. All cache owners must call this before going away.
Arguments: WaiterCache - the cache. It must be non-NULL. If it points to a cached waiter, the cached waiter will be freed.
Return Value:
--*/ { ASSERT(WaiterCache); if (*WaiterCache) FreeWaiter(*WaiterCache); } };
#endif // __SWMR_HXX
|