* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS system libraries
* PURPOSE: Condition Variable Routines
* PROGRAMMERS: Thomas Weidenmueller <w3seek@reactos.com>
* Stephan A. R�ger
*/
threads or for releasing all waiting threads at once. This
implementation is optimized for releasing a single thread at a time.
It wakes up sleeping threads in FIFO order. */
#include <rtl_vista.h>
#define NDEBUG
#include <debug.h>
#define COND_VAR_UNUSED_FLAG ((ULONG_PTR)1)
#define COND_VAR_LOCKED_FLAG ((ULONG_PTR)2)
#define COND_VAR_FLAGS_MASK ((ULONG_PTR)3)
#define COND_VAR_ADDRESS_MASK (~COND_VAR_FLAGS_MASK)
typedef struct _COND_VAR_WAIT_ENTRY
{
want COND_VAR_ADDRESS_MASK to cover all of the address. */
LIST_ENTRY ListEntry;
PVOID WaitKey;
BOOLEAN ListRemovalHandled;
} COND_VAR_WAIT_ENTRY, * PCOND_VAR_WAIT_ENTRY;
#define CONTAINING_COND_VAR_WAIT_ENTRY(address, field) \
CONTAINING_RECORD(address, COND_VAR_WAIT_ENTRY, field)
static HANDLE CondVarKeyedEventHandle = NULL;
FORCEINLINE
ULONG_PTR
InternalCmpXChgCondVarAcq(IN OUT PRTL_CONDITION_VARIABLE ConditionVariable,
IN ULONG_PTR Exchange,
IN ULONG_PTR Comperand)
{
return (ULONG_PTR)InterlockedCompareExchangePointerAcquire(&ConditionVariable->Ptr,
(PVOID)Exchange,
(PVOID)Comperand);
}
FORCEINLINE
ULONG_PTR
InternalCmpXChgCondVarRel(IN OUT PRTL_CONDITION_VARIABLE ConditionVariable,
IN ULONG_PTR Exchange,
IN ULONG_PTR Comperand)
{
return (ULONG_PTR)InterlockedCompareExchangePointerRelease(&ConditionVariable->Ptr,
(PVOID)Exchange,
(PVOID)Comperand);
}
FORCEINLINE
BOOLEAN *
InternalGetListRemovalHandledFlag(IN PCOND_VAR_WAIT_ENTRY Entry)
{
return (BOOLEAN *)&Entry->ListRemovalHandled;
}
static
PCOND_VAR_WAIT_ENTRY
InternalLockCondVar(IN OUT PRTL_CONDITION_VARIABLE ConditionVariable,
IN PCOND_VAR_WAIT_ENTRY InsertEntry OPTIONAL,
IN BOOLEAN * AbortIfLocked OPTIONAL)
{
will return NULL if the lock was not acquired. Otherwise it has
successfully acquired the lock and the return value is a valid
reference to the list head associated with ConditionVariable.
The caller must in this case call InternalUnlockCondVar later
in order to unlock the condition variable.
If InsertEntry is NULL and there are no entries on the list, this
routine will not acquire the lock and return NULL. If InsertEntry
is not NULL this routine ensures that InsertEntry will be on the
list when it returns successfully.
If the lock is owned by another thread and AbortIfLocked is NULL,
this routine will block until it acquires the lock. If AbortIfLocked
is not NULL and the lock is owned by another thread, this routine
will periodically check if *AbortIfLocked is nonzero and if so, will
return NULL instead of continuing the wait. */
ULONG_PTR OldVal = (ULONG_PTR)ConditionVariable->Ptr;
for (;;)
{
ULONG_PTR NewVal, LockRes;
PLIST_ENTRY OldListHead;
if (OldVal & COND_VAR_LOCKED_FLAG)
{
holds the lock. We'll spin until this flag becomes
clear or we're asked to abort. */
YieldProcessor();
if ((AbortIfLocked != NULL) && *AbortIfLocked)
{
return NULL;
}
OldVal = *(ULONG_PTR *)&ConditionVariable->Ptr;
continue;
}
condition variable. */
OldListHead = (PLIST_ENTRY)(OldVal & COND_VAR_ADDRESS_MASK);
if (InsertEntry == NULL)
{
if (OldListHead == NULL)
{
return NULL;
}
all of OldVal. */
NewVal = OldVal;
}
else
{
bits inside the COND_VAR_FLAGS_MASK range. */
NewVal = ((OldVal & COND_VAR_FLAGS_MASK) |
(ULONG_PTR)&InsertEntry->ListEntry);
}
try to update the condition variable thread-safe. */
NewVal |= COND_VAR_LOCKED_FLAG;
LockRes = InternalCmpXChgCondVarAcq(ConditionVariable, NewVal, OldVal);
if (LockRes == OldVal)
{
wanted and now hold the lock. */
if (InsertEntry == NULL)
{
this case. */
ASSERT(OldListHead != NULL);
return CONTAINING_COND_VAR_WAIT_ENTRY(OldListHead, ListEntry);
}
case InsertEntry will be the new list head. */
if (OldListHead == NULL)
{
InitializeListHead(&InsertEntry->ListEntry);
}
else
{
As InsertEntry will take the role as new list head,
OldListHead will become the second entry (InsertEntry->Flink)
on the new list. */
InsertTailList(OldListHead, &InsertEntry->ListEntry);
}
return InsertEntry;
}
OldVal = LockRes;
}
}
static
VOID
InternalUnlockCondVar(IN OUT PRTL_CONDITION_VARIABLE ConditionVariable,
IN PCOND_VAR_WAIT_ENTRY RemoveEntry OPTIONAL)
{
RemoveEntry may be NULL. If it is not NULL, this routine
assumes that RemoveEntry is on the list and will remove it
before releasing the lock. */
ULONG_PTR OldVal = (ULONG_PTR)ConditionVariable->Ptr;
PLIST_ENTRY NewHeadEntry;
ASSERT((OldVal & COND_VAR_LOCKED_FLAG) &&
(OldVal & COND_VAR_ADDRESS_MASK));
NewHeadEntry = (PLIST_ENTRY)(OldVal & COND_VAR_ADDRESS_MASK);
if (RemoveEntry != NULL)
{
if (&RemoveEntry->ListEntry == NewHeadEntry)
{
if (!IsListEmpty(NewHeadEntry))
{
list head. It's from the thread that arrived
right before the owner of RemoveEntry. */
NewHeadEntry = NewHeadEntry->Flink;
RemoveEntryList(&RemoveEntry->ListEntry);
}
else
{
NewHeadEntry = NULL;
}
}
else
{
will remain. */
RemoveEntryList(&RemoveEntry->ListEntry);
}
was removed from the list. RemoveEntry may not be touched
from here on. We don't use volatile semantics here since
the cache will anyway be flushed soon when we update
ConditionVariable. */
RemoveEntry->ListRemovalHandled = TRUE;
}
COND_VAR_FLAGS_MASK range except for COND_VAR_LOCKED_FLAG. */
for (;;)
{
ULONG_PTR NewVal = ((OldVal & (COND_VAR_FLAGS_MASK ^ COND_VAR_LOCKED_FLAG)) |
(ULONG_PTR)NewHeadEntry);
ULONG_PTR LockRes = InternalCmpXChgCondVarRel(ConditionVariable, NewVal, OldVal);
if (LockRes == OldVal)
{
break;
}
OldVal = LockRes;
}
}
static
VOID
InternalWake(IN OUT PRTL_CONDITION_VARIABLE ConditionVariable,
IN BOOLEAN ReleaseAll)
{
Otherwise all waiting threads are woken. Wakeups happen in FIFO
order. */
PCOND_VAR_WAIT_ENTRY CONST HeadEntry = InternalLockCondVar(ConditionVariable, NULL, NULL);
PCOND_VAR_WAIT_ENTRY Entry;
PCOND_VAR_WAIT_ENTRY NextEntry;
LARGE_INTEGER Timeout;
PCOND_VAR_WAIT_ENTRY RemoveOnUnlockEntry;
ASSERT(CondVarKeyedEventHandle != NULL);
if (HeadEntry == NULL)
{
and return immediately. We don't stockpile releases. */
return;
}
Timeout.QuadPart = 0;
RemoveOnUnlockEntry = NULL;
the list to the first. Note that the loop condition is always
true for the initial test. */
for (Entry = CONTAINING_COND_VAR_WAIT_ENTRY(HeadEntry->ListEntry.Blink, ListEntry);
Entry != NULL;
Entry = NextEntry)
{
NTSTATUS Status;
if (HeadEntry == Entry)
{
entire list in backward direction. Then exit.*/
NextEntry = NULL;
}
else
{
not touch Entry anymore at the end of the block. */
NextEntry = CONTAINING_COND_VAR_WAIT_ENTRY(Entry->ListEntry.Blink, ListEntry);
}
immediately return if we failed (zero timeout). */
Status = NtReleaseKeyedEvent(CondVarKeyedEventHandle,
&Entry->WaitKey,
FALSE,
&Timeout);
if (!NT_SUCCESS(Status))
{
ASSERT(STATUS_INVALID_HANDLE != Status);
continue;
}
is removed from the list. */
if (HeadEntry == Entry)
{
other entries and will pass it to the unlock routine
later (we will exit the loop after this round anyway). */
RemoveOnUnlockEntry = HeadEntry;
}
else
{
RemoveEntryList(&Entry->ListEntry);
already taken care of here so that this thread can resume
its normal operation more quickly. We may not touch
Entry after signaling this, since it may lie in invalid
memory from there on. */
*InternalGetListRemovalHandledFlag(Entry) = TRUE;
}
if (!ReleaseAll)
{
demanded. */
break;
}
}
InternalUnlockCondVar(ConditionVariable, RemoveOnUnlockEntry);
}
VOID
NTAPI
RtlAcquireSRWLockExclusive(IN OUT PRTL_SRWLOCK SRWLock);
VOID
NTAPI
RtlAcquireSRWLockShared(IN OUT PRTL_SRWLOCK SRWLock);
VOID
NTAPI
RtlReleaseSRWLockExclusive(IN OUT PRTL_SRWLOCK SRWLock);
VOID
NTAPI
RtlReleaseSRWLockShared(IN OUT PRTL_SRWLOCK SRWLock);
static
NTSTATUS
InternalSleep(IN OUT PRTL_CONDITION_VARIABLE ConditionVariable,
IN OUT PRTL_CRITICAL_SECTION CriticalSection OPTIONAL,
IN OUT PRTL_SRWLOCK SRWLock OPTIONAL,
IN ULONG SRWFlags,
IN const LARGE_INTEGER * TimeOut OPTIONAL)
{
These caller provided lock must be held on entry and will be
held again on return. */
COND_VAR_WAIT_ENTRY OwnEntry;
NTSTATUS Status;
ASSERT(CondVarKeyedEventHandle != NULL);
ASSERT((CriticalSection == NULL) != (SRWLock == NULL));
RtlZeroMemory(&OwnEntry, sizeof(OwnEntry));
InternalLockCondVar(ConditionVariable, &OwnEntry, NULL);
InternalUnlockCondVar(ConditionVariable, NULL);
going to sleep. */
if (CriticalSection == NULL)
{
if (0 == (RTL_CONDITION_VARIABLE_LOCKMODE_SHARED & SRWFlags))
{
RtlReleaseSRWLockExclusive(SRWLock);
}
else
{
RtlReleaseSRWLockShared(SRWLock);
}
}
else
{
RtlLeaveCriticalSection(CriticalSection);
}
Status = NtWaitForKeyedEvent(CondVarKeyedEventHandle,
&OwnEntry.WaitKey,
FALSE,
(PLARGE_INTEGER)TimeOut);
ASSERT(STATUS_INVALID_HANDLE != Status);
if (!*InternalGetListRemovalHandledFlag(&OwnEntry))
{
be on the list. We will know for sure once we've acquired
the lock. */
if (InternalLockCondVar(ConditionVariable,
NULL,
InternalGetListRemovalHandledFlag(&OwnEntry)))
{
usually only necessary when a timeout occurred. */
InternalUnlockCondVar(ConditionVariable,
!OwnEntry.ListRemovalHandled ?
&OwnEntry : NULL);
}
}
#ifdef _DEBUG
RtlZeroMemory(&OwnEntry, sizeof(OwnEntry));
#endif
if (CriticalSection == NULL)
{
if (0 == (RTL_CONDITION_VARIABLE_LOCKMODE_SHARED & SRWFlags))
{
RtlAcquireSRWLockExclusive(SRWLock);
}
else
{
RtlAcquireSRWLockShared(SRWLock);
}
}
else
{
RtlEnterCriticalSection(CriticalSection);
}
return Status;
}
VOID
NTAPI
RtlpInitializeKeyedEvent(VOID)
{
ASSERT(CondVarKeyedEventHandle == NULL);
NtCreateKeyedEvent(&CondVarKeyedEventHandle, EVENT_ALL_ACCESS, NULL, 0);
}
VOID
NTAPI
RtlpCloseKeyedEvent(VOID)
{
ASSERT(CondVarKeyedEventHandle != NULL);
NtClose(CondVarKeyedEventHandle);
CondVarKeyedEventHandle = NULL;
}
VOID
NTAPI
RtlInitializeConditionVariable(OUT PRTL_CONDITION_VARIABLE ConditionVariable)
{
ConditionVariable->Ptr = NULL;
}
VOID
NTAPI
RtlWakeConditionVariable(IN OUT PRTL_CONDITION_VARIABLE ConditionVariable)
{
InternalWake(ConditionVariable, FALSE);
}
VOID
NTAPI
RtlWakeAllConditionVariable(IN OUT PRTL_CONDITION_VARIABLE ConditionVariable)
{
InternalWake(ConditionVariable, TRUE);
}
NTSTATUS
NTAPI
RtlSleepConditionVariableCS(IN OUT PRTL_CONDITION_VARIABLE ConditionVariable,
IN OUT PRTL_CRITICAL_SECTION CriticalSection,
IN PLARGE_INTEGER TimeOut OPTIONAL)
{
return InternalSleep(ConditionVariable,
CriticalSection,
(PRTL_SRWLOCK)NULL,
0,
TimeOut);
}
NTSTATUS
NTAPI
RtlSleepConditionVariableSRW(IN OUT PRTL_CONDITION_VARIABLE ConditionVariable,
IN OUT PRTL_SRWLOCK SRWLock,
IN PLARGE_INTEGER TimeOut OPTIONAL,
IN ULONG Flags)
{
return InternalSleep(ConditionVariable,
(PRTL_CRITICAL_SECTION)NULL,
SRWLock,
Flags,
TimeOut);
}