[RTL]
[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 ULONG 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: %lu inside Process: %p\n",
314 DebugInfo,
315 EntryId,
316 NtCurrentTeb()->ClientId.UniqueProcess);
317 RtlpDebugInfoFreeList[EntryId] = FALSE;
318
319 } else {
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 }
328 }
329
330 /*++
331 * RtlDeleteCriticalSection
332 * @implemented NT4
333 *
334 * Deletes a Critical Section
335 *
336 * Params:
337 * CriticalSection - Critical section to delete.
338 *
339 * Returns:
340 * STATUS_SUCCESS, or error value returned by NtClose.
341 *
342 * Remarks:
343 * The critical section members should not be read after this call.
344 *
345 *--*/
346 NTSTATUS
347 NTAPI
348 RtlDeleteCriticalSection(PRTL_CRITICAL_SECTION CriticalSection)
349 {
350 NTSTATUS Status = STATUS_SUCCESS;
351
352 DPRINT("Deleting Critical Section: %p\n", CriticalSection);
353 /* Close the Event Object Handle if it exists */
354 if (CriticalSection->LockSemaphore) {
355
356 /* In case NtClose fails, return the status */
357 Status = NtClose(CriticalSection->LockSemaphore);
358
359 }
360
361 /* Protect List */
362 RtlEnterCriticalSection(&RtlCriticalSectionLock);
363
364 if (CriticalSection->DebugInfo)
365 {
366 /* Remove it from the list */
367 RemoveEntryList(&CriticalSection->DebugInfo->ProcessLocksList);
368 RtlZeroMemory(CriticalSection->DebugInfo, sizeof(RTL_CRITICAL_SECTION_DEBUG));
369 }
370
371 /* Unprotect */
372 RtlLeaveCriticalSection(&RtlCriticalSectionLock);
373
374 if (CriticalSection->DebugInfo)
375 {
376 /* Free it */
377 RtlpFreeDebugInfo(CriticalSection->DebugInfo);
378 }
379
380 /* Wipe it out */
381 RtlZeroMemory(CriticalSection, sizeof(RTL_CRITICAL_SECTION));
382
383 /* Return */
384 return Status;
385 }
386
387 /*++
388 * RtlSetCriticalSectionSpinCount
389 * @implemented NT4
390 *
391 * Sets the spin count for a critical section.
392 *
393 * Params:
394 * CriticalSection - Critical section to set the spin count for.
395 *
396 * SpinCount - Spin count for the critical section.
397 *
398 * Returns:
399 * STATUS_SUCCESS.
400 *
401 * Remarks:
402 * SpinCount is ignored on single-processor systems.
403 *
404 *--*/
405 DWORD
406 NTAPI
407 RtlSetCriticalSectionSpinCount(PRTL_CRITICAL_SECTION CriticalSection,
408 ULONG SpinCount)
409 {
410 ULONG OldCount = CriticalSection->SpinCount;
411
412 /* Set to parameter if MP, or to 0 if this is Uniprocessor */
413 CriticalSection->SpinCount = (NtCurrentPeb()->NumberOfProcessors > 1) ? SpinCount : 0;
414 return OldCount;
415 }
416
417 /*++
418 * RtlEnterCriticalSection
419 * @implemented NT4
420 *
421 * Waits to gain ownership of the critical section.
422 *
423 * Params:
424 * CriticalSection - Critical section to wait for.
425 *
426 * Returns:
427 * STATUS_SUCCESS.
428 *
429 * Remarks:
430 * Uses a fast-path unless contention happens.
431 *
432 *--*/
433 NTSTATUS
434 NTAPI
435 RtlEnterCriticalSection(PRTL_CRITICAL_SECTION CriticalSection)
436 {
437 HANDLE Thread = (HANDLE)NtCurrentTeb()->ClientId.UniqueThread;
438
439 /* Try to Lock it */
440 if (_InterlockedIncrement(&CriticalSection->LockCount) != 0) {
441
442 /*
443 * We've failed to lock it! Does this thread
444 * actually own it?
445 */
446 if (Thread == CriticalSection->OwningThread) {
447
448 /* You own it, so you'll get it when you're done with it! No need to
449 use the interlocked functions as only the thread who already owns
450 the lock can modify this data. */
451 CriticalSection->RecursionCount++;
452 return STATUS_SUCCESS;
453 }
454
455 /* NOTE - CriticalSection->OwningThread can be NULL here because changing
456 this information is not serialized. This happens when thread a
457 acquires the lock (LockCount == 0) and thread b tries to
458 acquire it as well (LockCount == 1) but thread a hasn't had a
459 chance to set the OwningThread! So it's not an error when
460 OwningThread is NULL here! */
461
462 /* We don't own it, so we must wait for it */
463 RtlpWaitForCriticalSection(CriticalSection);
464 }
465
466 /* Lock successful. Changing this information has not to be serialized because
467 only one thread at a time can actually change it (the one who acquired
468 the lock)! */
469 CriticalSection->OwningThread = Thread;
470 CriticalSection->RecursionCount = 1;
471 return STATUS_SUCCESS;
472 }
473
474 /*++
475 * RtlInitializeCriticalSection
476 * @implemented NT4
477 *
478 * Initialises a new critical section.
479 *
480 * Params:
481 * CriticalSection - Critical section to initialise
482 *
483 * Returns:
484 * STATUS_SUCCESS.
485 *
486 * Remarks:
487 * Simply calls RtlInitializeCriticalSectionAndSpinCount
488 *
489 *--*/
490 NTSTATUS
491 NTAPI
492 RtlInitializeCriticalSection(PRTL_CRITICAL_SECTION CriticalSection)
493 {
494 /* Call the Main Function */
495 return RtlInitializeCriticalSectionAndSpinCount(CriticalSection, 0);
496 }
497
498 /*++
499 * RtlInitializeCriticalSectionAndSpinCount
500 * @implemented NT4
501 *
502 * Initialises a new critical section.
503 *
504 * Params:
505 * CriticalSection - Critical section to initialise
506 *
507 * SpinCount - Spin count for the critical section.
508 *
509 * Returns:
510 * STATUS_SUCCESS.
511 *
512 * Remarks:
513 * SpinCount is ignored on single-processor systems.
514 *
515 *--*/
516 NTSTATUS
517 NTAPI
518 RtlInitializeCriticalSectionAndSpinCount(PRTL_CRITICAL_SECTION CriticalSection,
519 ULONG SpinCount)
520 {
521 PRTL_CRITICAL_SECTION_DEBUG CritcalSectionDebugData;
522
523 /* First things first, set up the Object */
524 DPRINT("Initializing Critical Section: %p\n", CriticalSection);
525 CriticalSection->LockCount = -1;
526 CriticalSection->RecursionCount = 0;
527 CriticalSection->OwningThread = 0;
528 CriticalSection->SpinCount = (NtCurrentPeb()->NumberOfProcessors > 1) ? SpinCount : 0;
529 CriticalSection->LockSemaphore = 0;
530
531 /* Allocate the Debug Data */
532 CritcalSectionDebugData = RtlpAllocateDebugInfo();
533 DPRINT("Allocated Debug Data: %p inside Process: %p\n",
534 CritcalSectionDebugData,
535 NtCurrentTeb()->ClientId.UniqueProcess);
536
537 if (!CritcalSectionDebugData) {
538
539 /* This is bad! */
540 DPRINT1("Couldn't allocate Debug Data for: %p\n", CriticalSection);
541 return STATUS_NO_MEMORY;
542 }
543
544 /* Set it up */
545 CritcalSectionDebugData->Type = RTL_CRITSECT_TYPE;
546 CritcalSectionDebugData->ContentionCount = 0;
547 CritcalSectionDebugData->EntryCount = 0;
548 CritcalSectionDebugData->CriticalSection = CriticalSection;
549 CriticalSection->DebugInfo = CritcalSectionDebugData;
550
551 /*
552 * Add it to the List of Critical Sections owned by the process.
553 * If we've initialized the Lock, then use it. If not, then probably
554 * this is the lock initialization itself, so insert it directly.
555 */
556 if ((CriticalSection != &RtlCriticalSectionLock) && (RtlpCritSectInitialized)) {
557
558 DPRINT("Securely Inserting into ProcessLocks: %p, %p, %p\n",
559 &CritcalSectionDebugData->ProcessLocksList,
560 CriticalSection,
561 &RtlCriticalSectionList);
562
563 /* Protect List */
564 RtlEnterCriticalSection(&RtlCriticalSectionLock);
565
566 /* Add this one */
567 InsertTailList(&RtlCriticalSectionList, &CritcalSectionDebugData->ProcessLocksList);
568
569 /* Unprotect */
570 RtlLeaveCriticalSection(&RtlCriticalSectionLock);
571
572 } else {
573
574 DPRINT("Inserting into ProcessLocks: %p, %p, %p\n",
575 &CritcalSectionDebugData->ProcessLocksList,
576 CriticalSection,
577 &RtlCriticalSectionList);
578
579 /* Add it directly */
580 InsertTailList(&RtlCriticalSectionList, &CritcalSectionDebugData->ProcessLocksList);
581 }
582
583 return STATUS_SUCCESS;
584 }
585
586 /*++
587 * RtlLeaveCriticalSection
588 * @implemented NT4
589 *
590 * Releases a critical section and makes if available for new owners.
591 *
592 * Params:
593 * CriticalSection - Critical section to release.
594 *
595 * Returns:
596 * STATUS_SUCCESS.
597 *
598 * Remarks:
599 * If another thread was waiting, the slow path is entered.
600 *
601 *--*/
602 NTSTATUS
603 NTAPI
604 RtlLeaveCriticalSection(PRTL_CRITICAL_SECTION CriticalSection)
605 {
606 #ifndef NDEBUG
607 HANDLE Thread = (HANDLE)NtCurrentTeb()->Cid.UniqueThread;
608
609 /* In win this case isn't checked. However it's a valid check so it should only
610 be performed in debug builds! */
611 if (Thread != CriticalSection->OwningThread)
612 {
613 DPRINT1("Releasing critical section not owned!\n");
614 return STATUS_INVALID_PARAMETER;
615 }
616 #endif
617
618 /* Decrease the Recursion Count. No need to do this atomically because only
619 the thread who holds the lock can call this function (unless the program
620 is totally screwed... */
621 if (--CriticalSection->RecursionCount) {
622
623 /* Someone still owns us, but we are free. This needs to be done atomically. */
624 _InterlockedDecrement(&CriticalSection->LockCount);
625
626 } else {
627
628 /* Nobody owns us anymore. No need to do this atomically. See comment
629 above. */
630 CriticalSection->OwningThread = 0;
631
632 /* Was someone wanting us? This needs to be done atomically. */
633 if (-1 != _InterlockedDecrement(&CriticalSection->LockCount)) {
634
635 /* Let him have us */
636 RtlpUnWaitCriticalSection(CriticalSection);
637 }
638 }
639
640 /* Sucessful! */
641 return STATUS_SUCCESS;
642 }
643
644 /*++
645 * RtlTryEnterCriticalSection
646 * @implemented NT4
647 *
648 * Attemps to gain ownership of the critical section without waiting.
649 *
650 * Params:
651 * CriticalSection - Critical section to attempt acquiring.
652 *
653 * Returns:
654 * TRUE if the critical section has been acquired, FALSE otherwise.
655 *
656 * Remarks:
657 * None
658 *
659 *--*/
660 BOOLEAN
661 NTAPI
662 RtlTryEnterCriticalSection(PRTL_CRITICAL_SECTION CriticalSection)
663 {
664 /* Try to take control */
665 if (_InterlockedCompareExchange(&CriticalSection->LockCount,
666 0,
667 -1) == -1) {
668
669 /* It's ours */
670 CriticalSection->OwningThread = NtCurrentTeb()->ClientId.UniqueThread;
671 CriticalSection->RecursionCount = 1;
672 return TRUE;
673
674 } else if (CriticalSection->OwningThread == NtCurrentTeb()->ClientId.UniqueThread) {
675
676 /* It's already ours */
677 _InterlockedIncrement(&CriticalSection->LockCount);
678 CriticalSection->RecursionCount++;
679 return TRUE;
680 }
681
682 /* It's not ours */
683 return FALSE;
684 }
685
686 /* EOF */