649 lines
15 KiB
C++
649 lines
15 KiB
C++
|
/*++
|
||
|
|
||
|
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
|
||
|
|