From da29c6127b0a6a22f4253be06038feaaa8e4f89e Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 3 Mar 2020 22:34:21 -0800 Subject: [PATCH 01/48] Add dependencies - Added for coreclr: Unix implementation of LowLevelLifoSemaphore for coreclr, Thread.UninterruptibleSleep0(), Thread.IsThreadPoolThread setter, LowLevelLock - Moved LowLevelSpinWaiter from mono to shared, restored some functionality from CoreRT --- .../System.Private.CoreLib.csproj | 3 + .../Threading/LowLevelLifoSemaphore.Unix.cs | 52 +++++ .../src/System/Threading/Thread.CoreCLR.cs | 5 + src/coreclr/src/vm/comsynchronizable.cpp | 27 +++ src/coreclr/src/vm/comsynchronizable.h | 4 +- src/coreclr/src/vm/comwaithandle.cpp | 19 ++ src/coreclr/src/vm/comwaithandle.h | 3 + src/coreclr/src/vm/ecalllist.h | 11 + src/coreclr/src/vm/threads.h | 6 + .../System.Private.CoreLib.Shared.projitems | 2 + .../src/System/Threading/LowLevelLock.cs | 215 ++++++++++++++++++ .../System/Threading/LowLevelSpinWaiter.cs | 50 +++- .../System.Private.CoreLib.csproj | 1 - 13 files changed, 392 insertions(+), 6 deletions(-) create mode 100644 src/coreclr/src/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs rename src/{mono/netcore => libraries}/System.Private.CoreLib/src/System/Threading/LowLevelSpinWaiter.cs (54%) diff --git a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj index 2075234371ffee..a142b148e6c224 100644 --- a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -20,6 +20,8 @@ true $(IntermediateOutputPath)System.Private.CoreLib.xml $(MSBuildThisFileDirectory)src\ILLink\ + + true @@ -289,6 +291,7 @@ + diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs new file mode 100644 index 00000000000000..80483d4b9666b8 --- /dev/null +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +namespace System.Threading +{ + /// + /// A LIFO semaphore implemented using the PAL's semaphore with uninterruptible waits. + /// + internal sealed partial class LowLevelLifoSemaphore : IDisposable + { + private Semaphore? _semaphore; + + private void Create(int maximumSignalCount) + { + Debug.Assert(maximumSignalCount > 0); + _semaphore = new Semaphore(0, maximumSignalCount); + } + + public bool WaitCore(int timeoutMs) + { + Debug.Assert(_semaphore != null); + Debug.Assert(timeoutMs >= -1); + + int waitResult = WaitNative(_semaphore!.SafeWaitHandle, timeoutMs); + Debug.Assert(waitResult == WaitHandle.WaitSuccess || waitResult == WaitHandle.WaitTimeout); + return waitResult == WaitHandle.WaitSuccess; + } + + [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)] + private static extern int WaitNative(SafeWaitHandle handle, int timeoutMs); + + public void ReleaseCore(int count) + { + Debug.Assert(_semaphore != null); + Debug.Assert(count > 0); + + _semaphore!.Release(count); + } + + public void Dispose() + { + Debug.Assert(_semaphore != null); + _semaphore!.Dispose(); + } + } +} diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index 7a58e32a818aca..69e4213b59cd2e 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -259,6 +259,9 @@ private void SetCultureOnUnstartedThreadNoCheck(CultureInfo value, bool uiCultur public static void Sleep(int millisecondsTimeout) => SleepInternal(millisecondsTimeout); + [DllImport(RuntimeHelpers.QCall)] + internal static extern void UninterruptibleSleep0(); + /// /// Wait for a length of time proportional to 'iterations'. Each iteration is should /// only take a few machine instructions. Calling this API is preferable to coding @@ -351,6 +354,8 @@ public extern bool IsThreadPoolThread { [MethodImpl(MethodImplOptions.InternalCall)] get; + [MethodImpl(MethodImplOptions.InternalCall)] + internal set; } /// Returns the priority of the thread. diff --git a/src/coreclr/src/vm/comsynchronizable.cpp b/src/coreclr/src/vm/comsynchronizable.cpp index 687dbabcbc9084..2e83ac69ceab99 100644 --- a/src/coreclr/src/vm/comsynchronizable.cpp +++ b/src/coreclr/src/vm/comsynchronizable.cpp @@ -648,6 +648,17 @@ FCIMPLEND #define Sleep(dwMilliseconds) Dont_Use_Sleep(dwMilliseconds) +void QCALLTYPE ThreadNative::UninterruptibleSleep0() +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + ClrSleepEx(0, false); + + END_QCALL; +} + FCIMPL1(INT32, ThreadNative::GetManagedThreadId, ThreadBaseObject* th) { FCALL_CONTRACT; @@ -1318,6 +1329,22 @@ FCIMPL1(FC_BOOL_RET, ThreadNative::IsThreadpoolThread, ThreadBaseObject* thread) } FCIMPLEND +FCIMPL1(void, ThreadNative::SetIsThreadpoolThread, ThreadBaseObject* thread) +{ + FCALL_CONTRACT; + + if (thread == NULL) + FCThrowResVoid(kNullReferenceException, W("NullReference_This")); + + Thread *pThread = thread->GetInternal(); + + if (pThread == NULL) + FCThrowExVoid(kThreadStateException, IDS_EE_THREAD_DEAD_STATE, NULL, NULL, NULL); + + pThread->SetIsThreadPoolThread(); +} +FCIMPLEND + INT32 QCALLTYPE ThreadNative::GetOptimalMaxSpinWaitsPerSpinIteration() { QCALL_CONTRACT; diff --git a/src/coreclr/src/vm/comsynchronizable.h b/src/coreclr/src/vm/comsynchronizable.h index 8de4ee3c5dbee9..2e6fe1e6fdcdb0 100644 --- a/src/coreclr/src/vm/comsynchronizable.h +++ b/src/coreclr/src/vm/comsynchronizable.h @@ -71,6 +71,7 @@ friend class ThreadBaseObject; #undef Sleep static FCDECL1(void, Sleep, INT32 iTime); #define Sleep(a) Dont_Use_Sleep(a) + static void QCALLTYPE UninterruptibleSleep0(); static FCDECL3(void, SetStart, ThreadBaseObject* pThisUNSAFE, Object* pDelegateUNSAFE, INT32 iRequestedStackSize); static FCDECL2(void, SetBackground, ThreadBaseObject* pThisUNSAFE, CLR_BOOL isBackground); static FCDECL1(FC_BOOL_RET, IsBackground, ThreadBaseObject* pThisUNSAFE); @@ -98,7 +99,8 @@ friend class ThreadBaseObject; #ifdef FEATURE_COMINTEROP static FCDECL1(void, DisableComObjectEagerCleanup, ThreadBaseObject* pThis); #endif //FEATURE_COMINTEROP - static FCDECL1(FC_BOOL_RET,IsThreadpoolThread, ThreadBaseObject* thread); + static FCDECL1(FC_BOOL_RET,IsThreadpoolThread, ThreadBaseObject* thread); + static FCDECL1(void, SetIsThreadpoolThread, ThreadBaseObject* thread); static FCDECL1(Object*, GetThreadDeserializationTracker, StackCrawlMark* stackMark); static FCDECL0(INT32, GetCurrentProcessorNumber); diff --git a/src/coreclr/src/vm/comwaithandle.cpp b/src/coreclr/src/vm/comwaithandle.cpp index d43693e1d3b166..5c9181a4722a60 100644 --- a/src/coreclr/src/vm/comwaithandle.cpp +++ b/src/coreclr/src/vm/comwaithandle.cpp @@ -35,6 +35,25 @@ FCIMPL2(INT32, WaitHandleNative::CorWaitOneNative, HANDLE handle, INT32 timeout) } FCIMPLEND +#ifdef TARGET_UNIX +INT32 QCALLTYPE WaitHandleNative::CorWaitOnePrioritizedNative(HANDLE handle, INT32 timeoutMs) +{ + QCALL_CONTRACT; + + DWORD result = WAIT_FAILED; + + BEGIN_QCALL; + + _ASSERTE(handle != NULL); + _ASSERTE(handle != INVALID_HANDLE_VALUE); + + result = PAL_WaitForSingleObjectPrioritized(handle, timeoutMs); + + END_QCALL; + return (INT32)result; +} +#endif + FCIMPL4(INT32, WaitHandleNative::CorWaitMultipleNative, HANDLE *handleArray, INT32 numHandles, CLR_BOOL waitForAll, INT32 timeout) { FCALL_CONTRACT; diff --git a/src/coreclr/src/vm/comwaithandle.h b/src/coreclr/src/vm/comwaithandle.h index 92a48ecbfdefb4..4bdee2221bf038 100644 --- a/src/coreclr/src/vm/comwaithandle.h +++ b/src/coreclr/src/vm/comwaithandle.h @@ -19,6 +19,9 @@ class WaitHandleNative { public: static FCDECL2(INT32, CorWaitOneNative, HANDLE handle, INT32 timeout); +#ifdef TARGET_UNIX + static INT32 QCALLTYPE CorWaitOnePrioritizedNative(HANDLE handle, INT32 timeoutMs); +#endif static FCDECL4(INT32, CorWaitMultipleNative, HANDLE *handleArray, INT32 numHandles, CLR_BOOL waitForAll, INT32 timeout); static FCDECL3(INT32, CorSignalAndWaitOneNative, HANDLE waitHandleSignalUNSAFE, HANDLE waitHandleWaitUNSAFE, INT32 timeout); }; diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h index c2bcec49deb757..da9ea6d59d0259 100644 --- a/src/coreclr/src/vm/ecalllist.h +++ b/src/coreclr/src/vm/ecalllist.h @@ -596,6 +596,7 @@ FCFuncStart(gThreadFuncs) #undef Sleep FCFuncElement("SleepInternal", ThreadNative::Sleep) #define Sleep(a) Dont_Use_Sleep(a) + QCFuncElement("UninterruptibleSleep0", ThreadNative::UninterruptibleSleep0) FCFuncElement("SetStart", ThreadNative::SetStart) QCFuncElement("InformThreadNameChange", ThreadNative::InformThreadNameChange) FCFuncElement("SpinWaitInternal", ThreadNative::SpinWait) @@ -610,6 +611,7 @@ FCFuncStart(gThreadFuncs) FCFuncElement("IsBackgroundNative", ThreadNative::IsBackground) FCFuncElement("SetBackgroundNative", ThreadNative::SetBackground) FCFuncElement("get_IsThreadPoolThread", ThreadNative::IsThreadpoolThread) + FCFuncElement("set_IsThreadPoolThread", ThreadNative::SetIsThreadpoolThread) FCFuncElement("GetPriorityNative", ThreadNative::GetPriority) FCFuncElement("SetPriorityNative", ThreadNative::SetPriority) QCFuncElement("GetCurrentOSThreadId", ThreadNative::GetCurrentOSThreadId) @@ -665,6 +667,12 @@ FCFuncStart(gWaitHandleFuncs) FCFuncElement("SignalAndWaitNative", WaitHandleNative::CorSignalAndWaitOneNative) FCFuncEnd() +#ifdef TARGET_UNIX +FCFuncStart(gLowLevelLifoSemaphoreFuncs) + QCFuncElement("WaitNative", WaitHandleNative::CorWaitOnePrioritizedNative) +FCFuncEnd() +#endif + #ifdef FEATURE_COMINTEROP FCFuncStart(gVariantFuncs) FCFuncElement("SetFieldsObject", COMVariant::SetFieldsObject) @@ -1151,6 +1159,9 @@ FCClassElement("Interlocked", "System.Threading", gInterlockedFuncs) FCClassElement("Kernel32", "", gPalKernel32Funcs) #endif FCClassElement("LoaderAllocatorScout", "System.Reflection", gLoaderAllocatorFuncs) +#ifdef TARGET_UNIX +FCClassElement("LowLevelLifoSemaphore", "System.Threading", gLowLevelLifoSemaphoreFuncs) +#endif FCClassElement("Marshal", "System.Runtime.InteropServices", gInteropMarshalFuncs) FCClassElement("Math", "System", gMathFuncs) FCClassElement("MathF", "System", gMathFFuncs) diff --git a/src/coreclr/src/vm/threads.h b/src/coreclr/src/vm/threads.h index fc99747d1c7188..7a964552264d69 100644 --- a/src/coreclr/src/vm/threads.h +++ b/src/coreclr/src/vm/threads.h @@ -2505,6 +2505,12 @@ class Thread return m_State & (Thread::TS_TPWorkerThread | Thread::TS_CompletionPortThread); } + void SetIsThreadPoolThread() + { + LIMITED_METHOD_CONTRACT; + FastInterlockOr((ULONG *)&m_State, Thread::TS_TPWorkerThread); + } + // public suspend functions. System ones are internal, like for GC. User ones // correspond to suspend/resume calls on the exposed System.Thread object. static bool SysStartSuspendForDebug(AppDomain *pAppDomain); diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 8c2111ed5df763..6520bde304db9e 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -954,6 +954,7 @@ + @@ -1948,6 +1949,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs new file mode 100644 index 00000000000000..12c4b7af37776c --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs @@ -0,0 +1,215 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.Threading +{ + /// + /// A lightweight non-recursive mutex. + /// Waits on this lock are uninterruptible. + /// + internal sealed class LowLevelLock : IDisposable + { + // TODO: Tune these values + private const int SpinCount = 8; + private const int SpinSleep0Threshold = 4; + + private const int LockedMask = 1; + private const int WaiterCountIncrement = 2; + + // Layout: + // - Bit 0: 1 if the lock is locked, 0 otherwise + // - Remaining bits: Number of threads waiting to acquire a lock + private int _state; + +#if DEBUG + private Thread? _ownerThread; +#endif + + // Indicates whether a thread has been signaled, but has not yet been released from the wait. See SignalWaiter. Reads + // and writes must occur while _monitor is locked. + private bool _isAnyWaitingThreadSignaled; + + private LowLevelSpinWaiter _spinWaiter; + private readonly Func _spinWaitTryAcquireCallback; + private readonly LowLevelMonitor _monitor; + + public LowLevelLock() + { + _spinWaiter = default(LowLevelSpinWaiter); + _spinWaitTryAcquireCallback = SpinWaitTryAcquireCallback; + _monitor = new LowLevelMonitor(); + } + + ~LowLevelLock() => Dispose(false); + public void Dispose() => Dispose(true); + + private void Dispose(bool disposing) + { + VerifyIsNotLockedByAnyThread(); + + if (disposing && _monitor != null) + { + _monitor.Dispose(); + } + + GC.SuppressFinalize(this); + } + + public void VerifyIsLocked() + { +#if DEBUG + Debug.Assert(_ownerThread == Thread.CurrentThread); + Debug.Assert((_state & LockedMask) != 0); +#endif + } + + public void VerifyIsNotLocked() + { +#if DEBUG + Debug.Assert(_ownerThread != Thread.CurrentThread); +#endif + } + + private void VerifyIsNotLockedByAnyThread() + { +#if DEBUG + Debug.Assert(_ownerThread == null); +#endif + } + + private void ResetOwnerThread() + { +#if DEBUG + VerifyIsLocked(); + _ownerThread = null; +#endif + } + + private void SetOwnerThreadToCurrent() + { +#if DEBUG + VerifyIsNotLockedByAnyThread(); + _ownerThread = Thread.CurrentThread; +#endif + } + + public bool TryAcquire() + { + VerifyIsNotLocked(); + + // A common case is that there are no waiters, so hope for that and try to acquire the lock + int state = Interlocked.CompareExchange(ref _state, LockedMask, 0); + if (state == 0 || TryAcquire_NoFastPath(state)) + { + SetOwnerThreadToCurrent(); + return true; + } + return false; + } + + private bool TryAcquire_NoFastPath(int state) + { + // The lock may be available, but there may be waiters. This thread could acquire the lock in that case. Acquiring + // the lock means that if this thread is repeatedly acquiring and releasing the lock, it could permanently starve + // waiters. Waiting instead in the same situation would deterministically create a lock convoy. Here, we opt for + // acquiring the lock to prevent a deterministic lock convoy in that situation, and rely on the system's + // waiting/waking implementation to mitigate starvation, even in cases where there are enough logical processors to + // accommodate all threads. + return (state & LockedMask) == 0 && Interlocked.CompareExchange(ref _state, state + LockedMask, state) == state; + } + + private bool SpinWaitTryAcquireCallback() => TryAcquire_NoFastPath(_state); + + public void Acquire() + { + if (!TryAcquire()) + { + WaitAndAcquire(); + } + } + + private void WaitAndAcquire() + { + VerifyIsNotLocked(); + + // Spin a bit to see if the lock becomes available, before forcing the thread into a wait state + if (_spinWaiter.SpinWaitForCondition(_spinWaitTryAcquireCallback, SpinCount, SpinSleep0Threshold)) + { + Debug.Assert((_state & LockedMask) != 0); + SetOwnerThreadToCurrent(); + return; + } + + _monitor.Acquire(); + + // Register this thread as a waiter by incrementing the waiter count. Incrementing the waiter count and waiting on + // the monitor need to appear atomic to SignalWaiter so that its signal won't be lost. + int state = Interlocked.Add(ref _state, WaiterCountIncrement); + + // Wait on the monitor until signaled, repeatedly until the lock can be acquired by this thread + while (true) + { + // The lock may have been released before the waiter count was incremented above, so try to acquire the lock + // with the new state before waiting + if ((state & LockedMask) == 0 && + Interlocked.CompareExchange(ref _state, state + (LockedMask - WaiterCountIncrement), state) == state) + { + break; + } + + _monitor.Wait(); + + // Indicate to SignalWaiter that the signaled thread has woken up + _isAnyWaitingThreadSignaled = false; + + state = _state; + Debug.Assert((uint)state >= WaiterCountIncrement); + } + + _monitor.Release(); + + Debug.Assert((_state & LockedMask) != 0); + SetOwnerThreadToCurrent(); + } + + public void Release() + { + Debug.Assert((_state & LockedMask) != 0); + ResetOwnerThread(); + + if (Interlocked.Decrement(ref _state) != 0) + { + SignalWaiter(); + } + } + + private void SignalWaiter() + { + // Since the lock was already released by the caller, there are no guarantees on the state at this point. For + // instance, if there was only one thread waiting before the lock was released, then after the lock was released, + // another thread may have acquired and released the lock, and signaled the waiter, before the first thread arrives + // here. The monitor's lock is used to synchronize changes to the waiter count, so acquire the monitor and recheck + // the waiter count before signaling. + _monitor.Acquire(); + + // Keep track of whether a thread has been signaled but has not yet been released from the wait. + // _isAnyWaitingThreadSignaled is set to false when a signaled thread wakes up. Since threads can preempt waiting + // threads and acquire the lock (see TryAcquire), it allows for example, one thread to acquire and release the lock + // multiple times while there are multiple waiting threads. In such a case, we don't want that thread to signal a + // waiter every time it releases the lock, as that will cause unnecessary context switches with more and more + // signaled threads waking up, finding that the lock is still locked, and going right back into a wait state. So, + // signal only one waiting thread at a time. + if ((uint)_state >= WaiterCountIncrement && !_isAnyWaitingThreadSignaled) + { + _isAnyWaitingThreadSignaled = true; + _monitor.Signal_Release(); + return; + } + + _monitor.Release(); + } + } +} diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/LowLevelSpinWaiter.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelSpinWaiter.cs similarity index 54% rename from src/mono/netcore/System.Private.CoreLib/src/System/Threading/LowLevelSpinWaiter.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelSpinWaiter.cs index 462780d0bf1495..e1c0766b3f0dfa 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/LowLevelSpinWaiter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelSpinWaiter.cs @@ -5,8 +5,52 @@ namespace System.Threading { + /// + /// A lightweight spin-waiter intended to be used as the first-level wait for a condition before the user forces the thread + /// into a wait state, and where the condition to be checked in each iteration is relatively cheap, like just an interlocked + /// operation. + /// + /// Used by the wait subsystem on Unix, so this class cannot have any dependencies on the wait subsystem. + /// internal struct LowLevelSpinWaiter { + private int _spinningThreadCount; + + public bool SpinWaitForCondition(Func condition, int spinCount, int sleep0Threshold) + { + Debug.Assert(condition != null); + + int processorCount = Environment.ProcessorCount; + int spinningThreadCount = Interlocked.Increment(ref _spinningThreadCount); + try + { + // Limit the maximum spinning thread count to the processor count to prevent unnecessary context switching + // caused by an excessive number of threads spin waiting, perhaps even slowing down the thread holding the + // resource being waited upon + if (spinningThreadCount <= processorCount) + { + // For uniprocessor systems, start at the yield threshold since the pause instructions used for waiting + // prior to that threshold would not help other threads make progress + for (int spinIndex = processorCount > 1 ? 0 : sleep0Threshold; spinIndex < spinCount; ++spinIndex) + { + // The caller should check the condition in a fast path before calling this method, so wait first + Wait(spinIndex, sleep0Threshold, processorCount); + + if (condition()) + { + return true; + } + } + } + } + finally + { + Interlocked.Decrement(ref _spinningThreadCount); + } + + return false; + } + public static void Wait(int spinIndex, int sleep0Threshold, int processorCount) { Debug.Assert(spinIndex >= 0); @@ -40,10 +84,8 @@ public static void Wait(int spinIndex, int sleep0Threshold, int processorCount) return; } - // Thread.Sleep(int) is interruptible. The current operation may not allow thread interrupt - // (for instance, LowLevelLock.Acquire as part of EventWaitHandle.Set). Use the - // uninterruptible version of Sleep(0). Not doing Thread.Yield, it does not seem to have any - // benefit over Sleep(0). + // Thread.Sleep is interruptible. The current operation may not allow thread interrupt. Use the uninterruptible + // version of Sleep(0). Not doing Thread.Yield, it does not seem to have any benefit over Sleep(0). Thread.UninterruptibleSleep0(); // Don't want to Sleep(1) in this spin wait: diff --git a/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj index fd39b8ff0f15e0..0c8cde19356e25 100644 --- a/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -259,7 +259,6 @@ - From 423d83034cb3b42fa0656773e08a4eb0f4c5d455 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 3 Mar 2020 22:34:21 -0800 Subject: [PATCH 02/48] Add config var --- .../System.Private.CoreLib.csproj | 1 + .../System/Threading/ThreadPool.CoreCLR.cs | 180 ++++++++++++-- src/coreclr/src/inc/clrconfigvalues.h | 3 +- src/coreclr/src/vm/ceemain.cpp | 6 + src/coreclr/src/vm/comthreadpool.cpp | 48 ++++ src/coreclr/src/vm/comthreadpool.h | 5 + src/coreclr/src/vm/ecalllist.h | 11 +- src/coreclr/src/vm/hillclimbing.cpp | 5 + src/coreclr/src/vm/threadpoolrequest.cpp | 44 +++- src/coreclr/src/vm/threadpoolrequest.h | 17 -- src/coreclr/src/vm/threads.cpp | 13 +- src/coreclr/src/vm/win32threadpool.cpp | 232 +++++++++++------- src/coreclr/src/vm/win32threadpool.h | 39 +-- .../System.Private.CoreLib.Shared.projitems | 4 +- .../src/System/AppContextConfigHelper.cs | 12 + .../System/Threading/ThreadPool.Portable.cs | 3 +- 16 files changed, 463 insertions(+), 160 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj index a142b148e6c224..e326528e0084e0 100644 --- a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -21,6 +21,7 @@ $(IntermediateOutputPath)System.Private.CoreLib.xml $(MSBuildThisFileDirectory)src\ILLink\ + true true diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index 86b6e4443cad84..a12a496be872d8 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -193,45 +193,131 @@ public static partial class ThreadPool // Time in ms for which ThreadPoolWorkQueue.Dispatch keeps executing work items before returning to the OS private const uint DispatchQuantum = 30; + internal static readonly bool UsePortableThreadPool = InitializeConfigAndDetermineUsePortableThreadPool(); + internal static readonly bool EnableWorkerTracking = GetEnableWorkerTracking(); internal static bool KeepDispatching(int startTickCount) { + if (UsePortableThreadPool) + { + return true; + } + // Note: this function may incorrectly return false due to TickCount overflow // if work item execution took around a multiple of 2^32 milliseconds (~49.7 days), // which is improbable. return (uint)(Environment.TickCount - startTickCount) < DispatchQuantum; } + private static unsafe bool InitializeConfigAndDetermineUsePortableThreadPool() + { + bool usePortableThreadPool = false; + int configVariableIndex = 0; + while (true) + { + int nextConfigVariableIndex = + GetNextConfigUInt32Value( + configVariableIndex, + out uint configValue, + out bool isBoolean, + out char* appContextConfigNameUnsafe); + if (nextConfigVariableIndex < 0) + { + break; + } + + Debug.Assert(nextConfigVariableIndex > configVariableIndex); + configVariableIndex = nextConfigVariableIndex; + + if (appContextConfigNameUnsafe == null) + { + // Special case for UsePortableThreadPool, which doesn't go into the AppContext + Debug.Assert(configValue != 0); + Debug.Assert(isBoolean); + usePortableThreadPool = true; + continue; + } + + var appContextConfigName = new string(appContextConfigNameUnsafe); + if (isBoolean) + { + AppContext.SetSwitch(appContextConfigName, configValue != 0); + } + else + { + AppContext.SetData(appContextConfigName, configValue); + } + } + + return usePortableThreadPool; + } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern unsafe int GetNextConfigUInt32Value( + int configVariableIndex, + out uint configValue, + out bool isBoolean, + out char* appContextConfigName); + public static bool SetMaxThreads(int workerThreads, int completionPortThreads) { - return - workerThreads >= 0 && - completionPortThreads >= 0 && - SetMaxThreadsNative(workerThreads, completionPortThreads); + if (workerThreads < 0 || completionPortThreads < 0) + { + return false; + } + + if (UsePortableThreadPool && !PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads)) + { + return false; + } + + return SetMaxThreadsNative(workerThreads, completionPortThreads); } public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) { GetMaxThreadsNative(out workerThreads, out completionPortThreads); + + if (UsePortableThreadPool) + { + workerThreads = PortableThreadPool.ThreadPoolInstance.GetMaxThreads(); + } } public static bool SetMinThreads(int workerThreads, int completionPortThreads) { - return - workerThreads >= 0 && - completionPortThreads >= 0 && - SetMinThreadsNative(workerThreads, completionPortThreads); + if (workerThreads < 0 || completionPortThreads < 0) + { + return false; + } + + if (UsePortableThreadPool && !PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads)) + { + return false; + } + + return SetMinThreadsNative(workerThreads, completionPortThreads); } public static void GetMinThreads(out int workerThreads, out int completionPortThreads) { GetMinThreadsNative(out workerThreads, out completionPortThreads); + + if (UsePortableThreadPool) + { + workerThreads = PortableThreadPool.ThreadPoolInstance.GetMinThreads(); + } } public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) { GetAvailableThreadsNative(out workerThreads, out completionPortThreads); + + if (UsePortableThreadPool) + { + workerThreads = PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(); + } } /// @@ -240,11 +326,11 @@ public static void GetAvailableThreads(out int workerThreads, out int completion /// /// 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; - } + public static int ThreadCount => + (UsePortableThreadPool ? PortableThreadPool.ThreadPoolInstance.ThreadCount : 0) + GetThreadCount(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern int GetThreadCount(); /// /// Gets the number of work items that have been processed so far. @@ -252,16 +338,26 @@ public static extern int ThreadCount /// /// For a thread pool implementation that may have different types of work items, the count includes all types. /// - public static long CompletedWorkItemCount => GetCompletedWorkItemCount(); + public static long CompletedWorkItemCount + { + get + { + long count = GetCompletedWorkItemCount(); + if (UsePortableThreadPool) + { + count += PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount; + } + return count; + } + } [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)] private static extern long GetCompletedWorkItemCount(); - private static extern long PendingUnmanagedWorkItemCount - { - [MethodImpl(MethodImplOptions.InternalCall)] - get; - } + private static long PendingUnmanagedWorkItemCount => UsePortableThreadPool ? 0 : GetPendingUnmanagedWorkItemCount(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern long GetPendingUnmanagedWorkItemCount(); private static RegisteredWaitHandle RegisterWaitForSingleObject( WaitHandle waitObject, @@ -295,8 +391,19 @@ bool compressStack return registeredWaitHandle; } + internal static void RequestWorkerThread() + { + if (UsePortableThreadPool) + { + PortableThreadPool.ThreadPoolInstance.RequestWorker(); + return; + } + + RequestWorkerThreadNative(); + } + [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)] - internal static extern Interop.BOOL RequestWorkerThread(); + private static extern Interop.BOOL RequestWorkerThreadNative(); [MethodImpl(MethodImplOptions.InternalCall)] private static extern unsafe bool PostQueuedCompletionStatus(NativeOverlapped* overlapped); @@ -322,19 +429,46 @@ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapp [MethodImpl(MethodImplOptions.InternalCall)] private static extern void GetAvailableThreadsNative(out int workerThreads, out int completionPortThreads); + internal static bool NotifyWorkItemComplete() + { + if (UsePortableThreadPool) + { + return PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(); + } + + return NotifyWorkItemCompleteNative(); + } + [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool NotifyWorkItemComplete(); + private static extern bool NotifyWorkItemCompleteNative(); + + internal static void ReportThreadStatus(bool isWorking) + { + if (UsePortableThreadPool) + { + // TODO: PortableThreadPool - Implement worker tracking + return; + } + + ReportThreadStatusNative(isWorking); + } [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void ReportThreadStatus(bool isWorking); + private static extern void ReportThreadStatusNative(bool isWorking); internal static void NotifyWorkItemProgress() { + if (UsePortableThreadPool) + { + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(); + return; + } + NotifyWorkItemProgressNative(); } [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void NotifyWorkItemProgressNative(); + private static extern void NotifyWorkItemProgressNative(); [MethodImpl(MethodImplOptions.InternalCall)] private static extern bool GetEnableWorkerTracking(); diff --git a/src/coreclr/src/inc/clrconfigvalues.h b/src/coreclr/src/inc/clrconfigvalues.h index 3186372d8d43d6..e60fb8ccef8527 100644 --- a/src/coreclr/src/inc/clrconfigvalues.h +++ b/src/coreclr/src/inc/clrconfigvalues.h @@ -569,6 +569,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_Thread_AssignCpuGroups, W("Thread_AssignCpuGro /// /// Threadpool /// +RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_UsePortableThreadPool, W("ThreadPool_UsePortableThreadPool"), 0, "Uses the managed portable thread pool implementation instead of the unmanaged one.") RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_ForceMinWorkerThreads, W("ThreadPool_ForceMinWorkerThreads"), 0, "Overrides the MinThreads setting for the ThreadPool worker pool") RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_ForceMaxWorkerThreads, W("ThreadPool_ForceMaxWorkerThreads"), 0, "Overrides the MaxThreads setting for the ThreadPool worker pool") RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_DisableStarvationDetection, W("ThreadPool_DisableStarvationDetection"), 0, "Disables the ThreadPool feature that forces new threads to be added when workitems run for too long") @@ -581,8 +582,6 @@ RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit, W("Thread RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit, W("ThreadPool_UnfairSemaphoreSpinLimit"), 0x46, "Maximum number of spins a thread pool worker thread performs before waiting for work") #endif // TARGET_ARM64 -CONFIG_DWORD_INFO(INTERNAL_ThreadpoolTickCountAdjustment, W("ThreadpoolTickCountAdjustment"), 0, "") - RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_Disable, W("HillClimbing_Disable"), 0, "Disables hill climbing for thread adjustments in the thread pool"); RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_WavePeriod, W("HillClimbing_WavePeriod"), 4, ""); RETAIL_CONFIG_DWORD_INFO(INTERNAL_HillClimbing_TargetSignalToNoiseRatio, W("HillClimbing_TargetSignalToNoiseRatio"), 300, ""); diff --git a/src/coreclr/src/vm/ceemain.cpp b/src/coreclr/src/vm/ceemain.cpp index c729cd66d0d3a4..d731c6d674ef1e 100644 --- a/src/coreclr/src/vm/ceemain.cpp +++ b/src/coreclr/src/vm/ceemain.cpp @@ -176,6 +176,10 @@ #include "stacksampler.h" #endif +#ifndef CROSSGEN_COMPILE +#include "win32threadpool.h" +#endif + #include #include "bbsweep.h" @@ -674,6 +678,8 @@ void EEStartupHelper() // This needs to be done before the EE has started InitializeStartupFlags(); + ThreadpoolMgr::StaticInitialize(); + MethodDescBackpatchInfoTracker::StaticInitialize(); CodeVersionManager::StaticInitialize(); TieredCompilationManager::StaticInitialize(); diff --git a/src/coreclr/src/vm/comthreadpool.cpp b/src/coreclr/src/vm/comthreadpool.cpp index edb8e33a2c6285..ab6611e14b49ad 100644 --- a/src/coreclr/src/vm/comthreadpool.cpp +++ b/src/coreclr/src/vm/comthreadpool.cpp @@ -122,6 +122,45 @@ DelegateInfo *DelegateInfo::MakeDelegateInfo(OBJECTREF *state, return delegateInfo; } +/*****************************************************************************************************/ +FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, + INT32 configVariableIndex, + UINT32 *configValueRef, + BOOL *isBooleanRef, + LPCWSTR *appContextConfigNameRef) +{ + FCALL_CONTRACT; + _ASSERTE(configVariableIndex >= 0); + _ASSERTE(configValueRef != NULL); + _ASSERTE(isBooleanRef != NULL); + _ASSERTE(appContextConfigNameRef != NULL); + + if (!ThreadpoolMgr::UsePortableThreadPool()) + { + *configValueRef = 0; + *isBooleanRef = false; + *appContextConfigNameRef = NULL; + return -1; + } + + switch (configVariableIndex) + { + case 0: + // Special case for UsePortableThreadPool, which doesn't go into the AppContext + *configValueRef = 1; + *isBooleanRef = true; + *appContextConfigNameRef = NULL; + return 1; + + default: + *configValueRef = 0; + *isBooleanRef = false; + *appContextConfigNameRef = NULL; + return -1; + } +} +FCIMPLEND + /*****************************************************************************************************/ FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorSetMaxThreads,DWORD workerThreads, DWORD completionPortThreads) { @@ -207,6 +246,8 @@ INT64 QCALLTYPE ThreadPoolNative::GetCompletedWorkItemCount() FCIMPL0(INT64, ThreadPoolNative::GetPendingUnmanagedWorkItemCount) { FCALL_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + return PerAppDomainTPCountList::GetUnmanagedTPCount()->GetNumRequests(); } FCIMPLEND @@ -216,6 +257,7 @@ FCIMPLEND FCIMPL0(VOID, ThreadPoolNative::NotifyRequestProgress) { FCALL_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); _ASSERTE(ThreadpoolMgr::IsInitialized()); // can't be here without requesting a thread first ThreadpoolMgr::NotifyWorkItemCompleted(); @@ -247,6 +289,7 @@ FCIMPLEND FCIMPL0(FC_BOOL_RET, ThreadPoolNative::NotifyRequestComplete) { FCALL_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); _ASSERTE(ThreadpoolMgr::IsInitialized()); // can't be here without requesting a thread first ThreadpoolMgr::NotifyWorkItemCompleted(); @@ -396,6 +439,7 @@ FCIMPL5(LPVOID, ThreadPoolNative::CorRegisterWaitForSingleObject, Object* registeredWaitObjectUNSAFE) { FCALL_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); HANDLE handle = 0; struct _gc @@ -473,6 +517,8 @@ BOOL QCALLTYPE ThreadPoolNative::RequestWorkerThread() BEGIN_QCALL; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + ThreadpoolMgr::EnsureInitialized(); ThreadpoolMgr::SetAppDomainRequestsActive(); @@ -498,6 +544,7 @@ BOOL QCALLTYPE ThreadPoolNative::RequestWorkerThread() FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorUnregisterWait, LPVOID WaitHandle, Object* objectToNotify) { FCALL_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); BOOL retVal = false; SAFEHANDLEREF refSH = (SAFEHANDLEREF) ObjectToOBJECTREF(objectToNotify); @@ -553,6 +600,7 @@ FCIMPLEND FCIMPL1(void, ThreadPoolNative::CorWaitHandleCleanupNative, LPVOID WaitHandle) { FCALL_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); HELPER_METHOD_FRAME_BEGIN_0(); diff --git a/src/coreclr/src/vm/comthreadpool.h b/src/coreclr/src/vm/comthreadpool.h index 9807482b53943f..1d100faf26bb73 100644 --- a/src/coreclr/src/vm/comthreadpool.h +++ b/src/coreclr/src/vm/comthreadpool.h @@ -22,6 +22,11 @@ class ThreadPoolNative { public: + static FCDECL4(INT32, GetNextConfigUInt32Value, + INT32 configVariableIndex, + UINT32 *configValueRef, + BOOL *isBooleanRef, + LPCWSTR *appContextConfigNameRef); static FCDECL2(FC_BOOL_RET, CorSetMaxThreads, DWORD workerThreads, DWORD completionPortThreads); static FCDECL2(VOID, CorGetMaxThreads, DWORD* workerThreads, DWORD* completionPortThreads); static FCDECL2(FC_BOOL_RET, CorSetMinThreads, DWORD workerThreads, DWORD completionPortThreads); diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h index da9ea6d59d0259..906e9d2e04c898 100644 --- a/src/coreclr/src/vm/ecalllist.h +++ b/src/coreclr/src/vm/ecalllist.h @@ -631,22 +631,23 @@ FCFuncStart(gThreadFuncs) FCFuncEnd() FCFuncStart(gThreadPoolFuncs) + FCFuncElement("GetNextConfigUInt32Value", ThreadPoolNative::GetNextConfigUInt32Value) FCFuncElement("PostQueuedCompletionStatus", ThreadPoolNative::CorPostQueuedCompletionStatus) FCFuncElement("GetAvailableThreadsNative", ThreadPoolNative::CorGetAvailableThreads) FCFuncElement("SetMinThreadsNative", ThreadPoolNative::CorSetMinThreads) FCFuncElement("GetMinThreadsNative", ThreadPoolNative::CorGetMinThreads) - FCFuncElement("get_ThreadCount", ThreadPoolNative::GetThreadCount) + FCFuncElement("GetThreadCount", ThreadPoolNative::GetThreadCount) QCFuncElement("GetCompletedWorkItemCount", ThreadPoolNative::GetCompletedWorkItemCount) - FCFuncElement("get_PendingUnmanagedWorkItemCount", ThreadPoolNative::GetPendingUnmanagedWorkItemCount) + FCFuncElement("GetPendingUnmanagedWorkItemCount", ThreadPoolNative::GetPendingUnmanagedWorkItemCount) FCFuncElement("RegisterWaitForSingleObjectNative", ThreadPoolNative::CorRegisterWaitForSingleObject) FCFuncElement("BindIOCompletionCallbackNative", ThreadPoolNative::CorBindIoCompletionCallback) FCFuncElement("SetMaxThreadsNative", ThreadPoolNative::CorSetMaxThreads) FCFuncElement("GetMaxThreadsNative", ThreadPoolNative::CorGetMaxThreads) - FCFuncElement("NotifyWorkItemComplete", ThreadPoolNative::NotifyRequestComplete) + FCFuncElement("NotifyWorkItemCompleteNative", ThreadPoolNative::NotifyRequestComplete) FCFuncElement("NotifyWorkItemProgressNative", ThreadPoolNative::NotifyRequestProgress) FCFuncElement("GetEnableWorkerTracking", ThreadPoolNative::GetEnableWorkerTracking) - FCFuncElement("ReportThreadStatus", ThreadPoolNative::ReportThreadStatus) - QCFuncElement("RequestWorkerThread", ThreadPoolNative::RequestWorkerThread) + FCFuncElement("ReportThreadStatusNative", ThreadPoolNative::ReportThreadStatus) + QCFuncElement("RequestWorkerThreadNative", ThreadPoolNative::RequestWorkerThread) FCFuncEnd() FCFuncStart(gTimerFuncs) diff --git a/src/coreclr/src/vm/hillclimbing.cpp b/src/coreclr/src/vm/hillclimbing.cpp index 0c6be1ec72133b..d8b1da18659357 100644 --- a/src/coreclr/src/vm/hillclimbing.cpp +++ b/src/coreclr/src/vm/hillclimbing.cpp @@ -43,6 +43,8 @@ void HillClimbing::Initialize() } CONTRACTL_END; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + m_wavePeriod = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_WavePeriod); m_maxThreadWaveMagnitude = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_MaxWaveMagnitude); m_threadMagnitudeMultiplier = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_WaveMagnitudeMultiplier) / 100.0; @@ -78,6 +80,7 @@ void HillClimbing::Initialize() int HillClimbing::Update(int currentThreadCount, double sampleDuration, int numCompletions, int* pNewSampleInterval) { LIMITED_METHOD_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); #ifdef DACCESS_COMPILE return 1; @@ -347,6 +350,7 @@ int HillClimbing::Update(int currentThreadCount, double sampleDuration, int numC void HillClimbing::ForceChange(int newThreadCount, HillClimbingStateTransition transition) { LIMITED_METHOD_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); if (newThreadCount != m_lastThreadCount) { @@ -410,6 +414,7 @@ void HillClimbing::LogTransition(int threadCount, double throughput, HillClimbin Complex HillClimbing::GetWaveComponent(double* samples, int sampleCount, double period) { LIMITED_METHOD_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); _ASSERTE(sampleCount >= period); //can't measure a wave that doesn't fit _ASSERTE(period >= 2); //can't measure above the Nyquist frequency diff --git a/src/coreclr/src/vm/threadpoolrequest.cpp b/src/coreclr/src/vm/threadpoolrequest.cpp index 470f8b4058bdb8..c155f6166b1e9c 100644 --- a/src/coreclr/src/vm/threadpoolrequest.cpp +++ b/src/coreclr/src/vm/threadpoolrequest.cpp @@ -38,7 +38,11 @@ ArrayListStatic PerAppDomainTPCountList::s_appDomainIndexList; void PerAppDomainTPCountList::InitAppDomainIndexList() { LIMITED_METHOD_CONTRACT; - s_appDomainIndexList.Init(); + + if (!ThreadpoolMgr::UsePortableThreadPool()) + { + s_appDomainIndexList.Init(); + } } @@ -56,6 +60,11 @@ TPIndex PerAppDomainTPCountList::AddNewTPIndex() { STANDARD_VM_CONTRACT; + if (ThreadpoolMgr::UsePortableThreadPool()) + { + return TPIndex(); + } + DWORD count = s_appDomainIndexList.GetCount(); DWORD i = FindFirstFreeTpEntry(); @@ -88,7 +97,7 @@ TPIndex PerAppDomainTPCountList::AddNewTPIndex() DWORD PerAppDomainTPCountList::FindFirstFreeTpEntry() { - CONTRACTL + CONTRACTL { NOTHROW; MODE_ANY; @@ -96,6 +105,8 @@ DWORD PerAppDomainTPCountList::FindFirstFreeTpEntry() } CONTRACTL_END; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + DWORD DwnumADs = s_appDomainIndexList.GetCount(); DWORD Dwi; IPerAppDomainTPCount * pAdCount; @@ -142,6 +153,12 @@ void PerAppDomainTPCountList::ResetAppDomainIndex(TPIndex index) } CONTRACTL_END; + if (ThreadpoolMgr::UsePortableThreadPool()) + { + _ASSERTE(index.m_dwIndex == TPIndex().m_dwIndex); + return; + } + IPerAppDomainTPCount * pAdCount = dac_cast(s_appDomainIndexList.Get(index.m_dwIndex-1)); _ASSERTE(pAdCount); @@ -168,6 +185,8 @@ bool PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains() } CONTRACTL_END; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + DWORD DwnumADs = s_appDomainIndexList.GetCount(); DWORD Dwi; IPerAppDomainTPCount * pAdCount; @@ -217,6 +236,8 @@ LONG PerAppDomainTPCountList::GetAppDomainIndexForThreadpoolDispatch() } CONTRACTL_END; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + LONG hint = s_ADHint; DWORD count = s_appDomainIndexList.GetCount(); IPerAppDomainTPCount * pAdCount; @@ -298,6 +319,8 @@ LONG PerAppDomainTPCountList::GetAppDomainIndexForThreadpoolDispatch() void UnManagedPerAppDomainTPCount::SetAppDomainRequestsActive() { WRAPPER_NO_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + #ifndef DACCESS_COMPILE LONG count = VolatileLoad(&m_outstandingThreadRequestCount); while (count < (LONG)ThreadpoolMgr::NumberOfProcessors) @@ -317,6 +340,8 @@ void UnManagedPerAppDomainTPCount::SetAppDomainRequestsActive() bool FORCEINLINE UnManagedPerAppDomainTPCount::TakeActiveRequest() { LIMITED_METHOD_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + LONG count = VolatileLoad(&m_outstandingThreadRequestCount); while (count > 0) @@ -344,6 +369,8 @@ void UnManagedPerAppDomainTPCount::QueueUnmanagedWorkRequest(LPTHREAD_START_ROUT } CONTRACTL_END;; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + #ifndef DACCESS_COMPILE WorkRequestHolder pWorkRequest; @@ -384,7 +411,9 @@ PVOID UnManagedPerAppDomainTPCount::DeQueueUnManagedWorkRequest(bool* lastOne) GC_TRIGGERS; MODE_ANY; } - CONTRACTL_END;; + CONTRACTL_END; + + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); *lastOne = true; @@ -408,6 +437,8 @@ PVOID UnManagedPerAppDomainTPCount::DeQueueUnManagedWorkRequest(bool* lastOne) // void UnManagedPerAppDomainTPCount::DispatchWorkItem(bool* foundWork, bool* wasNotRecalled) { + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + #ifndef DACCESS_COMPILE *foundWork = false; *wasNotRecalled = true; @@ -533,6 +564,7 @@ void ManagedPerAppDomainTPCount::SetAppDomainRequestsActive() // one. // + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); _ASSERTE(m_index.m_dwIndex != UNUSED_THREADPOOL_INDEX); #ifndef DACCESS_COMPILE @@ -554,6 +586,8 @@ void ManagedPerAppDomainTPCount::SetAppDomainRequestsActive() void ManagedPerAppDomainTPCount::ClearAppDomainRequestsActive() { LIMITED_METHOD_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + //This function should either be called by managed code or during AD unload, but before //the TpIndex is set to unused. @@ -572,6 +606,8 @@ void ManagedPerAppDomainTPCount::ClearAppDomainRequestsActive() bool ManagedPerAppDomainTPCount::TakeActiveRequest() { LIMITED_METHOD_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + LONG count = VolatileLoad(&m_numRequestsPending); while (count > 0) { @@ -592,6 +628,8 @@ bool ManagedPerAppDomainTPCount::TakeActiveRequest() // void ManagedPerAppDomainTPCount::DispatchWorkItem(bool* foundWork, bool* wasNotRecalled) { + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + *foundWork = false; *wasNotRecalled = true; diff --git a/src/coreclr/src/vm/threadpoolrequest.h b/src/coreclr/src/vm/threadpoolrequest.h index b7419241bb621e..1f7b335b5fb797 100644 --- a/src/coreclr/src/vm/threadpoolrequest.h +++ b/src/coreclr/src/vm/threadpoolrequest.h @@ -156,23 +156,6 @@ class UnManagedPerAppDomainTPCount : public IPerAppDomainTPCount { ResetState(); } - inline void InitResources() - { - CONTRACTL - { - THROWS; - MODE_ANY; - GC_NOTRIGGER; - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - } - - inline void CleanupResources() - { - } - inline void ResetState() { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/src/vm/threads.cpp b/src/coreclr/src/vm/threads.cpp index 1c1cba7d102aa9..87b61227926eed 100644 --- a/src/coreclr/src/vm/threads.cpp +++ b/src/coreclr/src/vm/threads.cpp @@ -7901,15 +7901,24 @@ UINT64 Thread::GetTotalThreadPoolCompletionCount() } CONTRACTL_END; + bool usePortableThreadPool = ThreadpoolMgr::UsePortableThreadPool(); + // enumerate all threads, summing their local counts. ThreadStoreLockHolder tsl; - UINT64 total = GetWorkerThreadPoolCompletionCountOverflow() + GetIOThreadPoolCompletionCountOverflow(); + UINT64 total = GetIOThreadPoolCompletionCountOverflow(); + if (!usePortableThreadPool) + { + total += GetWorkerThreadPoolCompletionCountOverflow(); + } Thread *pThread = NULL; while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL) { - total += pThread->m_workerThreadPoolCompletionCount; + if (!usePortableThreadPool) + { + total += pThread->m_workerThreadPoolCompletionCount; + } total += pThread->m_ioThreadPoolCompletionCount; } diff --git a/src/coreclr/src/vm/win32threadpool.cpp b/src/coreclr/src/vm/win32threadpool.cpp index 20d65a86cb0a54..7bb39d93b0aa9e 100644 --- a/src/coreclr/src/vm/win32threadpool.cpp +++ b/src/coreclr/src/vm/win32threadpool.cpp @@ -112,10 +112,11 @@ int ThreadpoolMgr::ThreadAdjustmentInterval; #define GATE_THREAD_DELAY 500 /*milliseconds*/ #define GATE_THREAD_DELAY_TOLERANCE 50 /*milliseconds*/ #define DELAY_BETWEEN_SUSPENDS (5000 + GATE_THREAD_DELAY) // time to delay between suspensions -#define SUSPEND_TIME (GATE_THREAD_DELAY + 100) // milliseconds to suspend during SuspendProcessing LONG ThreadpoolMgr::Initialization=0; // indicator of whether the threadpool is initialized. +bool ThreadpoolMgr::s_usePortableThreadPool = false; + // Cacheline aligned, hot variable DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) unsigned int ThreadpoolMgr::LastDequeueTime; // used to determine if work items are getting thread starved @@ -144,10 +145,6 @@ Thread *ThreadpoolMgr::pTimerThread=NULL; // Cacheline aligned, hot variable DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) DWORD ThreadpoolMgr::LastTickCount; -#ifdef _DEBUG -DWORD ThreadpoolMgr::TickCountAdjustment=0; -#endif - // Cacheline aligned, hot variable DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) LONG ThreadpoolMgr::GateThreadStatus=GATE_THREAD_STATUS_NOT_RUNNING; @@ -290,6 +287,8 @@ DWORD GetDefaultMaxLimitWorkerThreads(DWORD minLimit) } CONTRACTL_END; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + // // We determine the max limit for worker threads as follows: // @@ -328,12 +327,16 @@ DWORD GetDefaultMaxLimitWorkerThreads(DWORD minLimit) DWORD GetForceMinWorkerThreadsValue() { WRAPPER_NO_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + return Configuration::GetKnobDWORDValue(W("System.Threading.ThreadPool.MinThreads"), CLRConfig::INTERNAL_ThreadPool_ForceMinWorkerThreads); } DWORD GetForceMaxWorkerThreadsValue() { WRAPPER_NO_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + return Configuration::GetKnobDWORDValue(W("System.Threading.ThreadPool.MaxThreads"), CLRConfig::INTERNAL_ThreadPool_ForceMaxWorkerThreads); } @@ -351,9 +354,6 @@ BOOL ThreadpoolMgr::Initialize() BOOL bRet = FALSE; BOOL bExceptionCaught = FALSE; - UnManagedPerAppDomainTPCount* pADTPCount; - pADTPCount = PerAppDomainTPCountList::GetUnmanagedTPCount(); - #ifndef TARGET_UNIX //ThreadPool_CPUGroup CPUGroupInfo::EnsureInitialized(); @@ -368,17 +368,22 @@ BOOL ThreadpoolMgr::Initialize() EX_TRY { - WorkerThreadSpinLimit = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit); - IsHillClimbingDisabled = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_Disable) != 0; - ThreadAdjustmentInterval = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow); + if (!UsePortableThreadPool()) + { + WorkerThreadSpinLimit = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit); + IsHillClimbingDisabled = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_Disable) != 0; + ThreadAdjustmentInterval = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow); - pADTPCount->InitResources(); + WaitThreadsCriticalSection.Init(CrstThreadpoolWaitThreads); + } WorkerCriticalSection.Init(CrstThreadpoolWorker); - WaitThreadsCriticalSection.Init(CrstThreadpoolWaitThreads); TimerQueueCriticalSection.Init(CrstThreadpoolTimerQueue); - // initialize WaitThreadsHead - InitializeListHead(&WaitThreadsHead); + if (!UsePortableThreadPool()) + { + // initialize WaitThreadsHead + InitializeListHead(&WaitThreadsHead); + } // initialize TimerQueue InitializeListHead(&TimerQueue); @@ -387,11 +392,14 @@ BOOL ThreadpoolMgr::Initialize() RetiredCPWakeupEvent->CreateAutoEvent(FALSE); _ASSERTE(RetiredCPWakeupEvent->IsValid()); - WorkerSemaphore = new CLRLifoSemaphore(); - WorkerSemaphore->Create(0, ThreadCounter::MaxPossibleCount); + if (!UsePortableThreadPool()) + { + WorkerSemaphore = new CLRLifoSemaphore(); + WorkerSemaphore->Create(0, ThreadCounter::MaxPossibleCount); - RetiredWorkerSemaphore = new CLRLifoSemaphore(); - RetiredWorkerSemaphore->Create(0, ThreadCounter::MaxPossibleCount); + RetiredWorkerSemaphore = new CLRLifoSemaphore(); + RetiredWorkerSemaphore->Create(0, ThreadCounter::MaxPossibleCount); + } #ifndef TARGET_UNIX //ThreadPool_CPUGroup @@ -405,8 +413,6 @@ BOOL ThreadpoolMgr::Initialize() } EX_CATCH { - pADTPCount->CleanupResources(); - if (RetiredCPWakeupEvent) { delete RetiredCPWakeupEvent; @@ -414,8 +420,11 @@ BOOL ThreadpoolMgr::Initialize() } // Note: It is fine to call Destroy on uninitialized critical sections + if (!UsePortableThreadPool()) + { + WaitThreadsCriticalSection.Destroy(); + } WorkerCriticalSection.Destroy(); - WaitThreadsCriticalSection.Destroy(); TimerQueueCriticalSection.Destroy(); bExceptionCaught = TRUE; @@ -427,25 +436,24 @@ BOOL ThreadpoolMgr::Initialize() goto end; } - // initialize Worker and CP thread settings - DWORD forceMin; - forceMin = GetForceMinWorkerThreadsValue(); - MinLimitTotalWorkerThreads = forceMin > 0 ? (LONG)forceMin : (LONG)NumberOfProcessors; + if (!UsePortableThreadPool()) + { + // initialize Worker thread settings + DWORD forceMin; + forceMin = GetForceMinWorkerThreadsValue(); + MinLimitTotalWorkerThreads = forceMin > 0 ? (LONG)forceMin : (LONG)NumberOfProcessors; - DWORD forceMax; - forceMax = GetForceMaxWorkerThreadsValue(); - MaxLimitTotalWorkerThreads = forceMax > 0 ? (LONG)forceMax : (LONG)GetDefaultMaxLimitWorkerThreads(MinLimitTotalWorkerThreads); + DWORD forceMax; + forceMax = GetForceMaxWorkerThreadsValue(); + MaxLimitTotalWorkerThreads = forceMax > 0 ? (LONG)forceMax : (LONG)GetDefaultMaxLimitWorkerThreads(MinLimitTotalWorkerThreads); - ThreadCounter::Counts counts; - counts.NumActive = 0; - counts.NumWorking = 0; - counts.NumRetired = 0; - counts.MaxWorking = MinLimitTotalWorkerThreads; - WorkerCounter.counts.AsLongLong = counts.AsLongLong; - -#ifdef _DEBUG - TickCountAdjustment = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadpoolTickCountAdjustment); -#endif + ThreadCounter::Counts counts; + counts.NumActive = 0; + counts.NumWorking = 0; + counts.NumRetired = 0; + counts.MaxWorking = MinLimitTotalWorkerThreads; + WorkerCounter.counts.AsLongLong = counts.AsLongLong; + } // initialize CP thread settings MinLimitTotalCPThreads = NumberOfProcessors; @@ -453,6 +461,7 @@ BOOL ThreadpoolMgr::Initialize() // Use volatile store to guarantee make the value visible to the DAC (the store can be optimized out otherwise) VolatileStoreWithoutBarrier(&MaxFreeCPThreads, NumberOfProcessors*MaxFreeCPThreadsPerCPU); + ThreadCounter::Counts counts; counts.NumActive = 0; counts.NumWorking = 0; counts.NumRetired = 0; @@ -468,7 +477,10 @@ BOOL ThreadpoolMgr::Initialize() } #endif // !TARGET_UNIX - HillClimbingInstance.Initialize(); + if (!UsePortableThreadPool()) + { + HillClimbingInstance.Initialize(); + } bRet = TRUE; end: @@ -487,17 +499,20 @@ void ThreadpoolMgr::InitPlatformVariables() #ifndef TARGET_UNIX HINSTANCE hNtDll; - HINSTANCE hCoreSynch; + HINSTANCE hCoreSynch = nullptr; { CONTRACT_VIOLATION(GCViolation|FaultViolation); hNtDll = CLRLoadLibrary(W("ntdll.dll")); _ASSERTE(hNtDll); + if (!UsePortableThreadPool()) + { #ifdef FEATURE_CORESYSTEM - hCoreSynch = CLRLoadLibrary(W("api-ms-win-core-synch-l1-1-0.dll")); + hCoreSynch = CLRLoadLibrary(W("api-ms-win-core-synch-l1-1-0.dll")); #else - hCoreSynch = CLRLoadLibrary(W("kernel32.dll")); + hCoreSynch = CLRLoadLibrary(W("kernel32.dll")); #endif - _ASSERTE(hCoreSynch); + _ASSERTE(hCoreSynch); + } } // These APIs must be accessed via dynamic binding since they may be removed in future @@ -505,10 +520,12 @@ void ThreadpoolMgr::InitPlatformVariables() g_pufnNtQueryInformationThread = (NtQueryInformationThreadProc)GetProcAddress(hNtDll,"NtQueryInformationThread"); g_pufnNtQuerySystemInformation = (NtQuerySystemInformationProc)GetProcAddress(hNtDll,"NtQuerySystemInformation"); - - // These APIs are only supported on newer Windows versions - g_pufnCreateWaitableTimerEx = (CreateWaitableTimerExProc)GetProcAddress(hCoreSynch, "CreateWaitableTimerExW"); - g_pufnSetWaitableTimerEx = (SetWaitableTimerExProc)GetProcAddress(hCoreSynch, "SetWaitableTimerEx"); + if (!UsePortableThreadPool()) + { + // These APIs are only supported on newer Windows versions + g_pufnCreateWaitableTimerEx = (CreateWaitableTimerExProc)GetProcAddress(hCoreSynch, "CreateWaitableTimerExW"); + g_pufnSetWaitableTimerEx = (SetWaitableTimerExProc)GetProcAddress(hCoreSynch, "SetWaitableTimerEx"); + } #endif } @@ -528,12 +545,18 @@ BOOL ThreadpoolMgr::SetMaxThreadsHelper(DWORD MaxWorkerThreads, // doesn't need to be WorkerCS, but using it to avoid race condition between setting min and max, and didn't want to create a new CS. CrstHolder csh(&WorkerCriticalSection); - if (MaxWorkerThreads >= (DWORD)MinLimitTotalWorkerThreads && + bool usePortableThreadPool = UsePortableThreadPool(); + if (( + usePortableThreadPool || + ( + MaxWorkerThreads >= (DWORD)MinLimitTotalWorkerThreads && + MaxWorkerThreads != 0 + ) + ) && MaxIOCompletionThreads >= (DWORD)MinLimitTotalCPThreads && - MaxWorkerThreads != 0 && MaxIOCompletionThreads != 0) { - if (GetForceMaxWorkerThreadsValue() == 0) + if (!usePortableThreadPool && GetForceMaxWorkerThreadsValue() == 0) { MaxLimitTotalWorkerThreads = min(MaxWorkerThreads, (DWORD)ThreadCounter::MaxPossibleCount); @@ -581,7 +604,6 @@ BOOL ThreadpoolMgr::GetMaxThreads(DWORD* MaxWorkerThreads, { LIMITED_METHOD_CONTRACT; - if (!MaxWorkerThreads || !MaxIOCompletionThreads) { SetLastHRError(ERROR_INVALID_DATA); @@ -590,7 +612,7 @@ BOOL ThreadpoolMgr::GetMaxThreads(DWORD* MaxWorkerThreads, EnsureInitialized(); - *MaxWorkerThreads = (DWORD)MaxLimitTotalWorkerThreads; + *MaxWorkerThreads = UsePortableThreadPool() ? 1 : (DWORD)MaxLimitTotalWorkerThreads; *MaxIOCompletionThreads = MaxLimitTotalCPThreads; return TRUE; } @@ -613,11 +635,18 @@ BOOL ThreadpoolMgr::SetMinThreads(DWORD MinWorkerThreads, BOOL init_result = FALSE; - if (MinWorkerThreads >= 0 && MinIOCompletionThreads >= 0 && - MinWorkerThreads <= (DWORD) MaxLimitTotalWorkerThreads && + bool usePortableThreadPool = UsePortableThreadPool(); + if (( + usePortableThreadPool || + ( + MinWorkerThreads >= 0 && + MinWorkerThreads <= (DWORD) MaxLimitTotalWorkerThreads + ) + ) && + MinIOCompletionThreads >= 0 && MinIOCompletionThreads <= (DWORD) MaxLimitTotalCPThreads) { - if (GetForceMinWorkerThreadsValue() == 0) + if (!usePortableThreadPool && GetForceMinWorkerThreadsValue() == 0) { MinLimitTotalWorkerThreads = max(1, min(MinWorkerThreads, (DWORD)ThreadCounter::MaxPossibleCount)); @@ -660,7 +689,6 @@ BOOL ThreadpoolMgr::GetMinThreads(DWORD* MinWorkerThreads, { LIMITED_METHOD_CONTRACT; - if (!MinWorkerThreads || !MinIOCompletionThreads) { SetLastHRError(ERROR_INVALID_DATA); @@ -669,7 +697,7 @@ BOOL ThreadpoolMgr::GetMinThreads(DWORD* MinWorkerThreads, EnsureInitialized(); - *MinWorkerThreads = (DWORD)MinLimitTotalWorkerThreads; + *MinWorkerThreads = UsePortableThreadPool() ? 1 : (DWORD)MinLimitTotalWorkerThreads; *MinIOCompletionThreads = MinLimitTotalCPThreads; return TRUE; } @@ -689,7 +717,7 @@ BOOL ThreadpoolMgr::GetAvailableThreads(DWORD* AvailableWorkerThreads, ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - if (MaxLimitTotalWorkerThreads < counts.NumActive) + if (UsePortableThreadPool() || MaxLimitTotalWorkerThreads < counts.NumActive) *AvailableWorkerThreads = 0; else *AvailableWorkerThreads = MaxLimitTotalWorkerThreads - counts.NumWorking; @@ -711,7 +739,8 @@ INT32 ThreadpoolMgr::GetThreadCount() return 0; } - return WorkerCounter.DangerousGetDirtyCounts().NumActive + CPThreadCounter.DangerousGetDirtyCounts().NumActive; + INT32 workerThreadCount = UsePortableThreadPool() ? 0 : WorkerCounter.DangerousGetDirtyCounts().NumActive; + return workerThreadCount + CPThreadCounter.DangerousGetDirtyCounts().NumActive; } void QueueUserWorkItemHelp(LPTHREAD_START_ROUTINE Function, PVOID Context) @@ -728,6 +757,8 @@ void QueueUserWorkItemHelp(LPTHREAD_START_ROUTINE Function, PVOID Context) } CONTRACTL_END;*/ + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + Function(Context); Thread *pThread = GetThread(); @@ -842,6 +873,8 @@ BOOL ThreadpoolMgr::QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); + EnsureInitialized(); @@ -878,6 +911,7 @@ BOOL ThreadpoolMgr::QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, bool ThreadpoolMgr::ShouldWorkerKeepRunning() { WRAPPER_NO_CONTRACT; + _ASSERTE(!UsePortableThreadPool()); // // Maybe this thread should retire now. Let's see. @@ -932,6 +966,7 @@ void ThreadpoolMgr::AdjustMaxWorkersActive() } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); _ASSERTE(ThreadAdjustmentLock.IsHeld()); LARGE_INTEGER startTime = CurrentSampleStartTime; @@ -1016,6 +1051,8 @@ void ThreadpoolMgr::MaybeAddWorkingWorker() } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); + // counts volatile read paired with CompareExchangeCounts loop set ThreadCounter::Counts counts = WorkerCounter.DangerousGetDirtyCounts(); ThreadCounter::Counts newCounts; @@ -1376,6 +1413,12 @@ void ThreadpoolMgr::EnsureGateThreadRunning() { LIMITED_METHOD_CONTRACT; + // TODO: PortableThreadPool - Uncomment after gate thread is rerouted + //if (!UsePortableThreadPool()) + //{ + // return; + //} + while (true) { switch (GateThreadStatus) @@ -1453,7 +1496,7 @@ bool ThreadpoolMgr::ShouldGateThreadKeepRunning() // This imples that whenever a work queue goes from empty to non-empty, we need to call EnsureGateThreadRunning(). // bool needGateThreadForWorkerThreads = - PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains(); + !UsePortableThreadPool() && PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains(); // // If worker tracking is enabled, we need to fire periodic ETW events with active worker counts. This is @@ -1497,6 +1540,8 @@ void ThreadpoolMgr::EnqueueWorkRequest(WorkRequest* workRequest) } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); + AppendWorkRequest(workRequest); } @@ -1512,6 +1557,8 @@ WorkRequest* ThreadpoolMgr::DequeueWorkRequest() POSTCONDITION(CheckPointer(entry, NULL_OK)); } CONTRACT_END; + _ASSERTE(!UsePortableThreadPool()); + entry = RemoveWorkRequest(); RETURN entry; @@ -1527,6 +1574,8 @@ void ThreadpoolMgr::ExecuteWorkRequest(bool* foundWork, bool* wasNotRecalled) } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); + IPerAppDomainTPCount* pAdCount; LONG index = PerAppDomainTPCountList::GetAppDomainIndexForThreadpoolDispatch(); @@ -1572,6 +1621,8 @@ BOOL ThreadpoolMgr::SetAppDomainRequestsActive(BOOL UnmanagedTP) } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); + BOOL fShouldSignalEvent = FALSE; IPerAppDomainTPCount* pAdCount; @@ -1623,6 +1674,8 @@ void ThreadpoolMgr::ClearAppDomainRequestsActive(BOOL UnmanagedTP, LONG id) } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); + IPerAppDomainTPCount* pAdCount; if(UnmanagedTP) @@ -1823,6 +1876,8 @@ BOOL ThreadpoolMgr::CreateWorkerThread() } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); + Thread *pThread; BOOL fIsCLRThread; if ((pThread = CreateUnimpersonatedThread(WorkerThreadStart, NULL, &fIsCLRThread)) != NULL) @@ -1857,6 +1912,8 @@ DWORD WINAPI ThreadpoolMgr::WorkerThreadStart(LPVOID lpArgs) } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); + Thread *pThread = NULL; DWORD dwSwitchCount = 0; BOOL fThreadInit = FALSE; @@ -2167,34 +2224,6 @@ DWORD WINAPI ThreadpoolMgr::WorkerThreadStart(LPVOID lpArgs) return ERROR_SUCCESS; } - -BOOL ThreadpoolMgr::SuspendProcessing() -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - BOOL shouldRetire = TRUE; - DWORD sleepInterval = SUSPEND_TIME; - int oldCpuUtilization = cpuUtilization; - for (int i = 0; i < shouldRetire; i++) - { - __SwitchToThread(sleepInterval, CALLER_LIMITS_SPINNING); - if ((cpuUtilization <= (oldCpuUtilization - 4))) - { // if cpu util. dips by 4% or more, then put it back in circulation - shouldRetire = FALSE; - break; - } - } - - return shouldRetire; -} - - // this should only be called by unmanaged thread (i.e. there should be no mgd // caller on the stack) since we are swallowing terminal exceptions DWORD ThreadpoolMgr::SafeWait(CLREvent * ev, DWORD sleepTime, BOOL alertable) @@ -2239,6 +2268,9 @@ BOOL ThreadpoolMgr::RegisterWaitForSingleObject(PHANDLE phNewWaitObject, if (GetThread()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} } CONTRACTL_END; + + _ASSERTE(!UsePortableThreadPool()); + EnsureInitialized(); ThreadCB* threadCB; @@ -2305,6 +2337,9 @@ ThreadpoolMgr::ThreadCB* ThreadpoolMgr::FindWaitThread() GC_TRIGGERS; } CONTRACTL_END; + + _ASSERTE(!UsePortableThreadPool()); + do { for (LIST_ENTRY* Node = (LIST_ENTRY*) WaitThreadsHead.Flink ; @@ -2345,6 +2380,9 @@ BOOL ThreadpoolMgr::CreateWaitThread() INJECT_FAULT(COMPlusThrowOM()); } CONTRACTL_END; + + _ASSERTE(!UsePortableThreadPool()); + DWORD threadId; if (g_fEEShutDown & ShutDown_Finalize2){ @@ -2423,6 +2461,7 @@ BOOL ThreadpoolMgr::CreateWaitThread() void ThreadpoolMgr::InsertNewWaitForSelf(WaitInfo* pArgs) { WRAPPER_NO_CONTRACT; + _ASSERTE(!UsePortableThreadPool()); WaitInfo* waitInfo = pArgs; @@ -2468,6 +2507,7 @@ void ThreadpoolMgr::InsertNewWaitForSelf(WaitInfo* pArgs) int ThreadpoolMgr::FindWaitIndex(const ThreadCB* threadCB, const HANDLE waitHandle) { LIMITED_METHOD_CONTRACT; + _ASSERTE(!UsePortableThreadPool()); for (int i=0;iNumActiveWaits; i++) if (threadCB->waitHandle[i] == waitHandle) @@ -2490,6 +2530,7 @@ int ThreadpoolMgr::FindWaitIndex(const ThreadCB* threadCB, const HANDLE waitHand DWORD ThreadpoolMgr::MinimumRemainingWait(LIST_ENTRY* waitInfo, unsigned int numWaits) { LIMITED_METHOD_CONTRACT; + _ASSERTE(!UsePortableThreadPool()); unsigned int min = (unsigned int) -1; DWORD currentTime = GetTickCount(); @@ -2547,6 +2588,8 @@ DWORD WINAPI ThreadpoolMgr::WaitThreadStart(LPVOID lpArgs) ClrFlsSetThreadType (ThreadType_Wait); + _ASSERTE(!UsePortableThreadPool()); + ThreadCB* threadCB = (ThreadCB*) lpArgs; Thread* pThread = SetupThreadNoThrow(); @@ -2876,6 +2919,7 @@ void ThreadpoolMgr::DeactivateWait(WaitInfo* waitInfo) void ThreadpoolMgr::DeactivateNthWait(WaitInfo* waitInfo, DWORD index) { LIMITED_METHOD_CONTRACT; + _ASSERTE(!UsePortableThreadPool()); ThreadCB* threadCB = waitInfo->threadCB; @@ -2967,6 +3011,7 @@ BOOL ThreadpoolMgr::UnregisterWaitEx(HANDLE hWaitObject,HANDLE Event) } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); _ASSERTE(IsInitialized()); // cannot call unregister before first registering const BOOL Blocking = (Event == (HANDLE) -1); @@ -3084,6 +3129,7 @@ void ThreadpoolMgr::DeregisterWait(WaitInfo* pArgs) void ThreadpoolMgr::WaitHandleCleanup(HANDLE hWaitObject) { LIMITED_METHOD_CONTRACT; + _ASSERTE(!UsePortableThreadPool()); _ASSERTE(IsInitialized()); // cannot call cleanup before first registering WaitInfo* waitInfo = (WaitInfo*) hWaitObject; @@ -3099,6 +3145,8 @@ void ThreadpoolMgr::WaitHandleCleanup(HANDLE hWaitObject) BOOL ThreadpoolMgr::CreateGateThread() { LIMITED_METHOD_CONTRACT; + // TODO: PortableThreadPool - Uncomment after gate thread is rerouted + //_ASSERTE(!UsePortableThreadPool()); HANDLE threadHandle = Thread::CreateUtilityThread(Thread::StackSize_Small, GateThreadStart, NULL, W(".NET ThreadPool Gate")); @@ -4056,6 +4104,8 @@ DWORD WINAPI ThreadpoolMgr::GateThreadStart(LPVOID lpArgs) } CONTRACTL_END; + // TODO: PortableThreadPool - Uncomment after gate thread is rerouted + //_ASSERTE(!UsePortableThreadPool()); _ASSERTE(GateThreadStatus == GATE_THREAD_STATUS_REQUESTED); GateThreadTimer timer; @@ -4270,7 +4320,8 @@ DWORD WINAPI ThreadpoolMgr::GateThreadStart(LPVOID lpArgs) } #endif // !TARGET_UNIX - if (0 == CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection)) + if (!UsePortableThreadPool() && + 0 == CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection)) { if (PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains() && SufficientDelaySinceLastDequeue()) { @@ -4347,6 +4398,7 @@ BOOL ThreadpoolMgr::SufficientDelaySinceLastSample(unsigned int LastThreadCreati BOOL ThreadpoolMgr::SufficientDelaySinceLastDequeue() { LIMITED_METHOD_CONTRACT; + _ASSERTE(!UsePortableThreadPool()); #define DEQUEUE_DELAY_THRESHOLD (GATE_THREAD_DELAY * 2) diff --git a/src/coreclr/src/vm/win32threadpool.h b/src/coreclr/src/vm/win32threadpool.h index d25341ecb4ab0d..45a2e243da0452 100644 --- a/src/coreclr/src/vm/win32threadpool.h +++ b/src/coreclr/src/vm/win32threadpool.h @@ -223,6 +223,20 @@ class ThreadpoolMgr INT32 TimerId; } TimerInfoContext; +#ifndef DACCESS_COMPILE + static void StaticInitialize() + { + WRAPPER_NO_CONTRACT; + s_usePortableThreadPool = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UsePortableThreadPool) != 0; + } +#endif + + static bool UsePortableThreadPool() + { + LIMITED_METHOD_CONTRACT; + return s_usePortableThreadPool; + } + static BOOL Initialize(); static BOOL SetMaxThreadsHelper(DWORD MaxWorkerThreads, @@ -787,6 +801,8 @@ class ThreadpoolMgr } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); + if (WorkRequestTail) { _ASSERTE(WorkRequestHead != NULL); @@ -812,6 +828,8 @@ class ThreadpoolMgr } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); + WorkRequest* entry = NULL; if (WorkRequestHead) { @@ -842,6 +860,8 @@ class ThreadpoolMgr static void NotifyWorkItemCompleted() { WRAPPER_NO_CONTRACT; + _ASSERTE(!UsePortableThreadPool()); + Thread::IncrementWorkerThreadPoolCompletionCount(GetThread()); UpdateLastDequeueTime(); } @@ -849,6 +869,7 @@ class ThreadpoolMgr static bool ShouldAdjustMaxWorkersActive() { WRAPPER_NO_CONTRACT; + _ASSERTE(!UsePortableThreadPool()); DWORD priorTime = PriorCompletedWorkRequestsTime; MemoryBarrier(); // read fresh value for NextCompletedWorkRequestsTime below @@ -867,8 +888,6 @@ class ThreadpoolMgr static void AdjustMaxWorkersActive(); static bool ShouldWorkerKeepRunning(); - static BOOL SuspendProcessing(); - static DWORD SafeWait(CLREvent * ev, DWORD sleepTime, BOOL alertable); static DWORD WINAPI WorkerThreadStart(LPVOID lpArgs); @@ -985,6 +1004,8 @@ class ThreadpoolMgr } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPool()); + DWORD result = QueueUserAPC(reinterpret_cast(DeregisterWait), waitThread, reinterpret_cast(waitInfo)); SetWaitThreadAPCPending(); return result; @@ -995,19 +1016,13 @@ class ThreadpoolMgr inline static void ResetWaitThreadAPCPending() {IsApcPendingOnWaitThread = FALSE;} inline static BOOL IsWaitThreadAPCPending() {return IsApcPendingOnWaitThread;} -#ifdef _DEBUG - inline static DWORD GetTickCount() - { - LIMITED_METHOD_CONTRACT; - return ::GetTickCount() + TickCountAdjustment; - } -#endif - #endif // #ifndef DACCESS_COMPILE // Private variables static LONG Initialization; // indicator of whether the threadpool is initialized. + static bool s_usePortableThreadPool; + SVAL_DECL(LONG,MinLimitTotalWorkerThreads); // same as MinLimitTotalCPThreads SVAL_DECL(LONG,MaxLimitTotalWorkerThreads); // same as MaxLimitTotalCPThreads @@ -1092,10 +1107,6 @@ class ThreadpoolMgr static LONG cpuUtilizationAverage; DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static RecycledListsWrapper RecycledLists; - -#ifdef _DEBUG - static DWORD TickCountAdjustment; // add this value to value returned by GetTickCount -#endif }; diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 6520bde304db9e..6eb5040f2a3f27 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1935,8 +1935,8 @@ - - + + diff --git a/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs b/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs index 2144b989dfc2cf..334eaf8a6e4f23 100644 --- a/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs @@ -15,6 +15,9 @@ internal static int GetInt32Config(string configName, int defaultValue, bool all int result = defaultValue; switch (config) { + case uint value: + result = (int)value; + break; case string str: if (str.StartsWith('0')) { @@ -57,6 +60,15 @@ internal static short GetInt16Config(string configName, short defaultValue, bool short result = defaultValue; switch (config) { + case uint value: + { + result = (short)value; + if ((uint)result != value) + { + return defaultValue; // overflow + } + break; + } case string str: if (str.StartsWith("0x")) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index 7b49045defd237..41e097b4668d30 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -334,7 +334,7 @@ public static bool SetMaxThreads(int workerThreads, int completionPortThreads) public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) { // Note that worker threads and completion port threads share the same thread pool. - // The total number of threads cannot exceed MaxThreadCount. + // The total number of threads cannot exceed MaxPossibleThreadCount. workerThreads = PortableThreadPool.ThreadPoolInstance.GetMaxThreads(); completionPortThreads = 1; } @@ -350,7 +350,6 @@ public static bool SetMinThreads(int workerThreads, int completionPortThreads) public static void GetMinThreads(out int workerThreads, out int completionPortThreads) { - // All threads are pre-created at present workerThreads = PortableThreadPool.ThreadPoolInstance.GetMinThreads(); completionPortThreads = 0; } From ad228efc80fd08c5b74816c99556a93f38fb85d2 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 9 Mar 2020 08:20:21 -0700 Subject: [PATCH 03/48] Move portable RegisteredWaitHandle implementation to shared ThreadPool.cs --- .../System/Threading/ThreadPool.Portable.cs | 306 ----------------- .../src/System/Threading/ThreadPool.cs | 314 +++++++++++++++++- 2 files changed, 313 insertions(+), 307 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index 41e097b4668d30..afa9134b08317b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using Microsoft.Win32.SafeHandles; -using System.Runtime.Versioning; namespace System.Threading { @@ -11,311 +10,6 @@ namespace System.Threading // Portable implementation of ThreadPool // - /// - /// An object representing the registration of a via . - /// - [UnsupportedOSPlatform("browser")] - public sealed class RegisteredWaitHandle : MarshalByRefObject - { - internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper, - int millisecondsTimeout, bool repeating) - { - Handle = waitHandle; - Callback = callbackHelper; - TimeoutDurationMs = millisecondsTimeout; - Repeating = repeating; - RestartTimeout(Environment.TickCount); - } - - ~RegisteredWaitHandle() - { - if (WaitThread != null) - { - Unregister(null); - } - } - - private static AutoResetEvent? s_cachedEvent; - - private static AutoResetEvent RentEvent() => - Interlocked.Exchange(ref s_cachedEvent, null) ?? - new AutoResetEvent(false); - - private static void ReturnEvent(AutoResetEvent resetEvent) - { - if (Interlocked.CompareExchange(ref s_cachedEvent, resetEvent, null) != null) - { - resetEvent.Dispose(); - } - } - - /// - /// The callback to execute when the wait on either times out or completes. - /// - internal _ThreadPoolWaitOrTimerCallback Callback { get; } - - /// - /// The that was registered. - /// - internal WaitHandle Handle { get; } - - /// - /// The time this handle times out at in ms. - /// - internal int TimeoutTimeMs { get; private set; } - - private int TimeoutDurationMs { get; } - - internal bool IsInfiniteTimeout => TimeoutDurationMs == -1; - - internal void RestartTimeout(int currentTimeMs) - { - TimeoutTimeMs = currentTimeMs + TimeoutDurationMs; - } - - /// - /// Whether or not the wait is a repeating wait. - /// - internal bool Repeating { get; } - - /// - /// The the user passed in via . - /// - private SafeWaitHandle? UserUnregisterWaitHandle { get; set; } - - private IntPtr UserUnregisterWaitHandleValue { get; set; } - - internal bool IsBlocking => UserUnregisterWaitHandleValue == (IntPtr)(-1); - - /// - /// The this was registered on. - /// - internal PortableThreadPool.WaitThread? WaitThread { get; set; } - - /// - /// The number of callbacks that are currently queued on the Thread Pool or executing. - /// - private int _numRequestedCallbacks; - - private readonly LowLevelLock _callbackLock = new LowLevelLock(); - - /// - /// Notes if we need to signal the user's unregister event after all callbacks complete. - /// - private bool _signalAfterCallbacksComplete; - - private bool _unregisterCalled; - - private bool _unregistered; - - private AutoResetEvent? _callbacksComplete; - - private AutoResetEvent? _removed; - - /// - /// Unregisters this wait handle registration from the wait threads. - /// - /// The event to signal when the handle is unregistered. - /// If the handle was successfully marked to be removed and the provided wait handle was set as the user provided event. - /// - /// This method will only return true on the first call. - /// Passing in a wait handle with a value of -1 will result in a blocking wait, where Unregister will not return until the full unregistration is completed. - /// - public bool Unregister(WaitHandle? waitObject) - { - GC.SuppressFinalize(this); - _callbackLock.Acquire(); - bool needToRollBackRefCountOnException = false; - try - { - if (_unregisterCalled) - { - return false; - } - - UserUnregisterWaitHandle = waitObject?.SafeWaitHandle; - UserUnregisterWaitHandle?.DangerousAddRef(ref needToRollBackRefCountOnException); - - UserUnregisterWaitHandleValue = UserUnregisterWaitHandle?.DangerousGetHandle() ?? IntPtr.Zero; - - if (_unregistered) - { - SignalUserWaitHandle(); - return true; - } - - if (IsBlocking) - { - _callbacksComplete = RentEvent(); - } - else - { - _removed = RentEvent(); - } - _unregisterCalled = true; - } - catch (Exception) // Rollback state on exception - { - if (_removed != null) - { - ReturnEvent(_removed); - _removed = null; - } - else if (_callbacksComplete != null) - { - ReturnEvent(_callbacksComplete); - _callbacksComplete = null; - } - - UserUnregisterWaitHandleValue = IntPtr.Zero; - - if (needToRollBackRefCountOnException) - { - UserUnregisterWaitHandle?.DangerousRelease(); - } - - UserUnregisterWaitHandle = null; - throw; - } - finally - { - _callbackLock.Release(); - } - - WaitThread!.UnregisterWait(this); - return true; - } - - /// - /// Signal if it has not been signaled yet and is a valid handle. - /// - private void SignalUserWaitHandle() - { - _callbackLock.VerifyIsLocked(); - SafeWaitHandle? handle = UserUnregisterWaitHandle; - IntPtr handleValue = UserUnregisterWaitHandleValue; - try - { - if (handleValue != IntPtr.Zero && handleValue != (IntPtr)(-1)) - { - Debug.Assert(handleValue == handle!.DangerousGetHandle()); - EventWaitHandle.Set(handle); - } - } - finally - { - handle?.DangerousRelease(); - _callbacksComplete?.Set(); - _unregistered = true; - } - } - - /// - /// Perform the registered callback if the has not been signaled. - /// - /// Whether or not the wait timed out. - internal void PerformCallback(bool timedOut) - { -#if DEBUG - _callbackLock.Acquire(); - try - { - Debug.Assert(_numRequestedCallbacks != 0); - } - finally - { - _callbackLock.Release(); - } -#endif - _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Callback, timedOut); - CompleteCallbackRequest(); - } - - /// - /// Tell this handle that there is a callback queued on the thread pool for this handle. - /// - internal void RequestCallback() - { - _callbackLock.Acquire(); - try - { - _numRequestedCallbacks++; - } - finally - { - _callbackLock.Release(); - } - } - - /// - /// Called when the wait thread removes this handle registration. This will signal the user's event if there are no callbacks pending, - /// or note that the user's event must be signaled when the callbacks complete. - /// - internal void OnRemoveWait() - { - _callbackLock.Acquire(); - try - { - _removed?.Set(); - if (_numRequestedCallbacks == 0) - { - SignalUserWaitHandle(); - } - else - { - _signalAfterCallbacksComplete = true; - } - } - finally - { - _callbackLock.Release(); - } - } - - /// - /// Reduces the number of callbacks requested. If there are no more callbacks and the user's handle is queued to be signaled, signal it. - /// - private void CompleteCallbackRequest() - { - _callbackLock.Acquire(); - try - { - --_numRequestedCallbacks; - if (_numRequestedCallbacks == 0 && _signalAfterCallbacksComplete) - { - SignalUserWaitHandle(); - } - } - finally - { - _callbackLock.Release(); - } - } - - /// - /// Wait for all queued callbacks and the full unregistration to complete. - /// - internal void WaitForCallbacks() - { - Debug.Assert(IsBlocking); - Debug.Assert(_unregisterCalled); // Should only be called when the wait is unregistered by the user. - - _callbacksComplete!.WaitOne(); - ReturnEvent(_callbacksComplete); - _callbacksComplete = null; - } - - internal void WaitForRemoval() - { - Debug.Assert(!IsBlocking); - Debug.Assert(_unregisterCalled); // Should only be called when the wait is unregistered by the user. - - _removed!.WaitOne(); - ReturnEvent(_removed); - _removed = null; - } - } - public static partial class ThreadPool { internal const bool EnableWorkerTracking = false; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index a05c688694331b..65a445749088ad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -16,8 +16,8 @@ using System.Diagnostics.Tracing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading.Tasks; using System.Runtime.Versioning; +using System.Threading.Tasks; using Internal.Runtime.CompilerServices; namespace System.Threading @@ -934,6 +934,318 @@ internal static void PerformWaitOrTimerCallback(_ThreadPoolWaitOrTimerCallback h } } + /// + /// An object representing the registration of a via . + /// + [UnsupportedOSPlatform("browser")] + public sealed class RegisteredWaitHandle : MarshalByRefObject + { + internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper, + int millisecondsTimeout, bool repeating) + { + Handle = waitHandle; + Callback = callbackHelper; + TimeoutDurationMs = millisecondsTimeout; + Repeating = repeating; + RestartTimeout(Environment.TickCount); + } + + ~RegisteredWaitHandle() + { + if (WaitThread != null) + { + Unregister(null); + } + } + + private static AutoResetEvent s_cachedEvent; + + private static AutoResetEvent RentEvent() + { + AutoResetEvent resetEvent = Interlocked.Exchange(ref s_cachedEvent, (AutoResetEvent)null); + if (resetEvent == null) + { + resetEvent = new AutoResetEvent(false); + } + return resetEvent; + } + + private static void ReturnEvent(AutoResetEvent resetEvent) + { + if (Interlocked.CompareExchange(ref s_cachedEvent, resetEvent, null) != null) + { + resetEvent.Dispose(); + } + } + + /// + /// The callback to execute when the wait on either times out or completes. + /// + internal _ThreadPoolWaitOrTimerCallback Callback { get; } + + + /// + /// The that was registered. + /// + internal WaitHandle Handle { get; } + + /// + /// The time this handle times out at in ms. + /// + internal int TimeoutTimeMs { get; private set; } + + private int TimeoutDurationMs { get; } + + internal bool IsInfiniteTimeout => TimeoutDurationMs == -1; + + internal void RestartTimeout(int currentTimeMs) + { + TimeoutTimeMs = currentTimeMs + TimeoutDurationMs; + } + + /// + /// Whether or not the wait is a repeating wait. + /// + internal bool Repeating { get; } + + /// + /// The the user passed in via . + /// + private SafeWaitHandle UserUnregisterWaitHandle { get; set; } + + private IntPtr UserUnregisterWaitHandleValue { get; set; } + + internal bool IsBlocking => UserUnregisterWaitHandleValue == (IntPtr)(-1); + + /// + /// The this was registered on. + /// + internal PortableThreadPool.WaitThread WaitThread { get; set; } + + /// + /// The number of callbacks that are currently queued on the Thread Pool or executing. + /// + private int _numRequestedCallbacks; + + private LowLevelLock _callbackLock = new LowLevelLock(); + + /// + /// Notes if we need to signal the user's unregister event after all callbacks complete. + /// + private bool _signalAfterCallbacksComplete; + + private bool _unregisterCalled; + + private bool _unregistered; + + private AutoResetEvent _callbacksComplete; + + private AutoResetEvent _removed; + + /// + /// Unregisters this wait handle registration from the wait threads. + /// + /// The event to signal when the handle is unregistered. + /// If the handle was successfully marked to be removed and the provided wait handle was set as the user provided event. + /// + /// This method will only return true on the first call. + /// Passing in a wait handle with a value of -1 will result in a blocking wait, where Unregister will not return until the full unregistration is completed. + /// + public bool Unregister(WaitHandle waitObject) + { + GC.SuppressFinalize(this); + _callbackLock.Acquire(); + bool needToRollBackRefCountOnException = false; + try + { + if (_unregisterCalled) + { + return false; + } + + UserUnregisterWaitHandle = waitObject?.SafeWaitHandle; + UserUnregisterWaitHandle?.DangerousAddRef(ref needToRollBackRefCountOnException); + + UserUnregisterWaitHandleValue = UserUnregisterWaitHandle?.DangerousGetHandle() ?? IntPtr.Zero; + + if (_unregistered) + { + SignalUserWaitHandle(); + return true; + } + + if (IsBlocking) + { + _callbacksComplete = RentEvent(); + } + else + { + _removed = RentEvent(); + } + _unregisterCalled = true; + } + catch (Exception) // Rollback state on exception + { + if (_removed != null) + { + ReturnEvent(_removed); + _removed = null; + } + else if (_callbacksComplete != null) + { + ReturnEvent(_callbacksComplete); + _callbacksComplete = null; + } + + UserUnregisterWaitHandleValue = IntPtr.Zero; + + if (needToRollBackRefCountOnException) + { + UserUnregisterWaitHandle?.DangerousRelease(); + } + + UserUnregisterWaitHandle = null; + throw; + } + finally + { + _callbackLock.Release(); + } + + WaitThread.UnregisterWait(this); + return true; + } + + /// + /// Signal if it has not been signaled yet and is a valid handle. + /// + private void SignalUserWaitHandle() + { + _callbackLock.VerifyIsLocked(); + SafeWaitHandle handle = UserUnregisterWaitHandle; + IntPtr handleValue = UserUnregisterWaitHandleValue; + try + { + if (handleValue != IntPtr.Zero && handleValue != (IntPtr)(-1)) + { + Debug.Assert(handleValue == handle.DangerousGetHandle()); + EventWaitHandle.Set(handle); + } + } + finally + { + handle?.DangerousRelease(); + _callbacksComplete?.Set(); + _unregistered = true; + } + } + + /// + /// Perform the registered callback if the has not been signaled. + /// + /// Whether or not the wait timed out. + internal void PerformCallback(bool timedOut) + { +#if DEBUG + _callbackLock.Acquire(); + try + { + Debug.Assert(_numRequestedCallbacks != 0); + } + finally + { + _callbackLock.Release(); + } +#endif + _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Callback, timedOut); + CompleteCallbackRequest(); + } + + /// + /// Tell this handle that there is a callback queued on the thread pool for this handle. + /// + internal void RequestCallback() + { + _callbackLock.Acquire(); + try + { + _numRequestedCallbacks++; + } + finally + { + _callbackLock.Release(); + } + } + + /// + /// Called when the wait thread removes this handle registration. This will signal the user's event if there are no callbacks pending, + /// or note that the user's event must be signaled when the callbacks complete. + /// + internal void OnRemoveWait() + { + _callbackLock.Acquire(); + try + { + _removed?.Set(); + if (_numRequestedCallbacks == 0) + { + SignalUserWaitHandle(); + } + else + { + _signalAfterCallbacksComplete = true; + } + } + finally + { + _callbackLock.Release(); + } + } + + /// + /// Reduces the number of callbacks requested. If there are no more callbacks and the user's handle is queued to be signaled, signal it. + /// + private void CompleteCallbackRequest() + { + _callbackLock.Acquire(); + try + { + --_numRequestedCallbacks; + if (_numRequestedCallbacks == 0 && _signalAfterCallbacksComplete) + { + SignalUserWaitHandle(); + } + } + finally + { + _callbackLock.Release(); + } + } + + /// + /// Wait for all queued callbacks and the full unregistration to complete. + /// + internal void WaitForCallbacks() + { + Debug.Assert(IsBlocking); + Debug.Assert(_unregisterCalled); // Should only be called when the wait is unregistered by the user. + + _callbacksComplete.WaitOne(); + ReturnEvent(_callbacksComplete); + _callbacksComplete = null; + } + + internal void WaitForRemoval() + { + Debug.Assert(!IsBlocking); + Debug.Assert(_unregisterCalled); // Should only be called when the wait is unregistered by the user. + + _removed.WaitOne(); + ReturnEvent(_removed); + _removed = null; + } + } + public static partial class ThreadPool { internal static readonly ThreadPoolWorkQueue s_workQueue = new ThreadPoolWorkQueue(); From 1155fb072c3333135a459920aa53e5e130c053e8 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 3 Mar 2020 22:34:21 -0800 Subject: [PATCH 04/48] Merge RegisteredWaitHandle implementations --- .../System/Threading/ThreadPool.CoreCLR.cs | 232 +++++++----------- src/coreclr/src/vm/ecalllist.h | 2 +- .../PortableThreadPool.WaitThread.cs | 67 +++-- .../System/Threading/ThreadPool.Portable.cs | 40 ++- .../src/System/Threading/ThreadPool.cs | 114 +++++---- .../src/System/Threading/WaitHandle.cs | 41 ++++ 6 files changed, 246 insertions(+), 250 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index a12a496be872d8..34fcb7bb28d22c 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -29,128 +29,102 @@ internal static class _ThreadPoolWaitCallback internal static bool PerformWaitCallback() => ThreadPoolWorkQueue.Dispatch(); } - internal sealed class RegisteredWaitHandleSafe : CriticalFinalizerObject + public sealed partial class RegisteredWaitHandle : MarshalByRefObject { - private static IntPtr InvalidHandle => new IntPtr(-1); - private IntPtr registeredWaitHandle = InvalidHandle; - private WaitHandle? m_internalWaitObject; - private bool bReleaseNeeded; - private volatile int m_lock; + private IntPtr _nativeRegisteredWaitHandle = InvalidHandleValue; + private bool _releaseHandle; - internal IntPtr GetHandle() => registeredWaitHandle; + private static bool IsValidHandle(IntPtr handle) => handle != InvalidHandleValue && handle != IntPtr.Zero; - internal void SetHandle(IntPtr handle) + internal void SetNativeRegisteredWaitHandle(IntPtr nativeRegisteredWaitHandle) { - registeredWaitHandle = handle; + Debug.Assert(!ThreadPool.UsePortableThreadPool); + Debug.Assert(IsValidHandle(nativeRegisteredWaitHandle)); + Debug.Assert(!IsValidHandle(_nativeRegisteredWaitHandle)); + + _nativeRegisteredWaitHandle = nativeRegisteredWaitHandle; } - internal void SetWaitObject(WaitHandle waitObject) + internal void OnBeforeRegister() { - m_internalWaitObject = waitObject; - if (waitObject != null) + if (ThreadPool.UsePortableThreadPool) { - m_internalWaitObject.SafeWaitHandle.DangerousAddRef(ref bReleaseNeeded); + GC.SuppressFinalize(this); + return; } + + Handle.DangerousAddRef(ref _releaseHandle); } - internal bool Unregister( - WaitHandle? waitObject // object to be notified when all callbacks to delegates have completed - ) + /// + /// Unregisters this wait handle registration from the wait threads. + /// + /// The event to signal when the handle is unregistered. + /// If the handle was successfully marked to be removed and the provided wait handle was set as the user provided event. + /// + /// This method will only return true on the first call. + /// Passing in a wait handle with a value of -1 will result in a blocking wait, where Unregister will not return until the full unregistration is completed. + /// + public bool Unregister(WaitHandle waitObject) { - bool result = false; + if (ThreadPool.UsePortableThreadPool) + { + return UnregisterPortable(waitObject); + } - // lock(this) cannot be used reliably in Cer since thin lock could be - // promoted to syncblock and that is not a guaranteed operation - bool bLockTaken = false; - do + s_callbackLock.Acquire(); + try { - if (Interlocked.CompareExchange(ref m_lock, 1, 0) == 0) + if (!IsValidHandle(_nativeRegisteredWaitHandle) || + !UnregisterWaitNative(_nativeRegisteredWaitHandle, waitObject?.SafeWaitHandle)) + { + return false; + } + _nativeRegisteredWaitHandle = InvalidHandleValue; + + if (_releaseHandle) { - bLockTaken = true; - try - { - if (ValidHandle()) - { - result = UnregisterWaitNative(GetHandle(), waitObject?.SafeWaitHandle); - if (result) - { - if (bReleaseNeeded) - { - Debug.Assert(m_internalWaitObject != null, "Must be non-null for bReleaseNeeded to be true"); - m_internalWaitObject.SafeWaitHandle.DangerousRelease(); - bReleaseNeeded = false; - } - // if result not true don't release/suppress here so finalizer can make another attempt - SetHandle(InvalidHandle); - m_internalWaitObject = null; - GC.SuppressFinalize(this); - } - } - } - finally - { - m_lock = 0; - } + Handle.DangerousRelease(); + _releaseHandle = false; } - Thread.SpinWait(1); // yield to processor } - while (!bLockTaken); + finally + { + s_callbackLock.Release(); + } - return result; + GC.SuppressFinalize(this); + return true; } - private bool ValidHandle() => - registeredWaitHandle != InvalidHandle && registeredWaitHandle != IntPtr.Zero; - - ~RegisteredWaitHandleSafe() + ~RegisteredWaitHandle() { - // if the app has already unregistered the wait, there is nothing to cleanup - // we can detect this by checking the handle. Normally, there is no race condition here - // so no need to protect reading of handle. However, if this object gets - // resurrected and then someone does an unregister, it would introduce a race condition - // - // PrepareConstrainedRegions call not needed since finalizer already in Cer - // - // lock(this) cannot be used reliably even in Cer since thin lock could be - // promoted to syncblock and that is not a guaranteed operation - // - // Note that we will not "spin" to get this lock. We make only a single attempt; - // if we can't get the lock, it means some other thread is in the middle of a call - // to Unregister, which will do the work of the finalizer anyway. - // - // Further, it's actually critical that we *not* wait for the lock here, because - // the other thread that's in the middle of Unregister may be suspended for shutdown. - // Then, during the live-object finalization phase of shutdown, this thread would - // end up spinning forever, as the other thread would never release the lock. - // This will result in a "leak" of sorts (since the handle will not be cleaned up) - // but the process is exiting anyway. - // - // During AD-unload, we don't finalize live objects until all threads have been - // aborted out of the AD. Since these locked regions are CERs, we won't abort them - // while the lock is held. So there should be no leak on AD-unload. - // - if (Interlocked.CompareExchange(ref m_lock, 1, 0) == 0) + if (ThreadPool.UsePortableThreadPool) + { + return; + } + + s_callbackLock.Acquire(); + try { - try + if (!IsValidHandle(_nativeRegisteredWaitHandle)) { - if (ValidHandle()) - { - WaitHandleCleanupNative(registeredWaitHandle); - if (bReleaseNeeded) - { - Debug.Assert(m_internalWaitObject != null, "Must be non-null for bReleaseNeeded to be true"); - m_internalWaitObject.SafeWaitHandle.DangerousRelease(); - bReleaseNeeded = false; - } - SetHandle(InvalidHandle); - m_internalWaitObject = null; - } + return; } - finally + + WaitHandleCleanupNative(_nativeRegisteredWaitHandle); + _nativeRegisteredWaitHandle = InvalidHandleValue; + + if (_releaseHandle) { - m_lock = 0; + Handle.DangerousRelease(); + _releaseHandle = false; } } + finally + { + s_callbackLock.Release(); + } } [MethodImpl(MethodImplOptions.InternalCall)] @@ -160,34 +134,6 @@ private bool ValidHandle() => private static extern bool UnregisterWaitNative(IntPtr handle, SafeHandle? waitObject); } - [UnsupportedOSPlatform("browser")] - public sealed class RegisteredWaitHandle : MarshalByRefObject - { - private readonly RegisteredWaitHandleSafe internalRegisteredWait; - - internal RegisteredWaitHandle() - { - internalRegisteredWait = new RegisteredWaitHandleSafe(); - } - - internal void SetHandle(IntPtr handle) - { - internalRegisteredWait.SetHandle(handle); - } - - internal void SetWaitObject(WaitHandle waitObject) - { - internalRegisteredWait.SetWaitObject(waitObject); - } - - public bool Unregister( - WaitHandle? waitObject // object to be notified when all callbacks to delegates have completed - ) - { - return internalRegisteredWait.Unregister(waitObject); - } - } - public static partial class ThreadPool { // Time in ms for which ThreadPoolWorkQueue.Dispatch keeps executing work items before returning to the OS @@ -359,36 +305,24 @@ public static long CompletedWorkItemCount [MethodImpl(MethodImplOptions.InternalCall)] private static extern long GetPendingUnmanagedWorkItemCount(); - private static RegisteredWaitHandle RegisterWaitForSingleObject( - WaitHandle waitObject, - WaitOrTimerCallback callBack, - object? state, - uint millisecondsTimeOutInterval, - bool executeOnlyOnce, // NOTE: we do not allow other options that allow the callback to be queued as an APC - bool compressStack - ) + private static void RegisterWaitForSingleObjectCore(WaitHandle waitObject, RegisteredWaitHandle registeredWaitHandle) { - RegisteredWaitHandle registeredWaitHandle = new RegisteredWaitHandle(); + registeredWaitHandle.OnBeforeRegister(); - if (callBack != null) - { - _ThreadPoolWaitOrTimerCallback callBackHelper = new _ThreadPoolWaitOrTimerCallback(callBack, state, compressStack); - state = (object)callBackHelper; - // call SetWaitObject before native call so that waitObject won't be closed before threadpoolmgr registration - // this could occur if callback were to fire before SetWaitObject does its addref - registeredWaitHandle.SetWaitObject(waitObject); - IntPtr nativeRegisteredWaitHandle = RegisterWaitForSingleObjectNative(waitObject, - state, - millisecondsTimeOutInterval, - executeOnlyOnce, - registeredWaitHandle); - registeredWaitHandle.SetHandle(nativeRegisteredWaitHandle); - } - else + if (UsePortableThreadPool) { - throw new ArgumentNullException(nameof(WaitOrTimerCallback)); + PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle); + return; } - return registeredWaitHandle; + + IntPtr nativeRegisteredWaitHandle = + RegisterWaitForSingleObjectNative( + waitObject, + registeredWaitHandle.Callback, + (uint)registeredWaitHandle.TimeoutDurationMs, + !registeredWaitHandle.Repeating, + registeredWaitHandle); + registeredWaitHandle.SetNativeRegisteredWaitHandle(nativeRegisteredWaitHandle); } internal static void RequestWorkerThread() diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h index 906e9d2e04c898..c5588c2a943cd3 100644 --- a/src/coreclr/src/vm/ecalllist.h +++ b/src/coreclr/src/vm/ecalllist.h @@ -1190,7 +1190,7 @@ FCClassElement("OverlappedData", "System.Threading", gOverlappedFuncs) FCClassElement("PunkSafeHandle", "System.Reflection.Emit", gSymWrapperCodePunkSafeHandleFuncs) -FCClassElement("RegisteredWaitHandleSafe", "System.Threading", gRegisteredWaitHandleFuncs) +FCClassElement("RegisteredWaitHandle", "System.Threading", gRegisteredWaitHandleFuncs) FCClassElement("RuntimeAssembly", "System.Reflection", gRuntimeAssemblyFuncs) FCClassElement("RuntimeFieldHandle", "System", gCOMFieldHandleNewFuncs) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs index 78a3b2db8291be..267b585f17c3fd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using Microsoft.Win32.SafeHandles; namespace System.Threading { @@ -23,20 +24,17 @@ internal void RegisterWaitHandle(RegisteredWaitHandle handle) _waitThreadLock.Acquire(); try { - if (_waitThreadsHead == null) // Lazily create the first wait thread. + WaitThreadNode? current = _waitThreadsHead; + if (current == null) // Lazily create the first wait thread. { - _waitThreadsHead = new WaitThreadNode - { - Thread = new WaitThread() - }; + _waitThreadsHead = current = new WaitThreadNode(new WaitThread()); } // Register the wait handle on the first wait thread that is not at capacity. WaitThreadNode prev; - WaitThreadNode? current = _waitThreadsHead; do { - if (current.Thread!.RegisterWaitHandle(handle)) + if (current.Thread.RegisterWaitHandle(handle)) { return; } @@ -45,10 +43,7 @@ internal void RegisterWaitHandle(RegisteredWaitHandle handle) } while (current != null); // If all wait threads are full, create a new one. - prev.Next = new WaitThreadNode - { - Thread = new WaitThread() - }; + prev.Next = new WaitThreadNode(new WaitThread()); prev.Next.Thread.RegisterWaitHandle(handle); return; } @@ -87,14 +82,14 @@ private bool TryRemoveWaitThread(WaitThread thread) /// The wait thread to remove from the list. private void RemoveWaitThread(WaitThread thread) { - if (_waitThreadsHead!.Thread == thread) + WaitThreadNode? current = _waitThreadsHead!; + if (current.Thread == thread) { - _waitThreadsHead = _waitThreadsHead.Next; + _waitThreadsHead = current.Next; return; } WaitThreadNode prev; - WaitThreadNode? current = _waitThreadsHead; do { @@ -112,8 +107,10 @@ private void RemoveWaitThread(WaitThread thread) private class WaitThreadNode { - public WaitThread? Thread { get; set; } + public WaitThread Thread { get; } public WaitThreadNode? Next { get; set; } + + public WaitThreadNode(WaitThread thread) => Thread = thread; } /// @@ -146,7 +143,7 @@ public CompletedWaitHandle(RegisteredWaitHandle completedHandle, bool timedOut) /// /// The zeroth element of this array is always . /// - private readonly WaitHandle[] _waitHandles = new WaitHandle[WaitHandle.MaxWaitHandles]; + private readonly SafeWaitHandle[] _waitHandles = new SafeWaitHandle[WaitHandle.MaxWaitHandles]; /// /// The number of user-registered waits on this wait thread. /// @@ -170,7 +167,7 @@ public CompletedWaitHandle(RegisteredWaitHandle completedHandle, bool timedOut) public WaitThread() { - _waitHandles[0] = _changeHandlesEvent; + _waitHandles[0] = _changeHandlesEvent.SafeWaitHandle; Thread waitThread = new Thread(WaitThreadStart); waitThread.IsBackground = true; waitThread.Start(); @@ -197,12 +194,14 @@ private void WaitThreadStart() { for (int i = 0; i < numUserWaits; i++) { - if (_registeredWaits[i].IsInfiniteTimeout) + RegisteredWaitHandle registeredWait = _registeredWaits[i]; + Debug.Assert(registeredWait != null); + if (registeredWait.IsInfiniteTimeout) { continue; } - int handleTimeoutDurationMs = _registeredWaits[i].TimeoutTimeMs - preWaitTimeMs; + int handleTimeoutDurationMs = registeredWait.TimeoutTimeMs - preWaitTimeMs; if (timeoutDurationMs == Timeout.Infinite) { @@ -220,17 +219,25 @@ private void WaitThreadStart() } } - int signaledHandleIndex = WaitHandle.WaitAny(new ReadOnlySpan(_waitHandles, 0, numUserWaits + 1), timeoutDurationMs); + int signaledHandleIndex = WaitHandle.WaitAny(new ReadOnlySpan(_waitHandles, 0, numUserWaits + 1), timeoutDurationMs); + + if (signaledHandleIndex >= WaitHandle.WaitAbandoned && + signaledHandleIndex < WaitHandle.WaitAbandoned + 1 + numUserWaits) + { + // For compatibility, treat an abandoned mutex wait result as a success and ignore the abandonment + Debug.Assert(signaledHandleIndex != WaitHandle.WaitAbandoned); // the first wait handle is an event + signaledHandleIndex += WaitHandle.WaitSuccess - WaitHandle.WaitAbandoned; + } if (signaledHandleIndex == 0) // If we were woken up for a change in our handles, continue. { continue; } - RegisteredWaitHandle? signaledHandle = signaledHandleIndex != WaitHandle.WaitTimeout ? _registeredWaits[signaledHandleIndex - 1] : null; - - if (signaledHandle != null) + if (signaledHandleIndex != WaitHandle.WaitTimeout) { + RegisteredWaitHandle signaledHandle = _registeredWaits[signaledHandleIndex - 1]; + Debug.Assert(signaledHandle != null); QueueWaitCompletion(signaledHandle, false); } else @@ -247,6 +254,7 @@ private void WaitThreadStart() for (int i = 0; i < numUserWaits; i++) { RegisteredWaitHandle registeredHandle = _registeredWaits[i]; + Debug.Assert(registeredHandle != null); int handleTimeoutDurationMs = registeredHandle.TimeoutTimeMs - preWaitTimeMs; if (elapsedDurationMs >= handleTimeoutDurationMs) { @@ -282,17 +290,22 @@ private void ProcessRemovals() // This is O(N^2), but max(N) = 63 and N will usually be very low for (int i = 0; i < _numPendingRemoves; i++) { + RegisteredWaitHandle waitHandleToRemove = _pendingRemoves[i]!; for (int j = 0; j < _numUserWaits; j++) { - if (_pendingRemoves[i] == _registeredWaits[j]) + if (waitHandleToRemove == _registeredWaits[j]) { - _registeredWaits[j].OnRemoveWait(); + waitHandleToRemove.OnRemoveWait(); _registeredWaits[j] = _registeredWaits[_numUserWaits - 1]; + Debug.Assert(_registeredWaits[j] != null); _waitHandles[j + 1] = _waitHandles[_numUserWaits]; + Debug.Assert(_waitHandles[j + 1] != null); _registeredWaits[_numUserWaits - 1] = null!; _waitHandles[_numUserWaits] = null!; --_numUserWaits; _pendingRemoves[i] = null; + + waitHandleToRemove.Handle.DangerousRelease(); break; } } @@ -352,6 +365,10 @@ public bool RegisterWaitHandle(RegisteredWaitHandle handle) return false; } + bool success = false; + handle.Handle.DangerousAddRef(ref success); + Debug.Assert(success); + _registeredWaits[_numUserWaits] = handle; _waitHandles[_numUserWaits + 1] = handle.Handle; _numUserWaits++; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index afa9134b08317b..1c89b57dbeae3a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using Microsoft.Win32.SafeHandles; namespace System.Threading { @@ -10,11 +9,24 @@ namespace System.Threading // Portable implementation of ThreadPool // + public sealed partial class RegisteredWaitHandle : MarshalByRefObject + { + /// + /// Unregisters this wait handle registration from the wait threads. + /// + /// The event to signal when the handle is unregistered. + /// If the handle was successfully marked to be removed and the provided wait handle was set as the user provided event. + /// + /// This method will only return true on the first call. + /// Passing in a wait handle with a value of -1 will result in a blocking wait, where Unregister will not return until the full unregistration is completed. + /// + public bool Unregister(WaitHandle waitObject) => UnregisterPortable(waitObject); + } + public static partial class ThreadPool { internal const bool EnableWorkerTracking = false; - internal static void InitializeForThreadPoolThread() { } public static bool SetMaxThreads(int workerThreads, int completionPortThreads) { @@ -93,27 +105,7 @@ internal static bool NotifyWorkItemComplete() return PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(); } - private static RegisteredWaitHandle RegisterWaitForSingleObject( - WaitHandle waitObject, - WaitOrTimerCallback callBack, - object? state, - uint millisecondsTimeOutInterval, - bool executeOnlyOnce, - bool flowExecutionContext) - { - if (waitObject == null) - throw new ArgumentNullException(nameof(waitObject)); - - if (callBack == null) - throw new ArgumentNullException(nameof(callBack)); - - RegisteredWaitHandle registeredHandle = new RegisteredWaitHandle( - waitObject, - new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext), - (int)millisecondsTimeOutInterval, - !executeOnlyOnce); - PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredHandle); - return registeredHandle; - } + private static void RegisterWaitForSingleObjectCore(WaitHandle? waitObject, RegisteredWaitHandle registeredWaitHandle) => + PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index 65a445749088ad..a7e64ab425a4ba 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -19,6 +19,7 @@ using System.Runtime.Versioning; using System.Threading.Tasks; using Internal.Runtime.CompilerServices; +using Microsoft.Win32.SafeHandles; namespace System.Threading { @@ -938,31 +939,23 @@ internal static void PerformWaitOrTimerCallback(_ThreadPoolWaitOrTimerCallback h /// An object representing the registration of a via . /// [UnsupportedOSPlatform("browser")] - public sealed class RegisteredWaitHandle : MarshalByRefObject + public sealed partial class RegisteredWaitHandle : MarshalByRefObject { internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper, int millisecondsTimeout, bool repeating) { - Handle = waitHandle; + Handle = waitHandle.SafeWaitHandle; Callback = callbackHelper; TimeoutDurationMs = millisecondsTimeout; Repeating = repeating; RestartTimeout(Environment.TickCount); } - ~RegisteredWaitHandle() - { - if (WaitThread != null) - { - Unregister(null); - } - } - - private static AutoResetEvent s_cachedEvent; + private static AutoResetEvent? s_cachedEvent; private static AutoResetEvent RentEvent() { - AutoResetEvent resetEvent = Interlocked.Exchange(ref s_cachedEvent, (AutoResetEvent)null); + AutoResetEvent? resetEvent = Interlocked.Exchange(ref s_cachedEvent, (AutoResetEvent?)null); if (resetEvent == null) { resetEvent = new AutoResetEvent(false); @@ -978,6 +971,8 @@ private static void ReturnEvent(AutoResetEvent resetEvent) } } + private static readonly LowLevelLock s_callbackLock = new LowLevelLock(); + /// /// The callback to execute when the wait on either times out or completes. /// @@ -985,16 +980,16 @@ private static void ReturnEvent(AutoResetEvent resetEvent) /// - /// The that was registered. + /// The that was registered. /// - internal WaitHandle Handle { get; } + internal SafeWaitHandle Handle { get; } /// /// The time this handle times out at in ms. /// internal int TimeoutTimeMs { get; private set; } - private int TimeoutDurationMs { get; } + internal int TimeoutDurationMs { get; } internal bool IsInfiniteTimeout => TimeoutDurationMs == -1; @@ -1011,24 +1006,24 @@ internal void RestartTimeout(int currentTimeMs) /// /// The the user passed in via . /// - private SafeWaitHandle UserUnregisterWaitHandle { get; set; } + private SafeWaitHandle? UserUnregisterWaitHandle { get; set; } private IntPtr UserUnregisterWaitHandleValue { get; set; } - internal bool IsBlocking => UserUnregisterWaitHandleValue == (IntPtr)(-1); + private static IntPtr InvalidHandleValue => new IntPtr(-1); + + internal bool IsBlocking => UserUnregisterWaitHandleValue == InvalidHandleValue; /// /// The this was registered on. /// - internal PortableThreadPool.WaitThread WaitThread { get; set; } + internal PortableThreadPool.WaitThread? WaitThread { get; set; } /// /// The number of callbacks that are currently queued on the Thread Pool or executing. /// private int _numRequestedCallbacks; - private LowLevelLock _callbackLock = new LowLevelLock(); - /// /// Notes if we need to signal the user's unregister event after all callbacks complete. /// @@ -1038,23 +1033,17 @@ internal void RestartTimeout(int currentTimeMs) private bool _unregistered; - private AutoResetEvent _callbacksComplete; + private AutoResetEvent? _callbacksComplete; - private AutoResetEvent _removed; + private AutoResetEvent? _removed; - /// - /// Unregisters this wait handle registration from the wait threads. - /// - /// The event to signal when the handle is unregistered. - /// If the handle was successfully marked to be removed and the provided wait handle was set as the user provided event. - /// - /// This method will only return true on the first call. - /// Passing in a wait handle with a value of -1 will result in a blocking wait, where Unregister will not return until the full unregistration is completed. - /// - public bool Unregister(WaitHandle waitObject) + private bool UnregisterPortable(WaitHandle waitObject) { - GC.SuppressFinalize(this); - _callbackLock.Acquire(); + // The registered wait handle must have been registered by this time, otherwise the instance is not handed out to + // the caller of the public variants of RegisterWaitForSingleObject + Debug.Assert(WaitThread != null); + + s_callbackLock.Acquire(); bool needToRollBackRefCountOnException = false; try { @@ -1082,7 +1071,6 @@ public bool Unregister(WaitHandle waitObject) { _removed = RentEvent(); } - _unregisterCalled = true; } catch (Exception) // Rollback state on exception { @@ -1109,10 +1097,11 @@ public bool Unregister(WaitHandle waitObject) } finally { - _callbackLock.Release(); + _unregisterCalled = true; + s_callbackLock.Release(); } - WaitThread.UnregisterWait(this); + WaitThread!.UnregisterWait(this); return true; } @@ -1121,14 +1110,14 @@ public bool Unregister(WaitHandle waitObject) /// private void SignalUserWaitHandle() { - _callbackLock.VerifyIsLocked(); - SafeWaitHandle handle = UserUnregisterWaitHandle; + s_callbackLock.VerifyIsLocked(); + SafeWaitHandle? handle = UserUnregisterWaitHandle; IntPtr handleValue = UserUnregisterWaitHandleValue; try { - if (handleValue != IntPtr.Zero && handleValue != (IntPtr)(-1)) + if (handleValue != IntPtr.Zero && handleValue != InvalidHandleValue) { - Debug.Assert(handleValue == handle.DangerousGetHandle()); + Debug.Assert(handleValue == handle!.DangerousGetHandle()); EventWaitHandle.Set(handle); } } @@ -1147,14 +1136,14 @@ private void SignalUserWaitHandle() internal void PerformCallback(bool timedOut) { #if DEBUG - _callbackLock.Acquire(); + s_callbackLock.Acquire(); try { Debug.Assert(_numRequestedCallbacks != 0); } finally { - _callbackLock.Release(); + s_callbackLock.Release(); } #endif _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Callback, timedOut); @@ -1166,14 +1155,14 @@ internal void PerformCallback(bool timedOut) /// internal void RequestCallback() { - _callbackLock.Acquire(); + s_callbackLock.Acquire(); try { _numRequestedCallbacks++; } finally { - _callbackLock.Release(); + s_callbackLock.Release(); } } @@ -1183,7 +1172,7 @@ internal void RequestCallback() /// internal void OnRemoveWait() { - _callbackLock.Acquire(); + s_callbackLock.Acquire(); try { _removed?.Set(); @@ -1198,7 +1187,7 @@ internal void OnRemoveWait() } finally { - _callbackLock.Release(); + s_callbackLock.Release(); } } @@ -1207,7 +1196,7 @@ internal void OnRemoveWait() /// private void CompleteCallbackRequest() { - _callbackLock.Acquire(); + s_callbackLock.Acquire(); try { --_numRequestedCallbacks; @@ -1218,7 +1207,7 @@ private void CompleteCallbackRequest() } finally { - _callbackLock.Release(); + s_callbackLock.Release(); } } @@ -1230,7 +1219,7 @@ internal void WaitForCallbacks() Debug.Assert(IsBlocking); Debug.Assert(_unregisterCalled); // Should only be called when the wait is unregistered by the user. - _callbacksComplete.WaitOne(); + _callbacksComplete!.WaitOne(); ReturnEvent(_callbacksComplete); _callbacksComplete = null; } @@ -1240,7 +1229,7 @@ internal void WaitForRemoval() Debug.Assert(!IsBlocking); Debug.Assert(_unregisterCalled); // Should only be called when the wait is unregistered by the user. - _removed.WaitOne(); + _removed!.WaitOne(); ReturnEvent(_removed); _removed = null; } @@ -1386,6 +1375,29 @@ bool executeOnlyOnce return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)tm, executeOnlyOnce, false); } + private static RegisteredWaitHandle RegisterWaitForSingleObject( + WaitHandle? waitObject, + WaitOrTimerCallback? callBack, + object? state, + uint millisecondsTimeOutInterval, + bool executeOnlyOnce, + bool flowExecutionContext) + { + if (waitObject == null) + throw new ArgumentNullException(nameof(waitObject)); + + if (callBack == null) + throw new ArgumentNullException(nameof(callBack)); + + RegisteredWaitHandle registeredHandle = new RegisteredWaitHandle( + waitObject, + new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext), + (int)millisecondsTimeOutInterval, + !executeOnlyOnce); + RegisterWaitForSingleObjectCore(waitObject, registeredHandle); + return registeredHandle; + } + public static bool QueueUserWorkItem(WaitCallback callBack) => QueueUserWorkItem(callBack, null); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs index 9fcbf2075042b1..1459ee6393f575 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs @@ -314,6 +314,45 @@ private static int WaitMultiple(ReadOnlySpan waitHandles, bool waitA } } + private static int WaitAnyMultiple(ReadOnlySpan safeWaitHandles, int millisecondsTimeout) + { + // - Callers are expected to manage the lifetimes of the safe wait handles such that they would not expire during + // this wait + // - If the safe wait handle that satisfies the wait is an abandoned mutex, the wait result would reflect that and + // handling of that is left up to the caller + + Debug.Assert(safeWaitHandles.Length != 0); + Debug.Assert(safeWaitHandles.Length <= MaxWaitHandles); + Debug.Assert(millisecondsTimeout >= -1); + + SynchronizationContext? context = SynchronizationContext.Current; + bool useWaitContext = context != null && context.IsWaitNotificationRequired(); + + int waitResult; + if (useWaitContext) + { + IntPtr[] unsafeWaitHandles = new IntPtr[safeWaitHandles.Length]; + for (int i = 0; i < safeWaitHandles.Length; ++i) + { + Debug.Assert(safeWaitHandles[i] != null); + unsafeWaitHandles[i] = safeWaitHandles[i].DangerousGetHandle(); + } + waitResult = context!.Wait(unsafeWaitHandles, false, millisecondsTimeout); + } + else + { + Span unsafeWaitHandles = stackalloc IntPtr[safeWaitHandles.Length]; + for (int i = 0; i < safeWaitHandles.Length; ++i) + { + Debug.Assert(safeWaitHandles[i] != null); + unsafeWaitHandles[i] = safeWaitHandles[i].DangerousGetHandle(); + } + waitResult = WaitMultipleIgnoringSyncContext(unsafeWaitHandles, false, millisecondsTimeout); + } + + return waitResult; + } + private static bool SignalAndWait(WaitHandle toSignal, WaitHandle toWaitOn, int millisecondsTimeout) { if (toSignal == null) @@ -388,6 +427,8 @@ public static bool WaitAll(WaitHandle[] waitHandles, TimeSpan timeout, bool exit public static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout) => WaitMultiple(waitHandles, false, millisecondsTimeout); + internal static int WaitAny(ReadOnlySpan safeWaitHandles, int millisecondsTimeout) => + WaitAnyMultiple(safeWaitHandles, millisecondsTimeout); public static int WaitAny(WaitHandle[] waitHandles, TimeSpan timeout) => WaitMultiple(waitHandles, false, ToTimeoutMilliseconds(timeout)); public static int WaitAny(WaitHandle[] waitHandles) => From 6f9b3dc00a2302375250571785282b11b58b42c0 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sun, 24 May 2020 05:12:41 -0700 Subject: [PATCH 05/48] Separate portable-only portion of RegisteredWaitHandle --- .../System.Private.CoreLib.Shared.projitems | 1 + .../RegisteredWaitHandle.Portable.cs | 84 +++++++++++++++++++ .../src/System/Threading/ThreadPool.cs | 73 ---------------- 3 files changed, 85 insertions(+), 73 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 6eb5040f2a3f27..9ae131ba960a88 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1950,6 +1950,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs new file mode 100644 index 00000000000000..54585611700590 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.Threading +{ + public sealed partial class RegisteredWaitHandle : MarshalByRefObject + { + /// + /// The this was registered on. + /// + internal PortableThreadPool.WaitThread? WaitThread { get; set; } + + private bool UnregisterPortable(WaitHandle waitObject) + { + // The registered wait handle must have been registered by this time, otherwise the instance is not handed out to + // the caller of the public variants of RegisterWaitForSingleObject + Debug.Assert(WaitThread != null); + + s_callbackLock.Acquire(); + bool needToRollBackRefCountOnException = false; + try + { + if (_unregisterCalled) + { + return false; + } + + UserUnregisterWaitHandle = waitObject?.SafeWaitHandle; + UserUnregisterWaitHandle?.DangerousAddRef(ref needToRollBackRefCountOnException); + + UserUnregisterWaitHandleValue = UserUnregisterWaitHandle?.DangerousGetHandle() ?? IntPtr.Zero; + + if (_unregistered) + { + SignalUserWaitHandle(); + return true; + } + + if (IsBlocking) + { + _callbacksComplete = RentEvent(); + } + else + { + _removed = RentEvent(); + } + } + catch (Exception) // Rollback state on exception + { + if (_removed != null) + { + ReturnEvent(_removed); + _removed = null; + } + else if (_callbacksComplete != null) + { + ReturnEvent(_callbacksComplete); + _callbacksComplete = null; + } + + UserUnregisterWaitHandleValue = IntPtr.Zero; + + if (needToRollBackRefCountOnException) + { + UserUnregisterWaitHandle?.DangerousRelease(); + } + + UserUnregisterWaitHandle = null; + throw; + } + finally + { + _unregisterCalled = true; + s_callbackLock.Release(); + } + + WaitThread!.UnregisterWait(this); + return true; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index a7e64ab425a4ba..7f0af3029c6cb1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -1014,11 +1014,6 @@ internal void RestartTimeout(int currentTimeMs) internal bool IsBlocking => UserUnregisterWaitHandleValue == InvalidHandleValue; - /// - /// The this was registered on. - /// - internal PortableThreadPool.WaitThread? WaitThread { get; set; } - /// /// The number of callbacks that are currently queued on the Thread Pool or executing. /// @@ -1037,74 +1032,6 @@ internal void RestartTimeout(int currentTimeMs) private AutoResetEvent? _removed; - private bool UnregisterPortable(WaitHandle waitObject) - { - // The registered wait handle must have been registered by this time, otherwise the instance is not handed out to - // the caller of the public variants of RegisterWaitForSingleObject - Debug.Assert(WaitThread != null); - - s_callbackLock.Acquire(); - bool needToRollBackRefCountOnException = false; - try - { - if (_unregisterCalled) - { - return false; - } - - UserUnregisterWaitHandle = waitObject?.SafeWaitHandle; - UserUnregisterWaitHandle?.DangerousAddRef(ref needToRollBackRefCountOnException); - - UserUnregisterWaitHandleValue = UserUnregisterWaitHandle?.DangerousGetHandle() ?? IntPtr.Zero; - - if (_unregistered) - { - SignalUserWaitHandle(); - return true; - } - - if (IsBlocking) - { - _callbacksComplete = RentEvent(); - } - else - { - _removed = RentEvent(); - } - } - catch (Exception) // Rollback state on exception - { - if (_removed != null) - { - ReturnEvent(_removed); - _removed = null; - } - else if (_callbacksComplete != null) - { - ReturnEvent(_callbacksComplete); - _callbacksComplete = null; - } - - UserUnregisterWaitHandleValue = IntPtr.Zero; - - if (needToRollBackRefCountOnException) - { - UserUnregisterWaitHandle?.DangerousRelease(); - } - - UserUnregisterWaitHandle = null; - throw; - } - finally - { - _unregisterCalled = true; - s_callbackLock.Release(); - } - - WaitThread!.UnregisterWait(this); - return true; - } - /// /// Signal if it has not been signaled yet and is a valid handle. /// From d792cd74a5d217c64eb98c1cdb5bef273177506d Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sat, 14 Mar 2020 18:51:37 -0700 Subject: [PATCH 06/48] Fix timers, tiered compilation, introduced time-sensitive work item queue to simulate coreclr behavior --- .../System/Threading/ThreadPool.CoreCLR.cs | 43 ++-- src/coreclr/src/vm/comthreadpool.cpp | 12 + src/coreclr/src/vm/comthreadpool.h | 2 + src/coreclr/src/vm/corelib.h | 3 + src/coreclr/src/vm/ecalllist.h | 6 +- src/coreclr/src/vm/tieredcompilation.cpp | 50 ++++ src/coreclr/src/vm/tieredcompilation.h | 4 +- src/coreclr/src/vm/win32threadpool.cpp | 22 +- .../System/Threading/PortableThreadPool.cs | 6 +- .../System/Threading/ThreadPool.Portable.cs | 9 +- .../src/System/Threading/ThreadPool.cs | 219 ++++++++++++++---- .../System/Threading/TimerQueue.Portable.cs | 2 +- 12 files changed, 296 insertions(+), 82 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index 34fcb7bb28d22c..bc155a61b01873 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -134,27 +134,30 @@ public bool Unregister(WaitHandle waitObject) private static extern bool UnregisterWaitNative(IntPtr handle, SafeHandle? waitObject); } - public static partial class ThreadPool + internal sealed class UnmanagedThreadPoolWorkItem : IThreadPoolWorkItem { - // Time in ms for which ThreadPoolWorkQueue.Dispatch keeps executing work items before returning to the OS - private const uint DispatchQuantum = 30; + private readonly IntPtr _callback; + private readonly IntPtr _state; + + public UnmanagedThreadPoolWorkItem(IntPtr callback, IntPtr state) + { + _callback = callback; + _state = state; + } + + void IThreadPoolWorkItem.Execute() => ExecuteUnmanagedThreadPoolWorkItem(_callback, _state); + [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)] + private static extern void ExecuteUnmanagedThreadPoolWorkItem(IntPtr callback, IntPtr state); + } + + public static partial class ThreadPool + { internal static readonly bool UsePortableThreadPool = InitializeConfigAndDetermineUsePortableThreadPool(); + internal static bool SupportsTimeSensitiveWorkItems => UsePortableThreadPool; internal static readonly bool EnableWorkerTracking = GetEnableWorkerTracking(); - internal static bool KeepDispatching(int startTickCount) - { - if (UsePortableThreadPool) - { - return true; - } - - // Note: this function may incorrectly return false due to TickCount overflow - // if work item execution took around a multiple of 2^32 milliseconds (~49.7 days), - // which is improbable. - return (uint)(Environment.TickCount - startTickCount) < DispatchQuantum; - } private static unsafe bool InitializeConfigAndDetermineUsePortableThreadPool() { @@ -346,6 +349,13 @@ internal static void RequestWorkerThread() public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) => PostQueuedCompletionStatus(overlapped); + // Entry point from unmanaged code + private static void UnsafeQueueUnmanagedWorkItem(IntPtr callback, IntPtr state) + { + Debug.Assert(SupportsTimeSensitiveWorkItems); + UnsafeQueueTimeSensitiveWorkItemInternal(new UnmanagedThreadPoolWorkItem(callback, state)); + } + // Native methods: [MethodImpl(MethodImplOptions.InternalCall)] @@ -394,7 +404,7 @@ internal static void NotifyWorkItemProgress() { if (UsePortableThreadPool) { - PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(); + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); return; } @@ -446,4 +456,5 @@ public static bool BindHandle(SafeHandle osHandle) [MethodImpl(MethodImplOptions.InternalCall)] private static extern bool BindIOCompletionCallbackNative(IntPtr fileHandle); } + } diff --git a/src/coreclr/src/vm/comthreadpool.cpp b/src/coreclr/src/vm/comthreadpool.cpp index ab6611e14b49ad..786a5a196811d9 100644 --- a/src/coreclr/src/vm/comthreadpool.cpp +++ b/src/coreclr/src/vm/comthreadpool.cpp @@ -613,6 +613,18 @@ FCIMPLEND /********************************************************************************************************************/ +void QCALLTYPE ThreadPoolNative::ExecuteUnmanagedThreadPoolWorkItem(LPTHREAD_START_ROUTINE callback, LPVOID state) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + _ASSERTE(ThreadpoolMgr::UsePortableThreadPool()); + callback(state); + + END_QCALL; +} + /********************************************************************************************************************/ struct BindIoCompletion_Args diff --git a/src/coreclr/src/vm/comthreadpool.h b/src/coreclr/src/vm/comthreadpool.h index 1d100faf26bb73..513621e18518e9 100644 --- a/src/coreclr/src/vm/comthreadpool.h +++ b/src/coreclr/src/vm/comthreadpool.h @@ -57,6 +57,8 @@ class ThreadPoolNative static FCDECL2(FC_BOOL_RET, CorUnregisterWait, LPVOID WaitHandle, Object * objectToNotify); static FCDECL1(void, CorWaitHandleCleanupNative, LPVOID WaitHandle); static FCDECL1(FC_BOOL_RET, CorBindIoCompletionCallback, HANDLE fileHandle); + + static void QCALLTYPE ExecuteUnmanagedThreadPoolWorkItem(LPTHREAD_START_ROUTINE callback, LPVOID state); }; class AppDomainTimerNative diff --git a/src/coreclr/src/vm/corelib.h b/src/coreclr/src/vm/corelib.h index bd5a8a626d5548..1711460adeb072 100644 --- a/src/coreclr/src/vm/corelib.h +++ b/src/coreclr/src/vm/corelib.h @@ -905,6 +905,9 @@ DEFINE_METHOD(TP_WAIT_CALLBACK, PERFORM_WAIT_CALLBACK, Perf DEFINE_CLASS(TIMER_QUEUE, Threading, TimerQueue) DEFINE_METHOD(TIMER_QUEUE, APPDOMAIN_TIMER_CALLBACK, AppDomainTimerCallback, SM_Int_RetVoid) +DEFINE_CLASS(THREAD_POOL, Threading, ThreadPool) +DEFINE_METHOD(THREAD_POOL, UNSAFE_QUEUE_UNMANAGED_WORK_ITEM, UnsafeQueueUnmanagedWorkItem, SM_IntPtr_IntPtr_RetVoid) + DEFINE_CLASS(TIMESPAN, System, TimeSpan) diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h index c5588c2a943cd3..877a55faa48998 100644 --- a/src/coreclr/src/vm/ecalllist.h +++ b/src/coreclr/src/vm/ecalllist.h @@ -656,12 +656,15 @@ FCFuncStart(gTimerFuncs) QCFuncElement("DeleteAppDomainTimer", AppDomainTimerNative::DeleteAppDomainTimer) FCFuncEnd() - FCFuncStart(gRegisteredWaitHandleFuncs) FCFuncElement("UnregisterWaitNative", ThreadPoolNative::CorUnregisterWait) FCFuncElement("WaitHandleCleanupNative", ThreadPoolNative::CorWaitHandleCleanupNative) FCFuncEnd() +FCFuncStart(gUnmanagedThreadPoolWorkItemFuncs) + QCFuncElement("ExecuteUnmanagedThreadPoolWorkItem", ThreadPoolNative::ExecuteUnmanagedThreadPoolWorkItem) +FCFuncEnd() + FCFuncStart(gWaitHandleFuncs) FCFuncElement("WaitOneCore", WaitHandleNative::CorWaitOneNative) FCFuncElement("WaitMultipleIgnoringSyncContext", WaitHandleNative::CorWaitMultipleNative) @@ -1214,6 +1217,7 @@ FCClassElement("TypeBuilder", "System.Reflection.Emit", gCOMClassWriter) FCClassElement("TypeLoadException", "System", gTypeLoadExceptionFuncs) FCClassElement("TypeNameParser", "System", gTypeNameParser) FCClassElement("TypedReference", "System", gTypedReferenceFuncs) +FCClassElement("UnmanagedThreadPoolWorkItem", "System.Threading", gUnmanagedThreadPoolWorkItemFuncs) #ifdef FEATURE_UTF8STRING FCClassElement("Utf8String", "System", gUtf8StringFuncs) #endif // FEATURE_UTF8STRING diff --git a/src/coreclr/src/vm/tieredcompilation.cpp b/src/coreclr/src/vm/tieredcompilation.cpp index 05fc6735ff7417..29ce210b9d3a55 100644 --- a/src/coreclr/src/vm/tieredcompilation.cpp +++ b/src/coreclr/src/vm/tieredcompilation.cpp @@ -93,6 +93,7 @@ TieredCompilationManager::TieredCompilationManager() : m_countOfNewMethodsCalledDuringDelay(0), m_methodsPendingCountingForTier1(nullptr), m_tieringDelayTimerHandle(nullptr), + m_doBackgroundWorkTimerHandle(nullptr), m_isBackgroundWorkScheduled(false), m_tier1CallCountingCandidateMethodRecentlyRecorded(false), m_isPendingCallCountingCompletion(false), @@ -566,17 +567,65 @@ void TieredCompilationManager::RequestBackgroundWork() WRAPPER_NO_CONTRACT; _ASSERTE(m_isBackgroundWorkScheduled); + if (ThreadpoolMgr::UsePortableThreadPool()) + { + // QueueUserWorkItem is not intended to be supported in this mode, and there are call sites of this function where + // managed code cannot be called instead to queue a work item. Use a timer with zero due time instead, which would on + // the timer thread call into managed code to queue a work item. + + NewHolder timerContextHolder = new ThreadpoolMgr::TimerInfoContext(); + timerContextHolder->TimerId = 0; + + _ASSERTE(m_doBackgroundWorkTimerHandle == nullptr); + if (!ThreadpoolMgr::CreateTimerQueueTimer( + &m_doBackgroundWorkTimerHandle, + DoBackgroundWorkTimerCallback, + timerContextHolder, + 0 /* DueTime */, + (DWORD)-1 /* Period, non-repeating */, + 0 /* Flags */)) + { + _ASSERTE(m_doBackgroundWorkTimerHandle == nullptr); + ThrowOutOfMemory(); + } + + timerContextHolder.SuppressRelease(); // the timer context is automatically deleted by the timer infrastructure + return; + } + if (!ThreadpoolMgr::QueueUserWorkItem(StaticBackgroundWorkCallback, this, QUEUE_ONLY, TRUE)) { ThrowOutOfMemory(); } } +void WINAPI TieredCompilationManager::DoBackgroundWorkTimerCallback(PVOID parameter, BOOLEAN timerFired) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + _ASSERTE(ThreadpoolMgr::UsePortableThreadPool()); + _ASSERTE(timerFired); + + TieredCompilationManager *pTieredCompilationManager = GetAppDomain()->GetTieredCompilationManager(); + _ASSERTE(pTieredCompilationManager->m_doBackgroundWorkTimerHandle != nullptr); + ThreadpoolMgr::DeleteTimerQueueTimer(pTieredCompilationManager->m_doBackgroundWorkTimerHandle, nullptr); + pTieredCompilationManager->m_doBackgroundWorkTimerHandle = nullptr; + + pTieredCompilationManager->DoBackgroundWork(); +} + // This is the initial entrypoint for the background thread, called by // the threadpool. DWORD WINAPI TieredCompilationManager::StaticBackgroundWorkCallback(void *args) { STANDARD_VM_CONTRACT; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); TieredCompilationManager * pTieredCompilationManager = (TieredCompilationManager *)args; pTieredCompilationManager->DoBackgroundWork(); @@ -590,6 +639,7 @@ DWORD WINAPI TieredCompilationManager::StaticBackgroundWorkCallback(void *args) void TieredCompilationManager::DoBackgroundWork() { WRAPPER_NO_CONTRACT; + _ASSERTE(m_doBackgroundWorkTimerHandle == nullptr); AutoResetIsBackgroundWorkScheduled autoResetIsBackgroundWorkScheduled(this); diff --git a/src/coreclr/src/vm/tieredcompilation.h b/src/coreclr/src/vm/tieredcompilation.h index b3f32a67f15bdd..87ed9364172e07 100644 --- a/src/coreclr/src/vm/tieredcompilation.h +++ b/src/coreclr/src/vm/tieredcompilation.h @@ -57,6 +57,7 @@ class TieredCompilationManager void ScheduleBackgroundWork(); private: void RequestBackgroundWork(); + static void WINAPI DoBackgroundWorkTimerCallback(PVOID parameter, BOOLEAN timerFired); static DWORD StaticBackgroundWorkCallback(void* args); void DoBackgroundWork(); @@ -109,13 +110,12 @@ class TieredCompilationManager UINT32 m_countOfNewMethodsCalledDuringDelay; SArray* m_methodsPendingCountingForTier1; HANDLE m_tieringDelayTimerHandle; + HANDLE m_doBackgroundWorkTimerHandle; bool m_isBackgroundWorkScheduled; bool m_tier1CallCountingCandidateMethodRecentlyRecorded; bool m_isPendingCallCountingCompletion; bool m_recentlyRequestedCallCountingCompletionAgain; - CLREvent m_asyncWorkDoneEvent; - #endif // FEATURE_TIERED_COMPILATION }; diff --git a/src/coreclr/src/vm/win32threadpool.cpp b/src/coreclr/src/vm/win32threadpool.cpp index 7bb39d93b0aa9e..9b61f2adcb5415 100644 --- a/src/coreclr/src/vm/win32threadpool.cpp +++ b/src/coreclr/src/vm/win32threadpool.cpp @@ -4492,6 +4492,7 @@ BOOL ThreadpoolMgr::CreateTimerQueueTimer(PHANDLE phNewTimer, if (!params.setupSucceeded) { CloseHandle(TimerThreadHandle); + *phNewTimer = NULL; return FALSE; } @@ -4503,8 +4504,6 @@ BOOL ThreadpoolMgr::CreateTimerQueueTimer(PHANDLE phNewTimer, NewHolder timerInfoHolder; TimerInfo * timerInfo = new (nothrow) TimerInfo; - *phNewTimer = (HANDLE) timerInfo; - if (NULL == timerInfo) ThrowOutOfMemory(); @@ -4519,9 +4518,12 @@ BOOL ThreadpoolMgr::CreateTimerQueueTimer(PHANDLE phNewTimer, timerInfo->ExternalCompletionEvent = INVALID_HANDLE; timerInfo->ExternalEventSafeHandle = NULL; + *phNewTimer = (HANDLE)timerInfo; + BOOL status = QueueUserAPC((PAPCFUNC)InsertNewTimer,TimerThread,(size_t)timerInfo); if (FALSE == status) { + *phNewTimer = NULL; return FALSE; } @@ -4706,9 +4708,19 @@ DWORD ThreadpoolMgr::FireTimers() InterlockedIncrement(&timerInfo->refCount); - QueueUserWorkItem(AsyncTimerCallbackCompletion, - timerInfo, - QUEUE_ONLY /* TimerInfo take care of deleting*/); + if (UsePortableThreadPool()) + { + GCX_COOP(); + + ARG_SLOT args[] = { PtrToArgSlot(AsyncTimerCallbackCompletion), PtrToArgSlot(timerInfo) }; + MethodDescCallSite(METHOD__THREAD_POOL__UNSAFE_QUEUE_UNMANAGED_WORK_ITEM).Call(args); + } + else + { + QueueUserWorkItem(AsyncTimerCallbackCompletion, + timerInfo, + QUEUE_ONLY /* TimerInfo take care of deleting*/); + } if (timerInfo->Period != 0 && timerInfo->Period != (ULONG)-1) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index de23cda5e60066..bdaf4621d645e1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -195,7 +195,7 @@ public int GetAvailableThreads() public int ThreadCount => ThreadCounts.VolatileReadCounts(ref _separated.counts).numExistingThreads; public long CompletedWorkItemCount => _completionCounter.Count; - internal bool NotifyWorkItemComplete() + internal void NotifyWorkItemProgress() { _completionCounter.Increment(); Volatile.Write(ref _separated.lastDequeueTime, Environment.TickCount); @@ -211,7 +211,11 @@ internal bool NotifyWorkItemComplete() _hillClimbingThreadAdjustmentLock.Release(); } } + } + internal bool NotifyWorkItemComplete() + { + NotifyWorkItemProgress(); return !WorkerThread.ShouldStopProcessingWorkNow(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index 1c89b57dbeae3a..d997ff1c3392a4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -25,9 +25,9 @@ public sealed partial class RegisteredWaitHandle : MarshalByRefObject public static partial class ThreadPool { + internal const bool SupportsTimeSensitiveWorkItems = true; internal const bool EnableWorkerTracking = false; - public static bool SetMaxThreads(int workerThreads, int completionPortThreads) { if (workerThreads < 0 || completionPortThreads < 0) @@ -90,14 +90,9 @@ internal static void RequestWorkerThread() PortableThreadPool.ThreadPoolInstance.RequestWorker(); } - internal static bool KeepDispatching(int startTickCount) - { - return true; - } - internal static void NotifyWorkItemProgress() { - PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(); + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); } internal static bool NotifyWorkItemComplete() diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index 7f0af3029c6cb1..337ed919d46945 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -382,6 +382,8 @@ public int Count internal bool loggingEnabled; internal readonly ConcurrentQueue workItems = new ConcurrentQueue(); // SOS's ThreadPool command depends on this name + internal readonly ConcurrentQueue? timeSensitiveWorkQueue = + ThreadPool.SupportsTimeSensitiveWorkItems ? new ConcurrentQueue() : null; private readonly Internal.PaddingFor32 pad1; @@ -447,12 +449,34 @@ internal void MarkThreadRequestSatisfied() } } + public void EnqueueTimeSensitiveWorkItem(IThreadPoolWorkItem timeSensitiveWorkItem) + { + Debug.Assert(ThreadPool.SupportsTimeSensitiveWorkItems); + + if (loggingEnabled) + { + FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(timeSensitiveWorkItem); + } + + timeSensitiveWorkQueue!.Enqueue(timeSensitiveWorkItem); + EnsureThreadRequested(); + } + + public IThreadPoolWorkItem? TryDequeueTimeSensitiveWorkItem() + { + Debug.Assert(ThreadPool.SupportsTimeSensitiveWorkItems); + + bool success = timeSensitiveWorkQueue!.TryDequeue(out IThreadPoolWorkItem? timeSensitiveWorkItem); + Debug.Assert(success == (timeSensitiveWorkItem != null)); + return timeSensitiveWorkItem; + } + public void Enqueue(object callback, bool forceGlobal) { Debug.Assert((callback is IThreadPoolWorkItem) ^ (callback is Task)); if (loggingEnabled && FrameworkEventSource.Log.IsEnabled()) - System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(callback); + FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(callback); ThreadPoolWorkQueueThreadLocals? tl = null; if (!forceGlobal) @@ -499,11 +523,19 @@ internal static bool LocalFindAndPop(object callback) callback = otherQueue.TrySteal(ref missedSteal); if (callback != null) { - break; + return callback; } } c--; } + + Debug.Assert(callback == null); + + // No work in the normal queues, check for time-sensitive work items + if (ThreadPool.SupportsTimeSensitiveWorkItems) + { + callback = TryDequeueTimeSensitiveWorkItem(); + } } return callback; @@ -522,7 +554,13 @@ public static long LocalCount } } - public long GlobalCount => workItems.Count; + public long GlobalCount => + (ThreadPool.SupportsTimeSensitiveWorkItems ? timeSensitiveWorkQueue!.Count : 0) + workItems.Count; + + // Time in ms for which ThreadPoolWorkQueue.Dispatch keeps executing normal work items before either returning from + // Dispatch (if SupportsTimeSensitiveWorkItems is false), or checking for and dispatching a time-sensitive work item + // before continuing with normal work items + private const uint DispatchQuantumMs = 30; /// /// Dispatches work items to this thread. @@ -535,11 +573,6 @@ internal static bool Dispatch() { ThreadPoolWorkQueue outerWorkQueue = ThreadPool.s_workQueue; - // - // Save the start time - // - int startTickCount = Environment.TickCount; - // // Update our records to indicate that an outstanding request for a thread has now been fulfilled. // From this point on, we are responsible for requesting another thread if we stop working for any @@ -572,32 +605,43 @@ internal static bool Dispatch() currentThread._executionContext = null; currentThread._synchronizationContext = null; + // + // Save the start time + // + int startTickCount = Environment.TickCount; + + object? workItem = null; + // // Loop until our quantum expires or there is no work. // - while (ThreadPool.KeepDispatching(startTickCount)) + while (true) { - bool missedSteal = false; - // Use operate on workItem local to try block so it can be enregistered - object? workItem = workQueue.Dequeue(tl, ref missedSteal); - if (workItem == null) { - // - // No work. - // If we missed a steal, though, there may be more work in the queue. - // Instead of looping around and trying again, we'll just request another thread. Hopefully the thread - // that owns the contended work-stealing queue will pick up its own workitems in the meantime, - // which will be more efficient than this thread doing it anyway. - // - needAnotherThread = missedSteal; - - // Tell the VM we're returning normally, not because Hill Climbing asked us to return. - return true; + bool missedSteal = false; + // Operate on 'workQueue' instead of 'outerWorkQueue', as 'workQueue' is local to the try block and it + // may be enregistered + workItem = workQueue.Dequeue(tl, ref missedSteal); + + if (workItem == null) + { + // + // No work. + // If we missed a steal, though, there may be more work in the queue. + // Instead of looping around and trying again, we'll just request another thread. Hopefully the thread + // that owns the contended work-stealing queue will pick up its own workitems in the meantime, + // which will be more efficient than this thread doing it anyway. + // + needAnotherThread = missedSteal; + + // Tell the VM we're returning normally, not because Hill Climbing asked us to return. + return true; + } } if (workQueue.loggingEnabled && FrameworkEventSource.Log.IsEnabled()) - System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolDequeueWorkObject(workItem); + FrameworkEventSource.Log.ThreadPoolDequeueWorkObject(workItem); // // If we found work, there may be more work. Ask for another thread so that the other work can be processed @@ -611,26 +655,7 @@ internal static bool Dispatch() #pragma warning disable CS0162 // Unreachable code detected. EnableWorkerTracking may be constant false in some runtimes. if (ThreadPool.EnableWorkerTracking) { - bool reportedStatus = false; - try - { - ThreadPool.ReportThreadStatus(isWorking: true); - reportedStatus = true; - if (workItem is Task task) - { - task.ExecuteFromThreadPool(currentThread); - } - else - { - Debug.Assert(workItem is IThreadPoolWorkItem); - Unsafe.As(workItem).Execute(); - } - } - finally - { - if (reportedStatus) - ThreadPool.ReportThreadStatus(isWorking: false); - } + DispatchWorkItemWithWorkerTracking(workItem, currentThread); } #pragma warning restore CS0162 else if (workItem is Task task) @@ -661,10 +686,37 @@ internal static bool Dispatch() // if (!ThreadPool.NotifyWorkItemComplete()) return false; - } - // If we get here, it's because our quantum expired. Tell the VM we're returning normally. - return true; + // Check if the dispatch quantum has expired + int currentTickCount = Environment.TickCount; + if ((uint)(currentTickCount - startTickCount) < DispatchQuantumMs) + { + continue; + } + + // The quantum expired, do any necessary periodic activities + +#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be constant true in some runtimes. + if (!ThreadPool.SupportsTimeSensitiveWorkItems) + { + // The runtime-specific thread pool implementation does not support managed time-sensitive work, need to + // return to the VM to let it perform its own time-sensitive work. Tell the VM we're returning normally. + return true; + } +#pragma warning restore CS0162 + + // This method will continue to dispatch work items. Refresh the start tick count for the next dispatch + // quantum and do some periodic activities. + startTickCount = currentTickCount; + + // Periodically refresh whether logging is enabled + workQueue.loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer); + + // Consistent with CoreCLR currently, only one time-sensitive work item is run periodically between quantums + // of time spent running work items in the normal thread pool queues, until the normal queues are depleted. + // These are basically lower-priority but time-sensitive work items. + workItem = workQueue.TryDequeueTimeSensitiveWorkItem(); + } } finally { @@ -676,6 +728,34 @@ internal static bool Dispatch() outerWorkQueue.EnsureThreadRequested(); } } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void DispatchWorkItemWithWorkerTracking(object workItem, Thread currentThread) + { + Debug.Assert(ThreadPoolGlobals.enableWorkerTracking); + Debug.Assert(currentThread == Thread.CurrentThread); + + bool reportedStatus = false; + try + { + ThreadPool.ReportThreadStatus(isWorking: true); + reportedStatus = true; + if (workItem is Task task) + { + task.ExecuteFromThreadPool(currentThread); + } + else + { + Debug.Assert(workItem is IThreadPoolWorkItem); + Unsafe.As(workItem).Execute(); + } + } + finally + { + if (reportedStatus) + ThreadPool.ReportThreadStatus(isWorking: false); + } + } } // Simple random number generator. We don't need great randomness, we just need a little and for it to be fast. @@ -1433,6 +1513,22 @@ internal static void UnsafeQueueUserWorkItemInternal(object callBack, bool prefe s_workQueue.Enqueue(callBack, forceGlobal: !preferLocal); } + internal static void UnsafeQueueTimeSensitiveWorkItem(IThreadPoolWorkItem timeSensitiveWorkItem) + { +#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be constant true in some runtimes. + if (SupportsTimeSensitiveWorkItems) + { + UnsafeQueueTimeSensitiveWorkItemInternal(timeSensitiveWorkItem); + return; + } + + UnsafeQueueUserWorkItemInternal(timeSensitiveWorkItem, preferLocal: false); +#pragma warning restore CS0162 + } + + internal static void UnsafeQueueTimeSensitiveWorkItemInternal(IThreadPoolWorkItem timeSensitiveWorkItem) => + s_workQueue.EnqueueTimeSensitiveWorkItem(timeSensitiveWorkItem); + // This method tries to take the target callback out of the current thread's queue. internal static bool TryPopCustomWorkItem(object workItem) { @@ -1443,6 +1539,15 @@ internal static bool TryPopCustomWorkItem(object workItem) // Get all workitems. Called by TaskScheduler in its debugger hooks. internal static IEnumerable GetQueuedWorkItems() { + if (ThreadPool.SupportsTimeSensitiveWorkItems) + { + // Enumerate time-sensitive work item queue + foreach (object workItem in s_workQueue.timeSensitiveWorkQueue!) + { + yield return workItem; + } + } + // Enumerate global queue foreach (object workItem in s_workQueue.workItems) { @@ -1482,7 +1587,23 @@ internal static IEnumerable GetLocallyQueuedWorkItems() } } - internal static IEnumerable GetGloballyQueuedWorkItems() => s_workQueue.workItems; + internal static IEnumerable GetGloballyQueuedWorkItems() + { + if (ThreadPool.SupportsTimeSensitiveWorkItems) + { + // Enumerate time-sensitive work item queue + foreach (object workItem in s_workQueue.timeSensitiveWorkQueue!) + { + yield return workItem; + } + } + + // Enumerate global queue + foreach (object workItem in s_workQueue.workItems) + { + yield return workItem; + } + } private static object[] ToObjectArray(IEnumerable workitems) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs index 72afc1f40e6fb4..39fd7387917a2c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs @@ -121,7 +121,7 @@ private static void TimerThread() { foreach (TimerQueue timerToFire in timersToFire) { - ThreadPool.UnsafeQueueUserWorkItemInternal(timerToFire, preferLocal: false); + ThreadPool.UnsafeQueueTimeSensitiveWorkItem(timerToFire); } timersToFire.Clear(); } From a911883810f3d37edd57ce20a898d5e96623ec42 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sun, 15 Mar 2020 04:40:31 -0700 Subject: [PATCH 07/48] Implement ResetThreadPoolThread, set thread names for diagnostics --- .../src/System/Threading/Thread.CoreCLR.cs | 39 ++++++++++++++-- src/coreclr/src/vm/object.h | 5 ++- .../PortableThreadPool.GateThread.cs | 2 + .../PortableThreadPool.WaitThread.cs | 2 + .../PortableThreadPool.WorkerThread.cs | 2 + .../src/System/Threading/Thread.cs | 45 ++++++++++++++++++- .../System/Threading/ThreadPool.Portable.cs | 15 +++++++ .../src/System/Threading/ThreadPool.cs | 10 +++-- src/mono/mono/metadata/threads.c | 7 ++- .../src/System/Threading/Thread.Mono.cs | 25 ++++++----- 10 files changed, 130 insertions(+), 22 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index 69e4213b59cd2e..878f74d0472838 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -144,6 +144,11 @@ public sealed partial class Thread private int _managedThreadId; // INT32 #pragma warning restore CA1823, 169 + // This is used for a quick check on thread pool threads after running a work item to determine if the name, background + // state, or priority were changed by the work item, and if so to reset it. Other threads may also change some of those, + // but those types of changes may race with the reset anyway, so this field doesn't need to be synchronized. + private bool _mayNeedResetForThreadPool; + private Thread() { } private void Create(ThreadStart start) => @@ -340,7 +345,14 @@ public extern bool IsAlive public bool IsBackground { get => IsBackgroundNative(); - set => SetBackgroundNative(value); + set + { + SetBackgroundNative(value); + if (!value && !_mayNeedResetForThreadPool) + { + _mayNeedResetForThreadPool = value; + } + } } [MethodImpl(MethodImplOptions.InternalCall)] @@ -362,7 +374,14 @@ public extern bool IsThreadPoolThread public ThreadPriority Priority { get => (ThreadPriority)GetPriorityNative(); - set => SetPriorityNative((int)value); + set + { + SetPriorityNative((int)value); + if (value != ThreadPriority.Normal && !_mayNeedResetForThreadPool) + { + _mayNeedResetForThreadPool = true; + } + } } [MethodImpl(MethodImplOptions.InternalCall)] @@ -511,8 +530,20 @@ public static int GetCurrentProcessorId() #pragma warning disable CA1822 // Mark members as static internal void ResetThreadPoolThread() { - // Currently implemented in unmanaged method Thread::InternalReset and - // called internally from the ThreadPool in NotifyWorkItemComplete. + Debug.Assert(this == CurrentThread); + Debug.Assert(IsThreadPoolThread); + + if (!ThreadPool.UsePortableThreadPool) + { + // Currently implemented in unmanaged method Thread::InternalReset and + // called internally from the ThreadPool in NotifyWorkItemComplete. + return; + } + + if (_mayNeedResetForThreadPool) + { + ResetThreadPoolThreadSlow(); + } } #pragma warning restore CA1822 } // End of class Thread diff --git a/src/coreclr/src/vm/object.h b/src/coreclr/src/vm/object.h index bcfe1be3e95221..0604d6583401cd 100644 --- a/src/coreclr/src/vm/object.h +++ b/src/coreclr/src/vm/object.h @@ -1371,9 +1371,12 @@ class ThreadBaseObject : public Object Thread *m_InternalThread; INT32 m_Priority; - //We need to cache the thread id in managed code for perf reasons. + // We need to cache the thread id in managed code for perf reasons. INT32 m_ManagedThreadId; + // Only used by managed code, see comment there + bool m_MayNeedResetForThreadPool; + protected: // the ctor and dtor can do no useful work. ThreadBaseObject() {LIMITED_METHOD_CONTRACT;}; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index bcdaa2fbcc43a7..e0e5a78705ae19 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -133,7 +133,9 @@ private static int GetRunningStateForNumRuns(int numRuns) private static void CreateGateThread() { Thread gateThread = new Thread(GateThreadStart); + gateThread.IsThreadPoolThread = true; gateThread.IsBackground = true; + gateThread.Name = ".NET ThreadPool Gate"; gateThread.Start(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs index 267b585f17c3fd..1672a9a8865b8f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs @@ -169,7 +169,9 @@ public WaitThread() { _waitHandles[0] = _changeHandlesEvent.SafeWaitHandle; Thread waitThread = new Thread(WaitThreadStart); + waitThread.IsThreadPoolThread = true; waitThread.IsBackground = true; + waitThread.Name = ".NET ThreadPool Wait"; waitThread.Start(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs index 2cedaa5e3bc56b..266dd5d3e8227c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @@ -25,6 +25,8 @@ private static int SemaphoreSpinCount private static void WorkerThreadStart() { + Thread.CurrentThread.SetThreadPoolWorkerThreadName(); + PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; if (log.IsEnabled()) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs index bf85ffe8d78be2..2c175f2de648c6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Security.Principal; using System.Runtime.Versioning; @@ -171,12 +172,54 @@ public string? Name } _name = value; - ThreadNameChanged(value); + if (value != null && !_mayNeedResetForThreadPool) + { + _mayNeedResetForThreadPool = true; + } } } } + internal void SetThreadPoolWorkerThreadName() + { + Debug.Assert(this == CurrentThread); + Debug.Assert(IsThreadPoolThread); + + lock (this) + { + // Bypass the exception from setting the property + _name = ThreadPool.WorkerThreadName; + ThreadNameChanged(ThreadPool.WorkerThreadName); + _name = null; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void ResetThreadPoolThreadSlow() + { + Debug.Assert(this == CurrentThread); + Debug.Assert(IsThreadPoolThread); + Debug.Assert(_mayNeedResetForThreadPool); + + _mayNeedResetForThreadPool = false; + + if (_name != null) + { + SetThreadPoolWorkerThreadName(); + } + + if (!IsBackground) + { + IsBackground = true; + } + + if (Priority != ThreadPriority.Normal) + { + Priority = ThreadPriority.Normal; + } + } + [Obsolete(Obsoletions.ThreadAbortMessage, DiagnosticId = Obsoletions.ThreadAbortDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public void Abort() { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index d997ff1c3392a4..2e6edb65d4c897 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -9,6 +9,21 @@ namespace System.Threading // Portable implementation of ThreadPool // + public sealed partial class Thread + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ResetThreadPoolThread() + { + Debug.Assert(this == CurrentThread); + Debug.Assert(IsThreadPoolThread); + + if (_mayNeedResetForThreadPool) + { + ResetThreadPoolThreadSlow(); + } + } + } + public sealed partial class RegisteredWaitHandle : MarshalByRefObject { /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index 337ed919d46945..3faf1309a3ed24 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -672,14 +672,16 @@ internal static bool Dispatch() Unsafe.As(workItem).Execute(); } - currentThread.ResetThreadPoolThread(); - // Release refs workItem = null; - // Return to clean ExecutionContext and SynchronizationContext + // Return to clean ExecutionContext and SynchronizationContext. This may call user code (AsyncLocal value + // change notifications). ExecutionContext.ResetThreadPoolThread(currentThread); + // Reset thread state after all user code for the work item has completed + currentThread.ResetThreadPoolThread(); + // // Notify the VM that we executed this workitem. This is also our opportunity to ask whether Hill Climbing wants // us to return the thread to the pool or not. @@ -1244,6 +1246,8 @@ internal void WaitForRemoval() public static partial class ThreadPool { + internal const string WorkerThreadName = ".NET ThreadPool Worker"; + internal static readonly ThreadPoolWorkQueue s_workQueue = new ThreadPoolWorkQueue(); /// Shim used to invoke of the supplied . diff --git a/src/mono/mono/metadata/threads.c b/src/mono/mono/metadata/threads.c index fe3de4d5ccc368..76003c05ba0716 100644 --- a/src/mono/mono/metadata/threads.c +++ b/src/mono/mono/metadata/threads.c @@ -2134,8 +2134,13 @@ ves_icall_System_Threading_Thread_SetName_icall (MonoInternalThreadHandle thread char* name8 = name16 ? g_utf16_to_utf8 (name16, name16_length, NULL, &name8_length, NULL) : NULL; + // The managed thread implementation prevents the Name property from being set multiple times on normal threads. On thread + // pool threads, for compatibility the thread's name should be changeable and this function may be called to force-reset the + // thread's name if user code had changed it. So for the flags, MonoSetThreadNameFlag_Reset is passed instead of + // MonoSetThreadNameFlag_Permanent for all threads, relying on the managed side to prevent multiple changes where + // appropriate. mono_thread_set_name (mono_internal_thread_handle_ptr (thread_handle), - name8, (gint32)name8_length, name16, MonoSetThreadNameFlag_Permanent, error); + name8, (gint32)name8_length, name16, MonoSetThreadNameFlag_Reset, error); } #ifndef ENABLE_NETCORE diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs index 05fda86297391e..5a70c8d181956b 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs @@ -79,6 +79,11 @@ keep as an object to avoid triggering its class constructor when not needed */ internal ExecutionContext? _executionContext; internal SynchronizationContext? _synchronizationContext; + // This is used for a quick check on thread pool threads after running a work item to determine if the name, background + // state, or priority were changed by the work item, and if so to reset it. Other threads may also change some of those, + // but those types of changes may race with the reset anyway, so this field doesn't need to be synchronized. + private bool _mayNeedResetForThreadPool; + private Thread() { InitInternal(this); @@ -123,6 +128,10 @@ public bool IsBackground else { ClrState(this, ThreadState.Background); + if (!_mayNeedResetForThreadPool) + { + _mayNeedResetForThreadPool = value; + } } } } @@ -162,6 +171,10 @@ public ThreadPriority Priority { // TODO: arguments check SetPriority(this, (int)value); + if (value != ThreadPriority.Normal && !_mayNeedResetForThreadPool) + { + _mayNeedResetForThreadPool = true; + } } } @@ -207,18 +220,6 @@ public bool Join(int millisecondsTimeout) return JoinInternal(this, millisecondsTimeout); } - internal void ResetThreadPoolThread() - { - if (_name != null) - Name = null; - - if ((state & ThreadState.Background) == 0) - IsBackground = true; - - if ((ThreadPriority)priority != ThreadPriority.Normal) - Priority = ThreadPriority.Normal; - } - private void SetCultureOnUnstartedThreadNoCheck(CultureInfo value, bool uiCulture) { if (uiCulture) From 40ce9d022b503fb776b1e41e74ddbfcec6430bd0 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 16 Mar 2020 10:51:25 -0700 Subject: [PATCH 08/48] Cache-line-separate PortableThreadPool._numRequestedWorkers similarly to coreclr --- .../PortableThreadPool.GateThread.cs | 4 +-- .../PortableThreadPool.WorkerThread.cs | 6 ++--- .../System/Threading/PortableThreadPool.cs | 25 ++++++++----------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index e0e5a78705ae19..32b3846221d68f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -39,7 +39,7 @@ private static void GateThreadStart() if (!disableStarvationDetection) { - if (ThreadPoolInstance._numRequestedWorkers > 0 && SufficientDelaySinceLastDequeue()) + if (ThreadPoolInstance._separated.numRequestedWorkers > 0 && SufficientDelaySinceLastDequeue()) { try { @@ -71,7 +71,7 @@ private static void GateThreadStart() } } } - } while (ThreadPoolInstance._numRequestedWorkers > 0 || Interlocked.Decrement(ref s_runningState) > GetRunningStateForNumRuns(0)); + } while (ThreadPoolInstance._separated.numRequestedWorkers > 0 || Interlocked.Decrement(ref s_runningState) > GetRunningStateForNumRuns(0)); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs index 266dd5d3e8227c..65b0a5926cdd05 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @@ -129,7 +129,7 @@ private static void RemoveWorkingWorker() // but reduced the worker count *after* the request came in. In this case, we might // miss the notification of a thread request. So we wake up a thread (maybe this one!) // if there is work to do. - if (ThreadPoolInstance._numRequestedWorkers > 0) + if (ThreadPoolInstance._separated.numRequestedWorkers > 0) { MaybeAddWorkingWorker(); } @@ -233,10 +233,10 @@ internal static bool ShouldStopProcessingWorkNow() private static bool TakeActiveRequest() { - int count = ThreadPoolInstance._numRequestedWorkers; + int count = ThreadPoolInstance._separated.numRequestedWorkers; while (count > 0) { - int prevCount = Interlocked.CompareExchange(ref ThreadPoolInstance._numRequestedWorkers, count - 1, count); + int prevCount = Interlocked.CompareExchange(ref ThreadPoolInstance._separated.numRequestedWorkers, count - 1, count); if (prevCount == count) { return true; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index bdaf4621d645e1..40d1d4d666c9c8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -36,24 +36,21 @@ internal sealed partial class PortableThreadPool private short _maxThreads; private readonly LowLevelLock _maxMinThreadLock = new LowLevelLock(); - [StructLayout(LayoutKind.Explicit, Size = CacheLineSize * 5)] + [StructLayout(LayoutKind.Explicit, Size = Internal.PaddingHelpers.CACHE_LINE_SIZE * 5)] private struct CacheLineSeparated { -#if TARGET_ARM64 - private const int CacheLineSize = 128; -#else - private const int CacheLineSize = 64; -#endif - [FieldOffset(CacheLineSize * 1)] + [FieldOffset(Internal.PaddingHelpers.CACHE_LINE_SIZE * 1)] public ThreadCounts counts; - [FieldOffset(CacheLineSize * 2)] + [FieldOffset(Internal.PaddingHelpers.CACHE_LINE_SIZE * 2)] public int lastDequeueTime; - [FieldOffset(CacheLineSize * 3)] + [FieldOffset(Internal.PaddingHelpers.CACHE_LINE_SIZE * 3)] public int priorCompletionCount; - [FieldOffset(CacheLineSize * 3 + sizeof(int))] + [FieldOffset(Internal.PaddingHelpers.CACHE_LINE_SIZE * 3 + sizeof(int))] public int priorCompletedWorkRequestsTime; - [FieldOffset(CacheLineSize * 3 + sizeof(int) * 2)] + [FieldOffset(Internal.PaddingHelpers.CACHE_LINE_SIZE * 3 + sizeof(int) * 2)] public int nextCompletedWorkRequestsTime; + [FieldOffset(Internal.PaddingHelpers.CACHE_LINE_SIZE * 4)] + public volatile int numRequestedWorkers; } private CacheLineSeparated _separated; @@ -63,8 +60,6 @@ private struct CacheLineSeparated private readonly LowLevelLock _hillClimbingThreadAdjustmentLock = new LowLevelLock(); - private volatile int _numRequestedWorkers; - private PortableThreadPool() { _minThreads = s_forcedMinWorkerThreads > 0 ? s_forcedMinWorkerThreads : (short)Environment.ProcessorCount; @@ -115,7 +110,7 @@ public bool SetMinThreads(int minThreads) { counts = newCounts; - if (newCounts.numThreadsGoal > oldCounts.numThreadsGoal && _numRequestedWorkers > 0) + if (newCounts.numThreadsGoal > oldCounts.numThreadsGoal && _separated.numRequestedWorkers > 0) { WorkerThread.MaybeAddWorkingWorker(); } @@ -303,7 +298,7 @@ private bool ShouldAdjustMaxWorkersActive() internal void RequestWorker() { - Interlocked.Increment(ref _numRequestedWorkers); + Interlocked.Increment(ref _separated.numRequestedWorkers); WorkerThread.MaybeAddWorkingWorker(); GateThread.EnsureRunning(); } From a5d3c2bf4dbc62b2d548e4f49325fcf1384aef9f Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 16 Mar 2020 23:28:10 -0700 Subject: [PATCH 09/48] Post wait completions to the IO completion port on Windows for coreclr, similarly to before --- .../System/Threading/ThreadPool.CoreCLR.cs | 23 +++++- src/coreclr/src/vm/comthreadpool.cpp | 26 +++++++ src/coreclr/src/vm/comthreadpool.h | 4 +- src/coreclr/src/vm/corelib.h | 3 + src/coreclr/src/vm/ecalllist.h | 3 + src/coreclr/src/vm/win32threadpool.cpp | 70 +++++++++++++++++++ src/coreclr/src/vm/win32threadpool.h | 16 ++++- .../PortableThreadPool.WaitThread.cs | 29 +------- .../System/Threading/ThreadPool.Portable.cs | 3 + .../src/System/Threading/ThreadPool.cs | 17 +++++ 10 files changed, 165 insertions(+), 29 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index bc155a61b01873..09cf8d0777cf02 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -134,6 +134,12 @@ public bool Unregister(WaitHandle waitObject) private static extern bool UnregisterWaitNative(IntPtr handle, SafeHandle? waitObject); } + internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem + { + // Entry point from unmanaged code + private void CompleteWait() => ((IThreadPoolWorkItem)this).Execute(); + } + internal sealed class UnmanagedThreadPoolWorkItem : IThreadPoolWorkItem { private readonly IntPtr _callback; @@ -328,6 +334,22 @@ private static void RegisterWaitForSingleObjectCore(WaitHandle waitObject, Regis registeredWaitHandle.SetNativeRegisteredWaitHandle(nativeRegisteredWaitHandle); } + internal static void UnsafeQueueWaitCompletion(CompleteWaitThreadPoolWorkItem completeWaitWorkItem) + { + Debug.Assert(UsePortableThreadPool); + +#if TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows + QueueWaitCompletionNative(completeWaitWorkItem); +#else + UnsafeQueueUserWorkItemInternal(completeWaitWorkItem, preferLocal: false); +#endif + } + +#if TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void QueueWaitCompletionNative(CompleteWaitThreadPoolWorkItem completeWaitWorkItem); +#endif + internal static void RequestWorkerThread() { if (UsePortableThreadPool) @@ -456,5 +478,4 @@ public static bool BindHandle(SafeHandle osHandle) [MethodImpl(MethodImplOptions.InternalCall)] private static extern bool BindIOCompletionCallbackNative(IntPtr fileHandle); } - } diff --git a/src/coreclr/src/vm/comthreadpool.cpp b/src/coreclr/src/vm/comthreadpool.cpp index 786a5a196811d9..7043e54b9900f2 100644 --- a/src/coreclr/src/vm/comthreadpool.cpp +++ b/src/coreclr/src/vm/comthreadpool.cpp @@ -490,6 +490,32 @@ FCIMPL5(LPVOID, ThreadPoolNative::CorRegisterWaitForSingleObject, } FCIMPLEND +#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows +FCIMPL1(void, ThreadPoolNative::CorQueueWaitCompletion, Object* completeWaitWorkItemObjectUNSAFE) +{ + FCALL_CONTRACT; + _ASSERTE(ThreadpoolMgr::UsePortableThreadPool()); + + HANDLE completeWaitWorkItemHandle = NULL; + struct _gc + { + OBJECTREF completeWaitWorkItemObject; + } gc; + gc.completeWaitWorkItemObject = ObjectToOBJECTREF(completeWaitWorkItemObjectUNSAFE); + + HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); + + _ASSERTE(gc.completeWaitWorkItemObject != NULL); + + OBJECTHANDLE completeWaitWorkItemObjectHandle = GetAppDomain()->CreateHandle(gc.completeWaitWorkItemObject); + ThreadpoolMgr::PostQueuedCompletionStatus( + (LPOVERLAPPED)completeWaitWorkItemObjectHandle, + ThreadpoolMgr::ManagedWaitIOCompletionCallback); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND +#endif // TARGET_WINDOWS VOID QueueUserWorkItemManagedCallback(PVOID pArg) { diff --git a/src/coreclr/src/vm/comthreadpool.h b/src/coreclr/src/vm/comthreadpool.h index 513621e18518e9..5685852b726e1d 100644 --- a/src/coreclr/src/vm/comthreadpool.h +++ b/src/coreclr/src/vm/comthreadpool.h @@ -43,13 +43,15 @@ class ThreadPoolNative static FCDECL1(void, ReportThreadStatus, CLR_BOOL isWorking); - static FCDECL5(LPVOID, CorRegisterWaitForSingleObject, Object* waitObjectUNSAFE, Object* stateUNSAFE, UINT32 timeout, CLR_BOOL executeOnlyOnce, Object* registeredWaitObjectUNSAFE); +#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows + static FCDECL1(void, CorQueueWaitCompletion, Object* completeWaitWorkItemObjectUNSAFE); +#endif static BOOL QCALLTYPE RequestWorkerThread(); diff --git a/src/coreclr/src/vm/corelib.h b/src/coreclr/src/vm/corelib.h index 1711460adeb072..4aab6a8216e03a 100644 --- a/src/coreclr/src/vm/corelib.h +++ b/src/coreclr/src/vm/corelib.h @@ -908,6 +908,9 @@ DEFINE_METHOD(TIMER_QUEUE, APPDOMAIN_TIMER_CALLBACK, AppDomainTimerCall DEFINE_CLASS(THREAD_POOL, Threading, ThreadPool) DEFINE_METHOD(THREAD_POOL, UNSAFE_QUEUE_UNMANAGED_WORK_ITEM, UnsafeQueueUnmanagedWorkItem, SM_IntPtr_IntPtr_RetVoid) +DEFINE_CLASS(COMPLETE_WAIT_THREAD_POOL_WORK_ITEM, Threading, CompleteWaitThreadPoolWorkItem) +DEFINE_METHOD(COMPLETE_WAIT_THREAD_POOL_WORK_ITEM, COMPLETE_WAIT, CompleteWait, IM_RetVoid) + DEFINE_CLASS(TIMESPAN, System, TimeSpan) diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h index 877a55faa48998..ca00d1013f9c05 100644 --- a/src/coreclr/src/vm/ecalllist.h +++ b/src/coreclr/src/vm/ecalllist.h @@ -640,6 +640,9 @@ FCFuncStart(gThreadPoolFuncs) QCFuncElement("GetCompletedWorkItemCount", ThreadPoolNative::GetCompletedWorkItemCount) FCFuncElement("GetPendingUnmanagedWorkItemCount", ThreadPoolNative::GetPendingUnmanagedWorkItemCount) FCFuncElement("RegisterWaitForSingleObjectNative", ThreadPoolNative::CorRegisterWaitForSingleObject) +#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows + FCFuncElement("QueueWaitCompletionNative", ThreadPoolNative::CorQueueWaitCompletion) +#endif FCFuncElement("BindIOCompletionCallbackNative", ThreadPoolNative::CorBindIoCompletionCallback) FCFuncElement("SetMaxThreadsNative", ThreadPoolNative::CorSetMaxThreads) FCFuncElement("GetMaxThreadsNative", ThreadPoolNative::CorGetMaxThreads) diff --git a/src/coreclr/src/vm/win32threadpool.cpp b/src/coreclr/src/vm/win32threadpool.cpp index 9b61f2adcb5415..1aa425395b0bff 100644 --- a/src/coreclr/src/vm/win32threadpool.cpp +++ b/src/coreclr/src/vm/win32threadpool.cpp @@ -1200,6 +1200,76 @@ void ThreadpoolMgr::WaitIOCompletionCallback( DWORD ret = AsyncCallbackCompletion((PVOID)lpOverlapped); } +#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows + +void WINAPI ThreadpoolMgr::ManagedWaitIOCompletionCallback( + DWORD dwErrorCode, + DWORD dwNumberOfBytesTransfered, + LPOVERLAPPED lpOverlapped) +{ + Thread *pThread = GetThread(); + if (pThread == NULL) + { + ClrFlsSetThreadType(ThreadType_Threadpool_Worker); + pThread = SetupThreadNoThrow(); + if (pThread == NULL) + { + return; + } + } + + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + if (dwErrorCode != ERROR_SUCCESS) + { + return; + } + + _ASSERTE(lpOverlapped != NULL); + + { + GCX_COOP(); + ManagedThreadBase::ThreadPool(ManagedWaitIOCompletionCallback_Worker, lpOverlapped); + } + + Thread::IncrementIOThreadPoolCompletionCount(pThread); +} + +void ThreadpoolMgr::ManagedWaitIOCompletionCallback_Worker(LPVOID state) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + _ASSERTE(state != NULL); + + OBJECTHANDLE completeWaitWorkItemObjectHandle = (OBJECTHANDLE)state; + OBJECTREF completeWaitWorkItemObject = ObjectFromHandle(completeWaitWorkItemObjectHandle); + _ASSERTE(completeWaitWorkItemObject != NULL); + + GCPROTECT_BEGIN(completeWaitWorkItemObject); + + DestroyHandle(completeWaitWorkItemObjectHandle); + completeWaitWorkItemObjectHandle = NULL; + + ARG_SLOT args[] = { ObjToArgSlot(completeWaitWorkItemObject) }; + MethodDescCallSite(METHOD__COMPLETE_WAIT_THREAD_POOL_WORK_ITEM__COMPLETE_WAIT, &completeWaitWorkItemObject).Call(args); + + GCPROTECT_END(); +} + +#endif // TARGET_WINDOWS + #ifndef TARGET_UNIX // We need to make sure that the next jobs picked up by a completion port thread // is inserted into the queue after we start cleanup. The cleanup starts when a completion diff --git a/src/coreclr/src/vm/win32threadpool.h b/src/coreclr/src/vm/win32threadpool.h index 45a2e243da0452..0d0f9a00547ae1 100644 --- a/src/coreclr/src/vm/win32threadpool.h +++ b/src/coreclr/src/vm/win32threadpool.h @@ -294,6 +294,12 @@ class ThreadpoolMgr DWORD numBytesTransferred, LPOVERLAPPED lpOverlapped); +#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows + static void WINAPI ManagedWaitIOCompletionCallback(DWORD dwErrorCode, + DWORD dwNumberOfBytesTransfered, + LPOVERLAPPED lpOverlapped); +#endif + static VOID WINAPI CallbackForInitiateDrainageOfCompletionPortQueue( DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, @@ -354,7 +360,11 @@ class ThreadpoolMgr // We handle registered waits at a higher abstraction level return (Function == ThreadpoolMgr::CallbackForInitiateDrainageOfCompletionPortQueue || Function == ThreadpoolMgr::CallbackForContinueDrainageOfCompletionPortQueue - || Function == ThreadpoolMgr::WaitIOCompletionCallback); + || Function == ThreadpoolMgr::WaitIOCompletionCallback +#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows + || Function == ThreadpoolMgr::ManagedWaitIOCompletionCallback +#endif + ); } #endif @@ -909,6 +919,10 @@ class ThreadpoolMgr unsigned index, // array index BOOL waitTimedOut); +#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows + static void ManagedWaitIOCompletionCallback_Worker(LPVOID state); +#endif + static DWORD WINAPI WaitThreadStart(LPVOID lpArgs); static DWORD WINAPI AsyncCallbackCompletion(PVOID pArgs); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs index 1672a9a8865b8f..201fd4795e2ca2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs @@ -118,21 +118,6 @@ private class WaitThreadNode /// internal class WaitThread { - /// - /// The info for a completed wait on a specific . - /// - private struct CompletedWaitHandle - { - public CompletedWaitHandle(RegisteredWaitHandle completedHandle, bool timedOut) - { - CompletedHandle = completedHandle; - TimedOut = timedOut; - } - - public RegisteredWaitHandle CompletedHandle { get; } - public bool TimedOut { get; } - } - /// /// The wait handles registered on this wait thread. /// @@ -325,13 +310,14 @@ private void ProcessRemovals() } /// - /// Queue a call to on the ThreadPool. + /// Queue a call to complete the wait on the ThreadPool. /// /// The handle that completed. /// Whether or not the wait timed out. private void QueueWaitCompletion(RegisteredWaitHandle registeredHandle, bool timedOut) { registeredHandle.RequestCallback(); + // If the handle is a repeating handle, set up the next call. Otherwise, remove it from the wait thread. if (registeredHandle.Repeating) { @@ -341,17 +327,8 @@ private void QueueWaitCompletion(RegisteredWaitHandle registeredHandle, bool tim { UnregisterWait(registeredHandle, blocking: false); // We shouldn't block the wait thread on the unregistration. } - ThreadPool.QueueUserWorkItem(CompleteWait, new CompletedWaitHandle(registeredHandle, timedOut)); - } - /// - /// Process the completion of a user-registered wait (call the callback). - /// - /// A object representing the wait completion. - private void CompleteWait(object? state) - { - CompletedWaitHandle handle = (CompletedWaitHandle)state!; - handle.CompletedHandle.PerformCallback(handle.TimedOut); + ThreadPool.UnsafeQueueWaitCompletion(new CompleteWaitThreadPoolWorkItem(registeredHandle, timedOut)); } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index 2e6edb65d4c897..2d91699c65ec8d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -117,5 +117,8 @@ internal static bool NotifyWorkItemComplete() private static void RegisterWaitForSingleObjectCore(WaitHandle? waitObject, RegisteredWaitHandle registeredWaitHandle) => PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle); + + internal static void UnsafeQueueWaitCompletion(CompleteWaitThreadPoolWorkItem completeWaitWorkItem) => + UnsafeQueueUserWorkItemInternal(completeWaitWorkItem, preferLocal: false); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index 3faf1309a3ed24..44711cc1408b39 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -1244,6 +1244,23 @@ internal void WaitForRemoval() } } + /// + /// The info for a completed wait on a specific . + /// + internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem + { + private RegisteredWaitHandle _registeredWaitHandle; + private bool _timedOut; + + public CompleteWaitThreadPoolWorkItem(RegisteredWaitHandle registeredWaitHandle, bool timedOut) + { + _registeredWaitHandle = registeredWaitHandle; + _timedOut = timedOut; + } + + void IThreadPoolWorkItem.Execute() => _registeredWaitHandle.PerformCallback(_timedOut); + } + public static partial class ThreadPool { internal const string WorkerThreadName = ".NET ThreadPool Worker"; From ad632bed46a40a031d2eceeddc4244682d11ad80 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Wed, 18 Mar 2020 14:56:18 -0700 Subject: [PATCH 10/48] Reroute managed gate thread into unmanaged side to perform gate activites, don't use unmanaged gate thread --- .../System/Threading/ThreadPool.CoreCLR.cs | 21 ++ src/coreclr/src/vm/comthreadpool.cpp | 17 ++ src/coreclr/src/vm/comthreadpool.h | 1 + src/coreclr/src/vm/corelib.h | 1 + src/coreclr/src/vm/ecalllist.h | 1 + src/coreclr/src/vm/win32threadpool.cpp | 268 ++++++++++-------- src/coreclr/src/vm/win32threadpool.h | 3 +- .../PortableThreadPool.GateThread.cs | 75 ++--- .../System/Threading/ThreadPool.Portable.cs | 12 +- 9 files changed, 237 insertions(+), 162 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index 09cf8d0777cf02..c1391d7b6a4e77 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -364,6 +364,27 @@ internal static void RequestWorkerThread() [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)] private static extern Interop.BOOL RequestWorkerThreadNative(); + // Entry point from unmanaged code + private static void EnsureGateThreadRunning() + { + Debug.Assert(UsePortableThreadPool); + PortableThreadPool.EnsureGateThreadRunning(); + } + + /// + /// Called from the gate thread periodically to perform runtime-specific gate activities + /// + /// CPU utilization as a percentage since the last call + /// True if the runtime still needs to perform gate activities, false otherwise + internal static bool PerformRuntimeSpecificGateActivities(int cpuUtilization) + { + Debug.Assert(UsePortableThreadPool); + return PerformRuntimeSpecificGateActivitiesNative(cpuUtilization) != Interop.BOOL.FALSE; + } + + [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)] + private static extern Interop.BOOL PerformRuntimeSpecificGateActivitiesNative(int cpuUtilization); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern unsafe bool PostQueuedCompletionStatus(NativeOverlapped* overlapped); diff --git a/src/coreclr/src/vm/comthreadpool.cpp b/src/coreclr/src/vm/comthreadpool.cpp index 7043e54b9900f2..7bbf28f7d14523 100644 --- a/src/coreclr/src/vm/comthreadpool.cpp +++ b/src/coreclr/src/vm/comthreadpool.cpp @@ -564,6 +564,23 @@ BOOL QCALLTYPE ThreadPoolNative::RequestWorkerThread() return res; } +BOOL QCALLTYPE ThreadPoolNative::PerformGateActivities(INT32 cpuUtilization) +{ + QCALL_CONTRACT; + + bool needGateThread = false; + + BEGIN_QCALL; + + _ASSERTE(ThreadpoolMgr::UsePortableThreadPool()); + + ThreadpoolMgr::PerformGateActivities(cpuUtilization); + needGateThread = ThreadpoolMgr::NeedGateThreadForIOCompletions(); + + END_QCALL; + + return needGateThread; +} /********************************************************************************************************************/ diff --git a/src/coreclr/src/vm/comthreadpool.h b/src/coreclr/src/vm/comthreadpool.h index 5685852b726e1d..2583bf164a0110 100644 --- a/src/coreclr/src/vm/comthreadpool.h +++ b/src/coreclr/src/vm/comthreadpool.h @@ -54,6 +54,7 @@ class ThreadPoolNative #endif static BOOL QCALLTYPE RequestWorkerThread(); + static BOOL QCALLTYPE PerformGateActivities(INT32 cpuUtilization); static FCDECL1(FC_BOOL_RET, CorPostQueuedCompletionStatus, LPOVERLAPPED lpOverlapped); static FCDECL2(FC_BOOL_RET, CorUnregisterWait, LPVOID WaitHandle, Object * objectToNotify); diff --git a/src/coreclr/src/vm/corelib.h b/src/coreclr/src/vm/corelib.h index 4aab6a8216e03a..05cdb976b8be5d 100644 --- a/src/coreclr/src/vm/corelib.h +++ b/src/coreclr/src/vm/corelib.h @@ -906,6 +906,7 @@ DEFINE_CLASS(TIMER_QUEUE, Threading, TimerQueue) DEFINE_METHOD(TIMER_QUEUE, APPDOMAIN_TIMER_CALLBACK, AppDomainTimerCallback, SM_Int_RetVoid) DEFINE_CLASS(THREAD_POOL, Threading, ThreadPool) +DEFINE_METHOD(THREAD_POOL, ENSURE_GATE_THREAD_RUNNING, EnsureGateThreadRunning, SM_RetVoid) DEFINE_METHOD(THREAD_POOL, UNSAFE_QUEUE_UNMANAGED_WORK_ITEM, UnsafeQueueUnmanagedWorkItem, SM_IntPtr_IntPtr_RetVoid) DEFINE_CLASS(COMPLETE_WAIT_THREAD_POOL_WORK_ITEM, Threading, CompleteWaitThreadPoolWorkItem) diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h index ca00d1013f9c05..008c6d0974294d 100644 --- a/src/coreclr/src/vm/ecalllist.h +++ b/src/coreclr/src/vm/ecalllist.h @@ -651,6 +651,7 @@ FCFuncStart(gThreadPoolFuncs) FCFuncElement("GetEnableWorkerTracking", ThreadPoolNative::GetEnableWorkerTracking) FCFuncElement("ReportThreadStatusNative", ThreadPoolNative::ReportThreadStatus) QCFuncElement("RequestWorkerThreadNative", ThreadPoolNative::RequestWorkerThread) + QCFuncElement("PerformRuntimeSpecificGateActivitiesNative", ThreadPoolNative::PerformGateActivities) FCFuncEnd() FCFuncStart(gTimerFuncs) diff --git a/src/coreclr/src/vm/win32threadpool.cpp b/src/coreclr/src/vm/win32threadpool.cpp index 1aa425395b0bff..be3882f639ed93 100644 --- a/src/coreclr/src/vm/win32threadpool.cpp +++ b/src/coreclr/src/vm/win32threadpool.cpp @@ -91,7 +91,6 @@ SVAL_IMPL(LONG,ThreadpoolMgr,MinLimitTotalWorkerThreads); // = MaxLimit SVAL_IMPL(LONG,ThreadpoolMgr,MaxLimitTotalWorkerThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS SVAL_IMPL(LONG,ThreadpoolMgr,cpuUtilization); -LONG ThreadpoolMgr::cpuUtilizationAverage = 0; HillClimbing ThreadpoolMgr::HillClimbingInstance; @@ -1483,11 +1482,12 @@ void ThreadpoolMgr::EnsureGateThreadRunning() { LIMITED_METHOD_CONTRACT; - // TODO: PortableThreadPool - Uncomment after gate thread is rerouted - //if (!UsePortableThreadPool()) - //{ - // return; - //} + if (UsePortableThreadPool()) + { + GCX_COOP(); + MethodDescCallSite(METHOD__THREAD_POOL__ENSURE_GATE_THREAD_RUNNING).Call(NULL); + return; + } while (true) { @@ -1529,15 +1529,25 @@ void ThreadpoolMgr::EnsureGateThreadRunning() _ASSERTE(!"Invalid value of ThreadpoolMgr::GateThreadStatus"); } } - - return; } +bool ThreadpoolMgr::NeedGateThreadForIOCompletions() +{ + LIMITED_METHOD_CONTRACT; + + if (!InitCompletionPortThreadpool) + { + return false; + } + + ThreadCounter::Counts counts = CPThreadCounter.GetCleanCounts(); + return counts.NumActive <= counts.NumWorking; +} bool ThreadpoolMgr::ShouldGateThreadKeepRunning() { LIMITED_METHOD_CONTRACT; - + _ASSERTE(!UsePortableThreadPool()); _ASSERTE(GateThreadStatus == GATE_THREAD_STATUS_WAITING_FOR_REQUEST || GateThreadStatus == GATE_THREAD_STATUS_REQUESTED); @@ -1556,17 +1566,13 @@ bool ThreadpoolMgr::ShouldGateThreadKeepRunning() // Are there any free threads in the I/O completion pool? If there are, we don't need a gate thread. // This implies that whenever we decrement NumFreeCPThreads to 0, we need to call EnsureGateThreadRunning(). // - ThreadCounter::Counts counts = CPThreadCounter.GetCleanCounts(); - bool needGateThreadForCompletionPort = - InitCompletionPortThreadpool && - (counts.NumActive - counts.NumWorking) <= 0; + bool needGateThreadForCompletionPort = NeedGateThreadForIOCompletions(); // // Are there any work requests in any worker queue? If so, we need a gate thread. // This imples that whenever a work queue goes from empty to non-empty, we need to call EnsureGateThreadRunning(). // - bool needGateThreadForWorkerThreads = - !UsePortableThreadPool() && PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains(); + bool needGateThreadForWorkerThreads = PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains(); // // If worker tracking is enabled, we need to fire periodic ETW events with active worker counts. This is @@ -3215,8 +3221,7 @@ void ThreadpoolMgr::WaitHandleCleanup(HANDLE hWaitObject) BOOL ThreadpoolMgr::CreateGateThread() { LIMITED_METHOD_CONTRACT; - // TODO: PortableThreadPool - Uncomment after gate thread is rerouted - //_ASSERTE(!UsePortableThreadPool()); + _ASSERTE(!UsePortableThreadPool()); HANDLE threadHandle = Thread::CreateUtilityThread(Thread::StackSize_Small, GateThreadStart, NULL, W(".NET ThreadPool Gate")); @@ -4161,7 +4166,6 @@ class GateThreadTimer } }; - DWORD WINAPI ThreadpoolMgr::GateThreadStart(LPVOID lpArgs) { ClrFlsSetThreadType (ThreadType_Gate); @@ -4174,8 +4178,7 @@ DWORD WINAPI ThreadpoolMgr::GateThreadStart(LPVOID lpArgs) } CONTRACTL_END; - // TODO: PortableThreadPool - Uncomment after gate thread is rerouted - //_ASSERTE(!UsePortableThreadPool()); + _ASSERTE(!UsePortableThreadPool()); _ASSERTE(GateThreadStatus == GATE_THREAD_STATUS_REQUESTED); GateThreadTimer timer; @@ -4296,139 +4299,154 @@ DWORD WINAPI ThreadpoolMgr::GateThreadStart(LPVOID lpArgs) IgnoreNextSample = TRUE; } + PerformGateActivities(cpuUtilization); + } + while (ShouldGateThreadKeepRunning()); + + return 0; +} + +void ThreadpoolMgr::PerformGateActivities(int cpuUtilization) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + ThreadpoolMgr::cpuUtilization = cpuUtilization; + #ifndef TARGET_UNIX - // don't mess with CP thread pool settings if not initialized yet - if (InitCompletionPortThreadpool) - { - ThreadCounter::Counts oldCounts, newCounts; - oldCounts = CPThreadCounter.GetCleanCounts(); + // don't mess with CP thread pool settings if not initialized yet + if (InitCompletionPortThreadpool) + { + ThreadCounter::Counts oldCounts, newCounts; + oldCounts = CPThreadCounter.GetCleanCounts(); - if (oldCounts.NumActive == oldCounts.NumWorking && - oldCounts.NumRetired == 0 && - oldCounts.NumActive < MaxLimitTotalCPThreads && - !g_fCompletionPortDrainNeeded && - NumCPInfrastructureThreads == 0 && // infrastructure threads count as "to be free as needed" - !GCHeapUtilities::IsGCInProgress(TRUE)) + if (oldCounts.NumActive == oldCounts.NumWorking && + oldCounts.NumRetired == 0 && + oldCounts.NumActive < MaxLimitTotalCPThreads && + !g_fCompletionPortDrainNeeded && + NumCPInfrastructureThreads == 0 && // infrastructure threads count as "to be free as needed" + !GCHeapUtilities::IsGCInProgress(TRUE)) + { + BOOL status; + DWORD numBytes; + size_t key; + LPOVERLAPPED pOverlapped; + DWORD errorCode; + + errorCode = S_OK; + + status = GetQueuedCompletionStatus( + GlobalCompletionPort, + &numBytes, + (PULONG_PTR)&key, + &pOverlapped, + 0 // immediate return + ); + + if (status == 0) { - BOOL status; - DWORD numBytes; - size_t key; - LPOVERLAPPED pOverlapped; - DWORD errorCode; - - errorCode = S_OK; - - status = GetQueuedCompletionStatus( - GlobalCompletionPort, - &numBytes, - (PULONG_PTR)&key, - &pOverlapped, - 0 // immediate return - ); + errorCode = GetLastError(); + } - if (status == 0) - { - errorCode = GetLastError(); - } + if(pOverlapped == &overlappedForContinueCleanup) + { + // if we picked up a "Continue Drainage" notification DO NOT create a new CP thread + } + else + if (errorCode != WAIT_TIMEOUT) + { + QueuedStatus *CompletionStatus = NULL; - if(pOverlapped == &overlappedForContinueCleanup) - { - // if we picked up a "Continue Drainage" notification DO NOT create a new CP thread - } - else - if (errorCode != WAIT_TIMEOUT) + // loop, retrying until memory is allocated. Under such conditions the gate + // thread is not useful anyway, so I feel comfortable with this behavior + do { - QueuedStatus *CompletionStatus = NULL; - - // loop, retrying until memory is allocated. Under such conditions the gate - // thread is not useful anyway, so I feel comfortable with this behavior - do + // make sure to free mem later in thread + CompletionStatus = new (nothrow) QueuedStatus; + if (CompletionStatus == NULL) { - // make sure to free mem later in thread - CompletionStatus = new (nothrow) QueuedStatus; - if (CompletionStatus == NULL) - { - __SwitchToThread(GATE_THREAD_DELAY, CALLER_LIMITS_SPINNING); - } + __SwitchToThread(GATE_THREAD_DELAY, CALLER_LIMITS_SPINNING); } - while (CompletionStatus == NULL); + } + while (CompletionStatus == NULL); - CompletionStatus->numBytes = numBytes; - CompletionStatus->key = (PULONG_PTR)key; - CompletionStatus->pOverlapped = pOverlapped; - CompletionStatus->errorCode = errorCode; + CompletionStatus->numBytes = numBytes; + CompletionStatus->key = (PULONG_PTR)key; + CompletionStatus->pOverlapped = pOverlapped; + CompletionStatus->errorCode = errorCode; - // IOCP threads are created as "active" and "working" - while (true) - { - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - newCounts.NumActive++; - newCounts.NumWorking++; - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } + // IOCP threads are created as "active" and "working" + while (true) + { + // counts volatile read paired with CompareExchangeCounts loop set + oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); + newCounts = oldCounts; + newCounts.NumActive++; + newCounts.NumWorking++; + if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) + break; + } - // loop, retrying until thread is created. - while (!CreateCompletionPortThread((LPVOID)CompletionStatus)) - { - __SwitchToThread(GATE_THREAD_DELAY, CALLER_LIMITS_SPINNING); - } + // loop, retrying until thread is created. + while (!CreateCompletionPortThread((LPVOID)CompletionStatus)) + { + __SwitchToThread(GATE_THREAD_DELAY, CALLER_LIMITS_SPINNING); } } - else if (cpuUtilization < CpuUtilizationLow) + } + else if (cpuUtilization < CpuUtilizationLow) + { + // this could be an indication that threads might be getting blocked or there is no work + if (oldCounts.NumWorking == oldCounts.NumActive && // don't bump the limit if there are already free threads + oldCounts.NumRetired > 0) { - // this could be an indication that threads might be getting blocked or there is no work - if (oldCounts.NumWorking == oldCounts.NumActive && // don't bump the limit if there are already free threads - oldCounts.NumRetired > 0) - { - RetiredCPWakeupEvent->Set(); - } + RetiredCPWakeupEvent->Set(); } } + } #endif // !TARGET_UNIX - if (!UsePortableThreadPool() && - 0 == CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection)) + if (!UsePortableThreadPool() && + 0 == CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection)) + { + if (PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains() && SufficientDelaySinceLastDequeue()) { - if (PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains() && SufficientDelaySinceLastDequeue()) - { - DangerousNonHostedSpinLockHolder tal(&ThreadAdjustmentLock); + DangerousNonHostedSpinLockHolder tal(&ThreadAdjustmentLock); - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - while (counts.NumActive < MaxLimitTotalWorkerThreads && //don't add a thread if we're at the max - counts.NumActive >= counts.MaxWorking) //don't add a thread if we're already in the process of adding threads + ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); + while (counts.NumActive < MaxLimitTotalWorkerThreads && //don't add a thread if we're at the max + counts.NumActive >= counts.MaxWorking) //don't add a thread if we're already in the process of adding threads + { + bool breakIntoDebugger = (0 != CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_DebugBreakOnWorkerStarvation)); + if (breakIntoDebugger) { - bool breakIntoDebugger = (0 != CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_DebugBreakOnWorkerStarvation)); - if (breakIntoDebugger) - { - OutputDebugStringW(W("The CLR ThreadPool detected work queue starvation!")); - DebugBreak(); - } + OutputDebugStringW(W("The CLR ThreadPool detected work queue starvation!")); + DebugBreak(); + } - ThreadCounter::Counts newCounts = counts; - newCounts.MaxWorking = newCounts.NumActive + 1; + ThreadCounter::Counts newCounts = counts; + newCounts.MaxWorking = newCounts.NumActive + 1; - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - if (oldCounts == counts) - { - HillClimbingInstance.ForceChange(newCounts.MaxWorking, Starvation); - MaybeAddWorkingWorker(); - break; - } - else - { - counts = oldCounts; - } + ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); + if (oldCounts == counts) + { + HillClimbingInstance.ForceChange(newCounts.MaxWorking, Starvation); + MaybeAddWorkingWorker(); + break; + } + else + { + counts = oldCounts; } } } } - while (ShouldGateThreadKeepRunning()); - - return 0; } // called by logic to spawn a new completion port thread. diff --git a/src/coreclr/src/vm/win32threadpool.h b/src/coreclr/src/vm/win32threadpool.h index 0d0f9a00547ae1..c4a4d8e40eec01 100644 --- a/src/coreclr/src/vm/win32threadpool.h +++ b/src/coreclr/src/vm/win32threadpool.h @@ -986,8 +986,10 @@ class ThreadpoolMgr static BOOL CreateGateThread(); static void EnsureGateThreadRunning(); + static bool NeedGateThreadForIOCompletions(); static bool ShouldGateThreadKeepRunning(); static DWORD WINAPI GateThreadStart(LPVOID lpArgs); + static void PerformGateActivities(int cpuUtilization); static BOOL SufficientDelaySinceLastSample(unsigned int LastThreadCreationTime, unsigned NumThreads, // total number of threads of that type (worker or CP) double throttleRate=0.0 // the delay is increased by this percentage for each extra thread @@ -1118,7 +1120,6 @@ class ThreadpoolMgr static Volatile NumCPInfrastructureThreads; // number of threads currently busy handling draining cycle SVAL_DECL(LONG,cpuUtilization); - static LONG cpuUtilizationAverage; DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static RecycledListsWrapper RecycledLists; }; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index 32b3846221d68f..2f2cbf2675187c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -17,61 +17,70 @@ private static class GateThread private static readonly AutoResetEvent s_runGateThreadEvent = new AutoResetEvent(initialState: true); - private static CpuUtilizationReader s_cpu; private const int MaxRuns = 2; - // TODO: CoreCLR: Worker Tracking in CoreCLR? (Config name: ThreadPool_EnableWorkerTracking) + // TODO: PortableThreadPool - CoreCLR: Worker Tracking in CoreCLR? (Config name: ThreadPool_EnableWorkerTracking) private static void GateThreadStart() { - _ = s_cpu.CurrentUtilization; // The first reading is over a time range other than what we are focusing on, so we do not use the read. - AppContext.TryGetSwitch("System.Threading.ThreadPool.DisableStarvationDetection", out bool disableStarvationDetection); AppContext.TryGetSwitch("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation", out bool debuggerBreakOnWorkStarvation); + // The first reading is over a time range other than what we are focusing on, so we do not use the read other + // than to send it to any runtime-specific implementation that may also use the CPU utilization. + CpuUtilizationReader cpuUtilizationReader = default; + _ = cpuUtilizationReader.CurrentUtilization; + while (true) { s_runGateThreadEvent.WaitOne(); + + bool needGateThreadForRuntime; do { Thread.Sleep(GateThreadDelayMs); - ThreadPoolInstance._cpuUtilization = s_cpu.CurrentUtilization; + int cpuUtilization = cpuUtilizationReader.CurrentUtilization; + ThreadPoolInstance._cpuUtilization = cpuUtilization; - if (!disableStarvationDetection) + needGateThreadForRuntime = ThreadPool.PerformRuntimeSpecificGateActivities(cpuUtilization); + + if (!disableStarvationDetection && + ThreadPoolInstance._separated.numRequestedWorkers > 0 && + SufficientDelaySinceLastDequeue()) { - if (ThreadPoolInstance._separated.numRequestedWorkers > 0 && SufficientDelaySinceLastDequeue()) + try { - try + ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Acquire(); + ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts); + // don't add a thread if we're at max or if we are already in the process of adding threads + while (counts.numExistingThreads < ThreadPoolInstance._maxThreads && counts.numExistingThreads >= counts.numThreadsGoal) { - ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Acquire(); - ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts); - // don't add a thread if we're at max or if we are already in the process of adding threads - while (counts.numExistingThreads < ThreadPoolInstance._maxThreads && counts.numExistingThreads >= counts.numThreadsGoal) + if (debuggerBreakOnWorkStarvation) { - if (debuggerBreakOnWorkStarvation) - { - Debugger.Break(); - } - - ThreadCounts newCounts = counts; - newCounts.numThreadsGoal = (short)(newCounts.numExistingThreads + 1); - ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref ThreadPoolInstance._separated.counts, newCounts, counts); - if (oldCounts == counts) - { - HillClimbing.ThreadPoolHillClimber.ForceChange(newCounts.numThreadsGoal, HillClimbing.StateOrTransition.Starvation); - WorkerThread.MaybeAddWorkingWorker(); - break; - } - counts = oldCounts; + Debugger.Break(); } - } - finally - { - ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Release(); + + ThreadCounts newCounts = counts; + newCounts.numThreadsGoal = (short)(newCounts.numExistingThreads + 1); + ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref ThreadPoolInstance._separated.counts, newCounts, counts); + if (oldCounts == counts) + { + HillClimbing.ThreadPoolHillClimber.ForceChange(newCounts.numThreadsGoal, HillClimbing.StateOrTransition.Starvation); + WorkerThread.MaybeAddWorkingWorker(); + break; + } + counts = oldCounts; } } + finally + { + ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Release(); + } } - } while (ThreadPoolInstance._separated.numRequestedWorkers > 0 || Interlocked.Decrement(ref s_runningState) > GetRunningStateForNumRuns(0)); + } while ( + needGateThreadForRuntime || + ThreadPoolInstance._separated.numRequestedWorkers > 0 || + Interlocked.Decrement(ref s_runningState) > GetRunningStateForNumRuns(0)); } } @@ -139,5 +148,7 @@ private static void CreateGateThread() gateThread.Start(); } } + + internal static void EnsureGateThreadRunning() => GateThread.EnsureRunning(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index 2d91699c65ec8d..11a7ad131c51ea 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -100,10 +100,14 @@ public static void GetAvailableThreads(out int workerThreads, out int completion /// /// This method is called to request a new thread pool worker to handle pending work. /// - internal static void RequestWorkerThread() - { - PortableThreadPool.ThreadPoolInstance.RequestWorker(); - } + internal static void RequestWorkerThread() => PortableThreadPool.ThreadPoolInstance.RequestWorker(); + + /// + /// Called from the gate thread periodically to perform runtime-specific gate activities + /// + /// CPU utilization as a percentage since the last call + /// True if the runtime still needs to perform gate activities, false otherwise + internal static bool PerformRuntimeSpecificGateActivities(int cpuUtilization) => false; internal static void NotifyWorkItemProgress() { From f9427730fc699804713b2126ddab2a1128c2d852 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sun, 22 Mar 2020 07:50:37 -0700 Subject: [PATCH 11/48] Flow config values from CoreCLR to the portable thread pool for compat --- src/coreclr/src/vm/comthreadpool.cpp | 42 ++++++++++++++++++- .../PortableThreadPool.HillClimbing.cs | 26 ++++++------ .../PortableThreadPool.WorkerThread.cs | 2 +- .../System/Threading/PortableThreadPool.cs | 11 ++--- 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/coreclr/src/vm/comthreadpool.cpp b/src/coreclr/src/vm/comthreadpool.cpp index 7bbf28f7d14523..f84b25d35f5f3b 100644 --- a/src/coreclr/src/vm/comthreadpool.cpp +++ b/src/coreclr/src/vm/comthreadpool.cpp @@ -143,6 +143,24 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, return -1; } + INT32 nextConfigVariableIndex = configVariableIndex; + auto TryGetConfig = + [&](const CLRConfig::ConfigDWORDInfo &configInfo, bool isBoolean, const WCHAR *appContextConfigName) -> bool + { + ++nextConfigVariableIndex; + + bool wasNotConfigured = true; + *configValueRef = CLRConfig::GetConfigValue(configInfo, true /* acceptExplicitDefaultFromRegutil */, &wasNotConfigured); + if (wasNotConfigured) + { + return false; + } + + *isBooleanRef = isBoolean; + *appContextConfigNameRef = appContextConfigName; + return true; + }; + switch (configVariableIndex) { case 0: @@ -150,7 +168,29 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, *configValueRef = 1; *isBooleanRef = true; *appContextConfigNameRef = NULL; - return 1; + return nextConfigVariableIndex + 1; + + case 1: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMinWorkerThreads, false, W("System.Threading.ThreadPool.MinThreads"))) { return nextConfigVariableIndex; } // fall through + case 2: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMaxWorkerThreads, false, W("System.Threading.ThreadPool.MaxThreads"))) { return nextConfigVariableIndex; } // fall through + case 3: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection, true, W("System.Threading.ThreadPool.DisableStarvationDetection"))) { return nextConfigVariableIndex; } // fall through + case 4: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DebugBreakOnWorkerStarvation, true, W("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation"))) { return nextConfigVariableIndex; } // fall through + case 5: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking, true, W("System.Threading.ThreadPool.EnableWorkerTracking"))) { return nextConfigVariableIndex; } // fall through + case 6: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit, false, W("System.Threading.ThreadPool.UnfairSemaphoreSpinLimit"))) { return nextConfigVariableIndex; } // fall through + + case 7: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Disable, true, W("System.Threading.ThreadPool.HillClimbing.Disable"))) { return nextConfigVariableIndex; } // fall through + case 8: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WavePeriod, false, W("System.Threading.ThreadPool.HillClimbing.WavePeriod"))) { return nextConfigVariableIndex; } // fall through + case 9: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_TargetSignalToNoiseRatio, false, W("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio"))) { return nextConfigVariableIndex; } // fall through + case 10: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_ErrorSmoothingFactor, false, W("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor"))) { return nextConfigVariableIndex; } // fall through + case 11: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveMagnitudeMultiplier, false, W("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier"))) { return nextConfigVariableIndex; } // fall through + case 12: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxWaveMagnitude, false, W("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude"))) { return nextConfigVariableIndex; } // fall through + case 13: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveHistorySize, false, W("System.Threading.ThreadPool.HillClimbing.WaveHistorySize"))) { return nextConfigVariableIndex; } // fall through + case 14: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Bias, false, W("System.Threading.ThreadPool.HillClimbing.Bias"))) { return nextConfigVariableIndex; } // fall through + case 15: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSecond, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond"))) { return nextConfigVariableIndex; } // fall through + case 16: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSample, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample"))) { return nextConfigVariableIndex; } // fall through + case 17: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxSampleErrorPercent, false, W("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent"))) { return nextConfigVariableIndex; } // fall through + case 18: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow"))) { return nextConfigVariableIndex; } // fall through + case 19: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalHigh, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh"))) { return nextConfigVariableIndex; } // fall through + case 20: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_GainExponent, false, W("System.Threading.ThreadPool.HillClimbing.GainExponent"))) { return nextConfigVariableIndex; } // fall through default: *configValueRef = 0; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs index a84b75228cb3af..923010328410a0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs @@ -21,19 +21,19 @@ private partial class HillClimbing private static HillClimbing CreateHillClimber() { // Default values pulled from CoreCLR - return new HillClimbing(wavePeriod: AppContextConfigHelper.GetInt32Config("HillClimbing_WavePeriod", 4, false), - maxWaveMagnitude: AppContextConfigHelper.GetInt32Config("HillClimbing_MaxWaveMagnitude", 20, false), - waveMagnitudeMultiplier: AppContextConfigHelper.GetInt32Config("HillClimbing_WaveMagnitudeMultiplier", 100, false) / 100.0, - waveHistorySize: AppContextConfigHelper.GetInt32Config("HillClimbing_WaveHistorySize", 8, false), - targetThroughputRatio: AppContextConfigHelper.GetInt32Config("HillClimbing_Bias", 15, false) / 100.0, - targetSignalToNoiseRatio: AppContextConfigHelper.GetInt32Config("HillClimbing_TargetSignalToNoiseRatio", 300, false) / 100.0, - maxChangePerSecond: AppContextConfigHelper.GetInt32Config("HillClimbing_MaxChangePerSecond", 4, false), - maxChangePerSample: AppContextConfigHelper.GetInt32Config("HillClimbing_MaxChangePerSample", 20, false), - sampleIntervalMsLow: AppContextConfigHelper.GetInt32Config("HillClimbing_SampleIntervalLow", DefaultSampleIntervalMsLow, false), - sampleIntervalMsHigh: AppContextConfigHelper.GetInt32Config("HillClimbing_SampleIntervalHigh", DefaultSampleIntervalMsHigh, false), - errorSmoothingFactor: AppContextConfigHelper.GetInt32Config("HillClimbing_ErrorSmoothingFactor", 1, false) / 100.0, - gainExponent: AppContextConfigHelper.GetInt32Config("HillClimbing_GainExponent", 200, false) / 100.0, - maxSampleError: AppContextConfigHelper.GetInt32Config("HillClimbing_MaxSampleErrorPercent", 15, false) / 100.0 + return new HillClimbing(wavePeriod: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.WavePeriod", 4, false), + maxWaveMagnitude: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude", 20, false), + waveMagnitudeMultiplier: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier", 100, false) / 100.0, + waveHistorySize: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.WaveHistorySize", 8, false), + targetThroughputRatio: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.Bias", 15, false) / 100.0, + targetSignalToNoiseRatio: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio", 300, false) / 100.0, + maxChangePerSecond: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond", 4, false), + maxChangePerSample: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample", 20, false), + sampleIntervalMsLow: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow", DefaultSampleIntervalMsLow, false), + sampleIntervalMsHigh: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh", DefaultSampleIntervalMsHigh, false), + errorSmoothingFactor: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor", 1, false) / 100.0, + gainExponent: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.GainExponent", 200, false) / 100.0, + maxSampleError: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent", 15, false) / 100.0 ); } private const int LogCapacity = 200; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs index 65b0a5926cdd05..66ed155f8487b0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @@ -20,7 +20,7 @@ private static class WorkerThread /// private static int SemaphoreSpinCount { - get => AppContextConfigHelper.GetInt16Config("ThreadPool_UnfairSemaphoreSpinLimit", 70, false); + get => AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.UnfairSemaphoreSpinLimit", 70, false); } private static void WorkerThreadStart() diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index 40d1d4d666c9c8..c5fd43c3afa638 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -11,10 +11,6 @@ namespace System.Threading /// internal sealed partial class PortableThreadPool { -#pragma warning disable IDE1006 // Naming Styles - public static readonly PortableThreadPool ThreadPoolInstance = new PortableThreadPool(); -#pragma warning restore IDE1006 // Naming Styles - private const int ThreadPoolThreadTimeoutMs = 20 * 1000; // If you change this make sure to change the timeout times in the tests. #if TARGET_64BIT @@ -27,11 +23,16 @@ internal sealed partial class PortableThreadPool private const int CpuUtilizationHigh = 95; private const int CpuUtilizationLow = 80; - private int _cpuUtilization; private static readonly short s_forcedMinWorkerThreads = AppContextConfigHelper.GetInt16Config("System.Threading.ThreadPool.MinThreads", 0, false); private static readonly short s_forcedMaxWorkerThreads = AppContextConfigHelper.GetInt16Config("System.Threading.ThreadPool.MaxThreads", 0, false); +#pragma warning disable IDE1006 // Naming Styles + // The singleton must be initialized after the static variables above, as the constructor may be dependent on them + public static readonly PortableThreadPool ThreadPoolInstance = new PortableThreadPool(); +#pragma warning restore IDE1006 // Naming Styles + + private int _cpuUtilization = 0; private short _minThreads; private short _maxThreads; private readonly LowLevelLock _maxMinThreadLock = new LowLevelLock(); From 0ef2079090408da7a56027f3d43f763736848639 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 23 Mar 2020 16:37:34 -0700 Subject: [PATCH 12/48] Port - 44970522045f0c323f5735c4fe4b54bd8f71e800 - Fix hill climbing float overflow (dotnet/coreclr#14505) --- .../src/System/AppContextConfigHelper.cs | 3 +++ .../src/System/Threading/PortableThreadPool.GateThread.cs | 6 ++++-- .../src/System/Threading/PortableThreadPool.HillClimbing.cs | 4 +++- .../src/System/Threading/PortableThreadPool.cs | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs b/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs index 334eaf8a6e4f23..9175a3e8ba9129 100644 --- a/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/AppContextConfigHelper.cs @@ -7,6 +7,9 @@ namespace System { internal static class AppContextConfigHelper { + internal static bool GetBooleanConfig(string configName, bool defaultValue) => + AppContext.TryGetSwitch(configName, out bool value) ? value : defaultValue; + internal static int GetInt32Config(string configName, int defaultValue, bool allowNegative = true) { try diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index 2f2cbf2675187c..00a1393095ca87 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -22,8 +22,10 @@ private static class GateThread // TODO: PortableThreadPool - CoreCLR: Worker Tracking in CoreCLR? (Config name: ThreadPool_EnableWorkerTracking) private static void GateThreadStart() { - AppContext.TryGetSwitch("System.Threading.ThreadPool.DisableStarvationDetection", out bool disableStarvationDetection); - AppContext.TryGetSwitch("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation", out bool debuggerBreakOnWorkStarvation); + bool disableStarvationDetection = + AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.DisableStarvationDetection", false); + bool debuggerBreakOnWorkStarvation = + AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation", false); // The first reading is over a time range other than what we are focusing on, so we do not use the read other // than to send it to any runtime-specific implementation that may also use the CPU utilization. diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs index 923010328410a0..7fe4d3bca8c8fa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs @@ -18,6 +18,8 @@ private partial class HillClimbing private const int DefaultSampleIntervalMsLow = 10; private const int DefaultSampleIntervalMsHigh = 200; + public static readonly bool IsDisabled = AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.HillClimbing.Disable", false); + private static HillClimbing CreateHillClimber() { // Default values pulled from CoreCLR @@ -381,7 +383,7 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // int newSampleInterval; if (ratio.Real < 0.0 && newThreadCount == minThreads) - newSampleInterval = (int)(0.5 + _currentSampleMs * (10.0 * Math.Max(-ratio.Real, 1.0))); + newSampleInterval = (int)(0.5 + _currentSampleMs * (10.0 * Math.Min(-ratio.Real, 1.0))); else newSampleInterval = _currentSampleMs; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index c5fd43c3afa638..34516162132173 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -292,7 +292,7 @@ private bool ShouldAdjustMaxWorkersActive() // different from the original CoreCLR code from which this implementation was ported because in this // implementation there are no retired threads, so only the count of threads processing work is considered. ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref _separated.counts); - return counts.numProcessingWork <= counts.numThreadsGoal; + return counts.numProcessingWork <= counts.numThreadsGoal && !HillClimbing.IsDisabled; } return false; } From 31510574a3753bf446d31acb7a8b021fe4d57892 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 23 Mar 2020 16:42:15 -0700 Subject: [PATCH 13/48] Port - aa5ce2b1bc9ac553ddd493e269a53c999d61e965 - Limit min threads in thread pool to 1 (dotnet/coreclr#14864) --- .../src/System/Threading/PortableThreadPool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index 34516162132173..05d8be8169fc5d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -95,7 +95,7 @@ public bool SetMinThreads(int minThreads) } else { - short threads = (short)Math.Min(minThreads, MaxPossibleThreadCount); + short threads = (short)Math.Max(1, Math.Min(minThreads, MaxPossibleThreadCount)); if (s_forcedMinWorkerThreads == 0) { _minThreads = threads; From 5d8d4ae2ec93bad4174d5cc50ea342f3d6297c70 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 24 Mar 2020 10:02:35 -0700 Subject: [PATCH 14/48] Port - 8cc2aa35933677339b9e9ec5485754aa750907df - Optimize AdjustMaxWorkersActive (#1103) --- .../src/System/Threading/PortableThreadPool.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index 05d8be8169fc5d..b9010d276edbcb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -222,9 +222,6 @@ internal bool NotifyWorkItemComplete() private void AdjustMaxWorkersActive() { _hillClimbingThreadAdjustmentLock.VerifyIsLocked(); - int currentTicks = Environment.TickCount; - int totalNumCompletions = (int)_completionCounter.Count; - int numCompletions = totalNumCompletions - _separated.priorCompletionCount; long startTime = _currentSampleStartTime; long endTime = Stopwatch.GetTimestamp(); long freq = Stopwatch.Frequency; @@ -233,6 +230,10 @@ private void AdjustMaxWorkersActive() if (elapsedSeconds * 1000 >= _threadAdjustmentIntervalMs / 2) { + int currentTicks = Environment.TickCount; + int totalNumCompletions = (int)_completionCounter.Count; + int numCompletions = totalNumCompletions - _separated.priorCompletionCount; + ThreadCounts currentCounts = ThreadCounts.VolatileReadCounts(ref _separated.counts); int newMax; (newMax, _threadAdjustmentIntervalMs) = HillClimbing.ThreadPoolHillClimber.Update(currentCounts.numThreadsGoal, elapsedSeconds, numCompletions); @@ -269,6 +270,7 @@ private void AdjustMaxWorkersActive() currentCounts = oldCounts; } } + _separated.priorCompletionCount = totalNumCompletions; _separated.nextCompletedWorkRequestsTime = currentTicks + _threadAdjustmentIntervalMs; Volatile.Write(ref _separated.priorCompletedWorkRequestsTime, currentTicks); From 391661988ecac08921ccb9893a479b5197fe84ac Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 24 Mar 2020 21:50:14 -0700 Subject: [PATCH 15/48] Fix ETW events --- .../System/Threading/ThreadPool.CoreCLR.cs | 8 +- .../Tracing/FrameworkEventSource.cs | 45 +-- .../PortableThreadPool.HillClimbing.cs | 12 +- .../PortableThreadPool.WaitThread.cs | 17 ++ .../PortableThreadPool.WorkerThread.cs | 7 +- .../PortableThreadPoolEventSource.cs | 282 +++++++++++++----- .../System/Threading/ThreadPool.Portable.cs | 5 + .../src/System/Threading/ThreadPool.cs | 3 +- 8 files changed, 278 insertions(+), 101 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index c1391d7b6a4e77..3028e888adffec 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -136,8 +136,14 @@ public bool Unregister(WaitHandle waitObject) internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem { + void IThreadPoolWorkItem.Execute() => CompleteWait(); + // Entry point from unmanaged code - private void CompleteWait() => ((IThreadPoolWorkItem)this).Execute(); + private void CompleteWait() + { + Debug.Assert(ThreadPool.UsePortableThreadPool); + PortableThreadPool.CompleteWait(_registeredWaitHandle, _timedOut); + } } internal sealed class UnmanagedThreadPoolWorkItem : IThreadPoolWorkItem diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs index bf77b19b672d59..d13bd1dea57027 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs @@ -93,12 +93,12 @@ public void ThreadPoolEnqueueWork(long workID) WriteEvent(30, workID); } + // The object's current location in memory was being used before. Since objects can be moved, it may be difficult to + // associate Enqueue/Dequeue events with the object's at-the-time location in memory, the ETW listeners would have to + // know specifics about the events and track GC movements to associate events. The hash code is a stable value and + // easier to use for association, though there may be collisions. [NonEvent] - public unsafe void ThreadPoolEnqueueWorkObject(object workID) - { - // convert the Object Id to a long - ThreadPoolEnqueueWork((long)*((void**)Unsafe.AsPointer(ref workID))); - } + public void ThreadPoolEnqueueWorkObject(object workID) => ThreadPoolEnqueueWork(workID.GetHashCode()); [Event(31, Level = EventLevel.Verbose, Keywords = Keywords.ThreadPool | Keywords.ThreadTransfer)] public void ThreadPoolDequeueWork(long workID) @@ -106,12 +106,12 @@ public void ThreadPoolDequeueWork(long workID) WriteEvent(31, workID); } + // The object's current location in memory was being used before. Since objects can be moved, it may be difficult to + // associate Enqueue/Dequeue events with the object's at-the-time location in memory, the ETW listeners would have to + // know specifics about the events and track GC movements to associate events. The hash code is a stable value and + // easier to use for association, though there may be collisions. [NonEvent] - public unsafe void ThreadPoolDequeueWorkObject(object workID) - { - // convert the Object Id to a long - ThreadPoolDequeueWork((long)*((void**)Unsafe.AsPointer(ref workID))); - } + public void ThreadPoolDequeueWorkObject(object workID) => ThreadPoolDequeueWork(workID.GetHashCode()); // id - represents a correlation ID that allows correlation of two activities, one stamped by // ThreadTransferSend, the other by ThreadTransferReceive @@ -131,13 +131,15 @@ public void ThreadTransferSend(long id, int kind, string info, bool multiDequeue // keep track of GC movements in order to correlate the value passed to XyzSend with the // (possibly changed) value passed to XyzReceive [NonEvent] - public unsafe void ThreadTransferSendObj(object id, int kind, string info, bool multiDequeues, int intInfo1, int intInfo2) - { - ThreadTransferSend((long)*((void**)Unsafe.AsPointer(ref id)), kind, info, multiDequeues, intInfo1, intInfo2); - } + public void ThreadTransferSendObj(object id, int kind, string info, bool multiDequeues, int intInfo1, int intInfo2) => + ThreadTransferSend(id.GetHashCode(), kind, info, multiDequeues, intInfo1, intInfo2); // id - represents a correlation ID that allows correlation of two activities, one stamped by // ThreadTransferSend, the other by ThreadTransferReceive + // - The object's current location in memory was being used before. Since objects can be moved, it may be difficult to + // associate Enqueue/Dequeue events with the object's at-the-time location in memory, the ETW listeners would have to + // know specifics about the events and track GC movements to associate events. The hash code is a stable value and + // easier to use for association, though there may be collisions. // kind - identifies the transfer: values below 64 are reserved for the runtime. Currently used values: // 1 - managed Timers ("roaming" ID) // 2 - managed async IO operations (FileStream, PipeStream, a.o.) @@ -148,13 +150,14 @@ public void ThreadTransferReceive(long id, int kind, string? info) { WriteEvent(151, id, kind, info); } - // id - is a managed object. it gets translated to the object's address. ETW listeners must - // keep track of GC movements in order to correlate the value passed to XyzSend with the - // (possibly changed) value passed to XyzReceive + + // id - is a managed object. it gets translated to the object's address. + // - The object's current location in memory was being used before. Since objects can be moved, it may be difficult to + // associate Enqueue/Dequeue events with the object's at-the-time location in memory, the ETW listeners would have to + // know specifics about the events and track GC movements to associate events. The hash code is a stable value and + // easier to use for association, though there may be collisions. [NonEvent] - public unsafe void ThreadTransferReceiveObj(object id, int kind, string? info) - { - ThreadTransferReceive((long)*((void**)Unsafe.AsPointer(ref id)), kind, info); - } + public void ThreadTransferReceiveObj(object id, int kind, string? info) => + ThreadTransferReceive(id.GetHashCode(), kind, info); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs index 7fe4d3bca8c8fa..eaf62383114438 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs @@ -48,8 +48,8 @@ public enum StateOrTransition ClimbingMove, ChangePoint, Stabilizing, - Starvation, // Used as a message from the thread pool for a forced transition - ThreadTimedOut, // Usage as a message from the thread pool for a forced transition + Starvation, + ThreadTimedOut, } private struct LogEntry @@ -189,7 +189,7 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; if (log.IsEnabled()) { - log.WorkerThreadAdjustmentSample(throughput); + log.ThreadPoolWorkerThreadAdjustmentSample(throughput); } int sampleIndex = (int)(_totalSamples % _samplesToMeasure); @@ -362,8 +362,8 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu if (log.IsEnabled()) { - log.WorkerThreadAdjustmentStats(sampleDurationSeconds, throughput, threadWaveComponent.Real, throughputWaveComponent.Real, - throughputErrorEstimate, _averageThroughputNoise, ratio.Real, confidence, _currentControlSetting, (ushort)newThreadWaveMagnitude); + log.ThreadPoolWorkerThreadAdjustmentStats(sampleDurationSeconds, throughput, threadWaveComponent.Real, throughputWaveComponent.Real, + throughputErrorEstimate, _averageThroughputNoise, ratio.Real, confidence, _currentControlSetting, (ushort)newThreadWaveMagnitude); } @@ -424,7 +424,7 @@ private void LogTransition(int newThreadCount, double throughput, StateOrTransit PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; if (log.IsEnabled()) { - log.WorkerThreadAdjustmentAdjustment(throughput, newThreadCount, (int)stateOrTransition); + log.ThreadPoolWorkerThreadAdjustmentAdjustment(throughput, newThreadCount, (int)stateOrTransition); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs index 201fd4795e2ca2..b434d6b7efd334 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs @@ -21,6 +21,12 @@ internal partial class PortableThreadPool /// A description of the requested registration. internal void RegisterWaitHandle(RegisteredWaitHandle handle) { + PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; + if (log.IsEnabled()) + { + log.ThreadPoolIOEnqueue(handle); + } + _waitThreadLock.Acquire(); try { @@ -53,6 +59,17 @@ internal void RegisterWaitHandle(RegisteredWaitHandle handle) } } + internal static void CompleteWait(RegisteredWaitHandle handle, bool timedOut) + { + PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; + if (log.IsEnabled()) + { + log.ThreadPoolIODequeue(handle); + } + + handle.PerformCallback(timedOut); + } + /// /// Attempt to remove the given wait thread from the list. It is only removed if there are no user-provided waits on the thread. /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs index 66ed155f8487b0..652cd4a01ece0a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @@ -30,7 +30,7 @@ private static void WorkerThreadStart() PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; if (log.IsEnabled()) { - log.WorkerThreadStart(ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts).numExistingThreads); + log.ThreadPoolWorkerThreadStart(ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts).numExistingThreads); } while (true) @@ -79,7 +79,7 @@ private static void WorkerThreadStart() if (log.IsEnabled()) { - log.WorkerThreadStop(newCounts.numExistingThreads); + log.ThreadPoolWorkerThreadStop(newCounts.numExistingThreads); } return; } @@ -101,8 +101,9 @@ private static bool WaitForRequest() PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; if (log.IsEnabled()) { - log.WorkerThreadWait(ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts).numExistingThreads); + log.ThreadPoolWorkerThreadWait(ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts).numExistingThreads); } + return s_semaphore.Wait(ThreadPoolThreadTimeoutMs); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs index 7e2ee397a1c313..3551dcccaa154f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs @@ -2,119 +2,265 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.Tracing; +using System.Runtime.CompilerServices; +using Internal.Runtime.CompilerServices; namespace System.Threading { - [EventSource(Name = "Microsoft-Windows-DotNETRuntime", Guid = "{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}")] - public sealed class PortableThreadPoolEventSource : EventSource + [EventSource(Name = "Microsoft-Windows-DotNETRuntime", Guid = "e13c0d23-ccbc-4e12-931b-d9cc2eee27e4")] + internal sealed class PortableThreadPoolEventSource : EventSource { - private const string WorkerThreadMessage = "WorkerThreadCount=%1"; - private const string WorkerThreadAdjustmentSampleMessage = "Throughput=%1"; - private const string WorkerThreadAdjustmentAdjustmentEventMessage = "AverageThroughput=%1;%nNewWorkerThreadCount=%2;%nReason=%3"; - private const string WorkerThreadAdjustmentStatsEventMessage = "Duration=%1;%nThroughput=%2;%nThreadWave=%3;%nThroughputWave=%4;%nThroughputErrorEstimate=%5;%nAverageThroughputErrorEstimate=%6;%nThroughputRatio=%7;%nConfidence=%8;%nNewControlSetting=%9;%nNewThreadWaveMagnitude=%10"; + // This value does not seem to be used, leaving it as zero for now. It may be useful for a scenario that may involve + // multiple instances of the runtime within the same process, but then it seems unlikely that both instances' thread + // pools would be in moderate use. + private const short ClrInstanceId = 0; + + private static class Messages + { + public const string WorkerThread = "ActiveWorkerThreadCount={0};\nRetiredWorkerThreadCount={1};\nClrInstanceID={2}"; + public const string WorkerThreadAdjustmentSample = "Throughput={0};\nClrInstanceID={1}"; + public const string WorkerThreadAdjustmentAdjustment = "AverageThroughput={0};\nNewWorkerThreadCount={1};\nReason={2};\nClrInstanceID={3}"; + public const string WorkerThreadAdjustmentStats = "Duration={0};\nThroughput={1};\nThreadWave={2};\nThroughputWave={3};\nThroughputErrorEstimate={4};\nAverageThroughputErrorEstimate={5};\nThroughputRatio={6};\nConfidence={7};\nNewControlSetting={8};\nNewThreadWaveMagnitude={9};\nClrInstanceID={10}"; + public const string IOEnqueue = "NativeOverlapped={0};\nOverlapped={1};\nMultiDequeues={2};\nClrInstanceID={3}"; + public const string IO = "NativeOverlapped={0};\nOverlapped={1};\nClrInstanceID={2}"; + } // The task definitions for the ETW manifest - public static class Tasks + public static class Tasks // this name and visibility is important for EventSource { - public const EventTask WorkerThreadTask = (EventTask)16; - public const EventTask WorkerThreadAdjustmentTask = (EventTask)18; + public const EventTask ThreadPoolWorkerThread = (EventTask)16; + public const EventTask ThreadPoolWorkerThreadAdjustment = (EventTask)18; + public const EventTask ThreadPool = (EventTask)23; } - public static class Opcodes + public static class Opcodes // this name and visibility is important for EventSource { - public const EventOpcode WaitOpcode = (EventOpcode)90; - public const EventOpcode SampleOpcode = (EventOpcode)100; - public const EventOpcode AdjustmentOpcode = (EventOpcode)101; - public const EventOpcode StatsOpcode = (EventOpcode)102; + public const EventOpcode IOEnqueue = (EventOpcode)13; + public const EventOpcode IODequeue = (EventOpcode)14; + public const EventOpcode Wait = (EventOpcode)90; + public const EventOpcode Sample = (EventOpcode)100; + public const EventOpcode Adjustment = (EventOpcode)101; + public const EventOpcode Stats = (EventOpcode)102; } - public static class Keywords + public static class Keywords // this name and visibility is important for EventSource { public const EventKeywords ThreadingKeyword = (EventKeywords)0x10000; + public const EventKeywords ThreadTransferKeyword = (EventKeywords)0x80000000; } private PortableThreadPoolEventSource() + : base( + new Guid(0xe13c0d23, 0xccbc, 0x4e12, 0x93, 0x1b, 0xd9, 0xcc, 0x2e, 0xee, 0x27, 0xe4), + "Microsoft-Windows-DotNETRuntime") { } - [Event(1, Level = EventLevel.Informational, Message = WorkerThreadMessage, Task = Tasks.WorkerThreadTask, Opcode = EventOpcode.Start, Version = 0, Keywords = Keywords.ThreadingKeyword)] - public void WorkerThreadStart(short numExistingThreads) + [NonEvent] + private unsafe void WriteThreadEvent(int eventId, int numExistingThreads) { - WriteEvent(1, numExistingThreads); + int retiredWorkerThreadCount = 0; + short clrInstanceId = ClrInstanceId; + + EventData* data = stackalloc EventData[3]; + data[0].DataPointer = (IntPtr)(&numExistingThreads); + data[0].Size = sizeof(int); + data[0].Reserved = 0; + data[1].DataPointer = (IntPtr)(&retiredWorkerThreadCount); + data[1].Size = sizeof(int); + data[1].Reserved = 0; + data[2].DataPointer = (IntPtr)(&clrInstanceId); + data[2].Size = sizeof(short); + data[2].Reserved = 0; + WriteEventCore(eventId, 3, data); + } + + [Event(50, Level = EventLevel.Informational, Message = Messages.WorkerThread, Task = Tasks.ThreadPoolWorkerThread, Opcode = EventOpcode.Start, Version = 0, Keywords = Keywords.ThreadingKeyword)] + public unsafe void ThreadPoolWorkerThreadStart(int numExistingThreads) + { + if (IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword)) + { + WriteThreadEvent(50, numExistingThreads); + } } - [Event(2, Level = EventLevel.Informational, Message = WorkerThreadMessage, Task = Tasks.WorkerThreadTask, Opcode = EventOpcode.Stop, Version = 0, Keywords = Keywords.ThreadingKeyword)] - public void WorkerThreadStop(short numExistingThreads) + [Event(51, Level = EventLevel.Informational, Message = Messages.WorkerThread, Task = Tasks.ThreadPoolWorkerThread, Opcode = EventOpcode.Stop, Version = 0, Keywords = Keywords.ThreadingKeyword)] + public void ThreadPoolWorkerThreadStop(short numExistingThreads) { - WriteEvent(2, numExistingThreads); + if (IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword)) + { + WriteThreadEvent(51, numExistingThreads); + } } - [Event(3, Level = EventLevel.Informational, Message = WorkerThreadMessage, Task = Tasks.WorkerThreadTask, Opcode = Opcodes.WaitOpcode, Version = 0, Keywords = Keywords.ThreadingKeyword)] - public void WorkerThreadWait(short numExistingThreads) + [Event(57, Level = EventLevel.Informational, Message = Messages.WorkerThread, Task = Tasks.ThreadPoolWorkerThread, Opcode = Opcodes.Wait, Version = 0, Keywords = Keywords.ThreadingKeyword)] + [MethodImpl(MethodImplOptions.NoInlining)] + public void ThreadPoolWorkerThreadWait(short numExistingThreads) { - WriteEvent(3, numExistingThreads); + if (IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword)) + { + WriteThreadEvent(57, numExistingThreads); + } } - [Event(4, Level = EventLevel.Informational, Message = WorkerThreadAdjustmentSampleMessage, Opcode = Opcodes.SampleOpcode, Version = 0, Task = Tasks.WorkerThreadAdjustmentTask, Keywords = Keywords.ThreadingKeyword)] - public unsafe void WorkerThreadAdjustmentSample(double throughput) + [Event(54, Level = EventLevel.Informational, Message = Messages.WorkerThreadAdjustmentSample, Task = Tasks.ThreadPoolWorkerThreadAdjustment, Opcode = Opcodes.Sample, Version = 0, Keywords = Keywords.ThreadingKeyword)] + public unsafe void ThreadPoolWorkerThreadAdjustmentSample(double throughput) { - if (IsEnabled()) + if (!IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword)) { - EventData* data = stackalloc EventData[1]; - data[0].DataPointer = (IntPtr)(&throughput); - data[0].Size = sizeof(double); - WriteEventCore(4, 1, data); + return; } + + short clrInstanceId = ClrInstanceId; + + EventData* data = stackalloc EventData[2]; + data[0].DataPointer = (IntPtr)(&throughput); + data[0].Size = sizeof(double); + data[0].Reserved = 0; + data[1].DataPointer = (IntPtr)(&clrInstanceId); + data[1].Size = sizeof(short); + data[1].Reserved = 0; + WriteEventCore(54, 2, data); } - [Event(5, Level = EventLevel.Informational, Message = WorkerThreadAdjustmentAdjustmentEventMessage, Opcode = Opcodes.AdjustmentOpcode, Version = 0, Task = Tasks.WorkerThreadAdjustmentTask, Keywords = Keywords.ThreadingKeyword)] - public unsafe void WorkerThreadAdjustmentAdjustment(double averageThroughput, int newWorkerThreadCount, int stateOrTransition) + [Event(55, Level = EventLevel.Informational, Message = Messages.WorkerThreadAdjustmentAdjustment, Task = Tasks.ThreadPoolWorkerThreadAdjustment, Opcode = Opcodes.Adjustment, Version = 0, Keywords = Keywords.ThreadingKeyword)] + public unsafe void ThreadPoolWorkerThreadAdjustmentAdjustment(double averageThroughput, int newWorkerThreadCount, int stateOrTransition) { - if (IsEnabled()) + if (!IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword)) { - EventData* data = stackalloc EventData[3]; - data[0].DataPointer = (IntPtr)(&averageThroughput); - data[0].Size = sizeof(double); - data[1].DataPointer = (IntPtr)(&newWorkerThreadCount); - data[1].Size = sizeof(int); - data[2].DataPointer = (IntPtr)(&stateOrTransition); - data[2].Size = sizeof(int); - WriteEventCore(5, 3, data); + return; } + + short clrInstanceId = ClrInstanceId; + + EventData* data = stackalloc EventData[4]; + data[0].DataPointer = (IntPtr)(&averageThroughput); + data[0].Size = sizeof(double); + data[0].Reserved = 0; + data[1].DataPointer = (IntPtr)(&newWorkerThreadCount); + data[1].Size = sizeof(int); + data[1].Reserved = 0; + data[2].DataPointer = (IntPtr)(&stateOrTransition); + data[2].Size = sizeof(int); + data[2].Reserved = 0; + data[3].DataPointer = (IntPtr)(&clrInstanceId); + data[3].Size = sizeof(short); + data[3].Reserved = 0; + WriteEventCore(55, 4, data); } - [Event(6, Level = EventLevel.Verbose, Message = WorkerThreadAdjustmentStatsEventMessage, Opcode = Opcodes.StatsOpcode, Version = 0, Task = Tasks.WorkerThreadAdjustmentTask, Keywords = Keywords.ThreadingKeyword)] - [CLSCompliant(false)] - public unsafe void WorkerThreadAdjustmentStats(double duration, double throughput, double threadWave, double throughputWave, double throughputErrorEstimate, + [Event(56, Level = EventLevel.Verbose, Message = Messages.WorkerThreadAdjustmentStats, Task = Tasks.ThreadPoolWorkerThreadAdjustment, Opcode = Opcodes.Stats, Version = 0, Keywords = Keywords.ThreadingKeyword)] + public unsafe void ThreadPoolWorkerThreadAdjustmentStats(double duration, double throughput, double threadWave, double throughputWave, double throughputErrorEstimate, double averageThroughputNoise, double ratio, double confidence, double currentControlSetting, ushort newThreadWaveMagnitude) { - if (IsEnabled()) + if (!IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword)) { - EventData* data = stackalloc EventData[10]; - data[0].DataPointer = (IntPtr)(&duration); - data[0].Size = sizeof(double); - data[1].DataPointer = (IntPtr)(&throughput); - data[1].Size = sizeof(double); - data[2].DataPointer = (IntPtr)(&threadWave); - data[2].Size = sizeof(double); - data[3].DataPointer = (IntPtr)(&throughputWave); - data[3].Size = sizeof(double); - data[4].DataPointer = (IntPtr)(&throughputErrorEstimate); - data[4].Size = sizeof(double); - data[5].DataPointer = (IntPtr)(&averageThroughputNoise); - data[5].Size = sizeof(double); - data[6].DataPointer = (IntPtr)(&ratio); - data[6].Size = sizeof(double); - data[7].DataPointer = (IntPtr)(&confidence); - data[7].Size = sizeof(double); - data[8].DataPointer = (IntPtr)(¤tControlSetting); - data[8].Size = sizeof(double); - data[9].DataPointer = (IntPtr)(&newThreadWaveMagnitude); - data[9].Size = sizeof(ushort); - WriteEventCore(6, 10, data); + return; } + + short clrInstanceId = ClrInstanceId; + + EventData* data = stackalloc EventData[11]; + data[0].DataPointer = (IntPtr)(&duration); + data[0].Size = sizeof(double); + data[0].Reserved = 0; + data[1].DataPointer = (IntPtr)(&throughput); + data[1].Size = sizeof(double); + data[1].Reserved = 0; + data[2].DataPointer = (IntPtr)(&threadWave); + data[2].Size = sizeof(double); + data[2].Reserved = 0; + data[3].DataPointer = (IntPtr)(&throughputWave); + data[3].Size = sizeof(double); + data[3].Reserved = 0; + data[4].DataPointer = (IntPtr)(&throughputErrorEstimate); + data[4].Size = sizeof(double); + data[4].Reserved = 0; + data[5].DataPointer = (IntPtr)(&averageThroughputNoise); + data[5].Size = sizeof(double); + data[5].Reserved = 0; + data[6].DataPointer = (IntPtr)(&ratio); + data[6].Size = sizeof(double); + data[6].Reserved = 0; + data[7].DataPointer = (IntPtr)(&confidence); + data[7].Size = sizeof(double); + data[7].Reserved = 0; + data[8].DataPointer = (IntPtr)(¤tControlSetting); + data[8].Size = sizeof(double); + data[8].Reserved = 0; + data[9].DataPointer = (IntPtr)(&newThreadWaveMagnitude); + data[9].Size = sizeof(ushort); + data[9].Reserved = 0; + data[10].DataPointer = (IntPtr)(&clrInstanceId); + data[10].Size = sizeof(short); + data[10].Reserved = 0; + WriteEventCore(56, 11, data); } + [Event(63, Level = EventLevel.Verbose, Message = Messages.IOEnqueue, Task = Tasks.ThreadPool, Opcode = Opcodes.IOEnqueue, Version = 0, Keywords = Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)] + [MethodImpl(MethodImplOptions.NoInlining)] + private unsafe void ThreadPoolIOEnqueue(IntPtr nativeOverlapped, IntPtr overlapped, bool multiDequeues) + { + if (!IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)) + { + return; + } + + int multiDequeuesInt = Convert.ToInt32(multiDequeues); + short clrInstanceId = ClrInstanceId; + + EventData* data = stackalloc EventData[4]; + data[0].DataPointer = (IntPtr)(&nativeOverlapped); + data[0].Size = IntPtr.Size; + data[0].Reserved = 0; + data[1].DataPointer = (IntPtr)(&overlapped); + data[1].Size = IntPtr.Size; + data[1].Reserved = 0; + data[2].DataPointer = (IntPtr)(&multiDequeuesInt); + data[2].Size = sizeof(int); + data[2].Reserved = 0; + data[3].DataPointer = (IntPtr)(&clrInstanceId); + data[3].Size = sizeof(short); + data[3].Reserved = 0; + WriteEventCore(63, 4, data); + } + + // TODO: This event is fired for minor compat with CoreCLR in this case. Consider removing this method and use + // FrameworkEventSource's thread transfer send/receive events instead at callers. + [NonEvent] + public void ThreadPoolIOEnqueue(RegisteredWaitHandle registeredWaitHandle) => + ThreadPoolIOEnqueue((IntPtr)registeredWaitHandle.GetHashCode(), IntPtr.Zero, registeredWaitHandle.Repeating); + + [Event(64, Level = EventLevel.Verbose, Message = Messages.IO, Task = Tasks.ThreadPool, Opcode = Opcodes.IODequeue, Version = 0, Keywords = Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)] + [MethodImpl(MethodImplOptions.NoInlining)] + private unsafe void ThreadPoolIODequeue(IntPtr nativeOverlapped, IntPtr overlapped) + { + if (!IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)) + { + return; + } + + short clrInstanceId = ClrInstanceId; + + EventData* data = stackalloc EventData[3]; + data[0].DataPointer = (IntPtr)(&nativeOverlapped); + data[0].Size = IntPtr.Size; + data[0].Reserved = 0; + data[1].DataPointer = (IntPtr)(&overlapped); + data[1].Size = IntPtr.Size; + data[1].Reserved = 0; + data[2].DataPointer = (IntPtr)(&clrInstanceId); + data[2].Size = sizeof(short); + data[2].Reserved = 0; + WriteEventCore(64, 3, data); + } + + // TODO: This event is fired for minor compat with CoreCLR in this case. Consider removing this method and use + // FrameworkEventSource's thread transfer send/receive events instead at callers. + [NonEvent] + public void ThreadPoolIODequeue(RegisteredWaitHandle registeredWaitHandle) => + ThreadPoolIODequeue((IntPtr)registeredWaitHandle.GetHashCode(), IntPtr.Zero); + #pragma warning disable IDE1006 // Naming Styles public static readonly PortableThreadPoolEventSource Log = new PortableThreadPoolEventSource(); #pragma warning restore IDE1006 // Naming Styles diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index 11a7ad131c51ea..88329b759ec460 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -38,6 +38,11 @@ public sealed partial class RegisteredWaitHandle : MarshalByRefObject public bool Unregister(WaitHandle waitObject) => UnregisterPortable(waitObject); } + internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem + { + void IThreadPoolWorkItem.Execute() => PortableThreadPool.CompleteWait(_registeredWaitHandle, _timedOut); + } + public static partial class ThreadPool { internal const bool SupportsTimeSensitiveWorkItems = true; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index 44711cc1408b39..81e8b98aadb6a2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -1155,6 +1155,7 @@ internal void PerformCallback(bool timedOut) s_callbackLock.Release(); } #endif + _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Callback, timedOut); CompleteCallbackRequest(); } @@ -1257,8 +1258,6 @@ public CompleteWaitThreadPoolWorkItem(RegisteredWaitHandle registeredWaitHandle, _registeredWaitHandle = registeredWaitHandle; _timedOut = timedOut; } - - void IThreadPoolWorkItem.Execute() => _registeredWaitHandle.PerformCallback(_timedOut); } public static partial class ThreadPool From 88ea9c41271d105b46ea55d205b81257cc55bbb4 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 27 Mar 2020 12:24:16 -0700 Subject: [PATCH 16/48] Fix perf of counts structs --- .../System/Threading/LowLevelLifoSemaphore.cs | 204 ++++++++++++------ .../PortableThreadPool.GateThread.cs | 16 +- .../PortableThreadPool.ThreadCounts.cs | 102 +++++---- .../PortableThreadPool.WorkerThread.cs | 93 ++++---- .../System/Threading/PortableThreadPool.cs | 131 ++++++----- 5 files changed, 315 insertions(+), 231 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs index 6853a5e87edcb6..90fc4ea02046f9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs @@ -27,7 +27,7 @@ public LowLevelLifoSemaphore(int initialSignalCount, int maximumSignalCount, int Debug.Assert(spinCount >= 0); _separated = default; - _separated._counts._signalCount = (uint)initialSignalCount; + _separated._counts.SignalCount = (uint)initialSignalCount; _maximumSignalCount = maximumSignalCount; _spinCount = spinCount; @@ -45,35 +45,33 @@ public bool Wait(int timeoutMs) Counts counts = _separated._counts; while (true) { - Debug.Assert(counts._signalCount <= _maximumSignalCount); + Debug.Assert(counts.SignalCount <= _maximumSignalCount); Counts newCounts = counts; - - if (counts._signalCount != 0) + if (counts.SignalCount != 0) { - newCounts._signalCount--; + newCounts.DecrementSignalCount(); } else if (timeoutMs != 0) { - if (_spinCount > 0 && newCounts._spinnerCount < byte.MaxValue) + if (_spinCount > 0 && newCounts.SpinnerCount < byte.MaxValue) { - newCounts._spinnerCount++; + newCounts.IncrementSpinnerCount(); } else { // Maximum number of spinners reached, register as a waiter instead - newCounts._waiterCount++; - Debug.Assert(newCounts._waiterCount != 0); // overflow check, this many waiters is currently not supported + newCounts.IncrementWaiterCount(); } } - Counts countsBeforeUpdate = _separated._counts.CompareExchange(newCounts, counts); + Counts countsBeforeUpdate = _separated._counts.InterlockedCompareExchange(newCounts, counts); if (countsBeforeUpdate == counts) { - if (counts._signalCount != 0) + if (counts.SignalCount != 0) { return true; } - if (newCounts._waiterCount != counts._waiterCount) + if (newCounts.WaiterCount != counts.WaiterCount) { return WaitForSignal(timeoutMs); } @@ -96,13 +94,13 @@ public bool Wait(int timeoutMs) // Try to acquire the semaphore and unregister as a spinner counts = _separated._counts; - while (counts._signalCount > 0) + while (counts.SignalCount > 0) { Counts newCounts = counts; - newCounts._signalCount--; - newCounts._spinnerCount--; + newCounts.DecrementSignalCount(); + newCounts.DecrementSpinnerCount(); - Counts countsBeforeUpdate = _separated._counts.CompareExchange(newCounts, counts); + Counts countsBeforeUpdate = _separated._counts.InterlockedCompareExchange(newCounts, counts); if (countsBeforeUpdate == counts) { return true; @@ -117,21 +115,20 @@ public bool Wait(int timeoutMs) while (true) { Counts newCounts = counts; - newCounts._spinnerCount--; - if (counts._signalCount != 0) + newCounts.DecrementSpinnerCount(); + if (counts.SignalCount != 0) { - newCounts._signalCount--; + newCounts.DecrementSignalCount(); } else { - newCounts._waiterCount++; - Debug.Assert(newCounts._waiterCount != 0); // overflow check, this many waiters is currently not supported + newCounts.IncrementWaiterCount(); } - Counts countsBeforeUpdate = _separated._counts.CompareExchange(newCounts, counts); + Counts countsBeforeUpdate = _separated._counts.InterlockedCompareExchange(newCounts, counts); if (countsBeforeUpdate == counts) { - return counts._signalCount != 0 || WaitForSignal(timeoutMs); + return counts.SignalCount != 0 || WaitForSignal(timeoutMs); } counts = countsBeforeUpdate; @@ -151,15 +148,14 @@ public void Release(int releaseCount) Counts newCounts = counts; // Increase the signal count. The addition doesn't overflow because of the limit on the max signal count in constructor. - newCounts._signalCount += (uint)releaseCount; - Debug.Assert(newCounts._signalCount > counts._signalCount); + newCounts.AddSignalCount((uint)releaseCount); // Determine how many waiters to wake, taking into account how many spinners and waiters there are and how many waiters // have previously been signaled to wake but have not yet woken countOfWaitersToWake = - (int)Math.Min(newCounts._signalCount, (uint)newCounts._waiterCount + newCounts._spinnerCount) - - newCounts._spinnerCount - - newCounts._countOfWaitersSignaledToWake; + (int)Math.Min(newCounts.SignalCount, (uint)counts.WaiterCount + counts.SpinnerCount) - + counts.SpinnerCount - + counts.CountOfWaitersSignaledToWake; if (countOfWaitersToWake > 0) { // Ideally, limiting to a maximum of releaseCount would not be necessary and could be an assert instead, but since @@ -173,17 +169,13 @@ public void Release(int releaseCount) // Cap countOfWaitersSignaledToWake to its max value. It's ok to ignore some woken threads in this count, it just // means some more threads will be woken next time. Typically, it won't reach the max anyway. - newCounts._countOfWaitersSignaledToWake += (byte)Math.Min(countOfWaitersToWake, byte.MaxValue); - if (newCounts._countOfWaitersSignaledToWake <= counts._countOfWaitersSignaledToWake) - { - newCounts._countOfWaitersSignaledToWake = byte.MaxValue; - } + newCounts.AddUpToMaxCountOfWaitersSignaledToWake((uint)countOfWaitersToWake); } - Counts countsBeforeUpdate = _separated._counts.CompareExchange(newCounts, counts); + Counts countsBeforeUpdate = _separated._counts.InterlockedCompareExchange(newCounts, counts); if (countsBeforeUpdate == counts) { - Debug.Assert(releaseCount <= _maximumSignalCount - counts._signalCount); + Debug.Assert(releaseCount <= _maximumSignalCount - counts.SignalCount); if (countOfWaitersToWake > 0) ReleaseCore(countOfWaitersToWake); return; @@ -203,10 +195,7 @@ private bool WaitForSignal(int timeoutMs) { // Unregister the waiter. The wait subsystem used above guarantees that a thread that wakes due to a timeout does // not observe a signal to the object being waited upon. - Counts toSubtract = default; - toSubtract._waiterCount++; - Counts newCounts = _separated._counts.Subtract(toSubtract); - Debug.Assert(newCounts._waiterCount != ushort.MaxValue); // Check for underflow + _separated._counts.InterlockedDecrementWaiterCount(); return false; } @@ -214,24 +203,24 @@ private bool WaitForSignal(int timeoutMs) Counts counts = _separated._counts; while (true) { - Debug.Assert(counts._waiterCount != 0); + Debug.Assert(counts.WaiterCount != 0); Counts newCounts = counts; - if (counts._signalCount != 0) + if (counts.SignalCount != 0) { - --newCounts._signalCount; - --newCounts._waiterCount; + newCounts.DecrementSignalCount(); + newCounts.DecrementWaiterCount(); } // This waiter has woken up and this needs to be reflected in the count of waiters signaled to wake - if (counts._countOfWaitersSignaledToWake != 0) + if (counts.CountOfWaitersSignaledToWake != 0) { - --newCounts._countOfWaitersSignaledToWake; + newCounts.DecrementCountOfWaitersSignaledToWake(); } - Counts countsBeforeUpdate = _separated._counts.CompareExchange(newCounts, counts); + Counts countsBeforeUpdate = _separated._counts.InterlockedCompareExchange(newCounts, counts); if (countsBeforeUpdate == counts) { - if (counts._signalCount != 0) + if (counts.SignalCount != 0) { return true; } @@ -243,44 +232,119 @@ private bool WaitForSignal(int timeoutMs) } } - [StructLayout(LayoutKind.Explicit)] private struct Counts { - [FieldOffset(0)] - public uint _signalCount; - [FieldOffset(4)] - public ushort _waiterCount; - [FieldOffset(6)] - public byte _spinnerCount; - [FieldOffset(7)] - public byte _countOfWaitersSignaledToWake; - - [FieldOffset(0)] - private long _asLong; - - public Counts CompareExchange(Counts newCounts, Counts oldCounts) + private const byte SignalCountShift = 0; + private const byte WaiterCountShift = 32; + private const byte SpinnerCountShift = 48; + private const byte CountOfWaitersSignaledToWakeShift = 56; + + private ulong _data; + + private Counts(ulong data) => _data = data; + + private uint GetUInt32Value(byte shift) => (uint)(_data >> shift); + private void SetUInt32Value(uint value, byte shift) => + _data = (_data & ~((ulong)uint.MaxValue << shift)) | ((ulong)value << shift); + private ushort GetUInt16Value(byte shift) => (ushort)(_data >> shift); + private void SetUInt16Value(ushort value, byte shift) => + _data = (_data & ~((ulong)ushort.MaxValue << shift)) | ((ulong)value << shift); + private byte GetByteValue(byte shift) => (byte)(_data >> shift); + private void SetByteValue(byte value, byte shift) => + _data = (_data & ~((ulong)byte.MaxValue << shift)) | ((ulong)value << shift); + + public uint SignalCount + { + get => GetUInt32Value(SignalCountShift); + set => SetUInt32Value(value, SignalCountShift); + } + + public void AddSignalCount(uint value) + { + Debug.Assert(value <= uint.MaxValue - SignalCount); + _data += (ulong)value << SignalCountShift; + } + + public void IncrementSignalCount() => AddSignalCount(1); + + public void DecrementSignalCount() + { + Debug.Assert(SignalCount != 0); + _data -= (ulong)1 << SignalCountShift; + } + + public ushort WaiterCount + { + get => GetUInt16Value(WaiterCountShift); + set => SetUInt16Value(value, WaiterCountShift); + } + + public void IncrementWaiterCount() + { + Debug.Assert(WaiterCount < ushort.MaxValue); + _data += (ulong)1 << WaiterCountShift; + } + + public void DecrementWaiterCount() { - return new Counts { _asLong = Interlocked.CompareExchange(ref _asLong, newCounts._asLong, oldCounts._asLong) }; + Debug.Assert(WaiterCount != 0); + _data -= (ulong)1 << WaiterCountShift; } - public Counts Subtract(Counts subtractCounts) + public void InterlockedDecrementWaiterCount() { - return new Counts { _asLong = Interlocked.Add(ref _asLong, -subtractCounts._asLong) }; + var countsAfterUpdate = new Counts(Interlocked.Add(ref _data, unchecked((ulong)-1) << WaiterCountShift)); + Debug.Assert(countsAfterUpdate.WaiterCount != ushort.MaxValue); // underflow check } - public static bool operator ==(Counts lhs, Counts rhs) => lhs._asLong == rhs._asLong; + public byte SpinnerCount + { + get => GetByteValue(SpinnerCountShift); + set => SetByteValue(value, SpinnerCountShift); + } - public static bool operator !=(Counts lhs, Counts rhs) => lhs._asLong != rhs._asLong; + public void IncrementSpinnerCount() + { + Debug.Assert(SpinnerCount < byte.MaxValue); + _data += (ulong)1 << SpinnerCountShift; + } - public override bool Equals(object? obj) + public void DecrementSpinnerCount() { - return obj is Counts counts && this._asLong == counts._asLong; + Debug.Assert(SpinnerCount != 0); + _data -= (ulong)1 << SpinnerCountShift; } - public override int GetHashCode() + public byte CountOfWaitersSignaledToWake { - return (int)(_asLong >> 8); + get => GetByteValue(CountOfWaitersSignaledToWakeShift); + set => SetByteValue(value, CountOfWaitersSignaledToWakeShift); } + + public void AddUpToMaxCountOfWaitersSignaledToWake(uint value) + { + uint availableCount = (uint)(byte.MaxValue - CountOfWaitersSignaledToWake); + if (value > availableCount) + { + value = availableCount; + } + _data += (ulong)value << CountOfWaitersSignaledToWakeShift; + } + + public void DecrementCountOfWaitersSignaledToWake() + { + Debug.Assert(CountOfWaitersSignaledToWake != 0); + _data -= (ulong)1 << CountOfWaitersSignaledToWakeShift; + } + + public Counts InterlockedCompareExchange(Counts newCounts, Counts oldCounts) => + new Counts(Interlocked.CompareExchange(ref _data, newCounts._data, oldCounts._data)); + + public static bool operator ==(Counts lhs, Counts rhs) => lhs._data == rhs._data; + public static bool operator !=(Counts lhs, Counts rhs) => lhs._data != rhs._data; + + public override bool Equals(object? obj) => obj is Counts counts && _data == counts._data; + public override int GetHashCode() => (int)_data + (int)(_data >> 32); } [StructLayout(LayoutKind.Sequential)] diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index 00a1393095ca87..d6b0a9bb2bf3ec 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -53,9 +53,9 @@ private static void GateThreadStart() try { ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Acquire(); - ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts); + ThreadCounts counts = ThreadPoolInstance._separated.counts.VolatileRead(); // don't add a thread if we're at max or if we are already in the process of adding threads - while (counts.numExistingThreads < ThreadPoolInstance._maxThreads && counts.numExistingThreads >= counts.numThreadsGoal) + while (counts.NumExistingThreads < ThreadPoolInstance._maxThreads && counts.NumExistingThreads >= counts.NumThreadsGoal) { if (debuggerBreakOnWorkStarvation) { @@ -63,14 +63,16 @@ private static void GateThreadStart() } ThreadCounts newCounts = counts; - newCounts.numThreadsGoal = (short)(newCounts.numExistingThreads + 1); - ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref ThreadPoolInstance._separated.counts, newCounts, counts); + short newNumThreadsGoal = (short)(newCounts.NumExistingThreads + 1); + newCounts.NumThreadsGoal = newNumThreadsGoal; + ThreadCounts oldCounts = ThreadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); if (oldCounts == counts) { - HillClimbing.ThreadPoolHillClimber.ForceChange(newCounts.numThreadsGoal, HillClimbing.StateOrTransition.Starvation); + HillClimbing.ThreadPoolHillClimber.ForceChange(newNumThreadsGoal, HillClimbing.StateOrTransition.Starvation); WorkerThread.MaybeAddWorkingWorker(); break; } + counts = oldCounts; } } @@ -101,8 +103,8 @@ private static bool SufficientDelaySinceLastDequeue() } else { - ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts); - int numThreads = counts.numThreadsGoal; + ThreadCounts counts = ThreadPoolInstance._separated.counts.VolatileRead(); + int numThreads = counts.NumThreadsGoal; minimumDelay = numThreads * DequeueDelayThresholdMs; } return delay > minimumDelay; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.ThreadCounts.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.ThreadCounts.cs index bc5e9896d2eb91..3a231829e0e4bd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.ThreadCounts.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.ThreadCounts.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Runtime.InteropServices; namespace System.Threading { @@ -11,74 +10,85 @@ internal partial class PortableThreadPool /// /// Tracks information on the number of threads we want/have in different states in our thread pool. /// - [StructLayout(LayoutKind.Explicit)] private struct ThreadCounts { - /// - /// Max possible thread pool threads we want to have. - /// - [FieldOffset(0)] - public short numThreadsGoal; + private const byte NumProcessingWorkShift = 0; + private const byte NumExistingThreadsShift = 16; + private const byte NumThreadsGoalShift = 32; - /// - /// Number of thread pool threads that currently exist. - /// - [FieldOffset(2)] - public short numExistingThreads; + private ulong _data; + + private ThreadCounts(ulong data) => _data = data; + + private short GetInt16Value(byte shift) => (short)(_data >> shift); + private void SetInt16Value(short value, byte shift) => + _data = (_data & ~((ulong)ushort.MaxValue << shift)) | ((ulong)(ushort)value << shift); /// /// Number of threads processing work items. /// - [FieldOffset(4)] - public short numProcessingWork; - - [FieldOffset(0)] - private long _asLong; - - public static ThreadCounts VolatileReadCounts(ref ThreadCounts counts) + public short NumProcessingWork { - return new ThreadCounts + get => GetInt16Value(NumProcessingWorkShift); + set { - _asLong = Volatile.Read(ref counts._asLong) - }; + Debug.Assert(value >= 0); + SetInt16Value(value, NumProcessingWorkShift); + } } - public static ThreadCounts CompareExchangeCounts(ref ThreadCounts location, ThreadCounts newCounts, ThreadCounts oldCounts) + public void SubtractNumProcessingWork(short value) { - ThreadCounts result = new ThreadCounts - { - _asLong = Interlocked.CompareExchange(ref location._asLong, newCounts._asLong, oldCounts._asLong) - }; + Debug.Assert(value >= 0); + Debug.Assert(value <= NumProcessingWork); - if (result == oldCounts) - { - result.Validate(); - newCounts.Validate(); - } - return result; + _data -= (ulong)(ushort)value << NumProcessingWorkShift; } - public static bool operator ==(ThreadCounts lhs, ThreadCounts rhs) => lhs._asLong == rhs._asLong; - - public static bool operator !=(ThreadCounts lhs, ThreadCounts rhs) => lhs._asLong != rhs._asLong; - - public override bool Equals(object? obj) + /// + /// Number of thread pool threads that currently exist. + /// + public short NumExistingThreads { - return obj is ThreadCounts counts && this._asLong == counts._asLong; + get => GetInt16Value(NumExistingThreadsShift); + set + { + Debug.Assert(value >= 0); + SetInt16Value(value, NumExistingThreadsShift); + } } - public override int GetHashCode() + public void SubtractNumExistingThreads(short value) { - return (int)(_asLong >> 8) + numThreadsGoal; + Debug.Assert(value >= 0); + Debug.Assert(value <= NumExistingThreads); + + _data -= (ulong)(ushort)value << NumExistingThreadsShift; } - private void Validate() + /// + /// Max possible thread pool threads we want to have. + /// + public short NumThreadsGoal { - Debug.Assert(numThreadsGoal > 0, "Goal must be positive"); - Debug.Assert(numExistingThreads >= 0, "Number of existing threads must be non-zero"); - Debug.Assert(numProcessingWork >= 0, "Number of threads processing work must be non-zero"); - Debug.Assert(numProcessingWork <= numExistingThreads, $"Num processing work ({numProcessingWork}) must be less than or equal to Num existing threads ({numExistingThreads})"); + get => GetInt16Value(NumThreadsGoalShift); + set + { + Debug.Assert(value > 0); + SetInt16Value(value, NumThreadsGoalShift); + } } + + public ThreadCounts VolatileRead() => new ThreadCounts(Volatile.Read(ref _data)); + + public ThreadCounts InterlockedCompareExchange(ThreadCounts newCounts, ThreadCounts oldCounts) => + new ThreadCounts(Interlocked.CompareExchange(ref _data, newCounts._data, oldCounts._data)); + + public static bool operator ==(ThreadCounts lhs, ThreadCounts rhs) => lhs._data == rhs._data; + public static bool operator !=(ThreadCounts lhs, ThreadCounts rhs) => lhs._data != rhs._data; + + public override bool Equals(object? obj) => obj is ThreadCounts other && _data == other._data; + public override int GetHashCode() => (int)_data + (int)(_data >> 32); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs index 652cd4a01ece0a..c229295cf66699 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @@ -30,7 +30,7 @@ private static void WorkerThreadStart() PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; if (log.IsEnabled()) { - log.ThreadPoolWorkerThreadStart(ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts).numExistingThreads); + log.ThreadPoolWorkerThreadStart(ThreadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); } while (true) @@ -60,29 +60,38 @@ private static void WorkerThreadStart() // We are going to decrement the number of exisiting threads to no longer include this one // and then change the max number of threads in the thread pool to reflect that we don't need as many // as we had. Finally, we are going to tell hill climbing that we changed the max number of threads. - ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts); + ThreadCounts counts = ThreadPoolInstance._separated.counts.VolatileRead(); while (true) { - if (counts.numExistingThreads == counts.numProcessingWork) + // Since this thread is currently registered as an existing thread, if more work comes in meanwhile, + // this thread would be expected to satisfy the new work. Ensure that NumExistingThreads is not + // decreased below NumProcessingWork, as that would be indicative of such a case. + short numExistingThreads = counts.NumExistingThreads; + if (numExistingThreads <= counts.NumProcessingWork) { // In this case, enough work came in that this thread should not time out and should go back to work. break; } ThreadCounts newCounts = counts; - newCounts.numExistingThreads--; - newCounts.numThreadsGoal = Math.Max(ThreadPoolInstance._minThreads, Math.Min(newCounts.numExistingThreads, newCounts.numThreadsGoal)); - ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref ThreadPoolInstance._separated.counts, newCounts, counts); + newCounts.SubtractNumExistingThreads(1); + short newNumExistingThreads = (short)(numExistingThreads - 1); + short newNumThreadsGoal = Math.Max(ThreadPoolInstance._minThreads, Math.Min(newNumExistingThreads, newCounts.NumThreadsGoal)); + newCounts.NumThreadsGoal = newNumThreadsGoal; + + ThreadCounts oldCounts = ThreadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); if (oldCounts == counts) { - HillClimbing.ThreadPoolHillClimber.ForceChange(newCounts.numThreadsGoal, HillClimbing.StateOrTransition.ThreadTimedOut); + HillClimbing.ThreadPoolHillClimber.ForceChange(newNumThreadsGoal, HillClimbing.StateOrTransition.ThreadTimedOut); if (log.IsEnabled()) { - log.ThreadPoolWorkerThreadStop(newCounts.numExistingThreads); + log.ThreadPoolWorkerThreadStop(newNumExistingThreads); } return; } + + counts = oldCounts; } } finally @@ -101,7 +110,7 @@ private static bool WaitForRequest() PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; if (log.IsEnabled()) { - log.ThreadPoolWorkerThreadWait(ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts).numExistingThreads); + log.ThreadPoolWorkerThreadWait(ThreadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); } return s_semaphore.Wait(ThreadPoolThreadTimeoutMs); @@ -112,12 +121,12 @@ private static bool WaitForRequest() /// private static void RemoveWorkingWorker() { - ThreadCounts currentCounts = ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts); + ThreadCounts currentCounts = ThreadPoolInstance._separated.counts.VolatileRead(); while (true) { ThreadCounts newCounts = currentCounts; - newCounts.numProcessingWork--; - ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref ThreadPoolInstance._separated.counts, newCounts, currentCounts); + newCounts.SubtractNumProcessingWork(1); + ThreadCounts oldCounts = ThreadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, currentCounts); if (oldCounts == currentCounts) { @@ -138,20 +147,25 @@ private static void RemoveWorkingWorker() internal static void MaybeAddWorkingWorker() { - ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts); - ThreadCounts newCounts; + ThreadCounts counts = ThreadPoolInstance._separated.counts.VolatileRead(); + short numExistingThreads, numProcessingWork, newNumExistingThreads, newNumProcessingWork; while (true) { - newCounts = counts; - newCounts.numProcessingWork = Math.Max(counts.numProcessingWork, Math.Min((short)(counts.numProcessingWork + 1), counts.numThreadsGoal)); - newCounts.numExistingThreads = Math.Max(counts.numExistingThreads, newCounts.numProcessingWork); - - if (newCounts == counts) + numProcessingWork = counts.NumProcessingWork; + if (numProcessingWork >= counts.NumThreadsGoal) { return; } - ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref ThreadPoolInstance._separated.counts, newCounts, counts); + newNumProcessingWork = (short)(numProcessingWork + 1); + numExistingThreads = counts.NumExistingThreads; + newNumExistingThreads = Math.Max(numExistingThreads, newNumProcessingWork); + + ThreadCounts newCounts = counts; + newCounts.NumProcessingWork = newNumProcessingWork; + newCounts.NumExistingThreads = newNumExistingThreads; + + ThreadCounts oldCounts = ThreadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); if (oldCounts == counts) { @@ -161,8 +175,8 @@ internal static void MaybeAddWorkingWorker() counts = oldCounts; } - int toCreate = newCounts.numExistingThreads - counts.numExistingThreads; - int toRelease = newCounts.numProcessingWork - counts.numProcessingWork; + int toCreate = newNumExistingThreads - numExistingThreads; + int toRelease = newNumProcessingWork - numProcessingWork; if (toRelease > 0) { @@ -174,25 +188,24 @@ internal static void MaybeAddWorkingWorker() if (TryCreateWorkerThread()) { toCreate--; + continue; } - else + + counts = ThreadPoolInstance._separated.counts.VolatileRead(); + while (true) { - counts = ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts); - while (true) - { - newCounts = counts; - newCounts.numProcessingWork -= (short)toCreate; - newCounts.numExistingThreads -= (short)toCreate; + ThreadCounts newCounts = counts; + newCounts.SubtractNumProcessingWork((short)toCreate); + newCounts.SubtractNumExistingThreads((short)toCreate); - ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref ThreadPoolInstance._separated.counts, newCounts, counts); - if (oldCounts == counts) - { - break; - } - counts = oldCounts; + ThreadCounts oldCounts = ThreadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); + if (oldCounts == counts) + { + break; } - toCreate = 0; + counts = oldCounts; } + break; } } @@ -204,7 +217,7 @@ internal static void MaybeAddWorkingWorker() /// Whether or not this thread should stop processing work even if there is still work in the queue. internal static bool ShouldStopProcessingWorkNow() { - ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts); + ThreadCounts counts = ThreadPoolInstance._separated.counts.VolatileRead(); while (true) { // When there are more threads processing work than the thread count goal, hill climbing must have decided @@ -214,15 +227,15 @@ internal static bool ShouldStopProcessingWorkNow() // code from which this implementation was ported, which turns a processing thread into a retired thread // and checks for pending requests like RemoveWorkingWorker. In this implementation there are // no retired threads, so only the count of threads processing work is considered. - if (counts.numProcessingWork <= counts.numThreadsGoal) + if (counts.NumProcessingWork <= counts.NumThreadsGoal) { return false; } ThreadCounts newCounts = counts; - newCounts.numProcessingWork--; + newCounts.SubtractNumProcessingWork(1); - ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref ThreadPoolInstance._separated.counts, newCounts, counts); + ThreadCounts oldCounts = ThreadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); if (oldCounts == counts) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index b9010d276edbcb..890fe208599ad9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -79,7 +79,7 @@ private PortableThreadPool() { counts = new ThreadCounts { - numThreadsGoal = _minThreads + NumThreadsGoal = _minThreads } }; } @@ -93,37 +93,35 @@ public bool SetMinThreads(int minThreads) { return false; } - else + + if (s_forcedMinWorkerThreads != 0) { - short threads = (short)Math.Max(1, Math.Min(minThreads, MaxPossibleThreadCount)); - if (s_forcedMinWorkerThreads == 0) - { - _minThreads = threads; + return true; + } + + short newMinThreads = (short)Math.Max(1, Math.Min(minThreads, MaxPossibleThreadCount)); + _minThreads = newMinThreads; - ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref _separated.counts); - while (counts.numThreadsGoal < _minThreads) + ThreadCounts counts = _separated.counts.VolatileRead(); + while (counts.NumThreadsGoal < newMinThreads) + { + ThreadCounts newCounts = counts; + newCounts.NumThreadsGoal = newMinThreads; + + ThreadCounts oldCounts = _separated.counts.InterlockedCompareExchange(newCounts, counts); + if (oldCounts == counts) + { + if (_separated.numRequestedWorkers > 0) { - ThreadCounts newCounts = counts; - newCounts.numThreadsGoal = _minThreads; - - ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref _separated.counts, newCounts, counts); - if (oldCounts == counts) - { - counts = newCounts; - - if (newCounts.numThreadsGoal > oldCounts.numThreadsGoal && _separated.numRequestedWorkers > 0) - { - WorkerThread.MaybeAddWorkingWorker(); - } - } - else - { - counts = oldCounts; - } + WorkerThread.MaybeAddWorkingWorker(); } + break; } - return true; + + counts = oldCounts; } + + return true; } finally { @@ -142,32 +140,31 @@ public bool SetMaxThreads(int maxThreads) { return false; } - else + + if (s_forcedMaxWorkerThreads != 0) { - short threads = (short)Math.Min(maxThreads, MaxPossibleThreadCount); - if (s_forcedMaxWorkerThreads == 0) - { - _maxThreads = threads; + return true; + } - ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref _separated.counts); - while (counts.numThreadsGoal > _maxThreads) - { - ThreadCounts newCounts = counts; - newCounts.numThreadsGoal = _maxThreads; - - ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref _separated.counts, newCounts, counts); - if (oldCounts == counts) - { - counts = newCounts; - } - else - { - counts = oldCounts; - } - } + short newMaxThreads = (short)Math.Min(maxThreads, MaxPossibleThreadCount); + _maxThreads = newMaxThreads; + + ThreadCounts counts = _separated.counts.VolatileRead(); + while (counts.NumThreadsGoal > newMaxThreads) + { + ThreadCounts newCounts = counts; + newCounts.NumThreadsGoal = newMaxThreads; + + ThreadCounts oldCounts = _separated.counts.InterlockedCompareExchange(newCounts, counts); + if (oldCounts == counts) + { + break; } - return true; + + counts = oldCounts; } + + return true; } finally { @@ -179,8 +176,8 @@ public bool SetMaxThreads(int maxThreads) public int GetAvailableThreads() { - ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref _separated.counts); - int count = _maxThreads - counts.numProcessingWork; + ThreadCounts counts = _separated.counts.VolatileRead(); + int count = _maxThreads - counts.NumProcessingWork; if (count < 0) { return 0; @@ -188,7 +185,7 @@ public int GetAvailableThreads() return count; } - public int ThreadCount => ThreadCounts.VolatileReadCounts(ref _separated.counts).numExistingThreads; + public int ThreadCount => _separated.counts.VolatileRead().NumExistingThreads; public long CompletedWorkItemCount => _completionCounter.Count; internal void NotifyWorkItemProgress() @@ -234,16 +231,16 @@ private void AdjustMaxWorkersActive() int totalNumCompletions = (int)_completionCounter.Count; int numCompletions = totalNumCompletions - _separated.priorCompletionCount; - ThreadCounts currentCounts = ThreadCounts.VolatileReadCounts(ref _separated.counts); + ThreadCounts currentCounts = _separated.counts.VolatileRead(); int newMax; - (newMax, _threadAdjustmentIntervalMs) = HillClimbing.ThreadPoolHillClimber.Update(currentCounts.numThreadsGoal, elapsedSeconds, numCompletions); + (newMax, _threadAdjustmentIntervalMs) = HillClimbing.ThreadPoolHillClimber.Update(currentCounts.NumThreadsGoal, elapsedSeconds, numCompletions); - while (newMax != currentCounts.numThreadsGoal) + while (newMax != currentCounts.NumThreadsGoal) { ThreadCounts newCounts = currentCounts; - newCounts.numThreadsGoal = (short)newMax; + newCounts.NumThreadsGoal = (short)newMax; - ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref _separated.counts, newCounts, currentCounts); + ThreadCounts oldCounts = _separated.counts.InterlockedCompareExchange(newCounts, currentCounts); if (oldCounts == currentCounts) { // @@ -252,23 +249,21 @@ private void AdjustMaxWorkersActive() // // If we're reducing the max, whichever threads notice this first will sleep and timeout themselves. // - if (newMax > oldCounts.numThreadsGoal) + if (newMax > oldCounts.NumThreadsGoal) { WorkerThread.MaybeAddWorkingWorker(); } break; } - else - { - if (oldCounts.numThreadsGoal > currentCounts.numThreadsGoal && oldCounts.numThreadsGoal >= newMax) - { - // someone (probably the gate thread) increased the thread count more than - // we are about to do. Don't interfere. - break; - } - currentCounts = oldCounts; + if (oldCounts.NumThreadsGoal > currentCounts.NumThreadsGoal && oldCounts.NumThreadsGoal >= newMax) + { + // someone (probably the gate thread) increased the thread count more than + // we are about to do. Don't interfere. + break; } + + currentCounts = oldCounts; } _separated.priorCompletionCount = totalNumCompletions; @@ -293,8 +288,8 @@ private bool ShouldAdjustMaxWorkersActive() // threads processing work to stop in response to a decreased thread count goal. The logic here is a bit // different from the original CoreCLR code from which this implementation was ported because in this // implementation there are no retired threads, so only the count of threads processing work is considered. - ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref _separated.counts); - return counts.numProcessingWork <= counts.numThreadsGoal && !HillClimbing.IsDisabled; + ThreadCounts counts = _separated.counts.VolatileRead(); + return counts.NumProcessingWork <= counts.NumThreadsGoal && !HillClimbing.IsDisabled; } return false; } From 8ef63f9b1aea8b4e6f9bec43e024952468132338 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 27 Mar 2020 18:53:38 -0700 Subject: [PATCH 17/48] Fix perf of dispatch loop --- .../Tracing/FrameworkEventSource.cs | 3 ++ .../src/System/Threading/ThreadPool.cs | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs index d13bd1dea57027..c43a6b92593f07 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.CompilerServices; using Internal.Runtime.CompilerServices; namespace System.Diagnostics.Tracing @@ -98,6 +99,7 @@ public void ThreadPoolEnqueueWork(long workID) // know specifics about the events and track GC movements to associate events. The hash code is a stable value and // easier to use for association, though there may be collisions. [NonEvent] + [MethodImpl(MethodImplOptions.NoInlining)] public void ThreadPoolEnqueueWorkObject(object workID) => ThreadPoolEnqueueWork(workID.GetHashCode()); [Event(31, Level = EventLevel.Verbose, Keywords = Keywords.ThreadPool | Keywords.ThreadTransfer)] @@ -111,6 +113,7 @@ public void ThreadPoolDequeueWork(long workID) // know specifics about the events and track GC movements to associate events. The hash code is a stable value and // easier to use for association, though there may be collisions. [NonEvent] + [MethodImpl(MethodImplOptions.NoInlining)] public void ThreadPoolDequeueWorkObject(object workID) => ThreadPoolDequeueWork(workID.GetHashCode()); // id - represents a correlation ID that allows correlation of two activities, one stamped by diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index 81e8b98aadb6a2..08861c7b1aaba5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -393,7 +393,7 @@ public int Count public ThreadPoolWorkQueue() { - loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer); + RefreshLoggingEnabled(); } public ThreadPoolWorkQueueThreadLocals GetOrCreateThreadLocals() => @@ -407,6 +407,27 @@ private ThreadPoolWorkQueueThreadLocals CreateThreadLocals() return ThreadPoolWorkQueueThreadLocals.threadLocals = new ThreadPoolWorkQueueThreadLocals(this); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RefreshLoggingEnabled() + { + if (!FrameworkEventSource.Log.IsEnabled()) + { + if (loggingEnabled) + { + loggingEnabled = false; + } + return; + } + + RefreshLoggingEnabledFull(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void RefreshLoggingEnabledFull() + { + loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer); + } + internal void EnsureThreadRequested() { // @@ -462,6 +483,7 @@ public void EnqueueTimeSensitiveWorkItem(IThreadPoolWorkItem timeSensitiveWorkIt EnsureThreadRequested(); } + [MethodImpl(MethodImplOptions.NoInlining)] public IThreadPoolWorkItem? TryDequeueTimeSensitiveWorkItem() { Debug.Assert(ThreadPool.SupportsTimeSensitiveWorkItems); @@ -584,7 +606,7 @@ internal static bool Dispatch() outerWorkQueue.MarkThreadRequestSatisfied(); // Has the desire for logging changed since the last time we entered? - outerWorkQueue.loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer); + outerWorkQueue.RefreshLoggingEnabled(); // // Assume that we're going to need another thread if this one returns to the VM. We'll set this to @@ -712,7 +734,7 @@ internal static bool Dispatch() startTickCount = currentTickCount; // Periodically refresh whether logging is enabled - workQueue.loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer); + workQueue.RefreshLoggingEnabled(); // Consistent with CoreCLR currently, only one time-sensitive work item is run periodically between quantums // of time spent running work items in the normal thread pool queues, until the normal queues are depleted. From e01df4fd0d0f17e6e28d64b140463d4bc3d0c152 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sun, 29 Mar 2020 12:16:27 -0700 Subject: [PATCH 18/48] Fix perf of ThreadInt64PersistentCounter --- .../src/System/Threading/Thread.CoreCLR.cs | 3 +- .../System/Threading/ThreadPool.CoreCLR.cs | 8 ++- .../System.Private.CoreLib.Shared.projitems | 2 - .../src/System/Threading/LowLevelLock.cs | 6 +- .../System/Threading/PortableThreadPool.cs | 29 +++++++-- .../Threading/ThreadInt64PersistentCounter.cs | 64 ++++++++----------- .../System/Threading/ThreadPool.Portable.cs | 16 ++--- .../src/System/Threading/ThreadPool.cs | 5 +- 8 files changed, 74 insertions(+), 59 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index 878f74d0472838..a766da0368cba3 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -527,7 +527,7 @@ public static int GetCurrentProcessorId() // we will record that in a readonly static so that it could become a JIT constant and bypass caching entirely. private static readonly bool s_isProcessorNumberReallyFast = ProcessorIdCache.ProcessorNumberSpeedCheck(); -#pragma warning disable CA1822 // Mark members as static + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void ResetThreadPoolThread() { Debug.Assert(this == CurrentThread); @@ -545,6 +545,5 @@ internal void ResetThreadPoolThread() ResetThreadPoolThreadSlow(); } } -#pragma warning restore CA1822 } // End of class Thread } diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index 3028e888adffec..a7c33746d6b521 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -422,11 +422,12 @@ private static void UnsafeQueueUnmanagedWorkItem(IntPtr callback, IntPtr state) [MethodImpl(MethodImplOptions.InternalCall)] private static extern void GetAvailableThreadsNative(out int workerThreads, out int completionPortThreads); - internal static bool NotifyWorkItemComplete() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject) { if (UsePortableThreadPool) { - return PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(); + return PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(threadLocalCompletionCountObject); } return NotifyWorkItemCompleteNative(); @@ -463,6 +464,9 @@ internal static void NotifyWorkItemProgress() [MethodImpl(MethodImplOptions.InternalCall)] private static extern void NotifyWorkItemProgressNative(); + internal static object? GetOrCreateThreadLocalCompletionCountObject() => + UsePortableThreadPool ? PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject() : null; + [MethodImpl(MethodImplOptions.InternalCall)] private static extern bool GetEnableWorkerTracking(); diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 9ae131ba960a88..6e6782d8d7e03a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1951,8 +1951,6 @@ - - diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs index 12c4b7af37776c..e7927a4af1e5fe 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs @@ -7,8 +7,10 @@ namespace System.Threading { /// - /// A lightweight non-recursive mutex. - /// Waits on this lock are uninterruptible. + /// A lightweight non-recursive mutex. Waits on this lock are uninterruptible (from Thread.Interrupt(), which is supported + /// in some runtimes). That is the main reason this lock type would be used over interruptible locks, such as in a + /// low-level-infrastructure component that was historically not susceptible to a pending interrupt, and for compatibility + /// reasons, to ensure that it still would not be susceptible after porting that component to managed code. /// internal sealed class LowLevelLock : IDisposable { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index 890fe208599ad9..dab4432b2f2346 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace System.Threading @@ -27,6 +28,9 @@ internal sealed partial class PortableThreadPool private static readonly short s_forcedMinWorkerThreads = AppContextConfigHelper.GetInt16Config("System.Threading.ThreadPool.MinThreads", 0, false); private static readonly short s_forcedMaxWorkerThreads = AppContextConfigHelper.GetInt16Config("System.Threading.ThreadPool.MaxThreads", 0, false); + [ThreadStatic] + private static object? t_completionCountObject; + #pragma warning disable IDE1006 // Naming Styles // The singleton must be initialized after the static variables above, as the constructor may be dependent on them public static readonly PortableThreadPool ThreadPoolInstance = new PortableThreadPool(); @@ -188,9 +192,22 @@ public int GetAvailableThreads() public int ThreadCount => _separated.counts.VolatileRead().NumExistingThreads; public long CompletedWorkItemCount => _completionCounter.Count; - internal void NotifyWorkItemProgress() + public object GetOrCreateThreadLocalCompletionCountObject() => + t_completionCountObject ?? CreateThreadLocalCompletionCountObject(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private object CreateThreadLocalCompletionCountObject() + { + Debug.Assert(t_completionCountObject == null); + + object threadLocalCompletionCountObject = _completionCounter.CreateThreadLocalCountObject(); + t_completionCountObject = threadLocalCompletionCountObject; + return threadLocalCompletionCountObject; + } + + private void NotifyWorkItemProgress(object threadLocalCompletionCountObject) { - _completionCounter.Increment(); + ThreadInt64PersistentCounter.Increment(threadLocalCompletionCountObject); Volatile.Write(ref _separated.lastDequeueTime, Environment.TickCount); if (ShouldAdjustMaxWorkersActive() && _hillClimbingThreadAdjustmentLock.TryAcquire()) @@ -206,9 +223,13 @@ internal void NotifyWorkItemProgress() } } - internal bool NotifyWorkItemComplete() + internal void NotifyWorkItemProgress() => NotifyWorkItemProgress(GetOrCreateThreadLocalCompletionCountObject()); + + internal bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject) { - NotifyWorkItemProgress(); + Debug.Assert(threadLocalCompletionCountObject != null); + + NotifyWorkItemProgress(threadLocalCompletionCountObject!); return !WorkerThread.ShouldStopProcessingWorkNow(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs index 5867f0c30d9a29..79554a9f286bf6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs @@ -1,79 +1,69 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; +using Internal.Runtime.CompilerServices; namespace System.Threading { internal sealed class ThreadInt64PersistentCounter { - // This type is used by Monitor for lock contention counting, so can't use an object for a lock. Also it's preferable - // (though currently not required) to disallow/ignore thread interrupt for uses of this lock here. Using Lock directly - // is a possibility but maybe less compatible with other runtimes. Lock cases are relatively rare, static instance - // should be ok. private static readonly LowLevelLock s_lock = new LowLevelLock(); - private readonly ThreadLocal _threadLocalNode = new ThreadLocal(trackAllValues: true); private long _overflowCount; + // This should be a HashSet, that type is currently not available in CoreLib + private Dictionary _nodes = new Dictionary(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Increment() + public static void Increment(object threadLocalCountObject) { - ThreadLocalNode? node = _threadLocalNode.Value; - if (node != null) - { - node.Increment(); - return; - } - - TryCreateNode(); + Debug.Assert(threadLocalCountObject is ThreadLocalNode); + Unsafe.As(threadLocalCountObject).Increment(); } - [MethodImpl(MethodImplOptions.NoInlining)] - private void TryCreateNode() + public object CreateThreadLocalCountObject() { - Debug.Assert(_threadLocalNode.Value == null); + var node = new ThreadLocalNode(this); + s_lock.Acquire(); try { - _threadLocalNode.Value = new ThreadLocalNode(this); + _nodes.Add(node, false); } - catch (OutOfMemoryException) + finally { + s_lock.Release(); } + + return node; } public long Count { get { - long count = 0; + s_lock.Acquire(); + long count = _overflowCount; try { - s_lock.Acquire(); - try - { - count = _overflowCount; - foreach (ThreadLocalNode node in _threadLocalNode.ValuesAsEnumerable) - { - if (node != null) - { - count += node.Count; - } - } - return count; - } - finally + foreach (ThreadLocalNode node in _nodes.Keys) { - s_lock.Release(); + count += node.Count; } } catch (OutOfMemoryException) { // Some allocation occurs above and it may be a bit awkward to get an OOM from this property getter - return count; } + finally + { + s_lock.Release(); + } + + return count; } } @@ -85,8 +75,6 @@ private sealed class ThreadLocalNode public ThreadLocalNode(ThreadInt64PersistentCounter counter) { Debug.Assert(counter != null); - - _count = 1; _counter = counter; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index 88329b759ec460..255f1703ccdf98 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.CompilerServices; namespace System.Threading { @@ -114,15 +115,14 @@ public static void GetAvailableThreads(out int workerThreads, out int completion /// True if the runtime still needs to perform gate activities, false otherwise internal static bool PerformRuntimeSpecificGateActivities(int cpuUtilization) => false; - internal static void NotifyWorkItemProgress() - { - PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); - } + internal static void NotifyWorkItemProgress() => PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); - internal static bool NotifyWorkItemComplete() - { - return PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject) => + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(threadLocalCompletionCountObject); + + internal static object GetOrCreateThreadLocalCompletionCountObject() => + PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject(); private static void RegisterWaitForSingleObjectCore(WaitHandle? waitObject, RegisteredWaitHandle registeredWaitHandle) => PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index 08861c7b1aaba5..845d8ce4e184b1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -621,6 +621,7 @@ internal static bool Dispatch() // Use operate on workQueue local to try block so it can be enregistered ThreadPoolWorkQueue workQueue = outerWorkQueue; ThreadPoolWorkQueueThreadLocals tl = workQueue.GetOrCreateThreadLocals(); + object? threadLocalCompletionCountObject = tl.threadLocalCompletionCountObject; Thread currentThread = tl.currentThread; // Start on clean ExecutionContext and SynchronizationContext @@ -708,7 +709,7 @@ internal static bool Dispatch() // Notify the VM that we executed this workitem. This is also our opportunity to ask whether Hill Climbing wants // us to return the thread to the pool or not. // - if (!ThreadPool.NotifyWorkItemComplete()) + if (!ThreadPool.NotifyWorkItemComplete(threadLocalCompletionCountObject)) return false; // Check if the dispatch quantum has expired @@ -816,6 +817,7 @@ internal sealed class ThreadPoolWorkQueueThreadLocals public readonly ThreadPoolWorkQueue workQueue; public readonly ThreadPoolWorkQueue.WorkStealingQueue workStealingQueue; public readonly Thread currentThread; + public readonly object? threadLocalCompletionCountObject; public FastRandom random = new FastRandom(Environment.CurrentManagedThreadId); // mutable struct, do not copy or make readonly public ThreadPoolWorkQueueThreadLocals(ThreadPoolWorkQueue tpq) @@ -824,6 +826,7 @@ public ThreadPoolWorkQueueThreadLocals(ThreadPoolWorkQueue tpq) workStealingQueue = new ThreadPoolWorkQueue.WorkStealingQueue(); ThreadPoolWorkQueue.WorkStealingQueueList.Add(workStealingQueue); currentThread = Thread.CurrentThread; + threadLocalCompletionCountObject = ThreadPool.GetOrCreateThreadLocalCompletionCountObject(); } ~ThreadPoolWorkQueueThreadLocals() From 7c89f816ec47be62ef4cd28967ab6278c8d272e2 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 31 Mar 2020 20:52:36 -0700 Subject: [PATCH 19/48] Miscellaneous perf fixes --- .../System/Threading/ThreadPool.CoreCLR.cs | 7 +- .../Collections/Concurrent/ConcurrentQueue.cs | 25 +++- .../System/Threading/LowLevelLifoSemaphore.cs | 16 ++- .../PortableThreadPool.GateThread.cs | 97 +++++++------ .../PortableThreadPool.HillClimbing.cs | 7 +- .../PortableThreadPool.WaitThread.cs | 10 +- .../PortableThreadPool.WorkerThread.cs | 117 +++++++++------- .../System/Threading/PortableThreadPool.cs | 129 ++++++++++-------- .../System/Threading/ThreadPool.Portable.cs | 4 +- .../src/System/Threading/ThreadPool.cs | 88 +++++++----- 10 files changed, 297 insertions(+), 203 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index a7c33746d6b521..b2b2c3171c65a2 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -423,11 +423,14 @@ private static void UnsafeQueueUnmanagedWorkItem(IntPtr callback, IntPtr state) private static extern void GetAvailableThreadsNative(out int workerThreads, out int completionPortThreads); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject) + internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int currentTimeMs) { if (UsePortableThreadPool) { - return PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(threadLocalCompletionCountObject); + return + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete( + threadLocalCompletionCountObject, + currentTimeMs); } return NotifyWorkItemCompleteNative(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs index d46f4501d354f0..e45ed1e6a21b49 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs @@ -666,9 +666,28 @@ private void EnqueueSlow(T item) /// true if an element was removed and returned from the beginning of the /// successfully; otherwise, false. /// - public bool TryDequeue([MaybeNullWhen(false)] out T result) => - _head.TryDequeue(out result) || // fast-path that operates just on the head segment - TryDequeueSlow(out result); // slow path that needs to fix up segments + public bool TryDequeue([MaybeNullWhen(false)] out T result) + { + // Get the current head + ConcurrentQueueSegment head = _head; + + // Try to take. If we're successful, we're done. + if (head.TryDequeue(out result)) + { + return true; + } + + // Check to see whether this segment is the last. If it is, we can consider + // this to be a moment-in-time empty condition (even though between the TryDequeue + // check and this check, another item could have arrived). + if (head._nextSegment == null) + { + result = default!; + return false; + } + + return TryDequeueSlow(out result); // slow path that needs to fix up segments + } /// Tries to dequeue an item, removing empty segments as needed. private bool TryDequeueSlow([MaybeNullWhen(false)] out T item) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs index 90fc4ea02046f9..9dc3b47f85ec3f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs @@ -16,10 +16,11 @@ internal sealed partial class LowLevelLifoSemaphore : IDisposable private readonly int _maximumSignalCount; private readonly int _spinCount; + private readonly Action _onWait; private const int SpinSleep0Threshold = 10; - public LowLevelLifoSemaphore(int initialSignalCount, int maximumSignalCount, int spinCount) + public LowLevelLifoSemaphore(int initialSignalCount, int maximumSignalCount, int spinCount, Action onWait) { Debug.Assert(initialSignalCount >= 0); Debug.Assert(initialSignalCount <= maximumSignalCount); @@ -30,6 +31,7 @@ public LowLevelLifoSemaphore(int initialSignalCount, int maximumSignalCount, int _separated._counts.SignalCount = (uint)initialSignalCount; _maximumSignalCount = maximumSignalCount; _spinCount = spinCount; + _onWait = onWait; Create(maximumSignalCount); } @@ -38,6 +40,8 @@ public bool Wait(int timeoutMs) { Debug.Assert(timeoutMs >= -1); + int spinCount = _spinCount; + // Try to acquire the semaphore or // a) register as a spinner if spinCount > 0 and timeoutMs > 0 // b) register as a waiter if there's already too many spinners or spinCount == 0 and timeoutMs > 0 @@ -53,7 +57,7 @@ public bool Wait(int timeoutMs) } else if (timeoutMs != 0) { - if (_spinCount > 0 && newCounts.SpinnerCount < byte.MaxValue) + if (spinCount > 0 && newCounts.SpinnerCount < byte.MaxValue) { newCounts.IncrementSpinnerCount(); } @@ -85,9 +89,13 @@ public bool Wait(int timeoutMs) counts = countsBeforeUpdate; } +#if CORECLR && TARGET_UNIX + // The PAL's wait subsystem is slower, spin more to compensate for the more expensive wait + spinCount *= 2; +#endif int processorCount = Environment.ProcessorCount; int spinIndex = processorCount > 1 ? 0 : SpinSleep0Threshold; - while (spinIndex < _spinCount) + while (spinIndex < spinCount) { LowLevelSpinWaiter.Wait(spinIndex, SpinSleep0Threshold, processorCount); spinIndex++; @@ -189,6 +197,8 @@ private bool WaitForSignal(int timeoutMs) { Debug.Assert(timeoutMs > 0 || timeoutMs == -1); + _onWait(); + while (true) { if (!WaitCore(timeoutMs)) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index d6b0a9bb2bf3ec..9ba926c943e97e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Threading { @@ -13,8 +15,6 @@ private static class GateThread private const int DequeueDelayThresholdMs = GateThreadDelayMs * 2; private const int GateThreadRunningMask = 0x4; - private static int s_runningState; - private static readonly AutoResetEvent s_runGateThreadEvent = new AutoResetEvent(initialState: true); private const int MaxRuns = 2; @@ -32,6 +32,9 @@ private static void GateThreadStart() CpuUtilizationReader cpuUtilizationReader = default; _ = cpuUtilizationReader.CurrentUtilization; + PortableThreadPool threadPoolInstance = ThreadPoolInstance; + LowLevelLock hillClimbingThreadAdjustmentLock = threadPoolInstance._hillClimbingThreadAdjustmentLock; + while (true) { s_runGateThreadEvent.WaitOne(); @@ -42,20 +45,20 @@ private static void GateThreadStart() Thread.Sleep(GateThreadDelayMs); int cpuUtilization = cpuUtilizationReader.CurrentUtilization; - ThreadPoolInstance._cpuUtilization = cpuUtilization; + threadPoolInstance._cpuUtilization = cpuUtilization; needGateThreadForRuntime = ThreadPool.PerformRuntimeSpecificGateActivities(cpuUtilization); if (!disableStarvationDetection && - ThreadPoolInstance._separated.numRequestedWorkers > 0 && - SufficientDelaySinceLastDequeue()) + threadPoolInstance._separated.numRequestedWorkers > 0 && + SufficientDelaySinceLastDequeue(threadPoolInstance)) { try { - ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Acquire(); - ThreadCounts counts = ThreadPoolInstance._separated.counts.VolatileRead(); + hillClimbingThreadAdjustmentLock.Acquire(); + ThreadCounts counts = threadPoolInstance._separated.counts.VolatileRead(); // don't add a thread if we're at max or if we are already in the process of adding threads - while (counts.NumExistingThreads < ThreadPoolInstance._maxThreads && counts.NumExistingThreads >= counts.NumThreadsGoal) + while (counts.NumExistingThreads < threadPoolInstance._maxThreads && counts.NumExistingThreads >= counts.NumThreadsGoal) { if (debuggerBreakOnWorkStarvation) { @@ -65,11 +68,11 @@ private static void GateThreadStart() ThreadCounts newCounts = counts; short newNumThreadsGoal = (short)(newCounts.NumExistingThreads + 1); newCounts.NumThreadsGoal = newNumThreadsGoal; - ThreadCounts oldCounts = ThreadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); + ThreadCounts oldCounts = threadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); if (oldCounts == counts) { HillClimbing.ThreadPoolHillClimber.ForceChange(newNumThreadsGoal, HillClimbing.StateOrTransition.Starvation); - WorkerThread.MaybeAddWorkingWorker(); + WorkerThread.MaybeAddWorkingWorker(threadPoolInstance); break; } @@ -78,32 +81,32 @@ private static void GateThreadStart() } finally { - ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Release(); + hillClimbingThreadAdjustmentLock.Release(); } } } while ( needGateThreadForRuntime || - ThreadPoolInstance._separated.numRequestedWorkers > 0 || - Interlocked.Decrement(ref s_runningState) > GetRunningStateForNumRuns(0)); + threadPoolInstance._separated.numRequestedWorkers > 0 || + Interlocked.Decrement(ref threadPoolInstance._separated.gateThreadRunningState) > GetRunningStateForNumRuns(0)); } } // called by logic to spawn new worker threads, return true if it's been too long // since the last dequeue operation - takes number of worker threads into account // in deciding "too long" - private static bool SufficientDelaySinceLastDequeue() + private static bool SufficientDelaySinceLastDequeue(PortableThreadPool threadPoolInstance) { - int delay = Environment.TickCount - Volatile.Read(ref ThreadPoolInstance._separated.lastDequeueTime); + int delay = Environment.TickCount - Volatile.Read(ref threadPoolInstance._separated.lastDequeueTime); int minimumDelay; - if (ThreadPoolInstance._cpuUtilization < CpuUtilizationLow) + if (threadPoolInstance._cpuUtilization < CpuUtilizationLow) { minimumDelay = GateThreadDelayMs; } else { - ThreadCounts counts = ThreadPoolInstance._separated.counts.VolatileRead(); + ThreadCounts counts = threadPoolInstance._separated.counts.VolatileRead(); int numThreads = counts.NumThreadsGoal; minimumDelay = numThreads * DequeueDelayThresholdMs; } @@ -111,29 +114,28 @@ private static bool SufficientDelaySinceLastDequeue() } // This is called by a worker thread - internal static void EnsureRunning() + internal static void EnsureRunning(PortableThreadPool threadPoolInstance) { - int numRunsMask = Interlocked.Exchange(ref s_runningState, GetRunningStateForNumRuns(MaxRuns)); - if ((numRunsMask & GateThreadRunningMask) == 0) + // The callers ensure that this speculative load is sufficient to ensure that the gate thread is activated when + // it is needed + if (threadPoolInstance._separated.gateThreadRunningState != GetRunningStateForNumRuns(MaxRuns)) { - bool created = false; - try - { - CreateGateThread(); - created = true; - } - finally - { - if (!created) - { - Interlocked.Exchange(ref s_runningState, 0); - } - } + EnsureRunningSlow(threadPoolInstance); } - else if (numRunsMask == GetRunningStateForNumRuns(0)) + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void EnsureRunningSlow(PortableThreadPool threadPoolInstance) + { + int numRunsMask = Interlocked.Exchange(ref threadPoolInstance._separated.gateThreadRunningState, GetRunningStateForNumRuns(MaxRuns)); + if (numRunsMask == GetRunningStateForNumRuns(0)) { s_runGateThreadEvent.Set(); } + else if ((numRunsMask & GateThreadRunningMask) == 0) + { + CreateGateThread(threadPoolInstance); + } } private static int GetRunningStateForNumRuns(int numRuns) @@ -143,16 +145,29 @@ private static int GetRunningStateForNumRuns(int numRuns) return GateThreadRunningMask | numRuns; } - private static void CreateGateThread() + [MethodImpl(MethodImplOptions.NoInlining)] + private static void CreateGateThread(PortableThreadPool threadPoolInstance) { - Thread gateThread = new Thread(GateThreadStart); - gateThread.IsThreadPoolThread = true; - gateThread.IsBackground = true; - gateThread.Name = ".NET ThreadPool Gate"; - gateThread.Start(); + bool created = false; + try + { + Thread gateThread = new Thread(GateThreadStart); + gateThread.IsThreadPoolThread = true; + gateThread.IsBackground = true; + gateThread.Name = ".NET ThreadPool Gate"; + gateThread.Start(); + created = true; + } + finally + { + if (!created) + { + Interlocked.Exchange(ref threadPoolInstance._separated.gateThreadRunningState, 0); + } + } } } - internal static void EnsureGateThreadRunning() => GateThread.EnsureRunning(); + internal static void EnsureGateThreadRunning() => GateThread.EnsureRunning(ThreadPoolInstance); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs index eaf62383114438..2e975d74d50dc2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs @@ -320,7 +320,8 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // // If the result was positive, and CPU is > 95%, refuse the move. // - if (move > 0.0 && ThreadPoolInstance._cpuUtilization > CpuUtilizationHigh) + PortableThreadPool threadPoolInstance = ThreadPoolInstance; + if (move > 0.0 && threadPoolInstance._cpuUtilization > CpuUtilizationHigh) move = 0.0; // @@ -339,8 +340,8 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // // Make sure our control setting is within the ThreadPool's limits // - int maxThreads = ThreadPoolInstance._maxThreads; - int minThreads = ThreadPoolInstance._minThreads; + int maxThreads = threadPoolInstance._maxThreads; + int minThreads = threadPoolInstance._minThreads; _currentControlSetting = Math.Min(maxThreads - newThreadWaveMagnitude, _currentControlSetting); _currentControlSetting = Math.Max(minThreads, _currentControlSetting); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs index b434d6b7efd334..0c11ef112162a0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs @@ -275,7 +275,8 @@ private void WaitThreadStart() /// private void ProcessRemovals() { - ThreadPoolInstance._waitThreadLock.Acquire(); + PortableThreadPool threadPoolInstance = ThreadPoolInstance; + threadPoolInstance._waitThreadLock.Acquire(); try { Debug.Assert(_numPendingRemoves >= 0); @@ -322,7 +323,7 @@ private void ProcessRemovals() } finally { - ThreadPoolInstance._waitThreadLock.Release(); + threadPoolInstance._waitThreadLock.Release(); } } @@ -398,7 +399,8 @@ private void UnregisterWait(RegisteredWaitHandle handle, bool blocking) { bool pendingRemoval = false; // TODO: Optimization: Try to unregister wait directly if it isn't being waited on. - ThreadPoolInstance._waitThreadLock.Acquire(); + PortableThreadPool threadPoolInstance = ThreadPoolInstance; + threadPoolInstance._waitThreadLock.Acquire(); try { // If this handle is not already pending removal and hasn't already been removed @@ -411,7 +413,7 @@ private void UnregisterWait(RegisteredWaitHandle handle, bool blocking) } finally { - ThreadPoolInstance._waitThreadLock.Release(); + threadPoolInstance._waitThreadLock.Release(); } if (blocking) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs index c229295cf66699..df98a44ae31103 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @@ -13,54 +13,67 @@ private static class WorkerThread /// /// Semaphore for controlling how many threads are currently working. /// - private static readonly LowLevelLifoSemaphore s_semaphore = new LowLevelLifoSemaphore(0, MaxPossibleThreadCount, SemaphoreSpinCount); - - /// - /// Maximum number of spins a thread pool worker thread performs before waiting for work - /// - private static int SemaphoreSpinCount - { - get => AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.UnfairSemaphoreSpinLimit", 70, false); - } + private static readonly LowLevelLifoSemaphore s_semaphore = + new LowLevelLifoSemaphore( + 0, + MaxPossibleThreadCount, + AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.UnfairSemaphoreSpinLimit", 70, false), + onWait: () => + { + PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; + if (log.IsEnabled()) + { + log.ThreadPoolWorkerThreadWait(ThreadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); + } + }); private static void WorkerThreadStart() { Thread.CurrentThread.SetThreadPoolWorkerThreadName(); + PortableThreadPool threadPoolInstance = ThreadPoolInstance; + PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; if (log.IsEnabled()) { - log.ThreadPoolWorkerThreadStart(ThreadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); + log.ThreadPoolWorkerThreadStart(threadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); } + LowLevelLock hillClimbingThreadAdjustmentLock = threadPoolInstance._hillClimbingThreadAdjustmentLock; + while (true) { while (WaitForRequest()) { - if (TakeActiveRequest()) + bool alreadyRemovedWorkingWorker = false; + while (TakeActiveRequest(threadPoolInstance)) { - Volatile.Write(ref ThreadPoolInstance._separated.lastDequeueTime, Environment.TickCount); - if (ThreadPoolWorkQueue.Dispatch()) + Volatile.Write(ref threadPoolInstance._separated.lastDequeueTime, Environment.TickCount); + if (!ThreadPoolWorkQueue.Dispatch()) { - // If the queue runs out of work for us, we need to update the number of working workers to reflect that we are done working for now - RemoveWorkingWorker(); + // ShouldStopProcessingWorkNow() caused the thread to stop processing work, and it would have + // already removed this working worker in the counts + alreadyRemovedWorkingWorker = true; + break; } } - else + + if (!alreadyRemovedWorkingWorker) { - // If we woke up but couldn't find a request, we need to update the number of working workers to reflect that we are done working for now - RemoveWorkingWorker(); + // If we woke up but couldn't find a request, or ran out of work items to process, we need to update + // the number of working workers to reflect that we are done working for now + RemoveWorkingWorker(threadPoolInstance); } } - ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Acquire(); + hillClimbingThreadAdjustmentLock.Acquire(); try { // At this point, the thread's wait timed out. We are shutting down this thread. // We are going to decrement the number of exisiting threads to no longer include this one // and then change the max number of threads in the thread pool to reflect that we don't need as many // as we had. Finally, we are going to tell hill climbing that we changed the max number of threads. - ThreadCounts counts = ThreadPoolInstance._separated.counts.VolatileRead(); + ThreadCounts counts = threadPoolInstance._separated.counts.VolatileRead(); while (true) { // Since this thread is currently registered as an existing thread, if more work comes in meanwhile, @@ -76,10 +89,10 @@ private static void WorkerThreadStart() ThreadCounts newCounts = counts; newCounts.SubtractNumExistingThreads(1); short newNumExistingThreads = (short)(numExistingThreads - 1); - short newNumThreadsGoal = Math.Max(ThreadPoolInstance._minThreads, Math.Min(newNumExistingThreads, newCounts.NumThreadsGoal)); + short newNumThreadsGoal = Math.Max(threadPoolInstance._minThreads, Math.Min(newNumExistingThreads, newCounts.NumThreadsGoal)); newCounts.NumThreadsGoal = newNumThreadsGoal; - ThreadCounts oldCounts = ThreadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); + ThreadCounts oldCounts = threadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); if (oldCounts == counts) { HillClimbing.ThreadPoolHillClimber.ForceChange(newNumThreadsGoal, HillClimbing.StateOrTransition.ThreadTimedOut); @@ -96,7 +109,7 @@ private static void WorkerThreadStart() } finally { - ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Release(); + hillClimbingThreadAdjustmentLock.Release(); } } } @@ -105,28 +118,19 @@ private static void WorkerThreadStart() /// Waits for a request to work. /// /// If this thread was woken up before it timed out. - private static bool WaitForRequest() - { - PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; - if (log.IsEnabled()) - { - log.ThreadPoolWorkerThreadWait(ThreadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); - } - - return s_semaphore.Wait(ThreadPoolThreadTimeoutMs); - } + private static bool WaitForRequest() => s_semaphore.Wait(ThreadPoolThreadTimeoutMs); /// /// Reduce the number of working workers by one, but maybe add back a worker (possibily this thread) if a thread request comes in while we are marking this thread as not working. /// - private static void RemoveWorkingWorker() + private static void RemoveWorkingWorker(PortableThreadPool threadPoolInstance) { - ThreadCounts currentCounts = ThreadPoolInstance._separated.counts.VolatileRead(); + ThreadCounts currentCounts = threadPoolInstance._separated.counts.VolatileRead(); while (true) { ThreadCounts newCounts = currentCounts; newCounts.SubtractNumProcessingWork(1); - ThreadCounts oldCounts = ThreadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, currentCounts); + ThreadCounts oldCounts = threadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, currentCounts); if (oldCounts == currentCounts) { @@ -135,19 +139,34 @@ private static void RemoveWorkingWorker() currentCounts = oldCounts; } + if (currentCounts.NumProcessingWork > 1) + { + // In highly bursty cases with short bursts of work, especially in the portable thread pool implementation, + // worker threads are being released and entering Dispatch very quickly, not finding much work in Dispatch, + // and soon afterwards going back to Dispatch, causing extra thrashing on data and some interlocked + // operations. If this is not the last thread to stop processing work, introduce a slight delay to help + // other threads make more efficient progress. The spin-wait is mainly for when the sleep is not effective + // due to there being no other threads to schedule. + Thread.UninterruptibleSleep0(); + if (!Environment.IsSingleProcessor) + { + Thread.SpinWait(1); + } + } + // It's possible that we decided we had thread requests just before a request came in, // but reduced the worker count *after* the request came in. In this case, we might // miss the notification of a thread request. So we wake up a thread (maybe this one!) // if there is work to do. - if (ThreadPoolInstance._separated.numRequestedWorkers > 0) + if (threadPoolInstance._separated.numRequestedWorkers > 0) { - MaybeAddWorkingWorker(); + MaybeAddWorkingWorker(threadPoolInstance); } } - internal static void MaybeAddWorkingWorker() + internal static void MaybeAddWorkingWorker(PortableThreadPool threadPoolInstance) { - ThreadCounts counts = ThreadPoolInstance._separated.counts.VolatileRead(); + ThreadCounts counts = threadPoolInstance._separated.counts.VolatileRead(); short numExistingThreads, numProcessingWork, newNumExistingThreads, newNumProcessingWork; while (true) { @@ -165,7 +184,7 @@ internal static void MaybeAddWorkingWorker() newCounts.NumProcessingWork = newNumProcessingWork; newCounts.NumExistingThreads = newNumExistingThreads; - ThreadCounts oldCounts = ThreadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); + ThreadCounts oldCounts = threadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); if (oldCounts == counts) { @@ -191,14 +210,14 @@ internal static void MaybeAddWorkingWorker() continue; } - counts = ThreadPoolInstance._separated.counts.VolatileRead(); + counts = threadPoolInstance._separated.counts.VolatileRead(); while (true) { ThreadCounts newCounts = counts; newCounts.SubtractNumProcessingWork((short)toCreate); newCounts.SubtractNumExistingThreads((short)toCreate); - ThreadCounts oldCounts = ThreadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); + ThreadCounts oldCounts = threadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); if (oldCounts == counts) { break; @@ -215,9 +234,9 @@ internal static void MaybeAddWorkingWorker() /// there are more worker threads in the thread pool than we currently want. /// /// Whether or not this thread should stop processing work even if there is still work in the queue. - internal static bool ShouldStopProcessingWorkNow() + internal static bool ShouldStopProcessingWorkNow(PortableThreadPool threadPoolInstance) { - ThreadCounts counts = ThreadPoolInstance._separated.counts.VolatileRead(); + ThreadCounts counts = threadPoolInstance._separated.counts.VolatileRead(); while (true) { // When there are more threads processing work than the thread count goal, hill climbing must have decided @@ -235,7 +254,7 @@ internal static bool ShouldStopProcessingWorkNow() ThreadCounts newCounts = counts; newCounts.SubtractNumProcessingWork(1); - ThreadCounts oldCounts = ThreadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); + ThreadCounts oldCounts = threadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); if (oldCounts == counts) { @@ -245,12 +264,12 @@ internal static bool ShouldStopProcessingWorkNow() } } - private static bool TakeActiveRequest() + private static bool TakeActiveRequest(PortableThreadPool threadPoolInstance) { - int count = ThreadPoolInstance._separated.numRequestedWorkers; + int count = threadPoolInstance._separated.numRequestedWorkers; while (count > 0) { - int prevCount = Interlocked.CompareExchange(ref ThreadPoolInstance._separated.numRequestedWorkers, count - 1, count); + int prevCount = Interlocked.CompareExchange(ref threadPoolInstance._separated.numRequestedWorkers, count - 1, count); if (prevCount == count) { return true; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index dab4432b2f2346..35468c0b949e2e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -41,7 +41,7 @@ internal sealed partial class PortableThreadPool private short _maxThreads; private readonly LowLevelLock _maxMinThreadLock = new LowLevelLock(); - [StructLayout(LayoutKind.Explicit, Size = Internal.PaddingHelpers.CACHE_LINE_SIZE * 5)] + [StructLayout(LayoutKind.Explicit, Size = Internal.PaddingHelpers.CACHE_LINE_SIZE * 6)] private struct CacheLineSeparated { [FieldOffset(Internal.PaddingHelpers.CACHE_LINE_SIZE * 1)] @@ -56,6 +56,8 @@ private struct CacheLineSeparated public int nextCompletedWorkRequestsTime; [FieldOffset(Internal.PaddingHelpers.CACHE_LINE_SIZE * 4)] public volatile int numRequestedWorkers; + [FieldOffset(Internal.PaddingHelpers.CACHE_LINE_SIZE * 5)] + public int gateThreadRunningState; } private CacheLineSeparated _separated; @@ -117,7 +119,7 @@ public bool SetMinThreads(int minThreads) { if (_separated.numRequestedWorkers > 0) { - WorkerThread.MaybeAddWorkingWorker(); + WorkerThread.MaybeAddWorkingWorker(this); } break; } @@ -205,32 +207,26 @@ private object CreateThreadLocalCompletionCountObject() return threadLocalCompletionCountObject; } - private void NotifyWorkItemProgress(object threadLocalCompletionCountObject) + private void NotifyWorkItemProgress(object threadLocalCompletionCountObject, int currentTimeMs) { ThreadInt64PersistentCounter.Increment(threadLocalCompletionCountObject); Volatile.Write(ref _separated.lastDequeueTime, Environment.TickCount); - if (ShouldAdjustMaxWorkersActive() && _hillClimbingThreadAdjustmentLock.TryAcquire()) + if (ShouldAdjustMaxWorkersActive(currentTimeMs)) { - try - { - AdjustMaxWorkersActive(); - } - finally - { - _hillClimbingThreadAdjustmentLock.Release(); - } + AdjustMaxWorkersActive(); } } - internal void NotifyWorkItemProgress() => NotifyWorkItemProgress(GetOrCreateThreadLocalCompletionCountObject()); + internal void NotifyWorkItemProgress() => + NotifyWorkItemProgress(GetOrCreateThreadLocalCompletionCountObject(), Environment.TickCount); - internal bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject) + internal bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int currentTimeMs) { Debug.Assert(threadLocalCompletionCountObject != null); - NotifyWorkItemProgress(threadLocalCompletionCountObject!); - return !WorkerThread.ShouldStopProcessingWorkNow(); + NotifyWorkItemProgress(threadLocalCompletionCountObject!, currentTimeMs); + return !WorkerThread.ShouldStopProcessingWorkNow(this); } // @@ -239,67 +235,80 @@ internal bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject) // private void AdjustMaxWorkersActive() { - _hillClimbingThreadAdjustmentLock.VerifyIsLocked(); - long startTime = _currentSampleStartTime; - long endTime = Stopwatch.GetTimestamp(); - long freq = Stopwatch.Frequency; - - double elapsedSeconds = (double)(endTime - startTime) / freq; + LowLevelLock hillClimbingThreadAdjustmentLock = _hillClimbingThreadAdjustmentLock; + if (!hillClimbingThreadAdjustmentLock.TryAcquire()) + { + // The lock is held by someone else, they will take care of this for us + return; + } - if (elapsedSeconds * 1000 >= _threadAdjustmentIntervalMs / 2) + try { - int currentTicks = Environment.TickCount; - int totalNumCompletions = (int)_completionCounter.Count; - int numCompletions = totalNumCompletions - _separated.priorCompletionCount; + long startTime = _currentSampleStartTime; + long endTime = Stopwatch.GetTimestamp(); + long freq = Stopwatch.Frequency; - ThreadCounts currentCounts = _separated.counts.VolatileRead(); - int newMax; - (newMax, _threadAdjustmentIntervalMs) = HillClimbing.ThreadPoolHillClimber.Update(currentCounts.NumThreadsGoal, elapsedSeconds, numCompletions); + double elapsedSeconds = (double)(endTime - startTime) / freq; - while (newMax != currentCounts.NumThreadsGoal) + if (elapsedSeconds * 1000 >= _threadAdjustmentIntervalMs / 2) { - ThreadCounts newCounts = currentCounts; - newCounts.NumThreadsGoal = (short)newMax; + int currentTicks = Environment.TickCount; + int totalNumCompletions = (int)_completionCounter.Count; + int numCompletions = totalNumCompletions - _separated.priorCompletionCount; + + ThreadCounts currentCounts = _separated.counts.VolatileRead(); + int newMax; + (newMax, _threadAdjustmentIntervalMs) = HillClimbing.ThreadPoolHillClimber.Update(currentCounts.NumThreadsGoal, elapsedSeconds, numCompletions); - ThreadCounts oldCounts = _separated.counts.InterlockedCompareExchange(newCounts, currentCounts); - if (oldCounts == currentCounts) + while (newMax != currentCounts.NumThreadsGoal) { - // - // If we're increasing the max, inject a thread. If that thread finds work, it will inject - // another thread, etc., until nobody finds work or we reach the new maximum. - // - // If we're reducing the max, whichever threads notice this first will sleep and timeout themselves. - // - if (newMax > oldCounts.NumThreadsGoal) + ThreadCounts newCounts = currentCounts; + newCounts.NumThreadsGoal = (short)newMax; + + ThreadCounts oldCounts = _separated.counts.InterlockedCompareExchange(newCounts, currentCounts); + if (oldCounts == currentCounts) { - WorkerThread.MaybeAddWorkingWorker(); + // + // If we're increasing the max, inject a thread. If that thread finds work, it will inject + // another thread, etc., until nobody finds work or we reach the new maximum. + // + // If we're reducing the max, whichever threads notice this first will sleep and timeout themselves. + // + if (newMax > oldCounts.NumThreadsGoal) + { + WorkerThread.MaybeAddWorkingWorker(this); + } + break; } - break; - } - if (oldCounts.NumThreadsGoal > currentCounts.NumThreadsGoal && oldCounts.NumThreadsGoal >= newMax) - { - // someone (probably the gate thread) increased the thread count more than - // we are about to do. Don't interfere. - break; + if (oldCounts.NumThreadsGoal > currentCounts.NumThreadsGoal && oldCounts.NumThreadsGoal >= newMax) + { + // someone (probably the gate thread) increased the thread count more than + // we are about to do. Don't interfere. + break; + } + + currentCounts = oldCounts; } - currentCounts = oldCounts; + _separated.priorCompletionCount = totalNumCompletions; + _separated.nextCompletedWorkRequestsTime = currentTicks + _threadAdjustmentIntervalMs; + Volatile.Write(ref _separated.priorCompletedWorkRequestsTime, currentTicks); + _currentSampleStartTime = endTime; } - - _separated.priorCompletionCount = totalNumCompletions; - _separated.nextCompletedWorkRequestsTime = currentTicks + _threadAdjustmentIntervalMs; - Volatile.Write(ref _separated.priorCompletedWorkRequestsTime, currentTicks); - _currentSampleStartTime = endTime; + } + finally + { + hillClimbingThreadAdjustmentLock.Release(); } } - private bool ShouldAdjustMaxWorkersActive() + private bool ShouldAdjustMaxWorkersActive(int currentTimeMs) { // We need to subtract by prior time because Environment.TickCount can wrap around, making a comparison of absolute times unreliable. int priorTime = Volatile.Read(ref _separated.priorCompletedWorkRequestsTime); int requiredInterval = _separated.nextCompletedWorkRequestsTime - priorTime; - int elapsedInterval = Environment.TickCount - priorTime; + int elapsedInterval = currentTimeMs - priorTime; if (elapsedInterval >= requiredInterval) { // Avoid trying to adjust the thread count goal if there are already more threads than the thread count goal. @@ -317,9 +326,11 @@ private bool ShouldAdjustMaxWorkersActive() internal void RequestWorker() { + // The order of operations here is important. MaybeAddWorkingWorker() and EnsureRunning() use speculative checks to + // do their work and the memory barrier from the interlocked operation is necessary in this case for correctness. Interlocked.Increment(ref _separated.numRequestedWorkers); - WorkerThread.MaybeAddWorkingWorker(); - GateThread.EnsureRunning(); + WorkerThread.MaybeAddWorkingWorker(this); + GateThread.EnsureRunning(this); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index 255f1703ccdf98..c71005088bcd28 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -118,8 +118,8 @@ public static void GetAvailableThreads(out int workerThreads, out int completion internal static void NotifyWorkItemProgress() => PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject) => - PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(threadLocalCompletionCountObject); + internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int currentTimeMs) => + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(threadLocalCompletionCountObject, currentTimeMs); internal static object GetOrCreateThreadLocalCompletionCountObject() => PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index 845d8ce4e184b1..38c4e8d9d3365a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -23,7 +23,6 @@ namespace System.Threading { - [StructLayout(LayoutKind.Sequential)] // enforce layout so that padding reduces false sharing internal sealed class ThreadPoolWorkQueue { internal static class WorkStealingQueueList @@ -118,33 +117,7 @@ public void LocalPush(object obj) // We're going to increment the tail; if we'll overflow, then we need to reset our counts if (tail == int.MaxValue) { - bool lockTaken = false; - try - { - m_foreignLock.Enter(ref lockTaken); - - if (m_tailIndex == int.MaxValue) - { - // - // Rather than resetting to zero, we'll just mask off the bits we don't care about. - // This way we don't need to rearrange the items already in the queue; they'll be found - // correctly exactly where they are. One subtlety here is that we need to make sure that - // if head is currently < tail, it remains that way. This happens to just fall out from - // the bit-masking, because we only do this if tail == int.MaxValue, meaning that all - // bits are set, so all of the bits we're keeping will also be set. Thus it's impossible - // for the head to end up > than the tail, since you can't set any more bits than all of - // them. - // - m_headIndex &= m_mask; - m_tailIndex = tail = m_tailIndex & m_mask; - Debug.Assert(m_headIndex <= m_tailIndex); - } - } - finally - { - if (lockTaken) - m_foreignLock.Exit(useMemoryBarrier: true); - } + tail = LocalPush_HandleTailOverflow(); } // When there are at least 2 elements' worth of space, we can take the fast path. @@ -190,6 +163,41 @@ public void LocalPush(object obj) } } + [MethodImpl(MethodImplOptions.NoInlining)] + private int LocalPush_HandleTailOverflow() + { + bool lockTaken = false; + try + { + m_foreignLock.Enter(ref lockTaken); + + int tail = m_tailIndex; + if (tail == int.MaxValue) + { + // + // Rather than resetting to zero, we'll just mask off the bits we don't care about. + // This way we don't need to rearrange the items already in the queue; they'll be found + // correctly exactly where they are. One subtlety here is that we need to make sure that + // if head is currently < tail, it remains that way. This happens to just fall out from + // the bit-masking, because we only do this if tail == int.MaxValue, meaning that all + // bits are set, so all of the bits we're keeping will also be set. Thus it's impossible + // for the head to end up > than the tail, since you can't set any more bits than all of + // them. + // + m_headIndex &= m_mask; + m_tailIndex = tail = m_tailIndex & m_mask; + Debug.Assert(m_headIndex <= m_tailIndex); + } + + return tail; + } + finally + { + if (lockTaken) + m_foreignLock.Exit(useMemoryBarrier: true); + } + } + public bool LocalFindAndPop(object obj) { // Fast path: check the tail. If equal, we can skip the lock. @@ -385,11 +393,17 @@ public int Count internal readonly ConcurrentQueue? timeSensitiveWorkQueue = ThreadPool.SupportsTimeSensitiveWorkItems ? new ConcurrentQueue() : null; - private readonly Internal.PaddingFor32 pad1; + [StructLayout(LayoutKind.Sequential)] + private struct CacheLineSeparated + { + private readonly Internal.PaddingFor32 pad1; - private volatile int numOutstandingThreadRequests; + public volatile int numOutstandingThreadRequests; - private readonly Internal.PaddingFor32 pad2; + private readonly Internal.PaddingFor32 pad2; + } + + private CacheLineSeparated _separated; public ThreadPoolWorkQueue() { @@ -436,10 +450,10 @@ internal void EnsureThreadRequested() // CoreCLR: Note that there is a separate count in the VM which has already been incremented // by the VM by the time we reach this point. // - int count = numOutstandingThreadRequests; + int count = _separated.numOutstandingThreadRequests; while (count < Environment.ProcessorCount) { - int prev = Interlocked.CompareExchange(ref numOutstandingThreadRequests, count + 1, count); + int prev = Interlocked.CompareExchange(ref _separated.numOutstandingThreadRequests, count + 1, count); if (prev == count) { ThreadPool.RequestWorkerThread(); @@ -458,10 +472,10 @@ internal void MarkThreadRequestSatisfied() // CoreCLR: Note that there is a separate count in the VM which has already been decremented // by the VM by the time we reach this point. // - int count = numOutstandingThreadRequests; + int count = _separated.numOutstandingThreadRequests; while (count > 0) { - int prev = Interlocked.CompareExchange(ref numOutstandingThreadRequests, count - 1, count); + int prev = Interlocked.CompareExchange(ref _separated.numOutstandingThreadRequests, count - 1, count); if (prev == count) { break; @@ -709,11 +723,11 @@ internal static bool Dispatch() // Notify the VM that we executed this workitem. This is also our opportunity to ask whether Hill Climbing wants // us to return the thread to the pool or not. // - if (!ThreadPool.NotifyWorkItemComplete(threadLocalCompletionCountObject)) + int currentTickCount = Environment.TickCount; + if (!ThreadPool.NotifyWorkItemComplete(threadLocalCompletionCountObject, currentTickCount)) return false; // Check if the dispatch quantum has expired - int currentTickCount = Environment.TickCount; if ((uint)(currentTickCount - startTickCount) < DispatchQuantumMs) { continue; From 13c2d6274588a6751f411e099160bc600a8ee771 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 1 Jun 2020 02:05:31 -0700 Subject: [PATCH 20/48] Fix starvation heuristic --- .../Threading/PortableThreadPool.GateThread.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index 9ba926c943e97e..2a237a53acbbed 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -57,8 +57,19 @@ private static void GateThreadStart() { hillClimbingThreadAdjustmentLock.Acquire(); ThreadCounts counts = threadPoolInstance._separated.counts.VolatileRead(); - // don't add a thread if we're at max or if we are already in the process of adding threads - while (counts.NumExistingThreads < threadPoolInstance._maxThreads && counts.NumExistingThreads >= counts.NumThreadsGoal) + + // Don't add a thread if we're at max or if we are already in the process of adding threads. + // This logic is slightly different from the native implementation in CoreCLR because there are + // no retired threads. In the native implementation, when hill climbing reduces the thread count + // goal, threads that are stopped from processing work are switched to "retired" state, and they + // don't count towards the equivalent existing thread count. In this implementation, the + // existing thread count includes any worker thread that has not yet exited, including those + // stopped from working by hill climbing, so here the number of threads processing work, instead + // of the number of existing threads, is compared with the goal. There may be alternative + // solutions, for now this is only to maintain consistency in behavior. + while ( + counts.NumExistingThreads < threadPoolInstance._maxThreads && + counts.NumProcessingWork >= counts.NumThreadsGoal) { if (debuggerBreakOnWorkStarvation) { @@ -66,8 +77,9 @@ private static void GateThreadStart() } ThreadCounts newCounts = counts; - short newNumThreadsGoal = (short)(newCounts.NumExistingThreads + 1); + short newNumThreadsGoal = (short)(counts.NumThreadsGoal + 1); newCounts.NumThreadsGoal = newNumThreadsGoal; + ThreadCounts oldCounts = threadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); if (oldCounts == counts) { From dd234728cfa9e1b7cd17062e1ee9c1b186549ace Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 22 May 2020 23:11:50 -0700 Subject: [PATCH 21/48] Implement worker tracking --- .../System/Threading/ThreadPool.CoreCLR.cs | 11 +- src/coreclr/src/vm/ecalllist.h | 2 +- .../System.Private.CoreLib.Shared.projitems | 1 + .../PortableThreadPool.GateThread.cs | 10 +- .../PortableThreadPool.WorkerTracking.cs | 127 ++++++++++++++++++ .../PortableThreadPoolEventSource.cs | 23 ++++ .../System/Threading/ThreadPool.Portable.cs | 3 +- .../src/System/Threading/ThreadPool.cs | 2 +- 8 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerTracking.cs diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index b2b2c3171c65a2..e72dcc01a23cd9 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -168,6 +168,8 @@ public static partial class ThreadPool internal static readonly bool UsePortableThreadPool = InitializeConfigAndDetermineUsePortableThreadPool(); internal static bool SupportsTimeSensitiveWorkItems => UsePortableThreadPool; + // This needs to be initialized after UsePortableThreadPool above, as it may depend on UsePortableThreadPool and the + // config initialization internal static readonly bool EnableWorkerTracking = GetEnableWorkerTracking(); @@ -221,6 +223,11 @@ private static extern unsafe int GetNextConfigUInt32Value( out bool isBoolean, out char* appContextConfigName); + private static bool GetEnableWorkerTracking() => + UsePortableThreadPool + ? AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false) + : GetEnableWorkerTrackingNative(); + public static bool SetMaxThreads(int workerThreads, int completionPortThreads) { if (workerThreads < 0 || completionPortThreads < 0) @@ -443,7 +450,7 @@ internal static void ReportThreadStatus(bool isWorking) { if (UsePortableThreadPool) { - // TODO: PortableThreadPool - Implement worker tracking + PortableThreadPool.ThreadPoolInstance.ReportThreadStatus(isWorking); return; } @@ -471,7 +478,7 @@ internal static void NotifyWorkItemProgress() UsePortableThreadPool ? PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject() : null; [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool GetEnableWorkerTracking(); + private static extern bool GetEnableWorkerTrackingNative(); [MethodImpl(MethodImplOptions.InternalCall)] private static extern IntPtr RegisterWaitForSingleObjectNative( diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h index 008c6d0974294d..0a50ad25fe44a9 100644 --- a/src/coreclr/src/vm/ecalllist.h +++ b/src/coreclr/src/vm/ecalllist.h @@ -648,7 +648,7 @@ FCFuncStart(gThreadPoolFuncs) FCFuncElement("GetMaxThreadsNative", ThreadPoolNative::CorGetMaxThreads) FCFuncElement("NotifyWorkItemCompleteNative", ThreadPoolNative::NotifyRequestComplete) FCFuncElement("NotifyWorkItemProgressNative", ThreadPoolNative::NotifyRequestProgress) - FCFuncElement("GetEnableWorkerTracking", ThreadPoolNative::GetEnableWorkerTracking) + FCFuncElement("GetEnableWorkerTrackingNative", ThreadPoolNative::GetEnableWorkerTracking) FCFuncElement("ReportThreadStatusNative", ThreadPoolNative::ReportThreadStatus) QCFuncElement("RequestWorkerThreadNative", ThreadPoolNative::RequestWorkerThread) QCFuncElement("PerformRuntimeSpecificGateActivitiesNative", ThreadPoolNative::PerformGateActivities) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 6e6782d8d7e03a..7a8192e393a4fe 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1945,6 +1945,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index 2a237a53acbbed..9c964a122e02d8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Diagnostics.Tracing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -19,7 +20,6 @@ private static class GateThread private const int MaxRuns = 2; - // TODO: PortableThreadPool - CoreCLR: Worker Tracking in CoreCLR? (Config name: ThreadPool_EnableWorkerTracking) private static void GateThreadStart() { bool disableStarvationDetection = @@ -33,6 +33,7 @@ private static void GateThreadStart() _ = cpuUtilizationReader.CurrentUtilization; PortableThreadPool threadPoolInstance = ThreadPoolInstance; + PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; LowLevelLock hillClimbingThreadAdjustmentLock = threadPoolInstance._hillClimbingThreadAdjustmentLock; while (true) @@ -44,6 +45,13 @@ private static void GateThreadStart() { Thread.Sleep(GateThreadDelayMs); + if (ThreadPool.EnableWorkerTracking && + log.IsEnabled(EventLevel.Verbose, PortableThreadPoolEventSource.Keywords.ThreadingKeyword)) + { + log.ThreadPoolWorkingThreadCount( + threadPoolInstance.GetAndResetHighWatermarkCountOfThreadsProcessingUserCallbacks()); + } + int cpuUtilization = cpuUtilizationReader.CurrentUtilization; threadPoolInstance._cpuUtilization = cpuUtilization; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerTracking.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerTracking.cs new file mode 100644 index 00000000000000..292cdda4f15cc8 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerTracking.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.Threading +{ + internal partial class PortableThreadPool + { + private CountsOfThreadsProcessingUserCallbacks _countsOfThreadsProcessingUserCallbacks; + + public void ReportThreadStatus(bool isProcessingUserCallback) + { + CountsOfThreadsProcessingUserCallbacks counts = _countsOfThreadsProcessingUserCallbacks; + while (true) + { + CountsOfThreadsProcessingUserCallbacks newCounts = counts; + if (isProcessingUserCallback) + { + newCounts.IncrementCurrent(); + } + else + { + newCounts.DecrementCurrent(); + } + + CountsOfThreadsProcessingUserCallbacks countsBeforeUpdate = + _countsOfThreadsProcessingUserCallbacks.InterlockedCompareExchange(newCounts, counts); + if (countsBeforeUpdate == counts) + { + break; + } + + counts = countsBeforeUpdate; + } + } + + private short GetAndResetHighWatermarkCountOfThreadsProcessingUserCallbacks() + { + CountsOfThreadsProcessingUserCallbacks counts = _countsOfThreadsProcessingUserCallbacks; + while (true) + { + CountsOfThreadsProcessingUserCallbacks newCounts = counts; + newCounts.ResetHighWatermark(); + + CountsOfThreadsProcessingUserCallbacks countsBeforeUpdate = + _countsOfThreadsProcessingUserCallbacks.InterlockedCompareExchange(newCounts, counts); + if (countsBeforeUpdate == counts || countsBeforeUpdate.HighWatermark == countsBeforeUpdate.Current) + { + return countsBeforeUpdate.HighWatermark; + } + + counts = countsBeforeUpdate; + } + } + + /// + /// Tracks thread count information that is used when the EnableWorkerTracking config option is enabled. + /// + private struct CountsOfThreadsProcessingUserCallbacks + { + private const byte CurrentShift = 0; + private const byte HighWatermarkShift = 16; + + private uint _data; + + private CountsOfThreadsProcessingUserCallbacks(uint data) => _data = data; + + private short GetInt16Value(byte shift) => (short)(_data >> shift); + private void SetInt16Value(short value, byte shift) => + _data = (_data & ~((uint)ushort.MaxValue << shift)) | ((uint)(ushort)value << shift); + + /// + /// Number of threads currently processing user callbacks + /// + public short Current => GetInt16Value(CurrentShift); + + public void IncrementCurrent() + { + if (Current < HighWatermark) + { + _data += (uint)1 << CurrentShift; + } + else + { + Debug.Assert(Current == HighWatermark); + Debug.Assert(Current != short.MaxValue); + _data += ((uint)1 << CurrentShift) | ((uint)1 << HighWatermarkShift); + } + } + + public void DecrementCurrent() + { + Debug.Assert(Current > 0); + _data -= (uint)1 << CurrentShift; + } + + /// + /// The high-warkmark of number of threads processing user callbacks since the high-watermark was last reset + /// + public short HighWatermark => GetInt16Value(HighWatermarkShift); + + public void ResetHighWatermark() => SetInt16Value(Current, HighWatermarkShift); + + public CountsOfThreadsProcessingUserCallbacks InterlockedCompareExchange( + CountsOfThreadsProcessingUserCallbacks newCounts, + CountsOfThreadsProcessingUserCallbacks oldCounts) + { + return + new CountsOfThreadsProcessingUserCallbacks( + Interlocked.CompareExchange(ref _data, newCounts._data, oldCounts._data)); + } + + public static bool operator ==( + CountsOfThreadsProcessingUserCallbacks lhs, + CountsOfThreadsProcessingUserCallbacks rhs) => lhs._data == rhs._data; + public static bool operator !=( + CountsOfThreadsProcessingUserCallbacks lhs, + CountsOfThreadsProcessingUserCallbacks rhs) => lhs._data != rhs._data; + + public override bool Equals(object? obj) => + obj is CountsOfThreadsProcessingUserCallbacks other && _data == other._data; + public override int GetHashCode() => (int)_data; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs index 3551dcccaa154f..d76e5e6296bfde 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs @@ -23,6 +23,7 @@ private static class Messages public const string WorkerThreadAdjustmentStats = "Duration={0};\nThroughput={1};\nThreadWave={2};\nThroughputWave={3};\nThroughputErrorEstimate={4};\nAverageThroughputErrorEstimate={5};\nThroughputRatio={6};\nConfidence={7};\nNewControlSetting={8};\nNewThreadWaveMagnitude={9};\nClrInstanceID={10}"; public const string IOEnqueue = "NativeOverlapped={0};\nOverlapped={1};\nMultiDequeues={2};\nClrInstanceID={3}"; public const string IO = "NativeOverlapped={0};\nOverlapped={1};\nClrInstanceID={2}"; + public const string WorkingThreadCount = "Count={0};\nClrInstanceID={1}"; } // The task definitions for the ETW manifest @@ -31,6 +32,7 @@ private static class Messages public const EventTask ThreadPoolWorkerThread = (EventTask)16; public const EventTask ThreadPoolWorkerThreadAdjustment = (EventTask)18; public const EventTask ThreadPool = (EventTask)23; + public const EventTask ThreadPoolWorkingThreadCount = (EventTask)22; } public static class Opcodes // this name and visibility is important for EventSource @@ -261,6 +263,27 @@ private unsafe void ThreadPoolIODequeue(IntPtr nativeOverlapped, IntPtr overlapp public void ThreadPoolIODequeue(RegisteredWaitHandle registeredWaitHandle) => ThreadPoolIODequeue((IntPtr)registeredWaitHandle.GetHashCode(), IntPtr.Zero); + [Event(60, Level = EventLevel.Verbose, Message = Messages.WorkingThreadCount, Task = Tasks.ThreadPoolWorkingThreadCount, Opcode = EventOpcode.Start, Version = 0, Keywords = Keywords.ThreadingKeyword)] + public unsafe void ThreadPoolWorkingThreadCount(short count) + { + if (!IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword)) + { + return; + } + + int countInt = count; + short clrInstanceId = ClrInstanceId; + + EventData* data = stackalloc EventData[2]; + data[0].DataPointer = (IntPtr)(&countInt); + data[0].Size = sizeof(int); + data[0].Reserved = 0; + data[1].DataPointer = (IntPtr)(&clrInstanceId); + data[1].Size = sizeof(short); + data[1].Reserved = 0; + WriteEventCore(60, 2, data); + } + #pragma warning disable IDE1006 // Naming Styles public static readonly PortableThreadPoolEventSource Log = new PortableThreadPoolEventSource(); #pragma warning restore IDE1006 // Naming Styles diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index c71005088bcd28..0f5fed5276f5ca 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -47,7 +47,8 @@ internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkIt public static partial class ThreadPool { internal const bool SupportsTimeSensitiveWorkItems = true; - internal const bool EnableWorkerTracking = false; + internal static readonly bool EnableWorkerTracking = + AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false); public static bool SetMaxThreads(int workerThreads, int completionPortThreads) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index 38c4e8d9d3365a..61a2ca1bc2fa61 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -771,7 +771,7 @@ internal static bool Dispatch() [MethodImpl(MethodImplOptions.NoInlining)] private static void DispatchWorkItemWithWorkerTracking(object workItem, Thread currentThread) { - Debug.Assert(ThreadPoolGlobals.enableWorkerTracking); + Debug.Assert(ThreadPool.EnableWorkerTracking); Debug.Assert(currentThread == Thread.CurrentThread); bool reportedStatus = false; From 6cd2b2d556991de0b3c7c55cdb65339a0a7bc200 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Wed, 27 May 2020 03:40:53 -0700 Subject: [PATCH 22/48] Use smaller stack size for threads that don't run user code --- .../src/System/Threading/PortableThreadPool.GateThread.cs | 2 +- .../src/System/Threading/PortableThreadPool.WaitThread.cs | 2 +- .../src/System/Threading/PortableThreadPool.cs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index 9c964a122e02d8..19598e8b7f056d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -171,7 +171,7 @@ private static void CreateGateThread(PortableThreadPool threadPoolInstance) bool created = false; try { - Thread gateThread = new Thread(GateThreadStart); + Thread gateThread = new Thread(GateThreadStart, SmallStackSizeBytes); gateThread.IsThreadPoolThread = true; gateThread.IsBackground = true; gateThread.Name = ".NET ThreadPool Gate"; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs index 0c11ef112162a0..751e06a23fd936 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs @@ -170,7 +170,7 @@ internal class WaitThread public WaitThread() { _waitHandles[0] = _changeHandlesEvent.SafeWaitHandle; - Thread waitThread = new Thread(WaitThreadStart); + Thread waitThread = new Thread(WaitThreadStart, SmallStackSizeBytes); waitThread.IsThreadPoolThread = true; waitThread.IsBackground = true; waitThread.Name = ".NET ThreadPool Wait"; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index 35468c0b949e2e..0d4d2d20916756 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -13,6 +13,7 @@ namespace System.Threading internal sealed partial class PortableThreadPool { private const int ThreadPoolThreadTimeoutMs = 20 * 1000; // If you change this make sure to change the timeout times in the tests. + private const int SmallStackSizeBytes = 256 * 1024; #if TARGET_64BIT private const short MaxPossibleThreadCount = short.MaxValue; From c8bdfad1b1515b6159fafebea5d15b2adf0a63c1 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 1 Jun 2020 01:02:27 -0700 Subject: [PATCH 23/48] Note some SOS dependencies, small fixes in hill climbing to make equivalent to coreclr --- .../System/Threading/ThreadPool.CoreCLR.cs | 2 ++ .../PortableThreadPool.HillClimbing.cs | 22 ++++++++++--------- .../PortableThreadPool.ThreadCounts.cs | 3 ++- .../System/Threading/PortableThreadPool.cs | 9 ++++---- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index e72dcc01a23cd9..c2fe1278271ab4 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -165,7 +165,9 @@ public UnmanagedThreadPoolWorkItem(IntPtr callback, IntPtr state) public static partial class ThreadPool { + // SOS's ThreadPool command depends on this name internal static readonly bool UsePortableThreadPool = InitializeConfigAndDetermineUsePortableThreadPool(); + internal static bool SupportsTimeSensitiveWorkItems => UsePortableThreadPool; // This needs to be initialized after UsePortableThreadPool above, as it may depend on UsePortableThreadPool and the diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs index 2e975d74d50dc2..2adbf273e0b8b5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs @@ -12,14 +12,15 @@ internal partial class PortableThreadPool /// private partial class HillClimbing { - private static readonly Lazy s_threadPoolHillClimber = new Lazy(CreateHillClimber, true); - public static HillClimbing ThreadPoolHillClimber => s_threadPoolHillClimber.Value; - + private const int LogCapacity = 200; private const int DefaultSampleIntervalMsLow = 10; private const int DefaultSampleIntervalMsHigh = 200; public static readonly bool IsDisabled = AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.HillClimbing.Disable", false); + // SOS's ThreadPool command depends on this name + public static readonly HillClimbing ThreadPoolHillClimber = CreateHillClimber(); + private static HillClimbing CreateHillClimber() { // Default values pulled from CoreCLR @@ -38,8 +39,8 @@ private static HillClimbing CreateHillClimber() maxSampleError: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent", 15, false) / 100.0 ); } - private const int LogCapacity = 200; + // SOS's ThreadPool command depends on the enum values public enum StateOrTransition { Warmup, @@ -52,13 +53,14 @@ public enum StateOrTransition ThreadTimedOut, } + // SOS's ThreadPool command depends on the names of all fields private struct LogEntry { public int tickCount; public StateOrTransition stateOrTransition; public int newControlSetting; public int lastHistoryCount; - public double lastHistoryMean; + public float lastHistoryMean; } private readonly int _wavePeriod; @@ -89,9 +91,9 @@ private struct LogEntry private readonly Random _randomIntervalGenerator = new Random(); - private readonly LogEntry[] _log = new LogEntry[LogCapacity]; - private int _logStart; - private int _logSize; + private readonly LogEntry[] _log = new LogEntry[LogCapacity]; // SOS's ThreadPool command depends on this name + private int _logStart; // SOS's ThreadPool command depends on this name + private int _logSize; // SOS's ThreadPool command depends on this name public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMultiplier, int waveHistorySize, double targetThroughputRatio, double targetSignalToNoiseRatio, double maxChangePerSecond, double maxChangePerSample, int sampleIntervalMsLow, int sampleIntervalMsHigh, @@ -417,8 +419,8 @@ private void LogTransition(int newThreadCount, double throughput, StateOrTransit entry.tickCount = Environment.TickCount; entry.stateOrTransition = stateOrTransition; entry.newControlSetting = newThreadCount; - entry.lastHistoryCount = ((int)Math.Min(_totalSamples, _samplesToMeasure) / _wavePeriod) * _wavePeriod; - entry.lastHistoryMean = throughput; + entry.lastHistoryCount = (int)(Math.Min(_totalSamples, _samplesToMeasure) / _wavePeriod) * _wavePeriod; + entry.lastHistoryMean = (float)throughput; _logSize++; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.ThreadCounts.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.ThreadCounts.cs index 3a231829e0e4bd..1587a3011a7819 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.ThreadCounts.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.ThreadCounts.cs @@ -12,11 +12,12 @@ internal partial class PortableThreadPool /// private struct ThreadCounts { + // SOS's ThreadPool command depends on this layout private const byte NumProcessingWorkShift = 0; private const byte NumExistingThreadsShift = 16; private const byte NumThreadsGoalShift = 32; - private ulong _data; + private ulong _data; // SOS's ThreadPool command depends on this name private ThreadCounts(ulong data) => _data = data; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index 0d4d2d20916756..ccd72e040fc122 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -33,11 +33,12 @@ internal sealed partial class PortableThreadPool private static object? t_completionCountObject; #pragma warning disable IDE1006 // Naming Styles - // The singleton must be initialized after the static variables above, as the constructor may be dependent on them + // The singleton must be initialized after the static variables above, as the constructor may be dependent on them. + // SOS's ThreadPool command depends on this name. public static readonly PortableThreadPool ThreadPoolInstance = new PortableThreadPool(); #pragma warning restore IDE1006 // Naming Styles - private int _cpuUtilization = 0; + private int _cpuUtilization = 0; // SOS's ThreadPool command depends on this name private short _minThreads; private short _maxThreads; private readonly LowLevelLock _maxMinThreadLock = new LowLevelLock(); @@ -46,7 +47,7 @@ internal sealed partial class PortableThreadPool private struct CacheLineSeparated { [FieldOffset(Internal.PaddingHelpers.CACHE_LINE_SIZE * 1)] - public ThreadCounts counts; + public ThreadCounts counts; // SOS's ThreadPool command depends on this name [FieldOffset(Internal.PaddingHelpers.CACHE_LINE_SIZE * 2)] public int lastDequeueTime; [FieldOffset(Internal.PaddingHelpers.CACHE_LINE_SIZE * 3)] @@ -61,7 +62,7 @@ private struct CacheLineSeparated public int gateThreadRunningState; } - private CacheLineSeparated _separated; + private CacheLineSeparated _separated; // SOS's ThreadPool command depends on this name private long _currentSampleStartTime; private readonly ThreadInt64PersistentCounter _completionCounter = new ThreadInt64PersistentCounter(); private int _threadAdjustmentIntervalMs; From 38c00d591a591a35df75f98eb7f8ed0e1cb5cd18 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sun, 7 Jun 2020 11:08:24 -0700 Subject: [PATCH 24/48] Port some tests from CoreRT --- .../tests/RegisteredWaitTests.cs | 474 ++++++++++++ .../System.Threading.ThreadPool.Tests.csproj | 1 + .../tests/ThreadPoolTests.cs | 682 +++++++++++------- .../tests/TimerFiringTests.cs | 53 ++ 4 files changed, 930 insertions(+), 280 deletions(-) create mode 100644 src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs diff --git a/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs b/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs new file mode 100644 index 00000000000000..8dc3dae4501ccd --- /dev/null +++ b/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs @@ -0,0 +1,474 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tests; +using Microsoft.DotNet.RemoteExecutor; +using Xunit; + +namespace System.Threading.ThreadPools.Tests +{ + public partial class RegisteredWaitTests + { + private const int UnexpectedTimeoutMilliseconds = ThreadTestHelpers.UnexpectedTimeoutMilliseconds; + private const int ExpectedTimeoutMilliseconds = ThreadTestHelpers.ExpectedTimeoutMilliseconds; + + private sealed class InvalidWaitHandle : WaitHandle + { + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void QueueRegisterPositiveAndFlowTest() + { + var asyncLocal = new AsyncLocal(); + asyncLocal.Value = 1; + + var obj = new object(); + var registerWaitEvent = new AutoResetEvent(false); + var threadDone = new AutoResetEvent(false); + RegisteredWaitHandle registeredWaitHandle = null; + Exception backgroundEx = null; + int backgroundAsyncLocalValue = 0; + + Action commonBackgroundTest = + (isRegisteredWaitCallback, test) => + { + try + { + if (isRegisteredWaitCallback) + { + RegisteredWaitHandle toUnregister = registeredWaitHandle; + registeredWaitHandle = null; + Assert.True(toUnregister.Unregister(threadDone)); + } + test(); + backgroundAsyncLocalValue = asyncLocal.Value; + } + catch (Exception ex) + { + backgroundEx = ex; + } + finally + { + if (!isRegisteredWaitCallback) + { + threadDone.Set(); + } + } + }; + Action waitForBackgroundWork = + isWaitForRegisteredWaitCallback => + { + if (isWaitForRegisteredWaitCallback) + { + registerWaitEvent.Set(); + } + threadDone.CheckedWait(); + if (backgroundEx != null) + { + throw new AggregateException(backgroundEx); + } + }; + + ThreadPool.QueueUserWorkItem( + state => + { + commonBackgroundTest(false, () => + { + Assert.Same(obj, state); + }); + }, + obj); + waitForBackgroundWork(false); + Assert.Equal(1, backgroundAsyncLocalValue); + + ThreadPool.UnsafeQueueUserWorkItem( + state => + { + commonBackgroundTest(false, () => + { + Assert.Same(obj, state); + }); + }, + obj); + waitForBackgroundWork(false); + Assert.Equal(0, backgroundAsyncLocalValue); + + registeredWaitHandle = + ThreadPool.RegisterWaitForSingleObject( + registerWaitEvent, + (state, timedOut) => + { + commonBackgroundTest(true, () => + { + Assert.Same(obj, state); + Assert.False(timedOut); + }); + }, + obj, + UnexpectedTimeoutMilliseconds, + false); + waitForBackgroundWork(true); + Assert.Equal(1, backgroundAsyncLocalValue); + + registeredWaitHandle = + ThreadPool.UnsafeRegisterWaitForSingleObject( + registerWaitEvent, + (state, timedOut) => + { + commonBackgroundTest(true, () => + { + Assert.Same(obj, state); + Assert.False(timedOut); + }); + }, + obj, + UnexpectedTimeoutMilliseconds, + false); + waitForBackgroundWork(true); + Assert.Equal(0, backgroundAsyncLocalValue); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void QueueRegisterNegativeTest() + { + Assert.Throws(() => ThreadPool.QueueUserWorkItem(null)); + Assert.Throws(() => ThreadPool.UnsafeQueueUserWorkItem(null, null)); + + WaitHandle waitHandle = new ManualResetEvent(true); + WaitOrTimerCallback callback = (state, timedOut) => { }; + Assert.Throws(() => ThreadPool.RegisterWaitForSingleObject(null, callback, null, 0, true)); + Assert.Throws(() => ThreadPool.RegisterWaitForSingleObject(waitHandle, null, null, 0, true)); + AssertExtensions.Throws("millisecondsTimeOutInterval", () => + ThreadPool.RegisterWaitForSingleObject(waitHandle, callback, null, -2, true)); + AssertExtensions.Throws("millisecondsTimeOutInterval", () => + ThreadPool.RegisterWaitForSingleObject(waitHandle, callback, null, (long)-2, true)); + if (!PlatformDetection.IsNetFramework) // .NET Framework silently overflows the timeout + { + AssertExtensions.Throws("millisecondsTimeOutInterval", () => + ThreadPool.RegisterWaitForSingleObject(waitHandle, callback, null, (long)int.MaxValue + 1, true)); + } + AssertExtensions.Throws("timeout", () => + ThreadPool.RegisterWaitForSingleObject(waitHandle, callback, null, TimeSpan.FromMilliseconds(-2), true)); + AssertExtensions.Throws("timeout", () => + ThreadPool.RegisterWaitForSingleObject( + waitHandle, + callback, + null, + TimeSpan.FromMilliseconds((double)int.MaxValue + 1), + true)); + + Assert.Throws(() => ThreadPool.UnsafeRegisterWaitForSingleObject(null, callback, null, 0, true)); + Assert.Throws(() => ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, null, null, 0, true)); + AssertExtensions.Throws("millisecondsTimeOutInterval", () => + ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, callback, null, -2, true)); + AssertExtensions.Throws("millisecondsTimeOutInterval", () => + ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, callback, null, (long)-2, true)); + if (!PlatformDetection.IsNetFramework) // .NET Framework silently overflows the timeout + { + AssertExtensions.Throws("millisecondsTimeOutInterval", () => + ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, callback, null, (long)int.MaxValue + 1, true)); + } + AssertExtensions.Throws("timeout", () => + ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, callback, null, TimeSpan.FromMilliseconds(-2), true)); + AssertExtensions.Throws("timeout", () => + ThreadPool.UnsafeRegisterWaitForSingleObject( + waitHandle, + callback, + null, + TimeSpan.FromMilliseconds((double)int.MaxValue + 1), + true)); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void SignalingRegisteredWaitHandleCallsCalback() + { + var waitEvent = new AutoResetEvent(false); + var waitCallbackInvoked = new AutoResetEvent(false); + bool timedOut = false; + ThreadPool.RegisterWaitForSingleObject(waitEvent, (_, timedOut2) => + { + timedOut = timedOut2; + waitCallbackInvoked.Set(); + }, null, UnexpectedTimeoutMilliseconds, true); + + waitEvent.Set(); + waitCallbackInvoked.CheckedWait(); + Assert.False(timedOut); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void TimingOutRegisteredWaitHandleCallsCallback() + { + var waitEvent = new AutoResetEvent(false); + var waitCallbackInvoked = new AutoResetEvent(false); + bool timedOut = false; + ThreadPool.RegisterWaitForSingleObject(waitEvent, (_, timedOut2) => + { + timedOut = timedOut2; + waitCallbackInvoked.Set(); + }, null, ExpectedTimeoutMilliseconds, true); + + waitCallbackInvoked.CheckedWait(); + Assert.True(timedOut); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void UnregisteringWaitWithInvalidWaitHandleBeforeSignalingDoesNotCallCallback() + { + var waitEvent = new AutoResetEvent(false); + var waitCallbackInvoked = new AutoResetEvent(false); + var registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitEvent, (_, __) => + { + waitCallbackInvoked.Set(); + }, null, UnexpectedTimeoutMilliseconds, true); + + Assert.True(registeredWaitHandle.Unregister(new InvalidWaitHandle())); // blocking unregister + waitEvent.Set(); + Assert.False(waitCallbackInvoked.WaitOne(ExpectedTimeoutMilliseconds)); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void UnregisteringWaitWithEventBeforeSignalingDoesNotCallCallback() + { + var waitEvent = new AutoResetEvent(false); + var waitUnregistered = new AutoResetEvent(false); + var waitCallbackInvoked = new AutoResetEvent(false); + var registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitEvent, (_, __) => + { + waitCallbackInvoked.Set(); + }, null, UnexpectedTimeoutMilliseconds, true); + + Assert.True(registeredWaitHandle.Unregister(waitUnregistered)); + waitUnregistered.CheckedWait(); + waitEvent.Set(); + Assert.False(waitCallbackInvoked.WaitOne(ExpectedTimeoutMilliseconds)); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void NonrepeatingWaitFiresOnlyOnce() + { + var waitEvent = new AutoResetEvent(false); + var waitCallbackInvoked = new AutoResetEvent(false); + bool anyTimedOut = false; + var registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitEvent, (_, timedOut) => + { + anyTimedOut |= timedOut; + waitCallbackInvoked.Set(); + }, null, UnexpectedTimeoutMilliseconds, true); + + waitEvent.Set(); + waitCallbackInvoked.CheckedWait(); + waitEvent.Set(); + Assert.False(waitCallbackInvoked.WaitOne(ExpectedTimeoutMilliseconds)); + Assert.False(anyTimedOut); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void RepeatingWaitFiresUntilUnregistered() + { + var waitEvent = new AutoResetEvent(false); + var waitCallbackInvoked = new AutoResetEvent(false); + bool anyTimedOut = false; + var registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitEvent, (_, timedOut) => + { + anyTimedOut |= timedOut; + waitCallbackInvoked.Set(); + }, null, UnexpectedTimeoutMilliseconds, false); + + for (int i = 0; i < 4; ++i) + { + waitEvent.Set(); + waitCallbackInvoked.CheckedWait(); + } + + Assert.True(registeredWaitHandle.Unregister(new InvalidWaitHandle())); // blocking unregister + waitEvent.Set(); + Assert.False(waitCallbackInvoked.WaitOne(ExpectedTimeoutMilliseconds)); + Assert.False(anyTimedOut); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void UnregisterEventSignaledWhenUnregistered() + { + var waitEvent = new AutoResetEvent(false); + var waitCallbackInvoked = new AutoResetEvent(false); + var waitUnregistered = new AutoResetEvent(false); + bool timedOut = false; + WaitOrTimerCallback waitCallback = (_, timedOut2) => + { + timedOut = timedOut2; + waitCallbackInvoked.Set(); + }; + + // executeOnlyOnce = true, no timeout and no callback invocation + var registeredWaitHandle = + ThreadPool.RegisterWaitForSingleObject(waitEvent, waitCallback, null, Timeout.Infinite, executeOnlyOnce: true); + Assert.False(waitCallbackInvoked.WaitOne(ExpectedTimeoutMilliseconds)); + Assert.True(registeredWaitHandle.Unregister(waitUnregistered)); + waitUnregistered.CheckedWait(); + Assert.False(timedOut); + + // executeOnlyOnce = true, no timeout with callback invocation + registeredWaitHandle = + ThreadPool.RegisterWaitForSingleObject(waitEvent, waitCallback, null, Timeout.Infinite, executeOnlyOnce: true); + waitEvent.Set(); + waitCallbackInvoked.CheckedWait(); + Assert.True(registeredWaitHandle.Unregister(waitUnregistered)); + waitUnregistered.CheckedWait(); + Assert.False(timedOut); + + // executeOnlyOnce = true, with timeout + registeredWaitHandle = + ThreadPool.RegisterWaitForSingleObject( + waitEvent, waitCallback, null, ExpectedTimeoutMilliseconds, executeOnlyOnce: true); + waitCallbackInvoked.CheckedWait(); + Assert.False(waitCallbackInvoked.WaitOne(ExpectedTimeoutMilliseconds)); + Assert.True(registeredWaitHandle.Unregister(waitUnregistered)); + waitUnregistered.CheckedWait(); + Assert.True(timedOut); + timedOut = false; + + // executeOnlyOnce = false + registeredWaitHandle = + ThreadPool.RegisterWaitForSingleObject( + waitEvent, waitCallback, null, UnexpectedTimeoutMilliseconds, executeOnlyOnce: false); + Assert.False(waitCallbackInvoked.WaitOne(ExpectedTimeoutMilliseconds)); + Assert.True(registeredWaitHandle.Unregister(waitUnregistered)); + waitUnregistered.CheckedWait(); + Assert.False(timedOut); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void CanRegisterMoreThan64Waits() + { + RegisteredWaitHandle[] registeredWaitHandles = new RegisteredWaitHandle[65]; + WaitOrTimerCallback waitCallback = (_, __) => { }; + for (int i = 0; i < registeredWaitHandles.Length; ++i) + { + registeredWaitHandles[i] = + ThreadPool.RegisterWaitForSingleObject( + new AutoResetEvent(false), waitCallback, null, UnexpectedTimeoutMilliseconds, true); + } + for (int i = 0; i < registeredWaitHandles.Length; ++i) + { + Assert.True(registeredWaitHandles[i].Unregister(null)); + } + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void StateIsPasssedThroughToCallback() + { + object state = new object(); + var waitCallbackInvoked = new AutoResetEvent(false); + object statePassedToCallback = null; + ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(true), (callbackState, _) => + { + statePassedToCallback = callbackState; + waitCallbackInvoked.Set(); + }, state, 0, true); + + waitCallbackInvoked.CheckedWait(); + Assert.Same(state, statePassedToCallback); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void UnregisterWaitHandleIsNotSignaledWhenCallbackIsRunning() + { + var waitEvent = new AutoResetEvent(false); + var waitCallbackProgressMade = new AutoResetEvent(false); + var completeWaitCallback = new AutoResetEvent(false); + var waitUnregistered = new AutoResetEvent(false); + RegisteredWaitHandle registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitEvent, (_, __) => + { + waitCallbackProgressMade.Set(); + completeWaitCallback.WaitOne(UnexpectedTimeoutMilliseconds); + waitCallbackProgressMade.Set(); + }, null, UnexpectedTimeoutMilliseconds, false); + + waitEvent.Set(); + waitCallbackProgressMade.CheckedWait(); // one callback running + waitEvent.Set(); + waitCallbackProgressMade.CheckedWait(); // two callbacks running + Assert.True(registeredWaitHandle.Unregister(waitUnregistered)); + Assert.False(waitUnregistered.WaitOne(ExpectedTimeoutMilliseconds)); + completeWaitCallback.Set(); // complete one callback + waitCallbackProgressMade.CheckedWait(); + Assert.False(waitUnregistered.WaitOne(ExpectedTimeoutMilliseconds)); + completeWaitCallback.Set(); // complete other callback + waitCallbackProgressMade.CheckedWait(); + waitUnregistered.CheckedWait(); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void BlockingUnregisterBlocksWhileCallbackIsRunning() + { + var waitEvent = new AutoResetEvent(false); + var waitCallbackProgressMade = new AutoResetEvent(false); + var completeWaitCallback = new AutoResetEvent(false); + RegisteredWaitHandle registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitEvent, (_, __) => + { + waitCallbackProgressMade.Set(); + completeWaitCallback.WaitOne(UnexpectedTimeoutMilliseconds); + waitCallbackProgressMade.Set(); + }, null, UnexpectedTimeoutMilliseconds, false); + + waitEvent.Set(); + waitCallbackProgressMade.CheckedWait(); // one callback running + waitEvent.Set(); + waitCallbackProgressMade.CheckedWait(); // two callbacks running + + Thread t = ThreadTestHelpers.CreateGuardedThread(out Action waitForThread, () => + Assert.True(registeredWaitHandle.Unregister(new InvalidWaitHandle()))); + t.IsBackground = true; + t.Start(); + + Assert.False(t.Join(ExpectedTimeoutMilliseconds)); + completeWaitCallback.Set(); // complete one callback + waitCallbackProgressMade.CheckedWait(); + Assert.False(t.Join(ExpectedTimeoutMilliseconds)); + completeWaitCallback.Set(); // complete other callback + waitCallbackProgressMade.CheckedWait(); + waitForThread(); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void CallingUnregisterOnAutomaticallyUnregisteredHandleReturnsTrue() + { + var waitCallbackInvoked = new AutoResetEvent(false); + RegisteredWaitHandle registeredWaitHandle = + ThreadPool.RegisterWaitForSingleObject( + new AutoResetEvent(true), + (_, __) => waitCallbackInvoked.Set(), + null, + UnexpectedTimeoutMilliseconds, + true); + waitCallbackInvoked.CheckedWait(); + Thread.Sleep(ExpectedTimeoutMilliseconds); // wait for callback to exit + Assert.True(registeredWaitHandle.Unregister(null)); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void EventSetAfterUnregisterNotObservedOnWaitThread() + { + var waitEvent = new AutoResetEvent(false); + RegisteredWaitHandle registeredWaitHandle = + ThreadPool.RegisterWaitForSingleObject(waitEvent, (_, __) => { }, null, UnexpectedTimeoutMilliseconds, true); + Assert.True(registeredWaitHandle.Unregister(null)); + waitEvent.Set(); + Thread.Sleep(ExpectedTimeoutMilliseconds); // give wait thread a chance to observe the signal + waitEvent.CheckedWait(); // signal should not have been observed by wait thread + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void CanDisposeEventAfterNonblockingUnregister() + { + using (var waitEvent = new AutoResetEvent(false)) + { + RegisteredWaitHandle registeredWaitHandle = + ThreadPool.RegisterWaitForSingleObject(waitEvent, (_, __) => { }, null, UnexpectedTimeoutMilliseconds, true); + Assert.True(registeredWaitHandle.Unregister(null)); + } + } + } +} diff --git a/src/libraries/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj b/src/libraries/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj index f4741b497460e7..2a956d57e24897 100644 --- a/src/libraries/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj +++ b/src/libraries/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj @@ -6,6 +6,7 @@ + TwoBools() => [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void ConcurrentInitializeTest() { - int processorCount = Environment.ProcessorCount; - var countdownEvent = new CountdownEvent(processorCount); - Action threadMain = - () => - { - countdownEvent.Signal(); - countdownEvent.Wait(ThreadTestHelpers.UnexpectedTimeoutMilliseconds); - Assert.True(ThreadPool.SetMinThreads(processorCount, processorCount)); - }; - - var waitForThreadArray = new Action[processorCount]; - for (int i = 0; i < processorCount; ++i) + RemoteExecutor.Invoke(() => { - var t = ThreadTestHelpers.CreateGuardedThread(out waitForThreadArray[i], threadMain); - t.IsBackground = true; - t.Start(); - } + int processorCount = Environment.ProcessorCount; + var countdownEvent = new CountdownEvent(processorCount); + Action threadMain = + () => + { + countdownEvent.Signal(); + countdownEvent.Wait(ThreadTestHelpers.UnexpectedTimeoutMilliseconds); + Assert.True(ThreadPool.SetMinThreads(processorCount, processorCount)); + }; - foreach (Action waitForThread in waitForThreadArray) - { - waitForThread(); - } + var waitForThreadArray = new Action[processorCount]; + for (int i = 0; i < processorCount; ++i) + { + var t = ThreadTestHelpers.CreateGuardedThread(out waitForThreadArray[i], threadMain); + t.IsBackground = true; + t.Start(); + } + + foreach (Action waitForThread in waitForThreadArray) + { + waitForThread(); + } + }).Dispose(); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] @@ -87,90 +90,95 @@ public static void GetAvailableThreadsTest() Assert.True(c <= maxc); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [ActiveIssue("https://github.com/mono/mono/issues/15164", TestRuntimes.Mono)] public static void SetMinMaxThreadsTest() { - int minw, minc, maxw, maxc; - ThreadPool.GetMinThreads(out minw, out minc); - ThreadPool.GetMaxThreads(out maxw, out maxc); - - try - { - int mint = Environment.ProcessorCount * 2; - int maxt = mint + 1; - ThreadPool.SetMinThreads(mint, mint); - ThreadPool.SetMaxThreads(maxt, maxt); - - Assert.False(ThreadPool.SetMinThreads(maxt + 1, mint)); - Assert.False(ThreadPool.SetMinThreads(mint, maxt + 1)); - Assert.False(ThreadPool.SetMinThreads(MaxPossibleThreadCount, mint)); - Assert.False(ThreadPool.SetMinThreads(mint, MaxPossibleThreadCount)); - Assert.False(ThreadPool.SetMinThreads(MaxPossibleThreadCount + 1, mint)); - Assert.False(ThreadPool.SetMinThreads(mint, MaxPossibleThreadCount + 1)); - Assert.False(ThreadPool.SetMinThreads(-1, mint)); - Assert.False(ThreadPool.SetMinThreads(mint, -1)); - - Assert.False(ThreadPool.SetMaxThreads(mint - 1, maxt)); - Assert.False(ThreadPool.SetMaxThreads(maxt, mint - 1)); - - VerifyMinThreads(mint, mint); - VerifyMaxThreads(maxt, maxt); - - Assert.True(ThreadPool.SetMaxThreads(MaxPossibleThreadCount, MaxPossibleThreadCount)); - VerifyMaxThreads(MaxPossibleThreadCount, MaxPossibleThreadCount); - Assert.True(ThreadPool.SetMaxThreads(MaxPossibleThreadCount + 1, MaxPossibleThreadCount + 1)); - VerifyMaxThreads(MaxPossibleThreadCount, MaxPossibleThreadCount); - Assert.Equal(PlatformDetection.IsNetFramework, ThreadPool.SetMaxThreads(-1, -1)); - VerifyMaxThreads(MaxPossibleThreadCount, MaxPossibleThreadCount); - - Assert.True(ThreadPool.SetMinThreads(MaxPossibleThreadCount, MaxPossibleThreadCount)); - VerifyMinThreads(MaxPossibleThreadCount, MaxPossibleThreadCount); - - Assert.False(ThreadPool.SetMinThreads(MaxPossibleThreadCount + 1, MaxPossibleThreadCount)); - Assert.False(ThreadPool.SetMinThreads(MaxPossibleThreadCount, MaxPossibleThreadCount + 1)); - Assert.False(ThreadPool.SetMinThreads(-1, MaxPossibleThreadCount)); - Assert.False(ThreadPool.SetMinThreads(MaxPossibleThreadCount, -1)); - VerifyMinThreads(MaxPossibleThreadCount, MaxPossibleThreadCount); - - Assert.True(ThreadPool.SetMinThreads(0, 0)); - Assert.True(ThreadPool.SetMaxThreads(1, 1)); - VerifyMaxThreads(1, 1); - Assert.True(ThreadPool.SetMinThreads(1, 1)); - VerifyMinThreads(1, 1); - } - finally + RemoteExecutor.Invoke(() => { - Assert.True(ThreadPool.SetMaxThreads(maxw, maxc)); - VerifyMaxThreads(maxw, maxc); - Assert.True(ThreadPool.SetMinThreads(minw, minc)); - VerifyMinThreads(minw, minc); - } + int minw, minc, maxw, maxc; + ThreadPool.GetMinThreads(out minw, out minc); + ThreadPool.GetMaxThreads(out maxw, out maxc); + + try + { + int mint = Environment.ProcessorCount * 2; + int maxt = mint + 1; + ThreadPool.SetMinThreads(mint, mint); + ThreadPool.SetMaxThreads(maxt, maxt); + + Assert.False(ThreadPool.SetMinThreads(maxt + 1, mint)); + Assert.False(ThreadPool.SetMinThreads(mint, maxt + 1)); + Assert.False(ThreadPool.SetMinThreads(MaxPossibleThreadCount, mint)); + Assert.False(ThreadPool.SetMinThreads(mint, MaxPossibleThreadCount)); + Assert.False(ThreadPool.SetMinThreads(MaxPossibleThreadCount + 1, mint)); + Assert.False(ThreadPool.SetMinThreads(mint, MaxPossibleThreadCount + 1)); + Assert.False(ThreadPool.SetMinThreads(-1, mint)); + Assert.False(ThreadPool.SetMinThreads(mint, -1)); + + Assert.False(ThreadPool.SetMaxThreads(mint - 1, maxt)); + Assert.False(ThreadPool.SetMaxThreads(maxt, mint - 1)); + + VerifyMinThreads(mint, mint); + VerifyMaxThreads(maxt, maxt); + + Assert.True(ThreadPool.SetMaxThreads(MaxPossibleThreadCount, MaxPossibleThreadCount)); + VerifyMaxThreads(MaxPossibleThreadCount, MaxPossibleThreadCount); + Assert.True(ThreadPool.SetMaxThreads(MaxPossibleThreadCount + 1, MaxPossibleThreadCount + 1)); + VerifyMaxThreads(MaxPossibleThreadCount, MaxPossibleThreadCount); + Assert.Equal(PlatformDetection.IsNetFramework, ThreadPool.SetMaxThreads(-1, -1)); + VerifyMaxThreads(MaxPossibleThreadCount, MaxPossibleThreadCount); + + Assert.True(ThreadPool.SetMinThreads(MaxPossibleThreadCount, MaxPossibleThreadCount)); + VerifyMinThreads(MaxPossibleThreadCount, MaxPossibleThreadCount); + + Assert.False(ThreadPool.SetMinThreads(MaxPossibleThreadCount + 1, MaxPossibleThreadCount)); + Assert.False(ThreadPool.SetMinThreads(MaxPossibleThreadCount, MaxPossibleThreadCount + 1)); + Assert.False(ThreadPool.SetMinThreads(-1, MaxPossibleThreadCount)); + Assert.False(ThreadPool.SetMinThreads(MaxPossibleThreadCount, -1)); + VerifyMinThreads(MaxPossibleThreadCount, MaxPossibleThreadCount); + + Assert.True(ThreadPool.SetMinThreads(0, 0)); + Assert.True(ThreadPool.SetMaxThreads(1, 1)); + VerifyMaxThreads(1, 1); + Assert.True(ThreadPool.SetMinThreads(1, 1)); + VerifyMinThreads(1, 1); + } + finally + { + Assert.True(ThreadPool.SetMaxThreads(maxw, maxc)); + VerifyMaxThreads(maxw, maxc); + Assert.True(ThreadPool.SetMinThreads(minw, minc)); + VerifyMinThreads(minw, minc); + } + }).Dispose(); } - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/32020", TestRuntimes.Mono)] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void SetMinMaxThreadsTest_ChangedInDotNetCore() { - int minw, minc, maxw, maxc; - ThreadPool.GetMinThreads(out minw, out minc); - ThreadPool.GetMaxThreads(out maxw, out maxc); - - try - { - Assert.True(ThreadPool.SetMinThreads(0, 0)); - VerifyMinThreads(1, 1); - Assert.False(ThreadPool.SetMaxThreads(0, 1)); - Assert.False(ThreadPool.SetMaxThreads(1, 0)); - VerifyMaxThreads(maxw, maxc); - } - finally + RemoteExecutor.Invoke(() => { - Assert.True(ThreadPool.SetMaxThreads(maxw, maxc)); - VerifyMaxThreads(maxw, maxc); - Assert.True(ThreadPool.SetMinThreads(minw, minc)); - VerifyMinThreads(minw, minc); - } + int minw, minc, maxw, maxc; + ThreadPool.GetMinThreads(out minw, out minc); + ThreadPool.GetMaxThreads(out maxw, out maxc); + + try + { + Assert.True(ThreadPool.SetMinThreads(0, 0)); + VerifyMinThreads(1, 1); + Assert.False(ThreadPool.SetMaxThreads(0, 1)); + Assert.False(ThreadPool.SetMaxThreads(1, 0)); + VerifyMaxThreads(maxw, maxc); + } + finally + { + Assert.True(ThreadPool.SetMaxThreads(maxw, maxc)); + VerifyMaxThreads(maxw, maxc); + Assert.True(ThreadPool.SetMinThreads(minw, minc)); + VerifyMinThreads(minw, minc); + } + }).Dispose(); } private static void VerifyMinThreads(int expectedMinw, int expectedMinc) @@ -192,201 +200,41 @@ private static void VerifyMaxThreads(int expectedMaxw, int expectedMaxc) [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void SetMinThreadsTo0Test() { - int minw, minc, maxw, maxc; - ThreadPool.GetMinThreads(out minw, out minc); - ThreadPool.GetMaxThreads(out maxw, out maxc); - - try + RemoteExecutor.Invoke(() => { - Assert.True(ThreadPool.SetMinThreads(0, minc)); - Assert.True(ThreadPool.SetMaxThreads(1, maxc)); + int minw, minc, maxw, maxc; + ThreadPool.GetMinThreads(out minw, out minc); + ThreadPool.GetMaxThreads(out maxw, out maxc); - int count = 0; - var done = new ManualResetEvent(false); - WaitCallback callback = null; - callback = state => + try { - ++count; - if (count > 100) - { - done.Set(); - } - else - { - ThreadPool.QueueUserWorkItem(callback); - } - }; - ThreadPool.QueueUserWorkItem(callback); - done.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds); - } - finally - { - Assert.True(ThreadPool.SetMaxThreads(maxw, maxc)); - Assert.True(ThreadPool.SetMinThreads(minw, minc)); - } - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public static void QueueRegisterPositiveAndFlowTest() - { - var asyncLocal = new AsyncLocal(); - asyncLocal.Value = 1; - - var obj = new object(); - var registerWaitEvent = new AutoResetEvent(false); - var threadDone = new AutoResetEvent(false); - RegisteredWaitHandle registeredWaitHandle = null; - Exception backgroundEx = null; - int backgroundAsyncLocalValue = 0; + Assert.True(ThreadPool.SetMinThreads(0, minc)); + Assert.True(ThreadPool.SetMaxThreads(1, maxc)); - Action commonBackgroundTest = - (isRegisteredWaitCallback, test) => - { - try + int count = 0; + var done = new ManualResetEvent(false); + WaitCallback callback = null; + callback = state => { - if (isRegisteredWaitCallback) + ++count; + if (count > 100) { - RegisteredWaitHandle toUnregister = registeredWaitHandle; - registeredWaitHandle = null; - Assert.True(toUnregister.Unregister(threadDone)); + done.Set(); } - test(); - backgroundAsyncLocalValue = asyncLocal.Value; - } - catch (Exception ex) - { - backgroundEx = ex; - } - finally - { - if (!isRegisteredWaitCallback) + else { - threadDone.Set(); + ThreadPool.QueueUserWorkItem(callback); } - } - }; - Action waitForBackgroundWork = - isWaitForRegisteredWaitCallback => - { - if (isWaitForRegisteredWaitCallback) - { - registerWaitEvent.Set(); - } - threadDone.CheckedWait(); - if (backgroundEx != null) - { - throw new AggregateException(backgroundEx); - } - }; - - ThreadPool.QueueUserWorkItem( - state => - { - commonBackgroundTest(false, () => - { - Assert.Same(obj, state); - }); - }, - obj); - waitForBackgroundWork(false); - Assert.Equal(1, backgroundAsyncLocalValue); - - ThreadPool.UnsafeQueueUserWorkItem( - state => + }; + ThreadPool.QueueUserWorkItem(callback); + done.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds); + } + finally { - commonBackgroundTest(false, () => - { - Assert.Same(obj, state); - }); - }, - obj); - waitForBackgroundWork(false); - Assert.Equal(0, backgroundAsyncLocalValue); - - registeredWaitHandle = - ThreadPool.RegisterWaitForSingleObject( - registerWaitEvent, - (state, timedOut) => - { - commonBackgroundTest(true, () => - { - Assert.Same(obj, state); - Assert.False(timedOut); - }); - }, - obj, - UnexpectedTimeoutMilliseconds, - false); - waitForBackgroundWork(true); - Assert.Equal(1, backgroundAsyncLocalValue); - - registeredWaitHandle = - ThreadPool.UnsafeRegisterWaitForSingleObject( - registerWaitEvent, - (state, timedOut) => - { - commonBackgroundTest(true, () => - { - Assert.Same(obj, state); - Assert.False(timedOut); - }); - }, - obj, - UnexpectedTimeoutMilliseconds, - false); - waitForBackgroundWork(true); - Assert.Equal(0, backgroundAsyncLocalValue); - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public static void QueueRegisterNegativeTest() - { - Assert.Throws(() => ThreadPool.QueueUserWorkItem(null)); - Assert.Throws(() => ThreadPool.UnsafeQueueUserWorkItem(null, null)); - - WaitHandle waitHandle = new ManualResetEvent(true); - WaitOrTimerCallback callback = (state, timedOut) => { }; - Assert.Throws(() => ThreadPool.RegisterWaitForSingleObject(null, callback, null, 0, true)); - Assert.Throws(() => ThreadPool.RegisterWaitForSingleObject(waitHandle, null, null, 0, true)); - AssertExtensions.Throws("millisecondsTimeOutInterval", () => - ThreadPool.RegisterWaitForSingleObject(waitHandle, callback, null, -2, true)); - AssertExtensions.Throws("millisecondsTimeOutInterval", () => - ThreadPool.RegisterWaitForSingleObject(waitHandle, callback, null, (long)-2, true)); - if (!PlatformDetection.IsNetFramework) // .NET Framework silently overflows the timeout - { - AssertExtensions.Throws("millisecondsTimeOutInterval", () => - ThreadPool.RegisterWaitForSingleObject(waitHandle, callback, null, (long)int.MaxValue + 1, true)); - } - AssertExtensions.Throws("timeout", () => - ThreadPool.RegisterWaitForSingleObject(waitHandle, callback, null, TimeSpan.FromMilliseconds(-2), true)); - AssertExtensions.Throws("timeout", () => - ThreadPool.RegisterWaitForSingleObject( - waitHandle, - callback, - null, - TimeSpan.FromMilliseconds((double)int.MaxValue + 1), - true)); - - Assert.Throws(() => ThreadPool.UnsafeRegisterWaitForSingleObject(null, callback, null, 0, true)); - Assert.Throws(() => ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, null, null, 0, true)); - AssertExtensions.Throws("millisecondsTimeOutInterval", () => - ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, callback, null, -2, true)); - AssertExtensions.Throws("millisecondsTimeOutInterval", () => - ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, callback, null, (long)-2, true)); - if (!PlatformDetection.IsNetFramework) // .NET Framework silently overflows the timeout - { - AssertExtensions.Throws("millisecondsTimeOutInterval", () => - ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, callback, null, (long)int.MaxValue + 1, true)); - } - AssertExtensions.Throws("timeout", () => - ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, callback, null, TimeSpan.FromMilliseconds(-2), true)); - AssertExtensions.Throws("timeout", () => - ThreadPool.UnsafeRegisterWaitForSingleObject( - waitHandle, - callback, - null, - TimeSpan.FromMilliseconds((double)int.MaxValue + 1), - true)); + Assert.True(ThreadPool.SetMaxThreads(maxw, maxc)); + Assert.True(ThreadPool.SetMinThreads(minw, minc)); + } + }).Dispose(); } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] @@ -696,6 +544,280 @@ public void MetricsTest() }).Dispose(); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void RunProcessorCountItemsInParallel() + { + int processorCount = Environment.ProcessorCount; + AutoResetEvent allWorkItemsStarted = new AutoResetEvent(false); + int startedWorkItemCount = 0; + WaitCallback workItem = _ => + { + if (Interlocked.Increment(ref startedWorkItemCount) == processorCount) + { + allWorkItemsStarted.Set(); + } + }; + + // Run the test twice to make sure we can reuse the threads. + for (int j = 0; j < 2; ++j) + { + for (int i = 0; i < processorCount; ++i) + { + ThreadPool.QueueUserWorkItem(workItem); + } + + allWorkItemsStarted.CheckedWait(); + Interlocked.Exchange(ref startedWorkItemCount, 0); + } + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void ThreadPoolCanPickUpOneOrMoreWorkItemsWhenThreadIsAvailable() + { + int processorCount = Environment.ProcessorCount; + AutoResetEvent allBlockingWorkItemsStarted = new AutoResetEvent(false); + AutoResetEvent allTestWorkItemsStarted = new AutoResetEvent(false); + ManualResetEvent unblockWorkItems = new ManualResetEvent(false); + int startedBlockingWorkItemCount = 0; + int startedTestWorkItemCount = 0; + WaitCallback blockingWorkItem = _ => + { + if (Interlocked.Increment(ref startedBlockingWorkItemCount) == processorCount - 1) + { + allBlockingWorkItemsStarted.Set(); + } + unblockWorkItems.CheckedWait(); + }; + WaitCallback testWorkItem = _ => + { + if (Interlocked.Increment(ref startedTestWorkItemCount) == processorCount) + { + allTestWorkItemsStarted.Set(); + } + }; + + for (int i = 0; i < processorCount - 1; ++i) + { + ThreadPool.QueueUserWorkItem(blockingWorkItem); + } + + allBlockingWorkItemsStarted.CheckedWait(); + for (int i = 0; i < processorCount; ++i) + { + ThreadPool.QueueUserWorkItem(testWorkItem); + } + + allTestWorkItemsStarted.CheckedWait(); + unblockWorkItems.Set(); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void RunMoreThanMaxWorkItemsMakesOneWorkItemWaitForStarvationDetection() + { + int processorCount = Environment.ProcessorCount; + AutoResetEvent allBlockingWorkItemsStarted = new AutoResetEvent(false); + AutoResetEvent testWorkItemStarted = new AutoResetEvent(false); + ManualResetEvent unblockWorkItems = new ManualResetEvent(false); + int startedBlockingWorkItemCount = 0; + WaitCallback blockingWorkItem = _ => + { + if (Interlocked.Increment(ref startedBlockingWorkItemCount) == processorCount) + { + allBlockingWorkItemsStarted.Set(); + } + unblockWorkItems.CheckedWait(); + }; + + for (int i = 0; i < processorCount; ++i) + { + ThreadPool.QueueUserWorkItem(blockingWorkItem); + } + + allBlockingWorkItemsStarted.CheckedWait(); + ThreadPool.QueueUserWorkItem(_ => testWorkItemStarted.Set()); + testWorkItemStarted.CheckedWait(); + unblockWorkItems.Set(); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void WorkQueueDepletionTest() + { + ManualResetEvent done = new ManualResetEvent(false); + int numLocalScheduled = 1; + int numGlobalScheduled = 1; + int numOfEachTypeToSchedule = Environment.ProcessorCount * 64; + int numTotalCompleted = 0; + Action workItem = null; + workItem = preferLocal => + { + int numScheduled = + preferLocal ? Interlocked.Increment(ref numLocalScheduled) : Interlocked.Increment(ref numGlobalScheduled); + if (numScheduled <= numOfEachTypeToSchedule) + { + ThreadPool.QueueUserWorkItem(workItem, preferLocal, preferLocal); + if (Interlocked.Increment(ref numScheduled) <= numOfEachTypeToSchedule) + { + ThreadPool.QueueUserWorkItem(workItem, preferLocal, preferLocal); + } + } + + if (Interlocked.Increment(ref numTotalCompleted) == numOfEachTypeToSchedule * 2) + { + done.Set(); + } + }; + + ThreadPool.QueueUserWorkItem(workItem, true, preferLocal: true); + ThreadPool.QueueUserWorkItem(workItem, false, preferLocal: false); + done.CheckedWait(); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void WorkerThreadStateResetTest() + { + int processorCount = Environment.ProcessorCount; + int startedWorkItemCount = 0; + AutoResetEvent allWorkItemsStarted = new AutoResetEvent(false); + ManualResetEvent unblockChangeStateWorkItems = new ManualResetEvent(false); + ManualResetEvent unblockVerifyStateWorkItems = new ManualResetEvent(false); + + WaitCallback changeStateWorkItem = _ => + { + Thread currentThread = Thread.CurrentThread; + currentThread.Name = nameof(WorkerThreadStateResetTest); + currentThread.IsBackground = false; + currentThread.Priority = ThreadPriority.AboveNormal; + + if (Interlocked.Increment(ref startedWorkItemCount) == processorCount) + { + allWorkItemsStarted.Set(); + } + + // Block the thread to force using multiple threads to run the work items + unblockChangeStateWorkItems.CheckedWait(); + }; + + string failureMessage = string.Empty; + WaitCallback verifyStateWorkItem = _ => + { + Thread currentThread = Thread.CurrentThread; + if (currentThread.Name != null) + { + failureMessage += $"Name was not reset: {currentThread.Name}{Environment.NewLine}"; + } + else if (!currentThread.IsBackground) + { + failureMessage += $"IsBackground was not reset: {currentThread.IsBackground}{Environment.NewLine}"; + currentThread.IsBackground = true; + } + else if (currentThread.Priority != ThreadPriority.Normal) + { + failureMessage += $"Priority was not reset: {currentThread.Priority}{Environment.NewLine}"; + currentThread.Priority = ThreadPriority.Normal; + } + + if (Interlocked.Increment(ref startedWorkItemCount) == processorCount) + { + allWorkItemsStarted.Set(); + } + + // Block the thread to force using multiple threads to run the work items + unblockVerifyStateWorkItems.CheckedWait(); + }; + + for (int i = 0; i < processorCount; ++i) + { + ThreadPool.QueueUserWorkItem(changeStateWorkItem); + } + + allWorkItemsStarted.CheckedWait(); + unblockChangeStateWorkItems.Set(); + Interlocked.Exchange(ref startedWorkItemCount, 0); + + for (int i = 0; i < processorCount; ++i) + { + ThreadPool.QueueUserWorkItem(verifyStateWorkItem); + } + + allWorkItemsStarted.CheckedWait(); + unblockVerifyStateWorkItems.Set(); + + Assert.Equal(string.Empty, failureMessage); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void SettingMinWorkerThreadsWillCreateThreadsUpToMinimum() + { + RemoteExecutor.Invoke(() => + { + ThreadPool.GetMinThreads(out int minWorkerThreads, out int minIocpThreads); + ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxIocpThreads); + + AutoResetEvent allWorkItemsExceptOneStarted = new AutoResetEvent(false); + AutoResetEvent allWorkItemsStarted = new AutoResetEvent(false); + ManualResetEvent unblockWorkItems = new ManualResetEvent(false); + int startedWorkItemCount = 0; + WaitCallback workItem = _ => + { + int newStartedWorkItemCount = Interlocked.Increment(ref startedWorkItemCount); + if (newStartedWorkItemCount == minWorkerThreads) + { + allWorkItemsExceptOneStarted.Set(); + } + else if (newStartedWorkItemCount == minWorkerThreads + 1) + { + allWorkItemsStarted.Set(); + } + + unblockWorkItems.CheckedWait(); + }; + + ThreadPool.SetMaxThreads(minWorkerThreads, maxIocpThreads); + for (int i = 0; i < minWorkerThreads + 1; ++i) + { + ThreadPool.QueueUserWorkItem(workItem); + } + + allWorkItemsExceptOneStarted.CheckedWait(); + Assert.False(allWorkItemsStarted.WaitOne(ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + + Assert.True(ThreadPool.SetMaxThreads(minWorkerThreads + 1, maxIocpThreads)); + Assert.True(ThreadPool.SetMinThreads(minWorkerThreads + 1, minIocpThreads)); + allWorkItemsStarted.CheckedWait(); + + unblockWorkItems.Set(); + }).Dispose(); + } + + // See https://github.com/dotnet/corert/pull/6822 + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void ThreadPoolCanProcessManyWorkItemsInParallelWithoutDeadlocking() + { + int processorCount = Environment.ProcessorCount; + int iterationCount = 100_000; + var done = new ManualResetEvent(false); + + WaitCallback workItem = null; + workItem = _ => + { + if (Interlocked.Decrement(ref iterationCount) > 0) + { + ThreadPool.QueueUserWorkItem(workItem); + } + else + { + done.Set(); + } + }; + + for (int i = 0; i < processorCount; ++i) + { + ThreadPool.QueueUserWorkItem(workItem); + } + + done.CheckedWait(); + } + public static bool HasAtLeastThreeProcessorsAndRemoteExecutorSupported => Environment.ProcessorCount >= 3 && RemoteExecutor.IsSupported; } } diff --git a/src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs b/src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs index 65a570fe969b78..5c34de96dcc39a 100644 --- a/src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs +++ b/src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs @@ -362,6 +362,59 @@ orderby groupedByDueTime.Key } } + [Fact] + public static void TimersCreatedConcurrentlyOnDifferentThreadsAllFire() + { + int processorCount = Environment.ProcessorCount; + + int timerTickCount = 0; + TimerCallback timerCallback = data => Interlocked.Increment(ref timerTickCount); + + var threadStarted = new AutoResetEvent(false); + var createTimers = new ManualResetEvent(false); + var timers = new Timer[processorCount]; + Action createTimerThreadStart = data => + { + int i = (int)data; + var sw = new Stopwatch(); + threadStarted.Set(); + createTimers.WaitOne(); + + // Use the CPU a bit around creating the timer to try to have some of these threads run concurrently + sw.Restart(); + do + { + Thread.SpinWait(1000); + } while (sw.ElapsedMilliseconds < 10); + + timers[i] = new Timer(timerCallback, null, 1, Timeout.Infinite); + + // Use the CPU a bit around creating the timer to try to have some of these threads run concurrently + sw.Restart(); + do + { + Thread.SpinWait(1000); + } while (sw.ElapsedMilliseconds < 10); + }; + + var waitsForThread = new Action[timers.Length]; + for (int i = 0; i < timers.Length; ++i) + { + var t = ThreadTestHelpers.CreateGuardedThread(out waitsForThread[i], createTimerThreadStart); + t.IsBackground = true; + t.Start(i); + threadStarted.CheckedWait(); + } + + createTimers.Set(); + ThreadTestHelpers.WaitForCondition(() => timerTickCount == timers.Length); + + foreach (var waitForThread in waitsForThread) + { + waitForThread(); + } + } + private static Task DueTimeAsync(int dueTime) { // We could just use Task.Delay, but it only uses Timer as an implementation detail. From 0ff3b038e102863711c5300316d28c96f10aacc3 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sun, 7 Jun 2020 13:39:48 -0700 Subject: [PATCH 25/48] Fail-fast in thread pool native entry points specific to thread pool implementations based on config --- src/coreclr/src/vm/comthreadpool.cpp | 23 +++++++++++++---------- src/coreclr/src/vm/win32threadpool.cpp | 6 +++--- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/coreclr/src/vm/comthreadpool.cpp b/src/coreclr/src/vm/comthreadpool.cpp index f84b25d35f5f3b..c5a16fe3c134a2 100644 --- a/src/coreclr/src/vm/comthreadpool.cpp +++ b/src/coreclr/src/vm/comthreadpool.cpp @@ -286,7 +286,7 @@ INT64 QCALLTYPE ThreadPoolNative::GetCompletedWorkItemCount() FCIMPL0(INT64, ThreadPoolNative::GetPendingUnmanagedWorkItemCount) { FCALL_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); return PerAppDomainTPCountList::GetUnmanagedTPCount()->GetNumRequests(); } @@ -297,7 +297,7 @@ FCIMPLEND FCIMPL0(VOID, ThreadPoolNative::NotifyRequestProgress) { FCALL_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); _ASSERTE(ThreadpoolMgr::IsInitialized()); // can't be here without requesting a thread first ThreadpoolMgr::NotifyWorkItemCompleted(); @@ -322,6 +322,8 @@ FCIMPLEND FCIMPL1(VOID, ThreadPoolNative::ReportThreadStatus, CLR_BOOL isWorking) { FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); + ThreadpoolMgr::ReportThreadStatus(isWorking); } FCIMPLEND @@ -329,7 +331,7 @@ FCIMPLEND FCIMPL0(FC_BOOL_RET, ThreadPoolNative::NotifyRequestComplete) { FCALL_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); _ASSERTE(ThreadpoolMgr::IsInitialized()); // can't be here without requesting a thread first ThreadpoolMgr::NotifyWorkItemCompleted(); @@ -392,6 +394,7 @@ FCIMPLEND FCIMPL0(FC_BOOL_RET, ThreadPoolNative::GetEnableWorkerTracking) { FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); BOOL result = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking) ? TRUE : FALSE; FC_RETURN_BOOL(result); @@ -479,7 +482,7 @@ FCIMPL5(LPVOID, ThreadPoolNative::CorRegisterWaitForSingleObject, Object* registeredWaitObjectUNSAFE) { FCALL_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); HANDLE handle = 0; struct _gc @@ -534,7 +537,7 @@ FCIMPLEND FCIMPL1(void, ThreadPoolNative::CorQueueWaitCompletion, Object* completeWaitWorkItemObjectUNSAFE) { FCALL_CONTRACT; - _ASSERTE(ThreadpoolMgr::UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, ThreadpoolMgr::UsePortableThreadPool()); HANDLE completeWaitWorkItemHandle = NULL; struct _gc @@ -583,7 +586,7 @@ BOOL QCALLTYPE ThreadPoolNative::RequestWorkerThread() BEGIN_QCALL; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); ThreadpoolMgr::EnsureInitialized(); ThreadpoolMgr::SetAppDomainRequestsActive(); @@ -612,7 +615,7 @@ BOOL QCALLTYPE ThreadPoolNative::PerformGateActivities(INT32 cpuUtilization) BEGIN_QCALL; - _ASSERTE(ThreadpoolMgr::UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, ThreadpoolMgr::UsePortableThreadPool()); ThreadpoolMgr::PerformGateActivities(cpuUtilization); needGateThread = ThreadpoolMgr::NeedGateThreadForIOCompletions(); @@ -627,7 +630,7 @@ BOOL QCALLTYPE ThreadPoolNative::PerformGateActivities(INT32 cpuUtilization) FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorUnregisterWait, LPVOID WaitHandle, Object* objectToNotify) { FCALL_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); BOOL retVal = false; SAFEHANDLEREF refSH = (SAFEHANDLEREF) ObjectToOBJECTREF(objectToNotify); @@ -683,7 +686,7 @@ FCIMPLEND FCIMPL1(void, ThreadPoolNative::CorWaitHandleCleanupNative, LPVOID WaitHandle) { FCALL_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPool()); HELPER_METHOD_FRAME_BEGIN_0(); @@ -702,7 +705,7 @@ void QCALLTYPE ThreadPoolNative::ExecuteUnmanagedThreadPoolWorkItem(LPTHREAD_STA BEGIN_QCALL; - _ASSERTE(ThreadpoolMgr::UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, ThreadpoolMgr::UsePortableThreadPool()); callback(state); END_QCALL; diff --git a/src/coreclr/src/vm/win32threadpool.cpp b/src/coreclr/src/vm/win32threadpool.cpp index be3882f639ed93..f8c63633d507e4 100644 --- a/src/coreclr/src/vm/win32threadpool.cpp +++ b/src/coreclr/src/vm/win32threadpool.cpp @@ -872,7 +872,7 @@ BOOL ThreadpoolMgr::QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, } CONTRACTL_END; - _ASSERTE(!UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, !UsePortableThreadPool()); EnsureInitialized(); @@ -1988,7 +1988,7 @@ DWORD WINAPI ThreadpoolMgr::WorkerThreadStart(LPVOID lpArgs) } CONTRACTL_END; - _ASSERTE(!UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, !UsePortableThreadPool()); Thread *pThread = NULL; DWORD dwSwitchCount = 0; @@ -2664,7 +2664,7 @@ DWORD WINAPI ThreadpoolMgr::WaitThreadStart(LPVOID lpArgs) ClrFlsSetThreadType (ThreadType_Wait); - _ASSERTE(!UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, !UsePortableThreadPool()); ThreadCB* threadCB = (ThreadCB*) lpArgs; Thread* pThread = SetupThreadNoThrow(); From cc35f92c54e0ad27397a0b9314de0733336412bf Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 8 Jun 2020 10:40:27 -0700 Subject: [PATCH 26/48] Fix SetMinThreads() and SetMaxThreads() to return true only when both changes are successful with synchronization --- .../System/Threading/ThreadPool.CoreCLR.cs | 52 +++++++++++++------ src/coreclr/src/vm/comthreadpool.cpp | 22 ++++++++ src/coreclr/src/vm/comthreadpool.h | 2 + src/coreclr/src/vm/ecalllist.h | 2 + src/coreclr/src/vm/win32threadpool.cpp | 25 +++++++++ src/coreclr/src/vm/win32threadpool.h | 3 ++ .../System/Threading/PortableThreadPool.cs | 30 ++++++++--- .../System/Threading/ThreadPool.Portable.cs | 28 ++++------ 8 files changed, 123 insertions(+), 41 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index c2fe1278271ab4..7c8d9d384f2388 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -230,19 +230,41 @@ private static bool GetEnableWorkerTracking() => ? AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false) : GetEnableWorkerTrackingNative(); - public static bool SetMaxThreads(int workerThreads, int completionPortThreads) + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool CanSetMinIOCompletionThreads(int ioCompletionThreads); + + internal static void SetMinIOCompletionThreads(int ioCompletionThreads) { - if (workerThreads < 0 || completionPortThreads < 0) - { - return false; - } + Debug.Assert(UsePortableThreadPool); + Debug.Assert(ioCompletionThreads >= 0); + + bool success = SetMinThreadsNative(1, ioCompletionThreads); // worker thread count is ignored + Debug.Assert(success); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool CanSetMaxIOCompletionThreads(int ioCompletionThreads); + + internal static void SetMaxIOCompletionThreads(int ioCompletionThreads) + { + Debug.Assert(UsePortableThreadPool); + Debug.Assert(ioCompletionThreads > 0); + + bool success = SetMaxThreadsNative(1, ioCompletionThreads); // worker thread count is ignored + Debug.Assert(success); + } - if (UsePortableThreadPool && !PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads)) + public static bool SetMaxThreads(int workerThreads, int completionPortThreads) + { + if (UsePortableThreadPool) { - return false; + return PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads); } - return SetMaxThreadsNative(workerThreads, completionPortThreads); + return + workerThreads >= 0 && + completionPortThreads >= 0 && + SetMaxThreadsNative(workerThreads, completionPortThreads); } public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) @@ -257,17 +279,15 @@ public static void GetMaxThreads(out int workerThreads, out int completionPortTh public static bool SetMinThreads(int workerThreads, int completionPortThreads) { - if (workerThreads < 0 || completionPortThreads < 0) - { - return false; - } - - if (UsePortableThreadPool && !PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads)) + if (UsePortableThreadPool) { - return false; + return PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads, completionPortThreads); } - return SetMinThreadsNative(workerThreads, completionPortThreads); + return + workerThreads >= 0 && + completionPortThreads >= 0 && + SetMinThreadsNative(workerThreads, completionPortThreads); } public static void GetMinThreads(out int workerThreads, out int completionPortThreads) diff --git a/src/coreclr/src/vm/comthreadpool.cpp b/src/coreclr/src/vm/comthreadpool.cpp index c5a16fe3c134a2..c7092a2272b52e 100644 --- a/src/coreclr/src/vm/comthreadpool.cpp +++ b/src/coreclr/src/vm/comthreadpool.cpp @@ -201,6 +201,28 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, } FCIMPLEND +/*****************************************************************************************************/ +FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorCanSetMinIOCompletionThreads, DWORD ioCompletionThreads) +{ + FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, ThreadpoolMgr::UsePortableThreadPool()); + + BOOL result = ThreadpoolMgr::CanSetMinIOCompletionThreads(ioCompletionThreads); + FC_RETURN_BOOL(result); +} +FCIMPLEND + +/*****************************************************************************************************/ +FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorCanSetMaxIOCompletionThreads, DWORD ioCompletionThreads) +{ + FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, ThreadpoolMgr::UsePortableThreadPool()); + + BOOL result = ThreadpoolMgr::CanSetMaxIOCompletionThreads(ioCompletionThreads); + FC_RETURN_BOOL(result); +} +FCIMPLEND + /*****************************************************************************************************/ FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorSetMaxThreads,DWORD workerThreads, DWORD completionPortThreads) { diff --git a/src/coreclr/src/vm/comthreadpool.h b/src/coreclr/src/vm/comthreadpool.h index 2583bf164a0110..e4e8769d05ae4d 100644 --- a/src/coreclr/src/vm/comthreadpool.h +++ b/src/coreclr/src/vm/comthreadpool.h @@ -27,6 +27,8 @@ class ThreadPoolNative UINT32 *configValueRef, BOOL *isBooleanRef, LPCWSTR *appContextConfigNameRef); + static FCDECL1(FC_BOOL_RET, CorCanSetMinIOCompletionThreads, DWORD ioCompletionThreads); + static FCDECL1(FC_BOOL_RET, CorCanSetMaxIOCompletionThreads, DWORD ioCompletionThreads); static FCDECL2(FC_BOOL_RET, CorSetMaxThreads, DWORD workerThreads, DWORD completionPortThreads); static FCDECL2(VOID, CorGetMaxThreads, DWORD* workerThreads, DWORD* completionPortThreads); static FCDECL2(FC_BOOL_RET, CorSetMinThreads, DWORD workerThreads, DWORD completionPortThreads); diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h index 0a50ad25fe44a9..7d79d0bbbc01aa 100644 --- a/src/coreclr/src/vm/ecalllist.h +++ b/src/coreclr/src/vm/ecalllist.h @@ -634,6 +634,8 @@ FCFuncStart(gThreadPoolFuncs) FCFuncElement("GetNextConfigUInt32Value", ThreadPoolNative::GetNextConfigUInt32Value) FCFuncElement("PostQueuedCompletionStatus", ThreadPoolNative::CorPostQueuedCompletionStatus) FCFuncElement("GetAvailableThreadsNative", ThreadPoolNative::CorGetAvailableThreads) + FCFuncElement("CanSetMinIOCompletionThreads", ThreadPoolNative::CorCanSetMinIOCompletionThreads) + FCFuncElement("CanSetMaxIOCompletionThreads", ThreadPoolNative::CorCanSetMaxIOCompletionThreads) FCFuncElement("SetMinThreadsNative", ThreadPoolNative::CorSetMinThreads) FCFuncElement("GetMinThreadsNative", ThreadPoolNative::CorGetMinThreads) FCFuncElement("GetThreadCount", ThreadPoolNative::GetThreadCount) diff --git a/src/coreclr/src/vm/win32threadpool.cpp b/src/coreclr/src/vm/win32threadpool.cpp index f8c63633d507e4..9ea3bfcc5555d4 100644 --- a/src/coreclr/src/vm/win32threadpool.cpp +++ b/src/coreclr/src/vm/win32threadpool.cpp @@ -528,6 +528,31 @@ void ThreadpoolMgr::InitPlatformVariables() #endif } +bool ThreadpoolMgr::CanSetMinIOCompletionThreads(DWORD ioCompletionThreads) +{ + WRAPPER_NO_CONTRACT; + _ASSERTE(UsePortableThreadPool()); + + EnsureInitialized(); + + // The lock used by SetMinThreads() and SetMaxThreads() is not taken here, the caller is expected to synchronize between + // them. The conditions here should be the same as in the corresponding Set function. + return ioCompletionThreads <= (DWORD)MaxLimitTotalCPThreads; +} + +bool ThreadpoolMgr::CanSetMaxIOCompletionThreads(DWORD ioCompletionThreads) +{ + WRAPPER_NO_CONTRACT; + _ASSERTE(UsePortableThreadPool()); + _ASSERTE(ioCompletionThreads != 0); + + EnsureInitialized(); + + // The lock used by SetMinThreads() and SetMaxThreads() is not taken here, the caller is expected to synchronize between + // them. The conditions here should be the same as in the corresponding Set function. + return ioCompletionThreads >= (DWORD)MinLimitTotalCPThreads; +} + BOOL ThreadpoolMgr::SetMaxThreadsHelper(DWORD MaxWorkerThreads, DWORD MaxIOCompletionThreads) { diff --git a/src/coreclr/src/vm/win32threadpool.h b/src/coreclr/src/vm/win32threadpool.h index c4a4d8e40eec01..d1d2c1bc936f85 100644 --- a/src/coreclr/src/vm/win32threadpool.h +++ b/src/coreclr/src/vm/win32threadpool.h @@ -242,6 +242,9 @@ class ThreadpoolMgr static BOOL SetMaxThreadsHelper(DWORD MaxWorkerThreads, DWORD MaxIOCompletionThreads); + static bool CanSetMinIOCompletionThreads(DWORD ioCompletionThreads); + static bool CanSetMaxIOCompletionThreads(DWORD ioCompletionThreads); + static BOOL SetMaxThreads(DWORD MaxWorkerThreads, DWORD MaxIOCompletionThreads); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index ccd72e040fc122..1204997a8c3fc1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -92,22 +92,29 @@ private PortableThreadPool() }; } - public bool SetMinThreads(int minThreads) + public bool SetMinThreads(int workerThreads, int ioCompletionThreads) { + if (workerThreads < 0 || ioCompletionThreads < 0) + { + return false; + } + _maxMinThreadLock.Acquire(); try { - if (minThreads < 0 || minThreads > _maxThreads) + if (workerThreads > _maxThreads || !ThreadPool.CanSetMinIOCompletionThreads(ioCompletionThreads)) { return false; } + ThreadPool.SetMinIOCompletionThreads(ioCompletionThreads); + if (s_forcedMinWorkerThreads != 0) { return true; } - short newMinThreads = (short)Math.Max(1, Math.Min(minThreads, MaxPossibleThreadCount)); + short newMinThreads = (short)Math.Max(1, Math.Min(workerThreads, MaxPossibleThreadCount)); _minThreads = newMinThreads; ThreadCounts counts = _separated.counts.VolatileRead(); @@ -137,24 +144,31 @@ public bool SetMinThreads(int minThreads) } } - public int GetMinThreads() => _minThreads; + public int GetMinThreads() => Volatile.Read(ref _minThreads); - public bool SetMaxThreads(int maxThreads) + public bool SetMaxThreads(int workerThreads, int ioCompletionThreads) { + if (workerThreads <= 0 || ioCompletionThreads <= 0) + { + return false; + } + _maxMinThreadLock.Acquire(); try { - if (maxThreads < _minThreads || maxThreads == 0) + if (workerThreads < _minThreads || !ThreadPool.CanSetMaxIOCompletionThreads(ioCompletionThreads)) { return false; } + ThreadPool.SetMaxIOCompletionThreads(ioCompletionThreads); + if (s_forcedMaxWorkerThreads != 0) { return true; } - short newMaxThreads = (short)Math.Min(maxThreads, MaxPossibleThreadCount); + short newMaxThreads = (short)Math.Min(workerThreads, MaxPossibleThreadCount); _maxThreads = newMaxThreads; ThreadCounts counts = _separated.counts.VolatileRead(); @@ -180,7 +194,7 @@ public bool SetMaxThreads(int maxThreads) } } - public int GetMaxThreads() => _maxThreads; + public int GetMaxThreads() => Volatile.Read(ref _maxThreads); public int GetAvailableThreads() { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index 0f5fed5276f5ca..5b83f7aa48204d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -50,14 +50,14 @@ public static partial class ThreadPool internal static readonly bool EnableWorkerTracking = AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false); - public static bool SetMaxThreads(int workerThreads, int completionPortThreads) - { - if (workerThreads < 0 || completionPortThreads < 0) - { - return false; - } - return PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads); - } + internal static bool CanSetMinIOCompletionThreads(int ioCompletionThreads) => true; + internal static void SetMinIOCompletionThreads(int ioCompletionThreads) { } + + internal static bool CanSetMaxIOCompletionThreads(int ioCompletionThreads) => true; + internal static void SetMaxIOCompletionThreads(int ioCompletionThreads) { } + + public static bool SetMaxThreads(int workerThreads, int completionPortThreads) => + PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads); public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) { @@ -67,19 +67,13 @@ public static void GetMaxThreads(out int workerThreads, out int completionPortTh completionPortThreads = 1; } - public static bool SetMinThreads(int workerThreads, int completionPortThreads) - { - if (workerThreads < 0 || completionPortThreads < 0) - { - return false; - } - return PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads); - } + public static bool SetMinThreads(int workerThreads, int completionPortThreads) => + PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads, completionPortThreads); public static void GetMinThreads(out int workerThreads, out int completionPortThreads) { workerThreads = PortableThreadPool.ThreadPoolInstance.GetMinThreads(); - completionPortThreads = 0; + completionPortThreads = 1; } public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) From 79eb3b95ad124b6e930967ae64f11c31bd6a378c Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 9 Jun 2020 00:59:35 -0700 Subject: [PATCH 27/48] Fix registered wait removals for fairness since there can be duplicate system wait objects in the wait array --- .../PortableThreadPool.WaitThread.cs | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs index 751e06a23fd936..8efa3a13d56fee 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs @@ -296,25 +296,43 @@ private void ProcessRemovals() for (int i = 0; i < _numPendingRemoves; i++) { RegisteredWaitHandle waitHandleToRemove = _pendingRemoves[i]!; - for (int j = 0; j < _numUserWaits; j++) + int numUserWaits = _numUserWaits; + int j = 0; + for (; j < numUserWaits && waitHandleToRemove != _registeredWaits[j]; j++) { - if (waitHandleToRemove == _registeredWaits[j]) - { - waitHandleToRemove.OnRemoveWait(); - _registeredWaits[j] = _registeredWaits[_numUserWaits - 1]; - Debug.Assert(_registeredWaits[j] != null); - _waitHandles[j + 1] = _waitHandles[_numUserWaits]; - Debug.Assert(_waitHandles[j + 1] != null); - _registeredWaits[_numUserWaits - 1] = null!; - _waitHandles[_numUserWaits] = null!; - --_numUserWaits; - _pendingRemoves[i] = null; - - waitHandleToRemove.Handle.DangerousRelease(); - break; - } } - Debug.Assert(_pendingRemoves[i] == null); + Debug.Assert(j < numUserWaits); + + waitHandleToRemove.OnRemoveWait(); + + if (j + 1 < numUserWaits) + { + // Not removing the last element. Due to the possibility of there being duplicate system wait + // objects in the wait array, perhaps even with different handle values due to the use of + // DuplicateHandle(), don't reorder handles for fairness. When there are duplicate system wait + // objects in the wait array and the wait object gets signaled, the system may release the wait in + // in deterministic order based on the order in the wait array. Instead, shift the array. + + int removeAt = j; + int count = numUserWaits; + Array.Copy(_registeredWaits, removeAt + 1, _registeredWaits, removeAt, count - (removeAt + 1)); + + // Corresponding elements in the wait handles array are shifted up by one + removeAt++; + count++; + Array.Copy(_waitHandles, removeAt + 1, _waitHandles, removeAt, count - (removeAt + 1)); + } + else + { + // Removing the last element + _registeredWaits[j] = null!; + _waitHandles[j + 1] = null!; + } + + _numUserWaits = numUserWaits - 1; + _pendingRemoves[i] = null; + + waitHandleToRemove.Handle.DangerousRelease(); } _numPendingRemoves = 0; From 8b309cccc2fe4ff6bffcfb1cb4ae0d46c9c98a6a Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 12 Jun 2020 09:14:43 -0700 Subject: [PATCH 28/48] Allow multiple DotNETRuntime event providers/sources in EventPipe --- src/coreclr/src/vm/eventpipeconfiguration.cpp | 14 ++++++++++---- src/mono/mono/eventpipe/ep-config.c | 14 ++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/coreclr/src/vm/eventpipeconfiguration.cpp b/src/coreclr/src/vm/eventpipeconfiguration.cpp index d07f280b8af004..be25efce5569cc 100644 --- a/src/coreclr/src/vm/eventpipeconfiguration.cpp +++ b/src/coreclr/src/vm/eventpipeconfiguration.cpp @@ -146,10 +146,16 @@ bool EventPipeConfiguration::RegisterProvider(EventPipeProvider &provider, Event } CONTRACTL_END; - // See if we've already registered this provider. - EventPipeProvider *pExistingProvider = GetProviderNoLock(provider.GetProviderName()); - if (pExistingProvider != nullptr) - return false; + // See if we've already registered this provider. Allow there to be multiple DotNETRuntime providers that may each produce + // different sets of events by component through different event sources. + // TODO: This change to allow multiple DotNETRuntime providers is temporary to get EventPipe working for + // PortableThreadPoolEventSource. Once a long-term solution is figured out, this change should be reverted. + if (!provider.GetProviderName().Equals(W("Microsoft-Windows-DotNETRuntime"))) + { + EventPipeProvider *pExistingProvider = GetProviderNoLock(provider.GetProviderName()); + if (pExistingProvider != nullptr) + return false; + } // The provider list should be non-NULL, but can be NULL on shutdown. if (m_pProviderList != nullptr) diff --git a/src/mono/mono/eventpipe/ep-config.c b/src/mono/mono/eventpipe/ep-config.c index d912b0fabdf0f3..4ec72c19a9e3eb 100644 --- a/src/mono/mono/eventpipe/ep-config.c +++ b/src/mono/mono/eventpipe/ep-config.c @@ -96,10 +96,16 @@ config_register_provider ( ep_requires_lock_held (); - // See if we've already registered this provider. - EventPipeProvider *existing_provider = config_get_provider (config, ep_provider_get_provider_name (provider)); - if (existing_provider) - return false; + // See if we've already registered this provider. Allow there to be multiple DotNETRuntime providers that may each produce + // different sets of events by component through different event sources. + // TODO: This change to allow multiple DotNETRuntime providers is temporary to get EventPipe working for + // PortableThreadPoolEventSource. Once a long-term solution is figured out, this change should be reverted. + if (ep_rt_utf8_string_compare (ep_provider_get_provider_name (provider), "Microsoft-Windows-DotNETRuntime") != 0) + { + EventPipeProvider *existing_provider = config_get_provider (config, ep_provider_get_provider_name (provider)); + if (existing_provider) + return false; + } // The provider has not been registered, so register it. ep_rt_provider_list_append (&config->provider_list, provider); From ac1917d21dc017e87ded8276ba29f590c68121f4 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 15 Jun 2020 22:20:20 -0700 Subject: [PATCH 29/48] Fix registered wait handle timeout logic in the wait thread --- .../PortableThreadPool.WaitThread.cs | 51 +++++++++---------- .../src/System/Threading/ThreadPool.cs | 10 ++-- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs index 8efa3a13d56fee..ab9cfe5336c941 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs @@ -184,9 +184,12 @@ private void WaitThreadStart() { while (true) { - ProcessRemovals(); - int numUserWaits = _numUserWaits; - int preWaitTimeMs = Environment.TickCount; + // This value is taken inside the lock after processing removals. In this iteration these are the number of + // user waits that will be waited upon. Any new waits will wake the wait and the next iteration would + // consider them. + int numUserWaits = ProcessRemovals(); + + int currentTimeMs = Environment.TickCount; // Recalculate Timeout int timeoutDurationMs = Timeout.Infinite; @@ -205,15 +208,15 @@ private void WaitThreadStart() continue; } - int handleTimeoutDurationMs = registeredWait.TimeoutTimeMs - preWaitTimeMs; + int handleTimeoutDurationMs = Math.Max(0, registeredWait.TimeoutTimeMs - currentTimeMs); if (timeoutDurationMs == Timeout.Infinite) { - timeoutDurationMs = handleTimeoutDurationMs > 0 ? handleTimeoutDurationMs : 0; + timeoutDurationMs = handleTimeoutDurationMs; } else { - timeoutDurationMs = Math.Min(handleTimeoutDurationMs > 0 ? handleTimeoutDurationMs : 0, timeoutDurationMs); + timeoutDurationMs = Math.Min(handleTimeoutDurationMs, timeoutDurationMs); } if (timeoutDurationMs == 0) @@ -243,27 +246,22 @@ private void WaitThreadStart() RegisteredWaitHandle signaledHandle = _registeredWaits[signaledHandleIndex - 1]; Debug.Assert(signaledHandle != null); QueueWaitCompletion(signaledHandle, false); + continue; } - else + + if (numUserWaits == 0 && ThreadPoolInstance.TryRemoveWaitThread(this)) { - if (numUserWaits == 0) - { - if (ThreadPoolInstance.TryRemoveWaitThread(this)) - { - return; - } - } + return; + } - int elapsedDurationMs = Environment.TickCount - preWaitTimeMs; // Calculate using relative time to ensure we don't have issues with overflow wraparound - for (int i = 0; i < numUserWaits; i++) + currentTimeMs = Environment.TickCount; + for (int i = 0; i < numUserWaits; i++) + { + RegisteredWaitHandle registeredHandle = _registeredWaits[i]; + Debug.Assert(registeredHandle != null); + if (!registeredHandle.IsInfiniteTimeout && currentTimeMs - registeredHandle.TimeoutTimeMs >= 0) { - RegisteredWaitHandle registeredHandle = _registeredWaits[i]; - Debug.Assert(registeredHandle != null); - int handleTimeoutDurationMs = registeredHandle.TimeoutTimeMs - preWaitTimeMs; - if (elapsedDurationMs >= handleTimeoutDurationMs) - { - QueueWaitCompletion(registeredHandle, true); - } + QueueWaitCompletion(registeredHandle, true); } } } @@ -273,7 +271,7 @@ private void WaitThreadStart() /// Go through the array and remove those registered wait handles from the /// and arrays, filling the holes along the way. /// - private void ProcessRemovals() + private int ProcessRemovals() { PortableThreadPool threadPoolInstance = ThreadPoolInstance; threadPoolInstance._waitThreadLock.Acquire(); @@ -287,7 +285,7 @@ private void ProcessRemovals() if (_numPendingRemoves == 0 || _numUserWaits == 0) { - return; + return _numUserWaits; // return the value taken inside the lock for the caller } int originalNumUserWaits = _numUserWaits; int originalNumPendingRemoves = _numPendingRemoves; @@ -338,6 +336,7 @@ private void ProcessRemovals() Debug.Assert(originalNumUserWaits - originalNumPendingRemoves == _numUserWaits, $"{originalNumUserWaits} - {originalNumPendingRemoves} == {_numUserWaits}"); + return _numUserWaits; // return the value taken inside the lock for the caller } finally { @@ -357,7 +356,7 @@ private void QueueWaitCompletion(RegisteredWaitHandle registeredHandle, bool tim // If the handle is a repeating handle, set up the next call. Otherwise, remove it from the wait thread. if (registeredHandle.Repeating) { - registeredHandle.RestartTimeout(Environment.TickCount); + registeredHandle.RestartTimeout(); } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index 61a2ca1bc2fa61..4c3365d4fcd964 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -1069,7 +1069,10 @@ internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallb Callback = callbackHelper; TimeoutDurationMs = millisecondsTimeout; Repeating = repeating; - RestartTimeout(Environment.TickCount); + if (!IsInfiniteTimeout) + { + RestartTimeout(); + } } private static AutoResetEvent? s_cachedEvent; @@ -1114,9 +1117,10 @@ private static void ReturnEvent(AutoResetEvent resetEvent) internal bool IsInfiniteTimeout => TimeoutDurationMs == -1; - internal void RestartTimeout(int currentTimeMs) + internal void RestartTimeout() { - TimeoutTimeMs = currentTimeMs + TimeoutDurationMs; + Debug.Assert(!IsInfiniteTimeout); + TimeoutTimeMs = Environment.TickCount + TimeoutDurationMs; } /// From 73f911c1ed189a09f0fb8369046652a60dc9ba8b Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 22 Jun 2020 10:33:16 -0700 Subject: [PATCH 30/48] Fix Browser build --- .../src/System/Threading/ThreadPool.cs | 2 + .../Threading/ThreadPool.Browser.Mono.cs | 66 ++++++++++++------- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index 4c3365d4fcd964..ce293d101ed14f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -1151,7 +1151,9 @@ internal void RestartTimeout() private bool _unregisterCalled; +#pragma warning disable CS0414 // The field is assigned but its value is never used. Some runtimes may not support registered wait handles. private bool _unregistered; +#pragma warning restore CS0414 private AutoResetEvent? _callbacksComplete; diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs index e71edb9c10f8a6..f4922a57d27e2e 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs @@ -4,33 +4,54 @@ using System.Diagnostics; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Runtime.Versioning; using System.Diagnostics.CodeAnalysis; using Microsoft.Win32.SafeHandles; namespace System.Threading { - [UnsupportedOSPlatform("browser")] - public sealed class RegisteredWaitHandle : MarshalByRefObject + public sealed partial class Thread { - internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper, - int millisecondsTimeout, bool repeating) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ResetThreadPoolThread() { + Debug.Assert(this == CurrentThread); + Debug.Assert(IsThreadPoolThread); + + if (_mayNeedResetForThreadPool) + { + ResetThreadPoolThreadSlow(); + } } + } + public sealed partial class RegisteredWaitHandle : MarshalByRefObject + { public bool Unregister(WaitHandle? waitObject) { throw new PlatformNotSupportedException(); } } + internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem + { + void IThreadPoolWorkItem.Execute() + { + Debug.Fail("Registered wait handles are currently not supported"); + } + } + public static partial class ThreadPool { + internal const bool SupportsTimeSensitiveWorkItems = true; internal const bool EnableWorkerTracking = false; private static bool _callbackQueued; - internal static void InitializeForThreadPoolThread() { } + internal static bool CanSetMinIOCompletionThreads(int ioCompletionThreads) => true; + internal static void SetMinIOCompletionThreads(int ioCompletionThreads) { } + + internal static bool CanSetMaxIOCompletionThreads(int ioCompletionThreads) => true; + internal static void SetMaxIOCompletionThreads(int ioCompletionThreads) { } public static bool SetMaxThreads(int workerThreads, int completionPortThreads) { @@ -76,36 +97,31 @@ internal static void RequestWorkerThread() QueueCallback(); } - internal static bool KeepDispatching(int startTickCount) - { - return true; - } + + /// + /// Called from the gate thread periodically to perform runtime-specific gate activities + /// + /// CPU utilization as a percentage since the last call + /// True if the runtime still needs to perform gate activities, false otherwise + internal static bool PerformRuntimeSpecificGateActivities(int cpuUtilization) => false; internal static void NotifyWorkItemProgress() { } - internal static bool NotifyWorkItemComplete() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int currentTimeMs) { return true; } - private static RegisteredWaitHandle RegisterWaitForSingleObject( - WaitHandle waitObject, - WaitOrTimerCallback callBack, - object? state, - uint millisecondsTimeOutInterval, - bool executeOnlyOnce, - bool flowExecutionContext) - { - if (waitObject == null) - throw new ArgumentNullException(nameof(waitObject)); - - if (callBack == null) - throw new ArgumentNullException(nameof(callBack)); + internal static object? GetOrCreateThreadLocalCompletionCountObject() => null; + private static void RegisterWaitForSingleObjectCore(WaitHandle? waitObject, RegisteredWaitHandle registeredWaitHandle) => throw new PlatformNotSupportedException(); - } + + internal static void UnsafeQueueWaitCompletion(CompleteWaitThreadPoolWorkItem completeWaitWorkItem) => + UnsafeQueueUserWorkItemInternal(completeWaitWorkItem, preferLocal: false); [DynamicDependency("Callback")] [DynamicDependency("PumpThreadPool")] From 1b99da7baf76ec1b111481d9f274d187d518102a Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Wed, 24 Jun 2020 08:33:45 -0700 Subject: [PATCH 31/48] Remove unnecessary methods from Browser thread pool, address some feedback --- .../System/Threading/ThreadPool.CoreCLR.cs | 6 ++- .../Tracing/FrameworkEventSource.cs | 4 +- .../src/System/Threading/LowLevelLock.cs | 11 +++-- .../src/System/Threading/LowLevelMonitor.cs | 4 +- .../src/System/Threading/Thread.cs | 14 +++++++ .../System/Threading/ThreadPool.Portable.cs | 21 +++------- .../src/System/Threading/ThreadPool.cs | 14 +++++-- .../Threading/ThreadPool.Browser.Mono.cs | 40 ++++--------------- 8 files changed, 53 insertions(+), 61 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index 7c8d9d384f2388..d18e05002760d3 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -168,13 +168,17 @@ public static partial class ThreadPool // SOS's ThreadPool command depends on this name internal static readonly bool UsePortableThreadPool = InitializeConfigAndDetermineUsePortableThreadPool(); + // Time-senstiive work items are those that may need to run ahead of normal work items at least periodically. For a + // runtime that does not support time-sensitive work items on the managed side, the thread pool yields the thread to the + // runtime periodically (by exiting the dispatch loop) so that the runtime may use that thread for processing + // any time-sensitive work. For a runtime that supports time-sensitive work items on the managed side, the thread pool + // does not yield the thread and instead processes time-sensitive work items queued by specific APIs periodically. internal static bool SupportsTimeSensitiveWorkItems => UsePortableThreadPool; // This needs to be initialized after UsePortableThreadPool above, as it may depend on UsePortableThreadPool and the // config initialization internal static readonly bool EnableWorkerTracking = GetEnableWorkerTracking(); - private static unsafe bool InitializeConfigAndDetermineUsePortableThreadPool() { bool usePortableThreadPool = false; diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs index c43a6b92593f07..1d3b01fc0218f0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs @@ -130,9 +130,7 @@ public void ThreadTransferSend(long id, int kind, string info, bool multiDequeue WriteEvent(150, id, kind, info, multiDequeues, intInfo1, intInfo2); } - // id - is a managed object. it gets translated to the object's address. ETW listeners must - // keep track of GC movements in order to correlate the value passed to XyzSend with the - // (possibly changed) value passed to XyzReceive + // id - is a managed object's hash code [NonEvent] public void ThreadTransferSendObj(object id, int kind, string info, bool multiDequeues, int intInfo1, int intInfo2) => ThreadTransferSend(id.GetHashCode(), kind, info, multiDequeues, intInfo1, intInfo2); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs index e7927a4af1e5fe..4dd16fb3bd9ab6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs @@ -60,14 +60,16 @@ private void Dispose(bool disposing) GC.SuppressFinalize(this); } + [Conditional("DEBUG")] public void VerifyIsLocked() { #if DEBUG Debug.Assert(_ownerThread == Thread.CurrentThread); - Debug.Assert((_state & LockedMask) != 0); #endif + Debug.Assert((_state & LockedMask) != 0); } + [Conditional("DEBUG")] public void VerifyIsNotLocked() { #if DEBUG @@ -75,6 +77,7 @@ public void VerifyIsNotLocked() #endif } + [Conditional("DEBUG")] private void VerifyIsNotLockedByAnyThread() { #if DEBUG @@ -82,18 +85,20 @@ private void VerifyIsNotLockedByAnyThread() #endif } + [Conditional("DEBUG")] private void ResetOwnerThread() { -#if DEBUG VerifyIsLocked(); +#if DEBUG _ownerThread = null; #endif } + [Conditional("DEBUG")] private void SetOwnerThreadToCurrent() { -#if DEBUG VerifyIsNotLockedByAnyThread(); +#if DEBUG _ownerThread = Thread.CurrentThread; #endif } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelMonitor.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelMonitor.cs index e10c63e2ba6bc9..6aa1e0cc195fa2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelMonitor.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelMonitor.cs @@ -55,8 +55,8 @@ private void VerifyIsNotLockedByAnyThread() [Conditional("DEBUG")] private void ResetOwnerThread() { -#if DEBUG VerifyIsLocked(); +#if DEBUG _ownerThread = null; #endif } @@ -64,8 +64,8 @@ private void ResetOwnerThread() [Conditional("DEBUG")] private void SetOwnerThreadToCurrent() { -#if DEBUG VerifyIsNotLockedByAnyThread(); +#if DEBUG _ownerThread = Thread.CurrentThread; #endif } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs index 2c175f2de648c6..60b9158ae3834d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @@ -195,6 +195,20 @@ internal void SetThreadPoolWorkerThreadName() } } +#if !CORECLR + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ResetThreadPoolThread() + { + Debug.Assert(this == CurrentThread); + Debug.Assert(IsThreadPoolThread); + + if (_mayNeedResetForThreadPool) + { + ResetThreadPoolThreadSlow(); + } + } +#endif + [MethodImpl(MethodImplOptions.NoInlining)] private void ResetThreadPoolThreadSlow() { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index 5b83f7aa48204d..d509187237bf9d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -10,21 +10,6 @@ namespace System.Threading // Portable implementation of ThreadPool // - public sealed partial class Thread - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ResetThreadPoolThread() - { - Debug.Assert(this == CurrentThread); - Debug.Assert(IsThreadPoolThread); - - if (_mayNeedResetForThreadPool) - { - ResetThreadPoolThreadSlow(); - } - } - } - public sealed partial class RegisteredWaitHandle : MarshalByRefObject { /// @@ -46,7 +31,13 @@ internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkIt public static partial class ThreadPool { + // Time-senstiive work items are those that may need to run ahead of normal work items at least periodically. For a + // runtime that does not support time-sensitive work items on the managed side, the thread pool yields the thread to the + // runtime periodically (by exiting the dispatch loop) so that the runtime may use that thread for processing + // any time-sensitive work. For a runtime that supports time-sensitive work items on the managed side, the thread pool + // does not yield the thread and instead processes time-sensitive work items queued by specific APIs periodically. internal const bool SupportsTimeSensitiveWorkItems = true; + internal static readonly bool EnableWorkerTracking = AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index ce293d101ed14f..c95a95022053f6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -567,11 +567,13 @@ internal static bool LocalFindAndPop(object callback) Debug.Assert(callback == null); +#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be a constant in some runtimes. // No work in the normal queues, check for time-sensitive work items if (ThreadPool.SupportsTimeSensitiveWorkItems) { callback = TryDequeueTimeSensitiveWorkItem(); } +#pragma warning restore CS0162 } return callback; @@ -689,12 +691,11 @@ internal static bool Dispatch() // // Execute the workitem outside of any finally blocks, so that it can be aborted if needed. // -#pragma warning disable CS0162 // Unreachable code detected. EnableWorkerTracking may be constant false in some runtimes. +#pragma warning disable CS0162 // Unreachable code detected. EnableWorkerTracking may be a constant in some runtimes. if (ThreadPool.EnableWorkerTracking) { DispatchWorkItemWithWorkerTracking(workItem, currentThread); } -#pragma warning restore CS0162 else if (workItem is Task task) { // Check for Task first as it's currently faster to type check @@ -708,6 +709,7 @@ internal static bool Dispatch() Debug.Assert(workItem is IThreadPoolWorkItem); Unsafe.As(workItem).Execute(); } +#pragma warning restore CS0162 // Release refs workItem = null; @@ -735,14 +737,13 @@ internal static bool Dispatch() // The quantum expired, do any necessary periodic activities -#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be constant true in some runtimes. +#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be a constant in some runtimes. if (!ThreadPool.SupportsTimeSensitiveWorkItems) { // The runtime-specific thread pool implementation does not support managed time-sensitive work, need to // return to the VM to let it perform its own time-sensitive work. Tell the VM we're returning normally. return true; } -#pragma warning restore CS0162 // This method will continue to dispatch work items. Refresh the start tick count for the next dispatch // quantum and do some periodic activities. @@ -755,6 +756,7 @@ internal static bool Dispatch() // of time spent running work items in the normal thread pool queues, until the normal queues are depleted. // These are basically lower-priority but time-sensitive work items. workItem = workQueue.TryDequeueTimeSensitiveWorkItem(); +#pragma warning restore CS0162 } } finally @@ -1604,6 +1606,7 @@ internal static bool TryPopCustomWorkItem(object workItem) // Get all workitems. Called by TaskScheduler in its debugger hooks. internal static IEnumerable GetQueuedWorkItems() { +#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be a constant in some runtimes. if (ThreadPool.SupportsTimeSensitiveWorkItems) { // Enumerate time-sensitive work item queue @@ -1612,6 +1615,7 @@ internal static IEnumerable GetQueuedWorkItems() yield return workItem; } } +#pragma warning restore CS0162 // Enumerate global queue foreach (object workItem in s_workQueue.workItems) @@ -1654,6 +1658,7 @@ internal static IEnumerable GetLocallyQueuedWorkItems() internal static IEnumerable GetGloballyQueuedWorkItems() { +#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be a constant in some runtimes. if (ThreadPool.SupportsTimeSensitiveWorkItems) { // Enumerate time-sensitive work item queue @@ -1662,6 +1667,7 @@ internal static IEnumerable GetGloballyQueuedWorkItems() yield return workItem; } } +#pragma warning restore CS0162 // Enumerate global queue foreach (object workItem in s_workQueue.workItems) diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs index f4922a57d27e2e..500c69422d063d 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs @@ -9,21 +9,6 @@ namespace System.Threading { - public sealed partial class Thread - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ResetThreadPoolThread() - { - Debug.Assert(this == CurrentThread); - Debug.Assert(IsThreadPoolThread); - - if (_mayNeedResetForThreadPool) - { - ResetThreadPoolThreadSlow(); - } - } - } - public sealed partial class RegisteredWaitHandle : MarshalByRefObject { public bool Unregister(WaitHandle? waitObject) @@ -42,17 +27,17 @@ void IThreadPoolWorkItem.Execute() public static partial class ThreadPool { - internal const bool SupportsTimeSensitiveWorkItems = true; + // Time-senstiive work items are those that may need to run ahead of normal work items at least periodically. For a + // runtime that does not support time-sensitive work items on the managed side, the thread pool yields the thread to the + // runtime periodically (by exiting the dispatch loop) so that the runtime may use that thread for processing + // any time-sensitive work. For a runtime that supports time-sensitive work items on the managed side, the thread pool + // does not yield the thread and instead processes time-sensitive work items queued by specific APIs periodically. + internal const bool SupportsTimeSensitiveWorkItems = false; // the timer currently doesn't queue time-sensitive work + internal const bool EnableWorkerTracking = false; private static bool _callbackQueued; - internal static bool CanSetMinIOCompletionThreads(int ioCompletionThreads) => true; - internal static void SetMinIOCompletionThreads(int ioCompletionThreads) { } - - internal static bool CanSetMaxIOCompletionThreads(int ioCompletionThreads) => true; - internal static void SetMaxIOCompletionThreads(int ioCompletionThreads) { } - public static bool SetMaxThreads(int workerThreads, int completionPortThreads) { if (workerThreads == 1 && completionPortThreads == 1) @@ -97,14 +82,6 @@ internal static void RequestWorkerThread() QueueCallback(); } - - /// - /// Called from the gate thread periodically to perform runtime-specific gate activities - /// - /// CPU utilization as a percentage since the last call - /// True if the runtime still needs to perform gate activities, false otherwise - internal static bool PerformRuntimeSpecificGateActivities(int cpuUtilization) => false; - internal static void NotifyWorkItemProgress() { } @@ -120,9 +97,6 @@ internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountOb private static void RegisterWaitForSingleObjectCore(WaitHandle? waitObject, RegisteredWaitHandle registeredWaitHandle) => throw new PlatformNotSupportedException(); - internal static void UnsafeQueueWaitCompletion(CompleteWaitThreadPoolWorkItem completeWaitWorkItem) => - UnsafeQueueUserWorkItemInternal(completeWaitWorkItem, preferLocal: false); - [DynamicDependency("Callback")] [DynamicDependency("PumpThreadPool")] [MethodImplAttribute(MethodImplOptions.InternalCall)] From 17b3d2d7dbf7aaab690e30ef24eb5b340ed8ba0d Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 30 Jun 2020 08:32:32 -0700 Subject: [PATCH 32/48] Fix build warning after merge --- .../src/System/Threading/PortableThreadPool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index 1204997a8c3fc1..ead851a5a7bd06 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -38,7 +38,7 @@ internal sealed partial class PortableThreadPool public static readonly PortableThreadPool ThreadPoolInstance = new PortableThreadPool(); #pragma warning restore IDE1006 // Naming Styles - private int _cpuUtilization = 0; // SOS's ThreadPool command depends on this name + private int _cpuUtilization; // SOS's ThreadPool command depends on this name private short _minThreads; private short _maxThreads; private readonly LowLevelLock _maxMinThreadLock = new LowLevelLock(); From e8043ffa05450b1983a1e751d0a1af80e43e1f31 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 30 Jun 2020 08:30:09 -0700 Subject: [PATCH 33/48] Address feedback for events, undo EventPipe change in mono, change event source name in mono, add comments --- src/coreclr/src/vm/eventpipeconfiguration.cpp | 8 +- .../PortableThreadPool.GateThread.cs | 2 +- .../PortableThreadPool.HillClimbing.cs | 5 +- .../PortableThreadPool.WorkerThread.cs | 8 +- .../PortableThreadPoolEventSource.cs | 237 ++++++++++-------- src/mono/mono/eventpipe/ep-config.c | 14 +- 6 files changed, 146 insertions(+), 128 deletions(-) diff --git a/src/coreclr/src/vm/eventpipeconfiguration.cpp b/src/coreclr/src/vm/eventpipeconfiguration.cpp index be25efce5569cc..edf1a669bd3a23 100644 --- a/src/coreclr/src/vm/eventpipeconfiguration.cpp +++ b/src/coreclr/src/vm/eventpipeconfiguration.cpp @@ -8,6 +8,7 @@ #include "eventpipesessionprovider.h" #include "eventpipeprovider.h" #include "eventpipesession.h" +#include "win32threadpool.h" #ifdef FEATURE_PERFTRACING @@ -146,11 +147,12 @@ bool EventPipeConfiguration::RegisterProvider(EventPipeProvider &provider, Event } CONTRACTL_END; - // See if we've already registered this provider. Allow there to be multiple DotNETRuntime providers that may each produce - // different sets of events by component through different event sources. + // See if we've already registered this provider. When the portable thread pool is being used, allow there to be multiple + // DotNETRuntime providers, as the portable thread pool temporarily uses an event source on the managed side with the same + // provider name. // TODO: This change to allow multiple DotNETRuntime providers is temporary to get EventPipe working for // PortableThreadPoolEventSource. Once a long-term solution is figured out, this change should be reverted. - if (!provider.GetProviderName().Equals(W("Microsoft-Windows-DotNETRuntime"))) + if (!ThreadpoolMgr::UsePortableThreadPool() || !provider.GetProviderName().Equals(W("Microsoft-Windows-DotNETRuntime"))) { EventPipeProvider *pExistingProvider = GetProviderNoLock(provider.GetProviderName()); if (pExistingProvider != nullptr) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index 19598e8b7f056d..98bc09fe4a5582 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -49,7 +49,7 @@ private static void GateThreadStart() log.IsEnabled(EventLevel.Verbose, PortableThreadPoolEventSource.Keywords.ThreadingKeyword)) { log.ThreadPoolWorkingThreadCount( - threadPoolInstance.GetAndResetHighWatermarkCountOfThreadsProcessingUserCallbacks()); + (uint)threadPoolInstance.GetAndResetHighWatermarkCountOfThreadsProcessingUserCallbacks()); } int cpuUtilization = cpuUtilizationReader.CurrentUtilization; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs index 2adbf273e0b8b5..92e95a52a8b645 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs @@ -427,7 +427,10 @@ private void LogTransition(int newThreadCount, double throughput, StateOrTransit PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; if (log.IsEnabled()) { - log.ThreadPoolWorkerThreadAdjustmentAdjustment(throughput, newThreadCount, (int)stateOrTransition); + log.ThreadPoolWorkerThreadAdjustmentAdjustment( + throughput, + (uint)newThreadCount, + (PortableThreadPoolEventSource.ThreadAdjustmentReasonMap)stateOrTransition); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs index df98a44ae31103..ec959788cbea62 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @@ -23,7 +23,8 @@ private static class WorkerThread PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; if (log.IsEnabled()) { - log.ThreadPoolWorkerThreadWait(ThreadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); + log.ThreadPoolWorkerThreadWait( + (uint)ThreadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); } }); @@ -36,7 +37,8 @@ private static void WorkerThreadStart() PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; if (log.IsEnabled()) { - log.ThreadPoolWorkerThreadStart(threadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); + log.ThreadPoolWorkerThreadStart( + (uint)threadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); } LowLevelLock hillClimbingThreadAdjustmentLock = threadPoolInstance._hillClimbingThreadAdjustmentLock; @@ -99,7 +101,7 @@ private static void WorkerThreadStart() if (log.IsEnabled()) { - log.ThreadPoolWorkerThreadStop(newNumExistingThreads); + log.ThreadPoolWorkerThreadStop((uint)newNumExistingThreads); } return; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs index d76e5e6296bfde..bc10af65f194b2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs @@ -7,13 +7,32 @@ namespace System.Threading { +#if CORECLR + // Currently with EventPipe there isn't a way to move events from the native side to the managed side and get the same + // experience. For now, the same provider name and guid are used as the native side and a temporary change has been made to + // EventPipe to get thread pool events in performance profiles when the portable thread pool is enabled, as that seems to be + // the easiest way currently and the closest to the experience when the portable thread pool is disabled. + // TODO: Long-term options: + // - Use NativeRuntimeEventSource instead, change its guid to match the provider guid from the native side, and fix the + // underlying issues such that duplicate events are not sent. This should get the same experience as sending events from + // the native side, and would allow easily moving other events from the native side to the managed side in the future if + // necessary. + // - Use a different provider name and guid (maybe "System.Threading.ThreadPool"), update PerfView and dotnet-trace to + // enable the provider by default when the Threading or other ThreadPool-related keywords are specified for the runtime + // provider, and update PerfView with a trace event parser for the new provider so that it knows about the events and may + // use them to identify thread pool threads. [EventSource(Name = "Microsoft-Windows-DotNETRuntime", Guid = "e13c0d23-ccbc-4e12-931b-d9cc2eee27e4")] +#else + // TODO: The temporary EventPipe change made for CoreCLR above is not in Mono, change this too along with one of the + // long-term solutions above + [EventSource(Name = "Microsoft-Windows-DotNETRuntime-ThreadPool", Guid = "58c5b76b-0800-5ada-2146-c766e26b61de")] +#endif internal sealed class PortableThreadPoolEventSource : EventSource { // This value does not seem to be used, leaving it as zero for now. It may be useful for a scenario that may involve // multiple instances of the runtime within the same process, but then it seems unlikely that both instances' thread // pools would be in moderate use. - private const short ClrInstanceId = 0; + private const ushort DefaultClrInstanceId = 0; private static class Messages { @@ -51,178 +70,187 @@ private static class Messages public const EventKeywords ThreadTransferKeyword = (EventKeywords)0x80000000; } + public enum ThreadAdjustmentReasonMap : uint + { + Warmup, + Initializing, + RandomMove, + ClimbingMove, + ChangePoint, + Stabilizing, + Starvation, + ThreadTimedOut + } + private PortableThreadPoolEventSource() : base( +#if CORECLR // see note above new Guid(0xe13c0d23, 0xccbc, 0x4e12, 0x93, 0x1b, 0xd9, 0xcc, 0x2e, 0xee, 0x27, 0xe4), - "Microsoft-Windows-DotNETRuntime") + "Microsoft-Windows-DotNETRuntime" +#else + new Guid(0x58c5b76b, 0x0800, 0x5ada, 0x21, 0x46, 0xc7, 0x66, 0xe2, 0x6b, 0x61, 0xde), + "Microsoft-Windows-DotNETRuntime-ThreadPool" +#endif + ) { } [NonEvent] - private unsafe void WriteThreadEvent(int eventId, int numExistingThreads) + private unsafe void WriteThreadEvent(int eventId, uint numExistingThreads) { - int retiredWorkerThreadCount = 0; - short clrInstanceId = ClrInstanceId; + uint retiredWorkerThreadCount = 0; + ushort clrInstanceId = DefaultClrInstanceId; EventData* data = stackalloc EventData[3]; data[0].DataPointer = (IntPtr)(&numExistingThreads); - data[0].Size = sizeof(int); + data[0].Size = sizeof(uint); data[0].Reserved = 0; data[1].DataPointer = (IntPtr)(&retiredWorkerThreadCount); - data[1].Size = sizeof(int); + data[1].Size = sizeof(uint); data[1].Reserved = 0; data[2].DataPointer = (IntPtr)(&clrInstanceId); - data[2].Size = sizeof(short); + data[2].Size = sizeof(ushort); data[2].Reserved = 0; WriteEventCore(eventId, 3, data); } [Event(50, Level = EventLevel.Informational, Message = Messages.WorkerThread, Task = Tasks.ThreadPoolWorkerThread, Opcode = EventOpcode.Start, Version = 0, Keywords = Keywords.ThreadingKeyword)] - public unsafe void ThreadPoolWorkerThreadStart(int numExistingThreads) + public unsafe void ThreadPoolWorkerThreadStart( + uint ActiveWorkerThreadCount, + uint RetiredWorkerThreadCount = 0, + ushort ClrInstanceID = DefaultClrInstanceId) { - if (IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword)) - { - WriteThreadEvent(50, numExistingThreads); - } + WriteThreadEvent(50, ActiveWorkerThreadCount); } [Event(51, Level = EventLevel.Informational, Message = Messages.WorkerThread, Task = Tasks.ThreadPoolWorkerThread, Opcode = EventOpcode.Stop, Version = 0, Keywords = Keywords.ThreadingKeyword)] - public void ThreadPoolWorkerThreadStop(short numExistingThreads) + public void ThreadPoolWorkerThreadStop( + uint ActiveWorkerThreadCount, + uint RetiredWorkerThreadCount = 0, + ushort ClrInstanceID = DefaultClrInstanceId) { - if (IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword)) - { - WriteThreadEvent(51, numExistingThreads); - } + WriteThreadEvent(51, ActiveWorkerThreadCount); } [Event(57, Level = EventLevel.Informational, Message = Messages.WorkerThread, Task = Tasks.ThreadPoolWorkerThread, Opcode = Opcodes.Wait, Version = 0, Keywords = Keywords.ThreadingKeyword)] - [MethodImpl(MethodImplOptions.NoInlining)] - public void ThreadPoolWorkerThreadWait(short numExistingThreads) + public void ThreadPoolWorkerThreadWait( + uint ActiveWorkerThreadCount, + uint RetiredWorkerThreadCount = 0, + ushort ClrInstanceID = DefaultClrInstanceId) { - if (IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword)) - { - WriteThreadEvent(57, numExistingThreads); - } + WriteThreadEvent(57, ActiveWorkerThreadCount); } [Event(54, Level = EventLevel.Informational, Message = Messages.WorkerThreadAdjustmentSample, Task = Tasks.ThreadPoolWorkerThreadAdjustment, Opcode = Opcodes.Sample, Version = 0, Keywords = Keywords.ThreadingKeyword)] - public unsafe void ThreadPoolWorkerThreadAdjustmentSample(double throughput) + public unsafe void ThreadPoolWorkerThreadAdjustmentSample( + double Throughput, + ushort ClrInstanceID = DefaultClrInstanceId) { - if (!IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword)) - { - return; - } - - short clrInstanceId = ClrInstanceId; - EventData* data = stackalloc EventData[2]; - data[0].DataPointer = (IntPtr)(&throughput); + data[0].DataPointer = (IntPtr)(&Throughput); data[0].Size = sizeof(double); data[0].Reserved = 0; - data[1].DataPointer = (IntPtr)(&clrInstanceId); - data[1].Size = sizeof(short); + data[1].DataPointer = (IntPtr)(&ClrInstanceID); + data[1].Size = sizeof(ushort); data[1].Reserved = 0; WriteEventCore(54, 2, data); } [Event(55, Level = EventLevel.Informational, Message = Messages.WorkerThreadAdjustmentAdjustment, Task = Tasks.ThreadPoolWorkerThreadAdjustment, Opcode = Opcodes.Adjustment, Version = 0, Keywords = Keywords.ThreadingKeyword)] - public unsafe void ThreadPoolWorkerThreadAdjustmentAdjustment(double averageThroughput, int newWorkerThreadCount, int stateOrTransition) + public unsafe void ThreadPoolWorkerThreadAdjustmentAdjustment( + double AverageThroughput, + uint NewWorkerThreadCount, + ThreadAdjustmentReasonMap Reason, + ushort ClrInstanceID = DefaultClrInstanceId) { - if (!IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword)) - { - return; - } - - short clrInstanceId = ClrInstanceId; - EventData* data = stackalloc EventData[4]; - data[0].DataPointer = (IntPtr)(&averageThroughput); + data[0].DataPointer = (IntPtr)(&AverageThroughput); data[0].Size = sizeof(double); data[0].Reserved = 0; - data[1].DataPointer = (IntPtr)(&newWorkerThreadCount); - data[1].Size = sizeof(int); + data[1].DataPointer = (IntPtr)(&NewWorkerThreadCount); + data[1].Size = sizeof(uint); data[1].Reserved = 0; - data[2].DataPointer = (IntPtr)(&stateOrTransition); - data[2].Size = sizeof(int); + data[2].DataPointer = (IntPtr)(&Reason); + data[2].Size = sizeof(ThreadAdjustmentReasonMap); data[2].Reserved = 0; - data[3].DataPointer = (IntPtr)(&clrInstanceId); - data[3].Size = sizeof(short); + data[3].DataPointer = (IntPtr)(&ClrInstanceID); + data[3].Size = sizeof(ushort); data[3].Reserved = 0; WriteEventCore(55, 4, data); } [Event(56, Level = EventLevel.Verbose, Message = Messages.WorkerThreadAdjustmentStats, Task = Tasks.ThreadPoolWorkerThreadAdjustment, Opcode = Opcodes.Stats, Version = 0, Keywords = Keywords.ThreadingKeyword)] - public unsafe void ThreadPoolWorkerThreadAdjustmentStats(double duration, double throughput, double threadWave, double throughputWave, double throughputErrorEstimate, - double averageThroughputNoise, double ratio, double confidence, double currentControlSetting, ushort newThreadWaveMagnitude) + public unsafe void ThreadPoolWorkerThreadAdjustmentStats( + double Duration, + double Throughput, + double ThreadWave, + double ThroughputWave, + double ThroughputErrorEstimate, + double AverageThroughputErrorEstimate, + double ThroughputRatio, + double Confidence, + double NewControlSetting, + ushort NewThreadWaveMagnitude, + ushort ClrInstanceID = DefaultClrInstanceId) { - if (!IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword)) - { - return; - } - - short clrInstanceId = ClrInstanceId; - EventData* data = stackalloc EventData[11]; - data[0].DataPointer = (IntPtr)(&duration); + data[0].DataPointer = (IntPtr)(&Duration); data[0].Size = sizeof(double); data[0].Reserved = 0; - data[1].DataPointer = (IntPtr)(&throughput); + data[1].DataPointer = (IntPtr)(&Throughput); data[1].Size = sizeof(double); data[1].Reserved = 0; - data[2].DataPointer = (IntPtr)(&threadWave); + data[2].DataPointer = (IntPtr)(&ThreadWave); data[2].Size = sizeof(double); data[2].Reserved = 0; - data[3].DataPointer = (IntPtr)(&throughputWave); + data[3].DataPointer = (IntPtr)(&ThroughputWave); data[3].Size = sizeof(double); data[3].Reserved = 0; - data[4].DataPointer = (IntPtr)(&throughputErrorEstimate); + data[4].DataPointer = (IntPtr)(&ThroughputErrorEstimate); data[4].Size = sizeof(double); data[4].Reserved = 0; - data[5].DataPointer = (IntPtr)(&averageThroughputNoise); + data[5].DataPointer = (IntPtr)(&AverageThroughputErrorEstimate); data[5].Size = sizeof(double); data[5].Reserved = 0; - data[6].DataPointer = (IntPtr)(&ratio); + data[6].DataPointer = (IntPtr)(&ThroughputRatio); data[6].Size = sizeof(double); data[6].Reserved = 0; - data[7].DataPointer = (IntPtr)(&confidence); + data[7].DataPointer = (IntPtr)(&Confidence); data[7].Size = sizeof(double); data[7].Reserved = 0; - data[8].DataPointer = (IntPtr)(¤tControlSetting); + data[8].DataPointer = (IntPtr)(&NewControlSetting); data[8].Size = sizeof(double); data[8].Reserved = 0; - data[9].DataPointer = (IntPtr)(&newThreadWaveMagnitude); + data[9].DataPointer = (IntPtr)(&NewThreadWaveMagnitude); data[9].Size = sizeof(ushort); data[9].Reserved = 0; - data[10].DataPointer = (IntPtr)(&clrInstanceId); - data[10].Size = sizeof(short); + data[10].DataPointer = (IntPtr)(&ClrInstanceID); + data[10].Size = sizeof(ushort); data[10].Reserved = 0; WriteEventCore(56, 11, data); } [Event(63, Level = EventLevel.Verbose, Message = Messages.IOEnqueue, Task = Tasks.ThreadPool, Opcode = Opcodes.IOEnqueue, Version = 0, Keywords = Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)] - [MethodImpl(MethodImplOptions.NoInlining)] - private unsafe void ThreadPoolIOEnqueue(IntPtr nativeOverlapped, IntPtr overlapped, bool multiDequeues) + private unsafe void ThreadPoolIOEnqueue( + IntPtr NativeOverlapped, + IntPtr Overlapped, + bool MultiDequeues, + ushort ClrInstanceID = DefaultClrInstanceId) { - if (!IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)) - { - return; - } - - int multiDequeuesInt = Convert.ToInt32(multiDequeues); - short clrInstanceId = ClrInstanceId; + int multiDequeuesInt = Convert.ToInt32(MultiDequeues); // bool maps to "win:Boolean", a 4-byte boolean EventData* data = stackalloc EventData[4]; - data[0].DataPointer = (IntPtr)(&nativeOverlapped); + data[0].DataPointer = (IntPtr)(&NativeOverlapped); data[0].Size = IntPtr.Size; data[0].Reserved = 0; - data[1].DataPointer = (IntPtr)(&overlapped); + data[1].DataPointer = (IntPtr)(&Overlapped); data[1].Size = IntPtr.Size; data[1].Reserved = 0; data[2].DataPointer = (IntPtr)(&multiDequeuesInt); data[2].Size = sizeof(int); data[2].Reserved = 0; - data[3].DataPointer = (IntPtr)(&clrInstanceId); - data[3].Size = sizeof(short); + data[3].DataPointer = (IntPtr)(&ClrInstanceID); + data[3].Size = sizeof(ushort); data[3].Reserved = 0; WriteEventCore(63, 4, data); } @@ -230,29 +258,25 @@ private unsafe void ThreadPoolIOEnqueue(IntPtr nativeOverlapped, IntPtr overlapp // TODO: This event is fired for minor compat with CoreCLR in this case. Consider removing this method and use // FrameworkEventSource's thread transfer send/receive events instead at callers. [NonEvent] + [MethodImpl(MethodImplOptions.NoInlining)] public void ThreadPoolIOEnqueue(RegisteredWaitHandle registeredWaitHandle) => ThreadPoolIOEnqueue((IntPtr)registeredWaitHandle.GetHashCode(), IntPtr.Zero, registeredWaitHandle.Repeating); [Event(64, Level = EventLevel.Verbose, Message = Messages.IO, Task = Tasks.ThreadPool, Opcode = Opcodes.IODequeue, Version = 0, Keywords = Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)] - [MethodImpl(MethodImplOptions.NoInlining)] - private unsafe void ThreadPoolIODequeue(IntPtr nativeOverlapped, IntPtr overlapped) + private unsafe void ThreadPoolIODequeue( + IntPtr NativeOverlapped, + IntPtr Overlapped, + ushort ClrInstanceID = DefaultClrInstanceId) { - if (!IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)) - { - return; - } - - short clrInstanceId = ClrInstanceId; - EventData* data = stackalloc EventData[3]; - data[0].DataPointer = (IntPtr)(&nativeOverlapped); + data[0].DataPointer = (IntPtr)(&NativeOverlapped); data[0].Size = IntPtr.Size; data[0].Reserved = 0; - data[1].DataPointer = (IntPtr)(&overlapped); + data[1].DataPointer = (IntPtr)(&Overlapped); data[1].Size = IntPtr.Size; data[1].Reserved = 0; - data[2].DataPointer = (IntPtr)(&clrInstanceId); - data[2].Size = sizeof(short); + data[2].DataPointer = (IntPtr)(&ClrInstanceID); + data[2].Size = sizeof(ushort); data[2].Reserved = 0; WriteEventCore(64, 3, data); } @@ -260,26 +284,19 @@ private unsafe void ThreadPoolIODequeue(IntPtr nativeOverlapped, IntPtr overlapp // TODO: This event is fired for minor compat with CoreCLR in this case. Consider removing this method and use // FrameworkEventSource's thread transfer send/receive events instead at callers. [NonEvent] + [MethodImpl(MethodImplOptions.NoInlining)] public void ThreadPoolIODequeue(RegisteredWaitHandle registeredWaitHandle) => ThreadPoolIODequeue((IntPtr)registeredWaitHandle.GetHashCode(), IntPtr.Zero); [Event(60, Level = EventLevel.Verbose, Message = Messages.WorkingThreadCount, Task = Tasks.ThreadPoolWorkingThreadCount, Opcode = EventOpcode.Start, Version = 0, Keywords = Keywords.ThreadingKeyword)] - public unsafe void ThreadPoolWorkingThreadCount(short count) + public unsafe void ThreadPoolWorkingThreadCount(uint Count, ushort ClrInstanceID = DefaultClrInstanceId) { - if (!IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword)) - { - return; - } - - int countInt = count; - short clrInstanceId = ClrInstanceId; - EventData* data = stackalloc EventData[2]; - data[0].DataPointer = (IntPtr)(&countInt); - data[0].Size = sizeof(int); + data[0].DataPointer = (IntPtr)(&Count); + data[0].Size = sizeof(uint); data[0].Reserved = 0; - data[1].DataPointer = (IntPtr)(&clrInstanceId); - data[1].Size = sizeof(short); + data[1].DataPointer = (IntPtr)(&ClrInstanceID); + data[1].Size = sizeof(ushort); data[1].Reserved = 0; WriteEventCore(60, 2, data); } diff --git a/src/mono/mono/eventpipe/ep-config.c b/src/mono/mono/eventpipe/ep-config.c index 4ec72c19a9e3eb..d912b0fabdf0f3 100644 --- a/src/mono/mono/eventpipe/ep-config.c +++ b/src/mono/mono/eventpipe/ep-config.c @@ -96,16 +96,10 @@ config_register_provider ( ep_requires_lock_held (); - // See if we've already registered this provider. Allow there to be multiple DotNETRuntime providers that may each produce - // different sets of events by component through different event sources. - // TODO: This change to allow multiple DotNETRuntime providers is temporary to get EventPipe working for - // PortableThreadPoolEventSource. Once a long-term solution is figured out, this change should be reverted. - if (ep_rt_utf8_string_compare (ep_provider_get_provider_name (provider), "Microsoft-Windows-DotNETRuntime") != 0) - { - EventPipeProvider *existing_provider = config_get_provider (config, ep_provider_get_provider_name (provider)); - if (existing_provider) - return false; - } + // See if we've already registered this provider. + EventPipeProvider *existing_provider = config_get_provider (config, ep_provider_get_provider_name (provider)); + if (existing_provider) + return false; // The provider has not been registered, so register it. ep_rt_provider_list_append (&config->provider_list, provider); From 8d9cc2dbb839ff71635609e1f5788c7efa62b1c2 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Wed, 1 Jul 2020 02:33:51 -0700 Subject: [PATCH 34/48] Disable new timer test for browser --- src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs b/src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs index 5c34de96dcc39a..d7c963da7b8ea5 100644 --- a/src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs +++ b/src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs @@ -362,7 +362,7 @@ orderby groupedByDueTime.Key } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TimersCreatedConcurrentlyOnDifferentThreadsAllFire() { int processorCount = Environment.ProcessorCount; From f03709bccff0cb8bbf89a33cd12036a9b637aaaf Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Wed, 1 Jul 2020 03:36:52 -0700 Subject: [PATCH 35/48] Fix EventSource tests to expect the new provider name used in mono --- .../tests/BasicEventSourceTest/TestUtilities.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestUtilities.cs b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestUtilities.cs index 3c092bde0c6c46..84e3187c6a5206 100644 --- a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestUtilities.cs +++ b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestUtilities.cs @@ -39,6 +39,7 @@ public static void CheckNoEventSourcesRunning(string message = "") eventSource.Name != "System.Runtime.InteropServices.InteropEventProvider" && eventSource.Name != "System.Reflection.Runtime.Tracing" && eventSource.Name != "Microsoft-Windows-DotNETRuntime" && + eventSource.Name != "Microsoft-Windows-DotNETRuntime-ThreadPool" && eventSource.Name != "System.Runtime" ) { From ff48985a8f49160ecbdfe059f949e1f90d033288 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 3 Jul 2020 10:27:17 -0700 Subject: [PATCH 36/48] Revert event source name in mono to match coreclr --- .../BasicEventSourceTest/TestUtilities.cs | 1 - .../Threading/PortableThreadPoolEventSource.cs | 18 +++--------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestUtilities.cs b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestUtilities.cs index 84e3187c6a5206..3c092bde0c6c46 100644 --- a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestUtilities.cs +++ b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestUtilities.cs @@ -39,7 +39,6 @@ public static void CheckNoEventSourcesRunning(string message = "") eventSource.Name != "System.Runtime.InteropServices.InteropEventProvider" && eventSource.Name != "System.Reflection.Runtime.Tracing" && eventSource.Name != "Microsoft-Windows-DotNETRuntime" && - eventSource.Name != "Microsoft-Windows-DotNETRuntime-ThreadPool" && eventSource.Name != "System.Runtime" ) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs index bc10af65f194b2..66830b2cd4b099 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs @@ -7,11 +7,10 @@ namespace System.Threading { -#if CORECLR // Currently with EventPipe there isn't a way to move events from the native side to the managed side and get the same // experience. For now, the same provider name and guid are used as the native side and a temporary change has been made to - // EventPipe to get thread pool events in performance profiles when the portable thread pool is enabled, as that seems to be - // the easiest way currently and the closest to the experience when the portable thread pool is disabled. + // EventPipe in CoreCLR to get thread pool events in performance profiles when the portable thread pool is enabled, as that + // seems to be the easiest way currently and the closest to the experience when the portable thread pool is disabled. // TODO: Long-term options: // - Use NativeRuntimeEventSource instead, change its guid to match the provider guid from the native side, and fix the // underlying issues such that duplicate events are not sent. This should get the same experience as sending events from @@ -22,11 +21,6 @@ namespace System.Threading // provider, and update PerfView with a trace event parser for the new provider so that it knows about the events and may // use them to identify thread pool threads. [EventSource(Name = "Microsoft-Windows-DotNETRuntime", Guid = "e13c0d23-ccbc-4e12-931b-d9cc2eee27e4")] -#else - // TODO: The temporary EventPipe change made for CoreCLR above is not in Mono, change this too along with one of the - // long-term solutions above - [EventSource(Name = "Microsoft-Windows-DotNETRuntime-ThreadPool", Guid = "58c5b76b-0800-5ada-2146-c766e26b61de")] -#endif internal sealed class PortableThreadPoolEventSource : EventSource { // This value does not seem to be used, leaving it as zero for now. It may be useful for a scenario that may involve @@ -84,14 +78,8 @@ public enum ThreadAdjustmentReasonMap : uint private PortableThreadPoolEventSource() : base( -#if CORECLR // see note above new Guid(0xe13c0d23, 0xccbc, 0x4e12, 0x93, 0x1b, 0xd9, 0xcc, 0x2e, 0xee, 0x27, 0xe4), - "Microsoft-Windows-DotNETRuntime" -#else - new Guid(0x58c5b76b, 0x0800, 0x5ada, 0x21, 0x46, 0xc7, 0x66, 0xe2, 0x6b, 0x61, 0xde), - "Microsoft-Windows-DotNETRuntime-ThreadPool" -#endif - ) + "Microsoft-Windows-DotNETRuntime") { } From 93563e688e5b499cf5edd72025de00315b3a59bf Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 3 Jul 2020 10:36:34 -0700 Subject: [PATCH 37/48] Add issue link for prerequisites of enabling the portable thread pool by default --- src/coreclr/src/inc/clrconfigvalues.h | 2 ++ src/coreclr/src/vm/eventpipeconfiguration.cpp | 3 ++- .../src/System/Threading/PortableThreadPoolEventSource.cs | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/coreclr/src/inc/clrconfigvalues.h b/src/coreclr/src/inc/clrconfigvalues.h index e60fb8ccef8527..4f2c812faf8ab5 100644 --- a/src/coreclr/src/inc/clrconfigvalues.h +++ b/src/coreclr/src/inc/clrconfigvalues.h @@ -569,6 +569,8 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_Thread_AssignCpuGroups, W("Thread_AssignCpuGro /// /// Threadpool /// +// NOTE: UsePortableThreadPool - Before changing the default value of this config option, see +// https://github.com/dotnet/runtime/issues/38763 for prerequisites RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_UsePortableThreadPool, W("ThreadPool_UsePortableThreadPool"), 0, "Uses the managed portable thread pool implementation instead of the unmanaged one.") RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_ForceMinWorkerThreads, W("ThreadPool_ForceMinWorkerThreads"), 0, "Overrides the MinThreads setting for the ThreadPool worker pool") RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_ForceMaxWorkerThreads, W("ThreadPool_ForceMaxWorkerThreads"), 0, "Overrides the MaxThreads setting for the ThreadPool worker pool") diff --git a/src/coreclr/src/vm/eventpipeconfiguration.cpp b/src/coreclr/src/vm/eventpipeconfiguration.cpp index edf1a669bd3a23..346d015f9761fe 100644 --- a/src/coreclr/src/vm/eventpipeconfiguration.cpp +++ b/src/coreclr/src/vm/eventpipeconfiguration.cpp @@ -151,7 +151,8 @@ bool EventPipeConfiguration::RegisterProvider(EventPipeProvider &provider, Event // DotNETRuntime providers, as the portable thread pool temporarily uses an event source on the managed side with the same // provider name. // TODO: This change to allow multiple DotNETRuntime providers is temporary to get EventPipe working for - // PortableThreadPoolEventSource. Once a long-term solution is figured out, this change should be reverted. + // PortableThreadPoolEventSource. Once a long-term solution is figured out, this change should be reverted. See + // https://github.com/dotnet/runtime/issues/38763 for more information. if (!ThreadpoolMgr::UsePortableThreadPool() || !provider.GetProviderName().Equals(W("Microsoft-Windows-DotNETRuntime"))) { EventPipeProvider *pExistingProvider = GetProviderNoLock(provider.GetProviderName()); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs index 66830b2cd4b099..711893fd8b4804 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPoolEventSource.cs @@ -11,7 +11,7 @@ namespace System.Threading // experience. For now, the same provider name and guid are used as the native side and a temporary change has been made to // EventPipe in CoreCLR to get thread pool events in performance profiles when the portable thread pool is enabled, as that // seems to be the easiest way currently and the closest to the experience when the portable thread pool is disabled. - // TODO: Long-term options: + // TODO: Long-term options (also see https://github.com/dotnet/runtime/issues/38763): // - Use NativeRuntimeEventSource instead, change its guid to match the provider guid from the native side, and fix the // underlying issues such that duplicate events are not sent. This should get the same experience as sending events from // the native side, and would allow easily moving other events from the native side to the managed side in the future if From 100dad770b1c80bf38998182f5b998ca9f3613ae Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 6 Jul 2020 09:45:52 -0700 Subject: [PATCH 38/48] Address feedback --- .../src/System/Threading/Thread.CoreCLR.cs | 6 +- .../src/System/Threading/Thread.cs | 2 +- .../Threading/ThreadInt64PersistentCounter.cs | 8 +- .../tests/ThreadPoolTests.cs | 127 ++++++++++-------- src/mono/mono/metadata/threads.c | 8 +- .../src/System/Threading/Thread.Mono.cs | 7 +- 6 files changed, 85 insertions(+), 73 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index a766da0368cba3..cc894d286a9205 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -348,9 +348,9 @@ public bool IsBackground set { SetBackgroundNative(value); - if (!value && !_mayNeedResetForThreadPool) + if (!value) { - _mayNeedResetForThreadPool = value; + _mayNeedResetForThreadPool = true; } } } @@ -377,7 +377,7 @@ public ThreadPriority Priority set { SetPriorityNative((int)value); - if (value != ThreadPriority.Normal && !_mayNeedResetForThreadPool) + if (value != ThreadPriority.Normal) { _mayNeedResetForThreadPool = true; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs index 60b9158ae3834d..e756fee083f9e9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @@ -173,7 +173,7 @@ public string? Name _name = value; ThreadNameChanged(value); - if (value != null && !_mayNeedResetForThreadPool) + if (value != null) { _mayNeedResetForThreadPool = true; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs index 79554a9f286bf6..61fe075654216b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs @@ -13,9 +13,7 @@ internal sealed class ThreadInt64PersistentCounter private static readonly LowLevelLock s_lock = new LowLevelLock(); private long _overflowCount; - - // This should be a HashSet, that type is currently not available in CoreLib - private Dictionary _nodes = new Dictionary(); + private HashSet _nodes = new HashSet(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Increment(object threadLocalCountObject) @@ -31,7 +29,7 @@ public object CreateThreadLocalCountObject() s_lock.Acquire(); try { - _nodes.Add(node, false); + _nodes.Add(node); } finally { @@ -49,7 +47,7 @@ public long Count long count = _overflowCount; try { - foreach (ThreadLocalNode node in _nodes.Keys) + foreach (ThreadLocalNode node in _nodes) { count += node.Count; } diff --git a/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs b/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs index db727ca7546d28..0a465ebb3915fe 100644 --- a/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs +++ b/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs @@ -675,74 +675,85 @@ public static void WorkQueueDepletionTest() [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void WorkerThreadStateResetTest() { - int processorCount = Environment.ProcessorCount; - int startedWorkItemCount = 0; - AutoResetEvent allWorkItemsStarted = new AutoResetEvent(false); - ManualResetEvent unblockChangeStateWorkItems = new ManualResetEvent(false); - ManualResetEvent unblockVerifyStateWorkItems = new ManualResetEvent(false); - - WaitCallback changeStateWorkItem = _ => + RemoteExecutor.Invoke(() => { - Thread currentThread = Thread.CurrentThread; - currentThread.Name = nameof(WorkerThreadStateResetTest); - currentThread.IsBackground = false; - currentThread.Priority = ThreadPriority.AboveNormal; - - if (Interlocked.Increment(ref startedWorkItemCount) == processorCount) + ThreadPool.GetMinThreads(out int minw, out int minc); + ThreadPool.GetMaxThreads(out int maxw, out int maxc); + try { - allWorkItemsStarted.Set(); - } - - // Block the thread to force using multiple threads to run the work items - unblockChangeStateWorkItems.CheckedWait(); - }; + // Use maximum one worker thread to have all work items below run on the same thread + Assert.True(ThreadPool.SetMinThreads(1, minc)); + Assert.True(ThreadPool.SetMaxThreads(1, maxc)); - string failureMessage = string.Empty; - WaitCallback verifyStateWorkItem = _ => - { - Thread currentThread = Thread.CurrentThread; - if (currentThread.Name != null) - { - failureMessage += $"Name was not reset: {currentThread.Name}{Environment.NewLine}"; - } - else if (!currentThread.IsBackground) - { - failureMessage += $"IsBackground was not reset: {currentThread.IsBackground}{Environment.NewLine}"; - currentThread.IsBackground = true; - } - else if (currentThread.Priority != ThreadPriority.Normal) - { - failureMessage += $"Priority was not reset: {currentThread.Priority}{Environment.NewLine}"; - currentThread.Priority = ThreadPriority.Normal; - } + var done = new AutoResetEvent(false); + string failureMessage = string.Empty; + WaitCallback setNameWorkItem = null; + WaitCallback verifyNameWorkItem = null; + WaitCallback setIsBackgroundWorkItem = null; + WaitCallback verifyIsBackgroundWorkItem = null; + WaitCallback setPriorityWorkItem = null; + WaitCallback verifyPriorityWorkItem = null; - if (Interlocked.Increment(ref startedWorkItemCount) == processorCount) - { - allWorkItemsStarted.Set(); - } + setNameWorkItem = _ => + { + Thread.CurrentThread.Name = nameof(WorkerThreadStateResetTest); + ThreadPool.QueueUserWorkItem(verifyNameWorkItem); + }; - // Block the thread to force using multiple threads to run the work items - unblockVerifyStateWorkItems.CheckedWait(); - }; + verifyNameWorkItem = _ => + { + Thread currentThread = Thread.CurrentThread; + if (currentThread.Name != null) + { + failureMessage += $"Name was not reset: {currentThread.Name}{Environment.NewLine}"; + } + ThreadPool.QueueUserWorkItem(setIsBackgroundWorkItem); + }; - for (int i = 0; i < processorCount; ++i) - { - ThreadPool.QueueUserWorkItem(changeStateWorkItem); - } + setIsBackgroundWorkItem = _ => + { + Thread.CurrentThread.IsBackground = false; + ThreadPool.QueueUserWorkItem(verifyIsBackgroundWorkItem); + }; - allWorkItemsStarted.CheckedWait(); - unblockChangeStateWorkItems.Set(); - Interlocked.Exchange(ref startedWorkItemCount, 0); + verifyIsBackgroundWorkItem = _ => + { + Thread currentThread = Thread.CurrentThread; + if (!currentThread.IsBackground) + { + failureMessage += $"IsBackground was not reset: {currentThread.IsBackground}{Environment.NewLine}"; + currentThread.IsBackground = true; + } + ThreadPool.QueueUserWorkItem(setPriorityWorkItem); + }; - for (int i = 0; i < processorCount; ++i) - { - ThreadPool.QueueUserWorkItem(verifyStateWorkItem); - } + setPriorityWorkItem = _ => + { + Thread.CurrentThread.Priority = ThreadPriority.AboveNormal; + ThreadPool.QueueUserWorkItem(verifyPriorityWorkItem); + }; - allWorkItemsStarted.CheckedWait(); - unblockVerifyStateWorkItems.Set(); + verifyPriorityWorkItem = _ => + { + Thread currentThread = Thread.CurrentThread; + if (currentThread.Priority != ThreadPriority.Normal) + { + failureMessage += $"Priority was not reset: {currentThread.Priority}{Environment.NewLine}"; + currentThread.Priority = ThreadPriority.Normal; + } + done.Set(); + }; - Assert.Equal(string.Empty, failureMessage); + ThreadPool.QueueUserWorkItem(setNameWorkItem); + done.CheckedWait(); + Assert.Equal(string.Empty, failureMessage); + } + finally + { + Assert.True(ThreadPool.SetMaxThreads(maxw, maxc)); + Assert.True(ThreadPool.SetMinThreads(minw, minc)); + } + }).Dispose(); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] diff --git a/src/mono/mono/metadata/threads.c b/src/mono/mono/metadata/threads.c index 76003c05ba0716..4eb05cc8f7fdbb 100644 --- a/src/mono/mono/metadata/threads.c +++ b/src/mono/mono/metadata/threads.c @@ -2134,13 +2134,19 @@ ves_icall_System_Threading_Thread_SetName_icall (MonoInternalThreadHandle thread char* name8 = name16 ? g_utf16_to_utf8 (name16, name16_length, NULL, &name8_length, NULL) : NULL; +#ifdef ENABLE_NETCORE // The managed thread implementation prevents the Name property from being set multiple times on normal threads. On thread // pool threads, for compatibility the thread's name should be changeable and this function may be called to force-reset the // thread's name if user code had changed it. So for the flags, MonoSetThreadNameFlag_Reset is passed instead of // MonoSetThreadNameFlag_Permanent for all threads, relying on the managed side to prevent multiple changes where // appropriate. + MonoSetThreadNameFlags flags = MonoSetThreadNameFlag_Reset; +#else + MonoSetThreadNameFlags flags = MonoSetThreadNameFlag_Permanent; +#endif + mono_thread_set_name (mono_internal_thread_handle_ptr (thread_handle), - name8, (gint32)name8_length, name16, MonoSetThreadNameFlag_Reset, error); + name8, (gint32)name8_length, name16, flags, error); } #ifndef ENABLE_NETCORE diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs index 5a70c8d181956b..9148e6d918037a 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs @@ -128,10 +128,7 @@ public bool IsBackground else { ClrState(this, ThreadState.Background); - if (!_mayNeedResetForThreadPool) - { - _mayNeedResetForThreadPool = value; - } + _mayNeedResetForThreadPool = true; } } } @@ -171,7 +168,7 @@ public ThreadPriority Priority { // TODO: arguments check SetPriority(this, (int)value); - if (value != ThreadPriority.Normal && !_mayNeedResetForThreadPool) + if (value != ThreadPriority.Normal) { _mayNeedResetForThreadPool = true; } From 4c7f7780c0cf1bfb9e50de462e1263305bfa95b0 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 10 Jul 2020 18:17:56 -0700 Subject: [PATCH 39/48] Fix race condition in registered wait unregister - For a registered wait that is automatically unregistered (due to `executeOnlyOnce: true`), the registered wait handle gets added to the array of pending removals, and this automatic unregister does not wait for the removal to actually happen - If shortly after that a user calls `Unregister(null)` on the same registered wait handle, it is supposed to wait for the removal to actually happen, but was not because the handle is already in the array of pending removals - A `Dispose` on the wait handle shortly after `Unregister` returns would delete the safe handle and `DangerousRelease` upon removal would throw and crash the process - Fixed by waiting when a registered wait handle is pending removal, regardless of whether the caller of `Unregister` added the handle to the array of pending removals or if it was added by anyone else --- .../System/Threading/PortableThreadPool.WaitThread.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs index ab9cfe5336c941..17e097cab4ac9e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs @@ -421,10 +421,14 @@ private void UnregisterWait(RegisteredWaitHandle handle, bool blocking) try { // If this handle is not already pending removal and hasn't already been removed - if (Array.IndexOf(_registeredWaits, handle) != -1 && Array.IndexOf(_pendingRemoves, handle) == -1) + if (Array.IndexOf(_registeredWaits, handle) != -1) { - _pendingRemoves[_numPendingRemoves++] = handle; - _changeHandlesEvent.Set(); // Tell the wait thread that there are changes pending. + if (Array.IndexOf(_pendingRemoves, handle) == -1) + { + _pendingRemoves[_numPendingRemoves++] = handle; + _changeHandlesEvent.Set(); // Tell the wait thread that there are changes pending. + } + pendingRemoval = true; } } From d55c5fc002a014ffb6d5cff293ee521fbfe1a9dd Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sun, 12 Jul 2020 15:04:49 -0700 Subject: [PATCH 40/48] Fix a new issue in WaitThread after shifting items in arrays --- .../PortableThreadPool.WaitThread.cs | 2 + .../tests/RegisteredWaitTests.cs | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs index 17e097cab4ac9e..ed0580344d80b9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs @@ -314,11 +314,13 @@ private int ProcessRemovals() int removeAt = j; int count = numUserWaits; Array.Copy(_registeredWaits, removeAt + 1, _registeredWaits, removeAt, count - (removeAt + 1)); + _registeredWaits[count - 1] = null!; // Corresponding elements in the wait handles array are shifted up by one removeAt++; count++; Array.Copy(_waitHandles, removeAt + 1, _waitHandles, removeAt, count - (removeAt + 1)); + _waitHandles[count - 1] = null!; } else { diff --git a/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs b/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs index 8dc3dae4501ccd..c6ea0a736c1d12 100644 --- a/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs +++ b/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs @@ -470,5 +470,49 @@ public static void CanDisposeEventAfterNonblockingUnregister() Assert.True(registeredWaitHandle.Unregister(null)); } } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void MultipleRegisteredWaitsUnregisterHandleShiftTest() + { + var handlePendingRemoval = new AutoResetEvent(false); + var completeWaitCallback = new AutoResetEvent(false); + WaitOrTimerCallback waitCallback = (_, __) => + { + handlePendingRemoval.Set(); + completeWaitCallback.CheckedWait(); + }; + + var waitEvent = new AutoResetEvent(false); + RegisteredWaitHandle registeredWaitHandle = + ThreadPool.RegisterWaitForSingleObject(waitEvent, waitCallback, null, UnexpectedTimeoutMilliseconds, true); + + var waitEvent2 = new AutoResetEvent(false); + RegisteredWaitHandle registeredWaitHandle2 = + ThreadPool.RegisterWaitForSingleObject(waitEvent2, waitCallback, null, UnexpectedTimeoutMilliseconds, true); + + var waitEvent3 = new AutoResetEvent(false); + RegisteredWaitHandle registeredWaitHandle3 = + ThreadPool.RegisterWaitForSingleObject(waitEvent3, waitCallback, null, UnexpectedTimeoutMilliseconds, true); + + void SetAndUnregister(AutoResetEvent waitEvent, RegisteredWaitHandle registeredWaitHandle) + { + waitEvent.Set(); + handlePendingRemoval.CheckedWait(); + Thread.Sleep(ExpectedTimeoutMilliseconds); // wait for removal + Assert.True(registeredWaitHandle.Unregister(null)); + completeWaitCallback.Set(); + waitEvent.Dispose(); + } + + SetAndUnregister(waitEvent, registeredWaitHandle); + SetAndUnregister(waitEvent2, registeredWaitHandle2); + + var waitEvent4 = new AutoResetEvent(false); + RegisteredWaitHandle registeredWaitHandle4 = + ThreadPool.RegisterWaitForSingleObject(waitEvent4, waitCallback, null, UnexpectedTimeoutMilliseconds, true); + + SetAndUnregister(waitEvent3, registeredWaitHandle3); + SetAndUnregister(waitEvent4, registeredWaitHandle4); + } } } From 996597f8d0ab0eb8d7c65f4c9719cfafe5245f6a Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Thu, 10 Sep 2020 12:45:25 -0700 Subject: [PATCH 41/48] Fix usage of LowLevelMonitor after rebase --- .../src/System/Threading/LowLevelLock.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs index 4dd16fb3bd9ab6..d9101db5f12a28 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs @@ -36,27 +36,22 @@ internal sealed class LowLevelLock : IDisposable private LowLevelSpinWaiter _spinWaiter; private readonly Func _spinWaitTryAcquireCallback; - private readonly LowLevelMonitor _monitor; + private LowLevelMonitor _monitor; public LowLevelLock() { _spinWaiter = default(LowLevelSpinWaiter); _spinWaitTryAcquireCallback = SpinWaitTryAcquireCallback; - _monitor = new LowLevelMonitor(); + _monitor.Initialize(); } - ~LowLevelLock() => Dispose(false); - public void Dispose() => Dispose(true); + ~LowLevelLock() => Dispose(); - private void Dispose(bool disposing) + public void Dispose() { VerifyIsNotLockedByAnyThread(); - if (disposing && _monitor != null) - { - _monitor.Dispose(); - } - + _monitor.Dispose(); GC.SuppressFinalize(this); } From 82f7cda879876cf184360b128c83b3b1bd7968cc Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 11 Sep 2020 12:01:16 -0700 Subject: [PATCH 42/48] Fix tests that use RemoteExecutor to include a test for whether it is supported, fix assert for browser builds --- .../src/System/Threading/Thread.cs | 2 +- .../tests/ThreadPoolTests.cs | 25 +++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs index e756fee083f9e9..3fc1d7ef81a000 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @@ -200,7 +200,7 @@ internal void SetThreadPoolWorkerThreadName() internal void ResetThreadPoolThread() { Debug.Assert(this == CurrentThread); - Debug.Assert(IsThreadPoolThread); + Debug.Assert(!IsThreadStartSupported || IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads if (_mayNeedResetForThreadPool) { diff --git a/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs b/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs index 0a465ebb3915fe..889f2148360341 100644 --- a/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs +++ b/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs @@ -19,7 +19,10 @@ public partial class ThreadPoolTests static ThreadPoolTests() { // Run the following tests before any others - ConcurrentInitializeTest(); + if (IsThreadingAndRemoteExecutorSupported) + { + ConcurrentInitializeTest(); + } } public static IEnumerable OneBool() => @@ -31,8 +34,7 @@ public static IEnumerable TwoBools() => from b2 in new[] { true, false } select new object[] { b1, b2 }; - // Tests concurrent calls to ThreadPool.SetMinThreads - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + // Tests concurrent calls to ThreadPool.SetMinThreads. Invoked from the static constructor. public static void ConcurrentInitializeTest() { RemoteExecutor.Invoke(() => @@ -90,7 +92,7 @@ public static void GetAvailableThreadsTest() Assert.True(c <= maxc); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))] [ActiveIssue("https://github.com/mono/mono/issues/15164", TestRuntimes.Mono)] public static void SetMinMaxThreadsTest() { @@ -154,7 +156,7 @@ public static void SetMinMaxThreadsTest() }).Dispose(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))] public static void SetMinMaxThreadsTest_ChangedInDotNetCore() { RemoteExecutor.Invoke(() => @@ -197,7 +199,7 @@ private static void VerifyMaxThreads(int expectedMaxw, int expectedMaxc) Assert.Equal(expectedMaxc, maxc); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))] public static void SetMinThreadsTo0Test() { RemoteExecutor.Invoke(() => @@ -406,7 +408,9 @@ public InvalidWorkItemAndTask(Action action) : base(action) { } public void Execute() { } } - [ConditionalFact(nameof(HasAtLeastThreeProcessorsAndRemoteExecutorSupported))] + public static bool IsMetricsTestSupported => Environment.ProcessorCount >= 3 && IsThreadingAndRemoteExecutorSupported; + + [ConditionalFact(nameof(IsMetricsTestSupported))] public void MetricsTest() { RemoteExecutor.Invoke(() => @@ -672,7 +676,7 @@ public static void WorkQueueDepletionTest() done.CheckedWait(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))] public static void WorkerThreadStateResetTest() { RemoteExecutor.Invoke(() => @@ -756,7 +760,7 @@ public static void WorkerThreadStateResetTest() }).Dispose(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))] public static void SettingMinWorkerThreadsWillCreateThreadsUpToMinimum() { RemoteExecutor.Invoke(() => @@ -829,6 +833,7 @@ public static void ThreadPoolCanProcessManyWorkItemsInParallelWithoutDeadlocking done.CheckedWait(); } - public static bool HasAtLeastThreeProcessorsAndRemoteExecutorSupported => Environment.ProcessorCount >= 3 && RemoteExecutor.IsSupported; + public static bool IsThreadingAndRemoteExecutorSupported => + PlatformDetection.IsThreadingSupported && RemoteExecutor.IsSupported; } } From b53a5b94c976dabbe4d7ed68e5a324796ae5ea2f Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 11 Sep 2020 13:09:23 -0700 Subject: [PATCH 43/48] Fix build --- .../System.Threading.ThreadPool/tests/ThreadPoolTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs b/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs index 889f2148360341..698baca7bb278f 100644 --- a/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs +++ b/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs @@ -35,7 +35,7 @@ public static IEnumerable TwoBools() => select new object[] { b1, b2 }; // Tests concurrent calls to ThreadPool.SetMinThreads. Invoked from the static constructor. - public static void ConcurrentInitializeTest() + private static void ConcurrentInitializeTest() { RemoteExecutor.Invoke(() => { From 7dcb267b1cf39df669b708e070de597bb1ba3539 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 25 Sep 2020 10:39:17 -0700 Subject: [PATCH 44/48] Slight fix to starvation heuristic to be closer to what it was before --- .../src/System/Threading/PortableThreadPool.GateThread.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index 98bc09fe4a5582..71debc9231c73c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -85,7 +85,7 @@ private static void GateThreadStart() } ThreadCounts newCounts = counts; - short newNumThreadsGoal = (short)(counts.NumThreadsGoal + 1); + short newNumThreadsGoal = (short)(counts.NumProcessingWork + 1); newCounts.NumThreadsGoal = newNumThreadsGoal; ThreadCounts oldCounts = threadPoolInstance._separated.counts.InterlockedCompareExchange(newCounts, counts); From 65840d89cd04162522de29e7127618a2ccc521db Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Thu, 8 Oct 2020 17:57:21 -0700 Subject: [PATCH 45/48] Fix license headers in new files --- .../src/System/Threading/LowLevelLifoSemaphore.Unix.cs | 1 - .../System.Private.CoreLib/src/System/Threading/LowLevelLock.cs | 1 - .../src/System/Threading/PortableThreadPool.WorkerTracking.cs | 1 - .../src/System/Threading/RegisteredWaitHandle.Portable.cs | 1 - .../System.Threading.ThreadPool/tests/RegisteredWaitTests.cs | 1 - 5 files changed, 5 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs index 80483d4b9666b8..552fed57a7b9e1 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System.Diagnostics; using System.Runtime.CompilerServices; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs index d9101db5f12a28..4bf512a874996b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System.Diagnostics; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerTracking.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerTracking.cs index 292cdda4f15cc8..49e017435bcf77 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerTracking.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerTracking.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System.Diagnostics; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs index 54585611700590..6f67c1953aff68 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System.Diagnostics; diff --git a/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs b/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs index c6ea0a736c1d12..e900696dad339c 100644 --- a/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs +++ b/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System.Threading.Tests; using Microsoft.DotNet.RemoteExecutor; From fb28ad22da8364345ea5f01eca28971ff475596c Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Thu, 8 Oct 2020 18:47:47 -0700 Subject: [PATCH 46/48] Address feedback --- src/coreclr/src/vm/comthreadpool.cpp | 68 +++++++++---------- .../src/System/Threading/LowLevelLock.cs | 1 - .../System/Threading/ThreadPool.Portable.cs | 2 +- .../tests/RegisteredWaitTests.cs | 4 +- 4 files changed, 34 insertions(+), 41 deletions(-) diff --git a/src/coreclr/src/vm/comthreadpool.cpp b/src/coreclr/src/vm/comthreadpool.cpp index c7092a2272b52e..85edc1bcdc0ae2 100644 --- a/src/coreclr/src/vm/comthreadpool.cpp +++ b/src/coreclr/src/vm/comthreadpool.cpp @@ -123,6 +123,9 @@ DelegateInfo *DelegateInfo::MakeDelegateInfo(OBJECTREF *state, } /*****************************************************************************************************/ +// Enumerates some runtime config variables that are used by CoreLib for initialization. The config variable index should start +// at 0 to begin enumeration. If a config variable at or after the specified config variable index is configured, returns the +// next config variable index to pass in on the next call to continue enumeration. FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, INT32 configVariableIndex, UINT32 *configValueRef, @@ -143,12 +146,9 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, return -1; } - INT32 nextConfigVariableIndex = configVariableIndex; auto TryGetConfig = - [&](const CLRConfig::ConfigDWORDInfo &configInfo, bool isBoolean, const WCHAR *appContextConfigName) -> bool + [=](const CLRConfig::ConfigDWORDInfo &configInfo, bool isBoolean, const WCHAR *appContextConfigName) -> bool { - ++nextConfigVariableIndex; - bool wasNotConfigured = true; *configValueRef = CLRConfig::GetConfigValue(configInfo, true /* acceptExplicitDefaultFromRegutil */, &wasNotConfigured); if (wasNotConfigured) @@ -168,29 +168,29 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, *configValueRef = 1; *isBooleanRef = true; *appContextConfigNameRef = NULL; - return nextConfigVariableIndex + 1; - - case 1: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMinWorkerThreads, false, W("System.Threading.ThreadPool.MinThreads"))) { return nextConfigVariableIndex; } // fall through - case 2: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMaxWorkerThreads, false, W("System.Threading.ThreadPool.MaxThreads"))) { return nextConfigVariableIndex; } // fall through - case 3: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection, true, W("System.Threading.ThreadPool.DisableStarvationDetection"))) { return nextConfigVariableIndex; } // fall through - case 4: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DebugBreakOnWorkerStarvation, true, W("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation"))) { return nextConfigVariableIndex; } // fall through - case 5: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking, true, W("System.Threading.ThreadPool.EnableWorkerTracking"))) { return nextConfigVariableIndex; } // fall through - case 6: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit, false, W("System.Threading.ThreadPool.UnfairSemaphoreSpinLimit"))) { return nextConfigVariableIndex; } // fall through - - case 7: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Disable, true, W("System.Threading.ThreadPool.HillClimbing.Disable"))) { return nextConfigVariableIndex; } // fall through - case 8: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WavePeriod, false, W("System.Threading.ThreadPool.HillClimbing.WavePeriod"))) { return nextConfigVariableIndex; } // fall through - case 9: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_TargetSignalToNoiseRatio, false, W("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio"))) { return nextConfigVariableIndex; } // fall through - case 10: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_ErrorSmoothingFactor, false, W("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor"))) { return nextConfigVariableIndex; } // fall through - case 11: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveMagnitudeMultiplier, false, W("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier"))) { return nextConfigVariableIndex; } // fall through - case 12: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxWaveMagnitude, false, W("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude"))) { return nextConfigVariableIndex; } // fall through - case 13: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveHistorySize, false, W("System.Threading.ThreadPool.HillClimbing.WaveHistorySize"))) { return nextConfigVariableIndex; } // fall through - case 14: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Bias, false, W("System.Threading.ThreadPool.HillClimbing.Bias"))) { return nextConfigVariableIndex; } // fall through - case 15: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSecond, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond"))) { return nextConfigVariableIndex; } // fall through - case 16: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSample, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample"))) { return nextConfigVariableIndex; } // fall through - case 17: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxSampleErrorPercent, false, W("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent"))) { return nextConfigVariableIndex; } // fall through - case 18: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow"))) { return nextConfigVariableIndex; } // fall through - case 19: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalHigh, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh"))) { return nextConfigVariableIndex; } // fall through - case 20: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_GainExponent, false, W("System.Threading.ThreadPool.HillClimbing.GainExponent"))) { return nextConfigVariableIndex; } // fall through + return 1; + + case 1: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMinWorkerThreads, false, W("System.Threading.ThreadPool.MinThreads"))) { return 2; } // fall through + case 2: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMaxWorkerThreads, false, W("System.Threading.ThreadPool.MaxThreads"))) { return 3; } // fall through + case 3: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection, true, W("System.Threading.ThreadPool.DisableStarvationDetection"))) { return 4; } // fall through + case 4: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DebugBreakOnWorkerStarvation, true, W("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation"))) { return 5; } // fall through + case 5: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking, true, W("System.Threading.ThreadPool.EnableWorkerTracking"))) { return 6; } // fall through + case 6: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit, false, W("System.Threading.ThreadPool.UnfairSemaphoreSpinLimit"))) { return 7; } // fall through + + case 7: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Disable, true, W("System.Threading.ThreadPool.HillClimbing.Disable"))) { return 8; } // fall through + case 8: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WavePeriod, false, W("System.Threading.ThreadPool.HillClimbing.WavePeriod"))) { return 9; } // fall through + case 9: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_TargetSignalToNoiseRatio, false, W("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio"))) { return 10; } // fall through + case 10: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_ErrorSmoothingFactor, false, W("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor"))) { return 11; } // fall through + case 11: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveMagnitudeMultiplier, false, W("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier"))) { return 12; } // fall through + case 12: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxWaveMagnitude, false, W("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude"))) { return 13; } // fall through + case 13: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveHistorySize, false, W("System.Threading.ThreadPool.HillClimbing.WaveHistorySize"))) { return 14; } // fall through + case 14: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Bias, false, W("System.Threading.ThreadPool.HillClimbing.Bias"))) { return 15; } // fall through + case 15: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSecond, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond"))) { return 16; } // fall through + case 16: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSample, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample"))) { return 17; } // fall through + case 17: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxSampleErrorPercent, false, W("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent"))) { return 18; } // fall through + case 18: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow"))) { return 19; } // fall through + case 19: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalHigh, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh"))) { return 20; } // fall through + case 20: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_GainExponent, false, W("System.Threading.ThreadPool.HillClimbing.GainExponent"))) { return 21; } // fall through default: *configValueRef = 0; @@ -561,18 +561,12 @@ FCIMPL1(void, ThreadPoolNative::CorQueueWaitCompletion, Object* completeWaitWork FCALL_CONTRACT; _ASSERTE_ALL_BUILDS(__FILE__, ThreadpoolMgr::UsePortableThreadPool()); - HANDLE completeWaitWorkItemHandle = NULL; - struct _gc - { - OBJECTREF completeWaitWorkItemObject; - } gc; - gc.completeWaitWorkItemObject = ObjectToOBJECTREF(completeWaitWorkItemObjectUNSAFE); - - HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); + OBJECTREF completeWaitWorkItemObject = ObjectToOBJECTREF(completeWaitWorkItemObjectUNSAFE); + HELPER_METHOD_FRAME_BEGIN_1(completeWaitWorkItemObject); - _ASSERTE(gc.completeWaitWorkItemObject != NULL); + _ASSERTE(completeWaitWorkItemObject != NULL); - OBJECTHANDLE completeWaitWorkItemObjectHandle = GetAppDomain()->CreateHandle(gc.completeWaitWorkItemObject); + OBJECTHANDLE completeWaitWorkItemObjectHandle = GetAppDomain()->CreateHandle(completeWaitWorkItemObject); ThreadpoolMgr::PostQueuedCompletionStatus( (LPOVERLAPPED)completeWaitWorkItemObjectHandle, ThreadpoolMgr::ManagedWaitIOCompletionCallback); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs index 4bf512a874996b..1c3147540dd984 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs @@ -13,7 +13,6 @@ namespace System.Threading /// internal sealed class LowLevelLock : IDisposable { - // TODO: Tune these values private const int SpinCount = 8; private const int SpinSleep0Threshold = 4; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs index d509187237bf9d..25ed12783b84bf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs @@ -31,7 +31,7 @@ internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkIt public static partial class ThreadPool { - // Time-senstiive work items are those that may need to run ahead of normal work items at least periodically. For a + // Time-sensitive work items are those that may need to run ahead of normal work items at least periodically. For a // runtime that does not support time-sensitive work items on the managed side, the thread pool yields the thread to the // runtime periodically (by exiting the dispatch loop) so that the runtime may use that thread for processing // any time-sensitive work. For a runtime that supports time-sensitive work items on the managed side, the thread pool diff --git a/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs b/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs index e900696dad339c..f985c1a7a521d6 100644 --- a/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs +++ b/src/libraries/System.Threading.ThreadPool/tests/RegisteredWaitTests.cs @@ -180,7 +180,7 @@ public static void QueueRegisterNegativeTest() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public static void SignalingRegisteredWaitHandleCallsCalback() + public static void SignalingRegisteredWaitHandleCallsCallback() { var waitEvent = new AutoResetEvent(false); var waitCallbackInvoked = new AutoResetEvent(false); @@ -356,7 +356,7 @@ public static void CanRegisterMoreThan64Waits() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public static void StateIsPasssedThroughToCallback() + public static void StateIsPassedThroughToCallback() { object state = new object(); var waitCallbackInvoked = new AutoResetEvent(false); From 04326b6dd1bc9da50400d5545a712a785745a869 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 16 Oct 2020 09:47:59 -0700 Subject: [PATCH 47/48] Change pattern for accessing the event source for better ILLinker compatibility --- .../Threading/PortableThreadPool.GateThread.cs | 7 ++++--- .../Threading/PortableThreadPool.HillClimbing.cs | 14 ++++++-------- .../Threading/PortableThreadPool.WaitThread.cs | 10 ++++------ .../Threading/PortableThreadPool.WorkerThread.cs | 14 ++++++-------- .../src/System/Threading/ThreadPool.cs | 2 +- 5 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index 71debc9231c73c..c509fdfa008c9c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -33,7 +33,6 @@ private static void GateThreadStart() _ = cpuUtilizationReader.CurrentUtilization; PortableThreadPool threadPoolInstance = ThreadPoolInstance; - PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; LowLevelLock hillClimbingThreadAdjustmentLock = threadPoolInstance._hillClimbingThreadAdjustmentLock; while (true) @@ -46,9 +45,11 @@ private static void GateThreadStart() Thread.Sleep(GateThreadDelayMs); if (ThreadPool.EnableWorkerTracking && - log.IsEnabled(EventLevel.Verbose, PortableThreadPoolEventSource.Keywords.ThreadingKeyword)) + PortableThreadPoolEventSource.Log.IsEnabled( + EventLevel.Verbose, + PortableThreadPoolEventSource.Keywords.ThreadingKeyword)) { - log.ThreadPoolWorkingThreadCount( + PortableThreadPoolEventSource.Log.ThreadPoolWorkingThreadCount( (uint)threadPoolInstance.GetAndResetHighWatermarkCountOfThreadsProcessingUserCallbacks()); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs index 92e95a52a8b645..38995cc1ce0b95 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs @@ -188,10 +188,9 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // Add the current thread count and throughput sample to our history // double throughput = numCompletions / sampleDurationSeconds; - PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; - if (log.IsEnabled()) + if (PortableThreadPoolEventSource.Log.IsEnabled()) { - log.ThreadPoolWorkerThreadAdjustmentSample(throughput); + PortableThreadPoolEventSource.Log.ThreadPoolWorkerThreadAdjustmentSample(throughput); } int sampleIndex = (int)(_totalSamples % _samplesToMeasure); @@ -363,9 +362,9 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu // Record these numbers for posterity // - if (log.IsEnabled()) + if (PortableThreadPoolEventSource.Log.IsEnabled()) { - log.ThreadPoolWorkerThreadAdjustmentStats(sampleDurationSeconds, throughput, threadWaveComponent.Real, throughputWaveComponent.Real, + PortableThreadPoolEventSource.Log.ThreadPoolWorkerThreadAdjustmentStats(sampleDurationSeconds, throughput, threadWaveComponent.Real, throughputWaveComponent.Real, throughputErrorEstimate, _averageThroughputNoise, ratio.Real, confidence, _currentControlSetting, (ushort)newThreadWaveMagnitude); } @@ -424,10 +423,9 @@ private void LogTransition(int newThreadCount, double throughput, StateOrTransit _logSize++; - PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; - if (log.IsEnabled()) + if (PortableThreadPoolEventSource.Log.IsEnabled()) { - log.ThreadPoolWorkerThreadAdjustmentAdjustment( + PortableThreadPoolEventSource.Log.ThreadPoolWorkerThreadAdjustmentAdjustment( throughput, (uint)newThreadCount, (PortableThreadPoolEventSource.ThreadAdjustmentReasonMap)stateOrTransition); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs index ed0580344d80b9..5e43ef3f9f700b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs @@ -21,10 +21,9 @@ internal partial class PortableThreadPool /// A description of the requested registration. internal void RegisterWaitHandle(RegisteredWaitHandle handle) { - PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; - if (log.IsEnabled()) + if (PortableThreadPoolEventSource.Log.IsEnabled()) { - log.ThreadPoolIOEnqueue(handle); + PortableThreadPoolEventSource.Log.ThreadPoolIOEnqueue(handle); } _waitThreadLock.Acquire(); @@ -61,10 +60,9 @@ internal void RegisterWaitHandle(RegisteredWaitHandle handle) internal static void CompleteWait(RegisteredWaitHandle handle, bool timedOut) { - PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; - if (log.IsEnabled()) + if (PortableThreadPoolEventSource.Log.IsEnabled()) { - log.ThreadPoolIODequeue(handle); + PortableThreadPoolEventSource.Log.ThreadPoolIODequeue(handle); } handle.PerformCallback(timedOut); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs index ec959788cbea62..1f52e9deb25fe8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @@ -20,10 +20,9 @@ private static class WorkerThread AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.UnfairSemaphoreSpinLimit", 70, false), onWait: () => { - PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; - if (log.IsEnabled()) + if (PortableThreadPoolEventSource.Log.IsEnabled()) { - log.ThreadPoolWorkerThreadWait( + PortableThreadPoolEventSource.Log.ThreadPoolWorkerThreadWait( (uint)ThreadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); } }); @@ -34,10 +33,9 @@ private static void WorkerThreadStart() PortableThreadPool threadPoolInstance = ThreadPoolInstance; - PortableThreadPoolEventSource log = PortableThreadPoolEventSource.Log; - if (log.IsEnabled()) + if (PortableThreadPoolEventSource.Log.IsEnabled()) { - log.ThreadPoolWorkerThreadStart( + PortableThreadPoolEventSource.Log.ThreadPoolWorkerThreadStart( (uint)threadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); } @@ -99,9 +97,9 @@ private static void WorkerThreadStart() { HillClimbing.ThreadPoolHillClimber.ForceChange(newNumThreadsGoal, HillClimbing.StateOrTransition.ThreadTimedOut); - if (log.IsEnabled()) + if (PortableThreadPoolEventSource.Log.IsEnabled()) { - log.ThreadPoolWorkerThreadStop((uint)newNumExistingThreads); + PortableThreadPoolEventSource.Log.ThreadPoolWorkerThreadStop((uint)newNumExistingThreads); } return; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index c95a95022053f6..3bbb27ffc6b7fc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -488,7 +488,7 @@ public void EnqueueTimeSensitiveWorkItem(IThreadPoolWorkItem timeSensitiveWorkIt { Debug.Assert(ThreadPool.SupportsTimeSensitiveWorkItems); - if (loggingEnabled) + if (loggingEnabled && FrameworkEventSource.Log.IsEnabled()) { FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(timeSensitiveWorkItem); } From ec038cfd968953a87937f52960139e993f9363d1 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 16 Oct 2020 10:22:36 -0700 Subject: [PATCH 48/48] Move hill climbing config reads into constructor instead of passing them as parameters --- .../PortableThreadPool.HillClimbing.cs | 49 ++++++------------- 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs index 38995cc1ce0b95..c7ed0ee800c934 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs @@ -19,26 +19,7 @@ private partial class HillClimbing public static readonly bool IsDisabled = AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.HillClimbing.Disable", false); // SOS's ThreadPool command depends on this name - public static readonly HillClimbing ThreadPoolHillClimber = CreateHillClimber(); - - private static HillClimbing CreateHillClimber() - { - // Default values pulled from CoreCLR - return new HillClimbing(wavePeriod: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.WavePeriod", 4, false), - maxWaveMagnitude: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude", 20, false), - waveMagnitudeMultiplier: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier", 100, false) / 100.0, - waveHistorySize: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.WaveHistorySize", 8, false), - targetThroughputRatio: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.Bias", 15, false) / 100.0, - targetSignalToNoiseRatio: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio", 300, false) / 100.0, - maxChangePerSecond: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond", 4, false), - maxChangePerSample: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample", 20, false), - sampleIntervalMsLow: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow", DefaultSampleIntervalMsLow, false), - sampleIntervalMsHigh: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh", DefaultSampleIntervalMsHigh, false), - errorSmoothingFactor: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor", 1, false) / 100.0, - gainExponent: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.GainExponent", 200, false) / 100.0, - maxSampleError: AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent", 15, false) / 100.0 - ); - } + public static readonly HillClimbing ThreadPoolHillClimber = new HillClimbing(); // SOS's ThreadPool command depends on the enum values public enum StateOrTransition @@ -95,18 +76,18 @@ private struct LogEntry private int _logStart; // SOS's ThreadPool command depends on this name private int _logSize; // SOS's ThreadPool command depends on this name - public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMultiplier, int waveHistorySize, double targetThroughputRatio, - double targetSignalToNoiseRatio, double maxChangePerSecond, double maxChangePerSample, int sampleIntervalMsLow, int sampleIntervalMsHigh, - double errorSmoothingFactor, double gainExponent, double maxSampleError) + public HillClimbing() { - _wavePeriod = wavePeriod; - _maxThreadWaveMagnitude = maxWaveMagnitude; - _threadMagnitudeMultiplier = waveMagnitudeMultiplier; - _samplesToMeasure = wavePeriod * waveHistorySize; - _targetThroughputRatio = targetThroughputRatio; - _targetSignalToNoiseRatio = targetSignalToNoiseRatio; - _maxChangePerSecond = maxChangePerSecond; - _maxChangePerSample = maxChangePerSample; + _wavePeriod = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.WavePeriod", 4, false); + _maxThreadWaveMagnitude = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude", 20, false); + _threadMagnitudeMultiplier = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier", 100, false) / 100.0; + _samplesToMeasure = _wavePeriod * AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.WaveHistorySize", 8, false); + _targetThroughputRatio = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.Bias", 15, false) / 100.0; + _targetSignalToNoiseRatio = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio", 300, false) / 100.0; + _maxChangePerSecond = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond", 4, false); + _maxChangePerSample = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample", 20, false); + int sampleIntervalMsLow = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow", DefaultSampleIntervalMsLow, false); + int sampleIntervalMsHigh = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh", DefaultSampleIntervalMsHigh, false); if (sampleIntervalMsLow <= sampleIntervalMsHigh) { _sampleIntervalMsLow = sampleIntervalMsLow; @@ -117,9 +98,9 @@ public HillClimbing(int wavePeriod, int maxWaveMagnitude, double waveMagnitudeMu _sampleIntervalMsLow = DefaultSampleIntervalMsLow; _sampleIntervalMsHigh = DefaultSampleIntervalMsHigh; } - _throughputErrorSmoothingFactor = errorSmoothingFactor; - _gainExponent = gainExponent; - _maxSampleError = maxSampleError; + _throughputErrorSmoothingFactor = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor", 1, false) / 100.0; + _gainExponent = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.GainExponent", 200, false) / 100.0; + _maxSampleError = AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent", 15, false) / 100.0; _samples = new double[_samplesToMeasure]; _threadCounts = new double[_samplesToMeasure];