diff options
Diffstat (limited to 'src/vm/comsynchronizable.cpp')
-rw-r--r-- | src/vm/comsynchronizable.cpp | 2243 |
1 files changed, 2243 insertions, 0 deletions
diff --git a/src/vm/comsynchronizable.cpp b/src/vm/comsynchronizable.cpp new file mode 100644 index 0000000000..ef195bf5de --- /dev/null +++ b/src/vm/comsynchronizable.cpp @@ -0,0 +1,2243 @@ +// 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 <object.h> +#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<SharedState> 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<Thread *, DoNothing, ReleaseThreadExternalCount> 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<ContextTransitionFrame> 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<OBJECTREF>(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 + + |