60b5de0af2035d72645e36fad6d6e5ff9c41923e
[reactos.git] / reactos / lib / ntdll / rtl / critical.c
1 /* $Id$
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS system libraries
5 * FILE: lib/ntdll/rtl/critical.c
6 * PURPOSE: Critical sections
7 * UPDATE HISTORY:
8 * Created 30/09/98
9 * Rewritten ROS version, based on WINE code plus
10 * some fixes useful only for ROS right now - 03/01/05
11 */
12
13 /* INCLUDES ******************************************************************/
14
15 #include <ntdll.h>
16 #define NDEBUG
17 #include <debug.h>
18
19 /* FUNCTIONS *****************************************************************/
20
21 #define MAX_STATIC_CS_DEBUG_OBJECTS 64
22
23 static RTL_CRITICAL_SECTION RtlCriticalSectionLock;
24 static LIST_ENTRY RtlCriticalSectionList;
25 static BOOLEAN RtlpCritSectInitialized = FALSE;
26 static RTL_CRITICAL_SECTION_DEBUG RtlpStaticDebugInfo[MAX_STATIC_CS_DEBUG_OBJECTS];
27 static BOOLEAN RtlpDebugInfoFreeList[MAX_STATIC_CS_DEBUG_OBJECTS];
28
29 /*++
30 * RtlDeleteCriticalSection
31 * @implemented NT4
32 *
33 * Deletes a Critical Section
34 *
35 * Params:
36 * CriticalSection - Critical section to delete.
37 *
38 * Returns:
39 * STATUS_SUCCESS, or error value returned by NtClose.
40 *
41 * Remarks:
42 * The critical section members should not be read after this call.
43 *
44 *--*/
45 NTSTATUS
46 STDCALL
47 RtlDeleteCriticalSection(
48 PRTL_CRITICAL_SECTION CriticalSection)
49 {
50 NTSTATUS Status = STATUS_SUCCESS;
51
52 DPRINT("Deleting Critical Section: %x\n", CriticalSection);
53 /* Close the Event Object Handle if it exists */
54 if (CriticalSection->LockSemaphore) {
55
56 /* In case NtClose fails, return the status */
57 Status = NtClose(CriticalSection->LockSemaphore);
58
59 }
60
61 /* Protect List */
62 RtlEnterCriticalSection(&RtlCriticalSectionLock);
63
64 /* Remove it from the list */
65 RemoveEntryList(&CriticalSection->DebugInfo->ProcessLocksList);
66
67 /* Unprotect */
68 RtlLeaveCriticalSection(&RtlCriticalSectionLock);
69
70 /* Free it */
71 RtlpFreeDebugInfo(CriticalSection->DebugInfo);
72
73 /* Wipe it out */
74 RtlZeroMemory(CriticalSection, sizeof(RTL_CRITICAL_SECTION));
75
76 /* Return */
77 return Status;
78 }
79
80 /*++
81 * RtlSetCriticalSectionSpinCount
82 * @implemented NT4
83 *
84 * Sets the spin count for a critical section.
85 *
86 * Params:
87 * CriticalSection - Critical section to set the spin count for.
88 *
89 * SpinCount - Spin count for the critical section.
90 *
91 * Returns:
92 * STATUS_SUCCESS.
93 *
94 * Remarks:
95 * SpinCount is ignored on single-processor systems.
96 *
97 *--*/
98 DWORD
99 STDCALL
100 RtlSetCriticalSectionSpinCount(
101 PRTL_CRITICAL_SECTION CriticalSection,
102 ULONG SpinCount
103 )
104 {
105 ULONG OldCount = CriticalSection->SpinCount;
106
107 /* Set to parameter if MP, or to 0 if this is Uniprocessor */
108 CriticalSection->SpinCount = (NtCurrentPeb()->NumberOfProcessors > 1) ? SpinCount : 0;
109 return OldCount;
110 }
111
112 /*++
113 * RtlEnterCriticalSection
114 * @implemented NT4
115 *
116 * Waits to gain ownership of the critical section.
117 *
118 * Params:
119 * CriticalSection - Critical section to wait for.
120 *
121 * Returns:
122 * STATUS_SUCCESS.
123 *
124 * Remarks:
125 * Uses a fast-path unless contention happens.
126 *
127 *--*/
128 NTSTATUS
129 STDCALL
130 RtlEnterCriticalSection(
131 PRTL_CRITICAL_SECTION CriticalSection)
132 {
133 HANDLE Thread = (HANDLE)NtCurrentTeb()->Cid.UniqueThread;
134
135 /* Try to Lock it */
136 if (InterlockedIncrement(&CriticalSection->LockCount) != 0) {
137
138 /*
139 * We've failed to lock it! Does this thread
140 * actually own it?
141 */
142 if (Thread == CriticalSection->OwningThread) {
143
144 /* You own it, so you'll get it when you're done with it! No need to
145 use the interlocked functions as only the thread who already owns
146 the lock can modify this data. */
147 CriticalSection->RecursionCount++;
148 return STATUS_SUCCESS;
149 }
150
151 /* NOTE - CriticalSection->OwningThread can be NULL here because changing
152 this information is not serialized. This happens when thread a
153 acquires the lock (LockCount == 0) and thread b tries to
154 acquire it as well (LockCount == 1) but thread a hasn't had a
155 chance to set the OwningThread! So it's not an error when
156 OwningThread is NULL here! */
157
158 /* We don't own it, so we must wait for it */
159 RtlpWaitForCriticalSection(CriticalSection);
160 }
161
162 /* Lock successful. Changing this information has not to be serialized because
163 only one thread at a time can actually change it (the one who acquired
164 the lock)! */
165 CriticalSection->OwningThread = Thread;
166 CriticalSection->RecursionCount = 1;
167 return STATUS_SUCCESS;
168 }
169
170 /*++
171 * RtlInitializeCriticalSection
172 * @implemented NT4
173 *
174 * Initialises a new critical section.
175 *
176 * Params:
177 * CriticalSection - Critical section to initialise
178 *
179 * Returns:
180 * STATUS_SUCCESS.
181 *
182 * Remarks:
183 * Simply calls RtlInitializeCriticalSectionAndSpinCount
184 *
185 *--*/
186 NTSTATUS
187 STDCALL
188 RtlInitializeCriticalSection(
189 PRTL_CRITICAL_SECTION CriticalSection)
190 {
191 /* Call the Main Function */
192 return RtlInitializeCriticalSectionAndSpinCount(CriticalSection, 0);
193 }
194
195 /*++
196 * RtlInitializeCriticalSectionAndSpinCount
197 * @implemented NT4
198 *
199 * Initialises a new critical section.
200 *
201 * Params:
202 * CriticalSection - Critical section to initialise
203 *
204 * SpinCount - Spin count for the critical section.
205 *
206 * Returns:
207 * STATUS_SUCCESS.
208 *
209 * Remarks:
210 * SpinCount is ignored on single-processor systems.
211 *
212 *--*/
213 NTSTATUS
214 STDCALL
215 RtlInitializeCriticalSectionAndSpinCount (
216 PRTL_CRITICAL_SECTION CriticalSection,
217 ULONG SpinCount)
218 {
219 PRTL_CRITICAL_SECTION_DEBUG CritcalSectionDebugData;
220
221 /* First things first, set up the Object */
222 DPRINT("Initializing Critical Section: %x\n", CriticalSection);
223 CriticalSection->LockCount = -1;
224 CriticalSection->RecursionCount = 0;
225 CriticalSection->OwningThread = 0;
226 CriticalSection->SpinCount = (NtCurrentPeb()->NumberOfProcessors > 1) ? SpinCount : 0;
227 CriticalSection->LockSemaphore = 0;
228
229 /* Allocate the Debug Data */
230 CritcalSectionDebugData = RtlpAllocateDebugInfo();
231 DPRINT("Allocated Debug Data: %x inside Process: %x\n",
232 CritcalSectionDebugData,
233 NtCurrentTeb()->Cid.UniqueProcess);
234
235 if (!CritcalSectionDebugData) {
236
237 /* This is bad! */
238 DPRINT1("Couldn't allocate Debug Data for: %x\n", CriticalSection);
239 return STATUS_NO_MEMORY;
240 }
241
242 /* Set it up */
243 CritcalSectionDebugData->Type = RTL_CRITSECT_TYPE;
244 CritcalSectionDebugData->ContentionCount = 0;
245 CritcalSectionDebugData->EntryCount = 0;
246 CritcalSectionDebugData->CriticalSection = CriticalSection;
247 CriticalSection->DebugInfo = CritcalSectionDebugData;
248
249 /*
250 * Add it to the List of Critical Sections owned by the process.
251 * If we've initialized the Lock, then use it. If not, then probably
252 * this is the lock initialization itself, so insert it directly.
253 */
254 if ((CriticalSection != &RtlCriticalSectionLock) && (RtlpCritSectInitialized)) {
255
256 DPRINT("Securely Inserting into ProcessLocks: %x, %x, %x\n",
257 &CritcalSectionDebugData->ProcessLocksList,
258 CriticalSection,
259 &RtlCriticalSectionList);
260
261 /* Protect List */
262 RtlEnterCriticalSection(&RtlCriticalSectionLock);
263
264 /* Add this one */
265 InsertTailList(&RtlCriticalSectionList, &CritcalSectionDebugData->ProcessLocksList);
266
267 /* Unprotect */
268 RtlLeaveCriticalSection(&RtlCriticalSectionLock);
269
270 } else {
271
272 DPRINT("Inserting into ProcessLocks: %x, %x, %x\n",
273 &CritcalSectionDebugData->ProcessLocksList,
274 CriticalSection,
275 &RtlCriticalSectionList);
276
277 /* Add it directly */
278 InsertTailList(&RtlCriticalSectionList, &CritcalSectionDebugData->ProcessLocksList);
279 }
280
281 return STATUS_SUCCESS;
282 }
283
284 /*++
285 * RtlLeaveCriticalSection
286 * @implemented NT4
287 *
288 * Releases a critical section and makes if available for new owners.
289 *
290 * Params:
291 * CriticalSection - Critical section to release.
292 *
293 * Returns:
294 * STATUS_SUCCESS.
295 *
296 * Remarks:
297 * If another thread was waiting, the slow path is entered.
298 *
299 *--*/
300 NTSTATUS
301 STDCALL
302 RtlLeaveCriticalSection(
303 PRTL_CRITICAL_SECTION CriticalSection)
304 {
305 #ifndef NDEBUG
306 HANDLE Thread = (HANDLE)NtCurrentTeb()->Cid.UniqueThread;
307
308 /* In win this case isn't checked. However it's a valid check so it should only
309 be performed in debug builds! */
310 if (Thread != CriticalSection->OwningThread)
311 {
312 DPRINT1("Releasing critical section not owned!\n");
313 return STATUS_INVALID_PARAMETER;
314 }
315 #endif
316
317 /* Decrease the Recursion Count. No need to do this atomically because only
318 the thread who holds the lock can call this function (unless the program
319 is totally screwed... */
320 if (--CriticalSection->RecursionCount) {
321
322 /* Someone still owns us, but we are free. This needs to be done atomically. */
323 InterlockedDecrement(&CriticalSection->LockCount);
324
325 } else {
326
327 /* Nobody owns us anymore. No need to do this atomically. See comment
328 above. */
329 CriticalSection->OwningThread = 0;
330
331 /* Was someone wanting us? This needs to be done atomically. */
332 if (-1 != InterlockedDecrement(&CriticalSection->LockCount)) {
333
334 /* Let him have us */
335 RtlpUnWaitCriticalSection(CriticalSection);
336 }
337 }
338
339 /* Sucessful! */
340 return STATUS_SUCCESS;
341 }
342
343 /*++
344 * RtlTryEnterCriticalSection
345 * @implemented NT4
346 *
347 * Attemps to gain ownership of the critical section without waiting.
348 *
349 * Params:
350 * CriticalSection - Critical section to attempt acquiring.
351 *
352 * Returns:
353 * TRUE if the critical section has been acquired, FALSE otherwise.
354 *
355 * Remarks:
356 * None
357 *
358 *--*/
359 BOOLEAN
360 STDCALL
361 RtlTryEnterCriticalSection(
362 PRTL_CRITICAL_SECTION CriticalSection)
363 {
364 /* Try to take control */
365 if (InterlockedCompareExchange(&CriticalSection->LockCount,
366 0,
367 -1) == -1) {
368
369 /* It's ours */
370 CriticalSection->OwningThread = NtCurrentTeb()->Cid.UniqueThread;
371 CriticalSection->RecursionCount = 1;
372 return TRUE;
373
374 } else if (CriticalSection->OwningThread == NtCurrentTeb()->Cid.UniqueThread) {
375
376 /* It's already ours */
377 InterlockedIncrement(&CriticalSection->LockCount);
378 CriticalSection->RecursionCount++;
379 return TRUE;
380 }
381
382 /* It's not ours */
383 return FALSE;
384 }
385
386 /*++
387 * RtlpWaitForCriticalSection
388 *
389 * Slow path of RtlEnterCriticalSection. Waits on an Event Object.
390 *
391 * Params:
392 * CriticalSection - Critical section to acquire.
393 *
394 * Returns:
395 * STATUS_SUCCESS, or raises an exception if a deadlock is occuring.
396 *
397 * Remarks:
398 * None
399 *
400 *--*/
401 NTSTATUS
402 STDCALL
403 RtlpWaitForCriticalSection(
404 PRTL_CRITICAL_SECTION CriticalSection)
405 {
406 NTSTATUS Status;
407 EXCEPTION_RECORD ExceptionRecord;
408 BOOLEAN LastChance = FALSE;
409 LARGE_INTEGER Timeout;
410
411 /* Wait 2.5 minutes */
412 Timeout.QuadPart = 150000L * (ULONGLONG)10000;
413 Timeout.QuadPart = -Timeout.QuadPart;
414 /* ^^ HACK HACK HACK. Good way:
415 Timeout = &NtCurrentPeb()->CriticalSectionTimeout */
416
417 /* Do we have an Event yet? */
418 if (!CriticalSection->LockSemaphore) {
419 RtlpCreateCriticalSectionSem(CriticalSection);
420 }
421
422 /* Increase the Debug Entry count */
423 DPRINT("Waiting on Critical Section Event: %x %x\n",
424 CriticalSection,
425 CriticalSection->LockSemaphore);
426 CriticalSection->DebugInfo->EntryCount++;
427
428 for (;;) {
429
430 /* Increase the number of times we've had contention */
431 CriticalSection->DebugInfo->ContentionCount++;
432
433 /* Wait on the Event */
434 Status = NtWaitForSingleObject(CriticalSection->LockSemaphore,
435 FALSE,
436 &Timeout);
437
438 /* We have Timed out */
439 if (Status == STATUS_TIMEOUT) {
440
441 /* Is this the 2nd time we've timed out? */
442 if (LastChance) {
443
444 DPRINT1("Deadlock: %x\n", CriticalSection);
445
446 /* Yes it is, we are raising an exception */
447 ExceptionRecord.ExceptionCode = STATUS_POSSIBLE_DEADLOCK;
448 ExceptionRecord.ExceptionFlags = 0;
449 ExceptionRecord.ExceptionRecord = NULL;
450 ExceptionRecord.ExceptionAddress = RtlRaiseException;
451 ExceptionRecord.NumberParameters = 1;
452 ExceptionRecord.ExceptionInformation[0] = (ULONG_PTR)CriticalSection;
453 RtlRaiseException(&ExceptionRecord);
454
455 }
456
457 /* One more try */
458 LastChance = TRUE;
459
460 } else {
461
462 /* If we are here, everything went fine */
463 return STATUS_SUCCESS;
464 }
465 }
466 }
467
468 /*++
469 * RtlpUnWaitCriticalSection
470 *
471 * Slow path of RtlLeaveCriticalSection. Fires an Event Object.
472 *
473 * Params:
474 * CriticalSection - Critical section to release.
475 *
476 * Returns:
477 * None. Raises an exception if the system call failed.
478 *
479 * Remarks:
480 * None
481 *
482 *--*/
483 VOID
484 STDCALL
485 RtlpUnWaitCriticalSection(
486 PRTL_CRITICAL_SECTION CriticalSection)
487
488 {
489 NTSTATUS Status;
490
491 /* Do we have an Event yet? */
492 if (!CriticalSection->LockSemaphore) {
493 RtlpCreateCriticalSectionSem(CriticalSection);
494 }
495
496 /* Signal the Event */
497 DPRINT("Signaling Critical Section Event: %x, %x\n",
498 CriticalSection,
499 CriticalSection->LockSemaphore);
500 Status = NtSetEvent(CriticalSection->LockSemaphore, NULL);
501
502 if (!NT_SUCCESS(Status)) {
503
504 /* We've failed */
505 DPRINT1("Signaling Failed for: %x, %x, %x\n",
506 CriticalSection,
507 CriticalSection->LockSemaphore,
508 Status);
509 RtlRaiseStatus(Status);
510 }
511 }
512
513 /*++
514 * RtlpCreateCriticalSectionSem
515 *
516 * Checks if an Event has been created for the critical section.
517 *
518 * Params:
519 * None
520 *
521 * Returns:
522 * None. Raises an exception if the system call failed.
523 *
524 * Remarks:
525 * None
526 *
527 *--*/
528 VOID
529 STDCALL
530 RtlpCreateCriticalSectionSem(
531 PRTL_CRITICAL_SECTION CriticalSection)
532 {
533 HANDLE hEvent = CriticalSection->LockSemaphore;
534 HANDLE hNewEvent;
535 NTSTATUS Status;
536
537 /* Chevk if we have an event */
538 if (!hEvent) {
539
540 /* No, so create it */
541 if (!NT_SUCCESS(Status = NtCreateEvent(&hNewEvent,
542 EVENT_ALL_ACCESS,
543 NULL,
544 SynchronizationEvent,
545 FALSE))) {
546
547 /* We failed, this is bad... */
548 DPRINT1("Failed to Create Event!\n");
549 InterlockedDecrement(&CriticalSection->LockCount);
550 RtlRaiseStatus(Status);
551 return;
552 }
553 DPRINT("Created Event: %x \n", hNewEvent);
554
555 if ((hEvent = InterlockedCompareExchangePointer((PVOID*)&CriticalSection->LockSemaphore,
556 (PVOID)hNewEvent,
557 0))) {
558
559 /* Some just created an event */
560 DPRINT("Closing already created event: %x\n", hNewEvent);
561 NtClose(hNewEvent);
562 }
563 }
564
565 return;
566 }
567
568 /*++
569 * RtlpInitDeferedCriticalSection
570 *
571 * Initializes the Critical Section implementation.
572 *
573 * Params:
574 * None
575 *
576 * Returns:
577 * None.
578 *
579 * Remarks:
580 * After this call, the Process Critical Section list is protected.
581 *
582 *--*/
583 VOID
584 STDCALL
585 RtlpInitDeferedCriticalSection(
586 VOID)
587 {
588
589 /* Initialize the Process Critical Section List */
590 InitializeListHead(&RtlCriticalSectionList);
591
592 /* Initialize the CS Protecting the List */
593 RtlInitializeCriticalSection(&RtlCriticalSectionLock);
594
595 /* It's now safe to enter it */
596 RtlpCritSectInitialized = TRUE;
597 }
598
599 /*++
600 * RtlpAllocateDebugInfo
601 *
602 * Finds or allocates memory for a Critical Section Debug Object
603 *
604 * Params:
605 * None
606 *
607 * Returns:
608 * A pointer to an empty Critical Section Debug Object.
609 *
610 * Remarks:
611 * For optimization purposes, the first 64 entries can be cached. From
612 * then on, future Critical Sections will allocate memory from the heap.
613 *
614 *--*/
615 PRTL_CRITICAL_SECTION_DEBUG
616 STDCALL
617 RtlpAllocateDebugInfo(
618 VOID)
619 {
620 ULONG i;
621
622 /* Try to allocate from our buffer first */
623 for (i = 0; i < MAX_STATIC_CS_DEBUG_OBJECTS; i++) {
624
625 /* Check if Entry is free */
626 if (!RtlpDebugInfoFreeList[i]) {
627
628 /* Mark entry in use */
629 DPRINT("Using entry: %d. Buffer: %x\n", i, &RtlpStaticDebugInfo[i]);
630 RtlpDebugInfoFreeList[i] = TRUE;
631
632 /* Use free entry found */
633 return &RtlpStaticDebugInfo[i];
634 }
635
636 }
637
638 /* We are out of static buffer, allocate dynamic */
639 return RtlAllocateHeap(NtCurrentPeb()->ProcessHeap,
640 0,
641 sizeof(RTL_CRITICAL_SECTION_DEBUG));
642 }
643
644 /*++
645 * RtlpFreeDebugInfo
646 *
647 * Frees the memory for a Critical Section Debug Object
648 *
649 * Params:
650 * DebugInfo - Pointer to Critical Section Debug Object to free.
651 *
652 * Returns:
653 * None.
654 *
655 * Remarks:
656 * If the pointer is part of the static buffer, then the entry is made
657 * free again. If not, the object is de-allocated from the heap.
658 *
659 *--*/
660 VOID
661 STDCALL
662 RtlpFreeDebugInfo(
663 PRTL_CRITICAL_SECTION_DEBUG DebugInfo)
664 {
665 ULONG EntryId;
666
667 /* Is it part of our cached entries? */
668 if ((DebugInfo >= RtlpStaticDebugInfo) &&
669 (DebugInfo <= &RtlpStaticDebugInfo[MAX_STATIC_CS_DEBUG_OBJECTS-1])) {
670
671 /* Yes. zero it out */
672 RtlZeroMemory(DebugInfo, sizeof(RTL_CRITICAL_SECTION_DEBUG));
673
674 /* Mark as free */
675 EntryId = (DebugInfo - RtlpStaticDebugInfo);
676 DPRINT("Freeing from Buffer: %x. Entry: %d inside Process: %x\n",
677 DebugInfo,
678 EntryId,
679 NtCurrentTeb()->Cid.UniqueProcess);
680 RtlpDebugInfoFreeList[EntryId] = FALSE;
681
682 } else {
683
684 /* It's a dynamic one, so free from the heap */
685 DPRINT("Freeing from Heap: %x inside Process: %x\n",
686 DebugInfo,
687 NtCurrentTeb()->Cid.UniqueProcess);
688 RtlFreeHeap(NtCurrentPeb()->ProcessHeap, 0, DebugInfo);
689
690 }
691 }
692
693 /* EOF */