mirror of
https://github.com/reactos/reactos.git
synced 2024-08-16 16:39:08 +00:00
524 lines
17 KiB
C
524 lines
17 KiB
C
![]() |
/*
|
|||
|
* 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<EFBFBD>ger
|
|||
|
*/
|
|||
|
|
|||
|
/* NOTE: This functionality can be optimized for releasing single
|
|||
|
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. */
|
|||
|
|
|||
|
/* INCLUDES ******************************************************************/
|
|||
|
|
|||
|
#include <rtl_vista.h>
|
|||
|
|
|||
|
#define NDEBUG
|
|||
|
#include <debug.h>
|
|||
|
|
|||
|
/* INTERNAL TYPES ************************************************************/
|
|||
|
|
|||
|
#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
|
|||
|
{
|
|||
|
/* ListEntry must have an alignment of at least 32-bits, since we
|
|||
|
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)
|
|||
|
|
|||
|
/* GLOBALS *******************************************************************/
|
|||
|
|
|||
|
static HANDLE CondVarKeyedEventHandle = NULL;
|
|||
|
|
|||
|
/* INTERNAL FUNCTIONS ********************************************************/
|
|||
|
|
|||
|
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)
|
|||
|
{
|
|||
|
/* InsertEntry and AbortIfLocked may be NULL on entry. This routine
|
|||
|
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)
|
|||
|
{
|
|||
|
/* The locked flag is set, indicating someone else currently
|
|||
|
holds the lock. We'll spin until this flag becomes
|
|||
|
clear or we're asked to abort. */
|
|||
|
YieldProcessor();
|
|||
|
|
|||
|
if ((AbortIfLocked != NULL) && *AbortIfLocked)
|
|||
|
{
|
|||
|
/* The caller wants us to abort in this case. */
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/* Refresh OldVal and try again. */
|
|||
|
OldVal = *(ULONG_PTR *)&ConditionVariable->Ptr;
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
/* Retrieve the list head currently associated with the
|
|||
|
condition variable. */
|
|||
|
OldListHead = (PLIST_ENTRY)(OldVal & COND_VAR_ADDRESS_MASK);
|
|||
|
if (InsertEntry == NULL)
|
|||
|
{
|
|||
|
/* The caller doesn't want to put any entry on the list. */
|
|||
|
if (OldListHead == NULL)
|
|||
|
{
|
|||
|
/* The list is empty, so there is nothing to lock. */
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/* The list isn't empty. In this case we need to preserve
|
|||
|
all of OldVal. */
|
|||
|
NewVal = OldVal;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* Let InsertEntry be the new list head. Preserve only the
|
|||
|
bits inside the COND_VAR_FLAGS_MASK range. */
|
|||
|
NewVal = ((OldVal & COND_VAR_FLAGS_MASK) |
|
|||
|
(ULONG_PTR)&InsertEntry->ListEntry);
|
|||
|
}
|
|||
|
|
|||
|
/* Set the flag that indicates someone is holding the lock and
|
|||
|
try to update the condition variable thread-safe. */
|
|||
|
NewVal |= COND_VAR_LOCKED_FLAG;
|
|||
|
LockRes = InternalCmpXChgCondVarAcq(ConditionVariable, NewVal, OldVal);
|
|||
|
if (LockRes == OldVal)
|
|||
|
{
|
|||
|
/* We successfully updated ConditionVariable the way we
|
|||
|
wanted and now hold the lock. */
|
|||
|
if (InsertEntry == NULL)
|
|||
|
{
|
|||
|
/* We know that OldVal contains a valid address in
|
|||
|
this case. */
|
|||
|
ASSERT(OldListHead != NULL);
|
|||
|
return CONTAINING_COND_VAR_WAIT_ENTRY(OldListHead, ListEntry);
|
|||
|
}
|
|||
|
|
|||
|
/* InsertEntry is not on the list yet, so add it. In any
|
|||
|
case InsertEntry will be the new list head. */
|
|||
|
if (OldListHead == NULL)
|
|||
|
{
|
|||
|
/* List was empty before. */
|
|||
|
InitializeListHead(&InsertEntry->ListEntry);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* Make InsertEntry the last entry of the old list.
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/* We didn't manage to update ConditionVariable, so try again. */
|
|||
|
OldVal = LockRes;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static
|
|||
|
VOID
|
|||
|
InternalUnlockCondVar(IN OUT PRTL_CONDITION_VARIABLE ConditionVariable,
|
|||
|
IN PCOND_VAR_WAIT_ENTRY RemoveEntry OPTIONAL)
|
|||
|
{
|
|||
|
/* This routine assumes that the lock is being held on entry.
|
|||
|
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)
|
|||
|
{
|
|||
|
/* We have to drop RemoveEntry from the list. */
|
|||
|
if (&RemoveEntry->ListEntry == NewHeadEntry)
|
|||
|
{
|
|||
|
/* RemoveEntry is the list head. */
|
|||
|
if (!IsListEmpty(NewHeadEntry))
|
|||
|
{
|
|||
|
/* The second entry in the list will become the new
|
|||
|
list head. It's from the thread that arrived
|
|||
|
right before the owner of RemoveEntry. */
|
|||
|
NewHeadEntry = NewHeadEntry->Flink;
|
|||
|
RemoveEntryList(&RemoveEntry->ListEntry);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* The list will be empty, so discard the list. */
|
|||
|
NewHeadEntry = NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* RemoveEntry is not the list head. The current list head
|
|||
|
will remain. */
|
|||
|
RemoveEntryList(&RemoveEntry->ListEntry);
|
|||
|
}
|
|||
|
|
|||
|
/* Indicate to the owner of RemoveEntry that the entry
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/* Now unlock thread-safe, while preserving any flags within the
|
|||
|
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)
|
|||
|
{
|
|||
|
/* We unlocked. */
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
/* Try again. */
|
|||
|
OldVal = LockRes;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static
|
|||
|
VOID
|
|||
|
InternalWake(IN OUT PRTL_CONDITION_VARIABLE ConditionVariable,
|
|||
|
IN BOOLEAN ReleaseAll)
|
|||
|
{
|
|||
|
/* If ReleaseAll is zero on entry, one thread at most will be woken.
|
|||
|
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)
|
|||
|
{
|
|||
|
/* There is noone there to wake up. In this case do nothing
|
|||
|
and return immediately. We don't stockpile releases. */
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
Timeout.QuadPart = 0;
|
|||
|
RemoveOnUnlockEntry = NULL;
|
|||
|
|
|||
|
/* Release sleeping threads. We will iterate from the last entry on
|
|||
|
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)
|
|||
|
{
|
|||
|
/* After the current entry we've iterated through the
|
|||
|
entire list in backward direction. Then exit.*/
|
|||
|
NextEntry = NULL;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* Store away the next reference right now, since we may
|
|||
|
not touch Entry anymore at the end of the block. */
|
|||
|
NextEntry = CONTAINING_COND_VAR_WAIT_ENTRY(Entry->ListEntry.Blink, ListEntry);
|
|||
|
}
|
|||
|
|
|||
|
/* Wake the thread associated with this event. We will
|
|||
|
immediately return if we failed (zero timeout). */
|
|||
|
Status = NtReleaseKeyedEvent(CondVarKeyedEventHandle,
|
|||
|
&Entry->WaitKey,
|
|||
|
FALSE,
|
|||
|
&Timeout);
|
|||
|
|
|||
|
if (!NT_SUCCESS(Status))
|
|||
|
{
|
|||
|
/* We failed to wake a thread. We'll keep trying. */
|
|||
|
ASSERT(STATUS_INVALID_HANDLE != Status);
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
/* We've woken a thread and will make sure this thread
|
|||
|
is removed from the list. */
|
|||
|
if (HeadEntry == Entry)
|
|||
|
{
|
|||
|
/* This is the list head. We can't remove it as easily as
|
|||
|
other entries and will pass it to the unlock routine
|
|||
|
later (we will exit the loop after this round anyway). */
|
|||
|
RemoveOnUnlockEntry = HeadEntry;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* We can remove the entry right away. */
|
|||
|
RemoveEntryList(&Entry->ListEntry);
|
|||
|
|
|||
|
/* Now tell the woken thread that removal from the list was
|
|||
|
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)
|
|||
|
{
|
|||
|
/* We've successfully woken one thread as the caller
|
|||
|
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)
|
|||
|
{
|
|||
|
/* Either CriticalSection or SRWLock must be NULL, but not both.
|
|||
|
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));
|
|||
|
|
|||
|
/* Put OwnEntry on the list. */
|
|||
|
InternalLockCondVar(ConditionVariable, &OwnEntry, NULL);
|
|||
|
InternalUnlockCondVar(ConditionVariable, NULL);
|
|||
|
|
|||
|
/* We can now drop the caller provided lock as a preparation for
|
|||
|
going to sleep. */
|
|||
|
if (CriticalSection == NULL)
|
|||
|
{
|
|||
|
if (0 == (RTL_CONDITION_VARIABLE_LOCKMODE_SHARED & SRWFlags))
|
|||
|
{
|
|||
|
RtlReleaseSRWLockExclusive(SRWLock);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
RtlReleaseSRWLockShared(SRWLock);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
RtlLeaveCriticalSection(CriticalSection);
|
|||
|
}
|
|||
|
|
|||
|
/* Now sleep using the caller provided timeout. */
|
|||
|
Status = NtWaitForKeyedEvent(CondVarKeyedEventHandle,
|
|||
|
&OwnEntry.WaitKey,
|
|||
|
FALSE,
|
|||
|
(PLARGE_INTEGER)TimeOut);
|
|||
|
|
|||
|
ASSERT(STATUS_INVALID_HANDLE != Status);
|
|||
|
|
|||
|
if (!*InternalGetListRemovalHandledFlag(&OwnEntry))
|
|||
|
{
|
|||
|
/* Remove OwnEntry from the list again, since it still seems to
|
|||
|
be on the list. We will know for sure once we've acquired
|
|||
|
the lock. */
|
|||
|
if (InternalLockCondVar(ConditionVariable,
|
|||
|
NULL,
|
|||
|
InternalGetListRemovalHandledFlag(&OwnEntry)))
|
|||
|
{
|
|||
|
/* Unlock and potentially remove OwnEntry. Self-removal is
|
|||
|
usually only necessary when a timeout occurred. */
|
|||
|
InternalUnlockCondVar(ConditionVariable,
|
|||
|
!OwnEntry.ListRemovalHandled ?
|
|||
|
&OwnEntry : NULL);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#if _DEBUG
|
|||
|
/* Clear OwnEntry to aid in detecting bugs. */
|
|||
|
RtlZeroMemory(&OwnEntry, sizeof(OwnEntry));
|
|||
|
#endif
|
|||
|
|
|||
|
/* Reacquire the caller provided lock, as we are about to return. */
|
|||
|
if (CriticalSection == NULL)
|
|||
|
{
|
|||
|
if (0 == (RTL_CONDITION_VARIABLE_LOCKMODE_SHARED & SRWFlags))
|
|||
|
{
|
|||
|
RtlAcquireSRWLockExclusive(SRWLock);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
RtlAcquireSRWLockShared(SRWLock);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
RtlEnterCriticalSection(CriticalSection);
|
|||
|
}
|
|||
|
|
|||
|
/* Return whatever NtWaitForKeyedEvent returned. */
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
RtlpInitializeKeyedEvent(VOID)
|
|||
|
{
|
|||
|
ASSERT(CondVarKeyedEventHandle == NULL);
|
|||
|
NtCreateKeyedEvent(&CondVarKeyedEventHandle, EVENT_ALL_ACCESS, NULL, 0);
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
RtlpCloseKeyedEvent(VOID)
|
|||
|
{
|
|||
|
ASSERT(CondVarKeyedEventHandle != NULL);
|
|||
|
NtClose(CondVarKeyedEventHandle);
|
|||
|
CondVarKeyedEventHandle = NULL;
|
|||
|
}
|
|||
|
|
|||
|
/* EXPORTED FUNCTIONS ********************************************************/
|
|||
|
|
|||
|
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 const LARGE_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 const LARGE_INTEGER * TimeOut OPTIONAL,
|
|||
|
IN ULONG Flags)
|
|||
|
{
|
|||
|
return InternalSleep(ConditionVariable,
|
|||
|
(PRTL_CRITICAL_SECTION)NULL,
|
|||
|
SRWLock,
|
|||
|
Flags,
|
|||
|
TimeOut);
|
|||
|
}
|
|||
|
|
|||
|
/* EOF */
|