/*++ Copyright (c) 2000 Microsoft Corporation Module Name: rundown.c Abstract: This module houses routine that do safe rundown of data stuctures. The basic principle of these routines is to allow fast protection of a data structure that is torn down by a single thread. Threads wishing to access the data structure attempt to obtain rundown protection via calling ExAcquireRundownProtection. If this function returns TRUE then accesses are safe until the protected thread calls ExReleaseRundownProtection. The single teardown thread calls ExWaitForRundownProtectionRelease to mark the rundown structure as being run down and the call will return once all protected threads have released their protection references. Rundown protection is not a lock. Multiple threads may gain rundown protection at the same time. The rundown structure has the following format: Bottom bit set : This is a pointer to a rundown wait block (aligned on at least a word boundary) Bottom bit clear : This is a count of the total number of accessors multiplied by 2 granted rundown protection. Author: Neill Clift (NeillC) 18-Apr-2000 Revision History: --*/ #include "exp.h" #pragma hdrstop #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, ExAcquireRundownProtection) #pragma alloc_text(PAGE, ExReleaseRundownProtection) #pragma alloc_text(PAGE, ExAcquireRundownProtectionEx) #pragma alloc_text(PAGE, ExReleaseRundownProtectionEx) #pragma alloc_text(PAGE, ExWaitForRundownProtectionRelease) #pragma alloc_text(PAGE, ExReInitializeRundownProtection) #pragma alloc_text(PAGE, ExfInitializeRundownProtection) #pragma alloc_text(PAGE, ExRundownCompleted) #endif // // This is a block held on the local stack of the rundown thread. // typedef struct _EX_RUNDOWN_WAIT_BLOCK { ULONG Count; KEVENT WakeEvent; } EX_RUNDOWN_WAIT_BLOCK, *PEX_RUNDOWN_WAIT_BLOCK; NTKERNELAPI VOID FASTCALL ExfInitializeRundownProtection ( IN PEX_RUNDOWN_REF RunRef ) /*++ Routine Description: Initialize rundown protection structure Arguments: RunRef - Rundown block to be referenced Return Value: None --*/ { RunRef->Count = 0; } NTKERNELAPI VOID FASTCALL ExReInitializeRundownProtection ( IN PEX_RUNDOWN_REF RunRef ) /*++ Routine Description: Reinitialize rundown protection structure after its been rundown Arguments: RunRef - Rundown block to be referenced Return Value: None --*/ { PAGED_CODE (); ASSERT ((RunRef->Count&EX_RUNDOWN_ACTIVE) != 0); InterlockedExchangePointer (&RunRef->Ptr, NULL); } NTKERNELAPI VOID FASTCALL ExRundownCompleted ( IN PEX_RUNDOWN_REF RunRef ) /*++ Routine Description: Mark rundown block has having completed rundown so we can wait again safely. Arguments: RunRef - Rundown block to be referenced Return Value: None --*/ { PAGED_CODE (); ASSERT ((RunRef->Count&EX_RUNDOWN_ACTIVE) != 0); InterlockedExchangePointer (&RunRef->Ptr, (PVOID) EX_RUNDOWN_ACTIVE); } NTKERNELAPI BOOLEAN FASTCALL ExAcquireRundownProtection ( IN PEX_RUNDOWN_REF RunRef ) /*++ Routine Description: Reference a rundown block preventing rundown occuring if it hasn't already started Arguments: RunRef - Rundown block to be referenced Return Value: BOOLEAN - TRUE - rundown protection was acquired, FALSE - rundown is active or completed --*/ { ULONG_PTR Value, NewValue; PAGED_CODE (); Value = RunRef->Count; do { // // If rundown has started return with an error // if (Value & EX_RUNDOWN_ACTIVE) { return FALSE; } // // Rundown hasn't started yet so attempt to increment the unsage count. // NewValue = Value + EX_RUNDOWN_COUNT_INC; NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr, (PVOID) NewValue, (PVOID) Value); if (NewValue == Value) { return TRUE; } // // somebody else changed the variable before we did. Either a protection call came and went or rundown was // initiated. We just repeat the whole loop again. // Value = NewValue; } while (TRUE); } NTKERNELAPI BOOLEAN FASTCALL ExAcquireRundownProtectionEx ( IN PEX_RUNDOWN_REF RunRef, IN ULONG Count ) /*++ Routine Description: Reference a rundown block preventing rundown occuring if it hasn't already started Arguments: RunRef - Rundown block to be referenced Count - Number of references to add Return Value: BOOLEAN - TRUE - rundown protection was acquired, FALSE - rundown is active or completed --*/ { ULONG_PTR Value, NewValue; PAGED_CODE (); Value = RunRef->Count; do { // // If rundown has started return with an error // if (Value & EX_RUNDOWN_ACTIVE) { return FALSE; } // // Rundown hasn't started yet so attempt to increment the unsage count. // NewValue = Value + EX_RUNDOWN_COUNT_INC * Count; NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr, (PVOID) NewValue, (PVOID) Value); if (NewValue == Value) { return TRUE; } // // somebody else changed the variable before we did. Either a protection call came and went or rundown was // initiated. We just repeat the whole loop again. // Value = NewValue; } while (TRUE); } NTKERNELAPI VOID FASTCALL ExReleaseRundownProtection ( IN PEX_RUNDOWN_REF RunRef ) /*++ Routine Description: Dereference a rundown block and wake the rundown thread if we are the last to exit Arguments: RunRef - Rundown block to have its reference released Return Value: None --*/ { ULONG_PTR Value, NewValue; PAGED_CODE (); Value = RunRef->Count; do { // // If the block is already marked for rundown then decrement the wait block count and wake the // rundown thread if we are the last // if (Value & EX_RUNDOWN_ACTIVE) { PEX_RUNDOWN_WAIT_BLOCK WaitBlock; // // Rundown is active. since we are one of the threads blocking rundown we have the right to follow // the pointer and decrement the active count. If we are the last thread then we have the right to // wake up the waiter. After doing this we can't touch the data structures again. // WaitBlock = (PEX_RUNDOWN_WAIT_BLOCK) (Value & (~EX_RUNDOWN_ACTIVE)); ASSERT (WaitBlock->Count > 0); if (InterlockedDecrement ((PLONG)&WaitBlock->Count) == 0) { // // We are the last thread out. Wake up the waiter. // KeSetEvent (&WaitBlock->WakeEvent, 0, FALSE); } return; } else { // // Rundown isn't active. Just try and decrement the count. Some other protector thread way come and/or // go as we do this or rundown might be initiated. We detect this because the exchange will fail and // we have to retry // NewValue = Value - EX_RUNDOWN_COUNT_INC; NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr, (PVOID) NewValue, (PVOID) Value); if (NewValue == Value) { return; } Value = NewValue; } } while (TRUE); } NTKERNELAPI VOID FASTCALL ExReleaseRundownProtectionEx ( IN PEX_RUNDOWN_REF RunRef, IN ULONG Count ) /*++ Routine Description: Dereference a rundown block and wake the rundown thread if we are the last to exit Arguments: RunRef - Rundown block to have its reference released Count - Number of reference to remove Return Value: None --*/ { ULONG_PTR Value, NewValue; PAGED_CODE (); Value = RunRef->Count; do { // // If the block is already marked for rundown then decrement the wait block count and wake the // rundown thread if we are the last // if (Value & EX_RUNDOWN_ACTIVE) { PEX_RUNDOWN_WAIT_BLOCK WaitBlock; // // Rundown is active. since we are one of the threads blocking rundown we have the right to follow // the pointer and decrement the active count. If we are the last thread then we have the right to // wake up the waiter. After doing this we can't touch the data structures again. // WaitBlock = (PEX_RUNDOWN_WAIT_BLOCK) (Value & (~EX_RUNDOWN_ACTIVE)); ASSERT (WaitBlock->Count >= Count); if (InterlockedExchangeAdd ((PLONG)&WaitBlock->Count, -(LONG)Count) == (LONG) Count) { // // We are the last thread out. Wake up the waiter. // KeSetEvent (&WaitBlock->WakeEvent, 0, FALSE); } return; } else { // // Rundown isn't active. Just try and decrement the count. Some other protector thread way come and/or // go as we do this or rundown might be initiated. We detect this because the exchange will fail and // we have to retry // ASSERT (Value >= EX_RUNDOWN_COUNT_INC * Count); NewValue = Value - EX_RUNDOWN_COUNT_INC * Count; NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr, (PVOID) NewValue, (PVOID) Value); if (NewValue == Value) { return; } Value = NewValue; } } while (TRUE); } NTKERNELAPI VOID FASTCALL ExWaitForRundownProtectionRelease ( IN PEX_RUNDOWN_REF RunRef ) /*++ Routine Description: Wait till all outstanding rundown protection calls have exited Arguments: RunRef - Pointer to a rundown structure Return Value: None --*/ { EX_RUNDOWN_WAIT_BLOCK WaitBlock; PKEVENT Event; ULONG_PTR Value, NewValue; ULONG WaitCount; PAGED_CODE (); // // Fast path. this should be the normal case. If Value is zero then there are no current accessors and we have // marked the rundown structure as rundown. If the value is EX_RUNDOWN_ACTIVE then the structure has already // been rundown and ExRundownCompleted. This second case allows for callers that might initiate rundown // multiple times (like handle table rundown) to have subsequent rundowns become noops. // Value = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr, (PVOID) EX_RUNDOWN_ACTIVE, (PVOID) 0); if (Value == 0 || Value == EX_RUNDOWN_ACTIVE) { return; } // // Slow path // Event = NULL; do { // // Extract total number of waiters. Its biased by 2 so we can hanve the rundown active bit. // WaitCount = (ULONG) (Value >> EX_RUNDOWN_COUNT_SHIFT); // // If there are some accessors present then initialize and event (once only). // if (WaitCount > 0 && Event == NULL) { Event = &WaitBlock.WakeEvent; KeInitializeEvent (Event, SynchronizationEvent, FALSE); } // // Store the wait count in the wait block. Waiting threads will start to decrement this as they exit // if our exchange succeeds. Its possible for accessors to come and go between our initial fetch and // the interlocked swap. This doesn't matter so long as there is the same number of outstanding accessors // to wait for. // WaitBlock.Count = WaitCount; NewValue = ((ULONG_PTR) &WaitBlock) | EX_RUNDOWN_ACTIVE; NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr, (PVOID) NewValue, (PVOID) Value); if (NewValue == Value) { if (WaitCount > 0) { KeWaitForSingleObject (Event, Executive, KernelMode, FALSE, NULL); ASSERT (WaitBlock.Count == 0); } return; } Value = NewValue; ASSERT ((Value&EX_RUNDOWN_ACTIVE) == 0); } while (TRUE); }