1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4//
5
6//
7
8#include "common.h"
9
10#include "corhost.h"
11#include "synch.h"
12
13void CLREventBase::CreateAutoEvent (BOOL bInitialState // If TRUE, initial state is signalled
14 )
15{
16 CONTRACTL
17 {
18 THROWS;
19 GC_NOTRIGGER;
20 SO_TOLERANT;
21 // disallow creation of Crst before EE starts
22 // Can not assert here. ASP.Net uses our Threadpool before EE is started.
23 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
24 PRECONDITION((!IsOSEvent()));
25 }
26 CONTRACTL_END;
27
28 SetAutoEvent();
29
30 {
31 HANDLE h = UnsafeCreateEvent(NULL,FALSE,bInitialState,NULL);
32 if (h == NULL) {
33 ThrowOutOfMemory();
34 }
35 m_handle = h;
36 }
37
38}
39
40BOOL CLREventBase::CreateAutoEventNoThrow (BOOL bInitialState // If TRUE, initial state is signalled
41 )
42{
43 CONTRACTL
44 {
45 NOTHROW;
46 GC_NOTRIGGER;
47 SO_TOLERANT;
48 // disallow creation of Crst before EE starts
49 // Can not assert here. ASP.Net uses our Threadpool before EE is started.
50 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
51 PRECONDITION((!IsOSEvent()));
52 }
53 CONTRACTL_END;
54
55 EX_TRY
56 {
57 CreateAutoEvent(bInitialState);
58 }
59 EX_CATCH
60 {
61 }
62 EX_END_CATCH(SwallowAllExceptions);
63
64 return IsValid();
65}
66
67void CLREventBase::CreateManualEvent (BOOL bInitialState // If TRUE, initial state is signalled
68 )
69{
70 CONTRACTL
71 {
72 THROWS;
73 GC_NOTRIGGER;
74 SO_TOLERANT;
75 // disallow creation of Crst before EE starts
76 // Can not assert here. ASP.Net uses our Threadpool before EE is started.
77 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
78 PRECONDITION((!IsOSEvent()));
79 }
80 CONTRACTL_END;
81
82 {
83 HANDLE h = UnsafeCreateEvent(NULL,TRUE,bInitialState,NULL);
84 if (h == NULL) {
85 ThrowOutOfMemory();
86 }
87 m_handle = h;
88 }
89}
90
91BOOL CLREventBase::CreateManualEventNoThrow (BOOL bInitialState // If TRUE, initial state is signalled
92 )
93{
94 CONTRACTL
95 {
96 NOTHROW;
97 GC_NOTRIGGER;
98 SO_TOLERANT;
99 // disallow creation of Crst before EE starts
100 // Can not assert here. ASP.Net uses our Threadpool before EE is started.
101 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
102 PRECONDITION((!IsOSEvent()));
103 }
104 CONTRACTL_END;
105
106 EX_TRY
107 {
108 CreateManualEvent(bInitialState);
109 }
110 EX_CATCH
111 {
112 }
113 EX_END_CATCH(SwallowAllExceptions);
114
115 return IsValid();
116}
117
118void CLREventBase::CreateMonitorEvent(SIZE_T Cookie)
119{
120 CONTRACTL
121 {
122 THROWS;
123 GC_NOTRIGGER;
124 // disallow creation of Crst before EE starts
125 PRECONDITION((g_fEEStarted));
126 PRECONDITION((GetThread() != NULL));
127 PRECONDITION((!IsOSEvent()));
128 }
129 CONTRACTL_END;
130
131 // thread-safe SetAutoEvent
132 FastInterlockOr(&m_dwFlags, CLREVENT_FLAGS_AUTO_EVENT);
133
134 {
135 HANDLE h = UnsafeCreateEvent(NULL,FALSE,FALSE,NULL);
136 if (h == NULL) {
137 ThrowOutOfMemory();
138 }
139 if (FastInterlockCompareExchangePointer(&m_handle,
140 h,
141 INVALID_HANDLE_VALUE) != INVALID_HANDLE_VALUE)
142 {
143 // We lost the race
144 CloseHandle(h);
145 }
146 }
147
148 // thread-safe SetInDeadlockDetection
149 FastInterlockOr(&m_dwFlags, CLREVENT_FLAGS_IN_DEADLOCK_DETECTION);
150
151 for (;;)
152 {
153 LONG oldFlags = m_dwFlags;
154
155 if (oldFlags & CLREVENT_FLAGS_MONITOREVENT_ALLOCATED)
156 {
157 // Other thread has set the flag already. Nothing left for us to do.
158 break;
159 }
160
161 LONG newFlags = oldFlags | CLREVENT_FLAGS_MONITOREVENT_ALLOCATED;
162 if (FastInterlockCompareExchange((LONG*)&m_dwFlags, newFlags, oldFlags) != oldFlags)
163 {
164 // We lost the race
165 continue;
166 }
167
168 // Because we set the allocated bit, we are the ones to do the signalling
169 if (oldFlags & CLREVENT_FLAGS_MONITOREVENT_SIGNALLED)
170 {
171 // We got the honour to signal the event
172 Set();
173 }
174 break;
175 }
176}
177
178
179void CLREventBase::SetMonitorEvent()
180{
181 CONTRACTL
182 {
183 NOTHROW;
184 GC_NOTRIGGER;
185 }
186 CONTRACTL_END;
187
188 // SetMonitorEvent is robust against initialization races. It is possible to
189 // call CLREvent::SetMonitorEvent on event that has not been initialialized yet by CreateMonitorEvent.
190 // CreateMonitorEvent will signal the event once it is created if it happens.
191
192 for (;;)
193 {
194 LONG oldFlags = m_dwFlags;
195
196 if (oldFlags & CLREVENT_FLAGS_MONITOREVENT_ALLOCATED)
197 {
198 // Event has been allocated already. Use the regular codepath.
199 Set();
200 break;
201 }
202
203 LONG newFlags = oldFlags | CLREVENT_FLAGS_MONITOREVENT_SIGNALLED;
204 if (FastInterlockCompareExchange((LONG*)&m_dwFlags, newFlags, oldFlags) != oldFlags)
205 {
206 // We lost the race
207 continue;
208 }
209 break;
210 }
211}
212
213
214
215void CLREventBase::CreateOSAutoEvent (BOOL bInitialState // If TRUE, initial state is signalled
216 )
217{
218 CONTRACTL
219 {
220 THROWS;
221 GC_NOTRIGGER;
222 // disallow creation of Crst before EE starts
223 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
224 }
225 CONTRACTL_END;
226
227 // Can not assert here. ASP.Net uses our Threadpool before EE is started.
228 //_ASSERTE (g_fEEStarted);
229
230 SetOSEvent();
231 SetAutoEvent();
232
233 HANDLE h = UnsafeCreateEvent(NULL,FALSE,bInitialState,NULL);
234 if (h == NULL) {
235 ThrowOutOfMemory();
236 }
237 m_handle = h;
238}
239
240BOOL CLREventBase::CreateOSAutoEventNoThrow (BOOL bInitialState // If TRUE, initial state is signalled
241 )
242{
243 CONTRACTL
244 {
245 NOTHROW;
246 GC_NOTRIGGER;
247 // disallow creation of Crst before EE starts
248 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
249 }
250 CONTRACTL_END;
251
252 EX_TRY
253 {
254 CreateOSAutoEvent(bInitialState);
255 }
256 EX_CATCH
257 {
258 }
259 EX_END_CATCH(SwallowAllExceptions);
260
261 return IsValid();
262}
263
264void CLREventBase::CreateOSManualEvent (BOOL bInitialState // If TRUE, initial state is signalled
265 )
266{
267 CONTRACTL
268 {
269 THROWS;
270 GC_NOTRIGGER;
271 // disallow creation of Crst before EE starts
272 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
273 }
274 CONTRACTL_END;
275
276 // Can not assert here. ASP.Net uses our Threadpool before EE is started.
277 //_ASSERTE (g_fEEStarted);
278
279 SetOSEvent();
280
281 HANDLE h = UnsafeCreateEvent(NULL,TRUE,bInitialState,NULL);
282 if (h == NULL) {
283 ThrowOutOfMemory();
284 }
285 m_handle = h;
286}
287
288BOOL CLREventBase::CreateOSManualEventNoThrow (BOOL bInitialState // If TRUE, initial state is signalled
289 )
290{
291 CONTRACTL
292 {
293 NOTHROW;
294 GC_NOTRIGGER;
295 // disallow creation of Crst before EE starts
296 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
297 }
298 CONTRACTL_END;
299
300 EX_TRY
301 {
302 CreateOSManualEvent(bInitialState);
303 }
304 EX_CATCH
305 {
306 }
307 EX_END_CATCH(SwallowAllExceptions);
308
309 return IsValid();
310}
311
312void CLREventBase::CloseEvent()
313{
314 CONTRACTL
315 {
316 NOTHROW;
317 if (IsInDeadlockDetection()) {GC_TRIGGERS;} else {GC_NOTRIGGER;}
318 SO_TOLERANT;
319 }
320 CONTRACTL_END;
321
322 GCX_MAYBE_PREEMP(IsInDeadlockDetection() && IsValid());
323
324 _ASSERTE(Thread::Debug_AllowCallout());
325
326 if (m_handle != INVALID_HANDLE_VALUE) {
327 {
328 CloseHandle(m_handle);
329 }
330
331 m_handle = INVALID_HANDLE_VALUE;
332 }
333 m_dwFlags = 0;
334}
335
336
337BOOL CLREventBase::Set()
338{
339 CONTRACTL
340 {
341 NOTHROW;
342 GC_NOTRIGGER;
343 SO_TOLERANT;
344 PRECONDITION((m_handle != INVALID_HANDLE_VALUE));
345 }
346 CONTRACTL_END;
347
348 _ASSERTE(Thread::Debug_AllowCallout());
349
350 {
351 return UnsafeSetEvent(m_handle);
352 }
353
354}
355
356
357BOOL CLREventBase::Reset()
358{
359 CONTRACTL
360 {
361 NOTHROW;
362 GC_NOTRIGGER;
363 SO_TOLERANT;
364 PRECONDITION((m_handle != INVALID_HANDLE_VALUE));
365 }
366 CONTRACTL_END;
367
368 _ASSERTE(Thread::Debug_AllowCallout());
369
370 // We do not allow Reset on AutoEvent
371 _ASSERTE (!IsAutoEvent() ||
372 !"Can not call Reset on AutoEvent");
373
374 {
375 return UnsafeResetEvent(m_handle);
376 }
377}
378
379
380static DWORD CLREventWaitHelper2(HANDLE handle, DWORD dwMilliseconds, BOOL alertable)
381{
382 STATIC_CONTRACT_THROWS;
383 STATIC_CONTRACT_SO_TOLERANT;
384
385 return WaitForSingleObjectEx(handle,dwMilliseconds,alertable);
386}
387
388static DWORD CLREventWaitHelper(HANDLE handle, DWORD dwMilliseconds, BOOL alertable)
389{
390 STATIC_CONTRACT_NOTHROW;
391 STATIC_CONTRACT_SO_TOLERANT;
392
393 struct Param
394 {
395 HANDLE handle;
396 DWORD dwMilliseconds;
397 BOOL alertable;
398 DWORD result;
399 } param;
400 param.handle = handle;
401 param.dwMilliseconds = dwMilliseconds;
402 param.alertable = alertable;
403 param.result = WAIT_FAILED;
404
405 // Can not use EX_TRY/CATCH. EX_CATCH toggles GC mode. This function is called
406 // through RareDisablePreemptiveGC. EX_CATCH breaks profiler callback.
407 PAL_TRY(Param *, pParam, &param)
408 {
409 // Need to move to another helper (cannot have SEH and C++ destructors
410 // on automatic variables in one function)
411 pParam->result = CLREventWaitHelper2(pParam->handle, pParam->dwMilliseconds, pParam->alertable);
412 }
413 PAL_EXCEPT (EXCEPTION_EXECUTE_HANDLER)
414 {
415 param.result = WAIT_FAILED;
416 }
417 PAL_ENDTRY;
418
419 return param.result;
420}
421
422
423DWORD CLREventBase::Wait(DWORD dwMilliseconds, BOOL alertable, PendingSync *syncState)
424{
425 WRAPPER_NO_CONTRACT;
426 return WaitEx(dwMilliseconds, alertable?WaitMode_Alertable:WaitMode_None,syncState);
427}
428
429
430DWORD CLREventBase::WaitEx(DWORD dwMilliseconds, WaitMode mode, PendingSync *syncState)
431{
432 BOOL alertable = (mode & WaitMode_Alertable)!=0;
433 CONTRACTL
434 {
435 if (alertable)
436 {
437 THROWS; // Thread::DoAppropriateWait can throw
438 }
439 else
440 {
441 NOTHROW;
442 }
443 if (GetThread())
444 {
445 if (alertable)
446 GC_TRIGGERS;
447 else
448 GC_NOTRIGGER;
449 }
450 else
451 {
452 DISABLED(GC_TRIGGERS);
453 }
454 SO_TOLERANT;
455 PRECONDITION(m_handle != INVALID_HANDLE_VALUE); // Handle has to be valid
456 }
457 CONTRACTL_END;
458
459
460 _ASSERTE(Thread::Debug_AllowCallout());
461
462 Thread * pThread = GetThread();
463
464#ifdef _DEBUG
465 // If a CLREvent is OS event only, we can not wait for the event on a managed thread
466 if (IsOSEvent())
467 _ASSERTE (pThread == NULL);
468#endif
469 _ASSERTE((pThread != NULL) || !g_fEEStarted || dbgOnly_IsSpecialEEThread());
470
471 {
472 if (pThread && alertable) {
473 DWORD dwRet = WAIT_FAILED;
474 BEGIN_SO_INTOLERANT_CODE_NOTHROW (pThread, return WAIT_FAILED;);
475 dwRet = pThread->DoAppropriateWait(1, &m_handle, FALSE, dwMilliseconds,
476 mode,
477 syncState);
478 END_SO_INTOLERANT_CODE;
479 return dwRet;
480 }
481 else {
482 _ASSERTE (syncState == NULL);
483 return CLREventWaitHelper(m_handle,dwMilliseconds,alertable);
484 }
485 }
486}
487
488void CLRSemaphore::Create (DWORD dwInitial, DWORD dwMax)
489{
490 CONTRACTL
491 {
492 THROWS;
493 GC_NOTRIGGER;
494 SO_TOLERANT;
495 PRECONDITION(m_handle == INVALID_HANDLE_VALUE);
496 }
497 CONTRACTL_END;
498
499 {
500 HANDLE h = UnsafeCreateSemaphore(NULL,dwInitial,dwMax,NULL);
501 if (h == NULL) {
502 ThrowOutOfMemory();
503 }
504 m_handle = h;
505 }
506}
507
508
509void CLRSemaphore::Close()
510{
511 LIMITED_METHOD_CONTRACT;
512
513 if (m_handle != INVALID_HANDLE_VALUE) {
514 CloseHandle(m_handle);
515 m_handle = INVALID_HANDLE_VALUE;
516 }
517}
518
519BOOL CLRSemaphore::Release(LONG lReleaseCount, LONG *lpPreviousCount)
520{
521 CONTRACTL
522 {
523 NOTHROW;
524 GC_NOTRIGGER;
525 SO_TOLERANT;
526 PRECONDITION(m_handle != INVALID_HANDLE_VALUE);
527 }
528 CONTRACTL_END;
529
530 {
531 return ::UnsafeReleaseSemaphore(m_handle, lReleaseCount, lpPreviousCount);
532 }
533}
534
535
536DWORD CLRSemaphore::Wait(DWORD dwMilliseconds, BOOL alertable)
537{
538 CONTRACTL
539 {
540 if (GetThread() && alertable)
541 {
542 THROWS; // Thread::DoAppropriateWait can throw
543 }
544 else
545 {
546 NOTHROW;
547 }
548 if (GetThread())
549 {
550 if (alertable)
551 GC_TRIGGERS;
552 else
553 GC_NOTRIGGER;
554 }
555 else
556 {
557 DISABLED(GC_TRIGGERS);
558 }
559 SO_TOLERANT;
560 PRECONDITION(m_handle != INVALID_HANDLE_VALUE); // Invalid to have invalid handle
561 }
562 CONTRACTL_END;
563
564
565 Thread *pThread = GetThread();
566 _ASSERTE (pThread || !g_fEEStarted || dbgOnly_IsSpecialEEThread());
567
568 {
569 // TODO wwl: if alertable is FALSE, do we support a host to break a deadlock?
570 // Currently we can not call through DoAppropriateWait because of CannotThrowComplusException.
571 // We should re-consider this after our code is exception safe.
572 if (pThread && alertable) {
573 return pThread->DoAppropriateWait(1, &m_handle, FALSE, dwMilliseconds,
574 alertable?WaitMode_Alertable:WaitMode_None,
575 NULL);
576 }
577 else {
578 DWORD result = WAIT_FAILED;
579 EX_TRY
580 {
581 result = WaitForSingleObjectEx(m_handle,dwMilliseconds,alertable);
582 }
583 EX_CATCH
584 {
585 result = WAIT_FAILED;
586 }
587 EX_END_CATCH(SwallowAllExceptions);
588 return result;
589 }
590 }
591}
592
593void CLRLifoSemaphore::Create(INT32 initialSignalCount, INT32 maximumSignalCount)
594{
595 CONTRACTL
596 {
597 THROWS;
598 GC_NOTRIGGER;
599 SO_TOLERANT;
600 }
601 CONTRACTL_END;
602
603 _ASSERTE(maximumSignalCount > 0);
604 _ASSERTE(initialSignalCount <= maximumSignalCount);
605 _ASSERTE(m_handle == nullptr);
606
607#ifdef FEATURE_PAL
608 HANDLE h = UnsafeCreateSemaphore(nullptr, initialSignalCount, maximumSignalCount, nullptr);
609#else // !FEATURE_PAL
610 HANDLE h = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, maximumSignalCount);
611#endif // FEATURE_PAL
612 if (h == nullptr)
613 {
614 ThrowOutOfMemory();
615 }
616
617 m_handle = h;
618 m_counts.signalCount = initialSignalCount;
619 INDEBUG(m_maximumSignalCount = maximumSignalCount);
620}
621
622void CLRLifoSemaphore::Close()
623{
624 LIMITED_METHOD_CONTRACT;
625
626 if (m_handle == nullptr)
627 {
628 return;
629 }
630
631 CloseHandle(m_handle);
632 m_handle = nullptr;
633}
634
635bool CLRLifoSemaphore::WaitForSignal(DWORD timeoutMs)
636{
637 CONTRACTL
638 {
639 NOTHROW;
640 GC_NOTRIGGER;
641 SO_TOLERANT;
642 }
643 CONTRACTL_END;
644
645 _ASSERTE(timeoutMs != 0);
646 _ASSERTE(m_handle != nullptr);
647 _ASSERTE(m_counts.VolatileLoadWithoutBarrier().waiterCount != (UINT16)0);
648
649 while (true)
650 {
651 // Wait for a signal
652 BOOL waitSuccessful;
653 {
654#ifdef FEATURE_PAL
655 // Do a prioritized wait to get LIFO waiter release order
656 DWORD waitResult = PAL_WaitForSingleObjectPrioritized(m_handle, timeoutMs);
657 _ASSERTE(waitResult == WAIT_OBJECT_0 || waitResult == WAIT_TIMEOUT);
658 waitSuccessful = waitResult == WAIT_OBJECT_0;
659#else // !FEATURE_PAL
660 // I/O completion ports release waiters in LIFO order, see
661 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365198(v=vs.85).aspx
662 DWORD numberOfBytes;
663 ULONG_PTR completionKey;
664 LPOVERLAPPED overlapped;
665 waitSuccessful = GetQueuedCompletionStatus(m_handle, &numberOfBytes, &completionKey, &overlapped, timeoutMs);
666 _ASSERTE(waitSuccessful || GetLastError() == WAIT_TIMEOUT);
667 _ASSERTE(overlapped == nullptr);
668#endif // FEATURE_PAL
669 }
670
671 if (!waitSuccessful)
672 {
673 // Unregister the waiter. The wait subsystem used above guarantees that a thread that wakes due to a timeout does
674 // not observe a signal to the object being waited upon.
675 Counts toSubtract;
676 ++toSubtract.waiterCount;
677 Counts countsBeforeUpdate = m_counts.ExchangeAdd(-toSubtract);
678 _ASSERTE(countsBeforeUpdate.waiterCount != (UINT16)0);
679 return false;
680 }
681
682 // Unregister the waiter if this thread will not be waiting anymore, and try to acquire the semaphore
683 Counts counts = m_counts.VolatileLoadWithoutBarrier();
684 while (true)
685 {
686 _ASSERTE(counts.waiterCount != (UINT16)0);
687 Counts newCounts = counts;
688 if (counts.signalCount != 0)
689 {
690 --newCounts.signalCount;
691 --newCounts.waiterCount;
692 }
693
694 // This waiter has woken up and this needs to be reflected in the count of waiters signaled to wake
695 if (counts.countOfWaitersSignaledToWake != (UINT8)0)
696 {
697 --newCounts.countOfWaitersSignaledToWake;
698 }
699
700 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
701 if (countsBeforeUpdate == counts)
702 {
703 if (counts.signalCount != 0)
704 {
705 return true;
706 }
707 break;
708 }
709
710 counts = countsBeforeUpdate;
711 }
712 }
713}
714
715bool CLRLifoSemaphore::Wait(DWORD timeoutMs)
716{
717 WRAPPER_NO_CONTRACT;
718
719 _ASSERTE(m_handle != nullptr);
720
721 // Acquire the semaphore or register as a waiter
722 Counts counts = m_counts.VolatileLoadWithoutBarrier();
723 while (true)
724 {
725 _ASSERTE(counts.signalCount <= m_maximumSignalCount);
726 Counts newCounts = counts;
727 if (counts.signalCount != 0)
728 {
729 --newCounts.signalCount;
730 }
731 else if (timeoutMs != 0)
732 {
733 ++newCounts.waiterCount;
734 _ASSERTE(newCounts.waiterCount != (UINT16)0); // overflow check, this many waiters is currently not supported
735 }
736
737 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
738 if (countsBeforeUpdate == counts)
739 {
740 return counts.signalCount != 0 || (timeoutMs != 0 && WaitForSignal(timeoutMs));
741 }
742
743 counts = countsBeforeUpdate;
744 }
745}
746
747bool CLRLifoSemaphore::Wait(DWORD timeoutMs, UINT32 spinCount, UINT32 processorCount)
748{
749 CONTRACTL
750 {
751 NOTHROW;
752 GC_NOTRIGGER;
753 SO_TOLERANT;
754 }
755 CONTRACTL_END;
756
757 _ASSERTE(m_handle != nullptr);
758
759 if (timeoutMs == 0 || spinCount == 0)
760 {
761 return Wait(timeoutMs);
762 }
763
764 // Try to acquire the semaphore or register as a spinner
765 Counts counts = m_counts.VolatileLoadWithoutBarrier();
766 while (true)
767 {
768 Counts newCounts = counts;
769 if (counts.signalCount != 0)
770 {
771 --newCounts.signalCount;
772 }
773 else
774 {
775 ++newCounts.spinnerCount;
776 if (newCounts.spinnerCount == (UINT8)0)
777 {
778 // Maximum number of spinners reached, register as a waiter instead
779 --newCounts.spinnerCount;
780 ++newCounts.waiterCount;
781 _ASSERTE(newCounts.waiterCount != (UINT16)0); // overflow check, this many waiters is currently not supported
782 }
783 }
784
785 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
786 if (countsBeforeUpdate == counts)
787 {
788 if (counts.signalCount != 0)
789 {
790 return true;
791 }
792 if (newCounts.waiterCount != counts.waiterCount)
793 {
794 return WaitForSignal(timeoutMs);
795 }
796 break;
797 }
798
799 counts = countsBeforeUpdate;
800 }
801
802#ifdef _TARGET_ARM64_
803 // For now, the spinning changes are disabled on ARM64. The spin loop below replicates how UnfairSemaphore used to spin.
804 // Once more tuning is done on ARM64, it should be possible to come up with a spinning scheme that works well everywhere.
805 int spinCountPerProcessor = spinCount;
806 for (UINT32 i = 1; ; ++i)
807 {
808 // Wait
809 ClrSleepEx(0, false);
810
811 // Try to acquire the semaphore and unregister as a spinner
812 counts = m_counts.VolatileLoadWithoutBarrier();
813 while (true)
814 {
815 _ASSERTE(counts.spinnerCount != (UINT8)0);
816 if (counts.signalCount == 0)
817 {
818 break;
819 }
820
821 Counts newCounts = counts;
822 --newCounts.signalCount;
823 --newCounts.spinnerCount;
824
825 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
826 if (countsBeforeUpdate == counts)
827 {
828 return true;
829 }
830
831 counts = countsBeforeUpdate;
832 }
833
834 // Determine whether to spin further
835 double spinnersPerProcessor = (double)counts.spinnerCount / processorCount;
836 UINT32 spinLimit = (UINT32)(spinCountPerProcessor / spinnersPerProcessor + 0.5);
837 if (i >= spinLimit)
838 {
839 break;
840 }
841 }
842#else // !_TARGET_ARM64_
843 const UINT32 Sleep0Threshold = 10;
844 YieldProcessorNormalizationInfo normalizationInfo;
845#ifdef FEATURE_PAL
846 // The PAL's wait subsystem is quite slow, spin more to compensate for the more expensive wait
847 spinCount *= 2;
848#endif // FEATURE_PAL
849 for (UINT32 i = 0; i < spinCount; ++i)
850 {
851 // Wait
852 //
853 // (i - Sleep0Threshold) % 2 != 0: The purpose of this check is to interleave Thread.Yield/Sleep(0) with
854 // Thread.SpinWait. Otherwise, the following issues occur:
855 // - When there are no threads to switch to, Yield and Sleep(0) become no-op and it turns the spin loop into a
856 // busy-spin that may quickly reach the max spin count and cause the thread to enter a wait state. Completing the
857 // spin loop too early can cause excessive context switcing from the wait.
858 // - If there are multiple threads doing Yield and Sleep(0) (typically from the same spin loop due to contention),
859 // they may switch between one another, delaying work that can make progress.
860 if (i < Sleep0Threshold || (i - Sleep0Threshold) % 2 != 0)
861 {
862 YieldProcessorWithBackOffNormalized(normalizationInfo, i);
863 }
864 else
865 {
866 // Not doing SwitchToThread(), it does not seem to have any benefit over Sleep(0)
867 ClrSleepEx(0, false);
868 }
869
870 // Try to acquire the semaphore and unregister as a spinner
871 counts = m_counts.VolatileLoadWithoutBarrier();
872 while (true)
873 {
874 _ASSERTE(counts.spinnerCount != (UINT8)0);
875 if (counts.signalCount == 0)
876 {
877 break;
878 }
879
880 Counts newCounts = counts;
881 --newCounts.signalCount;
882 --newCounts.spinnerCount;
883
884 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
885 if (countsBeforeUpdate == counts)
886 {
887 return true;
888 }
889
890 counts = countsBeforeUpdate;
891 }
892 }
893#endif // _TARGET_ARM64_
894
895 // Unregister as a spinner, and acquire the semaphore or register as a waiter
896 counts = m_counts.VolatileLoadWithoutBarrier();
897 while (true)
898 {
899 _ASSERTE(counts.spinnerCount != (UINT8)0);
900 Counts newCounts = counts;
901 --newCounts.spinnerCount;
902 if (counts.signalCount != 0)
903 {
904 --newCounts.signalCount;
905 }
906 else
907 {
908 ++newCounts.waiterCount;
909 _ASSERTE(newCounts.waiterCount != (UINT16)0); // overflow check, this many waiters is currently not supported
910 }
911
912 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
913 if (countsBeforeUpdate == counts)
914 {
915 return counts.signalCount != 0 || WaitForSignal(timeoutMs);
916 }
917
918 counts = countsBeforeUpdate;
919 }
920}
921
922void CLRLifoSemaphore::Release(INT32 releaseCount)
923{
924 CONTRACTL
925 {
926 NOTHROW;
927 GC_NOTRIGGER;
928 SO_TOLERANT;
929 }
930 CONTRACTL_END;
931
932 _ASSERTE(releaseCount > 0);
933 _ASSERTE((UINT32)releaseCount <= m_maximumSignalCount);
934 _ASSERTE(m_handle != INVALID_HANDLE_VALUE);
935
936 INT32 countOfWaitersToWake;
937 Counts counts = m_counts.VolatileLoadWithoutBarrier();
938 while (true)
939 {
940 Counts newCounts = counts;
941
942 // Increase the signal count. The addition doesn't overflow because of the limit on the max signal count in Create.
943 newCounts.signalCount += releaseCount;
944 _ASSERTE(newCounts.signalCount > counts.signalCount);
945
946 // Determine how many waiters to wake, taking into account how many spinners and waiters there are and how many waiters
947 // have previously been signaled to wake but have not yet woken
948 countOfWaitersToWake =
949 (INT32)min(newCounts.signalCount, (UINT32)newCounts.waiterCount + newCounts.spinnerCount) -
950 newCounts.spinnerCount -
951 newCounts.countOfWaitersSignaledToWake;
952 if (countOfWaitersToWake > 0)
953 {
954 // Ideally, limiting to a maximum of releaseCount would not be necessary and could be an assert instead, but since
955 // WaitForSignal() does not have enough information to tell whether a woken thread was signaled, and due to the cap
956 // below, it's possible for countOfWaitersSignaledToWake to be less than the number of threads that have actually
957 // been signaled to wake.
958 if (countOfWaitersToWake > releaseCount)
959 {
960 countOfWaitersToWake = releaseCount;
961 }
962
963 // Cap countOfWaitersSignaledToWake to its max value. It's ok to ignore some woken threads in this count, it just
964 // means some more threads will be woken next time. Typically, it won't reach the max anyway.
965 newCounts.countOfWaitersSignaledToWake += (UINT8)min(countOfWaitersToWake, (INT32)UINT8_MAX);
966 if (newCounts.countOfWaitersSignaledToWake <= counts.countOfWaitersSignaledToWake)
967 {
968 newCounts.countOfWaitersSignaledToWake = UINT8_MAX;
969 }
970 }
971
972 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
973 if (countsBeforeUpdate == counts)
974 {
975 _ASSERTE((UINT32)releaseCount <= m_maximumSignalCount - counts.signalCount);
976 if (countOfWaitersToWake <= 0)
977 {
978 return;
979 }
980 break;
981 }
982
983 counts = countsBeforeUpdate;
984 }
985
986 // Wake waiters
987#ifdef FEATURE_PAL
988 BOOL released = UnsafeReleaseSemaphore(m_handle, countOfWaitersToWake, nullptr);
989 _ASSERTE(released);
990#else // !FEATURE_PAL
991 while (--countOfWaitersToWake >= 0)
992 {
993 while (!PostQueuedCompletionStatus(m_handle, 0, 0, nullptr))
994 {
995 // Probably out of memory. It's not valid to stop and throw here, so try again after a delay.
996 ClrSleepEx(1, false);
997 }
998 }
999#endif // FEATURE_PAL
1000}
1001
1002void CLRMutex::Create(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName)
1003{
1004 CONTRACTL
1005 {
1006 THROWS;
1007 GC_NOTRIGGER;
1008 SO_TOLERANT;
1009 PRECONDITION(m_handle == INVALID_HANDLE_VALUE && m_handle != NULL);
1010 }
1011 CONTRACTL_END;
1012
1013 m_handle = WszCreateMutex(lpMutexAttributes,bInitialOwner,lpName);
1014 if (m_handle == NULL)
1015 {
1016 ThrowOutOfMemory();
1017 }
1018}
1019
1020void CLRMutex::Close()
1021{
1022 LIMITED_METHOD_CONTRACT;
1023
1024 if (m_handle != INVALID_HANDLE_VALUE)
1025 {
1026 CloseHandle(m_handle);
1027 m_handle = INVALID_HANDLE_VALUE;
1028 }
1029}
1030
1031BOOL CLRMutex::Release()
1032{
1033 CONTRACTL
1034 {
1035 NOTHROW;
1036 GC_NOTRIGGER;
1037 SO_TOLERANT;
1038 PRECONDITION(m_handle != INVALID_HANDLE_VALUE && m_handle != NULL);
1039 }
1040 CONTRACTL_END;
1041
1042 BOOL fRet = ReleaseMutex(m_handle);
1043 if (fRet)
1044 {
1045 EE_LOCK_RELEASED(this);
1046 }
1047 return fRet;
1048}
1049
1050DWORD CLRMutex::Wait(DWORD dwMilliseconds, BOOL bAlertable)
1051{
1052 CONTRACTL {
1053 NOTHROW;
1054 GC_NOTRIGGER;
1055 CAN_TAKE_LOCK;
1056 PRECONDITION(m_handle != INVALID_HANDLE_VALUE && m_handle != NULL);
1057 }
1058 CONTRACTL_END;
1059
1060 DWORD fRet = WaitForSingleObjectEx(m_handle, dwMilliseconds, bAlertable);
1061
1062 if (fRet == WAIT_OBJECT_0)
1063 {
1064 EE_LOCK_TAKEN(this);
1065 }
1066
1067 return fRet;
1068}
1069