// 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. /*============================================================ ** ** Header: COMSynchronizable.cpp ** ** Purpose: Native methods on System.SynchronizableObject ** and its subclasses. ** ** ===========================================================*/ #include "common.h" #include #include "threads.h" #include "excep.h" #include "vars.hpp" #include "field.h" #include "security.h" #include "comsynchronizable.h" #include "dbginterface.h" #include "comdelegate.h" #ifdef FEATURE_REMOTING #include "remoting.h" #endif #include "eeconfig.h" #include "stackcompressor.h" #ifdef FEATURE_REMOTING #include "appdomainhelper.h" #include "objectclone.h" #else #include "callhelpers.h" #endif #include "appdomain.hpp" #include "appdomain.inl" #ifdef FEATURE_REMOTING #include "crossdomaincalls.h" #endif #include "newapis.h" // To include definition of CAPTURE_BUCKETS_AT_TRANSITION #include "exstate.h" // The two threads need to communicate some information. Any object references must // be declared to GC. struct SharedState { OBJECTHANDLE m_Threadable; OBJECTHANDLE m_ThreadStartArg; Thread *m_Internal; OBJECTHANDLE m_Principal; SharedState(OBJECTREF threadable, OBJECTREF threadStartArg, Thread *internal, OBJECTREF principal) { CONTRACTL { GC_NOTRIGGER; THROWS; // From CreateHandle() MODE_COOPERATIVE; } CONTRACTL_END; AppDomainFromIDHolder ad(internal->GetKickOffDomainId(), TRUE); if (ad.IsUnloaded()) COMPlusThrow(kAppDomainUnloadedException); m_Threadable = ad->CreateHandle(threadable); m_ThreadStartArg = ad->CreateHandle(threadStartArg); m_Internal = internal; m_Principal = ad->CreateHandle(principal); } ~SharedState() { CONTRACTL { GC_NOTRIGGER; NOTHROW; MODE_COOPERATIVE; } CONTRACTL_END; // It's important to have no GC rendez-vous point between the checking and the clean-up below. // The three handles below could be in an appdomain which is just starting to be unloaded, or an appdomain // which has been unloaded already. Thus, we need to check whether the appdomain is still valid before // we do the clean-up. Since we suspend all runtime threads when we try to do the unload, there will be no // race condition between the checking and the clean-up as long as this thread cannot be suspended in between. AppDomainFromIDHolder ad(m_Internal->GetKickOffDomainId(), TRUE); if (!ad.IsUnloaded()) { DestroyHandle(m_Threadable); DestroyHandle(m_ThreadStartArg); DestroyHandle(m_Principal); } } }; // For the following helpers, we make no attempt to synchronize. The app developer // is responsible for managing his own race conditions. // // Note: if the internal Thread is NULL, this implies that the exposed object has // finalized and then been resurrected. static inline BOOL ThreadNotStarted(Thread *t) { WRAPPER_NO_CONTRACT; return (t && t->IsUnstarted() && !t->HasValidThreadHandle()); } static inline BOOL ThreadIsRunning(Thread *t) { WRAPPER_NO_CONTRACT; #ifdef FEATURE_INCLUDE_ALL_INTERFACES return (t && (t->m_State & (Thread::TS_ReportDead|Thread::TS_Dead)) == 0 && (CLRTaskHosted()? t->GetHostTask()!=NULL:t->HasValidThreadHandle())); #else // !FEATURE_INCLUDE_ALL_INTERFACES return (t && (t->m_State & (Thread::TS_ReportDead|Thread::TS_Dead)) == 0 && (t->HasValidThreadHandle())); #endif // FEATURE_INCLUDE_ALL_INTERFACES } static inline BOOL ThreadIsDead(Thread *t) { WRAPPER_NO_CONTRACT; return (t == 0 || t->IsDead()); } // Map our exposed notion of thread priorities into the enumeration that NT uses. static INT32 MapToNTPriority(INT32 ours) { CONTRACTL { GC_NOTRIGGER; THROWS; MODE_ANY; } CONTRACTL_END; INT32 NTPriority = 0; switch (ours) { case ThreadNative::PRIORITY_LOWEST: NTPriority = THREAD_PRIORITY_LOWEST; break; case ThreadNative::PRIORITY_BELOW_NORMAL: NTPriority = THREAD_PRIORITY_BELOW_NORMAL; break; case ThreadNative::PRIORITY_NORMAL: NTPriority = THREAD_PRIORITY_NORMAL; break; case ThreadNative::PRIORITY_ABOVE_NORMAL: NTPriority = THREAD_PRIORITY_ABOVE_NORMAL; break; case ThreadNative::PRIORITY_HIGHEST: NTPriority = THREAD_PRIORITY_HIGHEST; break; default: COMPlusThrow(kArgumentOutOfRangeException, W("Argument_InvalidFlag")); } return NTPriority; } // Map to our exposed notion of thread priorities from the enumeration that NT uses. INT32 MapFromNTPriority(INT32 NTPriority) { LIMITED_METHOD_CONTRACT; INT32 ours = 0; if (NTPriority <= THREAD_PRIORITY_LOWEST) { // managed code does not support IDLE. Map it to PRIORITY_LOWEST. ours = ThreadNative::PRIORITY_LOWEST; } else if (NTPriority >= THREAD_PRIORITY_HIGHEST) { ours = ThreadNative::PRIORITY_HIGHEST; } else if (NTPriority == THREAD_PRIORITY_BELOW_NORMAL) { ours = ThreadNative::PRIORITY_BELOW_NORMAL; } else if (NTPriority == THREAD_PRIORITY_NORMAL) { ours = ThreadNative::PRIORITY_NORMAL; } else if (NTPriority == THREAD_PRIORITY_ABOVE_NORMAL) { ours = ThreadNative::PRIORITY_ABOVE_NORMAL; } else { _ASSERTE (!"not supported priority"); ours = ThreadNative::PRIORITY_NORMAL; } return ours; } void ThreadNative::KickOffThread_Worker(LPVOID ptr) { CONTRACTL { GC_TRIGGERS; THROWS; MODE_COOPERATIVE; SO_TOLERANT; } CONTRACTL_END; KickOffThread_Args *args = (KickOffThread_Args *) ptr; _ASSERTE(ObjectFromHandle(args->share->m_Threadable) != NULL); args->retVal = 0; // we are saving the delagate and result primarily for debugging struct _gc { OBJECTREF orPrincipal; OBJECTREF orThreadStartArg; OBJECTREF orDelegate; OBJECTREF orResult; OBJECTREF orThread; } gc; ZeroMemory(&gc, sizeof(gc)); Thread *pThread; pThread = GetThread(); _ASSERTE(pThread); GCPROTECT_BEGIN(gc); BEGIN_SO_INTOLERANT_CODE(pThread); gc.orPrincipal = ObjectFromHandle(args->share->m_Principal); #ifdef FEATURE_IMPERSONATION // Push the initial security principal object (if any) onto the // managed thread. if (gc.orPrincipal != NULL) { gc.orThread = args->pThread->GetExposedObject(); MethodDescCallSite setPrincipalInternal(METHOD__THREAD__SET_PRINCIPAL_INTERNAL, &gc.orThread); ARG_SLOT argsToSetPrincipal[2]; argsToSetPrincipal[0] = ObjToArgSlot(gc.orThread); argsToSetPrincipal[1] = ObjToArgSlot(gc.orPrincipal); setPrincipalInternal.Call(argsToSetPrincipal); } #endif gc.orDelegate = ObjectFromHandle(args->share->m_Threadable); gc.orThreadStartArg = ObjectFromHandle(args->share->m_ThreadStartArg); // We cannot call the Delegate Invoke method directly from ECall. The // stub has not been created for non multicast delegates. Instead, we // will invoke the Method on the OR stored in the delegate directly. // If there are changes to the signature of the ThreadStart delegate // this code will need to change. I've noted this in the Thread start // class. delete args->share; args->share = 0; MethodDesc *pMeth = ((DelegateEEClass*)( gc.orDelegate->GetMethodTable()->GetClass() ))->m_pInvokeMethod; _ASSERTE(pMeth); MethodDescCallSite invokeMethod(pMeth, &gc.orDelegate); if (MscorlibBinder::IsClass(gc.orDelegate->GetMethodTable(), CLASS__PARAMETERIZEDTHREADSTART)) { //Parameterized ThreadStart ARG_SLOT arg[2]; arg[0] = ObjToArgSlot(gc.orDelegate); arg[1]=ObjToArgSlot(gc.orThreadStartArg); invokeMethod.Call(arg); } else { //Simple ThreadStart ARG_SLOT arg[1]; arg[0] = ObjToArgSlot(gc.orDelegate); invokeMethod.Call(arg); } STRESS_LOG2(LF_SYNC, LL_INFO10, "Managed thread exiting normally for delegate %p Type %pT\n", OBJECTREFToObject(gc.orDelegate), (size_t) gc.orDelegate->GetMethodTable()); END_SO_INTOLERANT_CODE; GCPROTECT_END(); } // Helper to avoid two EX_TRY/EX_CATCH blocks in one function static void PulseAllHelper(Thread* pThread) { CONTRACTL { GC_TRIGGERS; DISABLED(NOTHROW); MODE_COOPERATIVE; } CONTRACTL_END; EX_TRY { // GetExposedObject() will either throw, or we have a valid object. Note // that we re-acquire it each time, since it may move during calls. pThread->GetExposedObject()->EnterObjMonitor(); pThread->GetExposedObject()->PulseAll(); pThread->GetExposedObject()->LeaveObjMonitor(); } EX_CATCH { // just keep going... } EX_END_CATCH(SwallowAllExceptions) } // When an exposed thread is started by Win32, this is where it starts. ULONG __stdcall ThreadNative::KickOffThread(void* pass) { CONTRACTL { GC_TRIGGERS; THROWS; MODE_ANY; SO_TOLERANT; } CONTRACTL_END; ULONG retVal = 0; // Before we do anything else, get Setup so that we have a real thread. // Our thread isn't setup yet, so we can't use the standard probe BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD(return E_FAIL); KickOffThread_Args args; // don't have a separate var becuase this can be updated in the worker args.share = (SharedState *) pass; args.pThread = args.share->m_Internal; Thread* pThread = args.pThread; _ASSERTE(pThread != NULL); BOOL ok = TRUE; { EX_TRY { CExecutionEngine::CheckThreadState(0); } EX_CATCH { // OOM might be thrown from CheckThreadState, so it's important // that we don't rethrow it; if we do then the process will die // because there are no installed handlers at this point, so // swallow the exception. this will set the thread's state to // FailStarted which will result in a ThreadStartException being // thrown from the thread that attempted to start this one. if (!GET_EXCEPTION()->IsTransient() && !SwallowUnhandledExceptions()) EX_RETHROW; } EX_END_CATCH(SwallowAllExceptions); if (CExecutionEngine::CheckThreadStateNoCreate(0) == NULL) { // We can not pThread->SetThreadState(Thread::TS_FailStarted); pThread->DetachThread(FALSE); // !!! Do not touch any field of Thread object. The Thread object is subject to delete // !!! after DetachThread call. ok = FALSE; } } if (ok) { ok = pThread->HasStarted(); } if (ok) { // Do not swallow the unhandled exception here // // Fire ETW event to correlate with the thread that created current thread if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_Context, ThreadRunning)) FireEtwThreadRunning(pThread, GetClrInstanceId()); // We have a sticky problem here. // // Under some circumstances, the context of 'this' doesn't match the context // of the thread. Today this can only happen if the thread is marked for an // STA. If so, the delegate that is stored in the object may not be directly // suitable for invocation. Instead, we need to call through a proxy so that // the correct context transitions occur. // // All the changes occur inside HasStarted(), which will switch this thread // over to a brand new STA as necessary. We have to notice this happening, so // we can adjust the delegate we are going to invoke on. _ASSERTE(GetThread() == pThread); // Now that it's started ManagedThreadBase::KickOff(pThread->GetKickOffDomainId(), KickOffThread_Worker, &args); // If TS_FailStarted is set then the args are deleted in ThreadNative::StartInner if ((args.share) && !pThread->HasThreadState(Thread::TS_FailStarted)) { delete args.share; } PulseAllHelper(pThread); GCX_PREEMP_NO_DTOR(); pThread->ClearThreadCPUGroupAffinity(); DestroyThread(pThread); } END_SO_INTOLERANT_CODE; return retVal; } FCIMPL3(void, ThreadNative::Start, ThreadBaseObject* pThisUNSAFE, Object* pPrincipalUNSAFE, StackCrawlMark* pStackMark) { FCALL_CONTRACT; HELPER_METHOD_FRAME_BEGIN_NOPOLL(); StartInner(pThisUNSAFE, pPrincipalUNSAFE, pStackMark); HELPER_METHOD_FRAME_END_POLL(); } FCIMPLEND // Start up a thread, which by now should be in the ThreadStore's Unstarted list. void ThreadNative::StartInner(ThreadBaseObject* pThisUNSAFE, Object* pPrincipalUNSAFE, StackCrawlMark* pStackMark) { CONTRACTL { GC_TRIGGERS; THROWS; MODE_COOPERATIVE; } CONTRACTL_END; struct _gc { OBJECTREF pPrincipal; THREADBASEREF pThis; } gc; gc.pPrincipal = (OBJECTREF) pPrincipalUNSAFE; gc.pThis = (THREADBASEREF) pThisUNSAFE; GCPROTECT_BEGIN(gc); if (gc.pThis == NULL) COMPlusThrow(kNullReferenceException, W("NullReference_This")); Thread *pNewThread = gc.pThis->GetInternal(); if (pNewThread == NULL) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); _ASSERTE(GetThread() != NULL); // Current thread wandered in! gc.pThis->EnterObjMonitor(); EX_TRY { // Is the thread already started? You can't restart a thread. if (!ThreadNotStarted(pNewThread)) { COMPlusThrow(kThreadStateException, IDS_EE_THREADSTART_STATE); } OBJECTREF threadable = gc.pThis->GetDelegate(); OBJECTREF threadStartArg = gc.pThis->GetThreadStartArg(); gc.pThis->SetDelegate(NULL); gc.pThis->SetThreadStartArg(NULL); // This can never happen, because we construct it with a valid one and then // we never let you change it (because SetStart is private). _ASSERTE(threadable != NULL); // Allocate this away from our stack, so we can unwind without affecting // KickOffThread. It is inside a GCFrame, so we can enable GC now. NewHolder share(new SharedState(threadable, threadStartArg, pNewThread, gc.pPrincipal)); pNewThread->IncExternalCount(); // Fire an ETW event to mark the current thread as the launcher of the new thread if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_Context, ThreadCreating)) FireEtwThreadCreating(pNewThread, GetClrInstanceId()); // As soon as we create the new thread, it is eligible for suspension, etc. // So it gets transitioned to cooperative mode before this call returns to // us. It is our duty to start it running immediately, so that GC isn't blocked. BOOL success = pNewThread->CreateNewThread( pNewThread->RequestedThreadStackSize() /* 0 stackSize override*/, KickOffThread, share); if (!success) { pNewThread->DecExternalCount(FALSE); COMPlusThrowOM(); } // After we have established the thread handle, we can check m_Priority. // This ordering is required to eliminate the race condition on setting the // priority of a thread just as it starts up. pNewThread->SetThreadPriority(MapToNTPriority(gc.pThis->m_Priority)); pNewThread->ChooseThreadCPUGroupAffinity(); FastInterlockOr((ULONG *) &pNewThread->m_State, Thread::TS_LegalToJoin); DWORD ret; ret = pNewThread->StartThread(); // When running under a user mode native debugger there is a race // between the moment we've created the thread (in CreateNewThread) and // the moment we resume it (in StartThread); the debugger may receive // the "ct" (create thread) notification, and it will attempt to // suspend/resume all threads in the process. Now imagine the debugger // resumes this thread first, and only later does it try to resume the // newly created thread. In these conditions our call to ResumeThread // may come before the debugger's call to ResumeThread actually causing // ret to equal 2. // We cannot use IsDebuggerPresent() in the condition below because the // debugger may have been detached between the time it got the notification // and the moment we execute the test below. _ASSERTE(ret == 1 || ret == 2); { GCX_PREEMP(); // Synchronize with HasStarted. YIELD_WHILE (!pNewThread->HasThreadState(Thread::TS_FailStarted) && pNewThread->HasThreadState(Thread::TS_Unstarted)); } if (!pNewThread->HasThreadState(Thread::TS_FailStarted)) { share.SuppressRelease(); // we have handed off ownership of the shared struct } else { share.Release(); PulseAllHelper(pNewThread); pNewThread->HandleThreadStartupFailure(); } } EX_CATCH { gc.pThis->LeaveObjMonitor(); EX_RETHROW; } EX_END_CATCH_UNREACHABLE; gc.pThis->LeaveObjMonitor(); GCPROTECT_END(); } FCIMPL1(void, ThreadNative::Abort, ThreadBaseObject* pThis) { FCALL_CONTRACT; if (pThis == NULL) FCThrowVoid(kNullReferenceException); THREADBASEREF thisRef(pThis); // We need to keep the managed Thread object alive so that we can call UserAbort on // unmanaged thread object. HELPER_METHOD_FRAME_BEGIN_1(thisRef); Thread *thread = thisRef->GetInternal(); if (thread == NULL) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); #ifdef _DEBUG DWORD testAbort = g_pConfig->GetHostTestThreadAbort(); if (testAbort != 0) { thread->UserAbort(Thread::TAR_Thread, testAbort == 1 ? EEPolicy::TA_Safe : EEPolicy::TA_Rude, INFINITE, Thread::UAC_Normal); } else #endif thread->UserAbort(Thread::TAR_Thread, EEPolicy::TA_V1Compatible, INFINITE, Thread::UAC_Normal); if (thread->CatchAtSafePoint()) CommonTripThread(); HELPER_METHOD_FRAME_END_POLL(); } FCIMPLEND FCIMPL1(void, ThreadNative::ResetAbort, ThreadBaseObject* pThis) { FCALL_CONTRACT; _ASSERTE(pThis); VALIDATEOBJECT(pThis); Thread *thread = pThis->GetInternal(); // We do not allow user to reset rude thread abort in MustRun code. if (thread && thread->IsRudeAbort()) { return; } HELPER_METHOD_FRAME_BEGIN_NOPOLL(); if (thread == NULL) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); thread->UserResetAbort(Thread::TAR_Thread); thread->ClearAborted(); HELPER_METHOD_FRAME_END_POLL(); } FCIMPLEND #ifndef FEATURE_CORECLR // You can only suspend a running thread. FCIMPL1(void, ThreadNative::Suspend, ThreadBaseObject* pThisUNSAFE) { FCALL_CONTRACT; if (pThisUNSAFE == NULL) FCThrowResVoid(kNullReferenceException, W("NullReference_This")); Thread *thread = pThisUNSAFE->GetInternal(); HELPER_METHOD_FRAME_BEGIN_0(); #ifdef MDA_SUPPORTED MDA_TRIGGER_ASSISTANT(DangerousThreadingAPI, ReportViolation(W("System.Threading.Thread.Suspend"))); #endif if (!ThreadIsRunning(thread)) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_SUSPEND_NON_RUNNING); thread->UserSuspendThread(); HELPER_METHOD_FRAME_END(); } FCIMPLEND // You can only resume a thread that is in the user-suspended state. (This puts a large // burden on the app developer, but we want him to be thinking carefully about race // conditions. Precise errors give him a hope of sorting out his logic). FCIMPL1(void, ThreadNative::Resume, ThreadBaseObject* pThisUNSAFE) { FCALL_CONTRACT; if (pThisUNSAFE == NULL) FCThrowResVoid(kNullReferenceException, W("NullReference_This")); Thread *thread = pThisUNSAFE->GetInternal(); HELPER_METHOD_FRAME_BEGIN_0(); // UserResumeThread() will return 0 if there isn't a user suspension for us to // clear. if (!ThreadIsRunning(thread)) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_RESUME_NON_RUNNING); if (thread->UserResumeThread() == 0) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_RESUME_NON_USER_SUSPEND); HELPER_METHOD_FRAME_END(); } FCIMPLEND #endif // FEATURE_CORECLR // Note that you can manipulate the priority of a thread that hasn't started yet, // or one that is running. But you get an exception if you manipulate the priority // of a thread that has died. FCIMPL1(INT32, ThreadNative::GetPriority, ThreadBaseObject* pThisUNSAFE) { FCALL_CONTRACT; if (pThisUNSAFE==NULL) FCThrowRes(kNullReferenceException, W("NullReference_This")); // validate the handle if (ThreadIsDead(pThisUNSAFE->GetInternal())) FCThrowEx(kThreadStateException, IDS_EE_THREAD_DEAD_PRIORITY, NULL, NULL, NULL); return pThisUNSAFE->m_Priority; } FCIMPLEND FCIMPL2(void, ThreadNative::SetPriority, ThreadBaseObject* pThisUNSAFE, INT32 iPriority) { FCALL_CONTRACT; int priority; Thread *thread; THREADBASEREF pThis = (THREADBASEREF) pThisUNSAFE; HELPER_METHOD_FRAME_BEGIN_1(pThis); if (pThis==NULL) { COMPlusThrow(kNullReferenceException, W("NullReference_This")); } // translate the priority (validating as well) priority = MapToNTPriority(iPriority); // can throw; needs a frame // validate the thread thread = pThis->GetInternal(); if (ThreadIsDead(thread)) { COMPlusThrow(kThreadStateException, IDS_EE_THREAD_DEAD_PRIORITY, NULL, NULL, NULL); } INT32 oldPriority = pThis->m_Priority; // Eliminate the race condition by establishing m_Priority before we check for if // the thread is running. See ThreadNative::Start() for the other half. pThis->m_Priority = iPriority; if (!thread->SetThreadPriority(priority)) { pThis->m_Priority = oldPriority; COMPlusThrow(kThreadStateException, IDS_EE_THREAD_PRIORITY_FAIL, NULL, NULL, NULL); } HELPER_METHOD_FRAME_END(); } FCIMPLEND // This service can be called on unstarted and dead threads. For unstarted ones, the // next wait will be interrupted. For dead ones, this service quietly does nothing. FCIMPL1(void, ThreadNative::Interrupt, ThreadBaseObject* pThisUNSAFE) { FCALL_CONTRACT; if (pThisUNSAFE==NULL) FCThrowResVoid(kNullReferenceException, W("NullReference_This")); Thread *thread = pThisUNSAFE->GetInternal(); if (thread == 0) FCThrowExVoid(kThreadStateException, IDS_EE_THREAD_CANNOT_GET, NULL, NULL, NULL); HELPER_METHOD_FRAME_BEGIN_0(); thread->UserInterrupt(Thread::TI_Interrupt); HELPER_METHOD_FRAME_END(); } FCIMPLEND FCIMPL1(FC_BOOL_RET, ThreadNative::IsAlive, ThreadBaseObject* pThisUNSAFE) { FCALL_CONTRACT; if (pThisUNSAFE==NULL) FCThrowRes(kNullReferenceException, W("NullReference_This")); THREADBASEREF thisRef(pThisUNSAFE); BOOL ret = false; // Keep managed Thread object alive, since the native object's // lifetime is tied to the managed object's finalizer. And with // resurrection, it may be possible to get a dangling pointer here - // consider both protecting thisRef and setting the managed object's // Thread* to NULL in the GC's ScanForFinalization method. HELPER_METHOD_FRAME_BEGIN_RET_1(thisRef); Thread *thread = thisRef->GetInternal(); if (thread == 0) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); ret = ThreadIsRunning(thread); HELPER_METHOD_POLL(); HELPER_METHOD_FRAME_END(); FC_RETURN_BOOL(ret); } FCIMPLEND FCIMPL2(FC_BOOL_RET, ThreadNative::Join, ThreadBaseObject* pThisUNSAFE, INT32 Timeout) { FCALL_CONTRACT; BOOL retVal = FALSE; THREADBASEREF pThis = (THREADBASEREF) pThisUNSAFE; HELPER_METHOD_FRAME_BEGIN_RET_1(pThis); if (pThis==NULL) COMPlusThrow(kNullReferenceException, W("NullReference_This")); // validate the timeout if ((Timeout < 0) && (Timeout != INFINITE_TIMEOUT)) COMPlusThrowArgumentOutOfRange(W("millisecondsTimeout"), W("ArgumentOutOfRange_NeedNonNegOrNegative1")); retVal = DoJoin(pThis, Timeout); HELPER_METHOD_FRAME_END(); FC_RETURN_BOOL(retVal); } FCIMPLEND #undef Sleep FCIMPL1(void, ThreadNative::Sleep, INT32 iTime) { FCALL_CONTRACT; HELPER_METHOD_FRAME_BEGIN_0(); // validate the sleep time if ((iTime < 0) && (iTime != INFINITE_TIMEOUT)) COMPlusThrowArgumentOutOfRange(W("millisecondsTimeout"), W("ArgumentOutOfRange_NeedNonNegOrNegative1")); while(true) { INT64 sPauseTime = g_PauseTime; INT64 sTime = CLRGetTickCount64(); GetThread()->UserSleep(iTime); iTime = (INT32)AdditionalWait(sPauseTime, sTime, iTime); if(iTime == 0) break; } HELPER_METHOD_FRAME_END(); } FCIMPLEND #define Sleep(dwMilliseconds) Dont_Use_Sleep(dwMilliseconds) FCIMPL1(INT32, ThreadNative::GetManagedThreadId, ThreadBaseObject* th) { FCALL_CONTRACT; FC_GC_POLL_NOT_NEEDED(); if (th == NULL) FCThrow(kNullReferenceException); return th->GetManagedThreadId(); } FCIMPLEND NOINLINE static Object* GetCurrentThreadHelper() { FCALL_CONTRACT; FC_INNER_PROLOG(ThreadNative::GetCurrentThread); OBJECTREF refRetVal = NULL; HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_EXACT_DEPTH|Frame::FRAME_ATTR_CAPTURE_DEPTH_2, refRetVal); refRetVal = GetThread()->GetExposedObject(); HELPER_METHOD_FRAME_END(); FC_INNER_EPILOG(); return OBJECTREFToObject(refRetVal); } FCIMPL0(Object*, ThreadNative::GetCurrentThread) { FCALL_CONTRACT; OBJECTHANDLE ExposedObject = GetThread()->m_ExposedObject; _ASSERTE(ExposedObject != 0); //Thread's constructor always initializes its GCHandle Object* result = *((Object**) ExposedObject); if (result != 0) return result; FC_INNER_RETURN(Object*, GetCurrentThreadHelper()); } FCIMPLEND FCIMPL3(void, ThreadNative::SetStart, ThreadBaseObject* pThisUNSAFE, Object* pDelegateUNSAFE, INT32 iRequestedStackSize) { FCALL_CONTRACT; if (pThisUNSAFE==NULL) FCThrowResVoid(kNullReferenceException, W("NullReference_This")); THREADBASEREF pThis = (THREADBASEREF) pThisUNSAFE; OBJECTREF pDelegate = (OBJECTREF ) pDelegateUNSAFE; HELPER_METHOD_FRAME_BEGIN_2(pThis, pDelegate); _ASSERTE(pThis != NULL); _ASSERTE(pDelegate != NULL); // Thread's constructor validates this if (pThis->m_InternalThread == NULL) { // if we don't have an internal Thread object associated with this exposed object, // now is our first opportunity to create one. Thread *unstarted = SetupUnstartedThread(); PREFIX_ASSUME(unstarted != NULL); if (GetThread()->GetDomain()->IgnoreUnhandledExceptions()) { unstarted->SetThreadStateNC(Thread::TSNC_IgnoreUnhandledExceptions); } pThis->SetInternal(unstarted); pThis->SetManagedThreadId(unstarted->GetThreadId()); unstarted->SetExposedObject(pThis); unstarted->RequestedThreadStackSize(iRequestedStackSize); } // save off the delegate pThis->SetDelegate(pDelegate); HELPER_METHOD_FRAME_END(); } FCIMPLEND // Set whether or not this is a background thread. FCIMPL2(void, ThreadNative::SetBackground, ThreadBaseObject* pThisUNSAFE, CLR_BOOL isBackground) { FCALL_CONTRACT; if (pThisUNSAFE==NULL) FCThrowResVoid(kNullReferenceException, W("NullReference_This")); // validate the thread Thread *thread = pThisUNSAFE->GetInternal(); if (ThreadIsDead(thread)) FCThrowExVoid(kThreadStateException, IDS_EE_THREAD_DEAD_STATE, NULL, NULL, NULL); HELPER_METHOD_FRAME_BEGIN_0(); thread->SetBackground(isBackground); HELPER_METHOD_FRAME_END(); } FCIMPLEND // Return whether or not this is a background thread. FCIMPL1(FC_BOOL_RET, ThreadNative::IsBackground, ThreadBaseObject* pThisUNSAFE) { FCALL_CONTRACT; if (pThisUNSAFE==NULL) FCThrowRes(kNullReferenceException, W("NullReference_This")); // validate the thread Thread *thread = pThisUNSAFE->GetInternal(); if (ThreadIsDead(thread)) FCThrowEx(kThreadStateException, IDS_EE_THREAD_DEAD_STATE, NULL, NULL, NULL); FC_RETURN_BOOL(thread->IsBackground()); } FCIMPLEND // Deliver the state of the thread as a consistent set of bits. // This copied in VM\EEDbgInterfaceImpl.h's // CorDebugUserState GetUserState( Thread *pThread ) // , so propogate changes to both functions FCIMPL1(INT32, ThreadNative::GetThreadState, ThreadBaseObject* pThisUNSAFE) { FCALL_CONTRACT; INT32 res = 0; Thread::ThreadState state; if (pThisUNSAFE==NULL) FCThrowRes(kNullReferenceException, W("NullReference_This")); // validate the thread. Failure here implies that the thread was finalized // and then resurrected. Thread *thread = pThisUNSAFE->GetInternal(); if (!thread) FCThrowEx(kThreadStateException, IDS_EE_THREAD_CANNOT_GET, NULL, NULL, NULL); HELPER_METHOD_FRAME_BEGIN_RET_0(); // grab a snapshot state = thread->GetSnapshotState(); if (state & Thread::TS_Background) res |= ThreadBackground; if (state & Thread::TS_Unstarted) res |= ThreadUnstarted; // Don't report a StopRequested if the thread has actually stopped. if (state & Thread::TS_Dead) { if (state & Thread::TS_Aborted) res |= ThreadAborted; else res |= ThreadStopped; } else { if (state & Thread::TS_AbortRequested) res |= ThreadAbortRequested; } if (state & Thread::TS_Interruptible) res |= ThreadWaitSleepJoin; // Don't report a SuspendRequested if the thread has actually Suspended. if ((state & Thread::TS_UserSuspendPending) && (state & Thread::TS_SyncSuspended) ) { res |= ThreadSuspended; } else if (state & Thread::TS_UserSuspendPending) { res |= ThreadSuspendRequested; } HELPER_METHOD_POLL(); HELPER_METHOD_FRAME_END(); return res; } FCIMPLEND #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT // Indicate whether the thread will host an STA (this may fail if the thread has // already been made part of the MTA, use GetApartmentState or the return state // from this routine to check for this). FCIMPL3(INT32, ThreadNative::SetApartmentState, ThreadBaseObject* pThisUNSAFE, INT32 iState, CLR_BOOL fireMDAOnMismatch) { FCALL_CONTRACT; if (pThisUNSAFE==NULL) FCThrowRes(kNullReferenceException, W("NullReference_This")); INT32 retVal = ApartmentUnknown; BOOL ok = TRUE; THREADBASEREF pThis = (THREADBASEREF) pThisUNSAFE; HELPER_METHOD_FRAME_BEGIN_RET_1(pThis); // Translate state input. ApartmentUnknown is not an acceptable input state. // Throw an exception here rather than pass it through to the internal // routine, which asserts. Thread::ApartmentState state = Thread::AS_Unknown; if (iState == ApartmentSTA) state = Thread::AS_InSTA; else if (iState == ApartmentMTA) state = Thread::AS_InMTA; else if (iState == ApartmentUnknown) state = Thread::AS_Unknown; else COMPlusThrow(kArgumentOutOfRangeException, W("ArgumentOutOfRange_Enum")); Thread *thread = pThis->GetInternal(); if (!thread) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); { pThis->EnterObjMonitor(); // We can only change the apartment if the thread is unstarted or // running, and if it's running we have to be in the thread's // context. if ((!ThreadNotStarted(thread) && !ThreadIsRunning(thread)) || (!ThreadNotStarted(thread) && (GetThread() != thread))) ok = FALSE; else { EX_TRY { state = thread->SetApartment(state, fireMDAOnMismatch == TRUE); } EX_CATCH { pThis->LeaveObjMonitor(); EX_RETHROW; } EX_END_CATCH_UNREACHABLE; } pThis->LeaveObjMonitor(); } // Now it's safe to throw exceptions again. if (!ok) COMPlusThrow(kThreadStateException); // Translate state back into external form if (state == Thread::AS_InSTA) retVal = ApartmentSTA; else if (state == Thread::AS_InMTA) retVal = ApartmentMTA; else if (state == Thread::AS_Unknown) retVal = ApartmentUnknown; else _ASSERTE(!"Invalid state returned from SetApartment"); HELPER_METHOD_FRAME_END(); return retVal; } FCIMPLEND // Return whether the thread hosts an STA, is a member of the MTA or is not // currently initialized for COM. FCIMPL1(INT32, ThreadNative::GetApartmentState, ThreadBaseObject* pThisUNSAFE) { FCALL_CONTRACT; INT32 retVal = 0; THREADBASEREF refThis = (THREADBASEREF) ObjectToOBJECTREF(pThisUNSAFE); HELPER_METHOD_FRAME_BEGIN_RET_1(refThis); if (refThis == NULL) { COMPlusThrow(kNullReferenceException, W("NullReference_This")); } Thread* thread = refThis->GetInternal(); if (ThreadIsDead(thread)) { COMPlusThrow(kThreadStateException, IDS_EE_THREAD_DEAD_STATE); } Thread::ApartmentState state = thread->GetApartment(); #ifdef FEATURE_COMINTEROP if (state == Thread::AS_Unknown) { // If the CLR hasn't started COM yet, start it up and attempt the call again. // We do this in order to minimize the number of situations under which we return // ApartmentState.Unknown to our callers. if (!g_fComStarted) { EnsureComStarted(); state = thread->GetApartment(); } } #endif // FEATURE_COMINTEROP // Translate state into external form retVal = ApartmentUnknown; if (state == Thread::AS_InSTA) { retVal = ApartmentSTA; } else if (state == Thread::AS_InMTA) { retVal = ApartmentMTA; } else if (state == Thread::AS_Unknown) { retVal = ApartmentUnknown; } else { _ASSERTE(!"Invalid state returned from GetApartment"); } HELPER_METHOD_FRAME_END(); return retVal; } FCIMPLEND // Attempt to eagerly set the apartment state during thread startup. FCIMPL1(void, ThreadNative::StartupSetApartmentState, ThreadBaseObject* pThisUNSAFE) { FCALL_CONTRACT; THREADBASEREF refThis = (THREADBASEREF) ObjectToOBJECTREF(pThisUNSAFE); HELPER_METHOD_FRAME_BEGIN_1(refThis); if (refThis == NULL) { COMPlusThrow(kNullReferenceException, W("NullReference_This")); } Thread* thread = refThis->GetInternal(); if (!ThreadNotStarted(thread)) COMPlusThrow(kThreadStateException, IDS_EE_THREADSTART_STATE); // Assert that the thread hasn't been started yet. _ASSERTE(Thread::TS_Unstarted & thread->GetSnapshotState()); if ((g_pConfig != NULL) && !g_pConfig->LegacyApartmentInitPolicy()) { Thread::ApartmentState as = thread->GetExplicitApartment(); if (as == Thread::AS_Unknown) { thread->SetApartment(Thread::AS_InMTA, TRUE); } } HELPER_METHOD_FRAME_END(); } FCIMPLEND #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT void ReleaseThreadExternalCount(Thread * pThread) { WRAPPER_NO_CONTRACT; pThread->DecExternalCount(FALSE); } typedef Holder ThreadExternalCountHolder; // Wait for the thread to die BOOL ThreadNative::DoJoin(THREADBASEREF DyingThread, INT32 timeout) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; PRECONDITION(DyingThread != NULL); PRECONDITION((timeout >= 0) || (timeout == INFINITE_TIMEOUT)); } CONTRACTL_END; Thread * DyingInternal = DyingThread->GetInternal(); // Validate the handle. It's valid to Join a thread that's not running -- so // long as it was once started. if (DyingInternal == 0 || !(DyingInternal->m_State & Thread::TS_LegalToJoin)) { COMPlusThrow(kThreadStateException, IDS_EE_THREAD_NOTSTARTED); } // Don't grab the handle until we know it has started, to eliminate the race // condition. if (ThreadIsDead(DyingInternal) || !DyingInternal->HasValidThreadHandle()) return TRUE; DWORD dwTimeOut32 = (timeout == INFINITE_TIMEOUT ? INFINITE : (DWORD) timeout); // There is a race here. DyingThread is going to close its thread handle. // If we grab the handle and then DyingThread closes it, we will wait forever // in DoAppropriateWait. int RefCount = DyingInternal->IncExternalCount(); if (RefCount == 1) { // !!! We resurrect the Thread Object. // !!! We will keep the Thread ref count to be 1 so that we will not try // !!! to destroy the Thread Object again. // !!! Do not call DecExternalCount here! _ASSERTE (!DyingInternal->HasValidThreadHandle()); return TRUE; } ThreadExternalCountHolder dyingInternalHolder(DyingInternal); if (!DyingInternal->HasValidThreadHandle()) { return TRUE; } GCX_PREEMP(); DWORD rv = DyingInternal->JoinEx(dwTimeOut32, (WaitMode)(WaitMode_Alertable/*alertable*/|WaitMode_InDeadlock)); switch(rv) { case WAIT_OBJECT_0: return TRUE; case WAIT_TIMEOUT: break; case WAIT_FAILED: if(!DyingInternal->HasValidThreadHandle()) return TRUE; break; default: _ASSERTE(!"This return code is not understood \n"); break; } return FALSE; } // We don't get a constructor for ThreadBaseObject, so we rely on the fact that this // method is only called once, out of SetStart. Since SetStart is private/native // and only called from the constructor, we'll only get called here once to set it // up and once (with NULL) to tear it down. The 'null' can only come from Finalize // because the constructor throws if it doesn't get a valid delegate. void ThreadBaseObject::SetDelegate(OBJECTREF delegate) { CONTRACTL { GC_TRIGGERS; THROWS; MODE_COOPERATIVE; } CONTRACTL_END; #ifdef APPDOMAIN_STATE if (delegate != NULL) { AppDomain *pDomain = delegate->GetAppDomain(); Thread *pThread = GetInternal(); AppDomain *kickoffDomain = pThread->GetKickOffDomain(); _ASSERTE_ALL_BUILDS("clr/src/VM/COMSynchronizable.cpp", !pDomain || pDomain == kickoffDomain); _ASSERTE_ALL_BUILDS("clr/src/VM/COMSynchronizable.cpp", kickoffDomain == GetThread()->GetDomain()); } #endif SetObjectReferenceUnchecked( (OBJECTREF *)&m_Delegate, delegate ); // If the delegate is being set then initialize the other data members. if (m_Delegate != NULL) { // Initialize the thread priority to normal. m_Priority = ThreadNative::PRIORITY_NORMAL; } } // If the exposed object is created after-the-fact, for an existing thread, we call // InitExisting on it. This is the other "construction", as opposed to SetDelegate. void ThreadBaseObject::InitExisting() { CONTRACTL { GC_NOTRIGGER; NOTHROW; MODE_COOPERATIVE; } CONTRACTL_END; Thread *pThread = GetInternal(); _ASSERTE (pThread); switch (pThread->GetThreadPriority()) { case THREAD_PRIORITY_LOWEST: case THREAD_PRIORITY_IDLE: m_Priority = ThreadNative::PRIORITY_LOWEST; break; case THREAD_PRIORITY_BELOW_NORMAL: m_Priority = ThreadNative::PRIORITY_BELOW_NORMAL; break; case THREAD_PRIORITY_NORMAL: m_Priority = ThreadNative::PRIORITY_NORMAL; break; case THREAD_PRIORITY_ABOVE_NORMAL: m_Priority = ThreadNative::PRIORITY_ABOVE_NORMAL; break; case THREAD_PRIORITY_HIGHEST: case THREAD_PRIORITY_TIME_CRITICAL: m_Priority = ThreadNative::PRIORITY_HIGHEST; break; case THREAD_PRIORITY_ERROR_RETURN: _ASSERTE(FALSE); m_Priority = ThreadNative::PRIORITY_NORMAL; break; default: m_Priority = ThreadNative::PRIORITY_NORMAL; break; } } #ifndef FEATURE_LEAK_CULTURE_INFO OBJECTREF ThreadBaseObject::GetManagedThreadCulture(BOOL bUICulture) { CONTRACTL { GC_NOTRIGGER; NOTHROW; MODE_COOPERATIVE; } CONTRACTL_END; // This is the case when we're building mscorlib and haven't yet created // the system assembly. if (SystemDomain::System()->SystemAssembly()==NULL || g_fForbidEnterEE) { return NULL; } OBJECTREF *pCurrentCulture = NULL; Thread *pThread = GetInternal(); FieldDesc *pFD = NULL; if (bUICulture) { pFD = pThread->managedThreadCurrentUICulture; } else { pFD = pThread->managedThreadCurrentCulture; } if (pFD != NULL) { pCurrentCulture = (OBJECTREF*)pThread->GetStaticFieldAddrNoCreate(pFD, NULL); if (pCurrentCulture) { return *pCurrentCulture; } } return NULL; } CULTUREINFOBASEREF ThreadBaseObject::GetCurrentUserCulture() { WRAPPER_NO_CONTRACT; return (CULTUREINFOBASEREF)GetManagedThreadCulture(false); } CULTUREINFOBASEREF ThreadBaseObject::GetCurrentUICulture() { WRAPPER_NO_CONTRACT; return (CULTUREINFOBASEREF)GetManagedThreadCulture(true); } // If the thread pool thread switched appdomains and the culture was set, the culture won't be // reset for the second appdomain. It's impossible to do general cleanup of thread pool threads // because we don't have the right extensible infrastructure for it. For example, if the second // appdomain was in a different CLR you won't be able to reset the culture without introducing // new cross-CLR communication mechanism. However, note that this isn't a user scenario in // CoreCLR anyway. void ThreadBaseObject::ResetCurrentUserCulture() { WRAPPER_NO_CONTRACT; ResetManagedThreadCulture(false); } void ThreadBaseObject::ResetCurrentUICulture() { WRAPPER_NO_CONTRACT; ResetManagedThreadCulture(true); } void ThreadBaseObject::ResetManagedThreadCulture(BOOL bUICulture) { CONTRACTL { GC_NOTRIGGER; NOTHROW; MODE_COOPERATIVE; SO_TOLERANT; } CONTRACTL_END; // This is the case when we're building mscorlib and haven't yet created // the system assembly. if (SystemDomain::System()->SystemAssembly()==NULL || g_fForbidEnterEE) { return; } Thread *pThread = GetInternal(); FieldDesc *pFD = NULL; if (bUICulture) { pFD = pThread->managedThreadCurrentUICulture; } else { pFD = pThread->managedThreadCurrentCulture; } if (pFD != NULL) { OBJECTREF *pCulture = NULL; BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD(COMPlusThrowSO()); pCulture = (OBJECTREF*)pThread->GetStaticFieldAddrNoCreate(pFD, NULL); if (pCulture) { SetObjectReferenceUnchecked(pCulture, NULL); } END_SO_INTOLERANT_CODE; } } #endif // FEATURE_LEAK_CULTURE_INFO FCIMPL1(void, ThreadNative::Finalize, ThreadBaseObject* pThisUNSAFE) { FCALL_CONTRACT; // This function is intentionally blank. // See comment in code:MethodTable::CallFinalizer. _ASSERTE (!"Should not be called"); FCUnique(0x21); } FCIMPLEND #ifdef FEATURE_COMINTEROP FCIMPL1(void, ThreadNative::DisableComObjectEagerCleanup, ThreadBaseObject* pThisUNSAFE) { FCALL_CONTRACT; _ASSERTE(pThisUNSAFE != NULL); VALIDATEOBJECT(pThisUNSAFE); Thread *pThread = pThisUNSAFE->GetInternal(); HELPER_METHOD_FRAME_BEGIN_0(); if (pThread == NULL) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); pThread->SetDisableComObjectEagerCleanup(); HELPER_METHOD_FRAME_END(); } FCIMPLEND #endif //FEATURE_COMINTEROP #ifdef FEATURE_LEAK_CULTURE_INFO FCIMPL1(FC_BOOL_RET, ThreadNative::SetThreadUILocale, StringObject* localeNameUNSAFE) { FCALL_CONTRACT; BOOL result = TRUE; STRINGREF name = (STRINGREF) localeNameUNSAFE; VALIDATEOBJECTREF(name); HELPER_METHOD_FRAME_BEGIN_RET_0(); LCID lcid=NewApis::LocaleNameToLCID(name->GetBuffer(),0); if (lcid == 0) { ThrowHR(HRESULT_FROM_WIN32(GetLastError())); } #ifdef FEATURE_INCLUDE_ALL_INTERFACES IHostTaskManager *manager = CorHost2::GetHostTaskManager(); if (manager) { BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()); result = (manager->SetUILocale(lcid) == S_OK); END_SO_TOLERANT_CODE_CALLING_HOST; } #endif // FEATURE_INCLUDE_ALL_INTERFACES HELPER_METHOD_FRAME_END(); FC_RETURN_BOOL(result); } FCIMPLEND #endif // FEATURE_LEAK_CULTURE_INFO FCIMPL0(Object*, ThreadNative::GetDomain) { FCALL_CONTRACT; APPDOMAINREF refRetVal = NULL; Thread* thread = GetThread(); if ((thread) && (thread->GetDomain())) { HELPER_METHOD_FRAME_BEGIN_RET_1(refRetVal); refRetVal = (APPDOMAINREF) thread->GetDomain()->GetExposedObject(); HELPER_METHOD_FRAME_END(); } return OBJECTREFToObject(refRetVal); } FCIMPLEND #ifdef _TARGET_X86_ __declspec(naked) LPVOID __fastcall ThreadNative::FastGetDomain() { STATIC_CONTRACT_MODE_COOPERATIVE; STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_SO_TOLERANT; __asm { call GetAppDomain test eax, eax je done mov eax, dword ptr [eax]AppDomain.m_ExposedObject test eax, eax je done mov eax, dword ptr [eax] done: ret } } #else // _TARGET_X86_ LPVOID F_CALL_CONV ThreadNative::FastGetDomain() { CONTRACTL { GC_NOTRIGGER; NOTHROW; MODE_COOPERATIVE; SO_TOLERANT; } CONTRACTL_END; Thread *pThread; AppDomain *pDomain; OBJECTHANDLE ExposedObject; pDomain = GetAppDomain(); if (!pDomain) { return NULL; } ExposedObject = pDomain->m_ExposedObject; if (ExposedObject) { return *(LPVOID *)ExposedObject; } return NULL; } #endif // _TARGET_X86_ #ifdef FEATURE_REMOTING // This is just a helper method that lets BCL get to the managed context // from the contextID. FCIMPL1(Object*, ThreadNative::GetContextFromContextID, LPVOID ContextID) { FCALL_CONTRACT; OBJECTREF rv = NULL; Context* pCtx = (Context *) ContextID; // Get the managed context backing this unmanaged context rv = pCtx->GetExposedObjectRaw(); // This assert maintains the following invariant: // Only default unmanaged contexts can have a null managed context // (All non-deafult contexts are created as managed contexts first, and then // hooked to the unmanaged context) _ASSERTE((rv != NULL) || (pCtx->GetDomain()->GetDefaultContext() == pCtx)); return OBJECTREFToObject(rv); } FCIMPLEND FCIMPL6(Object*, ThreadNative::InternalCrossContextCallback, ThreadBaseObject* refThis, ContextBaseObject* refContext, LPVOID contextID, INT32 appDomainId, Object* oDelegateUNSAFE, PtrArray* oArgsUNSAFE) { FCALL_CONTRACT; _ASSERTE(refThis != NULL); VALIDATEOBJECT(refThis); Thread *pThread = refThis->GetInternal(); Context *pCtx = (Context *)contextID; _ASSERTE(pCtx && (refContext == NULL || pCtx->GetExposedObjectRaw() == NULL || ObjectToOBJECTREF(refContext) == pCtx->GetExposedObjectRaw())); LOG((LF_APPDOMAIN, LL_INFO1000, "ThreadNative::InternalCrossContextCallback: %p, %p\n", refContext, pCtx)); // install our frame. We have to put it here before we put the helper frame on // Set the VM conext struct _gc { OBJECTREF oRetVal; OBJECTREF oDelegate; OBJECTREF oArgs; // We need to report the managed context object because it may become unreachable in the caller, // however we have to keep it alive, otherwise its finalizer could free the unmanaged internal context OBJECTREF oContext; } gc; gc.oRetVal = NULL; gc.oDelegate = ObjectToOBJECTREF(oDelegateUNSAFE); gc.oArgs = ObjectToOBJECTREF(oArgsUNSAFE); gc.oContext = ObjectToOBJECTREF(refContext); HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); if (pThread == NULL) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); #ifdef _DEBUG MethodDesc* pTargetMD = COMDelegate::GetMethodDesc(gc.oDelegate); _ASSERTE(pTargetMD->IsStatic()); #endif // If we have a non-zero appDomain index, this is a x-domain call // We must verify that the AppDomain is not unloaded PREPARE_NONVIRTUAL_CALLSITE(METHOD__THREAD__COMPLETE_CROSSCONTEXTCALLBACK); AppDomainFromIDHolder ad; if (appDomainId != 0) { // // NOTE: there is a potential race between the time we retrieve the app domain pointer, // and the time which this thread enters the domain. // // To solve the race, we rely on the fact that there is a thread sync // between releasing an app domain's handle, and destroying the app domain. Thus // it is important that we not go into preemptive gc mode in that window. // { ad.Assign(ADID(appDomainId), TRUE); if (ad.IsUnloaded() || !ad->CanThreadEnter(pThread)) COMPlusThrow(kAppDomainUnloadedException, W("Remoting_AppDomainUnloaded")); } } // Verify that the Context is valid. if ( !Context::ValidateContext(pCtx) ) COMPlusThrow(kRemotingException, W("Remoting_InvalidContext")); DEBUG_ASSURE_NO_RETURN_BEGIN(COMSYNCH) FrameWithCookie frame; Context* pCurrContext = pThread->GetContext(); bool fTransition = (pCurrContext != pCtx); BOOL fSameDomain = (appDomainId==0) || (pCurrContext->GetDomain()->GetId() == (ADID)appDomainId); _ASSERTE( fTransition || fSameDomain); if (fTransition) if (appDomainId!=0) ad->EnterContext(pThread,pCtx, &frame); else pThread->EnterContextRestricted(pCtx,&frame); ad.Release(); LOG((LF_EH, LL_INFO100, "MSCORLIB_ENTER_CONTEXT( %s::%s ): %s\n", pTargetMD->m_pszDebugClassName, pTargetMD->m_pszDebugMethodName, fTransition ? "ENTERED" : "NOP")); Exception* pOriginalException=NULL; EX_TRY { DECLARE_ARGHOLDER_ARRAY(callArgs, 2); #if CHECK_APP_DOMAIN_LEAKS // We're passing the delegate object to another appdomain // without marshaling, that is OK - it's a static function delegate // but we should mark it as agile then. gc.oDelegate->SetSyncBlockAppDomainAgile(); #endif callArgs[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(gc.oDelegate); callArgs[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(gc.oArgs); CATCH_HANDLER_FOUND_NOTIFICATION_CALLSITE; CALL_MANAGED_METHOD_RETREF(gc.oRetVal, OBJECTREF, callArgs); } EX_CATCH { LOG((LF_EH, LL_INFO100, "MSCORLIB_CONTEXT_TRANSITION( %s::%s ): exception in flight\n", pTargetMD->m_pszDebugClassName, pTargetMD->m_pszDebugMethodName)); if (!fTransition || fSameDomain) { if (fTransition) { GCX_FORBID(); pThread->ReturnToContext(&frame); } #ifdef FEATURE_TESTHOOKS if (appDomainId!=0) { TESTHOOKCALL(LeftAppDomain(appDomainId)); } #endif EX_RETHROW; } pOriginalException=EXTRACT_EXCEPTION(); CAPTURE_BUCKETS_AT_TRANSITION(pThread, CLRException::GetThrowableFromException(pOriginalException)); goto lAfterCtxUnwind; } EX_END_CATCH_UNREACHABLE; if (0) { lAfterCtxUnwind: LOG((LF_EH, LL_INFO100, "MSCORLIB_RaiseCrossContextException( %s::%s )\n", pTargetMD->m_pszDebugClassName, pTargetMD->m_pszDebugMethodName)); pThread->RaiseCrossContextException(pOriginalException,&frame); } LOG((LF_EH, LL_INFO100, "MSCORLIB_LEAVE_CONTEXT_TRANSITION( %s::%s )\n", pTargetMD->m_pszDebugClassName, pTargetMD->m_pszDebugMethodName)); if (fTransition) { GCX_FORBID(); pThread->ReturnToContext(&frame); } #ifdef FEATURE_TESTHOOKS if(appDomainId!=0) { TESTHOOKCALL(LeftAppDomain(appDomainId)); } #endif DEBUG_ASSURE_NO_RETURN_END(COMSYNCH) HELPER_METHOD_FRAME_END(); return OBJECTREFToObject(gc.oRetVal); } FCIMPLEND #endif //FEATURE_REMOTING // // nativeGetSafeCulture is used when the culture get requested from the thread object. // we have to check the culture in the FCALL because in FCALL the thread cannot be // interrupted and unload other app domian. // the concern here is if the thread hold a subclassed culture object and somebody // requested it from other app domain then we shouldn't hold any reference to that // culture object any time because the app domain created this culture may get // unloaded and this culture will survive although the type metadata will be unloaded // and GC will crash first time accessing this object after the app domain unload. // #ifdef FEATURE_LEAK_CULTURE_INFO FCIMPL4(FC_BOOL_RET, ThreadNative::nativeGetSafeCulture, ThreadBaseObject* threadUNSAFE, int appDomainId, CLR_BOOL isUI, OBJECTREF* safeCulture) { FCALL_CONTRACT; THREADBASEREF thread(threadUNSAFE); CULTUREINFOBASEREF pCulture = isUI ? thread->GetCurrentUICulture() : thread->GetCurrentUserCulture(); if (pCulture != NULL) { if (pCulture->IsSafeCrossDomain() || pCulture->GetCreatedDomainID() == ADID(appDomainId)) { SetObjectReference(safeCulture, pCulture, pCulture->GetAppDomain()); } else { FC_RETURN_BOOL(FALSE); } } FC_RETURN_BOOL(TRUE); } FCIMPLEND #endif // FEATURE_LEAK_CULTURE_INFO #ifndef FEATURE_LEAK_CULTURE_INFO void QCALLTYPE ThreadNative::nativeInitCultureAccessors() { QCALL_CONTRACT; BEGIN_QCALL; Thread* pThread = GetThread(); pThread->InitCultureAccessors(); END_QCALL; } #endif // FEATURE_LEAK_CULTURE_INFO void QCALLTYPE ThreadNative::InformThreadNameChange(QCall::ThreadHandle thread, LPCWSTR name, INT32 len) { QCALL_CONTRACT; BEGIN_QCALL; Thread* pThread = &(*thread); #ifdef PROFILING_SUPPORTED { BEGIN_PIN_PROFILER(CORProfilerTrackThreads()); if (name == NULL) { g_profControlBlock.pProfInterface->ThreadNameChanged((ThreadID)pThread, 0, NULL); } else { g_profControlBlock.pProfInterface->ThreadNameChanged((ThreadID)pThread, len, (WCHAR*)name); } END_PIN_PROFILER(); } #endif // PROFILING_SUPPORTED #ifdef DEBUGGING_SUPPORTED if (CORDebuggerAttached()) { _ASSERTE(NULL != g_pDebugInterface); g_pDebugInterface->NameChangeEvent(NULL, pThread); } #endif // DEBUGGING_SUPPORTED END_QCALL; } UINT64 QCALLTYPE ThreadNative::GetProcessDefaultStackSize() { QCALL_CONTRACT; SIZE_T reserve = 0; SIZE_T commit = 0; BEGIN_QCALL; if (!Thread::GetProcessDefaultStackSize(&reserve, &commit)) reserve = 1024 * 1024; END_QCALL; return (UINT64)reserve; } #ifndef FEATURE_CORECLR FCIMPL0(void, ThreadNative::BeginCriticalRegion) { FCALL_CONTRACT; if (CLRHosted()) { GetThread()->BeginCriticalRegion_NoCheck(); } } FCIMPLEND FCIMPL0(void, ThreadNative::EndCriticalRegion) { FCALL_CONTRACT; if (CLRHosted()) { GetThread()->EndCriticalRegion_NoCheck(); } } FCIMPLEND FCIMPL0(void, ThreadNative::BeginThreadAffinity) { FCALL_CONTRACT; Thread::BeginThreadAffinity(); } FCIMPLEND FCIMPL0(void, ThreadNative::EndThreadAffinity) { FCALL_CONTRACT; Thread::EndThreadAffinity(); } FCIMPLEND #endif // !FEATURE_CORECLR FCIMPL1(FC_BOOL_RET, ThreadNative::IsThreadpoolThread, ThreadBaseObject* thread) { FCALL_CONTRACT; if (thread==NULL) FCThrowRes(kNullReferenceException, W("NullReference_This")); Thread *pThread = thread->GetInternal(); if (pThread == NULL) FCThrowEx(kThreadStateException, IDS_EE_THREAD_DEAD_STATE, NULL, NULL, NULL); BOOL ret = pThread->IsThreadPoolThread(); FC_GC_POLL_RET(); FC_RETURN_BOOL(ret); } FCIMPLEND FCIMPL1(void, ThreadNative::SpinWait, int iterations) { FCALL_CONTRACT; // // If we're not going to spin for long, it's ok to remain in cooperative mode. // The threshold is determined by the cost of entering preemptive mode; if we're // spinning for less than that number of cycles, then switching to preemptive // mode won't help a GC start any faster. That number is right around 1000000 // on my machine. // if (iterations <= 1000000) { for(int i = 0; i < iterations; i++) YieldProcessor(); return; } // // Too many iterations; better switch to preemptive mode to avoid stalling a GC. // HELPER_METHOD_FRAME_BEGIN_NOPOLL(); GCX_PREEMP(); for(int i = 0; i < iterations; i++) YieldProcessor(); HELPER_METHOD_FRAME_END(); } FCIMPLEND BOOL QCALLTYPE ThreadNative::YieldThread() { QCALL_CONTRACT; BOOL ret = FALSE; BEGIN_QCALL ret = __SwitchToThread(0, CALLER_LIMITS_SPINNING); END_QCALL return ret; } #ifdef FEATURE_COMPRESSEDSTACK FCIMPL2(void*, ThreadNative::SetAppDomainStack, ThreadBaseObject* pThis, SafeHandle* hcsUNSAFE) { FCALL_CONTRACT; void* pRet = NULL; SAFEHANDLE hcsSAFE = (SAFEHANDLE) hcsUNSAFE; HELPER_METHOD_FRAME_BEGIN_RET_1(hcsSAFE); void* unmanagedCompressedStack = NULL; if (hcsSAFE != NULL) { unmanagedCompressedStack = (void *)hcsSAFE->GetHandle(); } VALIDATEOBJECT(pThis); Thread *pThread = pThis->GetInternal(); if (pThread == NULL) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); pRet = StackCompressor::SetAppDomainStack(pThread, unmanagedCompressedStack); HELPER_METHOD_FRAME_END_POLL(); return pRet; } FCIMPLEND FCIMPL2(void, ThreadNative::RestoreAppDomainStack, ThreadBaseObject* pThis, void* appDomainStack) { FCALL_CONTRACT; HELPER_METHOD_FRAME_BEGIN_NOPOLL(); VALIDATEOBJECT(pThis); Thread *pThread = pThis->GetInternal(); if (pThread == NULL) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); StackCompressor::RestoreAppDomainStack(pThread, appDomainStack); HELPER_METHOD_FRAME_END_POLL(); } FCIMPLEND #endif //#ifdef FEATURE_COMPRESSEDSTACK FCIMPL0(void, ThreadNative::FCMemoryBarrier) { FCALL_CONTRACT; MemoryBarrier(); FC_GC_POLL(); } FCIMPLEND FCIMPL2(void, ThreadNative::SetAbortReason, ThreadBaseObject* pThisUNSAFE, Object* pObject) { FCALL_CONTRACT; if (pThisUNSAFE==NULL) FCThrowResVoid(kNullReferenceException, W("NullReference_This")); OBJECTREF refObject = static_cast(pObject); Thread *pThread = pThisUNSAFE->GetInternal(); // If the OBJECTHANDLE is not 0, already set so just return if (pThread != NULL && pThread->m_AbortReason != 0) return; // Set up a frame in case of GC or EH HELPER_METHOD_FRAME_BEGIN_1(refObject) if (pThread == NULL) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); // Get the AppDomain ID for the AppDomain on the currently running thread. // NOTE: the currently running thread may be different from this thread object! AppDomain *pCurrentDomain = GetThread()->GetDomain(); ADID adid = pCurrentDomain->GetId(); // Create a OBJECTHANDLE for the object. OBJECTHANDLE oh = pCurrentDomain->CreateHandle(refObject); // Scope the lock to peeking at and updating the two fields on the Thread object. { // Atomically check whether the OBJECTHANDLE has been set, and if not, // store it and the ADID of the object. // NOTE: get the lock on this thread object, not on the executing thread. Thread::AbortRequestLockHolder lock(pThread); if (pThread->m_AbortReason == 0) { pThread->m_AbortReason = oh; pThread->m_AbortReasonDomainID = adid; // Set the OBJECTHANDLE so we can know that we stored it on the Thread object. oh = 0; } } // If the OBJECTHANDLE created above was not stored onto the Thread object, then // another thread beat this one to the update. Destroy the OBJECTHANDLE that // was not used, created above. if (oh != 0) { DestroyHandle(oh); } HELPER_METHOD_FRAME_END() } FCIMPLEND #ifndef FEATURE_CORECLR // core clr does not support abort reason FCIMPL1(Object*, ThreadNative::GetAbortReason, ThreadBaseObject *pThisUNSAFE) { FCALL_CONTRACT; if (pThisUNSAFE==NULL) FCThrowRes(kNullReferenceException, W("NullReference_This")); OBJECTREF refRetVal = NULL; Thread *pThread = pThisUNSAFE->GetInternal(); // Set up a frame in case of GC or EH HELPER_METHOD_FRAME_BEGIN_RET_1(refRetVal) if (pThread == NULL) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); // While the ExceptionInfo probably will be *set* from a different // thread, it should only be *read* from the current thread. _ASSERTE(GetThread() == pThread); // Set cooperative mode, to avoid AD unload while we're working. GCX_COOP(); OBJECTHANDLE oh=NULL; ADID adid; // Scope the lock to reading the two fields on the Thread object. { // Atomically get the OBJECTHANDLE and ADID of the object // NOTE: get the lock on this thread object, not on the executing thread. Thread::AbortRequestLockHolder lock(pThread); oh = pThread->m_AbortReason; adid = pThread->m_AbortReasonDomainID; } // If the OBJECTHANDLE is not 0... if (oh != 0) { AppDomain *pCurrentDomain = pThread->GetDomain(); // See if the appdomain is equal to the appdomain of the currently running // thread. if (pCurrentDomain->GetId() == adid) { // Same appdomain; just return object from the OBJECTHANDLE refRetVal = ObjectFromHandle(oh); } else { // Otherwise, try to marshal the object from the other AppDomain ENTER_DOMAIN_ID(adid); CrossAppDomainClonerCallback cadcc; ObjectClone Cloner(&cadcc, CrossAppDomain, FALSE); refRetVal = Cloner.Clone(ObjectFromHandle(oh), GetAppDomain(), pCurrentDomain, NULL); Cloner.RemoveGCFrames(); END_DOMAIN_TRANSITION; } } HELPER_METHOD_FRAME_END() return OBJECTREFToObject(refRetVal); } FCIMPLEND #endif // !FEATURE_CORECLR FCIMPL1(void, ThreadNative::ClearAbortReason, ThreadBaseObject* pThisUNSAFE) { FCALL_CONTRACT; if (pThisUNSAFE==NULL) FCThrowResVoid(kNullReferenceException, W("NullReference_This")); Thread *pThread = pThisUNSAFE->GetInternal(); // Clearing from managed code can only happen on the current thread. _ASSERTE(pThread == GetThread()); HELPER_METHOD_FRAME_BEGIN_0(); if (pThread == NULL) COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); pThread->ClearAbortReason(); HELPER_METHOD_FRAME_END(); } FCIMPLEND