/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS system libraries * PURPOSE: Condition Variable Routines * PROGRAMMERS: Thomas Weidenmueller * Stephan A. R�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 #define NDEBUG #include /* 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); } } #ifdef _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 */