Permalink
Fetching contributors…
Cannot retrieve contributors at this time
7805 lines (6675 sloc) 274 KB
// 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.
//
// threadsuspend.CPP
//
// This file contains the implementation of thread suspension. The implementation of thread suspension
// used to be spread through multiple places. That is why, many methods still live in their own homes
// (class Thread, class ThreadStore, etc.). They should be eventually refactored into class ThreadSuspend.
//
#include "common.h"
#include "threadsuspend.h"
#include "finalizerthread.h"
#include "dbginterface.h"
#include "mdaassistants.h"
// from ntstatus.h
#define STATUS_SUSPEND_COUNT_EXCEEDED ((NTSTATUS)0xC000004AL)
#define HIJACK_NONINTERRUPTIBLE_THREADS
bool ThreadSuspend::s_fSuspendRuntimeInProgress = false;
CLREvent* ThreadSuspend::g_pGCSuspendEvent = NULL;
ThreadSuspend::SUSPEND_REASON ThreadSuspend::m_suspendReason;
Thread* ThreadSuspend::m_pThreadAttemptingSuspendForGC;
CLREventBase * ThreadSuspend::s_hAbortEvt = NULL;
CLREventBase * ThreadSuspend::s_hAbortEvtCache = NULL;
// If you add any thread redirection function, make sure the debugger can 1) recognize the redirection
// function, and 2) retrieve the original CONTEXT. See code:Debugger.InitializeHijackFunctionAddress and
// code:DacDbiInterfaceImpl.RetrieveHijackedContext.
extern "C" void RedirectedHandledJITCaseForGCThreadControl_Stub(void);
extern "C" void RedirectedHandledJITCaseForDbgThreadControl_Stub(void);
extern "C" void RedirectedHandledJITCaseForUserSuspend_Stub(void);
#define GetRedirectHandlerForGCThreadControl() \
((PFN_REDIRECTTARGET) GetEEFuncEntryPoint(RedirectedHandledJITCaseForGCThreadControl_Stub))
#define GetRedirectHandlerForDbgThreadControl() \
((PFN_REDIRECTTARGET) GetEEFuncEntryPoint(RedirectedHandledJITCaseForDbgThreadControl_Stub))
#define GetRedirectHandlerForUserSuspend() \
((PFN_REDIRECTTARGET) GetEEFuncEntryPoint(RedirectedHandledJITCaseForUserSuspend_Stub))
#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
#if defined(HAVE_GCCOVER) && defined(USE_REDIRECT_FOR_GCSTRESS) // GCCOVER
extern "C" void RedirectedHandledJITCaseForGCStress_Stub(void);
#define GetRedirectHandlerForGCStress() \
((PFN_REDIRECTTARGET) GetEEFuncEntryPoint(RedirectedHandledJITCaseForGCStress_Stub))
#endif // HAVE_GCCOVER && USE_REDIRECT_FOR_GCSTRESS
#endif // _TARGET_AMD64_ || _TARGET_ARM_
// Every PING_JIT_TIMEOUT ms, check to see if a thread in JITted code has wandered
// into some fully interruptible code (or should have a different hijack to improve
// our chances of snagging it at a safe spot).
#define PING_JIT_TIMEOUT 10
// When we find a thread in a spot that's not safe to abort -- how long to wait before
// we try again.
#define ABORT_POLL_TIMEOUT 10
#ifdef _DEBUG
#define ABORT_FAIL_TIMEOUT 40000
#endif // _DEBUG
//
// CANNOT USE IsBad*Ptr() methods here. They are *banned* APIs because of various
// reasons (see http://winweb/wincet/bannedapis.htm).
//
#define IS_VALID_WRITE_PTR(addr, size) _ASSERTE(addr != NULL)
#define IS_VALID_CODE_PTR(addr) _ASSERTE(addr != NULL)
void ThreadSuspend::SetSuspendRuntimeInProgress()
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(ThreadStore::HoldingThreadStore() || IsAtProcessExit());
_ASSERTE(!s_fSuspendRuntimeInProgress || IsAtProcessExit());
s_fSuspendRuntimeInProgress = true;
}
void ThreadSuspend::ResetSuspendRuntimeInProgress()
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(ThreadStore::HoldingThreadStore() || IsAtProcessExit());
_ASSERTE(s_fSuspendRuntimeInProgress || IsAtProcessExit());
s_fSuspendRuntimeInProgress = false;
}
// When SuspendThread returns, target thread may still be executing user code.
// We can not access data, e.g. m_fPreemptiveGCDisabled, changed by target thread.
// But our code depends on reading these data. To make this operation safe, we
// call GetThreadContext which returns only after target thread does not execute
// any user code.
// Message from David Cutler
/*
After SuspendThread returns, can the suspended thread continue to execute code in user mode?
[David Cutler] The suspended thread cannot execute any more user code, but it might be currently "running"
on a logical processor whose other logical processor is currently actually executing another thread.
In this case the target thread will not suspend until the hardware switches back to executing instructions
on its logical processor. In this case even the memory barrier would not necessarily work - a better solution
would be to use interlocked operations on the variable itself.
After SuspendThread returns, does the store buffer of the CPU for the suspended thread still need to drain?
Historically, we've assumed that the answer to both questions is No. But on one 4/8 hyper-threaded machine
running Win2K3 SP1 build 1421, we've seen two stress failures where SuspendThread returns while writes seem to still be in flight.
Usually after we suspend a thread, we then call GetThreadContext. This seems to guarantee consistency.
But there are places we would like to avoid GetThreadContext, if it's safe and legal.
[David Cutler] Get context delivers a APC to the target thread and waits on an event that will be set
when the target thread has delivered its context.
Chris.
*/
// Message from Neill Clift
/*
What SuspendThread does is insert an APC block into a target thread and request an inter-processor interrupt to
do the APC interrupt. It doesn't wait till the thread actually enters some state or the interrupt has been serviced.
I took a quick look at the APIC spec in the Intel manuals this morning. Writing to the APIC posts a message on a bus.
Processors accept messages and presumably queue the s/w interrupts at this time. We don't wait for this acceptance
when we send the IPI so at least on APIC machines when you suspend a thread it continues to execute code for some short time
after the routine returns. We use other mechanisms for IPI and so it could work differently on different h/w.
*/
BOOL EnsureThreadIsSuspended (HANDLE hThread, Thread* pThread)
{
STATIC_CONTRACT_NOTHROW;
STATIC_CONTRACT_GC_NOTRIGGER;
WRAPPER_NO_CONTRACT;
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_INTEGER;
BOOL ret;
ret = ::GetThreadContext(hThread, &ctx);
return ret;
}
FORCEINLINE VOID MyEnterLogLock()
{
EnterLogLock();
}
FORCEINLINE VOID MyLeaveLogLock()
{
LeaveLogLock();
}
// On non-Windows CORECLR platforms remove Thread::SuspendThread support
#ifndef DISABLE_THREADSUSPEND
// SuspendThread
// Attempts to OS-suspend the thread, whichever GC mode it is in.
// Arguments:
// fOneTryOnly - If TRUE, report failure if the thread has its
// m_dwForbidSuspendThread flag set. If FALSE, retry.
// pdwSuspendCount - If non-NULL, will contain the return code
// of the underlying OS SuspendThread call on success,
// undefined on any kind of failure.
// Return value:
// A SuspendThreadResult value indicating success or failure.
Thread::SuspendThreadResult Thread::SuspendThread(BOOL fOneTryOnly, DWORD *pdwSuspendCount)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
#ifdef STRESS_LOG
if (StressLog::StressLogOn((unsigned int)-1, 0))
{
// Make sure to create the stress log for the current thread
// (if needed) before we suspend the target thread. The target
// thread may be holding the stress log lock when we suspend it,
// which could cause a deadlock.
if (StressLog::CreateThreadStressLog() == NULL)
{
return STR_NoStressLog;
}
}
#endif
Volatile<HANDLE> hThread;
SuspendThreadResult str = (SuspendThreadResult) -1;
DWORD dwSuspendCount = 0;
DWORD tries = 1;
#if defined(_DEBUG)
int nCnt = 0;
bool bDiagSuspend = g_pConfig->GetDiagnosticSuspend();
ULONGLONG i64TimestampStart = CLRGetTickCount64();
ULONGLONG i64TimestampCur = i64TimestampStart;
ULONGLONG i64TimestampPrev = i64TimestampStart;
// This is the max allowed timestamp ticks to transpire from beginning of
// our attempt to suspend the thread, before we'll assert (implying we believe
// there might be a deadlock) - (default = 2000).
ULONGLONG i64TimestampTicksMax = g_pConfig->SuspendThreadDeadlockTimeoutMs();
#endif // _DEBUG
#if defined(_DEBUG)
// Stop the stress log from allocating any new memory while in this function
// as that can lead to deadlocks
CantAllocHolder hldrCantAlloc;
#endif
DWORD dwSwitchCount = 0;
while (TRUE) {
StateHolder<MyEnterLogLock, MyLeaveLogLock> LogLockHolder(FALSE);
CounterHolder handleHolder(&m_dwThreadHandleBeingUsed);
// Whether or not "goto retry" should YieldProcessor and __SwitchToThread
BOOL doSwitchToThread = TRUE;
hThread = GetThreadHandle();
if (hThread == INVALID_HANDLE_VALUE) {
str = STR_UnstartedOrDead;
break;
}
else if (hThread == SWITCHOUT_HANDLE_VALUE) {
str = STR_SwitchedOut;
break;
}
{
// We do not want to suspend the target thread while it is holding the log lock.
// By acquiring the lock ourselves, we know that this is not the case.
LogLockHolder.Acquire();
// It is important to avoid two threads suspending each other.
// Before a thread suspends another, it increments its own m_dwForbidSuspendThread count first,
// then it checks the target thread's m_dwForbidSuspendThread.
ForbidSuspendThreadHolder forbidSuspend;
if ((m_dwForbidSuspendThread != 0))
{
#if defined(_DEBUG)
// Enable the diagnostic ::SuspendThread() if the
// DiagnosticSuspend config setting is set.
// This will interfere with the mutual suspend race but it's
// here only for diagnostic purposes anyway
if (!bDiagSuspend)
#endif // _DEBUG
goto retry;
}
dwSuspendCount = ::SuspendThread(hThread);
//
// Since SuspendThread is asynchronous, we now must wait for the thread to
// actually be suspended before decrementing our own m_dwForbidSuspendThread count.
// Otherwise there would still be a chance for the "suspended" thread to suspend us
// before it really stops running.
//
if ((int)dwSuspendCount >= 0)
{
if (!EnsureThreadIsSuspended(hThread, this))
{
::ResumeThread(hThread);
str = STR_Failure;
break;
}
}
}
if ((int)dwSuspendCount >= 0)
{
if (hThread == GetThreadHandle())
{
if (m_dwForbidSuspendThread != 0)
{
#if defined(_DEBUG)
// Log diagnostic below 8 times during the i64TimestampTicksMax period
if (i64TimestampCur-i64TimestampStart >= nCnt*(i64TimestampTicksMax>>3) )
{
CONTEXT ctx;
SetIP(&ctx, -1);
ctx.ContextFlags = CONTEXT_CONTROL;
this->GetThreadContext(&ctx);
STRESS_LOG7(LF_SYNC, LL_INFO1000,
"Thread::SuspendThread[%p]: EIP=%p. nCnt=%d. result=%d.\n"
"\t\t\t\t\t\t\t\t\t forbidSuspend=%d. coop=%d. state=%x.\n",
this, GetIP(&ctx), nCnt, dwSuspendCount,
(LONG)this->m_dwForbidSuspendThread, (ULONG)this->m_fPreemptiveGCDisabled, this->GetSnapshotState());
// Enable a preemptive assert in diagnostic mode: before we
// resume the target thread to get its current state in the debugger
if (bDiagSuspend)
{
// triggered after 6 * 250msec
_ASSERTE(nCnt < 6 && "Timing out in Thread::SuspendThread");
}
++nCnt;
}
#endif // _DEBUG
::ResumeThread(hThread);
#if defined(_DEBUG)
// If the suspend diagnostics are enabled we need to spin here in order to avoid
// the case where we Suspend/Resume the target thread without giving it a chance to run.
if ((!fOneTryOnly) && bDiagSuspend)
{
while ( m_dwForbidSuspendThread != 0 &&
CLRGetTickCount64()-i64TimestampStart < nCnt*(i64TimestampTicksMax>>3) )
{
if (g_SystemInfo.dwNumberOfProcessors > 1)
{
if ((tries++) % 20 != 0)
{
YieldProcessor(); // play nice on hyperthreaded CPUs
} else {
__SwitchToThread(0, ++dwSwitchCount);
}
}
else
{
__SwitchToThread(0, ++dwSwitchCount); // don't spin on uniproc machines
}
}
}
#endif // _DEBUG
goto retry;
}
// We suspend the right thread
#ifdef _DEBUG
Thread * pCurThread = GetThread();
if (pCurThread != NULL)
{
pCurThread->dbg_m_cSuspendedThreads ++;
_ASSERTE(pCurThread->dbg_m_cSuspendedThreads > 0);
}
#endif
IncCantAllocCount();
m_ThreadHandleForResume = hThread;
str = STR_Success;
break;
}
else
{
// A thread was switch out but in again.
// We suspend a wrong thread.
::ResumeThread(hThread);
doSwitchToThread = FALSE;
goto retry;
}
}
else {
// We can get here either SuspendThread fails
// Or the fiber thread dies after this fiber switched out.
if ((int)dwSuspendCount != -1) {
STRESS_LOG1(LF_SYNC, LL_INFO1000, "In Thread::SuspendThread ::SuspendThread returned %x\n", dwSuspendCount);
}
if (GetThreadHandle() == SWITCHOUT_HANDLE_VALUE) {
str = STR_SwitchedOut;
break;
}
else {
// Our callers generally expect that STR_Failure means that
// the thread has exited.
#ifndef FEATURE_PAL
_ASSERTE(NtCurrentTeb()->LastStatusValue != STATUS_SUSPEND_COUNT_EXCEEDED);
#endif // !FEATURE_PAL
str = STR_Failure;
break;
}
}
retry:
handleHolder.Release();
LogLockHolder.Release();
if (fOneTryOnly)
{
str = STR_Forbidden;
break;
}
#if defined(_DEBUG)
i64TimestampPrev = i64TimestampCur;
i64TimestampCur = CLRGetTickCount64();
// CLRGetTickCount64() is global per machine (not per CPU, like getTimeStamp()).
// Next ASSERT states that CLRGetTickCount64() is increasing, or has wrapped.
// If it wrapped, the last iteration should have executed faster then 0.5 seconds.
_ASSERTE(i64TimestampCur >= i64TimestampPrev || i64TimestampCur <= 500);
if (i64TimestampCur - i64TimestampStart >= i64TimestampTicksMax)
{
dwSuspendCount = ::SuspendThread(hThread);
_ASSERTE(!"It takes too long to suspend a thread");
if ((int)dwSuspendCount >= 0)
::ResumeThread(hThread);
}
#endif // _DEBUG
if (doSwitchToThread)
{
// When looking for deadlocks we need to allow the target thread to run in order to make some progress.
// On multi processor machines we saw the suspending thread resuming immediately after the __SwitchToThread()
// because it has another few processors available. As a consequence the target thread was being Resumed and
// Suspended right away, w/o a real chance to make any progress.
if (g_SystemInfo.dwNumberOfProcessors > 1)
{
if ((tries++) % 20 != 0) {
YieldProcessor(); // play nice on hyperthreaded CPUs
} else {
__SwitchToThread(0, ++dwSwitchCount);
}
}
else
{
__SwitchToThread(0, ++dwSwitchCount); // don't spin on uniproc machines
}
}
}
#ifdef PROFILING_SUPPORTED
{
BEGIN_PIN_PROFILER(CORProfilerTrackSuspends());
if (str == STR_Success)
{
g_profControlBlock.pProfInterface->RuntimeThreadSuspended((ThreadID)this);
}
END_PIN_PROFILER();
}
#endif // PROFILING_SUPPORTED
if (pdwSuspendCount != NULL)
{
*pdwSuspendCount = dwSuspendCount;
}
_ASSERTE(str != (SuspendThreadResult) -1);
return str;
}
#endif // DISABLE_THREADSUSPEND
// On non-Windows CORECLR platforms remove Thread::ResumeThread support
#ifndef DISABLE_THREADSUSPEND
DWORD Thread::ResumeThread()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
SO_TOLERANT;
MODE_ANY;
}
CONTRACTL_END;
_ASSERTE (m_ThreadHandleForResume != INVALID_HANDLE_VALUE);
_ASSERTE (GetThreadHandle() != SWITCHOUT_HANDLE_VALUE);
//DWORD res = ::ResumeThread(GetThreadHandle());
DWORD res = ::ResumeThread(m_ThreadHandleForResume);
_ASSERTE (res != 0 && "Thread is not previously suspended");
#ifdef _DEBUG_IMPL
_ASSERTE (!m_Creater.IsCurrentThread());
if ((res != (DWORD)-1) && (res != 0))
{
Thread * pCurThread = GetThread();
if (pCurThread != NULL)
{
_ASSERTE(pCurThread->dbg_m_cSuspendedThreads > 0);
pCurThread->dbg_m_cSuspendedThreads --;
_ASSERTE(pCurThread->dbg_m_cSuspendedThreadsWithoutOSLock <= pCurThread->dbg_m_cSuspendedThreads);
}
}
#endif
if (res != (DWORD) -1 && res != 0)
{
DecCantAllocCount();
}
#ifdef PROFILING_SUPPORTED
{
BEGIN_PIN_PROFILER(CORProfilerTrackSuspends());
if ((res != 0) && (res != (DWORD)-1))
{
g_profControlBlock.pProfInterface->RuntimeThreadResumed((ThreadID)this);
}
END_PIN_PROFILER();
}
#endif
return res;
}
#endif // DISABLE_THREADSUSPEND
#ifdef _DEBUG
void* forceStackA;
// CheckSuspended
// Checks whether the given thread is currently suspended.
// Note that if we cannot determine the true suspension status
// of the thread, we succeed. Intended to be used in asserts
// in operations that require the target thread to be suspended.
// Arguments:
// pThread - The thread to examine.
// Return value:
// FALSE, if the thread is definitely not suspended.
// TRUE, otherwise.
static inline BOOL CheckSuspended(Thread *pThread)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
DEBUG_ONLY;
}
CONTRACTL_END;
_ASSERTE(GetThread() != pThread);
_ASSERTE(CheckPointer(pThread));
#ifndef DISABLE_THREADSUSPEND
// Only perform this test if we're allowed to call back into the host.
// Thread::SuspendThread contains several potential calls into the host.
if (CanThisThreadCallIntoHost())
{
DWORD dwSuspendCount;
Thread::SuspendThreadResult str = pThread->SuspendThread(FALSE, &dwSuspendCount);
forceStackA = &dwSuspendCount;
if (str == Thread::STR_Success)
{
pThread->ResumeThread();
return dwSuspendCount >= 1;
}
}
#endif // !DISABLE_THREADSUSPEND
return TRUE;
}
#endif //_DEBUG
BOOL EEGetThreadContext(Thread *pThread, CONTEXT *pContext)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
_ASSERTE(CheckSuspended(pThread));
BOOL ret = pThread->GetThreadContext(pContext);
STRESS_LOG6(LF_SYNC, LL_INFO1000, "Got thread context ret = %d EIP = %p ESP = %p EBP = %p, pThread = %p, ContextFlags = 0x%x\n",
ret, GetIP(pContext), GetSP(pContext), GetFP(pContext), pThread, pContext->ContextFlags);
return ret;
}
BOOL EESetThreadContext(Thread *pThread, const CONTEXT *pContext)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
#ifdef _TARGET_X86_
_ASSERTE(CheckSuspended(pThread));
#endif
BOOL ret = pThread->SetThreadContext(pContext);
STRESS_LOG6(LF_SYNC, LL_INFO1000, "Set thread context ret = %d EIP = %p ESP = %p EBP = %p, pThread = %p, ContextFlags = 0x%x\n",
ret, GetIP((CONTEXT*)pContext), GetSP((CONTEXT*)pContext), GetFP((CONTEXT*)pContext), pThread, pContext->ContextFlags);
return ret;
}
// The AbortReason must be cleared at the following times:
//
// 1. When the application performs a ResetAbort.
//
// 2. When the physical thread stops running. That's because we must eliminate any
// cycles that would otherwise be uncollectible, between the Reason and the Thread.
// Nobody can retrieve the Reason after the thread stops running anyway.
//
// We don't have to do any work when the AppDomain containing the Reason object is unloaded.
// That's because the HANDLE is released as part of the tear-down. The 'adid' prevents us
// from ever using the trash handle value thereafter.
void Thread::ClearAbortReason(BOOL pNoLock)
{
CONTRACTL
{
GC_NOTRIGGER;
MODE_COOPERATIVE;
NOTHROW;
}
CONTRACTL_END;
OBJECTHANDLE oh;
ADID adid;
if (pNoLock){
// Stash the fields so we can destroy the OBJECTHANDLE if appropriate.
oh = m_AbortReason;
adid = m_AbortReasonDomainID;
// Clear the fields.
m_AbortReason = 0;
m_AbortReasonDomainID = ADID(INVALID_APPDOMAIN_ID);
}
else
// Scope the lock to stashing and clearing the two fields on the Thread object.
{
// Atomically get the OBJECTHANDLE and ADID of the object, and then
// clear them.
// NOTE: get the lock on this thread object, not on the executing thread.
Thread::AbortRequestLockHolder lock(this);
// Stash the fields so we can destroy the OBJECTHANDLE if appropriate.
oh = m_AbortReason;
adid = m_AbortReasonDomainID;
// Clear the fields.
m_AbortReason = 0;
m_AbortReasonDomainID = ADID(INVALID_APPDOMAIN_ID);
}
// If there is an OBJECTHANDLE, try to clear it.
if (oh != 0 && adid.m_dwId != 0)
{ // See if the domain is still valid; if so, destroy the ObjectHandle
AppDomainFromIDHolder ad(adid, TRUE);
if (!ad.IsUnloaded())
{ // Still a valid domain, so destroy the handle.
DestroyHandle(oh);
}
}
}
// Context passed down through a stack crawl (see code below).
struct StackCrawlContext
{
enum SCCType
{
SCC_CheckWithinEH = 0x00000001,
SCC_CheckWithinCer = 0x00000002,
};
Thread* pAbortee;
int eType;
BOOL fUnprotectedCode;
BOOL fWithinEHClause;
BOOL fWithinCer;
BOOL fHasManagedCodeOnStack;
BOOL fWriteToStressLog;
BOOL fHaveLatchedCF;
CrawlFrame LatchedCF;
};
// Crawl the stack looking for Thread Abort related information (whether we're executing inside a CER or an error handling clauses
// of some sort).
static StackWalkAction TAStackCrawlCallBackWorker(CrawlFrame* pCf, StackCrawlContext *pData)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
_ASSERTE(pData->eType & (StackCrawlContext::SCC_CheckWithinCer | StackCrawlContext::SCC_CheckWithinEH));
if(pCf->IsFrameless())
{
IJitManager* pJitManager = pCf->GetJitManager();
_ASSERTE(pJitManager);
if (pJitManager && !pData->fHasManagedCodeOnStack)
{
pData->fHasManagedCodeOnStack = TRUE;
}
}
// Get the method for this frame if it exists (might not be a managed method, so check the explicit frame if that's what we're
// looking at).
MethodDesc *pMD = pCf->GetFunction();
Frame *pFrame = pCf->GetFrame();
if (pMD == NULL && pFrame != NULL)
pMD = pFrame->GetFunction();
// Non-method frames don't interest us.
if (pMD == NULL)
return SWA_CONTINUE;
#if defined(_DEBUG)
#define METHODNAME(pFunc) (pFunc?pFunc->m_pszDebugMethodName:"<n/a>")
#else
#define METHODNAME(pFunc) "<n/a>"
#endif
if (pData->fWriteToStressLog)
{
STRESS_LOG5(LF_EH, LL_INFO100, "TAStackCrawlCallBack: STACKCRAWL method:%pM ('%s'), offset %x, Frame:%p, FrameVtable = %pV\n",
pMD, METHODNAME(pMD), pCf->IsFrameless()?pCf->GetRelOffset():0, pFrame, pCf->IsFrameless()?0:(*(void**)pFrame));
}
#undef METHODNAME
// If we weren't asked about EH clauses then we can return now (stop the stack trace if we have a definitive answer on the CER
// question, move to the next frame otherwise).
if ((pData->eType & StackCrawlContext::SCC_CheckWithinEH) == 0)
return ((pData->fWithinCer || pData->fUnprotectedCode) && pData->fHasManagedCodeOnStack) ? SWA_ABORT : SWA_CONTINUE;
// If we already discovered we're within an EH clause but are still processing (presumably to determine whether we're within a
// CER), then we can just skip to the next frame straight away. Also terminate here if the current frame is not frameless since
// there isn't any useful EH information for non-managed frames.
if (pData->fWithinEHClause || !pCf->IsFrameless())
return SWA_CONTINUE;
IJitManager* pJitManager = pCf->GetJitManager();
_ASSERTE(pJitManager);
EH_CLAUSE_ENUMERATOR pEnumState;
unsigned EHCount = pJitManager->InitializeEHEnumeration(pCf->GetMethodToken(), &pEnumState);
if (EHCount == 0)
// We do not have finally clause here.
return SWA_CONTINUE;
DWORD offs = (DWORD)pCf->GetRelOffset();
if (!pCf->IsActiveFrame())
{
// If we aren't the topmost method, then our IP is a return address and
// we can't use it to directly compare against the EH ranges because we
// may be in an cloned finally which has a call as the last instruction.
offs--;
}
if (pData->fWriteToStressLog)
{
STRESS_LOG1(LF_EH, LL_INFO100, "TAStackCrawlCallBack: STACKCRAWL Offset 0x%x V\n", offs);
}
EE_ILEXCEPTION_CLAUSE EHClause;
StackWalkAction action = SWA_CONTINUE;
#ifndef WIN64EXCEPTIONS
// On X86, the EH encoding for catch clause is completely mess.
// If catch clause is in its own basic block, the end of catch includes everything in the basic block.
// For nested catch, the end of catch may include several jmp instructions after JIT_EndCatch call.
// To better decide if we are inside a nested catch, we check if offs-1 is in more than one catch clause.
DWORD countInCatch = 0;
BOOL fAtJitEndCatch = FALSE;
if (pData->pAbortee == GetThread() &&
pData->pAbortee->ThrewControlForThread() == Thread::InducedThreadRedirectAtEndOfCatch &&
GetControlPC(pCf->GetRegisterSet()) == (PCODE)GetIP(pData->pAbortee->GetAbortContext()))
{
fAtJitEndCatch = TRUE;
offs -= 1;
}
#endif // !WIN64EXCEPTIONS
for(ULONG i=0; i < EHCount; i++)
{
pJitManager->GetNextEHClause(&pEnumState, &EHClause);
_ASSERTE(IsValidClause(&EHClause));
// !!! If this function is called on Aborter thread, we should check for finally only.
// !!! If this function is called on Aborter thread, we should check for finally only.
// !!! Catch and filter clause are skipped. In UserAbort, the first thing after ReadyForAbort
// !!! is to check if the target thread is processing exception.
// !!! If exception is in flight, we don't induce ThreadAbort. Instead at the end of Jit_EndCatch
// !!! we will handle abort.
if (pData->pAbortee != GetThread() && !IsFaultOrFinally(&EHClause))
{
continue;
}
if (offs >= EHClause.HandlerStartPC &&
offs < EHClause.HandlerEndPC)
{
#ifndef WIN64EXCEPTIONS
if (fAtJitEndCatch)
{
// On X86, JIT's EH info may include the instruction after JIT_EndCatch inside the same catch
// clause if it is in the same basic block.
// So for this case, the offs is in at least one catch handler, but since we are at the end of
// catch, this one should not be counted.
countInCatch ++;
if (countInCatch == 1)
{
continue;
}
}
#endif // !WIN64EXCEPTIONS
pData->fWithinEHClause = true;
// We're within an EH clause. If we're asking about CERs too then stop the stack walk if we've reached a conclusive
// result or continue looking otherwise. Else we can stop the stackwalk now.
if (pData->eType & StackCrawlContext::SCC_CheckWithinCer)
{
action = (pData->fWithinCer || pData->fUnprotectedCode) ? SWA_ABORT : SWA_CONTINUE;
}
else
{
action = SWA_ABORT;
}
break;
}
}
#ifndef WIN64EXCEPTIONS
#ifdef _DEBUG
if (fAtJitEndCatch)
{
_ASSERTE (countInCatch > 0);
}
#endif // _DEBUG
#endif // !WIN64EXCEPTIONS_
return action;
}
// Wrapper around code:TAStackCrawlCallBackWorker that abstracts away the differences between the reporting order
// of x86 and 64-bit stackwalker implementations, and also deals with interop calls that have an implicit reliability
// contract. If a P/Invoke or CLR->COM call returns SafeHandle or CriticalHandle, the IL stub could be aborted
// before having a chance to store the native handle into the Safe/CriticalHandle object. Therefore such calls are
// treated as unbreakable by convention.
StackWalkAction TAStackCrawlCallBack(CrawlFrame* pCf, void* data)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
StackCrawlContext *pData = (StackCrawlContext *)data;
// We have the current frame in pCf and possibly one latched frame in pData->LatchedCF. This enumeration
// describes which of these should be passed to code:TAStackCrawlCallBackWorker and in what order.
enum LatchedFrameAction
{
DiscardLatchedFrame, // forget the latched frame, report the current one
DiscardCurrentFrame, // ignore the current frame, report the latched one
ProcessLatchedInOrder, // report the latched frame, then report the current frame
ProcessLatchedReversed, // report the current frame, then report the latched frame
LatchCurrentFrame // latch the current frame, don't report anything
}
frameAction = DiscardLatchedFrame;
#ifdef _TARGET_X86_
// On X86 the IL stub method is reported to us before the frame with the actual interop method. We need to
// swap the order because if the worker saw the IL stub - which is a CER root - first, it would terminate the
// stack walk and wouldn't allow the thread to be aborted, regardless of how the interop method is annotated.
if (pData->fHaveLatchedCF)
{
// Does the current and latched frame represent the same call?
if (pCf->pFrame == pData->LatchedCF.pFrame)
{
if (pData->LatchedCF.GetFunction()->AsDynamicMethodDesc()->IsUnbreakable())
{
// Report only the latched IL stub frame which is a CER root.
frameAction = DiscardCurrentFrame;
}
else
{
// Report the interop method (current frame) which may be annotated, then the IL stub.
frameAction = ProcessLatchedReversed;
}
}
else
{
// The two frames are unrelated - process them in order.
frameAction = ProcessLatchedInOrder;
}
pData->fHaveLatchedCF = FALSE;
}
else
{
MethodDesc *pMD = pCf->GetFunction();
if (pMD != NULL && pMD->IsILStub() && InlinedCallFrame::FrameHasActiveCall(pCf->pFrame))
{
// This may be IL stub for an interesting interop call - latch it.
frameAction = LatchCurrentFrame;
}
}
#else // _TARGET_X86_
// On 64-bit the IL stub method is reported after the actual interop method so we don't have to swap them.
// However, we still want to discard the interop method frame if the call is unbreakable by convention.
if (pData->fHaveLatchedCF)
{
MethodDesc *pMD = pCf->GetFunction();
if (pMD != NULL && pMD->IsILStub() &&
pData->LatchedCF.GetFrame()->GetReturnAddress() == GetControlPC(pCf->GetRegisterSet()) &&
pMD->AsDynamicMethodDesc()->IsUnbreakable())
{
// The current and latched frame represent the same call and the IL stub is marked as unbreakable.
// We will discard the interop method and report only the IL stub which is a CER root.
frameAction = DiscardLatchedFrame;
}
else
{
// Otherwise process the two frames in order.
frameAction = ProcessLatchedInOrder;
}
pData->fHaveLatchedCF = FALSE;
}
else
{
MethodDesc *pMD = pCf->GetFunction();
if (pCf->GetFrame() != NULL && pMD != NULL && (pMD->IsNDirect() || pMD->IsComPlusCall()))
{
// This may be interop method of an interesting interop call - latch it.
frameAction = LatchCurrentFrame;
}
}
#endif // _TARGET_X86_
// Execute the "frame action".
StackWalkAction action;
switch (frameAction)
{
case DiscardLatchedFrame:
action = TAStackCrawlCallBackWorker(pCf, pData);
break;
case DiscardCurrentFrame:
action = TAStackCrawlCallBackWorker(&pData->LatchedCF, pData);
break;
case ProcessLatchedInOrder:
action = TAStackCrawlCallBackWorker(&pData->LatchedCF, pData);
if (action == SWA_CONTINUE)
action = TAStackCrawlCallBackWorker(pCf, pData);
break;
case ProcessLatchedReversed:
action = TAStackCrawlCallBackWorker(pCf, pData);
if (action == SWA_CONTINUE)
action = TAStackCrawlCallBackWorker(&pData->LatchedCF, pData);
break;
case LatchCurrentFrame:
pData->LatchedCF = *pCf;
pData->fHaveLatchedCF = TRUE;
action = SWA_CONTINUE;
break;
default:
UNREACHABLE();
}
return action;
}
// Is the current thread currently executing within a constrained execution region?
BOOL Thread::IsExecutingWithinCer()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
if (!g_fEEStarted)
return FALSE;
Thread *pThread = GetThread();
_ASSERTE (pThread);
StackCrawlContext sContext = { pThread,
StackCrawlContext::SCC_CheckWithinCer,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE};
pThread->StackWalkFrames(TAStackCrawlCallBack, &sContext);
#ifdef STRESS_LOG
if (sContext.fWithinCer && StressLog::StressLogOn(~0u, 0))
{
// If stress log is on, write info to stress log
StackCrawlContext sContext1 = { pThread,
StackCrawlContext::SCC_CheckWithinCer,
FALSE,
FALSE,
FALSE,
FALSE,
TRUE,
FALSE};
pThread->StackWalkFrames(TAStackCrawlCallBack, &sContext1);
}
#endif
return sContext.fWithinCer;
}
// Context structure used during stack walks to determine whether a given method is executing within a CER.
struct CerStackCrawlContext
{
MethodDesc *m_pStartMethod; // First method we crawl (here for debug purposes)
bool m_fFirstFrame; // True for first callback only
bool m_fWithinCer; // The result
};
// Determine whether the method at the given depth in the thread's execution stack is executing within a CER.
BOOL Thread::IsWithinCer(CrawlFrame *pCf)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
return FALSE;
}
#if defined(_TARGET_AMD64_) && defined(FEATURE_HIJACK)
BOOL Thread::IsSafeToInjectThreadAbort(PTR_CONTEXT pContextToCheck)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(pContextToCheck != NULL);
}
CONTRACTL_END;
EECodeInfo codeInfo(GetIP(pContextToCheck));
_ASSERTE(codeInfo.IsValid());
// Check if the method uses a frame register. If it does not, then RSP will be used by the OS as the frame register
// and returned as the EstablisherFrame. This is fine at any instruction in the method (including epilog) since there is always a
// difference of stackslot size between the callerSP and the callee SP due to return address having been pushed on the stack.
if (!codeInfo.HasFrameRegister())
{
return TRUE;
}
BOOL fSafeToInjectThreadAbort = TRUE;
if (IsIPInEpilog(pContextToCheck, &codeInfo, &fSafeToInjectThreadAbort))
{
return fSafeToInjectThreadAbort;
}
else
{
return TRUE;
}
}
#endif // defined(_TARGET_AMD64_) && defined(FEATURE_HIJACK)
#ifdef _TARGET_AMD64_
// CONTEXT_CONTROL does not include any nonvolatile registers that might be the frame pointer.
#define CONTEXT_MIN_STACKWALK (CONTEXT_CONTROL | CONTEXT_INTEGER)
#else
#define CONTEXT_MIN_STACKWALK (CONTEXT_CONTROL)
#endif
BOOL Thread::ReadyForAsyncException()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
SO_TOLERANT;
}
CONTRACTL_END;
if (!IsAbortRequested())
{
return FALSE;
}
if (IsAbortRequested() && HasThreadStateNC(TSNC_SOWorkNeeded))
{
return TRUE;
}
// This needs the probe with GenerateHardSO
CONTRACT_VIOLATION(SOToleranceViolation);
if (GetThread() == this && HasThreadStateNC (TSNC_PreparingAbort) && !IsRudeAbort() )
{
STRESS_LOG0(LF_APPDOMAIN, LL_INFO10, "in Thread::ReadyForAbort PreparingAbort\n");
// Avoid recursive call
return FALSE;
}
if (IsAbortPrevented())
{
//
// If the thread is marked to have a FuncEval abort request, then allow that to go through
// since we dont want to block funcEval aborts. Such requests are initiated by the
// right-side when the thread is doing funcEval and the exception would be caught in the
// left-side's funcEval implementation that will then clear the funcEval-abort-state from the thread.
//
// If another thread also marked this one for a non-FuncEval abort, then the left-side will
// proceed to [re]throw that exception post funcEval abort. When we come here next, we would follow
// the usual rules to raise the exception and if raised, to prevent the abort if applicable.
//
if (!IsFuncEvalAbort())
{
STRESS_LOG0(LF_APPDOMAIN, LL_INFO10, "in Thread::ReadyForAbort prevent abort\n");
return FALSE;
}
}
// The thread requests not to be aborted. Honor this for safe abort.
if (!IsRudeAbort() && IsAsyncPrevented())
{
STRESS_LOG0(LF_APPDOMAIN, LL_INFO10, "in Thread::ReadyForAbort AsyncPrevented\n");
return FALSE;
}
// If we are doing safe abort, we can not abort a thread if it has locks.
if (m_AbortType == EEPolicy::TA_Safe && HasLockInCurrentDomain()) {
STRESS_LOG0(LF_APPDOMAIN, LL_INFO10, "in Thread::ReadyForAbort HasLock\n");
return FALSE;
}
REGDISPLAY rd;
Frame *pStartFrame = NULL;
if (ThrewControlForThread() == Thread::InducedThreadRedirect ||
ThrewControlForThread() == Thread::InducedThreadRedirectAtEndOfCatch)
{
_ASSERTE(GetThread() == this);
_ASSERTE(ExecutionManager::IsManagedCode(GetIP(m_OSContext)));
FillRegDisplay(&rd, m_OSContext);
if (ThrewControlForThread() == Thread::InducedThreadRedirectAtEndOfCatch)
{
// On 64bit, this function may be called from COMPlusCheckForAbort when
// stack has not unwound, but m_OSContext points to the place after unwind.
//
TADDR sp = GetSP(m_OSContext);
Frame *pFrameAddr = m_pFrame;
while (pFrameAddr < (LPVOID)sp)
{
pFrameAddr = pFrameAddr->Next();
}
if (pFrameAddr != m_pFrame)
{
pStartFrame = pFrameAddr;
}
}
#if defined(_TARGET_AMD64_) && defined(FEATURE_HIJACK)
else if (ThrewControlForThread() == Thread::InducedThreadRedirect)
{
if (!IsSafeToInjectThreadAbort(m_OSContext))
{
STRESS_LOG0(LF_EH, LL_INFO10, "Thread::ReadyForAbort: Not injecting abort since we are at an unsafe instruction.\n");
return FALSE;
}
}
#endif // defined(_TARGET_AMD64_) && defined(FEATURE_HIJACK)
}
else
{
if (GetFilterContext())
{
FillRegDisplay(&rd, GetFilterContext());
}
else
{
CONTEXT ctx;
SetIP(&ctx, 0);
SetSP(&ctx, 0);
FillRegDisplay(&rd, &ctx);
}
}
#ifdef STRESS_LOG
REGDISPLAY rd1;
if (StressLog::StressLogOn(~0u, 0))
{
CONTEXT ctx1;
CopyRegDisplay(&rd, &rd1, &ctx1);
}
#endif
// Walk the stack to determine if we are running in Constrained Execution Region or finally EH clause (in the non-rude abort
// case). We cannot initiate an abort in these circumstances.
StackCrawlContext TAContext =
{
this,
StackCrawlContext::SCC_CheckWithinCer | (IsRudeAbort() ? 0 : StackCrawlContext::SCC_CheckWithinEH),
FALSE,
FALSE,
FALSE,
FALSE,
FALSE
};
StackWalkFramesEx(&rd, TAStackCrawlCallBack, &TAContext, QUICKUNWIND, pStartFrame);
if (!TAContext.fHasManagedCodeOnStack && IsAbortInitiated() && GetThread() == this)
{
EEResetAbort(TAR_Thread);
return FALSE;
}
if (TAContext.fWithinCer)
{
STRESS_LOG0(LF_APPDOMAIN, LL_INFO10, "in Thread::ReadyForAbort RunningCer\n");
return FALSE;
}
#ifdef STRESS_LOG
if (StressLog::StressLogOn(~0u, 0) &&
(IsRudeAbort() || !TAContext.fWithinEHClause))
{
//Save into stresslog.
StackCrawlContext TAContext1 =
{
this,
StackCrawlContext::SCC_CheckWithinCer | (IsRudeAbort() ? 0 : StackCrawlContext::SCC_CheckWithinEH),
FALSE,
FALSE,
FALSE,
FALSE,
TRUE
};
StackWalkFramesEx(&rd1, TAStackCrawlCallBack, &TAContext1, QUICKUNWIND, pStartFrame);
}
#endif
if (IsRudeAbort()) {
// If it is rude abort, there is no additional restriction on abort.
STRESS_LOG0(LF_APPDOMAIN, LL_INFO10, "in Thread::ReadyForAbort RudeAbort\n");
return TRUE;
}
if (TAContext.fWithinEHClause)
{
STRESS_LOG0(LF_APPDOMAIN, LL_INFO10, "in Thread::ReadyForAbort RunningEHClause\n");
}
//if (m_AbortType == EEPolicy::TA_V1Compatible) {
// return TRUE;
//}
// If we are running finally, we can not abort for Safe Abort.
return !TAContext.fWithinEHClause;
}
BOOL Thread::IsRudeAbort()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
SO_TOLERANT;
}
CONTRACTL_END;
return (IsAbortRequested() && (m_AbortType == EEPolicy::TA_Rude));
}
BOOL Thread::IsRudeAbortOnlyForADUnload()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
return (IsAbortRequested() &&
(m_AbortInfo & TAI_ADUnloadRudeAbort) &&
!(m_AbortInfo & (TAI_ThreadRudeAbort | TAI_FuncEvalRudeAbort))
);
}
BOOL Thread::IsRudeUnload()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
return (IsAbortRequested() && (m_AbortInfo & TAI_ADUnloadRudeAbort));
}
BOOL Thread::IsFuncEvalAbort()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
return (IsAbortRequested() && (m_AbortInfo & TAI_AnyFuncEvalAbort));
}
//
// If the OS is down in kernel mode when we do a GetThreadContext,any
// updates we make to the context will not take effect if we try to do
// a SetThreadContext. As a result, newer OSes expose the idea of
// "trap frame reporting" which will tell us if it is unsafe to modify
// the context and pass it along to SetThreadContext.
//
// On OSes that support trap frame reporting, we will return FALSE if
// we can determine that the OS is not in user mode. Otherwise, we
// return TRUE.
//
BOOL Thread::IsContextSafeToRedirect(CONTEXT* pContext)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
BOOL isSafeToRedirect = TRUE;
#ifndef FEATURE_PAL
#if !defined(_TARGET_X86_)
// In some cases (x86 WOW64, ARM32 on ARM64) Windows will not set the CONTEXT_EXCEPTION_REPORTING flag
// if the thread is executing in kernel mode (i.e. in the middle of a syscall or exception handling).
// Therefore, we should treat the absence of the CONTEXT_EXCEPTION_REPORTING flag as an indication that
// it is not safe to manipulate with the current state of the thread context.
// Note: the x86 WOW64 case is already handled in GetSafelyRedirectableThreadContext; in addition, this
// flag is never set on Windows7 x86 WOW64. So this check is valid for non-x86 architectures only.
isSafeToRedirect = (pContext->ContextFlags & CONTEXT_EXCEPTION_REPORTING) != 0;
#endif // !defined(_TARGET_X86_)
if (pContext->ContextFlags & CONTEXT_EXCEPTION_REPORTING)
{
if (pContext->ContextFlags & (CONTEXT_SERVICE_ACTIVE|CONTEXT_EXCEPTION_ACTIVE))
{
// cannot process exception
LOG((LF_ALWAYS, LL_WARNING, "thread [os id=0x08%x id=0x08%x] redirect failed due to ContextFlags of 0x%08x\n", m_OSThreadId, m_ThreadId, pContext->ContextFlags));
isSafeToRedirect = FALSE;
}
}
#endif // !FEATURE_PAL
return isSafeToRedirect;
}
void Thread::SetAbortEndTime(ULONGLONG endTime, BOOL fRudeAbort)
{
LIMITED_METHOD_CONTRACT;
{
AbortRequestLockHolder lh(this);
if (fRudeAbort)
{
if (endTime < m_RudeAbortEndTime)
{
m_RudeAbortEndTime = endTime;
}
}
else
{
if (endTime < m_AbortEndTime)
{
m_AbortEndTime = endTime;
}
}
}
}
#ifdef _PREFAST_
#pragma warning(push)
#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
#endif
HRESULT
Thread::UserAbort(ThreadAbortRequester requester,
EEPolicy::ThreadAbortTypes abortType,
DWORD timeout,
UserAbort_Client client
)
{
CONTRACTL
{
THROWS;
if (GetThread()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);}
}
CONTRACTL_END;
STRESS_LOG2(LF_SYNC | LF_APPDOMAIN, LL_INFO100, "UserAbort Thread %p Thread Id = %x\n", this, GetThreadId());
#ifdef _DEBUG
AddFiberInfo(ThreadTrackInfo_Abort);
#endif
BOOL fHoldingThreadStoreLock = ThreadStore::HoldingThreadStore();
// For SafeAbort from FuncEval abort, we do not apply escalation policy. Debugger
// tries SafeAbort first with a short timeout. The thread will return to debugger.
// After some break, the thread is going to do RudeAbort if abort has not finished.
EClrOperation operation;
if (abortType == EEPolicy::TA_Rude)
{
if (HasLockInCurrentDomain())
{
operation = OPR_ThreadRudeAbortInCriticalRegion;
}
else
{
operation = OPR_ThreadRudeAbortInNonCriticalRegion;
}
}
else
{
operation = OPR_ThreadAbort;
}
// Debugger func-eval aborts (both rude + normal) don't have any escalation policy. They are invoked
// by the debugger and the debugger handles the consequences.
// Furthermore, in interop-debugging, threads will be hard-suspened in preemptive mode while we try to abort them.
// So any abort strategy that relies on a timeout and the target thread slipping is dangerous. Escalation policy would let a
// host circumvent the timeout and thus we may wait forever for the target thread to slip. We'd deadlock here. Since the escalation
// policy doesn't let the host break this deadlock (and certianly doesn't let the debugger break the deadlock), it's unsafe
// to have an escalation policy for func-eval aborts at all.
BOOL fEscalation = (requester != TAR_FuncEval);
if (fEscalation)
{
EPolicyAction action = GetEEPolicy()->GetDefaultAction(operation, this);
switch (action)
{
case eAbortThread:
GetEEPolicy()->NotifyHostOnDefaultAction(operation,action);
break;
case eRudeAbortThread:
if (abortType != EEPolicy::TA_Rude)
{
abortType = EEPolicy::TA_Rude;
}
GetEEPolicy()->NotifyHostOnDefaultAction(operation,action);
break;
case eUnloadAppDomain:
{
AppDomain *pDomain = GetDomain();
if (!pDomain->IsDefaultDomain())
{
GetEEPolicy()->NotifyHostOnDefaultAction(operation,action);
pDomain->EnableADUnloadWorker(EEPolicy::ADU_Safe);
}
}
// AD unload does not abort finalizer thread.
if (this != FinalizerThread::GetFinalizerThread())
{
if (this == GetThread())
{
Join(INFINITE,TRUE);
}
return S_OK;
}
break;
case eRudeUnloadAppDomain:
{
AppDomain *pDomain = GetDomain();
if (!pDomain->IsDefaultDomain())
{
GetEEPolicy()->NotifyHostOnDefaultAction(operation,action);
pDomain->EnableADUnloadWorker(EEPolicy::ADU_Rude);
}
}
// AD unload does not abort finalizer thread.
if (this != FinalizerThread::GetFinalizerThread())
{
if (this == GetThread())
{
Join(INFINITE,TRUE);
}
return S_OK;
}
break;
case eExitProcess:
case eFastExitProcess:
case eRudeExitProcess:
case eDisableRuntime:
GetEEPolicy()->NotifyHostOnDefaultAction(operation,action);
EEPolicy::HandleExitProcessFromEscalation(action, HOST_E_EXITPROCESS_THREADABORT);
_ASSERTE (!"Should not reach here");
break;
default:
_ASSERTE (!"unknown policy for thread abort");
}
DWORD timeoutFromPolicy;
if (abortType != EEPolicy::TA_Rude)
{
timeoutFromPolicy = GetEEPolicy()->GetTimeout(OPR_ThreadAbort);
}
else if (!HasLockInCurrentDomain())
{
timeoutFromPolicy = GetEEPolicy()->GetTimeout(OPR_ThreadRudeAbortInNonCriticalRegion);
}
else
{
timeoutFromPolicy = GetEEPolicy()->GetTimeout(OPR_ThreadRudeAbortInCriticalRegion);
}
if (timeout > timeoutFromPolicy)
{
timeout = timeoutFromPolicy;
}
}
AbortControlHolder AbortController(this);
// Swap in timeout
if (timeout != INFINITE)
{
ULONG64 curTime = CLRGetTickCount64();
ULONG64 newEndTime = curTime + timeout;
SetAbortEndTime(newEndTime, abortType == EEPolicy::TA_Rude);
}
// If the abort comes from the thread abort watchdog, proceed with the abort only
// if the abort is still requested. This handles race between watchdog and UnmarkThreadForAbort.
BOOL fTentative = (requester == Thread::TAR_Thread) && (client == UAC_WatchDog);
MarkThreadForAbort(requester, abortType, fTentative);
Thread *pCurThread = GetThread();
// If aborting self
if (this == pCurThread)
{
SetAbortInitiated();
#ifdef _DEBUG
m_dwAbortPoint = 1;
#endif
if (CLRHosted() && GetAbortEndTime() != MAXULONGLONG)
{
// ToDo: Skip debugger funcval
// Use our helper thread to watch abort.
AppDomain::EnableADUnloadWorkerForThreadAbort();
}
GCX_COOP();
OBJECTREF exceptObj;
if (IsRudeAbort())
{
exceptObj = CLRException::GetPreallocatedRudeThreadAbortException();
}
else
{
EEException eeExcept(kThreadAbortException);
exceptObj = CLRException::GetThrowableFromException(&eeExcept);
}
RaiseTheExceptionInternalOnly(exceptObj, FALSE);
}
#ifdef MDA_SUPPORTED
if (requester != TAR_FuncEval)
{
// FuncEval abort is always aborting another thread. No need to trigger MDA.
MDA_TRIGGER_ASSISTANT(AsynchronousThreadAbort, ReportViolation(GetThread(), this));
}
#endif
_ASSERTE(this != pCurThread); // Aborting another thread.
if (client == UAC_Host)
{
// A host may call ICLRTask::Abort on a critical thread. We don't want to
// block this thread.
AppDomain::EnableADUnloadWorkerForThreadAbort();
return S_OK;
}
#ifdef _DEBUG
DWORD elapsed_time = 0;
#endif
// We do not want this thread to be alerted.
ThreadPreventAsyncHolder preventAsync(pCurThread != NULL);
#ifdef _DEBUG
// If UserAbort times out, put up msgbox once.
BOOL fAlreadyAssert = FALSE;
#endif
BOOL fOneTryOnly = (client == UAC_WatchDog) || (client == UAC_FinalizerTimeout);
BOOL fFirstRun = TRUE;
BOOL fNeedEscalation;
#if !defined(DISABLE_THREADSUSPEND)
DWORD dwSwitchCount = 0;
#endif // !defined(DISABLE_THREADSUSPEND)
LRetry:
fNeedEscalation = FALSE;
for (;;)
{
if (fOneTryOnly)
{
if (!fFirstRun)
{
return S_OK;
}
fFirstRun = FALSE;
}
// Lock the thread store
LOG((LF_SYNC, INFO3, "UserAbort obtain lock\n"));
ULONGLONG abortEndTime = GetAbortEndTime();
if (abortEndTime != MAXULONGLONG)
{
ULONGLONG now_time = CLRGetTickCount64();
if (now_time >= abortEndTime)
{
EPolicyAction action1 = eNoAction;
DWORD timeout1 = INFINITE;
if (fEscalation)
{
if (!IsRudeAbort())
{
action1 = GetEEPolicy()->GetActionOnTimeout(OPR_ThreadAbort, this);
timeout1 = GetEEPolicy()->GetTimeout(OPR_ThreadAbort);
}
else if (HasLockInCurrentDomain())
{
action1 = GetEEPolicy()->GetActionOnTimeout(OPR_ThreadRudeAbortInCriticalRegion, this);
timeout1 = GetEEPolicy()->GetTimeout(OPR_ThreadRudeAbortInCriticalRegion);
}
else
{
action1 = GetEEPolicy()->GetActionOnTimeout(OPR_ThreadRudeAbortInNonCriticalRegion, this);
timeout1 = GetEEPolicy()->GetTimeout(OPR_ThreadRudeAbortInNonCriticalRegion);
}
}
if (action1 == eNoAction)
{
// timeout, but no action on timeout.
// Debugger can call this function to about func-eval with a timeout
return HRESULT_FROM_WIN32(ERROR_TIMEOUT);
}
if (timeout1 != INFINITE)
{
// There is an escalation policy.
fNeedEscalation = TRUE;
break;
}
}
}
// Thread abort needs to walk stack to decide if thread abort can proceed.
// It is unsafe to crawl a stack of thread if the thread is OS-suspended which we do during
// thread abort. For example, Thread T1 aborts thread T2. T2 is suspended by T1. Inside SQL
// this means that no thread sharing the same scheduler with T2 can run. If T1 needs a lock which
// is owned by one thread on the scheduler, T1 will wait forever.
// Our solution is to move T2 to a safe point, resume it, and then do stack crawl.
// We need to make sure that ThreadStoreLock is released after CheckForAbort. This makes sure
// that ThreadAbort does not race against GC.
class CheckForAbort
{
private:
Thread *m_pThread;
BOOL m_fHoldingThreadStoreLock;
BOOL m_NeedRelease;
public:
CheckForAbort(Thread *pThread, BOOL fHoldingThreadStoreLock)
: m_pThread(pThread),
m_fHoldingThreadStoreLock(fHoldingThreadStoreLock),
m_NeedRelease(TRUE)
{
if (!fHoldingThreadStoreLock)
{
ThreadSuspend::LockThreadStore(ThreadSuspend::SUSPEND_OTHER);
}
ThreadStore::ResetStackCrawlEvent();
// The thread being aborted may clear the TS_AbortRequested bit and the matching increment
// of g_TrapReturningThreads behind our back. Increment g_TrapReturningThreads here
// to ensure that we stop for the stack crawl even if the TS_AbortRequested bit is cleared.
ThreadStore::TrapReturningThreads(TRUE);
}
void NeedStackCrawl()
{
m_pThread->SetThreadState(Thread::TS_StackCrawlNeeded);
}
~CheckForAbort()
{
Release();
}
void Release()
{
if (m_NeedRelease)
{
m_NeedRelease = FALSE;
ThreadStore::TrapReturningThreads(FALSE);
ThreadStore::SetStackCrawlEvent();
m_pThread->ResetThreadState(TS_StackCrawlNeeded);
if (!m_fHoldingThreadStoreLock)
{
ThreadSuspend::UnlockThreadStore();
}
}
}
};
CheckForAbort checkForAbort(this, fHoldingThreadStoreLock);
// We own TS lock. The state of the Thread can not be changed.
if (m_State & TS_Unstarted)
{
// This thread is not yet started.
#ifdef _DEBUG
m_dwAbortPoint = 2;
#endif
if(requester == Thread::TAR_Thread)
SetAborted();
return S_OK;
}
if (GetThreadHandle() == INVALID_HANDLE_VALUE &&
(m_State & TS_Unstarted) == 0)
{
// The thread is going to die or is already dead.
UnmarkThreadForAbort(Thread::TAR_ALL);
#ifdef _DEBUG
m_dwAbortPoint = 3;
#endif
if(requester == Thread::TAR_Thread)
SetAborted();
return S_OK;
}
// What if someone else has this thread suspended already? It'll depend where the
// thread got suspended.
//
// User Suspend:
// We'll just set the abort bit and hope for the best on the resume.
//
// GC Suspend:
// If it's suspended in jitted code, we'll hijack the IP.
// <REVISIT_TODO> Consider race w/ GC suspension</REVISIT_TODO>
// If it's suspended but not in jitted code, we'll get suspended for GC, the GC
// will complete, and then we'll abort the target thread.
//
// It's possible that the thread has completed the abort already.
//
if (!(m_State & TS_AbortRequested))
{
#ifdef _DEBUG
m_dwAbortPoint = 4;
#endif
if(requester == Thread::TAR_Thread)
SetAborted();
return S_OK;
}
// If a thread is Dead or Detached, abort is a NOP.
//
if (m_State & (TS_Dead | TS_Detached | TS_TaskReset))
{
UnmarkThreadForAbort(Thread::TAR_ALL);
if(requester == Thread::TAR_Thread)
SetAborted();
#ifdef _DEBUG
m_dwAbortPoint = 5;
#endif
return S_OK;
}
// It's possible that some stub notices the AbortRequested bit -- even though we
// haven't done any real magic yet. If the thread has already started it's abort, we're
// done.
//
// Two more cases can be folded in here as well. If the thread is unstarted, it'll
// abort when we start it.
//
// If the thread is user suspended (SyncSuspended) -- we're out of luck. Set the bit and
// hope for the best on resume.
//
if ((m_State & TS_AbortInitiated) && !IsRudeAbort())
{
#ifdef _DEBUG
m_dwAbortPoint = 6;
#endif
break;
}
BOOL fOutOfRuntime = FALSE;
BOOL fNeedStackCrawl = FALSE;
#ifdef DISABLE_THREADSUSPEND
// On platforms that do not support safe thread suspension we have to
// rely on the GCPOLL mechanism; the mechanism is activated above by
// TrapReturningThreads. However when reading shared state we need
// to erect appropriate memory barriers. So the interlocked operation
// below ensures that any future reads on this thread will happen after
// any earlier writes on a different thread have taken effect.
FastInterlockOr((DWORD*)&m_State, 0);
#else // DISABLE_THREADSUSPEND
// Win32 suspend the thread, so it isn't moving under us.
SuspendThreadResult str = SuspendThread();
switch (str)
{
case STR_Success:
break;
case STR_Failure:
case STR_UnstartedOrDead:
case STR_NoStressLog:
checkForAbort.Release();
__SwitchToThread(0, ++dwSwitchCount);
continue;
case STR_SwitchedOut:
// If the thread is in preemptive gc mode, we can erect a barrier to block the
// thread to return to cooperative mode. Then we can do stack crawl and make decision.
if (!m_fPreemptiveGCDisabled)
{
checkForAbort.NeedStackCrawl();
if (GetThreadHandle() != SWITCHOUT_HANDLE_VALUE || m_fPreemptiveGCDisabled)
{
checkForAbort.Release();
__SwitchToThread(0, ++dwSwitchCount);
continue;
}
else
{
goto LStackCrawl;
}
}
else
{
goto LPrepareRetry;
}
default:
UNREACHABLE();
}
_ASSERTE(str == STR_Success);
#endif // DISABLE_THREADSUSPEND
// It's possible that the thread has completed the abort already.
//
if (!(m_State & TS_AbortRequested))
{
#ifndef DISABLE_THREADSUSPEND
ResumeThread();
#endif
if(requester == Thread::TAR_Thread)
SetAborted();
#ifdef _DEBUG
m_dwAbortPoint = 63;
#endif
return S_OK;
}
// Check whether some stub noticed the AbortRequested bit in-between our test above
// and us suspending the thread.
if ((m_State & TS_AbortInitiated) && !IsRudeAbort())
{
#ifndef DISABLE_THREADSUSPEND
ResumeThread();
#endif
#ifdef _DEBUG
m_dwAbortPoint = 65;
#endif
break;
}
// If Threads is stopped under a managed debugger, it will have both
// TS_DebugSuspendPending and TS_SyncSuspended, regardless of whether
// the thread is actually suspended or not.
// If it's suspended w/o the debugger (eg, by via Thread.Suspend), it will
// also have TS_UserSuspendPending set.
if (m_State & TS_SyncSuspended)
{
#ifndef DISABLE_THREADSUSPEND
ResumeThread();
#endif
checkForAbort.Release();
#ifdef _DEBUG
m_dwAbortPoint = 7;
#endif
// CoreCLR does not support user-requested thread suspension
_ASSERTE(!(m_State & TS_UserSuspendPending));
//
// If it's stopped by the debugger, we don't want to throw an exception.
// Debugger suspension is to have no effect of the runtime behaviour.
//
if (m_State & TS_DebugSuspendPending)
{
return S_OK;
}
COMPlusThrow(kThreadStateException, IDS_EE_THREAD_ABORT_WHILE_SUSPEND);
}
// If the thread has no managed code on it's call stack, abort is a NOP. We're about
// to touch the unmanaged thread's stack -- for this to be safe, we can't be
// Dead/Detached/Unstarted.
//
_ASSERTE(!(m_State & ( TS_Dead
| TS_Detached
| TS_Unstarted)));
#if defined(_TARGET_X86_) && !defined(WIN64EXCEPTIONS)
// TODO WIN64: consider this if there is a way to detect of managed code on stack.
if ((m_pFrame == FRAME_TOP)
&& (GetFirstCOMPlusSEHRecord(this) == EXCEPTION_CHAIN_END)
)
{
#ifndef DISABLE_THREADSUSPEND
ResumeThread();
#endif
#ifdef _DEBUG
m_dwAbortPoint = 8;
#endif
if(requester == Thread::TAR_Thread)
SetAborted();
return S_OK;
}
#endif // _TARGET_X86_
if (!m_fPreemptiveGCDisabled)
{
if ((m_pFrame != FRAME_TOP) && m_pFrame->IsTransitionToNativeFrame()
#if defined(_TARGET_X86_) && !defined(WIN64EXCEPTIONS)
&& ((size_t) GetFirstCOMPlusSEHRecord(this) > ((size_t) m_pFrame) - 20)
#endif // _TARGET_X86_
)
{
fOutOfRuntime = TRUE;
}
}
checkForAbort.NeedStackCrawl();
if (!m_fPreemptiveGCDisabled)
{
fNeedStackCrawl = TRUE;
}
#if defined(FEATURE_HIJACK) && !defined(PLATFORM_UNIX)
else
{
HandleJITCaseForAbort();
}
#endif // FEATURE_HIJACK && !PLATFORM_UNIX
#ifndef DISABLE_THREADSUSPEND
// The thread is not suspended now.
ResumeThread();
#endif
if (!fNeedStackCrawl)
{
goto LPrepareRetry;
}
#ifndef DISABLE_THREADSUSPEND
LStackCrawl:
#endif // DISABLE_THREADSUSPEND
if (!ReadyForAbort()) {
goto LPrepareRetry;
}
// !!! Check for Exception in flight should happen before induced thread abort.
// !!! ReadyForAbort skips catch and filter clause.
// If an exception is currently being thrown, one of two things will happen. Either, we'll
// catch, and notice the abort request in our end-catch, or we'll not catch [in which case
// we're leaving managed code anyway. The top-most handler is responsible for resetting
// the bit.
//
if (HasException() &&
// For rude abort, we will initiated abort
!IsRudeAbort())
{
#ifdef _DEBUG
m_dwAbortPoint = 9;
#endif
break;
}
// If the thread is in sleep, wait, or join interrupt it
// However, we do NOT want to interrupt if the thread is already processing an exception
if (m_State & TS_Interruptible)
{
UserInterrupt(TI_Abort); // if the user wakes up because of this, it will read the
// abort requested bit and initiate the abort
#ifdef _DEBUG
m_dwAbortPoint = 10;
#endif
goto LPrepareRetry;
}
if (fOutOfRuntime)
{
// If the thread is running outside the EE, and is behind a stub that's going
// to catch...
#ifdef _DEBUG
m_dwAbortPoint = 11;
#endif
break;
}
// Ok. It's not in managed code, nor safely out behind a stub that's going to catch
// it on the way in. We have to poll.
LPrepareRetry:
checkForAbort.Release();
if (fOneTryOnly)
{
break;
}
// Don't do a Sleep. It's possible that the thread we are trying to abort is
// stuck in unmanaged code trying to get into the apartment that we are supposed
// to be pumping! Instead, ping the current thread's handle. Obviously this
// will time out, but it will pump if we need it to.
if (pCurThread)
{
pCurThread->Join(ABORT_POLL_TIMEOUT, TRUE);
}
else
{
ClrSleepEx(ABORT_POLL_TIMEOUT, FALSE);
}
#ifdef _DEBUG
elapsed_time += ABORT_POLL_TIMEOUT;
if (g_pConfig->GetGCStressLevel() == 0 && !fAlreadyAssert)
{
_ASSERTE(elapsed_time < ABORT_FAIL_TIMEOUT);
fAlreadyAssert = TRUE;
}
#endif
} // for(;;)
if (fOneTryOnly && !fNeedEscalation)
{
return S_OK;
}
if ((GetAbortEndTime() != MAXULONGLONG) && IsAbortRequested())
{
while (TRUE)
{
if (!IsAbortRequested())
{
return S_OK;
}
ULONGLONG curTime = CLRGetTickCount64();
if (curTime >= GetAbortEndTime())
{
break;
}
if (pCurThread)
{
pCurThread->Join(100, TRUE);
}
else
{
ClrSleepEx(100, FALSE);
}
}
if (IsAbortRequested() && fEscalation)
{
EPolicyAction action1;
EClrOperation operation1;
if (!IsRudeAbort())
{
operation1 = OPR_ThreadAbort;
}
else if (HasLockInCurrentDomain())
{
operation1 = OPR_ThreadRudeAbortInCriticalRegion;
}
else
{
operation1 = OPR_ThreadRudeAbortInNonCriticalRegion;
}
action1 = GetEEPolicy()->GetActionOnTimeout(operation1, this);
switch (action1)
{
case eRudeAbortThread:
GetEEPolicy()->NotifyHostOnTimeout(operation1, action1);
MarkThreadForAbort(requester, EEPolicy::TA_Rude);
SetRudeAbortEndTimeFromEEPolicy();
goto LRetry;
case eUnloadAppDomain:
{
AppDomain *pDomain = GetDomain();
if (!pDomain->IsDefaultDomain())
{
GetEEPolicy()->NotifyHostOnTimeout(operation1, action1);
pDomain->EnableADUnloadWorker(EEPolicy::ADU_Safe);
}
}
// AD unload does not abort finalizer thread.
if (this == FinalizerThread::GetFinalizerThread())
{
GetEEPolicy()->NotifyHostOnTimeout(operation1, action1);
MarkThreadForAbort(requester, EEPolicy::TA_Rude);
SetRudeAbortEndTimeFromEEPolicy();
goto LRetry;
}
else
{
if (this == GetThread())
{
Join(INFINITE,TRUE);
}
return S_OK;
}
break;
case eRudeUnloadAppDomain:
{
AppDomain *pDomain = GetDomain();
if (!pDomain->IsDefaultDomain())
{
GetEEPolicy()->NotifyHostOnTimeout(operation1, action1);
pDomain->EnableADUnloadWorker(EEPolicy::ADU_Rude);
}
}
// AD unload does not abort finalizer thread.
if (this == FinalizerThread::GetFinalizerThread())
{
MarkThreadForAbort(requester, EEPolicy::TA_Rude);
SetRudeAbortEndTimeFromEEPolicy();
goto LRetry;
}
else
{
if (this == GetThread())
{
Join(INFINITE,TRUE);
}
return S_OK;
}
break;
case eExitProcess:
case eFastExitProcess:
case eRudeExitProcess:
case eDisableRuntime:
GetEEPolicy()->NotifyHostOnTimeout(operation1, action1);
EEPolicy::HandleExitProcessFromEscalation(action1, HOST_E_EXITPROCESS_TIMEOUT);
_ASSERTE (!"Should not reach here");
break;
default:
break;
}
}
return HRESULT_FROM_WIN32(ERROR_TIMEOUT);
}
if(requester == Thread::TAR_Thread)
SetAborted();
return S_OK;
}
#ifdef _PREFAST_
#pragma warning(pop)
#endif
void Thread::SetRudeAbortEndTimeFromEEPolicy()
{
LIMITED_METHOD_CONTRACT;
DWORD timeout = GetEEPolicy()->GetTimeout(OPR_ThreadRudeAbortInCriticalRegion);
ULONGLONG newEndTime;
if (timeout == INFINITE)
{
newEndTime = MAXULONGLONG;
}
else
{
newEndTime = CLRGetTickCount64() + timeout;
}
SetAbortEndTime(newEndTime, TRUE);
}
ULONGLONG Thread::s_NextSelfAbortEndTime = MAXULONGLONG;
void Thread::ThreadAbortWatchDogAbort(Thread *pThread)
{
CONTRACTL
{
NOTHROW;
if (GetThread()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);}
}
CONTRACTL_END;
EEPolicy::ThreadAbortTypes abortType = EEPolicy::TA_Safe;
if (pThread->m_AbortInfo & TAI_ThreadRudeAbort)
{
abortType = EEPolicy::TA_Rude;
}
else if (pThread->m_AbortInfo & TAI_ThreadV1Abort)
{
abortType = EEPolicy::TA_V1Compatible;
}
else if (pThread->m_AbortInfo & TAI_ThreadAbort)
{
abortType = EEPolicy::TA_Safe;
}
else
{
return;
}
EX_TRY
{
pThread->UserAbort(Thread::TAR_Thread, abortType, INFINITE, Thread::UAC_WatchDog);
}
EX_CATCH
{
}
EX_END_CATCH(SwallowAllExceptions);
}
void Thread::ThreadAbortWatchDogEscalate(Thread *pThread)
{
CONTRACTL
{
NOTHROW;
if (GetThread()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);}
}
CONTRACTL_END;
EPolicyAction action = eNoAction;
EClrOperation operation = OPR_ThreadRudeAbortInNonCriticalRegion;
if (!pThread->IsRudeAbort())
{
operation = OPR_ThreadAbort;
}
else if (pThread->HasLockInCurrentDomain())
{
operation = OPR_ThreadRudeAbortInCriticalRegion;
}
else
{
operation = OPR_ThreadRudeAbortInNonCriticalRegion;
}
action = GetEEPolicy()->GetActionOnTimeout(operation, pThread);
// We only support escalation to rude abort
EX_TRY {
switch (action)
{
case eRudeAbortThread:
GetEEPolicy()->NotifyHostOnTimeout(operation,action);
pThread->UserAbort(Thread::TAR_Thread, EEPolicy::TA_Rude, INFINITE, Thread::UAC_WatchDog);
break;
case eUnloadAppDomain:
{
AppDomain *pDomain = pThread->GetDomain();
if (!pDomain->IsDefaultDomain())
{
GetEEPolicy()->NotifyHostOnTimeout(operation,action);
pDomain->EnableADUnloadWorker(EEPolicy::ADU_Safe);
}
}
break;
case eRudeUnloadAppDomain:
{
AppDomain *pDomain = pThread->GetDomain();
if (!pDomain->IsDefaultDomain())
{
GetEEPolicy()->NotifyHostOnTimeout(operation,action);
pDomain->EnableADUnloadWorker(EEPolicy::ADU_Rude);
}
}
break;
case eExitProcess:
case eFastExitProcess:
case eRudeExitProcess:
case eDisableRuntime:
// HandleExitProcessFromEscalation will try to grab ThreadStore again.
_ASSERTE (ThreadStore::HoldingThreadStore());
ThreadStore::UnlockThreadStore();
GetEEPolicy()->NotifyHostOnTimeout(operation,action);
EEPolicy::HandleExitProcessFromEscalation(action, HOST_E_EXITPROCESS_THREADABORT);
_ASSERTE (!"Should not reach here");
break;
case eNoAction:
break;
default:
_ASSERTE (!"unknown policy for thread abort");
}
}
EX_CATCH {
}
EX_END_CATCH(SwallowAllExceptions);
}
// If a thread is self-aborted and has a timeout, we need to watch the thread
void Thread::ThreadAbortWatchDog()
{
CONTRACTL
{
NOTHROW;
if (GetThread()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);}
}
CONTRACTL_END;
if (CLRHosted())
{
ThreadStoreLockHolder tsLock;
ULONGLONG curTime = CLRGetTickCount64();
s_NextSelfAbortEndTime = MAXULONGLONG;
Thread *thread = NULL;
while ((thread = ThreadStore::GetThreadList(thread)) != NULL)
{
if (!thread->IsAbortRequested())
{
continue;
}
if (thread == FinalizerThread::GetFinalizerThread() && !g_FinalizerIsRunning)
{
// if finalizer method is not running, don't try to abort the finalizer thread
continue;
}
BOOL fNeedsToInitiateAbort = !thread->IsAbortInitiated() || thread->IsRudeAbort();
ULONGLONG endTime = thread->GetAbortEndTime();
if (fNeedsToInitiateAbort)
{
s_NextSelfAbortEndTime = 0;
}
else if (endTime < s_NextSelfAbortEndTime)
{
s_NextSelfAbortEndTime = endTime;
}
if (thread->m_AbortController == 0)
{
STRESS_LOG3(LF_ALWAYS, LL_ALWAYS, "ThreadAbortWatchDog for Thread %p Thread Id = %x with timeout %x\n",
thread, thread->GetThreadId(), endTime);
if (endTime != MAXULONGLONG && curTime >= endTime)
{
ThreadAbortWatchDogEscalate(thread);
}
else if (fNeedsToInitiateAbort)
{
ThreadAbortWatchDogAbort(thread);
}
}
}
}
}
void Thread::LockAbortRequest(Thread* pThread)
{
WRAPPER_NO_CONTRACT;
DWORD dwSwitchCount = 0;
while (TRUE) {
for (unsigned i = 0; i < 10000; i ++) {
if (VolatileLoad(&(pThread->m_AbortRequestLock)) == 0) {
break;
}
YieldProcessor(); // indicate to the processor that we are spinning
}
if (FastInterlockCompareExchange(&(pThread->m_AbortRequestLock),1,0) == 0) {
return;
}
__SwitchToThread(0, ++dwSwitchCount);
}
}
void Thread::UnlockAbortRequest(Thread *pThread)
{
LIMITED_METHOD_CONTRACT;
_ASSERTE (pThread->m_AbortRequestLock == 1);
FastInterlockExchange(&pThread->m_AbortRequestLock, 0);
}
void Thread::MarkThreadForAbort(ThreadAbortRequester requester, EEPolicy::ThreadAbortTypes abortType, BOOL fTentative /*=FALSE*/)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
_ASSERTE ((requester & TAR_StackOverflow) == 0 || (requester & TAR_Thread) == TAR_Thread);
AbortRequestLockHolder lh(this);
if (fTentative)
{
if (!IsAbortRequested())
{
STRESS_LOG0(LF_SYNC, LL_INFO1000, "Tentative thread abort abandoned\n");
return;
}
}
#ifdef _DEBUG
if (abortType == EEPolicy::TA_Rude)
{
m_fRudeAborted = TRUE;
}
#endif
DWORD abortInfo = 0;
if (requester & TAR_Thread)
{
if (abortType == EEPolicy::TA_Safe)
{
abortInfo |= TAI_ThreadAbort;
}
else if (abortType == EEPolicy::TA_Rude)
{
abortInfo |= TAI_ThreadRudeAbort;
}
else if (abortType == EEPolicy::TA_V1Compatible)
{
abortInfo |= TAI_ThreadV1Abort;
}
}
if (requester & TAR_ADUnload)
{
if (abortType == EEPolicy::TA_Safe)
{
abortInfo |= TAI_ADUnloadAbort;
}
else if (abortType == EEPolicy::TA_Rude)
{
abortInfo |= TAI_ADUnloadRudeAbort;
}
else if (abortType == EEPolicy::TA_V1Compatible)
{
abortInfo |= TAI_ADUnloadV1Abort;
}
if (IsADUnloadHelperThread())
{
abortInfo |= TAI_ForADUnloadThread;
}
}
if (requester & TAR_FuncEval)
{
if (abortType == EEPolicy::TA_Safe)
{
abortInfo |= TAI_FuncEvalAbort;
}
else if (abortType == EEPolicy::TA_Rude)
{
abortInfo |= TAI_FuncEvalRudeAbort;
}
else if (abortType == EEPolicy::TA_V1Compatible)
{
abortInfo |= TAI_FuncEvalV1Abort;
}
}
if (abortInfo == 0)
{
ASSERT(!"Invalid abort information");
return;
}
if (requester == TAR_Thread)
{
DWORD timeoutFromPolicy;
if (abortType != EEPolicy::TA_Rude)
{
timeoutFromPolicy = GetEEPolicy()->GetTimeout(OPR_ThreadAbort);
}
else if (!HasLockInCurrentDomain())
{
timeoutFromPolicy = GetEEPolicy()->GetTimeout(OPR_ThreadRudeAbortInNonCriticalRegion);
}
else
{
timeoutFromPolicy = GetEEPolicy()->GetTimeout(OPR_ThreadRudeAbortInCriticalRegion);
}
if (timeoutFromPolicy != INFINITE)
{
ULONGLONG endTime = CLRGetTickCount64() + timeoutFromPolicy;
if (abortType != EEPolicy::TA_Rude)
{
if (endTime < m_AbortEndTime)
{
m_AbortEndTime = endTime;
}
}
else if (endTime < m_RudeAbortEndTime)
{
m_RudeAbortEndTime = endTime;
}
// We can not call into host if we are in the middle of stack overflow.
// And we don't need to wake up our watchdog if there is no timeout.
if (GetThread() == this && (requester & TAR_StackOverflow) == 0)
{
AppDomain::EnableADUnloadWorkerForThreadAbort();
}
}
}
if (abortInfo == (m_AbortInfo & abortInfo))
{
//
// We are already doing this kind of abort.
//
return;
}
m_AbortInfo |= abortInfo;
if (m_AbortType >= (DWORD)abortType)
{
// another thread is aborting at a higher level
return;
}
m_AbortType = abortType;
if (!IsAbortRequested())
{
// We must set this before we start flipping thread bits to avoid races where
// trap returning threads is already high due to other reasons.
// The thread is asked for abort the first time
SetAbortRequestBit();
#ifdef _DEBUG
AddFiberInfo(ThreadTrackInfo_Abort);
#endif
}
STRESS_LOG4(LF_APPDOMAIN, LL_ALWAYS, "Mark Thread %p Thread Id = %x for abort from requester %d (type %d)\n", this, GetThreadId(), requester, abortType);
}
void Thread::SetAbortRequestBit()
{
WRAPPER_NO_CONTRACT;
while (TRUE)
{
Volatile<LONG> curValue = (LONG)m_State;
if ((curValue & TS_AbortRequested) != 0)
{
break;
}
if (FastInterlockCompareExchange((LONG*)&m_State, curValue|TS_AbortRequested, curValue) == curValue)
{
ThreadStore::TrapReturningThreads(TRUE);
break;
}
}
}
void Thread::RemoveAbortRequestBit()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
#ifdef _DEBUG
// There's a race between removing the TS_AbortRequested bit and decrementing g_TrapReturningThreads
// We may remove the bit, but before we have a chance to call ThreadStore::TrapReturningThreads(FALSE)
// DbgFindThread() may execute, and find too few threads with the bit set.
// To ensure the assert in DbgFindThread does not fire under such a race we set the ChgInFlight before hand.
CounterHolder trtHolder(&g_trtChgInFlight);
#endif
while (TRUE)
{
Volatile<LONG> curValue = (LONG)m_State;
if ((curValue & TS_AbortRequested) == 0)
{
break;
}
if (FastInterlockCompareExchange((LONG*)&m_State, curValue&(~TS_AbortRequested), curValue) == curValue)
{
ThreadStore::TrapReturningThreads(FALSE);
break;
}
}
}
// Make sure that when AbortRequest bit is cleared, we also dec TrapReturningThreads count.
void Thread::UnmarkThreadForAbort(ThreadAbortRequester requester, BOOL fForce)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
// Switch to COOP (for ClearAbortReason) before acquiring AbortRequestLock
GCX_COOP();
AbortRequestLockHolder lh(this);
//
// Unmark the bits that are being turned off
//
if (requester & TAR_Thread)
{
if ((m_AbortInfo != TAI_ThreadRudeAbort) || fForce)
{
m_AbortInfo &= ~(TAI_ThreadAbort |
TAI_ThreadV1Abort |
TAI_ThreadRudeAbort );
}
if (m_AbortReason)
{
ClearAbortReason(TRUE);
}
}
if (requester & TAR_ADUnload)
{
m_AbortInfo &= ~(TAI_ADUnloadAbort |
TAI_ADUnloadV1Abort |
TAI_ADUnloadRudeAbort);
}
if (requester & TAR_FuncEval)
{
m_AbortInfo &= ~(TAI_FuncEvalAbort |
TAI_FuncEvalV1Abort |
TAI_FuncEvalRudeAbort);
}
//
// Decide which type of abort to do based on the new bit field.
//
if (m_AbortInfo & TAI_AnyRudeAbort)
{
m_AbortType = EEPolicy::TA_Rude;
}
else if (m_AbortInfo & TAI_AnyV1Abort)
{
m_AbortType = EEPolicy::TA_V1Compatible;
}
else if (m_AbortInfo & TAI_AnySafeAbort)
{
m_AbortType = EEPolicy::TA_Safe;
}
else
{
m_AbortType = EEPolicy::TA_None;
}
//
// If still aborting, do nothing
//
if (m_AbortType != EEPolicy::TA_None)
{
return;
}
m_AbortEndTime = MAXULONGLONG;
m_RudeAbortEndTime = MAXULONGLONG;
if (IsAbortRequested())
{
RemoveAbortRequestBit();
FastInterlockAnd((DWORD*)&m_State,~(TS_AbortInitiated));
m_fRudeAbortInitiated = FALSE;
ResetUserInterrupted();
#ifdef _DEBUG
AddFiberInfo(ThreadTrackInfo_Abort);
#endif
}
STRESS_LOG3(LF_APPDOMAIN, LL_ALWAYS, "Unmark Thread %p Thread Id = %x for abort from requester %d\n", this, GetThreadId(), requester);
}
// Make sure that when AbortRequest bit is cleared, we also dec TrapReturningThreads count.
void Thread::ResetBeginAbortedForADUnload()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
AbortRequestLockHolder lh(this);
m_AbortInfo &= ~TAI_ForADUnloadThread;
}
void Thread::InternalResetAbort(ThreadAbortRequester requester, BOOL fResetRudeAbort)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
_ASSERTE(this == GetThread());
_ASSERTE(!IsDead());
// managed code can not reset Rude thread abort
UnmarkThreadForAbort(requester, fResetRudeAbort);
#ifdef _DEBUG
AddFiberInfo(ThreadTrackInfo_Abort);
#endif
}
// Throw a thread abort request when a suspended thread is resumed. Make sure you know what you
// are doing when you call this routine.
void Thread::SetAbortRequest(EEPolicy::ThreadAbortTypes abortType)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
MarkThreadForAbort(TAR_ADUnload, abortType);
if (m_State & TS_Interruptible)
{
UserInterrupt(TI_Abort);
}
}
void ThreadSuspend::LockThreadStore(ThreadSuspend::SUSPEND_REASON reason)
{
CONTRACTL {
NOTHROW;
if ((GetThread() != NULL) && GetThread()->PreemptiveGCDisabled()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);}
}
CONTRACTL_END;
// There's a nasty problem here. Once we start shutting down because of a
// process detach notification, threads are disappearing from under us. There
// are a surprising number of cases where the dying thread holds the ThreadStore
// lock. For example, the finalizer thread holds this during startup in about
// 10 of our COM BVTs.
if (!IsAtProcessExit())
{
BOOL gcOnTransitions;
Thread *pCurThread = GetThread();
gcOnTransitions = GC_ON_TRANSITIONS(FALSE); // dont do GC for GCStress 3
BOOL toggleGC = ( pCurThread != NULL
&& pCurThread->PreemptiveGCDisabled()
&& reason != ThreadSuspend::SUSPEND_FOR_GC);
// Note: there is logic in gc.cpp surrounding suspending all
// runtime threads for a GC that depends on the fact that we
// do an EnablePreemptiveGC and a DisablePreemptiveGC around
// taking this lock.
if (toggleGC)
pCurThread->EnablePreemptiveGC();
LOG((LF_SYNC, INFO3, "Locking thread store\n"));
// Any thread that holds the thread store lock cannot be stopped by unmanaged breakpoints and exceptions when
// we're doing managed/unmanaged debugging. Calling SetDebugCantStop(true) on the current thread helps us
// remember that.
if (pCurThread)
pCurThread->SetDebugCantStop(true);
// This is used to avoid thread starvation if non-GC threads are competing for
// the thread store lock when there is a real GC-thread waiting to get in.
// This is initialized lazily when the first non-GC thread backs out because of
// a waiting GC thread.
if (s_hAbortEvt != NULL &&
!(reason == ThreadSuspend::SUSPEND_FOR_GC ||
reason == ThreadSuspend::SUSPEND_FOR_GC_PREP ||
reason == ThreadSuspend::SUSPEND_FOR_DEBUGGER_SWEEP) &&
m_pThreadAttemptingSuspendForGC != NULL &&
m_pThreadAttemptingSuspendForGC != pCurThread)
{
CLREventBase * hAbortEvt = s_hAbortEvt;
if (hAbortEvt != NULL)
{
LOG((LF_SYNC, INFO3, "Performing suspend abort wait.\n"));
hAbortEvt->Wait(INFINITE, FALSE);
LOG((LF_SYNC, INFO3, "Release from suspend abort wait.\n"));
}
}
// This is shutdown aware. If we're in shutdown, and not helper/finalizer/shutdown
// then this will not take the lock and just block forever.
ThreadStore::s_pThreadStore->Enter();
_ASSERTE(ThreadStore::s_pThreadStore->m_holderthreadid.IsUnknown());
ThreadStore::s_pThreadStore->m_holderthreadid.SetToCurrentThread();
LOG((LF_SYNC, INFO3, "Locked thread store\n"));
// Established after we obtain the lock, so only useful for synchronous tests.
// A thread attempting to suspend us asynchronously already holds this lock.
ThreadStore::s_pThreadStore->m_HoldingThread = pCurThread;
#ifndef _PREFAST_
if (toggleGC)
pCurThread->DisablePreemptiveGC();
#endif
GC_ON_TRANSITIONS(gcOnTransitions);
}
#ifdef _DEBUG
else
LOG((LF_SYNC, INFO3, "Locking thread store skipped upon detach\n"));
#endif
}
void ThreadSuspend::UnlockThreadStore(BOOL bThreadDestroyed, ThreadSuspend::SUSPEND_REASON reason)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;
// There's a nasty problem here. Once we start shutting down because of a
// process detach notification, threads are disappearing from under us. There
// are a surprising number of cases where the dying thread holds the ThreadStore
// lock. For example, the finalizer thread holds this during startup in about
// 10 of our COM BVTs.
if (!IsAtProcessExit())
{
Thread *pCurThread = GetThread();
LOG((LF_SYNC, INFO3, "Unlocking thread store\n"));
_ASSERTE(GetThread() == NULL || ThreadStore::s_pThreadStore->m_HoldingThread == GetThread());
#ifdef _DEBUG
// If Thread object has been destroyed, we need to reset the ownership info in Crst.
_ASSERTE(!bThreadDestroyed || GetThread() == NULL);
if (bThreadDestroyed) {
ThreadStore::s_pThreadStore->m_Crst.m_holderthreadid.SetToCurrentThread();
}
#endif
ThreadStore::s_pThreadStore->m_HoldingThread = NULL;
ThreadStore::s_pThreadStore->m_holderthreadid.Clear();
ThreadStore::s_pThreadStore->Leave();
// We're out of the critical area for managed/unmanaged debugging.
if (!bThreadDestroyed && pCurThread)
pCurThread->SetDebugCantStop(false);
}
#ifdef _DEBUG
else
LOG((LF_SYNC, INFO3, "Unlocking thread store skipped upon detach\n"));
#endif
}
void ThreadStore::AllocateOSContext()
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(HoldingThreadStore());
if (s_pOSContext == NULL
#ifdef _DEBUG
|| s_pOSContext == (CONTEXT*)0x1
#endif
)
{
s_pOSContext = new (nothrow) CONTEXT();
}
#ifdef _DEBUG
if (s_pOSContext == NULL)
{
s_pOSContext = (CONTEXT*)0x1;
}
#endif
}
CONTEXT *ThreadStore::GrabOSContext()
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(HoldingThreadStore());
CONTEXT *pContext = s_pOSContext;
s_pOSContext = NULL;
#ifdef _DEBUG
if (pContext == (CONTEXT*)0x1)
{
pContext = NULL;
}
#endif
return pContext;
}
extern void WaitForEndOfShutdown();
//----------------------------------------------------------------------------
//
// Suspending threads, rendezvousing with threads that reach safe places, etc.
//
//----------------------------------------------------------------------------
// A note on SUSPENSIONS.
//
// We must not suspend a thread while it is holding the ThreadStore lock, or
// the lock on the thread. Why? Because we need those locks to resume the
// thread (and to perform a GC, use the debugger, spawn or kill threads, etc.)
//
// There are two types of suspension we must consider to enforce the above
// rule. Synchronous suspensions are where we persuade the thread to suspend
// itself. This is CommonTripThread and its cousins. In other words, the
// thread toggles the GC mode, or it hits a hijack, or certain opcodes in the
// interpreter, etc. In these cases, the thread can simply check whether it
// is holding these locks before it suspends itself.
//
// The other style is an asynchronous suspension. This is where another
// thread looks to see where we are. If we are in a fully interruptible region
// of JIT code, we will be left suspended. In this case, the thread performing
// the suspension must hold the locks on the thread and the threadstore. This
// ensures that we aren't suspended while we are holding these locks.
//
// Note that in the asynchronous case it's not enough to just inspect the thread
// to see if it's holding these locks. Since the thread must be in preemptive
// mode to block to acquire these locks, and since there will be a few inst-
// ructions between acquiring the lock and noting in our state that we've
// acquired it, then there would be a window where we would seem eligible for
// suspension -- but in fact would not be.
//----------------------------------------------------------------------------
// We can't leave preemptive mode and enter cooperative mode, if a GC is
// currently in progress. This is the situation when returning back into
// the EE from outside. See the comments in DisablePreemptiveGC() to understand
// why we Enable GC here!
void Thread::RareDisablePreemptiveGC()
{
BEGIN_PRESERVE_LAST_ERROR;
CONTRACTL {
NOTHROW;
SO_TOLERANT;
DISABLED(GC_TRIGGERS); // I think this is actually wrong: prevents a p->c->p mode switch inside a NOTRIGGER region.
}
CONTRACTL_END;
CONTRACT_VIOLATION(SOToleranceViolation);
if (IsAtProcessExit())
{
goto Exit;
}
#ifdef _DEBUG
AddFiberInfo(ThreadTrackInfo_GCMode);
#endif
// This should NEVER be called if the TSNC_UnsafeSkipEnterCooperative bit is set!
_ASSERTE(!(m_StateNC & TSNC_UnsafeSkipEnterCooperative) && "DisablePreemptiveGC called while the TSNC_UnsafeSkipEnterCooperative bit is set");
// Holding a spin lock in preemp mode and switch to coop mode could cause other threads spinning
// waiting for GC
_ASSERTE ((m_StateNC & Thread::TSNC_OwnsSpinLock) == 0);
if (!GCHeapUtilities::IsGCHeapInitialized())
{
goto Exit;
}
// CoreCLR does not support user-requested thread suspension
_ASSERTE(!(m_State & TS_UserSuspendPending));
// Note IsGCInProgress is also true for say Pause (anywhere SuspendEE happens) and GCThread is the
// thread that did the Pause. While in Pause if another thread attempts Rev/Pinvoke it should get inside the following and
// block until resume
if (((GCHeapUtilities::IsGCInProgress() && (this != ThreadSuspend::GetSuspensionThread())) ||
(m_State & (TS_UserSuspendPending | TS_DebugSuspendPending | TS_StackCrawlNeeded))) &&
(!g_fSuspendOnShutdown || IsFinalizerThread() || IsShutdownSpecialThread()))
{
if (!ThreadStore::HoldingThreadStore(this))
{
STRESS_LOG1(LF_SYNC, LL_INFO1000, "RareDisablePreemptiveGC: entering. Thread state = %x\n", m_State.Load());
DWORD dwSwitchCount = 0;
do
{
// CoreCLR does not support user-requested thread suspension
_ASSERTE(!(m_State & TS_UserSuspendPending));
EnablePreemptiveGC();
// Cannot use GCX_PREEMP_NO_DTOR here because we're inside of the thread
// PREEMP->COOP switch mechanism and GCX_PREEMP's assert's will fire.
// Instead we use BEGIN_GCX_ASSERT_PREEMP to inform Scan of the mode
// change here.
BEGIN_GCX_ASSERT_PREEMP;
// just wait until the GC is over.
if (this != ThreadSuspend::GetSuspensionThread())
{
#ifdef PROFILING_SUPPORTED
// If profiler desires GC events, notify it that this thread is waiting until the GC is over
// Do not send suspend notifications for debugger suspensions
{
BEGIN_PIN_PROFILER(CORProfilerTrackSuspends());
if (!(m_State & TS_DebugSuspendPending))
{
g_profControlBlock.pProfInterface->RuntimeThreadSuspended((ThreadID)this);
}
END_PIN_PROFILER();
}
#endif // PROFILING_SUPPORTED
DWORD status = S_OK;
SetThreadStateNC(TSNC_WaitUntilGCFinished);
status = GCHeapUtilities::GetGCHeap()->WaitUntilGCComplete();
ResetThreadStateNC(TSNC_WaitUntilGCFinished);
if (status == (DWORD)COR_E_STACKOVERFLOW)
{
// One of two things can happen here:
// 1. GC is suspending the process. GC needs to wait.
// 2. GC is proceeding after suspension. The current thread needs to spin.
SetThreadState(TS_BlockGCForSO);
while (GCHeapUtilities::IsGCInProgress() && m_fPreemptiveGCDisabled.Load() == 0)
{
#undef Sleep
// We can not go to a host for blocking operation due ot lack of stack.
// Instead we will spin here until
// 1. GC is finished; Or
// 2. GC lets this thread to run and will wait for it
Sleep(10);
#define Sleep(a) Dont_Use_Sleep(a)
}
ResetThreadState(TS_BlockGCForSO);
if (m_fPreemptiveGCDisabled.Load() == 1)
{
// GC suspension has allowed this thread to switch back to cooperative mode.
break;
}
}
if (!GCHeapUtilities::IsGCInProgress())
{
if (HasThreadState(TS_StackCrawlNeeded))
{
SetThreadStateNC(TSNC_WaitUntilGCFinished);
ThreadStore::WaitForStackCrawlEvent();
ResetThreadStateNC(TSNC_WaitUntilGCFinished);
}
else
{
__SwitchToThread(0, ++dwSwitchCount);
}
}
#ifdef PROFILING_SUPPORTED
// Let the profiler know that this thread is resuming
{
BEGIN_PIN_PROFILER(CORProfilerTrackSuspends());
g_profControlBlock.pProfInterface->RuntimeThreadResumed((ThreadID)this);
END_PIN_PROFILER();
}
#endif // PROFILING_SUPPORTED
}
END_GCX_ASSERT_PREEMP;
// disable preemptive gc.
FastInterlockOr(&m_fPreemptiveGCDisabled, 1);
// The fact that we check whether 'this' is the GC thread may seem
// strange. After all, we determined this before entering the method.
// However, it is possible for the current thread to become the GC
// thread while in this loop. This happens if you use the COM+
// debugger to suspend this thread and then release it.
} while ((GCHeapUtilities::IsGCInProgress() && (this != ThreadSuspend::GetSuspensionThread())) ||
(m_State & (TS_UserSuspendPending | TS_DebugSuspendPending | TS_StackCrawlNeeded)));
}
STRESS_LOG0(LF_SYNC, LL_INFO1000, "RareDisablePreemptiveGC: leaving\n");
}
// Block all threads except finalizer and shutdown thread during shutdown.
// If g_fSuspendFinalizerOnShutdown is set, block the finalizer too.
if ((g_fSuspendOnShutdown && !IsFinalizerThread() && !IsShutdownSpecialThread()) ||
(g_fSuspendFinalizerOnShutdown && IsFinalizerThread()))
{
STRESS_LOG1(LF_SYNC, LL_INFO1000, "RareDisablePreemptiveGC: entering. Thread state = %x\n", m_State.Load());
EnablePreemptiveGC();
// Cannot use GCX_PREEMP_NO_DTOR here because we're inside of the thread
// PREEMP->COOP switch mechanism and GCX_PREEMP's assert's will fire.
// Instead we use BEGIN_GCX_ASSERT_PREEMP to inform Scan of the mode
// change here.
BEGIN_GCX_ASSERT_PREEMP;
#ifdef PROFILING_SUPPORTED
// If profiler desires GC events, notify it that this thread is waiting until the GC is over
// Do not send suspend notifications for debugger suspensions
{
BEGIN_PIN_PROFILER(CORProfilerTrackSuspends());
if (!(m_State & TS_DebugSuspendPending))
{
g_profControlBlock.pProfInterface->RuntimeThreadSuspended((ThreadID)this);
}
END_PIN_PROFILER();
}
#endif // PROFILING_SUPPORTED
// The thread is blocked for shutdown. We do not concern for GC violation.
CONTRACT_VIOLATION(GCViolation);
WaitForEndOfShutdown();
END_GCX_ASSERT_PREEMP;
__SwitchToThread(INFINITE, CALLER_LIMITS_SPINNING);
_ASSERTE(!"Cannot reach here");
}
Exit: ;
END_PRESERVE_LAST_ERROR;
}
void Thread::HandleThreadAbortTimeout()
{
WRAPPER_NO_CONTRACT;
EPolicyAction action = eNoAction;
EClrOperation operation = OPR_ThreadRudeAbortInNonCriticalRegion;
if (IsFuncEvalAbort())
{
// There can't be escalation policy for FuncEvalAbort timeout.
// The debugger should retain control of the policy. For example, if a RudeAbort times out, it's
// probably because the debugger had some other thread frozen. When the thread is thawed, things might
// be fine, so we don't want to escelate the FuncEvalRudeAbort (which will be swalled by FuncEvalHijackWorker)
// into a user RudeThreadAbort (which will at least rip the entire thread).
return;
}
if (!IsRudeAbort())
{
operation = OPR_ThreadAbort;
}
else if (HasLockInCurrentDomain())
{
operation = OPR_ThreadRudeAbortInCriticalRegion;
}
else
{
operation = OPR_ThreadRudeAbortInNonCriticalRegion;
}
action = GetEEPolicy()->GetActionOnTimeout(operation, this);
// We only support escalation to rude abort
EX_TRY {
switch (action)
{
case eRudeAbortThread:
GetEEPolicy()->NotifyHostOnTimeout(operation,action);
MarkThreadForAbort(TAR_Thread, EEPolicy::TA_Rude);
break;
case eUnloadAppDomain:
{
AppDomain *pDomain = GetDomain();
if (!pDomain->IsDefaultDomain())
{
GetEEPolicy()->NotifyHostOnTimeout(operation,action);
pDomain->EnableADUnloadWorker(EEPolicy::ADU_Safe);
}
}
break;
case eRudeUnloadAppDomain:
{
AppDomain *pDomain = GetDomain();
if (!pDomain->IsDefaultDomain())
{
GetEEPolicy()->NotifyHostOnTimeout(operation,action);
pDomain->EnableADUnloadWorker(EEPolicy::ADU_Rude);
}
}
break;
case eExitProcess:
case eFastExitProcess:
case eRudeExitProcess:
case eDisableRuntime:
GetEEPolicy()->NotifyHostOnTimeout(operation,action);
EEPolicy::HandleExitProcessFromEscalation(action, HOST_E_EXITPROCESS_THREADABORT);
_ASSERTE (!"Should not reach here");
break;
case eNoAction:
break;
default:
_ASSERTE (!"unknown policy for thread abort");
}
}
EX_CATCH {
}
EX_END_CATCH(SwallowAllExceptions);
}
void Thread::HandleThreadAbort (BOOL fForce)
{
BEGIN_PRESERVE_LAST_ERROR;
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_SO_TOLERANT;
BEGIN_SO_INTOLERANT_CODE(this);
TESTHOOKCALL(AppDomainCanBeUnloaded(GetDomain()->GetId().m_dwId,FALSE));
// It's possible we could go through here if we hit a hard SO and MC++ has called back
// into the runtime on this thread
FinishSOWork();
if (IsAbortRequested() && GetAbortEndTime() < CLRGetTickCount64())
{
HandleThreadAbortTimeout();
}
// @TODO: we should consider treating this function as an FCALL or HCALL and use FCThrow instead of COMPlusThrow
// Sometimes we call this without any CLR SEH in place. An example is UMThunkStubRareDisableWorker.
// That's okay since COMPlusThrow will eventually erect SEH around the RaiseException. It prevents
// us from stating CONTRACT here.
if (fForce || ReadyForAbort())
{
ResetThreadState ((ThreadState)(TS_Interrupted | TS_Interruptible));
// We are going to abort. Abort satisfies Thread.Interrupt requirement.
FastInterlockExchange (&m_UserInterrupt, 0);
// generate either a ThreadAbort exception
STRESS_LOG1(LF_APPDOMAIN, LL_INFO100, "Thread::HandleThreadAbort throwing abort for %x\n", GetThreadId());
GCX_COOP_NO_DTOR();
// Can not use holder. GCX_COOP forces the thread back to the original state during
// exception unwinding, which may put the thread back to cooperative mode.
// GCX_COOP();
if (!IsAbortInitiated() ||
(IsRudeAbort() && !IsRudeAbortInitiated()))
{
PreWorkForThreadAbort();
}
PreparingAbortHolder paHolder;
OBJECTREF exceptObj;
if (IsRudeAbort())
{
exceptObj = CLRException::GetPreallocatedRudeThreadAbortException();
}
else
{
EEException eeExcept(kThreadAbortException);
exceptObj = CLRException::GetThrowableFromException(&eeExcept);
}
#ifdef _DEBUG
AddFiberInfo(ThreadTrackInfo_Abort);
#endif
RaiseTheExceptionInternalOnly(exceptObj, FALSE);
}
END_SO_INTOLERANT_CODE;
END_PRESERVE_LAST_ERROR;
}
void Thread::PreWorkForThreadAbort()
{
WRAPPER_NO_CONTRACT;
SetAbortInitiated();
// if an abort and interrupt happen at the same time (e.g. on a sleeping thread),
// the abort is favored. But we do need to reset the interrupt bits.
FastInterlockAnd((ULONG *) &m_State, ~(TS_Interruptible | TS_Interrupted));
ResetUserInterrupted();
if (IsRudeAbort() && !(m_AbortInfo & (TAI_ADUnloadAbort |
TAI_ADUnloadRudeAbort |
TAI_ADUnloadV1Abort)
)) {
if (HasLockInCurrentDomain()) {
AppDomain *pDomain = GetAppDomain();
// Cannot enable the following assertion.
// We may take the lock, but the lock will be released during exception backout.
//_ASSERTE(!pDomain->IsDefaultDomain());
EPolicyAction action = GetEEPolicy()->GetDefaultAction(OPR_ThreadRudeAbortInCriticalRegion, this);
switch (action)
{
case eRudeUnloadAppDomain:
if (!pDomain->IsDefaultDomain())
{
GetEEPolicy()->NotifyHostOnDefaultAction(OPR_ThreadRudeAbortInCriticalRegion,action);
pDomain->EnableADUnloadWorker(EEPolicy::ADU_Rude);
}
break;
case eExitProcess:
case eFastExitProcess:
case eRudeExitProcess:
case eDisableRuntime:
{
// We're about to exit the process, if we take an SO here we'll just exit faster right???
CONTRACT_VIOLATION(SOToleranceViolation);
GetEEPolicy()->NotifyHostOnDefaultAction(OPR_ThreadRudeAbortInCriticalRegion,action);
GetEEPolicy()->HandleExitProcessFromEscalation(action,HOST_E_EXITPROCESS_ADUNLOAD);
}
break;
default:
break;
}
}
}
}
#if defined(STRESS_HEAP) && defined(_DEBUG)
// This function is for GC stress testing. Before we enable preemptive GC, let us do a GC
// because GC may happen while the thread is in preemptive GC mode.
void Thread::PerformPreemptiveGC()
{
CONTRACTL {
NOTHROW;
DISABLED(GC_TRIGGERS); // I think this is actually wrong: prevents a p->c->p mode switch inside a NOTRIGGER region.
DEBUG_ONLY;
}
CONTRACTL_END;
if (IsAtProcessExit())
return;
if (!GCStressPolicy::IsEnabled() || !GCStress<cfg_transition>::IsEnabled())
return;
if (!GCHeapUtilities::IsGCHeapInitialized())
return;
if (!m_GCOnTransitionsOK
#ifdef ENABLE_CONTRACTS
|| RawGCNoTrigger()
#endif
|| g_fEEShutDown
|| GCHeapUtilities::IsGCInProgress(TRUE)
|| GCHeapUtilities::GetGCHeap()->GetGcCount() == 0 // Need something that works for isolated heap.
|| ThreadStore::HoldingThreadStore())
return;
if (Thread::ThreadsAtUnsafePlaces())
return;
#ifdef DEBUGGING_SUPPORTED
// Don't collect if the debugger is attach and either 1) there
// are any threads held at unsafe places or 2) this thread is
// under the control of the debugger's dispatch logic (as
// evidenced by having a non-NULL filter context.)
if ((CORDebuggerAttached() &&
(g_pDebugInterface->ThreadsAtUnsafePlaces() ||
(GetFilterContext() != NULL))))
return;
#endif // DEBUGGING_SUPPORTED
_ASSERTE(m_fPreemptiveGCDisabled.Load() == 0); // we are in preemptive mode when we call this
m_GCOnTransitionsOK = FALSE;
{
GCX_COOP();
m_bGCStressing = TRUE;
// BUG(github #10318) - when not using allocation contexts, the alloc lock
// must be acquired here. Until fixed, this assert prevents random heap corruption.
_ASSERTE(GCHeapUtilities::UseThreadAllocationContexts());
GCHeapUtilities::GetGCHeap()->StressHeap(GetThread()->GetAllocContext());
m_bGCStressing = FALSE;
}
m_GCOnTransitionsOK = TRUE;
}
#endif // STRESS_HEAP && DEBUG
// To leave cooperative mode and enter preemptive mode, if a GC is in progress, we
// no longer care to suspend this thread. But if we are trying to suspend the thread
// for other reasons (e.g. Thread.Suspend()), now is a good time.
//
// Note that it is possible for an N/Direct call to leave the EE without explicitly
// enabling preemptive GC.
void Thread::RareEnablePreemptiveGC()
{
CONTRACTL {
NOTHROW;
DISABLED(GC_TRIGGERS); // I think this is actually wrong: prevents a p->c->p mode switch inside a NOTRIGGER region.
SO_TOLERANT;
}
CONTRACTL_END;
// @todo - Needs a hard SO probe
CONTRACT_VIOLATION(GCViolation|FaultViolation|SOToleranceViolation);
// If we have already received our PROCESS_DETACH during shutdown, there is only one thread in the
// process and no coordination is necessary.
if (IsAtProcessExit())
return;
#ifdef _DEBUG
AddFiberInfo(ThreadTrackInfo_GCMode);
#endif
// EnablePreemptiveGC already set us to preemptive mode before triggering the Rare path.
// Force other threads to see this update, since the Rare path implies that someone else
// is observing us (e.g. SuspendRuntime).
_ASSERTE (!m_fPreemptiveGCDisabled);
// holding a spin lock in coop mode and transit to preemp mode will cause deadlock on GC
_ASSERTE ((m_StateNC & Thread::TSNC_OwnsSpinLock) == 0);
FastInterlockOr (&m_fPreemptiveGCDisabled, 0);
#if defined(STRESS_HEAP) && defined(_DEBUG)
if (!IsDetached())
PerformPreemptiveGC();
#endif
STRESS_LOG1(LF_SYNC, LL_INFO100000, "RareEnablePreemptiveGC: entering. Thread state = %x\n", m_State.Load());
if (!ThreadStore::HoldingThreadStore(this))
{
#ifdef FEATURE_HIJACK
// Remove any hijacks we might have.
UnhijackThread();
#endif // FEATURE_HIJACK
// wake up any threads waiting to suspend us, like the GC thread.
ThreadSuspend::g_pGCSuspendEvent->Set();
// for GC, the fact that we are leaving the EE means that it no longer needs to
// suspend us. But if we are doing a non-GC suspend, we need to block now.
// Give the debugger precedence over user suspensions:
while (m_State & (TS_DebugSuspendPending | TS_UserSuspendPending))
{
// CoreCLR does not support user-requested thread suspension
_ASSERTE(!(m_State & TS_UserSuspendPending));
#ifdef DEBUGGING_SUPPORTED
// We don't notify the debugger that this thread is now suspended. We'll just
// let the debugger's helper thread sweep and pick it up.
// We also never take the TSL in here either.
// Life's much simpler this way...
#endif // DEBUGGING_SUPPORTED
#ifdef LOGGING
{
LOG((LF_CORDB, LL_INFO1000, "[0x%x] SUSPEND: suspended while enabling gc.\n", GetThreadId()));
}
#endif
WaitSuspendEvents(); // sets bits, too
}
}
STRESS_LOG0(LF_SYNC, LL_INFO100000, " RareEnablePreemptiveGC: leaving.\n");
}
// Called when we are passing through a safe point in CommonTripThread or
// HandleGCSuspensionForInterruptedThread. Do the right thing with this thread,
// which can either mean waiting for the GC to complete, or performing a
// pending suspension.
void Thread::PulseGCMode()
{
CONTRACTL {
NOTHROW;
GC_TRIGGERS;
}
CONTRACTL_END;
_ASSERTE(this == GetThread());
if (PreemptiveGCDisabled() && CatchAtSafePoint())
{
EnablePreemptiveGC();
DisablePreemptiveGC();
}
}
// Indicate whether threads should be trapped when returning to the EE (i.e. disabling
// preemptive GC mode)
Volatile<LONG> g_fTrapReturningThreadsLock;
void ThreadStore::TrapReturningThreads(BOOL yes)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
// make sure that a thread doesn't get suspended holding g_fTrapReturningThreadsLock
// if a suspended thread held this lock and then the suspending thread called in
// here (which it does) the suspending thread would deadlock causing the suspension
// as a whole to deadlock
ForbidSuspendThreadHolder suspend;
DWORD dwSwitchCount = 0;
while (1 == FastInterlockExchange(&g_fTrapReturningThreadsLock, 1))
{
// we can't forbid suspension while we are sleeping and don't hold the lock
// this will trigger an assert on SQLCLR but is a general issue
suspend.Release();
__SwitchToThread(0, ++dwSwitchCount);
suspend.Acquire();
}
if (yes)
{
#ifdef _DEBUG
CounterHolder trtHolder(&g_trtChgInFlight);
FastInterlockIncrement(&g_trtChgStamp);
#endif
GCHeapUtilities::GetGCHeap()->SetSuspensionPending(true);
FastInterlockIncrement (&g_TrapReturningThreads);
#ifdef ENABLE_FAST_GCPOLL_HELPER
EnableJitGCPoll();
#endif
_ASSERTE(g_TrapReturningThreads > 0);
#ifdef _DEBUG
trtHolder.Release();
#endif
}
else
{
FastInterlockDecrement (&g_TrapReturningThreads);
GCHeapUtilities::GetGCHeap()->SetSuspensionPending(false);
#ifdef ENABLE_FAST_GCPOLL_HELPER
if (0 == g_TrapReturningThreads)
{
DisableJitGCPoll();
}
#endif
_ASSERTE(g_TrapReturningThreads >= 0);
}
#ifdef ENABLE_FAST_GCPOLL_HELPER
//Ensure that we flush the cache line containing the GC Poll Helper.
MemoryBarrier();
#endif //ENABLE_FAST_GCPOLL_HELPER
g_fTrapReturningThreadsLock = 0;
}
#ifdef FEATURE_HIJACK
void RedirectedThreadFrame::ExceptionUnwind()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
SO_TOLERANT;
MODE_ANY;
}
CONTRACTL_END;
STRESS_LOG1(LF_SYNC, LL_INFO1000, "In RedirectedThreadFrame::ExceptionUnwind pFrame = %p\n", this);
Thread* pThread = GetThread();
if (pThread->GetSavedRedirectContext())
{
delete m_Regs;
}
else
{
// Save it for future use to avoid repeatedly new'ing
pThread->SetSavedRedirectContext(m_Regs);
}
m_Regs = NULL;
}
#ifndef PLATFORM_UNIX
#ifdef _TARGET_X86_
//****************************************************************************************
// This will check who caused the exception. If it was caused by the the redirect function,
// the reason is to resume the thread back at the point it was redirected in the first
// place. If the exception was not caused by the function, then it was caused by the call
// out to the I[GC|Debugger]ThreadControl client and we need to determine if it's an
// exception that we can just eat and let the runtime resume the thread, or if it's an
// uncatchable exception that we need to pass on to the runtime.
//
int RedirectedHandledJITCaseExceptionFilter(
PEXCEPTION_POINTERS pExcepPtrs, // Exception data
RedirectedThreadFrame *pFrame, // Frame on stack
BOOL fDone, // Whether redirect completed without exception
CONTEXT *pCtx) // Saved context
{
// !!! Do not use a non-static contract here.
// !!! Contract may insert an exception handling record.
// !!! This function assumes that GetCurrentSEHRecord() returns the exception record set up in
// !!! Thread::RedirectedHandledJITCase
//
// !!! Do not use an object with dtor, since it injects a fs:0 entry.
STATIC_CONTRACT_NOTHROW;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_ANY;
if (pExcepPtrs->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW)
{
return EXCEPTION_CONTINUE_SEARCH;
}
// Get the thread handle
Thread *pThread = GetThread();
_ASSERTE(pThread);
STRESS_LOG2(LF_SYNC, LL_INFO100, "In RedirectedHandledJITCaseExceptionFilter fDone = %d pFrame = %p\n", fDone, pFrame);
// If we get here via COM+ exception, gc-mode is unknown. We need it to
// be cooperative for this function.
GCX_COOP_NO_DTOR();
// If the exception was due to the called client, then we need to figure out if it
// is an exception that can be eaten or if it needs to be handled elsewhere.
if (!fDone)
{
if (pExcepPtrs->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
{
return (EXCEPTION_CONTINUE_SEARCH);
}
// Get the latest thrown object
OBJECTREF throwable = CLRException::GetThrowableFromExceptionRecord(pExcepPtrs->ExceptionRecord);
// If this is an uncatchable exception, then let the exception be handled elsewhere
if (IsUncatchable(&throwable))
{
pThread->EnablePreemptiveGC();
return (EXCEPTION_CONTINUE_SEARCH);
}
}
#ifdef _DEBUG
else
{
_ASSERTE(pExcepPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_HIJACK);
}
#endif
// Unlink the frame in preparation for resuming in managed code
pFrame->Pop();
// Copy the saved context record into the EH context;
ReplaceExceptionContextRecord(pExcepPtrs->ContextRecord, pCtx);
DWORD espValue = pCtx->Esp;
if (pThread->GetSavedRedirectContext())
{
delete pCtx;
}
else
{
// Save it for future use to avoid repeatedly new'ing
pThread->SetSavedRedirectContext(pCtx);
}
/////////////////////////////////////////////////////////////////////////////
// NOTE: Ugly, ugly workaround.
// We need to resume the thread into the managed code where it was redirected,
// and the corresponding ESP is below the current one. But C++ expects that
// on an EXCEPTION_CONTINUE_EXECUTION that the ESP will be above where it has
// installed the SEH handler. To solve this, we need to remove all handlers
// that reside above the resumed ESP, but we must leave the OS-installed
// handler at the top, so we grab the top SEH handler, call
// PopSEHRecords which will remove all SEH handlers above the target ESP and
// then link the OS handler back in with SetCurrentSEHRecord.
// Get the special OS handler and save it until PopSEHRecords is done
EXCEPTION_REGISTRATION_RECORD *pCurSEH = GetCurrentSEHRecord();
// Unlink all records above the target resume ESP
PopSEHRecords((LPVOID)(size_t)espValue);
// Link the special OS handler back in to the top
pCurSEH->Next = GetCurrentSEHRecord();
// Register the special OS handler as the top handler with the OS
SetCurrentSEHRecord(pCurSEH);
// Resume execution at point where thread was originally redirected
return (EXCEPTION_CONTINUE_EXECUTION);
}
#endif // _TARGET_X86_
void NotifyHostOnGCSuspension()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
SO_TOLERANT;
}
CONTRACTL_END;
}
// This function is called from the assembly functions used to redirect a thread. It must not cause
// an exception (except SO).
extern "C" PCONTEXT __stdcall GetCurrentSavedRedirectContext()
{
LIMITED_METHOD_CONTRACT;
DWORD dwLastError = GetLastError();
PCONTEXT pContext = GetThread()->GetSavedRedirectContext();
SetLastError(dwLastError);
return pContext;
}
void __stdcall Thread::RedirectedHandledJITCase(RedirectReason reason)
{
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_COOPERATIVE;
// We must preserve this in case we've interrupted an IL pinvoke stub before it
// was able to save the error.
DWORD dwLastError = GetLastError();
Thread *pThread = GetThread();
_ASSERTE(pThread);
#ifdef FEATURE_STACK_PROBE
if (GetEEPolicy()->GetActionOnFailure(FAIL_StackOverflow) == eRudeUnloadAppDomain)
{
RetailStackProbe(ADJUST_PROBE(DEFAULT_ENTRY_PROBE_AMOUNT), pThread);
}
#endif
BEGIN_CONTRACT_VIOLATION(SOToleranceViolation);
// Get the saved context
CONTEXT *pCtx = pThread->GetSavedRedirectContext();
_ASSERTE(pCtx);
INDEBUG(Thread::ObjectRefFlush(pThread));
// Create a frame on the stack
FrameWithCookie<RedirectedThreadFrame> frame(pCtx);
STRESS_LOG5(LF_SYNC, LL_INFO1000, "In RedirectedHandledJITcase reason 0x%x pFrame = %p pc = %p sp = %p fp = %p", reason, &frame, GetIP(pCtx), GetSP(pCtx), GetFP(pCtx));
#ifdef _TARGET_X86_
// This will indicate to the exception filter whether or not the exception is caused
// by us or the client.
BOOL fDone = FALSE;
int filter_count = 0; // A counter to avoid a nasty case where an
// up-stack filter throws another exception
// causing our filter to be run again for
// some unrelated exception.
__try
#endif // _TARGET_X86_
{
// Make sure this thread doesn't reuse the context memory in re-entrancy cases
_ASSERTE(pThread->GetSavedRedirectContext() != NULL);
pThread->SetSavedRedirectContext(NULL);
// Link in the frame
frame.Push();
#if defined(HAVE_GCCOVER) && defined(USE_REDIRECT_FOR_GCSTRESS) // GCCOVER
if (reason == RedirectReason_GCStress)
{
_ASSERTE(pThread->PreemptiveGCDisabledOther());
DoGcStress(frame.GetContext(), NULL);
}
else
#endif // HAVE_GCCOVER && USE_REDIRECT_FOR_GCSTRESS
{
// Enable PGC before calling out to the client to allow runtime suspend to finish
GCX_PREEMP_NO_DTOR();
// Notify the interface of the pending suspension
switch (reason) {
case RedirectReason_GCSuspension:
break;
case RedirectReason_DebugSuspension:
break;
case RedirectReason_UserSuspension:
// Do nothing;
break;
default:
_ASSERTE(!"Invalid redirect reason");
break;
}
// Disable preemptive GC so we can unlink the frame
GCX_PREEMP_NO_DTOR_END();
}
#ifdef _TARGET_X86_
pThread->HandleThreadAbort(); // Might throw an exception.
// Indicate that the call to the service went without an exception, and that
// we're raising our own exception to resume the thread to where it was
// redirected from
fDone = TRUE;
// Save the instruction pointer where we redirected last. This does not race with the check
// against this variable in HandledJitCase because the GC will not attempt to redirect the
// thread until the instruction pointer of this thread is back in managed code.
pThread->m_LastRedirectIP = GetIP(pCtx);
pThread->m_SpinCount = 0;
RaiseException(EXCEPTION_HIJACK, 0, 0, NULL);
#else // _TARGET_X86_
#if defined(HAVE_GCCOVER) && defined(USE_REDIRECT_FOR_GCSTRESS) // GCCOVER
//
// If GCStress interrupts an IL stub or inlined p/invoke while it's running in preemptive mode, it switches the mode to
// cooperative - but we will resume to preemptive below. We should not trigger an abort in that case, as it will fail
// due to the GC mode.
//
if (!pThread->m_fPreemptiveGCDisabledForGCStress)
#endif
{
UINT_PTR uAbortAddr;
UINT_PTR uResumePC = (UINT_PTR)GetIP(pCtx);
CopyOSContext(pThread->m_OSContext, pCtx);
uAbortAddr = (UINT_PTR)COMPlusCheckForAbort();
if (uAbortAddr)
{
LOG((LF_EH, LL_INFO100, "thread abort in progress, resuming thread under control... (handled jit case)\n"));
CONSISTENCY_CHECK(CheckPointer(pCtx));
STRESS_LOG1(LF_EH, LL_INFO10, "resume under control: ip: %p (handled jit case)\n", uResumePC);
SetIP(pThread->m_OSContext, uResumePC);
#if defined(_TARGET_ARM_)
// Save the original resume PC in Lr
pCtx->Lr = uResumePC;
// Since we have set a new IP, we have to clear conditional execution flags too.
ClearITState(pThread->m_OSContext);
#endif // _TARGET_ARM_
SetIP(pCtx, uAbortAddr);
}
}
// Unlink the frame in preparation for resuming in managed code
frame.Pop();
{
// Free the context struct if we already have one cached
if (pThread->GetSavedRedirectContext())
{
CONTEXT* pCtxTemp = (CONTEXT*)_alloca(sizeof(CONTEXT));
memcpy(pCtxTemp, pCtx, sizeof(CONTEXT));
delete pCtx;
pCtx = pCtxTemp;
}
else
{
// Save it for future use to avoid repeatedly new'ing
pThread->SetSavedRedirectContext(pCtx);
}
#if defined(HAVE_GCCOVER) && defined(USE_REDIRECT_FOR_GCSTRESS) // GCCOVER
if (pThread->m_fPreemptiveGCDisabledForGCStress)
{
pThread->EnablePreemptiveGC();
pThread->m_fPreemptiveGCDisabledForGCStress = false;
}
#endif
LOG((LF_SYNC, LL_INFO1000, "Resuming execution with RtlRestoreContext\n"));
SetLastError(dwLastError);
RtlRestoreContext(pCtx, NULL);
}
#endif // _TARGET_X86_
}
#ifdef _TARGET_X86_
__except (++filter_count == 1
? RedirectedHandledJITCaseExceptionFilter(GetExceptionInformation(), &frame, fDone, pCtx)
: EXCEPTION_CONTINUE_SEARCH)
{
_ASSERTE(!"Reached body of __except in Thread::RedirectedHandledJITCase");
}
#endif // _TARGET_X86_
END_CONTRACT_VIOLATION;
}
//****************************************************************************************
// This helper is called when a thread suspended in managed code at a sequence point while
// suspending the runtime and there is a client interested in re-assigning the thread to
// do interesting work while the runtime is suspended. This will call into the client
// notifying it that the thread will be suspended for a runtime suspension.
//
void __stdcall Thread::RedirectedHandledJITCaseForDbgThreadControl()
{
WRAPPER_NO_CONTRACT;
RedirectedHandledJITCase(RedirectReason_DebugSuspension);
}
//****************************************************************************************
// This helper is called when a thread suspended in managed code at a sequence point when
// suspending the runtime.
//
// We do this because the obvious code sequence:
//
// SuspendThread(t1);
// GetContext(t1, &ctx);
// ctx.Ecx = <some new value>;
// SetContext(t1, &ctx);
// ResumeThread(t1);
//
// simply does not work due to a nasty race with exception handling in the OS. If the
// thread that is suspended has just faulted, then the update can disappear without ever
// modifying the real thread ... and there is no way to tell.
//
// Updating the EIP may not work ... but when it doesn't, we're ok ... an exception ends
// up getting dispatched anyway.
//
// If the host is interested in getting control, then we give control to the host. If the