/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS system libraries * FILE: lib/rtl/critical.c * PURPOSE: Critical sections * PROGRAMMERS: Alex Ionescu (alex@relsoft.net) * Gunnar Dalsnes */ /* INCLUDES *****************************************************************/ #include #define NDEBUG #include #define MAX_STATIC_CS_DEBUG_OBJECTS 64 static RTL_CRITICAL_SECTION RtlCriticalSectionLock; static LIST_ENTRY RtlCriticalSectionList; static BOOLEAN RtlpCritSectInitialized = FALSE; static RTL_CRITICAL_SECTION_DEBUG RtlpStaticDebugInfo[MAX_STATIC_CS_DEBUG_OBJECTS]; static BOOLEAN RtlpDebugInfoFreeList[MAX_STATIC_CS_DEBUG_OBJECTS]; LARGE_INTEGER RtlpTimeout; extern BOOLEAN LdrpShutdownInProgress; extern HANDLE LdrpShutdownThreadId; /* FUNCTIONS *****************************************************************/ /*++ * RtlpCreateCriticalSectionSem * * Checks if an Event has been created for the critical section. * * Params: * None * * Returns: * None. Raises an exception if the system call failed. * * Remarks: * None * *--*/ _At_(CriticalSection->LockSemaphore, _Post_notnull_) VOID NTAPI RtlpCreateCriticalSectionSem(PRTL_CRITICAL_SECTION CriticalSection) { HANDLE hEvent = CriticalSection->LockSemaphore; HANDLE hNewEvent; NTSTATUS Status; /* Check if we have an event */ if (!hEvent) { /* No, so create it */ Status = NtCreateEvent(&hNewEvent, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to Create Event!\n"); /* * Use INVALID_HANDLE_VALUE (-1) to signal that * the global keyed event must be used. */ hNewEvent = INVALID_HANDLE_VALUE; } DPRINT("Created Event: %p \n", hNewEvent); /* Exchange the LockSemaphore field with the new handle, if it is still 0 */ if (InterlockedCompareExchangePointer((PVOID*)&CriticalSection->LockSemaphore, (PVOID)hNewEvent, NULL) != NULL) { /* Someone else just created an event */ if (hEvent != INVALID_HANDLE_VALUE) { DPRINT("Closing already created event: %p\n", hNewEvent); NtClose(hNewEvent); } } } return; } /*++ * RtlpWaitForCriticalSection * * Slow path of RtlEnterCriticalSection. Waits on an Event Object. * * Params: * CriticalSection - Critical section to acquire. * * Returns: * STATUS_SUCCESS, or raises an exception if a deadlock is occuring. * * Remarks: * None * *--*/ NTSTATUS NTAPI RtlpWaitForCriticalSection(PRTL_CRITICAL_SECTION CriticalSection) { NTSTATUS Status; EXCEPTION_RECORD ExceptionRecord; BOOLEAN LastChance = FALSE; /* Do we have an Event yet? */ if (!CriticalSection->LockSemaphore) { RtlpCreateCriticalSectionSem(CriticalSection); } /* Increase the Debug Entry count */ DPRINT("Waiting on Critical Section Event: %p %p\n", CriticalSection, CriticalSection->LockSemaphore); if (CriticalSection->DebugInfo) CriticalSection->DebugInfo->EntryCount++; /* * If we're shutting down the process, we're allowed to acquire any * critical sections by force (the loader lock in particular) */ if (LdrpShutdownInProgress && LdrpShutdownThreadId == NtCurrentTeb()->RealClientId.UniqueThread) { DPRINT("Forcing ownership of critical section %p\n", CriticalSection); CriticalSection->LockCount = 0; return STATUS_SUCCESS; } for (;;) { /* Increase the number of times we've had contention */ if (CriticalSection->DebugInfo) CriticalSection->DebugInfo->ContentionCount++; /* Check if allocating the event failed */ if (CriticalSection->LockSemaphore == INVALID_HANDLE_VALUE) { /* Use the global keyed event (NULL as keyed event handle) */ Status = NtWaitForKeyedEvent(NULL, CriticalSection, FALSE, &RtlpTimeout); } else { /* Wait on the Event */ Status = NtWaitForSingleObject(CriticalSection->LockSemaphore, FALSE, &RtlpTimeout); } /* We have Timed out */ if (Status == STATUS_TIMEOUT) { /* Is this the 2nd time we've timed out? */ if (LastChance) { ERROR_DBGBREAK("Deadlock: 0x%p\n", CriticalSection); /* Yes it is, we are raising an exception */ ExceptionRecord.ExceptionCode = STATUS_POSSIBLE_DEADLOCK; ExceptionRecord.ExceptionFlags = 0; ExceptionRecord.ExceptionRecord = NULL; ExceptionRecord.ExceptionAddress = RtlRaiseException; ExceptionRecord.NumberParameters = 1; ExceptionRecord.ExceptionInformation[0] = (ULONG_PTR)CriticalSection; RtlRaiseException(&ExceptionRecord); } /* One more try */ LastChance = TRUE; } else { /* If we are here, everything went fine */ return STATUS_SUCCESS; } } } /*++ * RtlpUnWaitCriticalSection * * Slow path of RtlLeaveCriticalSection. Fires an Event Object. * * Params: * CriticalSection - Critical section to release. * * Returns: * None. Raises an exception if the system call failed. * * Remarks: * None * *--*/ VOID NTAPI RtlpUnWaitCriticalSection(PRTL_CRITICAL_SECTION CriticalSection) { NTSTATUS Status; /* Do we have an Event yet? */ if (!CriticalSection->LockSemaphore) { RtlpCreateCriticalSectionSem(CriticalSection); } /* Signal the Event */ DPRINT("Signaling Critical Section Event: %p, %p\n", CriticalSection, CriticalSection->LockSemaphore); /* Check if this critical section needs to use the keyed event */ if (CriticalSection->LockSemaphore == INVALID_HANDLE_VALUE) { /* Release keyed event */ Status = NtReleaseKeyedEvent(NULL, CriticalSection, FALSE, &RtlpTimeout); } else { /* Set the event */ Status = NtSetEvent(CriticalSection->LockSemaphore, NULL); } if (!NT_SUCCESS(Status)) { /* We've failed */ DPRINT1("Signaling Failed for: %p, %p, 0x%08lx\n", CriticalSection, CriticalSection->LockSemaphore, Status); RtlRaiseStatus(Status); } } /*++ * RtlpInitDeferedCriticalSection * * Initializes the Critical Section implementation. * * Params: * None * * Returns: * None. * * Remarks: * After this call, the Process Critical Section list is protected. * *--*/ VOID NTAPI RtlpInitDeferedCriticalSection(VOID) { /* Initialize the Process Critical Section List */ InitializeListHead(&RtlCriticalSectionList); /* Initialize the CS Protecting the List */ RtlInitializeCriticalSection(&RtlCriticalSectionLock); /* It's now safe to enter it */ RtlpCritSectInitialized = TRUE; } /*++ * RtlpAllocateDebugInfo * * Finds or allocates memory for a Critical Section Debug Object * * Params: * None * * Returns: * A pointer to an empty Critical Section Debug Object. * * Remarks: * For optimization purposes, the first 64 entries can be cached. From * then on, future Critical Sections will allocate memory from the heap. * *--*/ PRTL_CRITICAL_SECTION_DEBUG NTAPI RtlpAllocateDebugInfo(VOID) { ULONG i; /* Try to allocate from our buffer first */ for (i = 0; i < MAX_STATIC_CS_DEBUG_OBJECTS; i++) { /* Check if Entry is free */ if (!RtlpDebugInfoFreeList[i]) { /* Mark entry in use */ DPRINT("Using entry: %lu. Buffer: %p\n", i, &RtlpStaticDebugInfo[i]); RtlpDebugInfoFreeList[i] = TRUE; /* Use free entry found */ return &RtlpStaticDebugInfo[i]; } } /* We are out of static buffer, allocate dynamic */ return RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(RTL_CRITICAL_SECTION_DEBUG)); } /*++ * RtlpFreeDebugInfo * * Frees the memory for a Critical Section Debug Object * * Params: * DebugInfo - Pointer to Critical Section Debug Object to free. * * Returns: * None. * * Remarks: * If the pointer is part of the static buffer, then the entry is made * free again. If not, the object is de-allocated from the heap. * *--*/ VOID NTAPI RtlpFreeDebugInfo(PRTL_CRITICAL_SECTION_DEBUG DebugInfo) { SIZE_T EntryId; /* Is it part of our cached entries? */ if ((DebugInfo >= RtlpStaticDebugInfo) && (DebugInfo <= &RtlpStaticDebugInfo[MAX_STATIC_CS_DEBUG_OBJECTS-1])) { /* Yes. zero it out */ RtlZeroMemory(DebugInfo, sizeof(RTL_CRITICAL_SECTION_DEBUG)); /* Mark as free */ EntryId = (DebugInfo - RtlpStaticDebugInfo); DPRINT("Freeing from Buffer: %p. Entry: %Iu inside Process: %p\n", DebugInfo, EntryId, NtCurrentTeb()->ClientId.UniqueProcess); RtlpDebugInfoFreeList[EntryId] = FALSE; } else if (!DebugInfo->Flags) { /* It's a dynamic one, so free from the heap */ DPRINT("Freeing from Heap: %p inside Process: %p\n", DebugInfo, NtCurrentTeb()->ClientId.UniqueProcess); RtlFreeHeap(NtCurrentPeb()->ProcessHeap, 0, DebugInfo); } else { /* Wine stores a section name pointer in the Flags member */ DPRINT("Assuming static: %p inside Process: %p\n", DebugInfo, NtCurrentTeb()->ClientId.UniqueProcess); } } /*++ * RtlDeleteCriticalSection * @implemented NT4 * * Deletes a Critical Section * * Params: * CriticalSection - Critical section to delete. * * Returns: * STATUS_SUCCESS, or error value returned by NtClose. * * Remarks: * The critical section members should not be read after this call. * *--*/ NTSTATUS NTAPI RtlDeleteCriticalSection(PRTL_CRITICAL_SECTION CriticalSection) { NTSTATUS Status = STATUS_SUCCESS; DPRINT("Deleting Critical Section: %p\n", CriticalSection); /* Close the Event Object Handle if it exists */ if (CriticalSection->LockSemaphore) { /* In case NtClose fails, return the status */ Status = NtClose(CriticalSection->LockSemaphore); } /* Protect List */ RtlEnterCriticalSection(&RtlCriticalSectionLock); if (CriticalSection->DebugInfo) { /* Remove it from the list */ RemoveEntryList(&CriticalSection->DebugInfo->ProcessLocksList); #if 0 /* We need to preserve Flags for RtlpFreeDebugInfo */ RtlZeroMemory(CriticalSection->DebugInfo, sizeof(RTL_CRITICAL_SECTION_DEBUG)); #endif } /* Unprotect */ RtlLeaveCriticalSection(&RtlCriticalSectionLock); if (CriticalSection->DebugInfo) { /* Free it */ RtlpFreeDebugInfo(CriticalSection->DebugInfo); } /* Wipe it out */ RtlZeroMemory(CriticalSection, sizeof(RTL_CRITICAL_SECTION)); /* Return */ return Status; } /*++ * RtlSetCriticalSectionSpinCount * @implemented NT4 * * Sets the spin count for a critical section. * * Params: * CriticalSection - Critical section to set the spin count for. * * SpinCount - Spin count for the critical section. * * Returns: * STATUS_SUCCESS. * * Remarks: * SpinCount is ignored on single-processor systems. * *--*/ ULONG NTAPI RtlSetCriticalSectionSpinCount(PRTL_CRITICAL_SECTION CriticalSection, ULONG SpinCount) { ULONG OldCount = (ULONG)CriticalSection->SpinCount; /* Set to parameter if MP, or to 0 if this is Uniprocessor */ CriticalSection->SpinCount = (NtCurrentPeb()->NumberOfProcessors > 1) ? SpinCount : 0; return OldCount; } /*++ * RtlEnterCriticalSection * @implemented NT4 * * Waits to gain ownership of the critical section. * * Params: * CriticalSection - Critical section to wait for. * * Returns: * STATUS_SUCCESS. * * Remarks: * Uses a fast-path unless contention happens. * *--*/ NTSTATUS NTAPI RtlEnterCriticalSection(PRTL_CRITICAL_SECTION CriticalSection) { HANDLE Thread = (HANDLE)NtCurrentTeb()->ClientId.UniqueThread; /* Try to lock it */ if (InterlockedIncrement(&CriticalSection->LockCount) != 0) { /* We've failed to lock it! Does this thread actually own it? */ if (Thread == CriticalSection->OwningThread) { /* * You own it, so you'll get it when you're done with it! No need to * use the interlocked functions as only the thread who already owns * the lock can modify this data. */ CriticalSection->RecursionCount++; return STATUS_SUCCESS; } /* NOTE - CriticalSection->OwningThread can be NULL here because changing this information is not serialized. This happens when thread a acquires the lock (LockCount == 0) and thread b tries to acquire it as well (LockCount == 1) but thread a hasn't had a chance to set the OwningThread! So it's not an error when OwningThread is NULL here! */ /* We don't own it, so we must wait for it */ RtlpWaitForCriticalSection(CriticalSection); } /* * Lock successful. Changing this information has not to be serialized * because only one thread at a time can actually change it (the one who * acquired the lock)! */ CriticalSection->OwningThread = Thread; CriticalSection->RecursionCount = 1; return STATUS_SUCCESS; } /*++ * RtlInitializeCriticalSection * @implemented NT4 * * Initialises a new critical section. * * Params: * CriticalSection - Critical section to initialise * * Returns: * STATUS_SUCCESS. * * Remarks: * Simply calls RtlInitializeCriticalSectionAndSpinCount * *--*/ NTSTATUS NTAPI RtlInitializeCriticalSection(PRTL_CRITICAL_SECTION CriticalSection) { /* Call the Main Function */ return RtlInitializeCriticalSectionAndSpinCount(CriticalSection, 0); } /*++ * RtlInitializeCriticalSectionAndSpinCount * @implemented NT4 * * Initialises a new critical section. * * Params: * CriticalSection - Critical section to initialise * * SpinCount - Spin count for the critical section. * * Returns: * STATUS_SUCCESS. * * Remarks: * SpinCount is ignored on single-processor systems. * *--*/ NTSTATUS NTAPI RtlInitializeCriticalSectionAndSpinCount(PRTL_CRITICAL_SECTION CriticalSection, ULONG SpinCount) { PRTL_CRITICAL_SECTION_DEBUG CritcalSectionDebugData; /* First things first, set up the Object */ DPRINT("Initializing Critical Section: %p\n", CriticalSection); CriticalSection->LockCount = -1; CriticalSection->RecursionCount = 0; CriticalSection->OwningThread = 0; CriticalSection->SpinCount = (NtCurrentPeb()->NumberOfProcessors > 1) ? SpinCount : 0; CriticalSection->LockSemaphore = 0; /* Allocate the Debug Data */ CritcalSectionDebugData = RtlpAllocateDebugInfo(); DPRINT("Allocated Debug Data: %p inside Process: %p\n", CritcalSectionDebugData, NtCurrentTeb()->ClientId.UniqueProcess); if (!CritcalSectionDebugData) { /* This is bad! */ DPRINT1("Couldn't allocate Debug Data for: %p\n", CriticalSection); return STATUS_NO_MEMORY; } /* Set it up */ CritcalSectionDebugData->Type = RTL_CRITSECT_TYPE; CritcalSectionDebugData->ContentionCount = 0; CritcalSectionDebugData->EntryCount = 0; CritcalSectionDebugData->CriticalSection = CriticalSection; CritcalSectionDebugData->Flags = 0; CriticalSection->DebugInfo = CritcalSectionDebugData; /* * Add it to the List of Critical Sections owned by the process. * If we've initialized the Lock, then use it. If not, then probably * this is the lock initialization itself, so insert it directly. */ if ((CriticalSection != &RtlCriticalSectionLock) && (RtlpCritSectInitialized)) { DPRINT("Securely Inserting into ProcessLocks: %p, %p, %p\n", &CritcalSectionDebugData->ProcessLocksList, CriticalSection, &RtlCriticalSectionList); /* Protect List */ RtlEnterCriticalSection(&RtlCriticalSectionLock); /* Add this one */ InsertTailList(&RtlCriticalSectionList, &CritcalSectionDebugData->ProcessLocksList); /* Unprotect */ RtlLeaveCriticalSection(&RtlCriticalSectionLock); } else { DPRINT("Inserting into ProcessLocks: %p, %p, %p\n", &CritcalSectionDebugData->ProcessLocksList, CriticalSection, &RtlCriticalSectionList); /* Add it directly */ InsertTailList(&RtlCriticalSectionList, &CritcalSectionDebugData->ProcessLocksList); } return STATUS_SUCCESS; } /*++ * RtlGetCriticalSectionRecursionCount * @implemented NT5.2 SP1 * * Retrieves the recursion count of a given critical section. * * Params: * CriticalSection - Critical section to retrieve its recursion count. * * Returns: * The recursion count. * * Remarks: * We return the recursion count of the critical section if it is owned * by the current thread, and otherwise we return zero. * *--*/ LONG NTAPI RtlGetCriticalSectionRecursionCount(PRTL_CRITICAL_SECTION CriticalSection) { if (CriticalSection->OwningThread == NtCurrentTeb()->ClientId.UniqueThread) { /* * The critical section is owned by the current thread, * therefore retrieve its actual recursion count. */ return CriticalSection->RecursionCount; } else { /* * It is not owned by the current thread, so * for this thread there is no recursion. */ return 0; } } /*++ * RtlLeaveCriticalSection * @implemented NT4 * * Releases a critical section and makes if available for new owners. * * Params: * CriticalSection - Critical section to release. * * Returns: * STATUS_SUCCESS. * * Remarks: * If another thread was waiting, the slow path is entered. * *--*/ NTSTATUS NTAPI RtlLeaveCriticalSection(PRTL_CRITICAL_SECTION CriticalSection) { #if DBG HANDLE Thread = (HANDLE)NtCurrentTeb()->ClientId.UniqueThread; /* * In win this case isn't checked. However it's a valid check so it should * only be performed in debug builds! */ if (Thread != CriticalSection->OwningThread) { DPRINT1("Releasing critical section not owned!\n"); return STATUS_INVALID_PARAMETER; } #endif /* * Decrease the Recursion Count. No need to do this atomically because only * the thread who holds the lock can call this function (unless the program * is totally screwed... */ if (--CriticalSection->RecursionCount) { /* Someone still owns us, but we are free. This needs to be done atomically. */ InterlockedDecrement(&CriticalSection->LockCount); } else { /* * Nobody owns us anymore. No need to do this atomically. * See comment above. */ CriticalSection->OwningThread = 0; /* Was someone wanting us? This needs to be done atomically. */ if (-1 != InterlockedDecrement(&CriticalSection->LockCount)) { /* Let him have us */ RtlpUnWaitCriticalSection(CriticalSection); } } /* Sucessful! */ return STATUS_SUCCESS; } /*++ * RtlTryEnterCriticalSection * @implemented NT4 * * Attemps to gain ownership of the critical section without waiting. * * Params: * CriticalSection - Critical section to attempt acquiring. * * Returns: * TRUE if the critical section has been acquired, FALSE otherwise. * * Remarks: * None * *--*/ BOOLEAN NTAPI RtlTryEnterCriticalSection(PRTL_CRITICAL_SECTION CriticalSection) { /* Try to take control */ if (InterlockedCompareExchange(&CriticalSection->LockCount, 0, -1) == -1) { /* It's ours */ CriticalSection->OwningThread = NtCurrentTeb()->ClientId.UniqueThread; CriticalSection->RecursionCount = 1; return TRUE; } else if (CriticalSection->OwningThread == NtCurrentTeb()->ClientId.UniqueThread) { /* It's already ours */ InterlockedIncrement(&CriticalSection->LockCount); CriticalSection->RecursionCount++; return TRUE; } /* It's not ours */ return FALSE; } VOID NTAPI RtlCheckForOrphanedCriticalSections(HANDLE ThreadHandle) { UNIMPLEMENTED; } ULONG NTAPI RtlIsCriticalSectionLocked(PRTL_CRITICAL_SECTION CriticalSection) { return CriticalSection->RecursionCount != 0; } ULONG NTAPI RtlIsCriticalSectionLockedByThread(PRTL_CRITICAL_SECTION CriticalSection) { return CriticalSection->OwningThread == NtCurrentTeb()->ClientId.UniqueThread && CriticalSection->RecursionCount != 0; } /* EOF */