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