/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS system libraries * PURPOSE: Slim Reader/Writer (SRW) Routines * PROGRAMMER: Thomas Weidenmueller * * NOTES: The algorithms used in this implementation * may be different from Vista's implementation. * Since applications should treat the RTL_SRWLOCK * structure as opaque data, it should not matter. * The algorithms are probably not as optimized. */ /* INCLUDES *****************************************************************/ #include #define NDEBUG #include /* FUNCTIONS *****************************************************************/ #ifdef _WIN64 #define InterlockedBitTestAndSetPointer(ptr,val) InterlockedBitTestAndSet64((PLONGLONG)ptr,(LONGLONG)val) #define InterlockedAddPointer(ptr,val) InterlockedAdd64((PLONGLONG)ptr,(LONGLONG)val) #define InterlockedAndPointer(ptr,val) InterlockedAnd64((PLONGLONG)ptr,(LONGLONG)val) #define InterlockedOrPointer(ptr,val) InterlockedOr64((PLONGLONG)ptr,(LONGLONG)val) #else #define InterlockedBitTestAndSetPointer(ptr,val) InterlockedBitTestAndSet((PLONG)ptr,(LONG)val) #define InterlockedAddPointer(ptr,val) InterlockedAdd((PLONG)ptr,(LONG)val) #define InterlockedAndPointer(ptr,val) InterlockedAnd((PLONG)ptr,(LONG)val) #define InterlockedOrPointer(ptr,val) InterlockedOr((PLONG)ptr,(LONG)val) #endif #define RTL_SRWLOCK_OWNED_BIT 0 #define RTL_SRWLOCK_CONTENDED_BIT 1 #define RTL_SRWLOCK_SHARED_BIT 2 #define RTL_SRWLOCK_CONTENTION_LOCK_BIT 3 #define RTL_SRWLOCK_OWNED (1 << RTL_SRWLOCK_OWNED_BIT) #define RTL_SRWLOCK_CONTENDED (1 << RTL_SRWLOCK_CONTENDED_BIT) #define RTL_SRWLOCK_SHARED (1 << RTL_SRWLOCK_SHARED_BIT) #define RTL_SRWLOCK_CONTENTION_LOCK (1 << RTL_SRWLOCK_CONTENTION_LOCK_BIT) #define RTL_SRWLOCK_MASK (RTL_SRWLOCK_OWNED | RTL_SRWLOCK_CONTENDED | \ RTL_SRWLOCK_SHARED | RTL_SRWLOCK_CONTENTION_LOCK) #define RTL_SRWLOCK_BITS 4 typedef struct _RTLP_SRWLOCK_SHARED_WAKE { LONG Wake; volatile struct _RTLP_SRWLOCK_SHARED_WAKE *Next; } volatile RTLP_SRWLOCK_SHARED_WAKE, *PRTLP_SRWLOCK_SHARED_WAKE; typedef struct _RTLP_SRWLOCK_WAITBLOCK { /* SharedCount is the number of shared acquirers. */ LONG SharedCount; /* Last points to the last wait block in the chain. The value is only valid when read from the first wait block. */ volatile struct _RTLP_SRWLOCK_WAITBLOCK *Last; /* Next points to the next wait block in the chain. */ volatile struct _RTLP_SRWLOCK_WAITBLOCK *Next; union { /* Wake is only valid for exclusive wait blocks */ LONG Wake; /* The wake chain is only valid for shared wait blocks */ struct { PRTLP_SRWLOCK_SHARED_WAKE SharedWakeChain; PRTLP_SRWLOCK_SHARED_WAKE LastSharedWake; }; }; BOOLEAN Exclusive; } volatile RTLP_SRWLOCK_WAITBLOCK, *PRTLP_SRWLOCK_WAITBLOCK; static VOID NTAPI RtlpReleaseWaitBlockLockExclusive(IN OUT PRTL_SRWLOCK SRWLock, IN PRTLP_SRWLOCK_WAITBLOCK FirstWaitBlock) { PRTLP_SRWLOCK_WAITBLOCK Next; LONG_PTR NewValue; /* NOTE: We're currently in an exclusive lock in contended mode. */ Next = FirstWaitBlock->Next; if (Next != NULL) { /* There's more blocks chained, we need to update the pointers in the next wait block and update the wait block pointer. */ NewValue = (LONG_PTR)Next | RTL_SRWLOCK_OWNED | RTL_SRWLOCK_CONTENDED; if (!FirstWaitBlock->Exclusive) { /* The next wait block has to be an exclusive lock! */ ASSERT(Next->Exclusive); /* Save the shared count */ Next->SharedCount = FirstWaitBlock->SharedCount; NewValue |= RTL_SRWLOCK_SHARED; } Next->Last = FirstWaitBlock->Last; } else { /* Convert the lock to a simple lock. */ if (FirstWaitBlock->Exclusive) NewValue = RTL_SRWLOCK_OWNED; else { ASSERT(FirstWaitBlock->SharedCount > 0); NewValue = ((LONG_PTR)FirstWaitBlock->SharedCount << RTL_SRWLOCK_BITS) | RTL_SRWLOCK_SHARED | RTL_SRWLOCK_OWNED; } } (void)InterlockedExchangePointer(&SRWLock->Ptr, (PVOID)NewValue); if (FirstWaitBlock->Exclusive) { (void)InterlockedOr(&FirstWaitBlock->Wake, TRUE); } else { PRTLP_SRWLOCK_SHARED_WAKE WakeChain, NextWake; /* If we were the first one to acquire the shared lock, we now need to wake all others... */ WakeChain = FirstWaitBlock->SharedWakeChain; do { NextWake = WakeChain->Next; (void)InterlockedOr((PLONG)&WakeChain->Wake, TRUE); WakeChain = NextWake; } while (WakeChain != NULL); } } static VOID NTAPI RtlpReleaseWaitBlockLockLastShared(IN OUT PRTL_SRWLOCK SRWLock, IN PRTLP_SRWLOCK_WAITBLOCK FirstWaitBlock) { PRTLP_SRWLOCK_WAITBLOCK Next; LONG_PTR NewValue; /* NOTE: We're currently in a shared lock in contended mode. */ /* The next acquirer to be unwaited *must* be an exclusive lock! */ ASSERT(FirstWaitBlock->Exclusive); Next = FirstWaitBlock->Next; if (Next != NULL) { /* There's more blocks chained, we need to update the pointers in the next wait block and update the wait block pointer. */ NewValue = (LONG_PTR)Next | RTL_SRWLOCK_OWNED | RTL_SRWLOCK_CONTENDED; Next->Last = FirstWaitBlock->Last; } else { /* Convert the lock to a simple exclusive lock. */ NewValue = RTL_SRWLOCK_OWNED; } (void)InterlockedExchangePointer(&SRWLock->Ptr, (PVOID)NewValue); (void)InterlockedOr(&FirstWaitBlock->Wake, TRUE); } static VOID NTAPI RtlpReleaseWaitBlockLock(IN OUT PRTL_SRWLOCK SRWLock) { InterlockedAndPointer(&SRWLock->Ptr, ~RTL_SRWLOCK_CONTENTION_LOCK); } static PRTLP_SRWLOCK_WAITBLOCK NTAPI RtlpAcquireWaitBlockLock(IN OUT PRTL_SRWLOCK SRWLock) { LONG_PTR PrevValue; PRTLP_SRWLOCK_WAITBLOCK WaitBlock; while (1) { PrevValue = InterlockedOrPointer(&SRWLock->Ptr, RTL_SRWLOCK_CONTENTION_LOCK); if (!(PrevValue & RTL_SRWLOCK_CONTENTION_LOCK)) break; YieldProcessor(); } if (!(PrevValue & RTL_SRWLOCK_CONTENDED) || (PrevValue & ~RTL_SRWLOCK_MASK) == 0) { /* Too bad, looks like the wait block was removed in the meanwhile, unlock again */ RtlpReleaseWaitBlockLock(SRWLock); return NULL; } WaitBlock = (PRTLP_SRWLOCK_WAITBLOCK)(PrevValue & ~RTL_SRWLOCK_MASK); return WaitBlock; } static VOID NTAPI RtlpAcquireSRWLockExclusiveWait(IN OUT PRTL_SRWLOCK SRWLock, IN PRTLP_SRWLOCK_WAITBLOCK WaitBlock) { LONG_PTR CurrentValue; while (1) { CurrentValue = *(volatile LONG_PTR *)&SRWLock->Ptr; if (!(CurrentValue & RTL_SRWLOCK_SHARED)) { if (CurrentValue & RTL_SRWLOCK_CONTENDED) { if (WaitBlock->Wake != 0) { /* Our wait block became the first one in the chain, we own the lock now! */ break; } } else { /* The last wait block was removed and/or we're finally a simple exclusive lock. This means we don't need to wait anymore, we acquired the lock! */ break; } } YieldProcessor(); } } static VOID NTAPI RtlpAcquireSRWLockSharedWait(IN OUT PRTL_SRWLOCK SRWLock, IN OUT PRTLP_SRWLOCK_WAITBLOCK FirstWait OPTIONAL, IN OUT PRTLP_SRWLOCK_SHARED_WAKE WakeChain) { if (FirstWait != NULL) { while (WakeChain->Wake == 0) { YieldProcessor(); } } else { LONG_PTR CurrentValue; while (1) { CurrentValue = *(volatile LONG_PTR *)&SRWLock->Ptr; if (CurrentValue & RTL_SRWLOCK_SHARED) { /* The RTL_SRWLOCK_OWNED bit always needs to be set when RTL_SRWLOCK_SHARED is set! */ ASSERT(CurrentValue & RTL_SRWLOCK_OWNED); if (CurrentValue & RTL_SRWLOCK_CONTENDED) { if (WakeChain->Wake != 0) { /* Our wait block became the first one in the chain, we own the lock now! */ break; } } else { /* The last wait block was removed and/or we're finally a simple shared lock. This means we don't need to wait anymore, we acquired the lock! */ break; } } YieldProcessor(); } } } VOID NTAPI RtlInitializeSRWLock(OUT PRTL_SRWLOCK SRWLock) { SRWLock->Ptr = NULL; } VOID NTAPI RtlAcquireSRWLockShared(IN OUT PRTL_SRWLOCK SRWLock) { __ALIGNED(16) RTLP_SRWLOCK_WAITBLOCK StackWaitBlock; RTLP_SRWLOCK_SHARED_WAKE SharedWake; LONG_PTR CurrentValue, NewValue; PRTLP_SRWLOCK_WAITBLOCK First, Shared, FirstWait; while (1) { CurrentValue = *(volatile LONG_PTR *)&SRWLock->Ptr; if (CurrentValue & RTL_SRWLOCK_SHARED) { /* NOTE: It is possible that the RTL_SRWLOCK_OWNED bit is set! */ if (CurrentValue & RTL_SRWLOCK_CONTENDED) { /* There's other waiters already, lock the wait blocks and increment the shared count */ First = RtlpAcquireWaitBlockLock(SRWLock); if (First != NULL) { FirstWait = NULL; if (First->Exclusive) { /* We need to setup a new wait block! Although we're currently in a shared lock and we're acquiring a shared lock, there are exclusive locks queued. We need to wait until those are released. */ Shared = First->Last; if (Shared->Exclusive) { StackWaitBlock.Exclusive = FALSE; StackWaitBlock.SharedCount = 1; StackWaitBlock.Next = NULL; StackWaitBlock.Last = &StackWaitBlock; StackWaitBlock.SharedWakeChain = &SharedWake; Shared->Next = &StackWaitBlock; First->Last = &StackWaitBlock; Shared = &StackWaitBlock; FirstWait = &StackWaitBlock; } else { Shared->LastSharedWake->Next = &SharedWake; Shared->SharedCount++; } } else { Shared = First; Shared->LastSharedWake->Next = &SharedWake; Shared->SharedCount++; } SharedWake.Next = NULL; SharedWake.Wake = 0; Shared->LastSharedWake = &SharedWake; RtlpReleaseWaitBlockLock(SRWLock); RtlpAcquireSRWLockSharedWait(SRWLock, FirstWait, &SharedWake); /* Successfully incremented the shared count, we acquired the lock */ break; } } else { /* This is a fastest path, just increment the number of current shared locks */ /* Since the RTL_SRWLOCK_SHARED bit is set, the RTL_SRWLOCK_OWNED bit also has to be set! */ ASSERT(CurrentValue & RTL_SRWLOCK_OWNED); NewValue = (CurrentValue >> RTL_SRWLOCK_BITS) + 1; NewValue = (NewValue << RTL_SRWLOCK_BITS) | (CurrentValue & RTL_SRWLOCK_MASK); if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr, (PVOID)NewValue, (PVOID)CurrentValue) == CurrentValue) { /* Successfully incremented the shared count, we acquired the lock */ break; } } } else { if (CurrentValue & RTL_SRWLOCK_OWNED) { /* The resource is currently acquired exclusively */ if (CurrentValue & RTL_SRWLOCK_CONTENDED) { SharedWake.Next = NULL; SharedWake.Wake = 0; /* There's other waiters already, lock the wait blocks and increment the shared count. If the last block in the chain is an exclusive lock, add another block. */ StackWaitBlock.Exclusive = FALSE; StackWaitBlock.SharedCount = 0; StackWaitBlock.Next = NULL; StackWaitBlock.Last = &StackWaitBlock; StackWaitBlock.SharedWakeChain = &SharedWake; First = RtlpAcquireWaitBlockLock(SRWLock); if (First != NULL) { Shared = First->Last; if (Shared->Exclusive) { Shared->Next = &StackWaitBlock; First->Last = &StackWaitBlock; Shared = &StackWaitBlock; FirstWait = &StackWaitBlock; } else { FirstWait = NULL; Shared->LastSharedWake->Next = &SharedWake; } Shared->SharedCount++; Shared->LastSharedWake = &SharedWake; RtlpReleaseWaitBlockLock(SRWLock); RtlpAcquireSRWLockSharedWait(SRWLock, FirstWait, &SharedWake); /* Successfully incremented the shared count, we acquired the lock */ break; } } else { SharedWake.Next = NULL; SharedWake.Wake = 0; /* We need to setup the first wait block. Currently an exclusive lock is held, change the lock to contended mode. */ StackWaitBlock.Exclusive = FALSE; StackWaitBlock.SharedCount = 1; StackWaitBlock.Next = NULL; StackWaitBlock.Last = &StackWaitBlock; StackWaitBlock.SharedWakeChain = &SharedWake; StackWaitBlock.LastSharedWake = &SharedWake; NewValue = (ULONG_PTR)&StackWaitBlock | RTL_SRWLOCK_OWNED | RTL_SRWLOCK_CONTENDED; if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr, (PVOID)NewValue, (PVOID)CurrentValue) == CurrentValue) { RtlpAcquireSRWLockSharedWait(SRWLock, &StackWaitBlock, &SharedWake); /* Successfully set the shared count, we acquired the lock */ break; } } } else { /* This is a fast path, we can simply try to set the shared count to 1 */ NewValue = (1 << RTL_SRWLOCK_BITS) | RTL_SRWLOCK_SHARED | RTL_SRWLOCK_OWNED; /* The RTL_SRWLOCK_CONTENDED bit should never be set if neither the RTL_SRWLOCK_SHARED nor the RTL_SRWLOCK_OWNED bit is set */ ASSERT(!(CurrentValue & RTL_SRWLOCK_CONTENDED)); if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr, (PVOID)NewValue, (PVOID)CurrentValue) == CurrentValue) { /* Successfully set the shared count, we acquired the lock */ break; } } } YieldProcessor(); } } VOID NTAPI RtlReleaseSRWLockShared(IN OUT PRTL_SRWLOCK SRWLock) { LONG_PTR CurrentValue, NewValue; PRTLP_SRWLOCK_WAITBLOCK WaitBlock; BOOLEAN LastShared; while (1) { CurrentValue = *(volatile LONG_PTR *)&SRWLock->Ptr; if (CurrentValue & RTL_SRWLOCK_SHARED) { if (CurrentValue & RTL_SRWLOCK_CONTENDED) { /* There's a wait block, we need to wake a pending exclusive acquirer if this is the last shared release */ WaitBlock = RtlpAcquireWaitBlockLock(SRWLock); if (WaitBlock != NULL) { LastShared = (--WaitBlock->SharedCount == 0); if (LastShared) RtlpReleaseWaitBlockLockLastShared(SRWLock, WaitBlock); else RtlpReleaseWaitBlockLock(SRWLock); /* We released the lock */ break; } } else { /* This is a fast path, we can simply decrement the shared count and store the pointer */ NewValue = CurrentValue >> RTL_SRWLOCK_BITS; if (--NewValue != 0) { NewValue = (NewValue << RTL_SRWLOCK_BITS) | RTL_SRWLOCK_SHARED | RTL_SRWLOCK_OWNED; } if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr, (PVOID)NewValue, (PVOID)CurrentValue) == CurrentValue) { /* Successfully released the lock */ break; } } } else { /* The RTL_SRWLOCK_SHARED bit has to be present now, even in the contended case! */ RtlRaiseStatus(STATUS_RESOURCE_NOT_OWNED); } YieldProcessor(); } } VOID NTAPI RtlAcquireSRWLockExclusive(IN OUT PRTL_SRWLOCK SRWLock) { __ALIGNED(16) RTLP_SRWLOCK_WAITBLOCK StackWaitBlock; PRTLP_SRWLOCK_WAITBLOCK First, Last; if (InterlockedBitTestAndSetPointer(&SRWLock->Ptr, RTL_SRWLOCK_OWNED_BIT)) { LONG_PTR CurrentValue, NewValue; while (1) { CurrentValue = *(volatile LONG_PTR *)&SRWLock->Ptr; if (CurrentValue & RTL_SRWLOCK_SHARED) { /* A shared lock is being held right now. We need to add a wait block! */ if (CurrentValue & RTL_SRWLOCK_CONTENDED) { goto AddWaitBlock; } else { /* There are no wait blocks so far, we need to add ourselves as the first wait block. We need to keep the shared count! */ StackWaitBlock.Exclusive = TRUE; StackWaitBlock.SharedCount = (LONG)(CurrentValue >> RTL_SRWLOCK_BITS); StackWaitBlock.Next = NULL; StackWaitBlock.Last = &StackWaitBlock; StackWaitBlock.Wake = 0; NewValue = (ULONG_PTR)&StackWaitBlock | RTL_SRWLOCK_SHARED | RTL_SRWLOCK_CONTENDED | RTL_SRWLOCK_OWNED; if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr, (PVOID)NewValue, (PVOID)CurrentValue) == CurrentValue) { RtlpAcquireSRWLockExclusiveWait(SRWLock, &StackWaitBlock); /* Successfully acquired the exclusive lock */ break; } } } else { if (CurrentValue & RTL_SRWLOCK_OWNED) { /* An exclusive lock is being held right now. We need to add a wait block! */ if (CurrentValue & RTL_SRWLOCK_CONTENDED) { AddWaitBlock: StackWaitBlock.Exclusive = TRUE; StackWaitBlock.SharedCount = 0; StackWaitBlock.Next = NULL; StackWaitBlock.Last = &StackWaitBlock; StackWaitBlock.Wake = 0; First = RtlpAcquireWaitBlockLock(SRWLock); if (First != NULL) { Last = First->Last; Last->Next = &StackWaitBlock; First->Last = &StackWaitBlock; RtlpReleaseWaitBlockLock(SRWLock); RtlpAcquireSRWLockExclusiveWait(SRWLock, &StackWaitBlock); /* Successfully acquired the exclusive lock */ break; } } else { /* There are no wait blocks so far, we need to add ourselves as the first wait block. We need to keep the shared count! */ StackWaitBlock.Exclusive = TRUE; StackWaitBlock.SharedCount = 0; StackWaitBlock.Next = NULL; StackWaitBlock.Last = &StackWaitBlock; StackWaitBlock.Wake = 0; NewValue = (ULONG_PTR)&StackWaitBlock | RTL_SRWLOCK_OWNED | RTL_SRWLOCK_CONTENDED; if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr, (PVOID)NewValue, (PVOID)CurrentValue) == CurrentValue) { RtlpAcquireSRWLockExclusiveWait(SRWLock, &StackWaitBlock); /* Successfully acquired the exclusive lock */ break; } } } else { if (!InterlockedBitTestAndSetPointer(&SRWLock->Ptr, RTL_SRWLOCK_OWNED_BIT)) { /* We managed to get hold of a simple exclusive lock! */ break; } } } YieldProcessor(); } } } VOID NTAPI RtlReleaseSRWLockExclusive(IN OUT PRTL_SRWLOCK SRWLock) { LONG_PTR CurrentValue, NewValue; PRTLP_SRWLOCK_WAITBLOCK WaitBlock; while (1) { CurrentValue = *(volatile LONG_PTR *)&SRWLock->Ptr; if (!(CurrentValue & RTL_SRWLOCK_OWNED)) { RtlRaiseStatus(STATUS_RESOURCE_NOT_OWNED); } if (!(CurrentValue & RTL_SRWLOCK_SHARED)) { if (CurrentValue & RTL_SRWLOCK_CONTENDED) { /* There's a wait block, we need to wake the next pending acquirer (exclusive or shared) */ WaitBlock = RtlpAcquireWaitBlockLock(SRWLock); if (WaitBlock != NULL) { RtlpReleaseWaitBlockLockExclusive(SRWLock, WaitBlock); /* We released the lock */ break; } } else { /* This is a fast path, we can simply clear the RTL_SRWLOCK_OWNED bit. All other bits should be 0 now because this is a simple exclusive lock and no one is waiting. */ ASSERT(!(CurrentValue & ~RTL_SRWLOCK_OWNED)); NewValue = 0; if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr, (PVOID)NewValue, (PVOID)CurrentValue) == CurrentValue) { /* We released the lock */ break; } } } else { /* The RTL_SRWLOCK_SHARED bit must not be present now, not even in the contended case! */ RtlRaiseStatus(STATUS_RESOURCE_NOT_OWNED); } YieldProcessor(); } }