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