diff options
Diffstat (limited to 'src/vm/comwaithandle.cpp')
-rw-r--r-- | src/vm/comwaithandle.cpp | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/src/vm/comwaithandle.cpp b/src/vm/comwaithandle.cpp new file mode 100644 index 0000000000..13886c3de8 --- /dev/null +++ b/src/vm/comwaithandle.cpp @@ -0,0 +1,463 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + +/*============================================================ +** +** COMWaitHandle.cpp +** +** Purpose: Native methods on System.WaitHandle +** +** +===========================================================*/ +#include "common.h" +#include "object.h" +#include "field.h" +#include "excep.h" +#include "comwaithandle.h" + + +//----------------------------------------------------------------------------- +// ObjArrayHolder : ideal for holding a managed array of items. Will run +// the ACQUIRE method sequentially on each item. Assume the ACQUIRE method +// may possibly fail. If it does, only release the ones we've acquired. +// Note: If a GC occurs during the ACQUIRE or RELEASE methods, you'll have to +// explicitly gc protect the objectref. +//----------------------------------------------------------------------------- +template <typename TYPE, void (*ACQUIRE)(TYPE), void (*RELEASEF)(TYPE)> +class ObjArrayHolder +{ + +public: + ObjArrayHolder() { + LIMITED_METHOD_CONTRACT; + m_numAcquired = 0; + m_pValues = NULL; + } + + // Assuming ACQUIRE can throw an exception, we must put this logic + // somewhere outside of the constructor. In C++, the destructor won't be + // run if the constructor didn't complete. + void Initialize(const unsigned int numElements, PTRARRAYREF* pValues) { + WRAPPER_NO_CONTRACT; + _ASSERTE(m_numAcquired == 0); + m_numElements = numElements; + m_pValues = pValues; + for (unsigned int i=0; i<m_numElements; i++) { + TYPE value = (TYPE) (*m_pValues)->GetAt(i); + ACQUIRE(value); + m_numAcquired++; + } + } + + ~ObjArrayHolder() { + WRAPPER_NO_CONTRACT; + + GCX_COOP(); + for (unsigned int i=0; i<m_numAcquired; i++) { + TYPE value = (TYPE) (*m_pValues)->GetAt(i); + RELEASEF(value); + } + } + +private: + unsigned int m_numElements; + unsigned int m_numAcquired; + PTRARRAYREF* m_pValues; + + FORCEINLINE ObjArrayHolder<TYPE, ACQUIRE, RELEASEF> &operator=(const ObjArrayHolder<TYPE, ACQUIRE, RELEASEF> &holder) + { + _ASSERTE(!"No assignment allowed"); + return NULL; + } + + FORCEINLINE ObjArrayHolder(const ObjArrayHolder<TYPE, ACQUIRE, RELEASEF> &holder) + { + _ASSERTE(!"No copy construction allowed"); + } +}; + +void AcquireSafeHandleFromWaitHandle(WAITHANDLEREF wh) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + SO_INTOLERANT; + MODE_COOPERATIVE; + PRECONDITION(wh != NULL); + } CONTRACTL_END; + + _ASSERTE(!wh->IsTransparentProxy()); + + SAFEHANDLEREF sh = wh->GetSafeHandle(); + if (sh == NULL) + COMPlusThrow(kObjectDisposedException); + sh->AddRef(); +} + +void ReleaseSafeHandleFromWaitHandle(WAITHANDLEREF wh) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + SO_TOLERANT; + MODE_COOPERATIVE; + PRECONDITION(wh != NULL); + } CONTRACTL_END; + + SAFEHANDLEREF sh = wh->GetSafeHandle(); + _ASSERTE(sh); + sh->Release(); +} + +typedef ObjArrayHolder<WAITHANDLEREF, AcquireSafeHandleFromWaitHandle, ReleaseSafeHandleFromWaitHandle> WaitHandleArrayHolder; + +INT64 AdditionalWait(INT64 sPauseTime, INT64 sTime, INT64 expDuration) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(g_PauseTime >= sPauseTime); + + INT64 pauseTime = g_PauseTime - sPauseTime; + // No pause was used inbetween this handle + if(pauseTime <= 0) + return 0; + + INT64 actDuration = CLRGetTickCount64() - sTime; + + // In case the CLR is paused inbetween a wait, this method calculates how much + // the wait has to be adjusted to account for the CLR Freeze. Essentially all + // pause duration has to be considered as "time that never existed". + // + // Two cases exists, consider that 10 sec wait is issued + // Case 1: All pauses happened before the wait completes. Hence just the + // pause time needs to be added back at the end of wait + // 0 3 8 10 + // |-----------|###################|------> + // 5-sec pause + // ....................> + // Additional 5 sec wait + // |=========================> + // + // Case 2: Pauses ended after the wait completes. + // 3 second of wait was left as the pause started at 7 so need to add that back + // 0 7 10 + // |---------------------------|###########> + // 5-sec pause 12 + // ...................> + // Additional 3 sec wait + // |==================> + // + // Both cases can be expressed in the same calculation + // pauseTime: sum of all pauses that were triggered after the timer was started + // expDuration: expected duration of the wait (without any pauses) 10 in the example + // actDuration: time when the wait finished. Since the CLR is frozen during pause it's + // max of timeout or pause-end. In case-1 it's 10, in case-2 it's 12 + INT64 additional = expDuration - (actDuration - pauseTime); + if(additional < 0) + additional = 0; + + return additional; +} + +FCIMPL4(INT32, WaitHandleNative::CorWaitOneNative, SafeHandle* safeWaitHandleUNSAFE, INT32 timeout, CLR_BOOL hasThreadAffinity, CLR_BOOL exitContext) +{ + FCALL_CONTRACT; + + INT32 retVal = 0; + SAFEHANDLEREF sh(safeWaitHandleUNSAFE); + HELPER_METHOD_FRAME_BEGIN_RET_1(sh); + + _ASSERTE(sh != NULL); + + Thread* pThread = GET_THREAD(); + + DWORD res = (DWORD) -1; + + Context* targetContext; + targetContext = pThread->GetContext(); + _ASSERTE(targetContext); + Context* defaultContext; + defaultContext = pThread->GetDomain()->GetDefaultContext(); + _ASSERTE(defaultContext); +#ifndef FEATURE_CORECLR + // DoAppropriateWait calls LeaveRuntime/EnterRuntime which may cause the current + // fiber to be re-scheduled. + ThreadAffinityAndCriticalRegionHolder affinityAndCriticalRegionHolder(hasThreadAffinity); +#endif + SafeHandleHolder shh(&sh); + // Note that SafeHandle is a GC object, and RequestCallback and + // DoAppropriateWait work on an array of handles. Don't pass the address + // of the handle field - that's a GC hole. Instead, pass this temp + // array. + HANDLE handles[1]; + handles[0] = sh->GetHandle(); +#ifdef FEATURE_REMOTING + if (exitContext != NULL && + targetContext != defaultContext) + { + Context::WaitArgs waitOneArgs = {1, handles, TRUE, timeout, TRUE, &res}; + Context::CallBackInfo callBackInfo = {Context::Wait_callback, (void*) &waitOneArgs}; + Context::RequestCallBack(CURRENT_APPDOMAIN_ID,defaultContext, &callBackInfo); + } + else +#else + _ASSERTE(exitContext == NULL || targetContext == defaultContext); +#endif + { + // Support for pause/resume (FXFREEZE) + while(true) + { + INT64 sPauseTime = g_PauseTime; + INT64 sTime = CLRGetTickCount64(); + res = pThread->DoAppropriateWait(1,handles,TRUE,timeout, WaitMode_Alertable /*alertable*/); + if(res != WAIT_TIMEOUT) + break; + timeout = (INT32)AdditionalWait(sPauseTime, sTime, timeout); + if(timeout == 0) + break; + } + } + + retVal = res; + +#ifndef FEATURE_CORECLR + if (res == WAIT_OBJECT_0 && hasThreadAffinity) { + affinityAndCriticalRegionHolder.SuppressRelease(); + } + else if(res == WAIT_ABANDONED_0) { + // WAIT_ABANDONED means the specified object is a mutex object that was not released by the thread + // that owned the mutex object before the owning thread terminated. + // Ownership of the mutex object is granted to the calling thread, and the mutex is set to nonsignaled. + _ASSERTE(hasThreadAffinity); + affinityAndCriticalRegionHolder.SuppressRelease(); + } +#endif + + HELPER_METHOD_FRAME_END(); + return retVal; +} +FCIMPLEND + +FCIMPL4(INT32, WaitHandleNative::CorWaitMultipleNative, Object* waitObjectsUNSAFE, INT32 timeout, CLR_BOOL exitContext, CLR_BOOL waitForAll) +{ + FCALL_CONTRACT; + + INT32 retVal = 0; + OBJECTREF waitObjects = (OBJECTREF) waitObjectsUNSAFE; + HELPER_METHOD_FRAME_BEGIN_RET_1(waitObjects); + + _ASSERTE(waitObjects); + + Thread* pThread = GET_THREAD(); + + PTRARRAYREF pWaitObjects = (PTRARRAYREF)waitObjects; // array of objects on which to wait + int numWaiters = pWaitObjects->GetNumComponents(); + + // Note: this should really be FEATURE_COMINTEROP_APARTMENT_SUPPORT. + // Because it's not, CoreCLR will allow WaitAll on STA threads. + // But fixing this would be a breaking change at this point, since we already shipped + // SL 2 and 3 this way. + // We we'll also check for FEATURE_CORECLR here, so that if we enable FEATURE_COMINTEROP + // on CoreCLR we won't break anyone. + // Perhaps in a future release we can fix this, if we aren't quite so concerned about + // compatibility.... +#if defined(FEATURE_COMINTEROP) && !defined(FEATURE_CORECLR) + if (waitForAll && numWaiters > 1 && pThread->GetApartment() == Thread::AS_InSTA) { + COMPlusThrow(kNotSupportedException, W("NotSupported_WaitAllSTAThread")); + } +#endif // FEATURE_COMINTEROP && !FEATURE_CORECLR + + WaitHandleArrayHolder arrayHolder; + arrayHolder.Initialize(numWaiters, (PTRARRAYREF*) &waitObjects); + + pWaitObjects = (PTRARRAYREF)waitObjects; // array of objects on which to wait + HANDLE* internalHandles = (HANDLE*) _alloca(numWaiters*sizeof(HANDLE)); +#ifndef FEATURE_CORECLR + BOOL *hasThreadAffinity = (BOOL*) _alloca(numWaiters*sizeof(BOOL)); + + BOOL mayRequireThreadAffinity = FALSE; +#endif // !FEATURE_CORECLR + for (int i=0;i<numWaiters;i++) + { + WAITHANDLEREF waitObject = (WAITHANDLEREF) pWaitObjects->m_Array[i]; + _ASSERTE(waitObject != NULL); + + //If the size of the array is 1 and m_handle is INVALID_HANDLE then WaitForMultipleObjectsEx will + // return ERROR_INVALID_HANDLE but DoAppropriateWait will convert to WAIT_OBJECT_0. i.e Success, + // this behavior seems wrong but someone explicitly coded that condition so it must have been for a reason. + internalHandles[i] = waitObject->m_handle; + +#ifndef FEATURE_CORECLR + // m_hasThreadAffinity is set for Mutex only + hasThreadAffinity[i] = waitObject->m_hasThreadAffinity; + if (hasThreadAffinity[i]) { + mayRequireThreadAffinity = TRUE; + } +#endif // !FEATURE_CORECLR + } + + DWORD res = (DWORD) -1; +#ifndef FEATURE_CORECLR + ThreadAffinityHolder affinityHolder(mayRequireThreadAffinity); +#endif // !FEATURE_CORECLR + Context* targetContext; + targetContext = pThread->GetContext(); + _ASSERTE(targetContext); + Context* defaultContext; + defaultContext = pThread->GetDomain()->GetDefaultContext(); + _ASSERTE(defaultContext); +#ifdef FEATURE_REMOTING + if (exitContext != NULL && + targetContext != defaultContext) + { + Context::WaitArgs waitMultipleArgs = {numWaiters, internalHandles, waitForAll, timeout, TRUE, &res}; + Context::CallBackInfo callBackInfo = {Context::Wait_callback, (void*) &waitMultipleArgs}; + Context::RequestCallBack(CURRENT_APPDOMAIN_ID,defaultContext, &callBackInfo); + } + else +#else + _ASSERTE(exitContext == NULL || targetContext == defaultContext); +#endif + { + // Support for pause/resume (FXFREEZE) + while(true) + { + INT64 sPauseTime = g_PauseTime; + INT64 sTime = CLRGetTickCount64(); + res = pThread->DoAppropriateWait(numWaiters, internalHandles, waitForAll, timeout, WaitMode_Alertable /*alertable*/); + if(res != WAIT_TIMEOUT) + break; + timeout = (INT32)AdditionalWait(sPauseTime, sTime, timeout); + if(timeout == 0) + break; + } + } + +#ifndef FEATURE_CORECLR + if (mayRequireThreadAffinity) { + if (waitForAll) { + if (res >= (DWORD) WAIT_OBJECT_0 && res < (DWORD) WAIT_OBJECT_0 + numWaiters) { + for (int i = 0; i < numWaiters; i ++) { + if (hasThreadAffinity[i]) { + Thread::BeginThreadAffinityAndCriticalRegion(); + } + } + } + // If some mutex is abandoned + else if (res >= (DWORD) WAIT_ABANDONED_0 && res < (DWORD) WAIT_ABANDONED_0+numWaiters) { + for (int i = 0; i < numWaiters; i ++) { + if (hasThreadAffinity[i]) + { + if (WaitForSingleObject(internalHandles[i],0) == WAIT_OBJECT_0) + { + BOOL result; + result = ReleaseMutex(internalHandles[i]); + _ASSERTE (result); + Thread::BeginThreadAffinityAndCriticalRegion(); + } + } + } + } + } + else { + if ( res >= (DWORD)WAIT_OBJECT_0 && res < (DWORD)WAIT_OBJECT_0 + numWaiters) { + if (hasThreadAffinity[res - WAIT_OBJECT_0]) { + Thread::BeginThreadAffinityAndCriticalRegion(); + } + } + else if (res >= (DWORD)WAIT_ABANDONED_0 && res < (DWORD)WAIT_ABANDONED_0 + numWaiters) { + _ASSERTE (hasThreadAffinity[res - WAIT_ABANDONED_0]); + Thread::BeginThreadAffinityAndCriticalRegion(); + } + } + } +#endif // !FEATURE_CORECLR + + retVal = res; + + HELPER_METHOD_FRAME_END(); + return retVal; +} +FCIMPLEND + +#ifndef FEATURE_PAL +FCIMPL5(INT32, WaitHandleNative::CorSignalAndWaitOneNative, SafeHandle* safeWaitHandleSignalUNSAFE,SafeHandle* safeWaitHandleWaitUNSAFE, INT32 timeout, CLR_BOOL hasThreadAffinity, CLR_BOOL exitContext) +{ + FCALL_CONTRACT; + + INT32 retVal = 0; + SAFEHANDLEREF shSignal(safeWaitHandleSignalUNSAFE); + SAFEHANDLEREF shWait(safeWaitHandleWaitUNSAFE); + + HELPER_METHOD_FRAME_BEGIN_RET_2(shSignal,shWait); + + if(shSignal == NULL || shWait == NULL) + COMPlusThrow(kObjectDisposedException); + + _ASSERTE(safeWaitHandleSignalUNSAFE != NULL); + _ASSERTE( safeWaitHandleWaitUNSAFE != NULL); + + + Thread* pThread = GET_THREAD(); + +#ifdef FEATURE_COMINTEROP + if (pThread->GetApartment() == Thread::AS_InSTA) { + COMPlusThrow(kNotSupportedException, W("NotSupported_SignalAndWaitSTAThread")); //<TODO> Change this message + } +#endif + + DWORD res = (DWORD) -1; + + Context* targetContext = pThread->GetContext(); + _ASSERTE(targetContext); + Context* defaultContext = pThread->GetDomain()->GetDefaultContext(); + _ASSERTE(defaultContext); + +#ifndef FEATURE_CORECLR + // DoSignalAndWait calls LeaveRuntime/EnterRuntime which may cause the current + // fiber to be re-scheduled. + ThreadAffinityAndCriticalRegionHolder affinityAndCriticalRegionHolder(hasThreadAffinity); +#endif // !FEATURE_CORECLR + + SafeHandleHolder shhSignal(&shSignal); + SafeHandleHolder shhWait(&shWait); + // Don't pass the address of the handle field + // - that's a GC hole. Instead, pass this temp array. + HANDLE handles[2]; + handles[0] = shSignal->GetHandle(); + handles[1] = shWait->GetHandle(); +#ifdef FEATURE_REMOTING + if (exitContext != NULL && + targetContext != defaultContext) + { + Context::SignalAndWaitArgs signalAndWaitArgs = {handles, timeout, TRUE, &res}; + Context::CallBackInfo callBackInfo = {Context::SignalAndWait_callback, (void*) &signalAndWaitArgs}; + Context::RequestCallBack(CURRENT_APPDOMAIN_ID,defaultContext, &callBackInfo); + } + else +#else + _ASSERTE(exitContext == NULL || targetContext == defaultContext); +#endif + { + res = pThread->DoSignalAndWait(handles,timeout,TRUE /*alertable*/); + } + +#ifndef FEATURE_CORECLR + if (res == WAIT_OBJECT_0 && hasThreadAffinity) { + affinityAndCriticalRegionHolder.SuppressRelease(); + } + else if(res == WAIT_ABANDONED_0) { + _ASSERTE(hasThreadAffinity); + affinityAndCriticalRegionHolder.SuppressRelease(); + } +#endif // !FEATURE_CORECLR + + retVal = res; + + HELPER_METHOD_FRAME_END(); + return retVal; +} +FCIMPLEND +#endif // !FEATURE_PAL |