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 | |
13 | void 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 | |
40 | BOOL 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 | |
67 | void 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 | |
91 | BOOL 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 | |
118 | void 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 | |
179 | void 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 | |
215 | void 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 | |
240 | BOOL 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 | |
264 | void 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 | |
288 | BOOL 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 | |
312 | void 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 | |
337 | BOOL 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 | |
357 | BOOL 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 | |
380 | static 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 | |
388 | static 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, ¶m) |
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 | |
423 | DWORD 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 | |
430 | DWORD 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 | |
488 | void 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 | |
509 | void 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 | |
519 | BOOL 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 | |
536 | DWORD 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 | |
593 | void 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 | |
622 | void 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 | |
635 | bool 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 | |
715 | bool 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 | |
747 | bool 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 | |
922 | void 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 | |
1002 | void 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 | |
1020 | void 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 | |
1031 | BOOL 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 | |
1050 | DWORD 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 | |