diff --git a/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs b/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs index f0f0d9ca8dc4..fa7c17ef8e0b 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs @@ -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; @@ -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; + /// /// Dispatches work items to this thread. /// @@ -1248,5 +1283,23 @@ internal static object[] GetGloballyQueuedWorkItemsForDebugger() => internal static object[] GetLocallyQueuedWorkItemsForDebugger() => ToObjectArray(GetLocallyQueuedWorkItems()); + + /// + /// Gets the number of work items that are currently queued to be processed. + /// + /// + /// 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. + /// + public static long PendingWorkItemCount + { + get + { + ThreadPoolWorkQueue workQueue = ThreadPoolGlobals.workQueue; + return workQueue.LocalCount + workQueue.GlobalCount + PendingUnmanagedWorkItemCount; + } + } } } diff --git a/src/System.Private.CoreLib/src/System/Threading/Monitor.cs b/src/System.Private.CoreLib/src/System/Threading/Monitor.cs index 2837d908c6c6..08b0e112a205 100644 --- a/src/System.Private.CoreLib/src/System/Threading/Monitor.cs +++ b/src/System.Private.CoreLib/src/System/Threading/Monitor.cs @@ -234,5 +234,14 @@ public static void PulseAll(object obj) ObjPulseAll(obj); } + + /// + /// Gets the number of times there was contention upon trying to take a 's lock so far. + /// + public static extern long LockContentionCount + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } } } diff --git a/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index 02dedbf6e1e9..22be96f41a9c 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -228,6 +228,36 @@ public static void GetAvailableThreads(out int workerThreads, out int completion GetAvailableThreadsNative(out workerThreads, out completionPortThreads); } + /// + /// Gets the number of thread pool threads that currently exist. + /// + /// + /// For a thread pool implementation that may have different types of threads, the count includes all types. + /// + public static extern int ThreadCount + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + + /// + /// Gets the number of work items that have been processed so far. + /// + /// + /// For a thread pool implementation that may have different types of work items, the count includes all types. + /// + 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, diff --git a/src/classlibnative/bcltype/objectnative.cpp b/src/classlibnative/bcltype/objectnative.cpp index 5304e0110e7d..a5619e107f79 100644 --- a/src/classlibnative/bcltype/objectnative.cpp +++ b/src/classlibnative/bcltype/objectnative.cpp @@ -355,3 +355,9 @@ FCIMPL1(FC_BOOL_RET, ObjectNative::IsLockHeld, Object* pThisUNSAFE) } FCIMPLEND +FCIMPL0(INT64, ObjectNative::GetMonitorLockContentionCount) +{ + FCALL_CONTRACT; + return (INT64)Thread::GetTotalMonitorLockContentionCount(); +} +FCIMPLEND diff --git a/src/classlibnative/bcltype/objectnative.h b/src/classlibnative/bcltype/objectnative.h index 573b04ea0a3a..3d008d95a907 100644 --- a/src/classlibnative/bcltype/objectnative.h +++ b/src/classlibnative/bcltype/objectnative.h @@ -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_ diff --git a/src/vm/comthreadpool.cpp b/src/vm/comthreadpool.cpp index 18db9951cb0b..f5dc4233e029 100644 --- a/src/vm/comthreadpool.cpp +++ b/src/vm/comthreadpool.cpp @@ -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) diff --git a/src/vm/comthreadpool.h b/src/vm/comthreadpool.h index 6b0f4ec969ca..d830c9e50513 100644 --- a/src/vm/comthreadpool.h +++ b/src/vm/comthreadpool.h @@ -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); diff --git a/src/vm/ecalllist.h b/src/vm/ecalllist.h index ae98cff19633..c91f7ee28865 100644 --- a/src/vm/ecalllist.h +++ b/src/vm/ecalllist.h @@ -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) @@ -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) diff --git a/src/vm/syncblk.cpp b/src/vm/syncblk.cpp index ce31ac90b38b..9aea18f0c6c0 100644 --- a/src/vm/syncblk.cpp +++ b/src/vm/syncblk.cpp @@ -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(); diff --git a/src/vm/threadpoolrequest.h b/src/vm/threadpoolrequest.h index eaba82389cee..3b2da28b5663 100644 --- a/src/vm/threadpoolrequest.h +++ b/src/vm/threadpoolrequest.h @@ -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; diff --git a/src/vm/threads.cpp b/src/vm/threads.cpp index 6890290caaf8..8a89d84ec677 100644 --- a/src/vm/threads.cpp +++ b/src/vm/threads.cpp @@ -83,7 +83,9 @@ PTR_ThreadLocalModule ThreadLocalBlock::GetTLMIfExists(MethodTable* pMT) BOOL Thread::s_fCleanFinalizedThread = FALSE; -Volatile Thread::s_threadPoolCompletionCountOverflow = 0; +UINT64 Thread::s_workerThreadPoolCompletionCountOverflow = 0; +UINT64 Thread::s_ioThreadPoolCompletionCountOverflow = 0; +UINT64 Thread::s_monitorLockContentionCountOverflow = 0; CrstStatic g_DeadlockAwareCrst; @@ -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(); @@ -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); @@ -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 { @@ -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 { diff --git a/src/vm/threads.h b/src/vm/threads.h index 94ce27560405..5d2e7b309b32 100644 --- a/src/vm/threads.h +++ b/src/vm/threads.h @@ -3871,21 +3871,113 @@ class Thread: public IUnknown #endif // defined(FEATURE_PROFAPI_ATTACH_DETACH) || defined(DATA_PROFAPI_ATTACH_DETACH) private: - Volatile m_threadPoolCompletionCount; - static Volatile s_threadPoolCompletionCountOverflow; //counts completions for threads that have been destroyed. + UINT32 m_workerThreadPoolCompletionCount; + static UINT64 s_workerThreadPoolCompletionCountOverflow; + UINT32 m_ioThreadPoolCompletionCount; + static UINT64 s_ioThreadPoolCompletionCountOverflow; + UINT32 m_monitorLockContentionCount; + static UINT64 s_monitorLockContentionCountOverflow; -public: - static void IncrementThreadPoolCompletionCount() +#ifndef DACCESS_COMPILE +private: + static UINT32 *GetThreadLocalCountRef(Thread *pThread, SIZE_T threadLocalCountOffset) { - LIMITED_METHOD_CONTRACT; - Thread* pThread = GetThread(); - if (pThread) - pThread->m_threadPoolCompletionCount++; + WRAPPER_NO_CONTRACT; + _ASSERTE(threadLocalCountOffset <= sizeof(Thread) - sizeof(UINT32)); + + return (UINT32 *)((SIZE_T)pThread + threadLocalCountOffset); + } + + static void IncrementCount(Thread *pThread, SIZE_T threadLocalCountOffset, UINT64 *overflowCount) + { + WRAPPER_NO_CONTRACT; + _ASSERTE(overflowCount != nullptr); + + if (pThread != nullptr) + { + UINT32 *threadLocalCount = GetThreadLocalCountRef(pThread, threadLocalCountOffset); + UINT32 newCount = *threadLocalCount + 1; + if (newCount != 0) + { + VolatileStoreWithoutBarrier(threadLocalCount, newCount); + } + else + { + OnIncrementCountOverflow(threadLocalCount, overflowCount); + } + } else - FastInterlockIncrement(&s_threadPoolCompletionCountOverflow); + { + InterlockedIncrement64((LONGLONG *)overflowCount); + } + } + + static void OnIncrementCountOverflow(UINT32 *threadLocalCount, UINT64 *overflowCount); + + static UINT64 GetOverflowCount(UINT64 *overflowCount) + { + WRAPPER_NO_CONTRACT; + + if (sizeof(void *) >= sizeof(*overflowCount)) + { + return VolatileLoad(overflowCount); + } + return InterlockedCompareExchange64((LONGLONG *)overflowCount, 0, 0); // prevent tearing + } + + static UINT64 GetTotalCount(SIZE_T threadLocalCountOffset, UINT64 *overflowCount); + +public: + static void IncrementWorkerThreadPoolCompletionCount(Thread *pThread) + { + WRAPPER_NO_CONTRACT; + IncrementCount(pThread, offsetof(Thread, m_workerThreadPoolCompletionCount), &s_workerThreadPoolCompletionCountOverflow); + } + + static UINT64 GetWorkerThreadPoolCompletionCountOverflow() + { + WRAPPER_NO_CONTRACT; + return GetOverflowCount(&s_workerThreadPoolCompletionCountOverflow); + } + + static UINT64 GetTotalWorkerThreadPoolCompletionCount() + { + WRAPPER_NO_CONTRACT; + return GetTotalCount(offsetof(Thread, m_workerThreadPoolCompletionCount), &s_workerThreadPoolCompletionCountOverflow); + } + + static void IncrementIOThreadPoolCompletionCount(Thread *pThread) + { + WRAPPER_NO_CONTRACT; + IncrementCount(pThread, offsetof(Thread, m_ioThreadPoolCompletionCount), &s_ioThreadPoolCompletionCountOverflow); + } + + static UINT64 GetIOThreadPoolCompletionCountOverflow() + { + WRAPPER_NO_CONTRACT; + return GetOverflowCount(&s_ioThreadPoolCompletionCountOverflow); + } + + static UINT64 GetTotalThreadPoolCompletionCount(); + + static void IncrementMonitorLockContentionCount(Thread *pThread) + { + WRAPPER_NO_CONTRACT; + IncrementCount(pThread, offsetof(Thread, m_monitorLockContentionCount), &s_monitorLockContentionCountOverflow); } - static LONG GetTotalThreadPoolCompletionCount(); + static UINT64 GetMonitorLockContentionCountOverflow() + { + WRAPPER_NO_CONTRACT; + return GetOverflowCount(&s_monitorLockContentionCountOverflow); + } + + static UINT64 GetTotalMonitorLockContentionCount() + { + WRAPPER_NO_CONTRACT; + return GetTotalCount(offsetof(Thread, m_monitorLockContentionCount), &s_monitorLockContentionCountOverflow); + } +#endif // !DACCESS_COMPILE private: diff --git a/src/vm/win32threadpool.cpp b/src/vm/win32threadpool.cpp index 29c1d21c99c0..13a1e7b3901a 100644 --- a/src/vm/win32threadpool.cpp +++ b/src/vm/win32threadpool.cpp @@ -694,6 +694,18 @@ BOOL ThreadpoolMgr::GetAvailableThreads(DWORD* AvailableWorkerThreads, return TRUE; } +INT32 ThreadpoolMgr::GetThreadCount() +{ + WRAPPER_NO_CONTRACT; + + if (!IsInitialized()) + { + return 0; + } + + return WorkerCounter.DangerousGetDirtyCounts().NumActive + CPThreadCounter.DangerousGetDirtyCounts().NumActive; +} + void QueueUserWorkItemHelp(LPTHREAD_START_ROUTINE Function, PVOID Context) { STATIC_CONTRACT_THROWS; @@ -912,7 +924,7 @@ void ThreadpoolMgr::AdjustMaxWorkersActive() _ASSERTE(ThreadAdjustmentLock.IsHeld()); DWORD currentTicks = GetTickCount(); - LONG totalNumCompletions = Thread::GetTotalThreadPoolCompletionCount(); + LONG totalNumCompletions = (LONG)Thread::GetTotalWorkerThreadPoolCompletionCount(); LONG numCompletions = totalNumCompletions - VolatileLoad(&PriorCompletedWorkRequests); LARGE_INTEGER startTime = CurrentSampleStartTime; @@ -2865,6 +2877,10 @@ DWORD WINAPI ThreadpoolMgr::AsyncCallbackCompletion(PVOID pArgs) ((WAITORTIMERCALLBACKFUNC) waitInfo->Callback) ( waitInfo->Context, asyncCallback->waitTimedOut != FALSE); + +#ifndef FEATURE_PAL + Thread::IncrementIOThreadPoolCompletionCount(pThread); +#endif } return ERROR_SUCCESS; @@ -3610,6 +3626,12 @@ DWORD WINAPI ThreadpoolMgr::CompletionPortThreadStart(LPVOID lpArgs) ((LPOVERLAPPED_COMPLETION_ROUTINE) key)(errorCode, numBytes, pOverlapped); } + if ((void *)key != CallbackForInitiateDrainageOfCompletionPortQueue && + (void *)key != CallbackForContinueDrainageOfCompletionPortQueue) + { + Thread::IncrementIOThreadPoolCompletionCount(pThread); + } + if (pThread == NULL) { pThread = GetThread(); } diff --git a/src/vm/win32threadpool.h b/src/vm/win32threadpool.h index bb6ebc06130a..a215f301e405 100644 --- a/src/vm/win32threadpool.h +++ b/src/vm/win32threadpool.h @@ -244,6 +244,8 @@ class ThreadpoolMgr static BOOL GetAvailableThreads(DWORD* AvailableWorkerThreads, DWORD* AvailableIOCompletionThreads); + static INT32 GetThreadCount(); + static BOOL QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, PVOID Context, ULONG Flags, @@ -831,7 +833,7 @@ class ThreadpoolMgr static void NotifyWorkItemCompleted() { WRAPPER_NO_CONTRACT; - Thread::IncrementThreadPoolCompletionCount(); + Thread::IncrementWorkerThreadPoolCompletionCount(GetThread()); UpdateLastDequeueTime(); }