Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all 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
53 changes: 53 additions & 0 deletions src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,26 @@ public object TrySteal(ref bool missedSteal)
return null;
}
}

public int Count
{
get
{
bool lockTaken = false;
try
{
m_foreignLock.Enter(ref lockTaken);
return Math.Max(0, m_tailIndex - m_headIndex);
}
finally
{
if (lockTaken)
{
m_foreignLock.Exit(useMemoryBarrier: false);
}
}
}
}
}

internal bool loggingEnabled;
Expand Down Expand Up @@ -512,6 +532,21 @@ public object Dequeue(ThreadPoolWorkQueueThreadLocals tl, ref bool missedSteal)
return callback;
}

public long LocalCount
{
get
{
long count = 0;
foreach (WorkStealingQueue workStealingQueue in WorkStealingQueueList.Queues)
{
count += workStealingQueue.Count;
}
return count;
}
}

public long GlobalCount => workItems.Count;

/// <summary>
/// Dispatches work items to this thread.
/// </summary>
Expand Down Expand Up @@ -1248,5 +1283,23 @@ internal static object[] GetGloballyQueuedWorkItemsForDebugger() =>

internal static object[] GetLocallyQueuedWorkItemsForDebugger() =>
ToObjectArray(GetLocallyQueuedWorkItems());

/// <summary>
/// Gets the number of work items that are currently queued to be processed.
/// </summary>
/// <remarks>
/// For a thread pool implementation that may have different types of work items, the count includes all types that can
/// be tracked, which may only be the user work items including tasks. Some implementations may also include queued
/// timer and wait callbacks in the count. On Windows, the count is unlikely to include the number of pending IO
/// completions, as they get posted directly to an IO completion port.
/// </remarks>
public static long PendingWorkItemCount
{
get
{
ThreadPoolWorkQueue workQueue = ThreadPoolGlobals.workQueue;
return workQueue.LocalCount + workQueue.GlobalCount + PendingUnmanagedWorkItemCount;
}
}
}
}
9 changes: 9 additions & 0 deletions src/System.Private.CoreLib/src/System/Threading/Monitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,5 +234,14 @@ public static void PulseAll(object obj)

ObjPulseAll(obj);
}

/// <summary>
/// Gets the number of times there was contention upon trying to take a <see cref="Monitor"/>'s lock so far.
/// </summary>
public static extern long LockContentionCount
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,36 @@ public static void GetAvailableThreads(out int workerThreads, out int completion
GetAvailableThreadsNative(out workerThreads, out completionPortThreads);
}

/// <summary>
/// Gets the number of thread pool threads that currently exist.
/// </summary>
/// <remarks>
/// For a thread pool implementation that may have different types of threads, the count includes all types.
/// </remarks>
public static extern int ThreadCount
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
}

/// <summary>
/// Gets the number of work items that have been processed so far.
/// </summary>
/// <remarks>
/// For a thread pool implementation that may have different types of work items, the count includes all types.
/// </remarks>
public static extern long CompletedWorkItemCount
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
}

private static extern long PendingUnmanagedWorkItemCount
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
}

private static RegisteredWaitHandle RegisterWaitForSingleObject( // throws RegisterWaitException
WaitHandle waitObject,
WaitOrTimerCallback callBack,
Expand Down
6 changes: 6 additions & 0 deletions src/classlibnative/bcltype/objectnative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,9 @@ FCIMPL1(FC_BOOL_RET, ObjectNative::IsLockHeld, Object* pThisUNSAFE)
}
FCIMPLEND

FCIMPL0(INT64, ObjectNative::GetMonitorLockContentionCount)
{
FCALL_CONTRACT;
return (INT64)Thread::GetTotalMonitorLockContentionCount();
}
FCIMPLEND
1 change: 1 addition & 0 deletions src/classlibnative/bcltype/objectnative.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class ObjectNative
static FCDECL1(void, Pulse, Object* pThisUNSAFE);
static FCDECL1(void, PulseAll, Object* pThisUNSAFE);
static FCDECL1(FC_BOOL_RET, IsLockHeld, Object* pThisUNSAFE);
static FCDECL0(INT64, GetMonitorLockContentionCount);
};

#endif // _OBJECTNATIVE_H_
24 changes: 24 additions & 0 deletions src/vm/comthreadpool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,30 @@ FCIMPL2(VOID, ThreadPoolNative::CorGetAvailableThreads,DWORD* workerThreads, DWO
}
FCIMPLEND

/*****************************************************************************************************/
FCIMPL0(INT32, ThreadPoolNative::GetThreadCount)
{
FCALL_CONTRACT;
return ThreadpoolMgr::GetThreadCount();
}
FCIMPLEND

/*****************************************************************************************************/
FCIMPL0(INT64, ThreadPoolNative::GetCompletedWorkItemCount)
{
FCALL_CONTRACT;
return (INT64)Thread::GetTotalThreadPoolCompletionCount();
}
FCIMPLEND

/*****************************************************************************************************/
FCIMPL0(INT64, ThreadPoolNative::GetPendingUnmanagedWorkItemCount)
{
FCALL_CONTRACT;
return PerAppDomainTPCountList::GetUnmanagedTPCount()->GetNumRequests();
}
FCIMPLEND

/*****************************************************************************************************/

FCIMPL0(VOID, ThreadPoolNative::NotifyRequestProgress)
Expand Down
3 changes: 3 additions & 0 deletions src/vm/comthreadpool.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class ThreadPoolNative
static FCDECL2(FC_BOOL_RET, CorSetMinThreads, DWORD workerThreads, DWORD completionPortThreads);
static FCDECL2(VOID, CorGetMinThreads, DWORD* workerThreads, DWORD* completionPortThreads);
static FCDECL2(VOID, CorGetAvailableThreads, DWORD* workerThreads, DWORD* completionPortThreads);
static FCDECL0(INT32, GetThreadCount);
static FCDECL0(INT64, GetCompletedWorkItemCount);
static FCDECL0(INT64, GetPendingUnmanagedWorkItemCount);

static FCDECL0(VOID, NotifyRequestProgress);
static FCDECL0(FC_BOOL_RET, NotifyRequestComplete);
Expand Down
4 changes: 4 additions & 0 deletions src/vm/ecalllist.h
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,9 @@ FCFuncStart(gThreadPoolFuncs)
FCFuncElement("GetAvailableThreadsNative", ThreadPoolNative::CorGetAvailableThreads)
FCFuncElement("SetMinThreadsNative", ThreadPoolNative::CorSetMinThreads)
FCFuncElement("GetMinThreadsNative", ThreadPoolNative::CorGetMinThreads)
FCFuncElement("get_ThreadCount", ThreadPoolNative::GetThreadCount)
FCFuncElement("get_CompletedWorkItemCount", ThreadPoolNative::GetCompletedWorkItemCount)
FCFuncElement("get_PendingUnmanagedWorkItemCount", ThreadPoolNative::GetPendingUnmanagedWorkItemCount)
FCFuncElement("RegisterWaitForSingleObjectNative", ThreadPoolNative::CorRegisterWaitForSingleObject)
FCFuncElement("BindIOCompletionCallbackNative", ThreadPoolNative::CorBindIoCompletionCallback)
FCFuncElement("SetMaxThreadsNative", ThreadPoolNative::CorSetMaxThreads)
Expand Down Expand Up @@ -913,6 +916,7 @@ FCFuncStart(gMonitorFuncs)
FCFuncElement("ObjPulse", ObjectNative::Pulse)
FCFuncElement("ObjPulseAll", ObjectNative::PulseAll)
FCFuncElement("IsEnteredNative", ObjectNative::IsLockHeld)
FCFuncElement("get_LockContentionCount", ObjectNative::GetMonitorLockContentionCount)
FCFuncEnd()

FCFuncStart(gOverlappedFuncs)
Expand Down
1 change: 1 addition & 0 deletions src/vm/syncblk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2575,6 +2575,7 @@ BOOL AwareLock::EnterEpilogHelper(Thread* pCurThread, INT32 timeOut)
FireEtwContentionStart_V1(ETW::ContentionLog::ContentionStructs::ManagedContention, GetClrInstanceId());

LogContention();
Thread::IncrementMonitorLockContentionCount(pCurThread);

OBJECTREF obj = GetOwningObject();

Expand Down
6 changes: 6 additions & 0 deletions src/vm/threadpoolrequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ class UnManagedPerAppDomainTPCount : public IPerAppDomainTPCount {
_ASSERT(FALSE);
}

inline ULONG GetNumRequests()
{
LIMITED_METHOD_CONTRACT;
return VolatileLoad(&m_NumRequests);
}

private:
SpinLock m_lock;
ULONG m_NumRequests;
Expand Down
82 changes: 60 additions & 22 deletions src/vm/threads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ PTR_ThreadLocalModule ThreadLocalBlock::GetTLMIfExists(MethodTable* pMT)

BOOL Thread::s_fCleanFinalizedThread = FALSE;

Volatile<LONG> Thread::s_threadPoolCompletionCountOverflow = 0;
UINT64 Thread::s_workerThreadPoolCompletionCountOverflow = 0;
UINT64 Thread::s_ioThreadPoolCompletionCountOverflow = 0;
UINT64 Thread::s_monitorLockContentionCountOverflow = 0;

CrstStatic g_DeadlockAwareCrst;

Expand Down Expand Up @@ -1526,7 +1528,9 @@ Thread::Thread()
m_sfEstablisherOfActualHandlerFrame.Clear();
#endif // WIN64EXCEPTIONS

m_threadPoolCompletionCount = 0;
m_workerThreadPoolCompletionCount = 0;
m_ioThreadPoolCompletionCount = 0;
m_monitorLockContentionCount = 0;

Thread *pThread = GetThread();
InitContext();
Expand Down Expand Up @@ -5352,9 +5356,15 @@ BOOL ThreadStore::RemoveThread(Thread *target)
if (target->IsBackground())
s_pThreadStore->m_BackgroundThreadCount--;

FastInterlockExchangeAdd(
&Thread::s_threadPoolCompletionCountOverflow,
target->m_threadPoolCompletionCount);
FastInterlockExchangeAddLong(
(LONGLONG *)&Thread::s_workerThreadPoolCompletionCountOverflow,
target->m_workerThreadPoolCompletionCount);
FastInterlockExchangeAddLong(
(LONGLONG *)&Thread::s_ioThreadPoolCompletionCountOverflow,
target->m_ioThreadPoolCompletionCount);
FastInterlockExchangeAddLong(
(LONGLONG *)&Thread::s_monitorLockContentionCountOverflow,
target->m_monitorLockContentionCount);

_ASSERTE(s_pThreadStore->m_ThreadCount >= 0);
_ASSERTE(s_pThreadStore->m_BackgroundThreadCount >= 0);
Expand Down Expand Up @@ -8004,7 +8014,23 @@ BOOL ThreadStore::HoldingThreadStore(Thread *pThread)
}
}

LONG Thread::GetTotalThreadPoolCompletionCount()
NOINLINE void Thread::OnIncrementCountOverflow(UINT32 *threadLocalCount, UINT64 *overflowCount)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(threadLocalCount != nullptr);
_ASSERTE(overflowCount != nullptr);

// Increment overflow, accumulate the count for this increment into the overflow count and reset the thread-local count

// The thread store lock, in coordination with other places that read these values, ensures that both changes
// below become visible together
ThreadStoreLockHolder tsl;

*threadLocalCount = 0;
InterlockedExchangeAdd64((LONGLONG *)overflowCount, (LONGLONG)UINT32_MAX + 1);
}

UINT64 Thread::GetTotalCount(SIZE_T threadLocalCountOffset, UINT64 *overflowCount)
{
CONTRACTL
{
Expand All @@ -8013,32 +8039,44 @@ LONG Thread::GetTotalThreadPoolCompletionCount()
}
CONTRACTL_END;

LONG total;
if (g_fEEStarted) //make sure we actually have a thread store
{
// make sure up-to-date thread-local counts are visible to us
::FlushProcessWriteBuffers();
// enumerate all threads, summing their local counts.
ThreadStoreLockHolder tsl;

// enumerate all threads, summing their local counts.
ThreadStoreLockHolder tsl;
UINT64 total = GetOverflowCount(overflowCount);

total = s_threadPoolCompletionCountOverflow.Load();
Thread *pThread = NULL;
while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL)
{
total += *GetThreadLocalCountRef(pThread, threadLocalCountOffset);
}

Thread *pThread = NULL;
while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL)
{
total += pThread->m_threadPoolCompletionCount;
}
return total;
}

UINT64 Thread::GetTotalThreadPoolCompletionCount()
{
CONTRACTL
{
NOTHROW;
MODE_ANY;
}
else
CONTRACTL_END;

// enumerate all threads, summing their local counts.
ThreadStoreLockHolder tsl;

UINT64 total = GetWorkerThreadPoolCompletionCountOverflow() + GetIOThreadPoolCompletionCountOverflow();

Thread *pThread = NULL;
while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL)
{
total = s_threadPoolCompletionCountOverflow.Load();
total += pThread->m_workerThreadPoolCompletionCount;
total += pThread->m_ioThreadPoolCompletionCount;
}

return total;
}


INT32 Thread::ResetManagedThreadObject(INT32 nPriority)
{
CONTRACTL {
Expand Down
Loading