2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS system libraries
4 * FILE: lib/rtl/critical.c
5 * PURPOSE: Critical sections
6 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net)
10 /* INCLUDES *****************************************************************/
17 #define MAX_STATIC_CS_DEBUG_OBJECTS 64
19 static RTL_CRITICAL_SECTION RtlCriticalSectionLock
;
20 static LIST_ENTRY RtlCriticalSectionList
;
21 static BOOLEAN RtlpCritSectInitialized
= FALSE
;
22 static RTL_CRITICAL_SECTION_DEBUG RtlpStaticDebugInfo
[MAX_STATIC_CS_DEBUG_OBJECTS
];
23 static BOOLEAN RtlpDebugInfoFreeList
[MAX_STATIC_CS_DEBUG_OBJECTS
];
25 /* FUNCTIONS *****************************************************************/
28 * RtlpCreateCriticalSectionSem
30 * Checks if an Event has been created for the critical section.
36 * None. Raises an exception if the system call failed.
44 RtlpCreateCriticalSectionSem(PRTL_CRITICAL_SECTION CriticalSection
)
46 HANDLE hEvent
= CriticalSection
->LockSemaphore
;
50 /* Chevk if we have an event */
53 /* No, so create it */
54 if (!NT_SUCCESS(Status
= NtCreateEvent(&hNewEvent
,
60 /* We failed, this is bad... */
61 DPRINT1("Failed to Create Event!\n");
62 InterlockedDecrement(&CriticalSection
->LockCount
);
63 RtlRaiseStatus(Status
);
66 DPRINT("Created Event: %p \n", hNewEvent
);
68 if (InterlockedCompareExchangePointer((PVOID
*)&CriticalSection
->LockSemaphore
,
72 /* Some just created an event */
73 DPRINT("Closing already created event: %p\n", hNewEvent
);
82 * RtlpWaitForCriticalSection
84 * Slow path of RtlEnterCriticalSection. Waits on an Event Object.
87 * CriticalSection - Critical section to acquire.
90 * STATUS_SUCCESS, or raises an exception if a deadlock is occuring.
98 RtlpWaitForCriticalSection(PRTL_CRITICAL_SECTION CriticalSection
)
101 EXCEPTION_RECORD ExceptionRecord
;
102 BOOLEAN LastChance
= FALSE
;
103 LARGE_INTEGER Timeout
;
105 /* Wait 2.5 minutes */
106 Timeout
.QuadPart
= 150000L * (ULONGLONG
)10000;
107 Timeout
.QuadPart
= -Timeout
.QuadPart
;
108 /* ^^ HACK HACK HACK. Good way:
109 Timeout = &NtCurrentPeb()->CriticalSectionTimeout */
111 /* Do we have an Event yet? */
112 if (!CriticalSection
->LockSemaphore
) {
113 RtlpCreateCriticalSectionSem(CriticalSection
);
116 /* Increase the Debug Entry count */
117 DPRINT("Waiting on Critical Section Event: %p %p\n",
119 CriticalSection
->LockSemaphore
);
121 if (CriticalSection
->DebugInfo
)
122 CriticalSection
->DebugInfo
->EntryCount
++;
126 /* Increase the number of times we've had contention */
127 if (CriticalSection
->DebugInfo
)
128 CriticalSection
->DebugInfo
->ContentionCount
++;
130 /* Wait on the Event */
131 Status
= NtWaitForSingleObject(CriticalSection
->LockSemaphore
,
135 /* We have Timed out */
136 if (Status
== STATUS_TIMEOUT
) {
138 /* Is this the 2nd time we've timed out? */
141 DPRINT1("Deadlock: %p\n", CriticalSection
);
143 /* Yes it is, we are raising an exception */
144 ExceptionRecord
.ExceptionCode
= STATUS_POSSIBLE_DEADLOCK
;
145 ExceptionRecord
.ExceptionFlags
= 0;
146 ExceptionRecord
.ExceptionRecord
= NULL
;
147 ExceptionRecord
.ExceptionAddress
= RtlRaiseException
;
148 ExceptionRecord
.NumberParameters
= 1;
149 ExceptionRecord
.ExceptionInformation
[0] = (ULONG_PTR
)CriticalSection
;
150 RtlRaiseException(&ExceptionRecord
);
159 /* If we are here, everything went fine */
160 return STATUS_SUCCESS
;
166 * RtlpUnWaitCriticalSection
168 * Slow path of RtlLeaveCriticalSection. Fires an Event Object.
171 * CriticalSection - Critical section to release.
174 * None. Raises an exception if the system call failed.
182 RtlpUnWaitCriticalSection(PRTL_CRITICAL_SECTION CriticalSection
)
186 /* Do we have an Event yet? */
187 if (!CriticalSection
->LockSemaphore
) {
188 RtlpCreateCriticalSectionSem(CriticalSection
);
191 /* Signal the Event */
192 DPRINT("Signaling Critical Section Event: %p, %p\n",
194 CriticalSection
->LockSemaphore
);
195 Status
= NtSetEvent(CriticalSection
->LockSemaphore
, NULL
);
197 if (!NT_SUCCESS(Status
)) {
200 DPRINT1("Signaling Failed for: %p, %p, 0x%08lx\n",
202 CriticalSection
->LockSemaphore
,
204 RtlRaiseStatus(Status
);
209 * RtlpInitDeferedCriticalSection
211 * Initializes the Critical Section implementation.
220 * After this call, the Process Critical Section list is protected.
225 RtlpInitDeferedCriticalSection(VOID
)
228 /* Initialize the Process Critical Section List */
229 InitializeListHead(&RtlCriticalSectionList
);
231 /* Initialize the CS Protecting the List */
232 RtlInitializeCriticalSection(&RtlCriticalSectionLock
);
234 /* It's now safe to enter it */
235 RtlpCritSectInitialized
= TRUE
;
239 * RtlpAllocateDebugInfo
241 * Finds or allocates memory for a Critical Section Debug Object
247 * A pointer to an empty Critical Section Debug Object.
250 * For optimization purposes, the first 64 entries can be cached. From
251 * then on, future Critical Sections will allocate memory from the heap.
254 PRTL_CRITICAL_SECTION_DEBUG
256 RtlpAllocateDebugInfo(VOID
)
260 /* Try to allocate from our buffer first */
261 for (i
= 0; i
< MAX_STATIC_CS_DEBUG_OBJECTS
; i
++) {
263 /* Check if Entry is free */
264 if (!RtlpDebugInfoFreeList
[i
]) {
266 /* Mark entry in use */
267 DPRINT("Using entry: %lu. Buffer: %p\n", i
, &RtlpStaticDebugInfo
[i
]);
268 RtlpDebugInfoFreeList
[i
] = TRUE
;
270 /* Use free entry found */
271 return &RtlpStaticDebugInfo
[i
];
276 /* We are out of static buffer, allocate dynamic */
277 return RtlAllocateHeap(NtCurrentPeb()->ProcessHeap
,
279 sizeof(RTL_CRITICAL_SECTION_DEBUG
));
285 * Frees the memory for a Critical Section Debug Object
288 * DebugInfo - Pointer to Critical Section Debug Object to free.
294 * If the pointer is part of the static buffer, then the entry is made
295 * free again. If not, the object is de-allocated from the heap.
300 RtlpFreeDebugInfo(PRTL_CRITICAL_SECTION_DEBUG DebugInfo
)
304 /* Is it part of our cached entries? */
305 if ((DebugInfo
>= RtlpStaticDebugInfo
) &&
306 (DebugInfo
<= &RtlpStaticDebugInfo
[MAX_STATIC_CS_DEBUG_OBJECTS
-1])) {
308 /* Yes. zero it out */
309 RtlZeroMemory(DebugInfo
, sizeof(RTL_CRITICAL_SECTION_DEBUG
));
312 EntryId
= (DebugInfo
- RtlpStaticDebugInfo
);
313 DPRINT("Freeing from Buffer: %p. Entry: %Iu inside Process: %p\n",
316 NtCurrentTeb()->ClientId
.UniqueProcess
);
317 RtlpDebugInfoFreeList
[EntryId
] = FALSE
;
319 } else if (!DebugInfo
->Flags
) {
321 /* It's a dynamic one, so free from the heap */
322 DPRINT("Freeing from Heap: %p inside Process: %p\n",
324 NtCurrentTeb()->ClientId
.UniqueProcess
);
325 RtlFreeHeap(NtCurrentPeb()->ProcessHeap
, 0, DebugInfo
);
329 /* Wine stores a section name pointer in the Flags member */
330 DPRINT("Assuming static: %p inside Process: %p\n",
332 NtCurrentTeb()->ClientId
.UniqueProcess
);
338 * RtlDeleteCriticalSection
341 * Deletes a Critical Section
344 * CriticalSection - Critical section to delete.
347 * STATUS_SUCCESS, or error value returned by NtClose.
350 * The critical section members should not be read after this call.
355 RtlDeleteCriticalSection(PRTL_CRITICAL_SECTION CriticalSection
)
357 NTSTATUS Status
= STATUS_SUCCESS
;
359 DPRINT("Deleting Critical Section: %p\n", CriticalSection
);
360 /* Close the Event Object Handle if it exists */
361 if (CriticalSection
->LockSemaphore
) {
363 /* In case NtClose fails, return the status */
364 Status
= NtClose(CriticalSection
->LockSemaphore
);
369 RtlEnterCriticalSection(&RtlCriticalSectionLock
);
371 if (CriticalSection
->DebugInfo
)
373 /* Remove it from the list */
374 RemoveEntryList(&CriticalSection
->DebugInfo
->ProcessLocksList
);
375 #if 0 /* We need to preserve Flags for RtlpFreeDebugInfo */
376 RtlZeroMemory(CriticalSection
->DebugInfo
, sizeof(RTL_CRITICAL_SECTION_DEBUG
));
381 RtlLeaveCriticalSection(&RtlCriticalSectionLock
);
383 if (CriticalSection
->DebugInfo
)
386 RtlpFreeDebugInfo(CriticalSection
->DebugInfo
);
390 RtlZeroMemory(CriticalSection
, sizeof(RTL_CRITICAL_SECTION
));
397 * RtlSetCriticalSectionSpinCount
400 * Sets the spin count for a critical section.
403 * CriticalSection - Critical section to set the spin count for.
405 * SpinCount - Spin count for the critical section.
411 * SpinCount is ignored on single-processor systems.
416 RtlSetCriticalSectionSpinCount(PRTL_CRITICAL_SECTION CriticalSection
,
419 ULONG OldCount
= (ULONG
)CriticalSection
->SpinCount
;
421 /* Set to parameter if MP, or to 0 if this is Uniprocessor */
422 CriticalSection
->SpinCount
= (NtCurrentPeb()->NumberOfProcessors
> 1) ? SpinCount
: 0;
427 * RtlEnterCriticalSection
430 * Waits to gain ownership of the critical section.
433 * CriticalSection - Critical section to wait for.
439 * Uses a fast-path unless contention happens.
444 RtlEnterCriticalSection(PRTL_CRITICAL_SECTION CriticalSection
)
446 HANDLE Thread
= (HANDLE
)NtCurrentTeb()->ClientId
.UniqueThread
;
449 if (InterlockedIncrement(&CriticalSection
->LockCount
) != 0) {
452 * We've failed to lock it! Does this thread
455 if (Thread
== CriticalSection
->OwningThread
) {
457 /* You own it, so you'll get it when you're done with it! No need to
458 use the interlocked functions as only the thread who already owns
459 the lock can modify this data. */
460 CriticalSection
->RecursionCount
++;
461 return STATUS_SUCCESS
;
464 /* NOTE - CriticalSection->OwningThread can be NULL here because changing
465 this information is not serialized. This happens when thread a
466 acquires the lock (LockCount == 0) and thread b tries to
467 acquire it as well (LockCount == 1) but thread a hasn't had a
468 chance to set the OwningThread! So it's not an error when
469 OwningThread is NULL here! */
471 /* We don't own it, so we must wait for it */
472 RtlpWaitForCriticalSection(CriticalSection
);
475 /* Lock successful. Changing this information has not to be serialized because
476 only one thread at a time can actually change it (the one who acquired
478 CriticalSection
->OwningThread
= Thread
;
479 CriticalSection
->RecursionCount
= 1;
480 return STATUS_SUCCESS
;
484 * RtlInitializeCriticalSection
487 * Initialises a new critical section.
490 * CriticalSection - Critical section to initialise
496 * Simply calls RtlInitializeCriticalSectionAndSpinCount
501 RtlInitializeCriticalSection(PRTL_CRITICAL_SECTION CriticalSection
)
503 /* Call the Main Function */
504 return RtlInitializeCriticalSectionAndSpinCount(CriticalSection
, 0);
508 * RtlInitializeCriticalSectionAndSpinCount
511 * Initialises a new critical section.
514 * CriticalSection - Critical section to initialise
516 * SpinCount - Spin count for the critical section.
522 * SpinCount is ignored on single-processor systems.
527 RtlInitializeCriticalSectionAndSpinCount(PRTL_CRITICAL_SECTION CriticalSection
,
530 PRTL_CRITICAL_SECTION_DEBUG CritcalSectionDebugData
;
532 /* First things first, set up the Object */
533 DPRINT("Initializing Critical Section: %p\n", CriticalSection
);
534 CriticalSection
->LockCount
= -1;
535 CriticalSection
->RecursionCount
= 0;
536 CriticalSection
->OwningThread
= 0;
537 CriticalSection
->SpinCount
= (NtCurrentPeb()->NumberOfProcessors
> 1) ? SpinCount
: 0;
538 CriticalSection
->LockSemaphore
= 0;
540 /* Allocate the Debug Data */
541 CritcalSectionDebugData
= RtlpAllocateDebugInfo();
542 DPRINT("Allocated Debug Data: %p inside Process: %p\n",
543 CritcalSectionDebugData
,
544 NtCurrentTeb()->ClientId
.UniqueProcess
);
546 if (!CritcalSectionDebugData
) {
549 DPRINT1("Couldn't allocate Debug Data for: %p\n", CriticalSection
);
550 return STATUS_NO_MEMORY
;
554 CritcalSectionDebugData
->Type
= RTL_CRITSECT_TYPE
;
555 CritcalSectionDebugData
->ContentionCount
= 0;
556 CritcalSectionDebugData
->EntryCount
= 0;
557 CritcalSectionDebugData
->CriticalSection
= CriticalSection
;
558 CritcalSectionDebugData
->Flags
= 0;
559 CriticalSection
->DebugInfo
= CritcalSectionDebugData
;
562 * Add it to the List of Critical Sections owned by the process.
563 * If we've initialized the Lock, then use it. If not, then probably
564 * this is the lock initialization itself, so insert it directly.
566 if ((CriticalSection
!= &RtlCriticalSectionLock
) && (RtlpCritSectInitialized
)) {
568 DPRINT("Securely Inserting into ProcessLocks: %p, %p, %p\n",
569 &CritcalSectionDebugData
->ProcessLocksList
,
571 &RtlCriticalSectionList
);
574 RtlEnterCriticalSection(&RtlCriticalSectionLock
);
577 InsertTailList(&RtlCriticalSectionList
, &CritcalSectionDebugData
->ProcessLocksList
);
580 RtlLeaveCriticalSection(&RtlCriticalSectionLock
);
584 DPRINT("Inserting into ProcessLocks: %p, %p, %p\n",
585 &CritcalSectionDebugData
->ProcessLocksList
,
587 &RtlCriticalSectionList
);
589 /* Add it directly */
590 InsertTailList(&RtlCriticalSectionList
, &CritcalSectionDebugData
->ProcessLocksList
);
593 return STATUS_SUCCESS
;
597 * RtlLeaveCriticalSection
600 * Releases a critical section and makes if available for new owners.
603 * CriticalSection - Critical section to release.
609 * If another thread was waiting, the slow path is entered.
614 RtlLeaveCriticalSection(PRTL_CRITICAL_SECTION CriticalSection
)
617 HANDLE Thread
= (HANDLE
)NtCurrentTeb()->ClientId
.UniqueThread
;
619 /* In win this case isn't checked. However it's a valid check so it should only
620 be performed in debug builds! */
621 if (Thread
!= CriticalSection
->OwningThread
)
623 DPRINT1("Releasing critical section not owned!\n");
624 return STATUS_INVALID_PARAMETER
;
628 /* Decrease the Recursion Count. No need to do this atomically because only
629 the thread who holds the lock can call this function (unless the program
630 is totally screwed... */
631 if (--CriticalSection
->RecursionCount
) {
633 /* Someone still owns us, but we are free. This needs to be done atomically. */
634 InterlockedDecrement(&CriticalSection
->LockCount
);
638 /* Nobody owns us anymore. No need to do this atomically. See comment
640 CriticalSection
->OwningThread
= 0;
642 /* Was someone wanting us? This needs to be done atomically. */
643 if (-1 != InterlockedDecrement(&CriticalSection
->LockCount
)) {
645 /* Let him have us */
646 RtlpUnWaitCriticalSection(CriticalSection
);
651 return STATUS_SUCCESS
;
655 * RtlTryEnterCriticalSection
658 * Attemps to gain ownership of the critical section without waiting.
661 * CriticalSection - Critical section to attempt acquiring.
664 * TRUE if the critical section has been acquired, FALSE otherwise.
672 RtlTryEnterCriticalSection(PRTL_CRITICAL_SECTION CriticalSection
)
674 /* Try to take control */
675 if (InterlockedCompareExchange(&CriticalSection
->LockCount
,
680 CriticalSection
->OwningThread
= NtCurrentTeb()->ClientId
.UniqueThread
;
681 CriticalSection
->RecursionCount
= 1;
684 } else if (CriticalSection
->OwningThread
== NtCurrentTeb()->ClientId
.UniqueThread
) {
686 /* It's already ours */
687 InterlockedIncrement(&CriticalSection
->LockCount
);
688 CriticalSection
->RecursionCount
++;