Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
[3.1] Fail FuncEval if slot backpatching lock is held by any thread (#…
Browse files Browse the repository at this point in the history
…28006)

- In many cases cooperative GC mode is entered after acquiring the slot backpatching lock and the thread may block for debugger suspension while holding the lock. A FuncEval may time out on entering the lock if for example it calls a virtual or interface method for the first time. Failing the FuncEval when the lock is held enables the debugger to fall back to other options for expression evaluation.
- Also added polls for debugger suspension before acquiring the slot backpatching lock on background threads that often operate in preemptive GC mode. A common case is when the debugger breaks while the tiering delay timer is active, the timer ticks shortly afterwards (after debugger suspension completes) and if a thread pool thread is already available, the background thread would block while holding the lock. The poll checks for debugger suspension and pulses the GC mode to block before acquiring the lock.

Risks:
- The fix is only a heuristic and lessens the problem when it is detected that the lock is held by some thread. Since the lock is acquired in preemptive GC mode, it is still possible that after the check at the start of a FuncEval, another thread acquires the lock and the FuncEval may time out. The polling makes it less likely for the lock to be taken by background tiering work, for example if a FuncEval starts while rejitting a method.
- The expression evaluation experience may be worse when it is detected that the lock is held, and may still happen from unfortunate timing
- Low risk for the change itself

Port of dotnet/runtime#2380
Fix for dotnet/runtime#1537
  • Loading branch information
kouvel authored Feb 18, 2020
1 parent b4b4098 commit c5d3d75
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 3 deletions.
9 changes: 9 additions & 0 deletions src/debug/ee/debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15313,6 +15313,15 @@ HRESULT Debugger::FuncEvalSetup(DebuggerIPCE_FuncEvalInfo *pEvalInfo,
return CORDBG_E_FUNC_EVAL_BAD_START_POINT;
}

if (MethodDescBackpatchInfoTracker::IsLockedByAnyThread())
{
// A thread may have suspended for the debugger while holding the slot backpatching lock while trying to enter
// cooperative GC mode. If the FuncEval calls a method that is eligible for slot backpatching (virtual or interface
// methods that are eligible for tiering), the FuncEval may deadlock on trying to acquire the same lock. Fail the
// FuncEval to avoid the issue.
return CORDBG_E_FUNC_EVAL_BAD_START_POINT;
}

// Create a DebuggerEval to hold info about this eval while its in progress. Constructor copies the thread's
// CONTEXT.
DebuggerEval *pDE = new (interopsafe, nothrow) DebuggerEval(filterContext, pEvalInfo, fInException);
Expand Down
27 changes: 27 additions & 0 deletions src/vm/methoddescbackpatchinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ void EntryPointSlots::Backpatch_Locked(TADDR slot, SlotType slotType, PCODE entr
// MethodDescBackpatchInfoTracker

CrstStatic MethodDescBackpatchInfoTracker::s_lock;
bool MethodDescBackpatchInfoTracker::s_isLocked = false;

#ifndef DACCESS_COMPILE

Expand Down Expand Up @@ -140,4 +141,30 @@ bool MethodDescBackpatchInfoTracker::MayHaveEntryPointSlotsToBackpatch(PTR_Metho

#endif // _DEBUG

#ifndef DACCESS_COMPILE
void MethodDescBackpatchInfoTracker::PollForDebuggerSuspension()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_PREEMPTIVE;
}
CONTRACTL_END;

_ASSERTE(!IsLockedByCurrentThread());

// If suspension is pending for the debugger, pulse the GC mode to suspend the thread here. Following this call, typically
// the lock is acquired and the GC mode is changed, and suspending there would cause FuncEvals to fail (see
// Debugger::FuncEvalSetup() at the reference to IsLockOwnedByAnyThread()). Since this thread is in preemptive mode, the
// debugger may think it's already suspended and it would be unfortunate to suspend the thread with the lock held.
Thread *thread = GetThread();
_ASSERTE(thread != nullptr);
if (thread->HasThreadState(Thread::TS_DebugSuspendPending))
{
GCX_COOP();
}
}
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
44 changes: 42 additions & 2 deletions src/vm/methoddescbackpatchinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class MethodDescBackpatchInfoTracker
{
private:
static CrstStatic s_lock;
static bool s_isLocked;

class BackpatchInfoTrackerHashTraits : public NoRemoveDefaultCrossLoaderAllocatorHashTraits<MethodDesc *, UINT_PTR>
{
Expand Down Expand Up @@ -93,9 +94,23 @@ class MethodDescBackpatchInfoTracker
static bool IsLockedByCurrentThread();
#endif

#ifndef DACCESS_COMPILE
public:
static bool IsLockedByAnyThread()
{
LIMITED_METHOD_CONTRACT;
return VolatileLoadWithoutBarrier(&s_isLocked);
}

static void PollForDebuggerSuspension();
#endif

public:
class ConditionalLockHolder : CrstHolderWithState
{
private:
bool m_isLocked;

public:
ConditionalLockHolder(bool acquireLock = true)
: CrstHolderWithState(
Expand All @@ -104,9 +119,34 @@ class MethodDescBackpatchInfoTracker
#else
nullptr
#endif
)
),
m_isLocked(false)
{
WRAPPER_NO_CONTRACT;

#ifndef DACCESS_COMPILE
if (acquireLock)
{
_ASSERTE(IsLockedByCurrentThread());
_ASSERTE(!s_isLocked);
m_isLocked = true;
s_isLocked = true;
}
#endif
}

~ConditionalLockHolder()
{
LIMITED_METHOD_CONTRACT;
WRAPPER_NO_CONTRACT;

#ifndef DACCESS_COMPILE
if (m_isLocked)
{
_ASSERTE(IsLockedByCurrentThread());
_ASSERTE(s_isLocked);
s_isLocked = false;
}
#endif
}
};

Expand Down
12 changes: 11 additions & 1 deletion src/vm/tieredcompilation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,13 @@ void TieredCompilationManager::ResumeCountingCalls(MethodDesc* pMethodDesc)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(pMethodDesc != nullptr);
MethodDescBackpatchInfoTracker::ConditionalLockHolder lockHolder(pMethodDesc->MayHaveEntryPointSlotsToBackpatch());

bool mayHaveEntryPointSlotsToBackpatch = pMethodDesc->MayHaveEntryPointSlotsToBackpatch();
if (mayHaveEntryPointSlotsToBackpatch)
{
MethodDescBackpatchInfoTracker::PollForDebuggerSuspension();
}
MethodDescBackpatchInfoTracker::ConditionalLockHolder lockHolder(mayHaveEntryPointSlotsToBackpatch);

EX_TRY
{
Expand Down Expand Up @@ -746,6 +752,10 @@ void TieredCompilationManager::ActivateCodeVersion(NativeCodeVersion nativeCodeV
ILCodeVersion ilParent;
HRESULT hr = S_OK;
bool mayHaveEntryPointSlotsToBackpatch = pMethod->MayHaveEntryPointSlotsToBackpatch();
if (mayHaveEntryPointSlotsToBackpatch)
{
MethodDescBackpatchInfoTracker::PollForDebuggerSuspension();
}
MethodDescBackpatchInfoTracker::ConditionalLockHolder lockHolder(mayHaveEntryPointSlotsToBackpatch);

{
Expand Down

0 comments on commit c5d3d75

Please sign in to comment.