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.
13 void CLREventBase::CreateAutoEvent (BOOL bInitialState // If TRUE, initial state is signalled
20 // disallow creation of Crst before EE starts
21 // Can not assert here. ASP.NET uses our Threadpool before EE is started.
22 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
23 PRECONDITION((!IsOSEvent()));
30 HANDLE h = WszCreateEvent(NULL,FALSE,bInitialState,NULL);
39 BOOL CLREventBase::CreateAutoEventNoThrow (BOOL bInitialState // If TRUE, initial state is signalled
46 // disallow creation of Crst before EE starts
47 // Can not assert here. ASP.NET uses our Threadpool before EE is started.
48 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
49 PRECONDITION((!IsOSEvent()));
55 CreateAutoEvent(bInitialState);
60 EX_END_CATCH(SwallowAllExceptions);
65 void CLREventBase::CreateManualEvent (BOOL bInitialState // If TRUE, initial state is signalled
72 // disallow creation of Crst before EE starts
73 // Can not assert here. ASP.NET uses our Threadpool before EE is started.
74 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
75 PRECONDITION((!IsOSEvent()));
80 HANDLE h = WszCreateEvent(NULL,TRUE,bInitialState,NULL);
88 BOOL CLREventBase::CreateManualEventNoThrow (BOOL bInitialState // If TRUE, initial state is signalled
95 // disallow creation of Crst before EE starts
96 // Can not assert here. ASP.NET uses our Threadpool before EE is started.
97 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
98 PRECONDITION((!IsOSEvent()));
104 CreateManualEvent(bInitialState);
109 EX_END_CATCH(SwallowAllExceptions);
114 void CLREventBase::CreateMonitorEvent(SIZE_T Cookie)
120 // disallow creation of Crst before EE starts
121 PRECONDITION((g_fEEStarted));
122 PRECONDITION((GetThread() != NULL));
123 PRECONDITION((!IsOSEvent()));
127 // thread-safe SetAutoEvent
128 FastInterlockOr(&m_dwFlags, CLREVENT_FLAGS_AUTO_EVENT);
131 HANDLE h = WszCreateEvent(NULL,FALSE,FALSE,NULL);
135 if (FastInterlockCompareExchangePointer(&m_handle,
137 INVALID_HANDLE_VALUE) != INVALID_HANDLE_VALUE)
144 // thread-safe SetInDeadlockDetection
145 FastInterlockOr(&m_dwFlags, CLREVENT_FLAGS_IN_DEADLOCK_DETECTION);
149 LONG oldFlags = m_dwFlags;
151 if (oldFlags & CLREVENT_FLAGS_MONITOREVENT_ALLOCATED)
153 // Other thread has set the flag already. Nothing left for us to do.
157 LONG newFlags = oldFlags | CLREVENT_FLAGS_MONITOREVENT_ALLOCATED;
158 if (FastInterlockCompareExchange((LONG*)&m_dwFlags, newFlags, oldFlags) != oldFlags)
164 // Because we set the allocated bit, we are the ones to do the signalling
165 if (oldFlags & CLREVENT_FLAGS_MONITOREVENT_SIGNALLED)
167 // We got the honour to signal the event
175 void CLREventBase::SetMonitorEvent()
184 // SetMonitorEvent is robust against initialization races. It is possible to
185 // call CLREvent::SetMonitorEvent on event that has not been initialialized yet by CreateMonitorEvent.
186 // CreateMonitorEvent will signal the event once it is created if it happens.
190 LONG oldFlags = m_dwFlags;
192 if (oldFlags & CLREVENT_FLAGS_MONITOREVENT_ALLOCATED)
194 // Event has been allocated already. Use the regular codepath.
199 LONG newFlags = oldFlags | CLREVENT_FLAGS_MONITOREVENT_SIGNALLED;
200 if (FastInterlockCompareExchange((LONG*)&m_dwFlags, newFlags, oldFlags) != oldFlags)
211 void CLREventBase::CreateOSAutoEvent (BOOL bInitialState // If TRUE, initial state is signalled
218 // disallow creation of Crst before EE starts
219 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
223 // Can not assert here. ASP.NET uses our Threadpool before EE is started.
224 //_ASSERTE (g_fEEStarted);
229 HANDLE h = WszCreateEvent(NULL,FALSE,bInitialState,NULL);
236 BOOL CLREventBase::CreateOSAutoEventNoThrow (BOOL bInitialState // If TRUE, initial state is signalled
243 // disallow creation of Crst before EE starts
244 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
250 CreateOSAutoEvent(bInitialState);
255 EX_END_CATCH(SwallowAllExceptions);
260 void CLREventBase::CreateOSManualEvent (BOOL bInitialState // If TRUE, initial state is signalled
267 // disallow creation of Crst before EE starts
268 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
272 // Can not assert here. ASP.NET uses our Threadpool before EE is started.
273 //_ASSERTE (g_fEEStarted);
277 HANDLE h = WszCreateEvent(NULL,TRUE,bInitialState,NULL);
284 BOOL CLREventBase::CreateOSManualEventNoThrow (BOOL bInitialState // If TRUE, initial state is signalled
291 // disallow creation of Crst before EE starts
292 PRECONDITION((m_handle == INVALID_HANDLE_VALUE));
298 CreateOSManualEvent(bInitialState);
303 EX_END_CATCH(SwallowAllExceptions);
308 void CLREventBase::CloseEvent()
313 if (IsInDeadlockDetection()) {GC_TRIGGERS;} else {GC_NOTRIGGER;}
317 GCX_MAYBE_PREEMP(IsInDeadlockDetection() && IsValid());
319 _ASSERTE(Thread::Debug_AllowCallout());
321 if (m_handle != INVALID_HANDLE_VALUE) {
323 CloseHandle(m_handle);
326 m_handle = INVALID_HANDLE_VALUE;
332 BOOL CLREventBase::Set()
338 PRECONDITION((m_handle != INVALID_HANDLE_VALUE));
342 _ASSERTE(Thread::Debug_AllowCallout());
345 return SetEvent(m_handle);
351 BOOL CLREventBase::Reset()
357 PRECONDITION((m_handle != INVALID_HANDLE_VALUE));
361 _ASSERTE(Thread::Debug_AllowCallout());
363 // We do not allow Reset on AutoEvent
364 _ASSERTE (!IsAutoEvent() ||
365 !"Can not call Reset on AutoEvent");
368 return ResetEvent(m_handle);
373 static DWORD CLREventWaitHelper2(HANDLE handle, DWORD dwMilliseconds, BOOL alertable)
375 STATIC_CONTRACT_THROWS;
377 return WaitForSingleObjectEx(handle,dwMilliseconds,alertable);
380 static DWORD CLREventWaitHelper(HANDLE handle, DWORD dwMilliseconds, BOOL alertable)
382 STATIC_CONTRACT_NOTHROW;
387 DWORD dwMilliseconds;
391 param.handle = handle;
392 param.dwMilliseconds = dwMilliseconds;
393 param.alertable = alertable;
394 param.result = WAIT_FAILED;
396 // Can not use EX_TRY/CATCH. EX_CATCH toggles GC mode. This function is called
397 // through RareDisablePreemptiveGC. EX_CATCH breaks profiler callback.
398 PAL_TRY(Param *, pParam, ¶m)
400 // Need to move to another helper (cannot have SEH and C++ destructors
401 // on automatic variables in one function)
402 pParam->result = CLREventWaitHelper2(pParam->handle, pParam->dwMilliseconds, pParam->alertable);
404 PAL_EXCEPT (EXCEPTION_EXECUTE_HANDLER)
406 param.result = WAIT_FAILED;
414 DWORD CLREventBase::Wait(DWORD dwMilliseconds, BOOL alertable, PendingSync *syncState)
417 return WaitEx(dwMilliseconds, alertable?WaitMode_Alertable:WaitMode_None,syncState);
421 DWORD CLREventBase::WaitEx(DWORD dwMilliseconds, WaitMode mode, PendingSync *syncState)
423 BOOL alertable = (mode & WaitMode_Alertable)!=0;
428 THROWS; // Thread::DoAppropriateWait can throw
443 DISABLED(GC_TRIGGERS);
445 PRECONDITION(m_handle != INVALID_HANDLE_VALUE); // Handle has to be valid
450 _ASSERTE(Thread::Debug_AllowCallout());
452 Thread * pThread = GetThread();
455 // If a CLREvent is OS event only, we can not wait for the event on a managed thread
457 _ASSERTE (pThread == NULL);
459 _ASSERTE((pThread != NULL) || !g_fEEStarted || dbgOnly_IsSpecialEEThread());
462 if (pThread && alertable) {
463 DWORD dwRet = WAIT_FAILED;
464 dwRet = pThread->DoAppropriateWait(1, &m_handle, FALSE, dwMilliseconds,
470 _ASSERTE (syncState == NULL);
471 return CLREventWaitHelper(m_handle,dwMilliseconds,alertable);
476 void CLRSemaphore::Create (DWORD dwInitial, DWORD dwMax)
482 PRECONDITION(m_handle == INVALID_HANDLE_VALUE);
487 HANDLE h = WszCreateSemaphore(NULL,dwInitial,dwMax,NULL);
496 void CLRSemaphore::Close()
498 LIMITED_METHOD_CONTRACT;
500 if (m_handle != INVALID_HANDLE_VALUE) {
501 CloseHandle(m_handle);
502 m_handle = INVALID_HANDLE_VALUE;
506 BOOL CLRSemaphore::Release(LONG lReleaseCount, LONG *lpPreviousCount)
512 PRECONDITION(m_handle != INVALID_HANDLE_VALUE);
517 return ::ReleaseSemaphore(m_handle, lReleaseCount, lpPreviousCount);
522 DWORD CLRSemaphore::Wait(DWORD dwMilliseconds, BOOL alertable)
526 if (GetThread() && alertable)
528 THROWS; // Thread::DoAppropriateWait can throw
543 DISABLED(GC_TRIGGERS);
545 PRECONDITION(m_handle != INVALID_HANDLE_VALUE); // Invalid to have invalid handle
550 Thread *pThread = GetThread();
551 _ASSERTE (pThread || !g_fEEStarted || dbgOnly_IsSpecialEEThread());
554 // TODO wwl: if alertable is FALSE, do we support a host to break a deadlock?
555 // Currently we can not call through DoAppropriateWait because of CannotThrowComplusException.
556 // We should re-consider this after our code is exception safe.
557 if (pThread && alertable) {
558 return pThread->DoAppropriateWait(1, &m_handle, FALSE, dwMilliseconds,
559 alertable?WaitMode_Alertable:WaitMode_None,
563 DWORD result = WAIT_FAILED;
566 result = WaitForSingleObjectEx(m_handle,dwMilliseconds,alertable);
570 result = WAIT_FAILED;
572 EX_END_CATCH(SwallowAllExceptions);
578 void CLRLifoSemaphore::Create(INT32 initialSignalCount, INT32 maximumSignalCount)
587 _ASSERTE(maximumSignalCount > 0);
588 _ASSERTE(initialSignalCount <= maximumSignalCount);
589 _ASSERTE(m_handle == nullptr);
592 HANDLE h = WszCreateSemaphore(nullptr, 0, maximumSignalCount, nullptr);
593 #else // !FEATURE_PAL
594 HANDLE h = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, maximumSignalCount);
595 #endif // FEATURE_PAL
602 m_counts.signalCount = initialSignalCount;
603 INDEBUG(m_maximumSignalCount = maximumSignalCount);
606 void CLRLifoSemaphore::Close()
608 LIMITED_METHOD_CONTRACT;
610 if (m_handle == nullptr)
615 CloseHandle(m_handle);
619 bool CLRLifoSemaphore::WaitForSignal(DWORD timeoutMs)
628 _ASSERTE(timeoutMs != 0);
629 _ASSERTE(m_handle != nullptr);
630 _ASSERTE(m_counts.VolatileLoadWithoutBarrier().waiterCount != (UINT16)0);
638 // Do a prioritized wait to get LIFO waiter release order
639 DWORD waitResult = PAL_WaitForSingleObjectPrioritized(m_handle, timeoutMs);
640 _ASSERTE(waitResult == WAIT_OBJECT_0 || waitResult == WAIT_TIMEOUT);
641 waitSuccessful = waitResult == WAIT_OBJECT_0;
642 #else // !FEATURE_PAL
643 // I/O completion ports release waiters in LIFO order, see
644 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365198(v=vs.85).aspx
646 ULONG_PTR completionKey;
647 LPOVERLAPPED overlapped;
648 waitSuccessful = GetQueuedCompletionStatus(m_handle, &numberOfBytes, &completionKey, &overlapped, timeoutMs);
649 _ASSERTE(waitSuccessful || GetLastError() == WAIT_TIMEOUT);
650 _ASSERTE(overlapped == nullptr);
651 #endif // FEATURE_PAL
656 // Unregister the waiter. The wait subsystem used above guarantees that a thread that wakes due to a timeout does
657 // not observe a signal to the object being waited upon.
659 ++toSubtract.waiterCount;
660 Counts countsBeforeUpdate = m_counts.ExchangeAdd(-toSubtract);
661 _ASSERTE(countsBeforeUpdate.waiterCount != (UINT16)0);
665 // Unregister the waiter if this thread will not be waiting anymore, and try to acquire the semaphore
666 Counts counts = m_counts.VolatileLoadWithoutBarrier();
669 _ASSERTE(counts.waiterCount != (UINT16)0);
670 Counts newCounts = counts;
671 if (counts.signalCount != 0)
673 --newCounts.signalCount;
674 --newCounts.waiterCount;
677 // This waiter has woken up and this needs to be reflected in the count of waiters signaled to wake
678 if (counts.countOfWaitersSignaledToWake != (UINT8)0)
680 --newCounts.countOfWaitersSignaledToWake;
683 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
684 if (countsBeforeUpdate == counts)
686 if (counts.signalCount != 0)
693 counts = countsBeforeUpdate;
698 bool CLRLifoSemaphore::Wait(DWORD timeoutMs)
702 _ASSERTE(m_handle != nullptr);
704 // Acquire the semaphore or register as a waiter
705 Counts counts = m_counts.VolatileLoadWithoutBarrier();
708 _ASSERTE(counts.signalCount <= m_maximumSignalCount);
709 Counts newCounts = counts;
710 if (counts.signalCount != 0)
712 --newCounts.signalCount;
714 else if (timeoutMs != 0)
716 ++newCounts.waiterCount;
717 _ASSERTE(newCounts.waiterCount != (UINT16)0); // overflow check, this many waiters is currently not supported
720 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
721 if (countsBeforeUpdate == counts)
723 return counts.signalCount != 0 || (timeoutMs != 0 && WaitForSignal(timeoutMs));
726 counts = countsBeforeUpdate;
730 bool CLRLifoSemaphore::Wait(DWORD timeoutMs, UINT32 spinCount, UINT32 processorCount)
739 _ASSERTE(m_handle != nullptr);
741 if (timeoutMs == 0 || spinCount == 0)
743 return Wait(timeoutMs);
746 // Try to acquire the semaphore or register as a spinner
747 Counts counts = m_counts.VolatileLoadWithoutBarrier();
750 Counts newCounts = counts;
751 if (counts.signalCount != 0)
753 --newCounts.signalCount;
757 ++newCounts.spinnerCount;
758 if (newCounts.spinnerCount == (UINT8)0)
760 // Maximum number of spinners reached, register as a waiter instead
761 --newCounts.spinnerCount;
762 ++newCounts.waiterCount;
763 _ASSERTE(newCounts.waiterCount != (UINT16)0); // overflow check, this many waiters is currently not supported
767 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
768 if (countsBeforeUpdate == counts)
770 if (counts.signalCount != 0)
774 if (newCounts.waiterCount != counts.waiterCount)
776 return WaitForSignal(timeoutMs);
781 counts = countsBeforeUpdate;
784 #ifdef _TARGET_ARM64_
785 // For now, the spinning changes are disabled on ARM64. The spin loop below replicates how UnfairSemaphore used to spin.
786 // Once more tuning is done on ARM64, it should be possible to come up with a spinning scheme that works well everywhere.
787 int spinCountPerProcessor = spinCount;
788 for (UINT32 i = 1; ; ++i)
791 ClrSleepEx(0, false);
793 // Try to acquire the semaphore and unregister as a spinner
794 counts = m_counts.VolatileLoadWithoutBarrier();
797 _ASSERTE(counts.spinnerCount != (UINT8)0);
798 if (counts.signalCount == 0)
803 Counts newCounts = counts;
804 --newCounts.signalCount;
805 --newCounts.spinnerCount;
807 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
808 if (countsBeforeUpdate == counts)
813 counts = countsBeforeUpdate;
816 // Determine whether to spin further
817 double spinnersPerProcessor = (double)counts.spinnerCount / processorCount;
818 UINT32 spinLimit = (UINT32)(spinCountPerProcessor / spinnersPerProcessor + 0.5);
824 #else // !_TARGET_ARM64_
825 const UINT32 Sleep0Threshold = 10;
826 YieldProcessorNormalizationInfo normalizationInfo;
828 // The PAL's wait subsystem is quite slow, spin more to compensate for the more expensive wait
830 #endif // FEATURE_PAL
831 for (UINT32 i = 0; i < spinCount; ++i)
835 // (i - Sleep0Threshold) % 2 != 0: The purpose of this check is to interleave Thread.Yield/Sleep(0) with
836 // Thread.SpinWait. Otherwise, the following issues occur:
837 // - When there are no threads to switch to, Yield and Sleep(0) become no-op and it turns the spin loop into a
838 // busy-spin that may quickly reach the max spin count and cause the thread to enter a wait state. Completing the
839 // spin loop too early can cause excessive context switcing from the wait.
840 // - If there are multiple threads doing Yield and Sleep(0) (typically from the same spin loop due to contention),
841 // they may switch between one another, delaying work that can make progress.
842 if (i < Sleep0Threshold || (i - Sleep0Threshold) % 2 != 0)
844 YieldProcessorWithBackOffNormalized(normalizationInfo, i);
848 // Not doing SwitchToThread(), it does not seem to have any benefit over Sleep(0)
849 ClrSleepEx(0, false);
852 // Try to acquire the semaphore and unregister as a spinner
853 counts = m_counts.VolatileLoadWithoutBarrier();
856 _ASSERTE(counts.spinnerCount != (UINT8)0);
857 if (counts.signalCount == 0)
862 Counts newCounts = counts;
863 --newCounts.signalCount;
864 --newCounts.spinnerCount;
866 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
867 if (countsBeforeUpdate == counts)
872 counts = countsBeforeUpdate;
875 #endif // _TARGET_ARM64_
877 // Unregister as a spinner, and acquire the semaphore or register as a waiter
878 counts = m_counts.VolatileLoadWithoutBarrier();
881 _ASSERTE(counts.spinnerCount != (UINT8)0);
882 Counts newCounts = counts;
883 --newCounts.spinnerCount;
884 if (counts.signalCount != 0)
886 --newCounts.signalCount;
890 ++newCounts.waiterCount;
891 _ASSERTE(newCounts.waiterCount != (UINT16)0); // overflow check, this many waiters is currently not supported
894 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
895 if (countsBeforeUpdate == counts)
897 return counts.signalCount != 0 || WaitForSignal(timeoutMs);
900 counts = countsBeforeUpdate;
904 void CLRLifoSemaphore::Release(INT32 releaseCount)
913 _ASSERTE(releaseCount > 0);
914 _ASSERTE((UINT32)releaseCount <= m_maximumSignalCount);
915 _ASSERTE(m_handle != INVALID_HANDLE_VALUE);
917 INT32 countOfWaitersToWake;
918 Counts counts = m_counts.VolatileLoadWithoutBarrier();
921 Counts newCounts = counts;
923 // Increase the signal count. The addition doesn't overflow because of the limit on the max signal count in Create.
924 newCounts.signalCount += releaseCount;
925 _ASSERTE(newCounts.signalCount > counts.signalCount);
927 // Determine how many waiters to wake, taking into account how many spinners and waiters there are and how many waiters
928 // have previously been signaled to wake but have not yet woken
929 countOfWaitersToWake =
930 (INT32)min(newCounts.signalCount, (UINT32)newCounts.waiterCount + newCounts.spinnerCount) -
931 newCounts.spinnerCount -
932 newCounts.countOfWaitersSignaledToWake;
933 if (countOfWaitersToWake > 0)
935 // Ideally, limiting to a maximum of releaseCount would not be necessary and could be an assert instead, but since
936 // WaitForSignal() does not have enough information to tell whether a woken thread was signaled, and due to the cap
937 // below, it's possible for countOfWaitersSignaledToWake to be less than the number of threads that have actually
938 // been signaled to wake.
939 if (countOfWaitersToWake > releaseCount)
941 countOfWaitersToWake = releaseCount;
944 // Cap countOfWaitersSignaledToWake to its max value. It's ok to ignore some woken threads in this count, it just
945 // means some more threads will be woken next time. Typically, it won't reach the max anyway.
946 newCounts.countOfWaitersSignaledToWake += (UINT8)min(countOfWaitersToWake, (INT32)UINT8_MAX);
947 if (newCounts.countOfWaitersSignaledToWake <= counts.countOfWaitersSignaledToWake)
949 newCounts.countOfWaitersSignaledToWake = UINT8_MAX;
953 Counts countsBeforeUpdate = m_counts.CompareExchange(newCounts, counts);
954 if (countsBeforeUpdate == counts)
956 _ASSERTE((UINT32)releaseCount <= m_maximumSignalCount - counts.signalCount);
957 if (countOfWaitersToWake <= 0)
964 counts = countsBeforeUpdate;
969 BOOL released = ReleaseSemaphore(m_handle, countOfWaitersToWake, nullptr);
971 #else // !FEATURE_PAL
972 while (--countOfWaitersToWake >= 0)
974 while (!PostQueuedCompletionStatus(m_handle, 0, 0, nullptr))
976 // Probably out of memory. It's not valid to stop and throw here, so try again after a delay.
977 ClrSleepEx(1, false);
980 #endif // FEATURE_PAL
983 void CLRMutex::Create(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName)
989 PRECONDITION(m_handle == INVALID_HANDLE_VALUE && m_handle != NULL);
993 m_handle = WszCreateMutex(lpMutexAttributes,bInitialOwner,lpName);
994 if (m_handle == NULL)
1000 void CLRMutex::Close()
1002 LIMITED_METHOD_CONTRACT;
1004 if (m_handle != INVALID_HANDLE_VALUE)
1006 CloseHandle(m_handle);
1007 m_handle = INVALID_HANDLE_VALUE;
1011 BOOL CLRMutex::Release()
1017 PRECONDITION(m_handle != INVALID_HANDLE_VALUE && m_handle != NULL);
1021 BOOL fRet = ReleaseMutex(m_handle);
1024 EE_LOCK_RELEASED(this);
1029 DWORD CLRMutex::Wait(DWORD dwMilliseconds, BOOL bAlertable)
1035 PRECONDITION(m_handle != INVALID_HANDLE_VALUE && m_handle != NULL);
1039 DWORD fRet = WaitForSingleObjectEx(m_handle, dwMilliseconds, bAlertable);
1041 if (fRet == WAIT_OBJECT_0)
1043 EE_LOCK_TAKEN(this);