| 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 | //----------------------------------------------------------------------------- |
| 28 | template <typename TYPE, void (*ACQUIRE)(TYPE), void (*RELEASEF)(TYPE)> |
| 29 | class ObjArrayHolder |
| 30 | { |
| 31 | |
| 32 | public: |
| 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 | |
| 64 | private: |
| 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 | |
| 81 | void 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 | |
| 97 | void 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 | |
| 112 | typedef ObjArrayHolder<WAITHANDLEREF, AcquireSafeHandleFromWaitHandle, ReleaseSafeHandleFromWaitHandle> WaitHandleArrayHolder; |
| 113 | |
| 114 | INT64 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 | |
| 162 | FCIMPL4(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 | } |
| 204 | FCIMPLEND |
| 205 | |
| 206 | FCIMPL4(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 | } |
| 269 | FCIMPLEND |
| 270 | |
| 271 | FCIMPL5(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 | } |
| 315 | FCIMPLEND |
| 316 | |