summaryrefslogtreecommitdiff
path: root/src/vm/comwaithandle.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vm/comwaithandle.cpp')
-rw-r--r--src/vm/comwaithandle.cpp463
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