Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some refactoring of thread suspension. #34666

Merged
18 commits merged into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions src/coreclr/src/debug/di/rsthread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ bool CordbThread::OwnsFrame(CordbFrame * pFrame)
// This routine is a internal helper function for ICorDebugThread2::GetTaskId.
//
// Arguments:
// pHandle - return thread handle here after fetching from the left side. Can return SWITCHOUT_HANDLE_VALUE.
// pHandle - return thread handle here after fetching from the left side.
//
// Return Value:
// hr - It can fail with CORDBG_E_THREAD_NOT_SCHEDULED.
Expand All @@ -629,12 +629,6 @@ void CordbThread::RefreshHandle(HANDLE * phThread)
IDacDbiInterface * pDAC = GetProcess()->GetDAC();
HANDLE hThread = pDAC->GetThreadHandle(m_vmThreadToken);

if (hThread == SWITCHOUT_HANDLE_VALUE)
{
*phThread = SWITCHOUT_HANDLE_VALUE;
ThrowHR(CORDBG_E_THREAD_NOT_SCHEDULED);
}

_ASSERTE(hThread != INVALID_HANDLE_VALUE);
PREFAST_ASSUME(hThread != NULL);

Expand Down
2 changes: 0 additions & 2 deletions src/coreclr/src/inc/cor.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ typedef UNALIGNED void const *UVCP_CONSTANT;
#define TARGET_MAIN_CLR_DLL_NAME_W MAKE_TARGET_DLLNAME_W(MAIN_CLR_MODULE_NAME_W)
#define TARGET_MAIN_CLR_DLL_NAME_A MAKE_TARGET_DLLNAME_A(MAIN_CLR_MODULE_NAME_A)

#define SWITCHOUT_HANDLE_VALUE ((HANDLE)(LONG_PTR)-2)

//*****************************************************************************
//*****************************************************************************
//
Expand Down
7 changes: 5 additions & 2 deletions src/coreclr/src/jit/flowgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4016,8 +4016,11 @@ BasicBlock* Compiler::fgCreateGCPoll(GCPollType pollType, BasicBlock* block)
value = gtNewIndOfIconHandleNode(TYP_INT, (size_t)addrTrap, GTF_ICON_GLOBAL_PTR, false);
}

// Treat the reading of g_TrapReturningThreads as volatile.
Copy link
Member

@jkotas jkotas Oct 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be marked as non-CSEable at least? #Resolved

Copy link
Member Author

@VSadov VSadov Oct 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-CSEable would be good to have. I thought this is injected by JIT after optimizations.

In c++ we use compiler-only fences to make sure the read gets emitted in program order (the only gurantee that we need on the read side).
Some equivalent would be sufficient in JIT code as well.
#Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added GTF_DONT_CSE


In reply to: 503509382 [](ancestors = 503509382)

value->gtFlags |= GTF_IND_VOLATILE;
Copy link
Member

@davidwrighton davidwrighton Oct 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it reasonable to remove the volatility here? #Resolved

Copy link
Member Author

@VSadov VSadov Oct 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in c++ an equivalent load is done via LoadWithoutBarrier() to ensure that the
program order of the load is preserved. (not hoisted out of a loop or cached in a local, for example)

So we only need to ensure that this load is not optimizable.
I have confirmed with the JIT team that it is not optimizable for several reasons:

  • we introduce the load really late in terms of JIT stages - after all major optimizations are done
  • the load feeds a branch and the location is formally unknown

since noone could optimize the load, no special flags are needed.

I've added a comment explaining this instead. #Resolved

// NOTE: in c++ an equivalent load is done via LoadWithoutBarrier() to ensure that the
// program order is preserved. (not hoisted out of a loop or cached in a local, for example)
//
// Here we introduce the read really late after all major optimizations are done, and the location
// is formally unknown, so noone could optimize the load, thus no special flags are needed.

// Compare for equal to zero
GenTree* trapRelop = gtNewOperNode(GT_EQ, TYP_INT, value, gtNewIconNode(0, TYP_INT));
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/vm/gccover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1266,7 +1266,7 @@ void RemoveGcCoverageInterrupt(TADDR instrPtr, BYTE * savedInstrPtr, GCCoverageI

// A managed thread (T) can race with the GC as follows:
// 1) At the first safepoint, we notice that T is in preemptive mode during the call for GCStress
// So, it is put it in cooperative mode for the purpose of GCStress(fPremptiveGcDisabledForGcStress)
// So, it is put it in cooperative mode for the purpose of GCStress(fPreemptiveGcDisabledForGcStress)
// 2) We DoGCStress(). Start off background GC in a different thread.
// 3) Then the thread T is put back to preemptive mode (because that's where it was).
// Thread T continues execution along with the GC thread.
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/vm/proftoeeinterfaceimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9159,7 +9159,7 @@ HRESULT ProfToEEInterfaceImpl::RequestReJIT(ULONG cFunctions, // in
// Yay!
NOTHROW;

// When we suspend the runtime we drop into premptive mode
// When we suspend the runtime we drop into preemptive mode
GC_TRIGGERS;

// Yay!
Expand Down
27 changes: 20 additions & 7 deletions src/coreclr/src/vm/threads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ BOOL Thread::Alert ()
BOOL fRetVal = FALSE;
{
HANDLE handle = GetThreadHandle();
if (handle != INVALID_HANDLE_VALUE && handle != SWITCHOUT_HANDLE_VALUE)
if (handle != INVALID_HANDLE_VALUE)
{
fRetVal = ::QueueUserAPC(UserInterruptAPC, handle, APC_Code);
}
Expand Down Expand Up @@ -422,7 +422,7 @@ DWORD Thread::JoinEx(DWORD timeout, WaitMode mode)
mode = (WaitMode)(mode & ~WaitMode_InDeadlock);

HANDLE handle = GetThreadHandle();
if (handle == INVALID_HANDLE_VALUE || handle == SWITCHOUT_HANDLE_VALUE) {
if (handle == INVALID_HANDLE_VALUE) {
return WAIT_FAILED;
}
if (pCurThread) {
Expand Down Expand Up @@ -572,8 +572,7 @@ DWORD Thread::StartThread()
m_Creater.Clear();
#endif

_ASSERTE (GetThreadHandle() != INVALID_HANDLE_VALUE &&
GetThreadHandle() != SWITCHOUT_HANDLE_VALUE);
_ASSERTE (GetThreadHandle() != INVALID_HANDLE_VALUE);
dwRetVal = ::ResumeThread(GetThreadHandle());


Expand Down Expand Up @@ -1000,7 +999,7 @@ HRESULT Thread::DetachThread(BOOL fDLLThreadDetach)
}

HANDLE hThread = GetThreadHandle();
SetThreadHandle (SWITCHOUT_HANDLE_VALUE);
SetThreadHandle (INVALID_HANDLE_VALUE);
while (m_dwThreadHandleBeingUsed > 0)
{
// Another thread is using the handle now.
Expand Down Expand Up @@ -5181,15 +5180,29 @@ void ThreadStore::InitThreadStore()
// additional semantics well beyond a normal lock.
DEBUG_NOINLINE void ThreadStore::Enter()
{
WRAPPER_NO_CONTRACT;
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
// we must be in preemptive mode while taking this lock
// if suspension is in progress, the lock is taken, and there is no way to suspend us once we block
MODE_PREEMPTIVE;
}
CONTRACTL_END;

ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
CHECK_ONE_STORE();
m_Crst.Enter();
}

DEBUG_NOINLINE void ThreadStore::Leave()
{
WRAPPER_NO_CONTRACT;
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
}
CONTRACTL_END;

ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
CHECK_ONE_STORE();
m_Crst.Leave();
Expand Down
36 changes: 21 additions & 15 deletions src/coreclr/src/vm/threads.h
Original file line number Diff line number Diff line change
Expand Up @@ -1097,7 +1097,11 @@ class Thread
TS_Unknown = 0x00000000, // threads are initialized this way

TS_AbortRequested = 0x00000001, // Abort the thread
TS_GCSuspendPending = 0x00000002, // waiting to get to safe spot for GC

TS_GCSuspendPending = 0x00000002, // ThreadSuspend::SuspendRuntime watches this thread to leave coop mode.
TS_GCSuspendRedirected = 0x00000004, // ThreadSuspend::SuspendRuntime has redirected the thread to suspention routine.
TS_GCSuspendFlags = TS_GCSuspendPending | TS_GCSuspendRedirected, // used to track suspension progress. Only SuspendRuntime writes/resets these.

TS_DebugSuspendPending = 0x00000008, // Is the debugger suspending threads?
TS_GCOnTransitions = 0x00000010, // Force a GC on stub transitions (GCStress only)

Expand Down Expand Up @@ -1656,9 +1660,12 @@ class Thread
EEThreadId m_Creater;
#endif

// After we suspend a thread, we may need to call EEJitManager::JitCodeToMethodInfo
// or StressLog which may waits on a spinlock. It is unsafe to suspend a thread while it
// is in this state.
// A thread may forbid its own suspension. For example when holding certain locks.
// The state is a counter to allow nested forbids.
// The state is only modified by the current thread.
// Other threads may read this state, but must mind the races.
// It must be assumed that a running thread (or one that may start running) may change this state at any time.
// One exception: SuspendThread can read this "reliably" from other threads by temporarily suspending them, reading, and releasing if != 0.
Volatile<LONG> m_dwForbidSuspendThread;

public:
Expand All @@ -1684,7 +1691,8 @@ class Thread
STRESS_LOG2(LF_SYNC, LL_INFO100000, "Set forbid suspend [%d] for thread %p.\n", pThread->m_dwForbidSuspendThread.Load(), pThread);
}
#endif
FastInterlockIncrement(&pThread->m_dwForbidSuspendThread);
// modified only by the current thread, so ++ is ok
pThread->m_dwForbidSuspendThread.RawValue()++;
}
#endif //!DACCESS_COMPILE
}
Expand All @@ -1703,8 +1711,10 @@ class Thread
Thread * pThread = GetThreadNULLOk();
if (pThread)
{
_ASSERTE (pThread->m_dwForbidSuspendThread != (LONG)0);
FastInterlockDecrement(&pThread->m_dwForbidSuspendThread);
_ASSERTE (pThread->m_dwForbidSuspendThread >= (LONG)0);

// modified only by the current thread, so -- is ok
pThread->m_dwForbidSuspendThread.RawValue()--;
#ifdef _DEBUG
{
//DEBUG_ONLY;
Expand All @@ -1715,9 +1725,11 @@ class Thread
#endif //!DACCESS_COMPILE
}

// Returns the state of m_dwForbidSuspendThread as it was at the time of the call.
// It may asynchronously change if there are no additional guarantees (i.e. it is the current thread or the thread is suspended)
bool IsInForbidSuspendRegion()
{
return m_dwForbidSuspendThread != (LONG)0;
return m_dwForbidSuspendThread.LoadWithoutBarrier() != (LONG)0;
}

typedef StateHolder<Thread::IncForbidSuspendThread, Thread::DecForbidSuspendThread> ForbidSuspendThreadHolder;
Expand Down Expand Up @@ -2397,10 +2409,6 @@ class Thread
// that either we are not allowed to call into the host, or we ran
// out of memory.
STR_NoStressLog,

// The EE thread is currently switched out. This can only happen
// if we are hosted and the host schedules EE threads on fibers.
STR_SwitchedOut,
};

#if defined(FEATURE_HIJACK) && defined(TARGET_UNIX)
Expand Down Expand Up @@ -3506,7 +3514,6 @@ class Thread
CounterHolder handleHolder(&m_dwThreadHandleBeingUsed);
HANDLE handle = m_ThreadHandle;
_ASSERTE ( handle == INVALID_HANDLE_VALUE
|| handle == SWITCHOUT_HANDLE_VALUE
|| m_OSThreadId == 0
|| m_OSThreadId == 0xbaadf00d
|| ::MatchThreadHandleToOsId(handle, (DWORD)m_OSThreadId) );
Expand All @@ -3522,7 +3529,6 @@ class Thread
LIMITED_METHOD_CONTRACT;
#if defined(_DEBUG)
_ASSERTE ( h == INVALID_HANDLE_VALUE
|| h == SWITCHOUT_HANDLE_VALUE
|| m_OSThreadId == 0
|| m_OSThreadId == 0xbaadf00d
|| ::MatchThreadHandleToOsId(h, (DWORD)m_OSThreadId) );
Expand Down Expand Up @@ -3934,7 +3940,7 @@ class Thread
UnhijackThread();
#endif // FEATURE_HIJACK

ResetThreadState(TS_GCSuspendPending);
_ASSERTE(!HasThreadStateOpportunistic(Thread::TS_GCSuspendPending));
}

static LPVOID GetStaticFieldAddress(FieldDesc *pFD);
Expand Down
Loading