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** COMWaitHandle.cpp
9**
10** Purpose: Native methods on System.WaitHandle
11**
12**
13===========================================================*/
14#include "common.h"
15#include "object.h"
16#include "field.h"
17#include "excep.h"
18#include "comwaithandle.h"
19
20
21//-----------------------------------------------------------------------------
22// ObjArrayHolder : ideal for holding a managed array of items. Will run
23// the ACQUIRE method sequentially on each item. Assume the ACQUIRE method
24// may possibly fail. If it does, only release the ones we've acquired.
25// Note: If a GC occurs during the ACQUIRE or RELEASE methods, you'll have to
26// explicitly gc protect the objectref.
27//-----------------------------------------------------------------------------
28template <typename TYPE, void (*ACQUIRE)(TYPE), void (*RELEASEF)(TYPE)>
29class ObjArrayHolder
30{
31
32public:
33 ObjArrayHolder() {
34 LIMITED_METHOD_CONTRACT;
35 m_numAcquired = 0;
36 m_pValues = NULL;
37 }
38
39 // Assuming ACQUIRE can throw an exception, we must put this logic
40 // somewhere outside of the constructor. In C++, the destructor won't be
41 // run if the constructor didn't complete.
42 void Initialize(const unsigned int numElements, PTRARRAYREF* pValues) {
43 WRAPPER_NO_CONTRACT;
44 _ASSERTE(m_numAcquired == 0);
45 m_numElements = numElements;
46 m_pValues = pValues;
47 for (unsigned int i=0; i<m_numElements; i++) {
48 TYPE value = (TYPE) (*m_pValues)->GetAt(i);
49 ACQUIRE(value);
50 m_numAcquired++;
51 }
52 }
53
54 ~ObjArrayHolder() {
55 WRAPPER_NO_CONTRACT;
56
57 GCX_COOP();
58 for (unsigned int i=0; i<m_numAcquired; i++) {
59 TYPE value = (TYPE) (*m_pValues)->GetAt(i);
60 RELEASEF(value);
61 }
62 }
63
64private:
65 unsigned int m_numElements;
66 unsigned int m_numAcquired;
67 PTRARRAYREF* m_pValues;
68
69 FORCEINLINE ObjArrayHolder<TYPE, ACQUIRE, RELEASEF> &operator=(const ObjArrayHolder<TYPE, ACQUIRE, RELEASEF> &holder)
70 {
71 _ASSERTE(!"No assignment allowed");
72 return NULL;
73 }
74
75 FORCEINLINE ObjArrayHolder(const ObjArrayHolder<TYPE, ACQUIRE, RELEASEF> &holder)
76 {
77 _ASSERTE(!"No copy construction allowed");
78 }
79};
80
81void AcquireSafeHandleFromWaitHandle(WAITHANDLEREF wh)
82{
83 CONTRACTL {
84 THROWS;
85 GC_TRIGGERS;
86 SO_INTOLERANT;
87 MODE_COOPERATIVE;
88 PRECONDITION(wh != NULL);
89 } CONTRACTL_END;
90
91 SAFEHANDLEREF sh = wh->GetSafeHandle();
92 if (sh == NULL)
93 COMPlusThrow(kObjectDisposedException);
94 sh->AddRef();
95}
96
97void ReleaseSafeHandleFromWaitHandle(WAITHANDLEREF wh)
98{
99 CONTRACTL {
100 THROWS;
101 GC_TRIGGERS;
102 SO_TOLERANT;
103 MODE_COOPERATIVE;
104 PRECONDITION(wh != NULL);
105 } CONTRACTL_END;
106
107 SAFEHANDLEREF sh = wh->GetSafeHandle();
108 _ASSERTE(sh);
109 sh->Release();
110}
111
112typedef ObjArrayHolder<WAITHANDLEREF, AcquireSafeHandleFromWaitHandle, ReleaseSafeHandleFromWaitHandle> WaitHandleArrayHolder;
113
114INT64 AdditionalWait(INT64 sPauseTime, INT64 sTime, INT64 expDuration)
115{
116 LIMITED_METHOD_CONTRACT;
117
118 _ASSERTE(g_PauseTime >= sPauseTime);
119
120 INT64 pauseTime = g_PauseTime - sPauseTime;
121 // No pause was used inbetween this handle
122 if(pauseTime <= 0)
123 return 0;
124
125 INT64 actDuration = CLRGetTickCount64() - sTime;
126
127 // In case the CLR is paused inbetween a wait, this method calculates how much
128 // the wait has to be adjusted to account for the CLR Freeze. Essentially all
129 // pause duration has to be considered as "time that never existed".
130 //
131 // Two cases exists, consider that 10 sec wait is issued
132 // Case 1: All pauses happened before the wait completes. Hence just the
133 // pause time needs to be added back at the end of wait
134 // 0 3 8 10
135 // |-----------|###################|------>
136 // 5-sec pause
137 // ....................>
138 // Additional 5 sec wait
139 // |=========================>
140 //
141 // Case 2: Pauses ended after the wait completes.
142 // 3 second of wait was left as the pause started at 7 so need to add that back
143 // 0 7 10
144 // |---------------------------|###########>
145 // 5-sec pause 12
146 // ...................>
147 // Additional 3 sec wait
148 // |==================>
149 //
150 // Both cases can be expressed in the same calculation
151 // pauseTime: sum of all pauses that were triggered after the timer was started
152 // expDuration: expected duration of the wait (without any pauses) 10 in the example
153 // actDuration: time when the wait finished. Since the CLR is frozen during pause it's
154 // max of timeout or pause-end. In case-1 it's 10, in case-2 it's 12
155 INT64 additional = expDuration - (actDuration - pauseTime);
156 if(additional < 0)
157 additional = 0;
158
159 return additional;
160}
161
162FCIMPL4(INT32, WaitHandleNative::CorWaitOneNative, SafeHandle* safeWaitHandleUNSAFE, INT32 timeout, CLR_BOOL hasThreadAffinity, CLR_BOOL exitContext)
163{
164 FCALL_CONTRACT;
165
166 INT32 retVal = 0;
167 SAFEHANDLEREF sh(safeWaitHandleUNSAFE);
168 HELPER_METHOD_FRAME_BEGIN_RET_1(sh);
169
170 _ASSERTE(sh != NULL);
171
172 Thread* pThread = GET_THREAD();
173
174 DWORD res = (DWORD) -1;
175
176 SafeHandleHolder shh(&sh);
177 // Note that SafeHandle is a GC object, and RequestCallback and
178 // DoAppropriateWait work on an array of handles. Don't pass the address
179 // of the handle field - that's a GC hole. Instead, pass this temp
180 // array.
181 HANDLE handles[1];
182 handles[0] = sh->GetHandle();
183 {
184 // Support for pause/resume (FXFREEZE)
185 while(true)
186 {
187 INT64 sPauseTime = g_PauseTime;
188 INT64 sTime = CLRGetTickCount64();
189 res = pThread->DoAppropriateWait(1,handles,TRUE,timeout, WaitMode_Alertable /*alertable*/);
190 if(res != WAIT_TIMEOUT)
191 break;
192 timeout = (INT32)AdditionalWait(sPauseTime, sTime, timeout);
193 if(timeout == 0)
194 break;
195 }
196 }
197
198 retVal = res;
199
200
201 HELPER_METHOD_FRAME_END();
202 return retVal;
203}
204FCIMPLEND
205
206FCIMPL4(INT32, WaitHandleNative::CorWaitMultipleNative, Object* waitObjectsUNSAFE, INT32 timeout, CLR_BOOL exitContext, CLR_BOOL waitForAll)
207{
208 FCALL_CONTRACT;
209
210 INT32 retVal = 0;
211 OBJECTREF waitObjects = (OBJECTREF) waitObjectsUNSAFE;
212 HELPER_METHOD_FRAME_BEGIN_RET_1(waitObjects);
213
214 _ASSERTE(waitObjects);
215
216 Thread* pThread = GET_THREAD();
217
218 PTRARRAYREF pWaitObjects = (PTRARRAYREF)waitObjects; // array of objects on which to wait
219 int numWaiters = pWaitObjects->GetNumComponents();
220
221#ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT
222 // There are some issues with wait-all from an STA thread
223 // - https://github.com/dotnet/coreclr/issues/17787#issuecomment-385117537
224 if (waitForAll && numWaiters > 1 && pThread->GetApartment() == Thread::AS_InSTA)
225 {
226 COMPlusThrow(kNotSupportedException, W("NotSupported_WaitAllSTAThread"));
227 }
228#endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT
229
230 WaitHandleArrayHolder arrayHolder;
231 arrayHolder.Initialize(numWaiters, (PTRARRAYREF*) &waitObjects);
232
233 pWaitObjects = (PTRARRAYREF)waitObjects; // array of objects on which to wait
234 HANDLE* internalHandles = (HANDLE*) _alloca(numWaiters*sizeof(HANDLE));
235 for (int i=0;i<numWaiters;i++)
236 {
237 WAITHANDLEREF waitObject = (WAITHANDLEREF) pWaitObjects->m_Array[i];
238 _ASSERTE(waitObject != NULL);
239
240 //If the size of the array is 1 and m_handle is INVALID_HANDLE then WaitForMultipleObjectsEx will
241 // return ERROR_INVALID_HANDLE but DoAppropriateWait will convert to WAIT_OBJECT_0. i.e Success,
242 // this behavior seems wrong but someone explicitly coded that condition so it must have been for a reason.
243 internalHandles[i] = waitObject->m_handle;
244
245 }
246
247 DWORD res = (DWORD) -1;
248 {
249 // Support for pause/resume (FXFREEZE)
250 while(true)
251 {
252 INT64 sPauseTime = g_PauseTime;
253 INT64 sTime = CLRGetTickCount64();
254 res = pThread->DoAppropriateWait(numWaiters, internalHandles, waitForAll, timeout, WaitMode_Alertable /*alertable*/);
255 if(res != WAIT_TIMEOUT)
256 break;
257 timeout = (INT32)AdditionalWait(sPauseTime, sTime, timeout);
258 if(timeout == 0)
259 break;
260 }
261 }
262
263
264 retVal = res;
265
266 HELPER_METHOD_FRAME_END();
267 return retVal;
268}
269FCIMPLEND
270
271FCIMPL5(INT32, WaitHandleNative::CorSignalAndWaitOneNative, SafeHandle* safeWaitHandleSignalUNSAFE,SafeHandle* safeWaitHandleWaitUNSAFE, INT32 timeout, CLR_BOOL hasThreadAffinity, CLR_BOOL exitContext)
272{
273 FCALL_CONTRACT;
274
275 INT32 retVal = 0;
276 SAFEHANDLEREF shSignal(safeWaitHandleSignalUNSAFE);
277 SAFEHANDLEREF shWait(safeWaitHandleWaitUNSAFE);
278
279 HELPER_METHOD_FRAME_BEGIN_RET_2(shSignal,shWait);
280
281 if(shSignal == NULL || shWait == NULL)
282 COMPlusThrow(kObjectDisposedException);
283
284 _ASSERTE(safeWaitHandleSignalUNSAFE != NULL);
285 _ASSERTE( safeWaitHandleWaitUNSAFE != NULL);
286
287
288 Thread* pThread = GET_THREAD();
289
290#ifdef FEATURE_COMINTEROP
291 if (pThread->GetApartment() == Thread::AS_InSTA) {
292 COMPlusThrow(kNotSupportedException, W("NotSupported_SignalAndWaitSTAThread")); //<TODO> Change this message
293 }
294#endif
295
296 DWORD res = (DWORD) -1;
297
298 SafeHandleHolder shhSignal(&shSignal);
299 SafeHandleHolder shhWait(&shWait);
300 // Don't pass the address of the handle field
301 // - that's a GC hole. Instead, pass this temp array.
302 HANDLE handles[2];
303 handles[0] = shSignal->GetHandle();
304 handles[1] = shWait->GetHandle();
305 {
306 res = pThread->DoSignalAndWait(handles,timeout,TRUE /*alertable*/);
307 }
308
309
310 retVal = res;
311
312 HELPER_METHOD_FRAME_END();
313 return retVal;
314}
315FCIMPLEND
316