windows-nt/Source/XPSP1/NT/com/rpc/runtime/mtrt/swmr.hxx
2020-09-26 16:20:57 +08:00

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