Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Vorlicek <janvorli@microsoft.com>2022-06-16 13:41:45 +0300
committerGitHub <noreply@github.com>2022-06-16 13:41:45 +0300
commit3fc61ebb562afc327a8fc6de5c82d76e86bf6f5d (patch)
tree3e89c22c8c5e736bfdbbc0848313b1a2576201bc
parent3c33b4293107c607ae8669545114d6f9891432cb (diff)
Enable ThreadAbort with CET enabled via the QueueUserAPC2 (#70803)
This change makes ThreadAbort work when CET is enabled. Instead of thread redirection, it uses the new user mode APC mechanism to get a thread to a handler and then throws the ThreadAbortException from there if the thread was interrupted at a safe location. I have verified it works using mdbg tests and also by manual testing in the Visual Studio 2022 using a test app that creates an instance of classes with properties containing infinite loop, wait on a lock, wait on a handle, infinite loop inside of a lock and infinite loop in finally. For the testing, I've enabled this separately from the CET so that the missing support for CET in the debugger code doesn't cause troubles. So we could enable this without CET enabled too, but I'd prefer doing that separately.
-rw-r--r--src/coreclr/vm/threads.h1
-rw-r--r--src/coreclr/vm/threadsuspend.cpp567
2 files changed, 290 insertions, 278 deletions
diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h
index f4bf60ec167..aab86391073 100644
--- a/src/coreclr/vm/threads.h
+++ b/src/coreclr/vm/threads.h
@@ -2215,6 +2215,7 @@ public:
{
SuspendForGC,
SuspendForDebugger,
+ ThreadAbort,
};
bool InjectActivation(ActivationReason reason);
diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp
index d0725699893..305145572dd 100644
--- a/src/coreclr/vm/threadsuspend.cpp
+++ b/src/coreclr/vm/threadsuspend.cpp
@@ -1227,398 +1227,400 @@ Thread::UserAbort(EEPolicy::ThreadAbortTypes abortType, DWORD timeout)
_ASSERTE(this != pCurThread); // Aborting another thread.
+ if (!UseSpecialUserModeApc())
+ {
#ifdef _DEBUG
- DWORD elapsed_time = 0;
+ DWORD elapsed_time = 0;
#endif
- // We do not want this thread to be alerted.
- ThreadPreventAsyncHolder preventAsync(pCurThread != NULL);
+ // 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;
+ // If UserAbort times out, put up msgbox once.
+ BOOL fAlreadyAssert = FALSE;
#endif
#if !defined(DISABLE_THREADSUSPEND)
- DWORD dwSwitchCount = 0;
+ DWORD dwSwitchCount = 0;
#endif // !defined(DISABLE_THREADSUSPEND)
- while (true)
- {
- // Lock the thread store
- LOG((LF_SYNC, INFO3, "UserAbort obtain lock\n"));
-
- ULONGLONG abortEndTime = GetAbortEndTime();
- if (abortEndTime != MAXULONGLONG)
+ while (true)
{
- ULONGLONG now_time = CLRGetTickCount64();
+ // Lock the thread store
+ LOG((LF_SYNC, INFO3, "UserAbort obtain lock\n"));
- if (now_time >= abortEndTime)
+ ULONGLONG abortEndTime = GetAbortEndTime();
+ if (abortEndTime != MAXULONGLONG)
{
- // timeout, but no action on timeout.
- // Debugger can call this function to abort func-eval with a timeout
- return HRESULT_FROM_WIN32(ERROR_TIMEOUT);
+ ULONGLONG now_time = CLRGetTickCount64();
+
+ if (now_time >= abortEndTime)
+ {
+ // timeout, but no action on timeout.
+ // Debugger can call this function to abort func-eval with a timeout
+ return HRESULT_FROM_WIN32(ERROR_TIMEOUT);
+ }
}
- }
- // 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.
+ // 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)
+ // We need to make sure that ThreadStoreLock is released after CheckForAbort. This makes sure
+ // that ThreadAbort does not race against GC.
+ class CheckForAbort
{
- if (!fHoldingThreadStoreLock)
+ 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)
{
- ThreadSuspend::LockThreadStore(ThreadSuspend::SUSPEND_OTHER);
- }
- ThreadStore::ResetStackCrawlEvent();
+ 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)
+ // 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()
{
- m_NeedRelease = FALSE;
- ThreadStore::TrapReturningThreads(FALSE);
- ThreadStore::SetStackCrawlEvent();
- m_pThread->ResetThreadState(TS_StackCrawlNeeded);
- if (!m_fHoldingThreadStoreLock)
+ Release();
+ }
+ void Release()
+ {
+ if (m_NeedRelease)
{
- ThreadSuspend::UnlockThreadStore();
+ m_NeedRelease = FALSE;
+ ThreadStore::TrapReturningThreads(FALSE);
+ ThreadStore::SetStackCrawlEvent();
+ m_pThread->ResetThreadState(TS_StackCrawlNeeded);
+ if (!m_fHoldingThreadStoreLock)
+ {
+ ThreadSuspend::UnlockThreadStore();
+ }
}
}
- }
- };
- CheckForAbort checkForAbort(this, fHoldingThreadStoreLock);
+ };
+ 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.
+ // 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;
+ m_dwAbortPoint = 2;
#endif
- return S_OK;
- }
+ return S_OK;
+ }
- if (GetThreadHandle() == INVALID_HANDLE_VALUE &&
- (m_State & TS_Unstarted) == 0)
- {
- // The thread is going to die or is already dead.
- UnmarkThreadForAbort();
+ if (GetThreadHandle() == INVALID_HANDLE_VALUE &&
+ (m_State & TS_Unstarted) == 0)
+ {
+ // The thread is going to die or is already dead.
+ UnmarkThreadForAbort();
#ifdef _DEBUG
- m_dwAbortPoint = 3;
+ m_dwAbortPoint = 3;
#endif
- return S_OK;
- }
+ 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.
- //
+ // 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))
- {
+ // It's possible that the thread has completed the abort already.
+ //
+ if (!(m_State & TS_AbortRequested))
+ {
#ifdef _DEBUG
- m_dwAbortPoint = 4;
+ m_dwAbortPoint = 4;
#endif
- return S_OK;
- }
+ return S_OK;
+ }
- // If a thread is Dead or Detached, abort is a NOP.
- //
- if (m_State & (TS_Dead | TS_Detached | TS_TaskReset))
- {
- UnmarkThreadForAbort();
+ // If a thread is Dead or Detached, abort is a NOP.
+ //
+ if (m_State & (TS_Dead | TS_Detached | TS_TaskReset))
+ {
+ UnmarkThreadForAbort();
#ifdef _DEBUG
- m_dwAbortPoint = 5;
+ m_dwAbortPoint = 5;
#endif
- return S_OK;
- }
+ 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())
- {
+ // 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;
+ m_dwAbortPoint = 6;
#endif
- break;
- }
+ break;
+ }
- BOOL fOutOfRuntime = FALSE;
- BOOL fNeedStackCrawl = FALSE;
+ 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.
- InterlockedOr((LONG*)&m_State, 0);
+ // 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.
+ InterlockedOr((LONG*)&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;
+ // 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_Failure:
+ case STR_UnstartedOrDead:
+ case STR_NoStressLog:
+ checkForAbort.Release();
+ __SwitchToThread(0, ++dwSwitchCount);
+ continue;
- default:
- UNREACHABLE();
- }
+ default:
+ UNREACHABLE();
+ }
- _ASSERTE(str == STR_Success);
+ _ASSERTE(str == STR_Success);
#endif // DISABLE_THREADSUSPEND
- // It's possible that the thread has completed the abort already.
- //
- if (!(m_State & TS_AbortRequested))
- {
+ // It's possible that the thread has completed the abort already.
+ //
+ if (!(m_State & TS_AbortRequested))
+ {
#ifndef DISABLE_THREADSUSPEND
- ResumeThread();
+ ResumeThread();
#endif
#ifdef _DEBUG
- m_dwAbortPoint = 63;
+ m_dwAbortPoint = 63;
#endif
- return S_OK;
- }
+ 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())
- {
+ // 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();
+ ResumeThread();
#endif
#ifdef _DEBUG
- m_dwAbortPoint = 65;
+ m_dwAbortPoint = 65;
#endif
- break;
- }
+ 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 (m_State & TS_SyncSuspended)
- {
+ // 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 (m_State & TS_SyncSuspended)
+ {
#ifndef DISABLE_THREADSUSPEND
- ResumeThread();
+ ResumeThread();
#endif
- checkForAbort.Release();
+ checkForAbort.Release();
#ifdef _DEBUG
- m_dwAbortPoint = 7;
+ m_dwAbortPoint = 7;
#endif
- //
- // 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;
- }
+ //
+ // 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);
- }
+ 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 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(FEATURE_EH_FUNCLETS)
- // 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)
- )
- {
+ // 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();
+ ResumeThread();
#endif
#ifdef _DEBUG
- m_dwAbortPoint = 8;
+ m_dwAbortPoint = 8;
#endif
- return S_OK;
- }
+ return S_OK;
+ }
#endif // TARGET_X86
- if (!m_fPreemptiveGCDisabled)
- {
- if ((m_pFrame != FRAME_TOP) && m_pFrame->IsTransitionToNativeFrame()
+ if (!m_fPreemptiveGCDisabled)
+ {
+ if ((m_pFrame != FRAME_TOP) && m_pFrame->IsTransitionToNativeFrame()
#if defined(TARGET_X86) && !defined(FEATURE_EH_FUNCLETS)
- && ((size_t) GetFirstCOMPlusSEHRecord(this) > ((size_t) m_pFrame) - 20)
+ && ((size_t) GetFirstCOMPlusSEHRecord(this) > ((size_t) m_pFrame) - 20)
#endif // TARGET_X86
- )
- {
- fOutOfRuntime = TRUE;
+ )
+ {
+ fOutOfRuntime = TRUE;
+ }
}
- }
- checkForAbort.NeedStackCrawl();
- if (!m_fPreemptiveGCDisabled)
- {
- fNeedStackCrawl = TRUE;
- }
+ checkForAbort.NeedStackCrawl();
+ if (!m_fPreemptiveGCDisabled)
+ {
+ fNeedStackCrawl = TRUE;
+ }
#if defined(FEATURE_HIJACK) && !defined(TARGET_UNIX)
- else
- {
- HandleJITCaseForAbort();
- }
+ else
+ {
+ HandleJITCaseForAbort();
+ }
#endif // FEATURE_HIJACK && !TARGET_UNIX
#ifndef DISABLE_THREADSUSPEND
- // The thread is not suspended now.
- ResumeThread();
+ // The thread is not suspended now.
+ ResumeThread();
#endif
- if (!fNeedStackCrawl)
- {
- goto LPrepareRetry;
- }
+ if (!fNeedStackCrawl)
+ {
+ goto LPrepareRetry;
+ }
- if (!ReadyForAbort()) {
- goto LPrepareRetry;
- }
+ if (!ReadyForAbort()) {
+ goto LPrepareRetry;
+ }
- // !!! Check for Exception in flight should happen before induced thread abort.
- // !!! ReadyForAbort skips catch and filter clause.
+ // !!! 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())
- {
+ // 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;
+ m_dwAbortPoint = 9;
#endif
- break;
- }
+ 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
+ // 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;
+ m_dwAbortPoint = 10;
#endif
- goto LPrepareRetry;
- }
+ goto LPrepareRetry;
+ }
- if (fOutOfRuntime)
- {
- // If the thread is running outside the EE, and is behind a stub that's going
- // to catch...
+ 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;
+ m_dwAbortPoint = 11;
#endif
- break;
- }
+ 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.
+ // 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();
+ checkForAbort.Release();
- // 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);
- }
+ // 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;
- }
+ elapsed_time += ABORT_POLL_TIMEOUT;
+ if (g_pConfig->GetGCStressLevel() == 0 && !fAlreadyAssert)
+ {
+ _ASSERTE(elapsed_time < ABORT_FAIL_TIMEOUT);
+ fAlreadyAssert = TRUE;
+ }
#endif
- } // while (true)
-
+ } // while (true)
+ }
if ((GetAbortEndTime() != MAXULONGLONG) && IsAbortRequested())
{
while (TRUE)
@@ -1627,6 +1629,14 @@ LPrepareRetry:
{
return S_OK;
}
+
+#ifdef FEATURE_THREAD_ACTIVATION
+ if (UseSpecialUserModeApc())
+ {
+ InjectActivation(ActivationReason::ThreadAbort);
+ }
+#endif // FEATURE_THREAD_ACTIVATION
+
ULONGLONG curTime = CLRGetTickCount64();
if (curTime >= GetAbortEndTime())
{
@@ -5950,6 +5960,7 @@ void Thread::ApcActivationCallback(ULONG_PTR Parameter)
{
case ActivationReason::SuspendForGC:
case ActivationReason::SuspendForDebugger:
+ case ActivationReason::ThreadAbort:
HandleSuspensionForInterruptedThread(pContext);
break;