diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs index 2f7fd6721e67e7..141aa3a7290791 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs @@ -25,41 +25,6 @@ namespace System.Threading { - #region class _IOCompletionCallback - - internal sealed unsafe partial class _IOCompletionCallback - { - // call back helper - internal static void PerformIOCompletionCallback(uint errorCode, uint numBytes, NativeOverlapped* pNativeOverlapped) - { - do - { - OverlappedData overlapped = OverlappedData.GetOverlappedFromNative(pNativeOverlapped); - - if (overlapped._callback is IOCompletionCallback iocb) - { - // We got here because of UnsafePack (or) Pack with EC flow suppressed - iocb(errorCode, numBytes, pNativeOverlapped); - } - else - { - // We got here because of Pack - var helper = (_IOCompletionCallback?)overlapped._callback; - Debug.Assert(helper != null, "Should only be receiving a completion callback if a delegate was provided."); - helper._errorCode = errorCode; - helper._numBytes = numBytes; - helper._pNativeOverlapped = pNativeOverlapped; - ExecutionContext.RunInternal(helper._executionContext, IOCompletionCallback_Context_Delegate, helper); - } - - // Quickly check the VM again, to see if a packet has arrived. - OverlappedData.CheckVMForIOPacket(out pNativeOverlapped, out errorCode, out numBytes); - } while (pNativeOverlapped != null); - } - } - - #endregion class _IOCompletionCallback - #region class OverlappedData internal sealed unsafe class OverlappedData @@ -120,9 +85,6 @@ internal sealed unsafe class OverlappedData [MethodImpl(MethodImplOptions.InternalCall)] internal static extern OverlappedData GetOverlappedFromNative(NativeOverlapped* nativeOverlappedPtr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void CheckVMForIOPacket(out NativeOverlapped* pNativeOverlapped, out uint errorCode, out uint numBytes); } #endregion class OverlappedData diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index fda212fdf08227..e44eb3b3d74e4c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -356,13 +356,6 @@ internal void ResetThreadPoolThread() 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(); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs index 66b91d05c38d36..279abdf1baa204 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs @@ -18,32 +18,19 @@ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapp ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped); } - if (UsePortableThreadPoolForIO) - { - // OS doesn't signal handle, so do it here - overlapped->InternalLow = IntPtr.Zero; - - PortableThreadPool.ThreadPoolInstance.QueueNativeOverlapped(overlapped); - return true; - } + // OS doesn't signal handle, so do it here + overlapped->InternalLow = IntPtr.Zero; - return PostQueuedCompletionStatus(overlapped); + PortableThreadPool.ThreadPoolInstance.QueueNativeOverlapped(overlapped); + return true; } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern unsafe bool PostQueuedCompletionStatus(NativeOverlapped* overlapped); - [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] [SupportedOSPlatform("windows")] public static bool BindHandle(IntPtr osHandle) { - if (UsePortableThreadPoolForIO) - { - PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle); - return true; - } - - return BindIOCompletionCallbackNative(osHandle); + PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle); + return true; } [SupportedOSPlatform("windows")] @@ -56,13 +43,8 @@ public static bool BindHandle(SafeHandle osHandle) { osHandle.DangerousAddRef(ref mustReleaseSafeHandle); - if (UsePortableThreadPoolForIO) - { - PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle.DangerousGetHandle()); - return true; - } - - return BindIOCompletionCallbackNative(osHandle.DangerousGetHandle()); + PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle.DangerousGetHandle()); + return true; } finally { @@ -70,8 +52,5 @@ public static bool BindHandle(SafeHandle osHandle) osHandle.DangerousRelease(); } } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool BindIOCompletionCallbackNative(IntPtr fileHandle); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index 0906b97c6f5f35..031ff0cda2c800 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -18,122 +18,6 @@ namespace System.Threading { - // - // This type is necessary because VS 2010's debugger looks for a method named _ThreadPoolWaitCallbacck.PerformWaitCallback - // on the stack to determine if a thread is a ThreadPool thread or not. We have a better way to do this for .NET 4.5, but - // still need to maintain compatibility with VS 2010. When compat with VS 2010 is no longer an issue, this type may be - // removed. - // - internal static class _ThreadPoolWaitCallback - { - internal static bool PerformWaitCallback() => ThreadPoolWorkQueue.Dispatch(); - } - - public sealed partial class RegisteredWaitHandle : MarshalByRefObject - { - private IntPtr _nativeRegisteredWaitHandle = InvalidHandleValue; - private bool _releaseHandle; - - private static bool IsValidHandle(IntPtr handle) => handle != InvalidHandleValue && handle != IntPtr.Zero; - - internal void SetNativeRegisteredWaitHandle(IntPtr nativeRegisteredWaitHandle) - { - Debug.Assert(!ThreadPool.UsePortableThreadPool); - Debug.Assert(IsValidHandle(nativeRegisteredWaitHandle)); - Debug.Assert(!IsValidHandle(_nativeRegisteredWaitHandle)); - - _nativeRegisteredWaitHandle = nativeRegisteredWaitHandle; - } - - internal void OnBeforeRegister() - { - if (ThreadPool.UsePortableThreadPool) - { - GC.SuppressFinalize(this); - return; - } - - Handle.DangerousAddRef(ref _releaseHandle); - } - - /// - /// 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) - { - if (ThreadPool.UsePortableThreadPool) - { - return UnregisterPortable(waitObject); - } - - s_callbackLock.Acquire(); - try - { - if (!IsValidHandle(_nativeRegisteredWaitHandle) || - !UnregisterWaitNative(_nativeRegisteredWaitHandle, waitObject?.SafeWaitHandle)) - { - return false; - } - _nativeRegisteredWaitHandle = InvalidHandleValue; - - if (_releaseHandle) - { - Handle.DangerousRelease(); - _releaseHandle = false; - } - } - finally - { - s_callbackLock.Release(); - } - - GC.SuppressFinalize(this); - return true; - } - - ~RegisteredWaitHandle() - { - if (ThreadPool.UsePortableThreadPool) - { - return; - } - - s_callbackLock.Acquire(); - try - { - if (!IsValidHandle(_nativeRegisteredWaitHandle)) - { - return; - } - - WaitHandleCleanupNative(_nativeRegisteredWaitHandle); - _nativeRegisteredWaitHandle = InvalidHandleValue; - - if (_releaseHandle) - { - Handle.DangerousRelease(); - _releaseHandle = false; - } - } - finally - { - s_callbackLock.Release(); - } - } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void WaitHandleCleanupNative(IntPtr handle); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool UnregisterWaitNative(IntPtr handle, SafeHandle? waitObject); - } - internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem { void IThreadPoolWorkItem.Execute() => CompleteWait(); @@ -141,7 +25,6 @@ internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkIt // Entry point from unmanaged code private void CompleteWait() { - Debug.Assert(ThreadPool.UsePortableThreadPool); PortableThreadPool.CompleteWait(_registeredWaitHandle, _timedOut); } } @@ -162,24 +45,22 @@ public UnmanagedThreadPoolWorkItem(IntPtr callback, IntPtr state) public static partial class ThreadPool { - private static readonly byte UsePortableThreadPoolConfigValues = InitializeConfigAndDetermineUsePortableThreadPool(); + internal static bool EnsureConfigInitialized() + { + return s_initialized; + } - // SOS's ThreadPool command depends on the following names - internal static readonly bool UsePortableThreadPool = UsePortableThreadPoolConfigValues != 0; - internal static readonly bool UsePortableThreadPoolForIO = UsePortableThreadPoolConfigValues > 1; + private static readonly bool s_initialized = InitializeConfig(); // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that // the runtime may use the thread for processing other work - internal static bool YieldFromDispatchLoop => !UsePortableThreadPool; + internal static bool YieldFromDispatchLoop => false; - // This needs to be initialized after UsePortableThreadPool above, as it may depend on UsePortableThreadPool and the - // config initialization private static readonly bool IsWorkerTrackingEnabledInConfig = GetEnableWorkerTracking(); - private static unsafe byte InitializeConfigAndDetermineUsePortableThreadPool() + private static unsafe bool InitializeConfig() { - byte usePortableThreadPoolConfigValues = 0; - int configVariableIndex = 0; + int configVariableIndex = 1; while (true) { int nextConfigVariableIndex = @@ -196,14 +77,7 @@ private static unsafe byte InitializeConfigAndDetermineUsePortableThreadPool() Debug.Assert(nextConfigVariableIndex > configVariableIndex); configVariableIndex = nextConfigVariableIndex; - if (appContextConfigNameUnsafe == null) - { - // Special case for UsePortableThreadPool and similar, which don't go into the AppContext - Debug.Assert(configValue != 0); - Debug.Assert(!isBoolean); - usePortableThreadPoolConfigValues = (byte)configValue; - continue; - } + Debug.Assert(appContextConfigNameUnsafe != null); var appContextConfigName = new string(appContextConfigNameUnsafe); if (isBoolean) @@ -216,7 +90,7 @@ private static unsafe byte InitializeConfigAndDetermineUsePortableThreadPool() } } - return usePortableThreadPoolConfigValues; + return true; } [MethodImpl(MethodImplOptions.InternalCall)] @@ -227,111 +101,31 @@ private static extern unsafe int GetNextConfigUInt32Value( out char* appContextConfigName); private static bool GetEnableWorkerTracking() => - UsePortableThreadPool - ? AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false) - : GetEnableWorkerTrackingNative(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool CanSetMinIOCompletionThreads(int ioCompletionThreads); - - internal static void SetMinIOCompletionThreads(int ioCompletionThreads) - { - Debug.Assert(UsePortableThreadPool); - Debug.Assert(!UsePortableThreadPoolForIO); - 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(!UsePortableThreadPoolForIO); - Debug.Assert(ioCompletionThreads > 0); - - bool success = SetMaxThreadsNative(1, ioCompletionThreads); // worker thread count is ignored - Debug.Assert(success); - } + AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false); public static bool SetMaxThreads(int workerThreads, int completionPortThreads) { - if (UsePortableThreadPool) - { - return PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads); - } - - return - workerThreads >= 0 && - completionPortThreads >= 0 && - SetMaxThreadsNative(workerThreads, completionPortThreads); + return PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads); } public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) { - if (UsePortableThreadPoolForIO) - { - PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out completionPortThreads); - } - else if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out _); - GetMaxThreadsNative(out _, out completionPortThreads); - } - else - { - GetMaxThreadsNative(out workerThreads, out completionPortThreads); - } + PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out completionPortThreads); } public static bool SetMinThreads(int workerThreads, int completionPortThreads) { - if (UsePortableThreadPool) - { - return PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads, completionPortThreads); - } - - return - workerThreads >= 0 && - completionPortThreads >= 0 && - SetMinThreadsNative(workerThreads, completionPortThreads); + return PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads, completionPortThreads); } public static void GetMinThreads(out int workerThreads, out int completionPortThreads) { - if (UsePortableThreadPoolForIO) - { - PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out completionPortThreads); - } - else if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out _); - GetMinThreadsNative(out _, out completionPortThreads); - } - else - { - GetMinThreadsNative(out workerThreads, out completionPortThreads); - } + PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out completionPortThreads); } public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) { - if (UsePortableThreadPoolForIO) - { - PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads); - } - else if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out _); - GetAvailableThreadsNative(out _, out completionPortThreads); - } - else - { - GetAvailableThreadsNative(out workerThreads, out completionPortThreads); - } + PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads); } /// @@ -344,22 +138,10 @@ public static int ThreadCount { get { - int count = 0; - if (UsePortableThreadPool) - { - count += PortableThreadPool.ThreadPoolInstance.ThreadCount; - } - if (!UsePortableThreadPoolForIO) - { - count += GetThreadCount(); - } - return count; + return PortableThreadPool.ThreadPoolInstance.ThreadCount; } } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern int GetThreadCount(); - /// /// Gets the number of work items that have been processed so far. /// @@ -370,27 +152,10 @@ public static long CompletedWorkItemCount { get { - long count = 0; - if (UsePortableThreadPool) - { - count += PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount; - } - if (!UsePortableThreadPoolForIO) - { - count += GetCompletedWorkItemCount(); - } - return count; + return PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount; } } - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadPool_GetCompletedWorkItemCount")] - private static partial long GetCompletedWorkItemCount(); - - private static long PendingUnmanagedWorkItemCount => UsePortableThreadPool ? 0 : GetPendingUnmanagedWorkItemCount(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern long GetPendingUnmanagedWorkItemCount(); - private static RegisteredWaitHandle RegisterWaitForSingleObject( WaitHandle waitObject, WaitOrTimerCallback callBack, @@ -408,161 +173,43 @@ private static RegisteredWaitHandle RegisterWaitForSingleObject( (int)millisecondsTimeOutInterval, !executeOnlyOnce); - registeredWaitHandle.OnBeforeRegister(); - - if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle); - } - else - { - IntPtr nativeRegisteredWaitHandle = - RegisterWaitForSingleObjectNative( - waitObject, - registeredWaitHandle.Callback, - (uint)registeredWaitHandle.TimeoutDurationMs, - !registeredWaitHandle.Repeating, - registeredWaitHandle); - registeredWaitHandle.SetNativeRegisteredWaitHandle(nativeRegisteredWaitHandle); - } + PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle); return registeredWaitHandle; } internal static void RequestWorkerThread() { - if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.RequestWorker(); - return; - } - - RequestWorkerThreadNative(); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadPool_RequestWorkerThread")] - private static partial Interop.BOOL RequestWorkerThreadNative(); - - // Entry point from unmanaged code - private static void EnsureGateThreadRunning() - { - Debug.Assert(UsePortableThreadPool); - Debug.Assert(!UsePortableThreadPoolForIO); - - PortableThreadPool.EnsureGateThreadRunning(); + 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) - { - Debug.Assert(UsePortableThreadPool); - - if (UsePortableThreadPoolForIO) - { - return false; - } - - return PerformRuntimeSpecificGateActivitiesNative(cpuUtilization) != Interop.BOOL.FALSE; - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadPool_PerformGateActivities")] - private static partial Interop.BOOL PerformRuntimeSpecificGateActivitiesNative(int cpuUtilization); - - // Entry point from unmanaged code - private static void UnsafeQueueUnmanagedWorkItem(IntPtr callback, IntPtr state) - { - Debug.Assert(UsePortableThreadPool); - UnsafeQueueHighPriorityWorkItemInternal(new UnmanagedThreadPoolWorkItem(callback, state)); - } - - // Native methods: - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool SetMinThreadsNative(int workerThreads, int completionPortThreads); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool SetMaxThreadsNative(int workerThreads, int completionPortThreads); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void GetMinThreadsNative(out int workerThreads, out int completionPortThreads); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void GetMaxThreadsNative(out int workerThreads, out int completionPortThreads); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void GetAvailableThreadsNative(out int workerThreads, out int completionPortThreads); - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int currentTimeMs) { - if (UsePortableThreadPool) - { - return - PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete( - threadLocalCompletionCountObject, - currentTimeMs); - } - - return NotifyWorkItemCompleteNative(); + return + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete( + threadLocalCompletionCountObject, + currentTimeMs); } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool NotifyWorkItemCompleteNative(); - internal static void ReportThreadStatus(bool isWorking) { - if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.ReportThreadStatus(isWorking); - return; - } - - ReportThreadStatusNative(isWorking); + PortableThreadPool.ThreadPoolInstance.ReportThreadStatus(isWorking); } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void ReportThreadStatusNative(bool isWorking); - internal static void NotifyWorkItemProgress() { - if (UsePortableThreadPool) - { - PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); - return; - } - - NotifyWorkItemProgressNative(); + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void NotifyWorkItemProgressNative(); - - internal static bool NotifyThreadBlocked() => - UsePortableThreadPool && PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked(); + internal static bool NotifyThreadBlocked() => PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked(); internal static void NotifyThreadUnblocked() { - Debug.Assert(UsePortableThreadPool); PortableThreadPool.ThreadPoolInstance.NotifyThreadUnblocked(); } internal static object? GetOrCreateThreadLocalCompletionCountObject() => - UsePortableThreadPool ? PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject() : null; - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool GetEnableWorkerTrackingNative(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern IntPtr RegisterWaitForSingleObjectNative( - WaitHandle waitHandle, - object state, - uint timeOutInterval, - bool executeOnlyOnce, - RegisteredWaitHandle registeredWaitHandle - ); + PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject(); } } diff --git a/src/coreclr/debug/daccess/enummem.cpp b/src/coreclr/debug/daccess/enummem.cpp index 7db97915d132d1..2503e68718d044 100644 --- a/src/coreclr/debug/daccess/enummem.cpp +++ b/src/coreclr/debug/daccess/enummem.cpp @@ -18,7 +18,6 @@ #include "typestring.h" #include "daccess.h" #include "binder.h" -#include "win32threadpool.h" #include "runtimeinfo.h" #ifdef FEATURE_COMWRAPPERS diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 7e2906cfcae443..c71829f9f6e483 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -10,7 +10,6 @@ //***************************************************************************** #include "stdafx.h" -#include #include "typestring.h" #include @@ -268,91 +267,21 @@ VOID GetJITMethodInfo (EECodeInfo * pCodeInfo, JITTypes *pJITType, CLRDATA_ADDRE } HRESULT -ClrDataAccess::GetWorkRequestData(CLRDATA_ADDRESS addr, struct DacpWorkRequestData *workRequestData) +ClrDataAccess::GetHillClimbingLogEntry(CLRDATA_ADDRESS addr, struct DacpHillClimbingLogEntry* entry) { - if (addr == 0 || workRequestData == NULL) - return E_INVALIDARG; - - SOSDacEnter(); - - WorkRequest *pRequest = PTR_WorkRequest(TO_TADDR(addr)); - workRequestData->Function = (TADDR)(pRequest->Function); - workRequestData->Context = (TADDR)(pRequest->Context); - workRequestData->NextWorkRequest = (TADDR)(pRequest->next); - - SOSDacLeave(); - return hr; + return E_NOTIMPL; } HRESULT -ClrDataAccess::GetHillClimbingLogEntry(CLRDATA_ADDRESS addr, struct DacpHillClimbingLogEntry *entry) +ClrDataAccess::GetWorkRequestData(CLRDATA_ADDRESS addr, struct DacpWorkRequestData *workRequestData) { - if (addr == 0 || entry == NULL) - return E_INVALIDARG; - - SOSDacEnter(); - - HillClimbingLogEntry *pLogEntry = PTR_HillClimbingLogEntry(TO_TADDR(addr)); - entry->TickCount = pLogEntry->TickCount; - entry->NewControlSetting = pLogEntry->NewControlSetting; - entry->LastHistoryCount = pLogEntry->LastHistoryCount; - entry->LastHistoryMean = pLogEntry->LastHistoryMean; - entry->Transition = pLogEntry->Transition; - - SOSDacLeave(); - return hr; + return E_NOTIMPL; } HRESULT ClrDataAccess::GetThreadpoolData(struct DacpThreadpoolData *threadpoolData) { - if (threadpoolData == NULL) - return E_INVALIDARG; - - SOSDacEnter(); - - threadpoolData->cpuUtilization = ThreadpoolMgr::cpuUtilization; - threadpoolData->MinLimitTotalWorkerThreads = ThreadpoolMgr::MinLimitTotalWorkerThreads; - threadpoolData->MaxLimitTotalWorkerThreads = ThreadpoolMgr::MaxLimitTotalWorkerThreads; - - // - // Read ThreadpoolMgr::WorkerCounter - // - TADDR pCounter = DacGetTargetAddrForHostAddr(&ThreadpoolMgr::WorkerCounter,true); - ThreadpoolMgr::ThreadCounter counter; - DacReadAll(pCounter,&counter,sizeof(ThreadpoolMgr::ThreadCounter),true); - ThreadpoolMgr::ThreadCounter::Counts counts = counter.counts; - - threadpoolData->NumWorkingWorkerThreads = counts.NumWorking; - threadpoolData->NumIdleWorkerThreads = counts.NumActive - counts.NumWorking; - threadpoolData->NumRetiredWorkerThreads = counts.NumRetired; - - threadpoolData->FirstUnmanagedWorkRequest = HOST_CDADDR(ThreadpoolMgr::WorkRequestHead); - - threadpoolData->HillClimbingLog = dac_cast(&HillClimbingLog); - threadpoolData->HillClimbingLogFirstIndex = HillClimbingLogFirstIndex; - threadpoolData->HillClimbingLogSize = HillClimbingLogSize; - - - // - // Read ThreadpoolMgr::CPThreadCounter - // - pCounter = DacGetTargetAddrForHostAddr(&ThreadpoolMgr::CPThreadCounter,true); - DacReadAll(pCounter,&counter,sizeof(ThreadpoolMgr::ThreadCounter),true); - counts = counter.counts; - - threadpoolData->NumCPThreads = (LONG)(counts.NumActive + counts.NumRetired); - threadpoolData->NumFreeCPThreads = (LONG)(counts.NumActive - counts.NumWorking); - threadpoolData->MaxFreeCPThreads = ThreadpoolMgr::MaxFreeCPThreads; - threadpoolData->NumRetiredCPThreads = (LONG)(counts.NumRetired); - threadpoolData->MaxLimitTotalCPThreads = ThreadpoolMgr::MaxLimitTotalCPThreads; - threadpoolData->CurrentLimitTotalCPThreads = (LONG)(counts.NumActive); //legacy: currently has no meaning - threadpoolData->MinLimitTotalCPThreads = ThreadpoolMgr::MinLimitTotalCPThreads; - threadpoolData->NumTimers = 0; - threadpoolData->AsyncTimerCallbackCompletionFPtr = NULL; - - SOSDacLeave(); - return hr; + return E_NOTIMPL; } HRESULT ClrDataAccess::GetThreadStoreData(struct DacpThreadStoreData *threadStoreData) diff --git a/src/coreclr/debug/daccess/request_svr.cpp b/src/coreclr/debug/daccess/request_svr.cpp index d913d0f5938ebb..36d21efb8df121 100644 --- a/src/coreclr/debug/daccess/request_svr.cpp +++ b/src/coreclr/debug/daccess/request_svr.cpp @@ -16,7 +16,6 @@ #if defined(FEATURE_SVR_GC) #include -#include #include "request_common.h" int GCHeapCount() diff --git a/src/coreclr/debug/ee/dactable.cpp b/src/coreclr/debug/ee/dactable.cpp index e888d4eda47e9b..6dac100ffb39c4 100644 --- a/src/coreclr/debug/ee/dactable.cpp +++ b/src/coreclr/debug/ee/dactable.cpp @@ -13,8 +13,6 @@ #include #include "../../vm/virtualcallstub.h" -#include "../../vm/win32threadpool.h" -#include "../../vm/hillclimbing.h" #include "../../vm/codeman.h" #include "../../vm/eedbginterfaceimpl.h" #include "../../vm/common.h" diff --git a/src/coreclr/inc/CrstTypes.def b/src/coreclr/inc/CrstTypes.def index 3d2cdcf8203b18..5ab977844a4deb 100644 --- a/src/coreclr/inc/CrstTypes.def +++ b/src/coreclr/inc/CrstTypes.def @@ -292,7 +292,7 @@ End Crst JumpStubCache AcquiredBefore ExecuteManRangeLock LoaderHeap SingleUseLock AcquiredAfter AppDomainCache - ILStubGen ThreadpoolTimerQueue ThreadpoolWaitThreads + ILStubGen TypeIDMap BaseDomain AssemblyLoader End @@ -476,18 +476,6 @@ End Crst ThreadIdDispenser End -Crst ThreadpoolTimerQueue - AcquiredBefore UniqueStack -End - -Crst ThreadpoolWaitThreads - AcquiredBefore UniqueStack -End - -Crst ThreadpoolWorker - AcquiredBefore ThreadIdDispenser ThreadStore -End - Crst ThreadStore AcquiredBefore AvailableParamTypes DeadlockDetection DebuggerController DebuggerHeapLock DebuggerJitInfo DynamicIL ExecuteManRangeLock HandleTable IbcProfile @@ -561,7 +549,7 @@ End Crst TieredCompilation AcquiredAfter CodeVersioning - AcquiredBefore ThreadpoolTimerQueue + AcquiredBefore FuncPtrStubs End Crst COMCallWrapper diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index ce7d1836a363e9..9d8413f4ec0141 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -529,8 +529,6 @@ RETAIL_CONFIG_DWORD_INFO_EX(EXTERNAL_ProcessorCount, W("PROCESSOR_COUNT"), 0, "S /// /// Threadpool /// -RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_UsePortableThreadPool, W("ThreadPool_UsePortableThreadPool"), 1, "Uses the managed portable thread pool implementation instead of the unmanaged one.") -RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadPool_UsePortableThreadPoolForIO, W("ThreadPool_UsePortableThreadPoolForIO"), 1, "Uses the managed portable thread pool implementation instead of the unmanaged one for async IO.") 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") diff --git a/src/coreclr/inc/crsttypes.h b/src/coreclr/inc/crsttypes.h index b5e52aef0a4afb..c41a84b50073f6 100644 --- a/src/coreclr/inc/crsttypes.h +++ b/src/coreclr/inc/crsttypes.h @@ -120,21 +120,18 @@ enum CrstType CrstSystemDomain = 102, CrstSystemDomainDelayedUnloadList = 103, CrstThreadIdDispenser = 104, - CrstThreadpoolTimerQueue = 105, - CrstThreadpoolWaitThreads = 106, - CrstThreadpoolWorker = 107, - CrstThreadStore = 108, - CrstTieredCompilation = 109, - CrstTypeEquivalenceMap = 110, - CrstTypeIDMap = 111, - CrstUMEntryThunkCache = 112, - CrstUMEntryThunkFreeListLock = 113, - CrstUniqueStack = 114, - CrstUnresolvedClassLock = 115, - CrstUnwindInfoTableLock = 116, - CrstVSDIndirectionCellLock = 117, - CrstWrapperTemplate = 118, - kNumberOfCrstTypes = 119 + CrstThreadStore = 105, + CrstTieredCompilation = 106, + CrstTypeEquivalenceMap = 107, + CrstTypeIDMap = 108, + CrstUMEntryThunkCache = 109, + CrstUMEntryThunkFreeListLock = 110, + CrstUniqueStack = 111, + CrstUnresolvedClassLock = 112, + CrstUnwindInfoTableLock = 113, + CrstVSDIndirectionCellLock = 114, + CrstWrapperTemplate = 115, + kNumberOfCrstTypes = 116 }; #endif // __CRST_TYPES_INCLUDED @@ -250,9 +247,6 @@ int g_rgCrstLevelMap[] = 13, // CrstSystemDomain 0, // CrstSystemDomainDelayedUnloadList 0, // CrstThreadIdDispenser - 7, // CrstThreadpoolTimerQueue - 7, // CrstThreadpoolWaitThreads - 13, // CrstThreadpoolWorker 12, // CrstThreadStore 8, // CrstTieredCompilation 4, // CrstTypeEquivalenceMap @@ -374,9 +368,6 @@ LPCSTR g_rgCrstNameMap[] = "CrstSystemDomain", "CrstSystemDomainDelayedUnloadList", "CrstThreadIdDispenser", - "CrstThreadpoolTimerQueue", - "CrstThreadpoolWaitThreads", - "CrstThreadpoolWorker", "CrstThreadStore", "CrstTieredCompilation", "CrstTypeEquivalenceMap", diff --git a/src/coreclr/inc/dacvars.h b/src/coreclr/inc/dacvars.h index 4e91e8fa189f37..e7a1e8b5e82457 100644 --- a/src/coreclr/inc/dacvars.h +++ b/src/coreclr/inc/dacvars.h @@ -100,20 +100,6 @@ DEFINE_DACVAR(PTR_CallCountingStubManager, CallCountingStubManager__g_pManager, DEFINE_DACVAR(PTR_ThreadStore, ThreadStore__s_pThreadStore, ThreadStore::s_pThreadStore) -DEFINE_DACVAR(int, ThreadpoolMgr__cpuUtilization, ThreadpoolMgr::cpuUtilization) -DEFINE_DACVAR(ThreadpoolMgr::ThreadCounter, ThreadpoolMgr__WorkerCounter, ThreadpoolMgr::WorkerCounter) -DEFINE_DACVAR(int, ThreadpoolMgr__MinLimitTotalWorkerThreads, ThreadpoolMgr::MinLimitTotalWorkerThreads) -DEFINE_DACVAR(DWORD, ThreadpoolMgr__MaxLimitTotalWorkerThreads, ThreadpoolMgr::MaxLimitTotalWorkerThreads) -DEFINE_DACVAR(UNKNOWN_POINTER_TYPE /*PTR_WorkRequest*/, ThreadpoolMgr__WorkRequestHead, ThreadpoolMgr::WorkRequestHead) // PTR_WorkRequest is not defined. So use a pointer type -DEFINE_DACVAR(UNKNOWN_POINTER_TYPE /*PTR_WorkRequest*/, ThreadpoolMgr__WorkRequestTail, ThreadpoolMgr::WorkRequestTail) // -DEFINE_DACVAR(ThreadpoolMgr::ThreadCounter, ThreadpoolMgr__CPThreadCounter, ThreadpoolMgr::CPThreadCounter) -DEFINE_DACVAR(LONG, ThreadpoolMgr__MaxFreeCPThreads, ThreadpoolMgr::MaxFreeCPThreads) -DEFINE_DACVAR(LONG, ThreadpoolMgr__MaxLimitTotalCPThreads, ThreadpoolMgr::MaxLimitTotalCPThreads) -DEFINE_DACVAR(LONG, ThreadpoolMgr__MinLimitTotalCPThreads, ThreadpoolMgr::MinLimitTotalCPThreads) -DEFINE_DACVAR_NO_DUMP(SIZE_T, dac__HillClimbingLog, ::HillClimbingLog) -DEFINE_DACVAR(int, dac__HillClimbingLogFirstIndex, ::HillClimbingLogFirstIndex) -DEFINE_DACVAR(int, dac__HillClimbingLogSize, ::HillClimbingLogSize) - DEFINE_DACVAR(PTR_Thread, dac__g_pFinalizerThread, ::g_pFinalizerThread) DEFINE_DACVAR(PTR_Thread, dac__g_pSuspensionThread, ::g_pSuspensionThread) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs index 1811bfad804062..67b66bd0e18484 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs @@ -23,7 +23,5 @@ public static partial class ThreadPool internal static void ReportThreadStatus(bool isWorking) { } - - private static long PendingUnmanagedWorkItemCount => 0; } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs index ba38ba52860115..13bb51c78bd3c6 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs @@ -426,7 +426,7 @@ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapp ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped); } - // OS doesn't signal handle, so do it here (CoreCLR does this assignment in ThreadPoolNative::CorPostQueuedCompletionStatus) + // OS doesn't signal handle, so do it here overlapped->InternalLow = (IntPtr)0; // Both types of callbacks are executed on the same thread pool return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (nint)overlapped, preferLocal: false); diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index cfb764368b576a..a48d0aab98f2a0 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -1270,16 +1270,6 @@ GetExitCodeProcess( IN HANDLE hProcess, IN LPDWORD lpExitCode); -PALIMPORT -BOOL -PALAPI -GetProcessTimes( - IN HANDLE hProcess, - OUT LPFILETIME lpCreationTime, - OUT LPFILETIME lpExitTime, - OUT LPFILETIME lpKernelTime, - OUT LPFILETIME lpUserTime); - #define MAXIMUM_WAIT_OBJECTS 64 #define WAIT_OBJECT_0 0 #define WAIT_ABANDONED 0x00000080 @@ -4549,23 +4539,6 @@ PALIMPORT DLLEXPORT int __cdecl _putenv(const char *); PALIMPORT WCHAR __cdecl PAL_ToUpperInvariant(WCHAR); PALIMPORT WCHAR __cdecl PAL_ToLowerInvariant(WCHAR); -/******************* PAL-specific I/O completion port *****************/ - -typedef struct _PAL_IOCP_CPU_INFORMATION { - union { - FILETIME ftLastRecordedIdleTime; - FILETIME ftLastRecordedCurrentTime; - } LastRecordedTime; - FILETIME ftLastRecordedKernelTime; - FILETIME ftLastRecordedUserTime; -} PAL_IOCP_CPU_INFORMATION; - -PALIMPORT -INT -PALAPI -PAL_GetCPUBusyTime( - IN OUT PAL_IOCP_CPU_INFORMATION *lpPrevCPUInfo); - /****************PAL Perf functions for PInvoke*********************/ #if PAL_PERF PALIMPORT diff --git a/src/coreclr/pal/src/thread/process.cpp b/src/coreclr/pal/src/thread/process.cpp index 69c7fe04326a4e..18209de5070071 100644 --- a/src/coreclr/pal/src/thread/process.cpp +++ b/src/coreclr/pal/src/thread/process.cpp @@ -1819,249 +1819,6 @@ PAL_GetTransportPipeName( suffix); } -/*++ -Function: - GetProcessTimes - -See MSDN doc. ---*/ -BOOL -PALAPI -GetProcessTimes( - IN HANDLE hProcess, - OUT LPFILETIME lpCreationTime, - OUT LPFILETIME lpExitTime, - OUT LPFILETIME lpKernelTime, - OUT LPFILETIME lpUserTime) -{ - BOOL retval = FALSE; - struct rusage resUsage; - UINT64 calcTime; - const UINT64 SECS_TO_100NS = 10000000ULL; // 10^7 - const UINT64 USECS_TO_100NS = 10ULL; // 10 - const UINT64 EPOCH_DIFF = 11644473600ULL; // number of seconds from 1 Jan. 1601 00:00 to 1 Jan 1970 00:00 UTC - - PERF_ENTRY(GetProcessTimes); - ENTRY("GetProcessTimes(hProcess=%p, lpExitTime=%p, lpKernelTime=%p," - "lpUserTime=%p)\n", - hProcess, lpCreationTime, lpExitTime, lpKernelTime, lpUserTime ); - - /* Make sure hProcess is the current process, this is the only supported - case */ - if(PROCGetProcessIDFromHandle(hProcess)!=GetCurrentProcessId()) - { - ASSERT("GetProcessTimes() does not work on a process other than the " - "current process.\n"); - SetLastError(ERROR_INVALID_HANDLE); - goto GetProcessTimesExit; - } - - /* First, we need to actually retrieve the relevant statistics from the - OS */ - if (getrusage (RUSAGE_SELF, &resUsage) == -1) - { - ASSERT("Unable to get resource usage information for the current " - "process\n"); - SetLastError(ERROR_INTERNAL_ERROR); - goto GetProcessTimesExit; - } - - TRACE ("getrusage User: %ld sec,%ld microsec. Kernel: %ld sec,%ld" - " microsec\n", - resUsage.ru_utime.tv_sec, resUsage.ru_utime.tv_usec, - resUsage.ru_stime.tv_sec, resUsage.ru_stime.tv_usec); - - if (lpCreationTime) - { - // The IBC profile data uses this, instead of the actual - // process creation time we just return the current time - - struct timeval tv; - if (gettimeofday(&tv, NULL) == -1) - { - ASSERT("gettimeofday() failed; errno is %d (%s)\n", errno, strerror(errno)); - - // Assign zero to lpCreationTime - lpCreationTime->dwLowDateTime = 0; - lpCreationTime->dwHighDateTime = 0; - } - else - { - calcTime = EPOCH_DIFF; - calcTime += (UINT64)tv.tv_sec; - calcTime *= SECS_TO_100NS; - calcTime += ((UINT64)tv.tv_usec * USECS_TO_100NS); - - // Assign the time into lpCreationTime - lpCreationTime->dwLowDateTime = (DWORD)calcTime; - lpCreationTime->dwHighDateTime = (DWORD)(calcTime >> 32); - } - } - - if (lpExitTime) - { - // Assign zero to lpExitTime - lpExitTime->dwLowDateTime = 0; - lpExitTime->dwHighDateTime = 0; - } - - if (lpUserTime) - { - /* Get the time of user mode execution, in 100s of nanoseconds */ - calcTime = (UINT64)resUsage.ru_utime.tv_sec * SECS_TO_100NS; - calcTime += (UINT64)resUsage.ru_utime.tv_usec * USECS_TO_100NS; - - /* Assign the time into lpUserTime */ - lpUserTime->dwLowDateTime = (DWORD)calcTime; - lpUserTime->dwHighDateTime = (DWORD)(calcTime >> 32); - } - - if (lpKernelTime) - { - /* Get the time of kernel mode execution, in 100s of nanoseconds */ - calcTime = (UINT64)resUsage.ru_stime.tv_sec * SECS_TO_100NS; - calcTime += (UINT64)resUsage.ru_stime.tv_usec * USECS_TO_100NS; - - /* Assign the time into lpUserTime */ - lpKernelTime->dwLowDateTime = (DWORD)calcTime; - lpKernelTime->dwHighDateTime = (DWORD)(calcTime >> 32); - } - - retval = TRUE; - - -GetProcessTimesExit: - LOGEXIT("GetProcessTimes returns BOOL %d\n", retval); - PERF_EXIT(GetProcessTimes); - return (retval); -} - -#define FILETIME_TO_ULONGLONG(f) \ - (((ULONGLONG)(f).dwHighDateTime << 32) | ((ULONGLONG)(f).dwLowDateTime)) - -/*++ -Function: - PAL_GetCPUBusyTime - -The main purpose of this function is to compute the overall CPU utilization -for the CLR thread pool to regulate the number of I/O completion port -worker threads. -Since there is no consistent API on Unix to get the CPU utilization -from a user process, getrusage and gettimeofday are used to -compute the current process's CPU utilization instead. -This function emulates the ThreadpoolMgr::GetCPUBusyTime_NT function in -win32threadpool.cpp of the CLR. - -See MSDN doc for GetSystemTimes. ---*/ -INT -PALAPI -PAL_GetCPUBusyTime( - IN OUT PAL_IOCP_CPU_INFORMATION *lpPrevCPUInfo) -{ - ULONGLONG nLastRecordedCurrentTime = 0; - ULONGLONG nLastRecordedUserTime = 0; - ULONGLONG nLastRecordedKernelTime = 0; - ULONGLONG nKernelTime = 0; - ULONGLONG nUserTime = 0; - ULONGLONG nCurrentTime = 0; - ULONGLONG nCpuBusyTime = 0; - ULONGLONG nCpuTotalTime = 0; - DWORD nReading = 0; - struct rusage resUsage; - struct timeval tv; - static DWORD dwNumberOfProcessors = 0; - - if (dwNumberOfProcessors <= 0) - { - SYSTEM_INFO SystemInfo; - GetSystemInfo(&SystemInfo); - dwNumberOfProcessors = SystemInfo.dwNumberOfProcessors; - if (dwNumberOfProcessors <= 0) - { - return 0; - } - - UINT cpuLimit; - if (PAL_GetCpuLimit(&cpuLimit) && cpuLimit < dwNumberOfProcessors) - { - dwNumberOfProcessors = cpuLimit; - } - } - - if (getrusage(RUSAGE_SELF, &resUsage) == -1) - { - ASSERT("getrusage() failed; errno is %d (%s)\n", errno, strerror(errno)); - return 0; - } - else - { - nKernelTime = (ULONGLONG)resUsage.ru_stime.tv_sec*tccSecondsTo100NanoSeconds + - resUsage.ru_stime.tv_usec*tccMicroSecondsTo100NanoSeconds; - nUserTime = (ULONGLONG)resUsage.ru_utime.tv_sec*tccSecondsTo100NanoSeconds + - resUsage.ru_utime.tv_usec*tccMicroSecondsTo100NanoSeconds; - } - - if (gettimeofday(&tv, NULL) == -1) - { - ASSERT("gettimeofday() failed; errno is %d (%s)\n", errno, strerror(errno)); - return 0; - } - else - { - nCurrentTime = (ULONGLONG)tv.tv_sec*tccSecondsTo100NanoSeconds + - tv.tv_usec*tccMicroSecondsTo100NanoSeconds; - } - - nLastRecordedCurrentTime = FILETIME_TO_ULONGLONG(lpPrevCPUInfo->LastRecordedTime.ftLastRecordedCurrentTime); - nLastRecordedUserTime = FILETIME_TO_ULONGLONG(lpPrevCPUInfo->ftLastRecordedUserTime); - nLastRecordedKernelTime = FILETIME_TO_ULONGLONG(lpPrevCPUInfo->ftLastRecordedKernelTime); - - if (nCurrentTime > nLastRecordedCurrentTime) - { - nCpuTotalTime = (nCurrentTime - nLastRecordedCurrentTime); -#if HAVE_THREAD_SELF || HAVE__LWP_SELF || HAVE_VM_READ - // For systems that run multiple threads of a process on multiple processors, - // the accumulated userTime and kernelTime of this process may exceed - // the elapsed time. In this case, the cpuTotalTime needs to be adjusted - // according to number of processors so that the cpu utilization - // will not be greater than 100. - nCpuTotalTime *= dwNumberOfProcessors; -#endif // HAVE_THREAD_SELF || HAVE__LWP_SELF || HAVE_VM_READ - } - - if (nUserTime >= nLastRecordedUserTime && - nKernelTime >= nLastRecordedKernelTime) - { - nCpuBusyTime = - (nUserTime - nLastRecordedUserTime)+ - (nKernelTime - nLastRecordedKernelTime); - } - - if (nCpuTotalTime > 0 && nCpuBusyTime > 0) - { - nReading = (DWORD)((nCpuBusyTime*100)/nCpuTotalTime); - TRACE("PAL_GetCPUBusyTime: nCurrentTime=%lld, nKernelTime=%lld, nUserTime=%lld, nReading=%d\n", - nCurrentTime, nKernelTime, nUserTime, nReading); - } - - if (nReading > 100) - { - ERROR("cpu utilization(%d) > 100\n", nReading); - } - - lpPrevCPUInfo->LastRecordedTime.ftLastRecordedCurrentTime.dwLowDateTime = (DWORD)nCurrentTime; - lpPrevCPUInfo->LastRecordedTime.ftLastRecordedCurrentTime.dwHighDateTime = (DWORD)(nCurrentTime >> 32); - - lpPrevCPUInfo->ftLastRecordedUserTime.dwLowDateTime = (DWORD)nUserTime; - lpPrevCPUInfo->ftLastRecordedUserTime.dwHighDateTime = (DWORD)(nUserTime >> 32); - - lpPrevCPUInfo->ftLastRecordedKernelTime.dwLowDateTime = (DWORD)nKernelTime; - lpPrevCPUInfo->ftLastRecordedKernelTime.dwHighDateTime = (DWORD)(nKernelTime >> 32); - - return (DWORD)nReading; -} - /*++ Function: GetCommandLineW diff --git a/src/coreclr/pal/tests/palsuite/CMakeLists.txt b/src/coreclr/pal/tests/palsuite/CMakeLists.txt index 4cef1073a89cbe..c88f4effb42ee4 100644 --- a/src/coreclr/pal/tests/palsuite/CMakeLists.txt +++ b/src/coreclr/pal/tests/palsuite/CMakeLists.txt @@ -848,7 +848,6 @@ add_executable_clr(paltests threading/GetCurrentThreadId/test1/threadId.cpp threading/GetExitCodeProcess/test1/childProcess.cpp threading/GetExitCodeProcess/test1/test1.cpp - threading/GetProcessTimes/test2/test2.cpp threading/GetThreadTimes/test1/test1.cpp threading/NamedMutex/test1/namedmutex.cpp threading/NamedMutex/test1/nopal.cpp diff --git a/src/coreclr/pal/tests/palsuite/compilableTests.txt b/src/coreclr/pal/tests/palsuite/compilableTests.txt index 7f2d2804c41331..1cd7c5b964395b 100644 --- a/src/coreclr/pal/tests/palsuite/compilableTests.txt +++ b/src/coreclr/pal/tests/palsuite/compilableTests.txt @@ -738,7 +738,6 @@ threading/GetCurrentThread/test1/paltest_getcurrentthread_test1 threading/GetCurrentThread/test2/paltest_getcurrentthread_test2 threading/GetCurrentThreadId/test1/paltest_getcurrentthreadid_test1 threading/GetExitCodeProcess/test1/paltest_getexitcodeprocess_test1 -threading/GetProcessTimes/test2/paltest_getprocesstimes_test2 threading/GetThreadTimes/test1/paltest_getthreadtimes_test1 threading/NamedMutex/test1/paltest_namedmutex_test1 threading/OpenEventW/test1/paltest_openeventw_test1 diff --git a/src/coreclr/pal/tests/palsuite/paltestlist.txt b/src/coreclr/pal/tests/palsuite/paltestlist.txt index 95cf097c591930..607f8c7d686417 100644 --- a/src/coreclr/pal/tests/palsuite/paltestlist.txt +++ b/src/coreclr/pal/tests/palsuite/paltestlist.txt @@ -649,7 +649,6 @@ threading/ExitThread/test1/paltest_exitthread_test1 threading/GetCurrentProcessId/test1/paltest_getcurrentprocessid_test1 threading/GetCurrentThread/test1/paltest_getcurrentthread_test1 threading/GetCurrentThread/test2/paltest_getcurrentthread_test2 -threading/GetProcessTimes/test2/paltest_getprocesstimes_test2 threading/GetThreadTimes/test1/paltest_getthreadtimes_test1 threading/NamedMutex/test1/paltest_namedmutex_test1 threading/QueryThreadCycleTime/test1/paltest_querythreadcycletime_test1 diff --git a/src/coreclr/pal/tests/palsuite/threading/GetProcessTimes/test2/test2.cpp b/src/coreclr/pal/tests/palsuite/threading/GetProcessTimes/test2/test2.cpp deleted file mode 100644 index 7c1c7a10f65691..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/GetProcessTimes/test2/test2.cpp +++ /dev/null @@ -1,121 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================================= -** -** Source: test2.c -** -** Purpose: Test to ensure GetProcessTimes works properly. -** -** Dependencies: PAL_Initialize -** PAL_Terminate -** Fail -** ZeroMemory -** CompareFileTime -** GetLastError -** - -** -**===========================================================================*/ -#include - - -PALTEST(threading_GetProcessTimes_test2_paltest_getprocesstimes_test2, "threading/GetProcessTimes/test2/paltest_getprocesstimes_test2") - -{ - int i, j, k; - int *total; - - HANDLE hProcess; - FILETIME createTime; - FILETIME exitTime; - FILETIME kernelTime1; - FILETIME userTime1; - FILETIME kernelTime2; - FILETIME userTime2; - - DWORD dwError; - - /* initialize the PAL */ - if( PAL_Initialize(argc, argv) != 0 ) - { - return( FAIL ); - } - - /* get our own process handle */ - hProcess = GetCurrentProcess(); - if( hProcess == NULL ) - { - Fail( "GetCurrentProcess() returned a NULL handle.\n" ); - } - - /* zero our time structures */ - ZeroMemory( &createTime, sizeof(createTime) ); - ZeroMemory( &exitTime, sizeof(exitTime) ); - ZeroMemory( &kernelTime1, sizeof(kernelTime1) ); - ZeroMemory( &userTime1, sizeof(userTime1) ); - ZeroMemory( &kernelTime2, sizeof(kernelTime2) ); - ZeroMemory( &userTime2, sizeof(userTime2) ); - - /* check the process times for the child process */ - if( ! GetProcessTimes( hProcess, - &createTime, - &exitTime, - &kernelTime1, - &userTime1 ) ) - { - dwError = GetLastError(); - Fail( "GetProcessTimes() call failed with error code %d\n", - dwError ); - } - - - /* simulate some activity */ - for( i=0; i<1000; i++ ) - { - for( j=0; j<1000; j++ ) - { - /* do kernel work to increase system usage counters */ - total = (int*)malloc(1024 * 1024); - - *total = j * i; - for( k=0; k<1000; k++ ) - { - *total += k + i; - } - - free(total); - } - } - - /* check the process times for the child process */ - if( ! GetProcessTimes( hProcess, - &createTime, - &exitTime, - &kernelTime2, - &userTime2 ) ) - { - dwError = GetLastError(); - Fail( "GetProcessTimes() call failed with error code %d\n", - dwError ); - } - - - /* very simple logical checking of the results */ - if( CompareFileTime( &kernelTime1, &kernelTime2 ) > 0 ) - { - Fail( "Unexpected kernel time value reported.\n" ); - } - - if( CompareFileTime( &userTime1, &userTime2 ) > 0 ) - { - Fail( "Unexpected user time value reported.\n" ); - } - - - /* terminate the PAL */ - PAL_Terminate(); - - /* return success */ - return PASS; -} diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index b5b9afb48b40e5..c59090a44f4405 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -82,7 +82,6 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON genericdict.cpp generics.cpp hash.cpp - hillclimbing.cpp ilinstrumentation.cpp ilstubcache.cpp ilstubresolver.cpp @@ -116,7 +115,6 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON stublink.cpp stubmgr.cpp syncblk.cpp - threadpoolrequest.cpp threads.cpp threadstatics.cpp tieredcompilation.cpp @@ -130,7 +128,6 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON vars.cpp versionresilienthashcode.cpp virtualcallstub.cpp - win32threadpool.cpp zapsig.cpp ) @@ -180,7 +177,6 @@ set(VM_HEADERS_DAC_AND_WKS_COMMON gcheaputilities.h generics.h hash.h - hillclimbing.h ilinstrumentation.h ilstubcache.h ilstubresolver.h @@ -222,7 +218,6 @@ set(VM_HEADERS_DAC_AND_WKS_COMMON stubmgr.h syncblk.h syncblk.inl - threadpoolrequest.h threads.h threads.inl threadstatics.h @@ -239,7 +234,6 @@ set(VM_HEADERS_DAC_AND_WKS_COMMON vars.hpp versionresilienthashcode.h virtualcallstub.h - win32threadpool.h zapsig.h ) diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 39a02a3ffdd639..a08e5e0494e1fe 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -51,7 +51,6 @@ #include "appdomain.inl" #include "typeparse.h" -#include "threadpoolrequest.h" #include "nativeoverlapped.h" @@ -962,8 +961,6 @@ void SystemDomain::Attach() CallCountingStubManager::Init(); #endif - PerAppDomainTPCountList::InitAppDomainIndexList(); - m_SystemDomainCrst.Init(CrstSystemDomain, (CrstFlags)(CRST_REENTRANCY | CRST_TAKEN_DURING_SHUTDOWN)); m_DelayedUnloadCrst.Init(CrstSystemDomainDelayedUnloadList, CRST_UNSAFE_COOPGC); @@ -1913,14 +1910,7 @@ AppDomain::~AppDomain() } CONTRACTL_END; - - // release the TPIndex. note that since TPIndex values are recycled the TPIndex - // can only be released once all threads in the AppDomain have exited. - if (GetTPIndex().m_dwIndex != 0) - PerAppDomainTPCountList::ResetAppDomainIndex(GetTPIndex()); - m_AssemblyCache.Clear(); - } //***************************************************************************** @@ -1938,11 +1928,6 @@ void AppDomain::Init() SetStage( STAGE_CREATING); - //Allocate the threadpool entry before the appdomain id list. Otherwise, - //the thread pool list will be out of sync if insertion of id in - //the appdomain fails. - m_tpIndex = PerAppDomainTPCountList::AddNewTPIndex(); - BaseDomain::Init(); // Set up the binding caches diff --git a/src/coreclr/vm/appdomain.hpp b/src/coreclr/vm/appdomain.hpp index 5798301810d2d0..d82018c67506ba 100644 --- a/src/coreclr/vm/appdomain.hpp +++ b/src/coreclr/vm/appdomain.hpp @@ -1956,12 +1956,6 @@ class AppDomain : public BaseDomain RCWRefCache *GetRCWRefCache(); #endif // FEATURE_COMWRAPPERS - TPIndex GetTPIndex() - { - LIMITED_METHOD_CONTRACT; - return m_tpIndex; - } - DefaultAssemblyBinder *CreateDefaultBinder(); void SetIgnoreUnhandledExceptions() @@ -2210,9 +2204,6 @@ class AppDomain : public BaseDomain RCWRefCache *m_pRCWRefCache; #endif // FEATURE_COMWRAPPERS - // The thread-pool index of this app domain among existing app domains (starting from 1) - TPIndex m_tpIndex; - Volatile m_Stage; ArrayList m_failedAssemblies; diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 4094b4ff28db83..b3a161260114ac 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -175,8 +175,6 @@ #include "stacksampler.h" #endif -#include "win32threadpool.h" - #include #ifdef FEATURE_COMINTEROP @@ -644,7 +642,6 @@ void EEStartupHelper() IfFailGo(ExecutableAllocator::StaticInitialize(FatalErrorHandler)); Thread::StaticInitialize(); - ThreadpoolMgr::StaticInitialize(); JITInlineTrackingMap::StaticInitialize(); MethodDescBackpatchInfoTracker::StaticInitialize(); diff --git a/src/coreclr/vm/comcache.cpp b/src/coreclr/vm/comcache.cpp index c9afbbed923dd9..573ead1a96e355 100644 --- a/src/coreclr/vm/comcache.cpp +++ b/src/coreclr/vm/comcache.cpp @@ -9,7 +9,6 @@ #include "comcache.h" #include "runtimecallablewrapper.h" #include -#include "win32threadpool.h" #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT #include "olecontexthelpers.h" diff --git a/src/coreclr/vm/comthreadpool.cpp b/src/coreclr/vm/comthreadpool.cpp index 88094ff5323555..2d941b130991de 100644 --- a/src/coreclr/vm/comthreadpool.cpp +++ b/src/coreclr/vm/comthreadpool.cpp @@ -1,126 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Header: COMThreadPool.cpp -** -** Purpose: Native methods on System.ThreadPool -** and its inner classes -** -** -===========================================================*/ - -/********************************************************************************************************************/ #include "common.h" -#include "comdelegate.h" + #include "comthreadpool.h" -#include "threadpoolrequest.h" -#include "win32threadpool.h" -#include "class.h" -#include "object.h" -#include "field.h" -#include "excep.h" #include "eeconfig.h" -#include "corhost.h" -#include "nativeoverlapped.h" -#include "comsynchronizable.h" -#include "callhelpers.h" -#include "appdomain.inl" -/*****************************************************************************************************/ -#ifdef _DEBUG -void LogCall(MethodDesc* pMD, LPCUTF8 api) -{ - LIMITED_METHOD_CONTRACT; - - LPCUTF8 cls = pMD->GetMethodTable()->GetDebugClassName(); - LPCUTF8 name = pMD->GetName(); - - LOG((LF_THREADPOOL,LL_INFO1000,"%s: ", api)); - LOG((LF_THREADPOOL, LL_INFO1000, - " calling %s.%s\n", cls, name)); -} -#else -#define LogCall(pMd,api) -#endif - -VOID -AcquireDelegateInfo(DelegateInfo *pDelInfo) -{ - LIMITED_METHOD_CONTRACT; -} - -VOID -ReleaseDelegateInfo(DelegateInfo *pDelInfo) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - } CONTRACTL_END; - - // The release methods of holders can be called with preemptive GC enabled. Ensure we're in cooperative mode - // before calling pDelInfo->Release(), since that requires coop mode. - GCX_COOP(); - - pDelInfo->Release(); - ThreadpoolMgr::RecycleMemory( pDelInfo, ThreadpoolMgr::MEMTYPE_DelegateInfo ); -} - -//typedef Holder DelegateInfoHolder; - -typedef Wrapper DelegateInfoHolder; - -/*****************************************************************************************************/ -// Caller has to GC protect Objectrefs being passed in -DelegateInfo *DelegateInfo::MakeDelegateInfo(OBJECTREF *state, - OBJECTREF *waitEvent, - OBJECTREF *registeredWaitHandle) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - if (state != NULL || waitEvent != NULL || registeredWaitHandle != NULL) - { - MODE_COOPERATIVE; - } - else - { - MODE_ANY; - } - PRECONDITION(state == NULL || IsProtectedByGCFrame(state)); - PRECONDITION(waitEvent == NULL || IsProtectedByGCFrame(waitEvent)); - PRECONDITION(registeredWaitHandle == NULL || IsProtectedByGCFrame(registeredWaitHandle)); - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - DelegateInfoHolder delegateInfo = (DelegateInfo*) ThreadpoolMgr::GetRecycledMemory(ThreadpoolMgr::MEMTYPE_DelegateInfo); - - AppDomain* pAppDomain = ::GetAppDomain(); - - if (state != NULL) - delegateInfo->m_stateHandle = pAppDomain->CreateHandle(*state); - else - delegateInfo->m_stateHandle = NULL; - - if (waitEvent != NULL) - delegateInfo->m_eventHandle = pAppDomain->CreateHandle(*waitEvent); - else - delegateInfo->m_eventHandle = NULL; - - if (registeredWaitHandle != NULL) - delegateInfo->m_registeredWaitHandle = pAppDomain->CreateHandle(*registeredWaitHandle); - else - delegateInfo->m_registeredWaitHandle = NULL; - - delegateInfo.SuppressRelease(); - - return delegateInfo; -} /*****************************************************************************************************/ // Enumerates some runtime config variables that are used by CoreLib for initialization. The config variable index should start @@ -138,14 +22,6 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, _ASSERTE(isBooleanRef != NULL); _ASSERTE(appContextConfigNameRef != NULL); - if (!ThreadpoolMgr::UsePortableThreadPool()) - { - *configValueRef = 0; - *isBooleanRef = false; - *appContextConfigNameRef = NULL; - return -1; - } - auto TryGetConfig = [=](const CLRConfig::ConfigDWORDInfo &configInfo, bool isBoolean, const WCHAR *appContextConfigName) -> bool { @@ -163,13 +39,6 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, switch (configVariableIndex) { - case 0: - // Special case for UsePortableThreadPool and similar, which don't go into the AppContext - *configValueRef = ThreadpoolMgr::UsePortableThreadPoolForIO() ? 2 : 1; - *isBooleanRef = false; - *appContextConfigNameRef = NULL; - return 1; - case 1: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMinWorkerThreads, false, W("System.Threading.ThreadPool.MinThreads"))) { return 2; } FALLTHROUGH; case 2: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMaxWorkerThreads, false, W("System.Threading.ThreadPool.MaxThreads"))) { return 3; } FALLTHROUGH; case 3: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection, true, W("System.Threading.ThreadPool.DisableStarvationDetection"))) { return 4; } FALLTHROUGH; @@ -200,655 +69,3 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, } } FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorCanSetMinIOCompletionThreads, DWORD ioCompletionThreads) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(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(ThreadpoolMgr::UsePortableThreadPool()); - - BOOL result = ThreadpoolMgr::CanSetMaxIOCompletionThreads(ioCompletionThreads); - FC_RETURN_BOOL(result); -} -FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorSetMaxThreads,DWORD workerThreads, DWORD completionPortThreads) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPoolForIO()); - - BOOL bRet = FALSE; - HELPER_METHOD_FRAME_BEGIN_RET_0(); - - bRet = ThreadpoolMgr::SetMaxThreads(workerThreads,completionPortThreads); - HELPER_METHOD_FRAME_END(); - FC_RETURN_BOOL(bRet); -} -FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL2(VOID, ThreadPoolNative::CorGetMaxThreads,DWORD* workerThreads, DWORD* completionPortThreads) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPoolForIO()); - - ThreadpoolMgr::GetMaxThreads(workerThreads,completionPortThreads); - return; -} -FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorSetMinThreads,DWORD workerThreads, DWORD completionPortThreads) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPoolForIO()); - - BOOL bRet = FALSE; - HELPER_METHOD_FRAME_BEGIN_RET_0(); - - bRet = ThreadpoolMgr::SetMinThreads(workerThreads,completionPortThreads); - HELPER_METHOD_FRAME_END(); - FC_RETURN_BOOL(bRet); -} -FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL2(VOID, ThreadPoolNative::CorGetMinThreads,DWORD* workerThreads, DWORD* completionPortThreads) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPoolForIO()); - - ThreadpoolMgr::GetMinThreads(workerThreads,completionPortThreads); - return; -} -FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL2(VOID, ThreadPoolNative::CorGetAvailableThreads,DWORD* workerThreads, DWORD* completionPortThreads) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPoolForIO()); - - ThreadpoolMgr::GetAvailableThreads(workerThreads,completionPortThreads); - return; -} -FCIMPLEND - -/*****************************************************************************************************/ -FCIMPL0(INT32, ThreadPoolNative::GetThreadCount) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPoolForIO()); - - return ThreadpoolMgr::GetThreadCount(); -} -FCIMPLEND - -/*****************************************************************************************************/ -extern "C" INT64 QCALLTYPE ThreadPool_GetCompletedWorkItemCount() -{ - QCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPoolForIO()); - - INT64 result = 0; - - BEGIN_QCALL; - - result = (INT64)Thread::GetTotalThreadPoolCompletionCount(); - - END_QCALL; - return result; -} - -/*****************************************************************************************************/ -FCIMPL0(INT64, ThreadPoolNative::GetPendingUnmanagedWorkItemCount) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPool()); - - return PerAppDomainTPCountList::GetUnmanagedTPCount()->GetNumRequests(); -} -FCIMPLEND - -/*****************************************************************************************************/ - -FCIMPL0(VOID, ThreadPoolNative::NotifyRequestProgress) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPool()); - _ASSERTE(ThreadpoolMgr::IsInitialized()); // can't be here without requesting a thread first - - ThreadpoolMgr::NotifyWorkItemCompleted(); - - if (ThreadpoolMgr::ShouldAdjustMaxWorkersActive()) - { - DangerousNonHostedSpinLockTryHolder tal(&ThreadpoolMgr::ThreadAdjustmentLock); - if (tal.Acquired()) - { - HELPER_METHOD_FRAME_BEGIN_0(); - ThreadpoolMgr::AdjustMaxWorkersActive(); - HELPER_METHOD_FRAME_END(); - } - else - { - // the lock is held by someone else, so they will take care of this for us. - } - } -} -FCIMPLEND - -FCIMPL1(VOID, ThreadPoolNative::ReportThreadStatus, CLR_BOOL isWorking) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPool()); - - ThreadpoolMgr::ReportThreadStatus(isWorking); -} -FCIMPLEND - -FCIMPL0(FC_BOOL_RET, ThreadPoolNative::NotifyRequestComplete) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPool()); - _ASSERTE(ThreadpoolMgr::IsInitialized()); // can't be here without requesting a thread first - - ThreadpoolMgr::NotifyWorkItemCompleted(); - - // - // Now we need to possibly do one or both of: reset the thread's state, and/or perform a - // "worker thread adjustment" (i.e., invoke Hill Climbing). We try to avoid these at all costs, - // because they require an expensive helper method frame. So we first try a minimal thread reset, - // then check if it covered everything that was needed, and we ask ThreadpoolMgr whether - // we need a thread adjustment, before setting up the frame. - // - Thread *pThread = GetThread(); - - INT32 priority = pThread->ResetManagedThreadObjectInCoopMode(ThreadNative::PRIORITY_NORMAL); - - bool needReset = - priority != ThreadNative::PRIORITY_NORMAL || - !pThread->IsBackground(); - - bool shouldAdjustWorkers = ThreadpoolMgr::ShouldAdjustMaxWorkersActive(); - - // - // If it's time for a thread adjustment, try to get the lock. This is just a "try," it won't block, - // so it's ok to do this in cooperative mode. If we can't get the lock, then some other thread is - // already doing the thread adjustment, so we needn't bother. - // - DangerousNonHostedSpinLockTryHolder tal(&ThreadpoolMgr::ThreadAdjustmentLock, shouldAdjustWorkers); - if (!tal.Acquired()) - shouldAdjustWorkers = false; - - if (needReset || shouldAdjustWorkers) - { - HELPER_METHOD_FRAME_BEGIN_RET_0(); - - if (shouldAdjustWorkers) - { - ThreadpoolMgr::AdjustMaxWorkersActive(); - tal.Release(); - } - - if (needReset) - pThread->InternalReset(TRUE, TRUE, FALSE); - - HELPER_METHOD_FRAME_END(); - } - - // - // Finally, ask ThreadpoolMgr whether it's ok to keep running work on this thread. Maybe Hill Climbing - // wants this thread back. - // - BOOL result = ThreadpoolMgr::ShouldWorkerKeepRunning() ? TRUE : FALSE; - FC_RETURN_BOOL(result); -} -FCIMPLEND - - -/*****************************************************************************************************/ - -FCIMPL0(FC_BOOL_RET, ThreadPoolNative::GetEnableWorkerTracking) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPool()); - - BOOL result = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking) ? TRUE : FALSE; - FC_RETURN_BOOL(result); -} -FCIMPLEND - -/*****************************************************************************************************/ - -struct RegisterWaitForSingleObjectCallback_Args -{ - DelegateInfo *delegateInfo; - BOOLEAN TimerOrWaitFired; -}; - -static VOID -RegisterWaitForSingleObjectCallback_Worker(LPVOID ptr) -{ - CONTRACTL - { - GC_TRIGGERS; - THROWS; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - OBJECTREF orState = NULL; - - GCPROTECT_BEGIN( orState ); - - RegisterWaitForSingleObjectCallback_Args *args = (RegisterWaitForSingleObjectCallback_Args *) ptr; - orState = ObjectFromHandle(((DelegateInfo*) args->delegateInfo)->m_stateHandle); - -#ifdef _DEBUG - MethodDesc *pMeth = CoreLibBinder::GetMethod(METHOD__TPWAITORTIMER_HELPER__PERFORM_WAITORTIMER_CALLBACK); - LogCall(pMeth,"RWSOCallback"); -#endif - - // Caution: the args are not protected, we have to garantee there's no GC from here till - // the managed call happens. - PREPARE_NONVIRTUAL_CALLSITE(METHOD__TPWAITORTIMER_HELPER__PERFORM_WAITORTIMER_CALLBACK); - DECLARE_ARGHOLDER_ARRAY(arg, 2); - arg[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(orState); - arg[ARGNUM_1] = DWORD_TO_ARGHOLDER(args->TimerOrWaitFired); - - // Call the method... - CALL_MANAGED_METHOD_NORET(arg); - - GCPROTECT_END(); -} - -VOID NTAPI RegisterWaitForSingleObjectCallback(PVOID delegateInfo, BOOLEAN TimerOrWaitFired) -{ - Thread* pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(); - if (pThread == NULL) { - return; - } - } - - CONTRACTL - { - MODE_PREEMPTIVE; // Worker thread will be in preempt mode. We switch to coop below. - THROWS; - GC_TRIGGERS; - - PRECONDITION(CheckPointer(delegateInfo)); - } - CONTRACTL_END; - - GCX_COOP(); - - RegisterWaitForSingleObjectCallback_Args args = { ((DelegateInfo*) delegateInfo), TimerOrWaitFired }; - - ManagedThreadBase::ThreadPool(RegisterWaitForSingleObjectCallback_Worker, &args); -} - -FCIMPL5(LPVOID, ThreadPoolNative::CorRegisterWaitForSingleObject, - Object* waitObjectUNSAFE, - Object* stateUNSAFE, - UINT32 timeout, - CLR_BOOL executeOnlyOnce, - Object* registeredWaitObjectUNSAFE) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPool()); - - HANDLE handle = 0; - struct _gc - { - WAITHANDLEREF waitObject; - OBJECTREF state; - OBJECTREF registeredWaitObject; - } gc; - gc.waitObject = (WAITHANDLEREF) ObjectToOBJECTREF(waitObjectUNSAFE); - gc.state = (OBJECTREF) stateUNSAFE; - gc.registeredWaitObject = (OBJECTREF) registeredWaitObjectUNSAFE; - HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); - - if(gc.waitObject == NULL) - COMPlusThrow(kArgumentNullException); - - _ASSERTE(gc.registeredWaitObject != NULL); - - ULONG flag = executeOnlyOnce ? WAIT_SINGLE_EXECUTION | WAIT_FREE_CONTEXT : WAIT_FREE_CONTEXT; - - HANDLE hWaitHandle = gc.waitObject->GetWaitHandle(); - _ASSERTE(hWaitHandle); - - Thread* pCurThread = GetThread(); - - DelegateInfoHolder delegateInfo = DelegateInfo::MakeDelegateInfo( - &gc.state, - (OBJECTREF *)&gc.waitObject, - &gc.registeredWaitObject); - - if (!(ThreadpoolMgr::RegisterWaitForSingleObject(&handle, - hWaitHandle, - RegisterWaitForSingleObjectCallback, - (PVOID) delegateInfo, - (ULONG) timeout, - flag))) - - { - _ASSERTE(GetLastError() != ERROR_CALL_NOT_IMPLEMENTED); - - COMPlusThrowWin32(); - } - - delegateInfo.SuppressRelease(); - HELPER_METHOD_FRAME_END(); - return (LPVOID) handle; -} -FCIMPLEND - -VOID QueueUserWorkItemManagedCallback(PVOID pArg) -{ - CONTRACTL - { - GC_TRIGGERS; - THROWS; - MODE_COOPERATIVE; - } CONTRACTL_END; - - _ASSERTE(NULL != pArg); - - bool* wasNotRecalled = (bool*)pArg; - - MethodDescCallSite dispatch(METHOD__TP_WAIT_CALLBACK__PERFORM_WAIT_CALLBACK); - *wasNotRecalled = dispatch.Call_RetBool(NULL); -} - -extern "C" BOOL QCALLTYPE ThreadPool_RequestWorkerThread() -{ - QCALL_CONTRACT; - - BOOL res = FALSE; - - BEGIN_QCALL; - - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPool()); - - ThreadpoolMgr::EnsureInitialized(); - ThreadpoolMgr::SetAppDomainRequestsActive(); - - res = ThreadpoolMgr::QueueUserWorkItem(NULL, - NULL, - 0, - FALSE); - if (!res) - { - if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) - COMPlusThrow(kNotSupportedException); - else - COMPlusThrowWin32(); - } - - END_QCALL; - return res; -} - -extern "C" BOOL QCALLTYPE ThreadPool_PerformGateActivities(INT32 cpuUtilization) -{ - QCALL_CONTRACT; - - bool needGateThread = false; - - BEGIN_QCALL; - - _ASSERTE_ALL_BUILDS(ThreadpoolMgr::UsePortableThreadPool() && !ThreadpoolMgr::UsePortableThreadPoolForIO()); - - ThreadpoolMgr::PerformGateActivities(cpuUtilization); - needGateThread = ThreadpoolMgr::NeedGateThreadForIOCompletions(); - - END_QCALL; - - return needGateThread; -} - -/********************************************************************************************************************/ - -FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorUnregisterWait, LPVOID WaitHandle, Object* objectToNotify) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPool()); - - BOOL retVal = false; - SAFEHANDLEREF refSH = (SAFEHANDLEREF) ObjectToOBJECTREF(objectToNotify); - HELPER_METHOD_FRAME_BEGIN_RET_1(refSH); - - HANDLE hWait = (HANDLE) WaitHandle; - HANDLE hObjectToNotify = NULL; - - ThreadpoolMgr::WaitInfo *pWaitInfo = (ThreadpoolMgr::WaitInfo *)hWait; - _ASSERTE(pWaitInfo != NULL); - - ThreadpoolMgr::WaitInfoHolder wiHolder(NULL); - - if (refSH != NULL) - { - // Create a GCHandle in the WaitInfo, so that it can hold on to the safe handle - pWaitInfo->ExternalEventSafeHandle = GetAppDomain()->CreateHandle(NULL); - - // Holder will now release objecthandle in face of exceptions - wiHolder.Assign(pWaitInfo); - - // Store SafeHandle in object handle. Holder will now release both safehandle and objecthandle - // in case of exceptions - StoreObjectInHandle(pWaitInfo->ExternalEventSafeHandle, refSH); - - // Acquire safe handle to examine its handle, then release. - SafeHandleHolder shHolder(&refSH); - - if (refSH->GetHandle() == INVALID_HANDLE_VALUE) - { - hObjectToNotify = INVALID_HANDLE_VALUE; - // We do not need the ObjectHandle, refcount on the safehandle etc - wiHolder.Release(); - _ASSERTE(pWaitInfo->ExternalEventSafeHandle == NULL); - } - } - - _ASSERTE(hObjectToNotify == NULL || hObjectToNotify == INVALID_HANDLE_VALUE); - - // When hObjectToNotify is NULL ExternalEventSafeHandle contains event to notify (if it is non NULL). - // When hObjectToNotify is INVALID_HANDLE_VALUE, UnregisterWaitEx blocks until dispose is complete. - retVal = ThreadpoolMgr::UnregisterWaitEx(hWait, hObjectToNotify); - - if (retVal) - wiHolder.SuppressRelease(); - - HELPER_METHOD_FRAME_END(); - FC_RETURN_BOOL(retVal); -} -FCIMPLEND - -/********************************************************************************************************************/ -FCIMPL1(void, ThreadPoolNative::CorWaitHandleCleanupNative, LPVOID WaitHandle) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPool()); - - HELPER_METHOD_FRAME_BEGIN_0(); - - HANDLE hWait = (HANDLE)WaitHandle; - ThreadpoolMgr::WaitHandleCleanup(hWait); - - HELPER_METHOD_FRAME_END(); -} -FCIMPLEND - -/********************************************************************************************************************/ - -struct BindIoCompletion_Args -{ - DWORD ErrorCode; - DWORD numBytesTransferred; - LPOVERLAPPED lpOverlapped; -}; - -VOID BindIoCompletionCallBack_Worker(LPVOID args) -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_ANY; - - DWORD ErrorCode = ((BindIoCompletion_Args *)args)->ErrorCode; - DWORD numBytesTransferred = ((BindIoCompletion_Args *)args)->numBytesTransferred; - LPOVERLAPPED lpOverlapped = ((BindIoCompletion_Args *)args)->lpOverlapped; - - OVERLAPPEDDATAREF overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(lpOverlapped)); - - GCPROTECT_BEGIN(overlapped); - // we set processed to TRUE, now it's our responsibility to guarantee proper cleanup - -#ifdef _DEBUG - MethodDesc *pMeth = CoreLibBinder::GetMethod(METHOD__IOCB_HELPER__PERFORM_IOCOMPLETION_CALLBACK); - LogCall(pMeth,"IOCallback"); -#endif - - if (overlapped->m_callback != NULL) - { - // Caution: the args are not protected, we have to garantee there's no GC from here till - PREPARE_NONVIRTUAL_CALLSITE(METHOD__IOCB_HELPER__PERFORM_IOCOMPLETION_CALLBACK); - DECLARE_ARGHOLDER_ARRAY(arg, 3); - arg[ARGNUM_0] = DWORD_TO_ARGHOLDER(ErrorCode); - arg[ARGNUM_1] = DWORD_TO_ARGHOLDER(numBytesTransferred); - arg[ARGNUM_2] = PTR_TO_ARGHOLDER(lpOverlapped); - - // Call the method... - CALL_MANAGED_METHOD_NORET(arg); - } - else - { - // no user delegate to callback - _ASSERTE((overlapped->m_callback == NULL) || !"This is benign, but should be optimized"); - } - GCPROTECT_END(); -} - -void __stdcall BindIoCompletionCallbackStubEx(DWORD ErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped, - BOOL setStack) -{ - Thread* pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - // TODO: how do we notify user of OOM here? - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(); - if (pThread == NULL) { - return; - } - } - - CONTRACTL - { - THROWS; - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - LOG((LF_INTEROP, LL_INFO10000, "In IO_CallBackStub thread 0x%x retCode 0x%x, overlap 0x%x\n", pThread, ErrorCode, lpOverlapped)); - - GCX_COOP(); - - BindIoCompletion_Args args = {ErrorCode, numBytesTransferred, lpOverlapped}; - ManagedThreadBase::ThreadPool(BindIoCompletionCallBack_Worker, &args); - - LOG((LF_INTEROP, LL_INFO10000, "Leaving IO_CallBackStub thread 0x%x retCode 0x%x, overlap 0x%x\n", pThread, ErrorCode, lpOverlapped)); -} - -void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped) -{ - WRAPPER_NO_CONTRACT; - BindIoCompletionCallbackStubEx(ErrorCode, numBytesTransferred, lpOverlapped, TRUE); -} - -FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorBindIoCompletionCallback, HANDLE fileHandle) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPoolForIO()); - - BOOL retVal = FALSE; - - HELPER_METHOD_FRAME_BEGIN_RET_0(); - - HANDLE hFile = (HANDLE) fileHandle; - DWORD errCode = 0; - - retVal = ThreadpoolMgr::BindIoCompletionCallback(hFile, - BindIoCompletionCallbackStub, - 0, // reserved, must be 0 - OUT errCode); - if (!retVal) - { - if (errCode == ERROR_CALL_NOT_IMPLEMENTED) - COMPlusThrow(kPlatformNotSupportedException); - else - { - SetLastError(errCode); - COMPlusThrowWin32(); - } - } - - HELPER_METHOD_FRAME_END(); - FC_RETURN_BOOL(retVal); -} -FCIMPLEND - -FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorPostQueuedCompletionStatus, LPOVERLAPPED lpOverlapped) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPoolForIO()); - - OVERLAPPEDDATAREF overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(lpOverlapped)); - - BOOL res = FALSE; - - HELPER_METHOD_FRAME_BEGIN_RET_1(overlapped); - - // OS doesn't signal handle, so do it here - lpOverlapped->Internal = 0; - - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIOEnqueue)) - FireEtwThreadPoolIOEnqueue(lpOverlapped, OBJECTREFToObject(overlapped), false, GetClrInstanceId()); - - res = ThreadpoolMgr::PostQueuedCompletionStatus(lpOverlapped, - BindIoCompletionCallbackStub); - - if (!res) - { - if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) - COMPlusThrow(kPlatformNotSupportedException); - else - COMPlusThrowWin32(); - } - - HELPER_METHOD_FRAME_END(); - FC_RETURN_BOOL(res); -} -FCIMPLEND diff --git a/src/coreclr/vm/comthreadpool.h b/src/coreclr/vm/comthreadpool.h index 46d9fda34d857b..e3ad28c01c0c17 100644 --- a/src/coreclr/vm/comthreadpool.h +++ b/src/coreclr/vm/comthreadpool.h @@ -1,73 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Header: COMThreadPool.h -** -** Purpose: Native methods on System.ThreadPool -** and its inner classes -** -** -===========================================================*/ - #ifndef _COMTHREADPOOL_H #define _COMTHREADPOOL_H -#include "delegateinfo.h" -#include "nativeoverlapped.h" - class ThreadPoolNative { - public: static FCDECL4(INT32, GetNextConfigUInt32Value, INT32 configVariableIndex, 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); - static FCDECL2(VOID, CorGetMinThreads, DWORD* workerThreads, DWORD* completionPortThreads); - static FCDECL2(VOID, CorGetAvailableThreads, DWORD* workerThreads, DWORD* completionPortThreads); - static FCDECL0(INT32, GetThreadCount); - static FCDECL0(INT64, GetPendingUnmanagedWorkItemCount); - - static FCDECL0(VOID, NotifyRequestProgress); - static FCDECL0(FC_BOOL_RET, NotifyRequestComplete); - - static FCDECL0(FC_BOOL_RET, GetEnableWorkerTracking); - - static FCDECL1(void, ReportThreadStatus, CLR_BOOL isWorking); - - static FCDECL5(LPVOID, CorRegisterWaitForSingleObject, - Object* waitObjectUNSAFE, - Object* stateUNSAFE, - UINT32 timeout, - CLR_BOOL executeOnlyOnce, - Object* registeredWaitObjectUNSAFE); - - static FCDECL1(FC_BOOL_RET, CorPostQueuedCompletionStatus, LPOVERLAPPED lpOverlapped); - static FCDECL2(FC_BOOL_RET, CorUnregisterWait, LPVOID WaitHandle, Object * objectToNotify); - static FCDECL1(void, CorWaitHandleCleanupNative, LPVOID WaitHandle); - static FCDECL1(FC_BOOL_RET, CorBindIoCompletionCallback, HANDLE fileHandle); }; -extern "C" INT64 QCALLTYPE ThreadPool_GetCompletedWorkItemCount(); -extern "C" BOOL QCALLTYPE ThreadPool_RequestWorkerThread(); -extern "C" BOOL QCALLTYPE ThreadPool_PerformGateActivities(INT32 cpuUtilization); - -VOID QueueUserWorkItemManagedCallback(PVOID pArg); -void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped); -void SetAsyncResultProperties( - OVERLAPPEDDATAREF overlapped, - DWORD dwErrorCode, - DWORD dwNumBytes); - #endif diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 3537c57d8cf05b..62cb4d8a634c12 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -900,19 +900,6 @@ DEFINE_METHOD(AUTORELEASEPOOL, CREATEAUTORELEASEPOOL, CreateAutoreleasePoo DEFINE_METHOD(AUTORELEASEPOOL, DRAINAUTORELEASEPOOL, DrainAutoreleasePool, SM_RetVoid) #endif // FEATURE_OBJCMARSHAL -DEFINE_CLASS(IOCB_HELPER, Threading, _IOCompletionCallback) -DEFINE_METHOD(IOCB_HELPER, PERFORM_IOCOMPLETION_CALLBACK, PerformIOCompletionCallback, SM_UInt_UInt_PtrNativeOverlapped_RetVoid) - -DEFINE_CLASS(TPWAITORTIMER_HELPER, Threading, _ThreadPoolWaitOrTimerCallback) -DEFINE_METHOD(TPWAITORTIMER_HELPER, PERFORM_WAITORTIMER_CALLBACK, PerformWaitOrTimerCallback, SM__ThreadPoolWaitOrTimerCallback_Bool_RetVoid) - -DEFINE_CLASS(TP_WAIT_CALLBACK, Threading, _ThreadPoolWaitCallback) -DEFINE_METHOD(TP_WAIT_CALLBACK, PERFORM_WAIT_CALLBACK, PerformWaitCallback, SM_RetBool) - -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(TIMESPAN, System, TimeSpan) diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index e1babc7f6228e4..db99db62b10c56 100644 --- a/src/coreclr/vm/corhost.cpp +++ b/src/coreclr/vm/corhost.cpp @@ -28,7 +28,6 @@ #include "dllimportcallback.h" #include "eventtrace.h" -#include "win32threadpool.h" #include "eventtrace.h" #include "finalizerthread.h" #include "threadsuspend.h" diff --git a/src/coreclr/vm/delegateinfo.h b/src/coreclr/vm/delegateinfo.h deleted file mode 100644 index 2f9f2d71860248..00000000000000 --- a/src/coreclr/vm/delegateinfo.h +++ /dev/null @@ -1,62 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Header: DelegateInfo.h -** -** -** Purpose: Native methods on System.ThreadPool -** and its inner classes -** - -** -===========================================================*/ -#ifndef DELEGATE_INFO -#define DELEGATE_INFO - -struct DelegateInfo; -typedef DelegateInfo* DelegateInfoPtr; - -struct DelegateInfo -{ - OBJECTHANDLE m_stateHandle; - OBJECTHANDLE m_eventHandle; - OBJECTHANDLE m_registeredWaitHandle; - -#ifndef DACCESS_COMPILE - void Release() - { - CONTRACTL { - // m_compressedStack->Release() can actually throw today because it has got a call - // to new down the stack. However that is recent and the semantic of that api is such - // it should not throw. I am expecting clenup of that function to take care of that - // so I am adding this comment to make sure the issue is document. - // Remove this comment once that work is done - NOTHROW; - GC_TRIGGERS; - MODE_COOPERATIVE; - FORBID_FAULT; - } - CONTRACTL_END; - - - if (m_stateHandle) - DestroyHandle(m_stateHandle); - if (m_eventHandle) - DestroyHandle(m_eventHandle); - if (m_registeredWaitHandle) - DestroyHandle(m_registeredWaitHandle); - } -#endif - - static DelegateInfo *MakeDelegateInfo(OBJECTREF *state, - OBJECTREF *waitEvent, - OBJECTREF *registeredWaitObject); -}; - - - - - -#endif // DELEGATE_INFO diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 6c06aacbf4fc76..bf3c849f318642 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -394,27 +394,6 @@ FCFuncEnd() 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) - FCFuncElement("GetPendingUnmanagedWorkItemCount", ThreadPoolNative::GetPendingUnmanagedWorkItemCount) - FCFuncElement("RegisterWaitForSingleObjectNative", ThreadPoolNative::CorRegisterWaitForSingleObject) - FCFuncElement("BindIOCompletionCallbackNative", ThreadPoolNative::CorBindIoCompletionCallback) - FCFuncElement("SetMaxThreadsNative", ThreadPoolNative::CorSetMaxThreads) - FCFuncElement("GetMaxThreadsNative", ThreadPoolNative::CorGetMaxThreads) - FCFuncElement("NotifyWorkItemCompleteNative", ThreadPoolNative::NotifyRequestComplete) - FCFuncElement("NotifyWorkItemProgressNative", ThreadPoolNative::NotifyRequestProgress) - FCFuncElement("GetEnableWorkerTrackingNative", ThreadPoolNative::GetEnableWorkerTracking) - FCFuncElement("ReportThreadStatusNative", ThreadPoolNative::ReportThreadStatus) -FCFuncEnd() - -FCFuncStart(gRegisteredWaitHandleFuncs) - FCFuncElement("UnregisterWaitNative", ThreadPoolNative::CorUnregisterWait) - FCFuncElement("WaitHandleCleanupNative", ThreadPoolNative::CorWaitHandleCleanupNative) FCFuncEnd() FCFuncStart(gWaitHandleFuncs) @@ -573,7 +552,6 @@ FCFuncEnd() FCFuncStart(gOverlappedFuncs) FCFuncElement("AllocateNativeOverlapped", AllocateNativeOverlapped) FCFuncElement("FreeNativeOverlapped", FreeNativeOverlapped) - FCFuncElement("CheckVMForIOPacket", CheckVMForIOPacket) FCFuncElement("GetOverlappedFromNative", GetOverlappedFromNative) FCFuncEnd() @@ -794,8 +772,6 @@ FCClassElement("ObjectMarshaler", "System.StubHelpers", gObjectMarshalerFuncs) #endif FCClassElement("OverlappedData", "System.Threading", gOverlappedFuncs) -FCClassElement("RegisteredWaitHandle", "System.Threading", gRegisteredWaitHandleFuncs) - FCClassElement("RuntimeAssembly", "System.Reflection", gRuntimeAssemblyFuncs) FCClassElement("RuntimeFieldHandle", "System", gCOMFieldHandleNewFuncs) FCClassElement("RuntimeHelpers", "System.Runtime.CompilerServices", gRuntimeHelpers) diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h index 620fe62f30268e..a494b4d156607e 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h @@ -11,7 +11,6 @@ #include #include "fstream.h" #include "typestring.h" -#include "win32threadpool.h" #include "clrversion.h" #undef EP_INFINITE_WAIT @@ -1661,15 +1660,6 @@ ep_rt_config_value_get_output_streaming (void) return CLRConfig::GetConfigValue (CLRConfig::INTERNAL_EventPipeOutputStreaming) != 0; } -static -inline -bool -ep_rt_config_value_get_use_portable_thread_pool (void) -{ - STATIC_CONTRACT_NOTHROW; - return ThreadpoolMgr::UsePortableThreadPool (); -} - /* * EventPipeSampleProfiler. */ diff --git a/src/coreclr/vm/hillclimbing.cpp b/src/coreclr/vm/hillclimbing.cpp deleted file mode 100644 index 315ceb2bba8ea0..00000000000000 --- a/src/coreclr/vm/hillclimbing.cpp +++ /dev/null @@ -1,443 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -//========================================================================= - -// -// HillClimbing.cpp -// -// Defines classes for the ThreadPool's HillClimbing concurrency-optimization -// algorithm. -// - -//========================================================================= - -// -// TODO: write an essay about how/why this works. Maybe put it in BotR? -// - -#include "common.h" -#include "hillclimbing.h" -#include "win32threadpool.h" - -// -// Default compilation mode is /fp:precise, which disables fp intrinsics. This causes us to pull in FP stuff (sin,cos,etc.) from -// The CRT, and increases our download size by ~5k. We don't need the extra precision this gets us, so let's switch to -// the intrinsic versions. -// -#ifdef _MSC_VER -#pragma float_control(precise, off) -#endif - - - -const double pi = 3.141592653589793; - -void HillClimbing::Initialize() -{ - CONTRACTL - { - THROWS; - GC_NOTRIGGER; - MODE_ANY; - } - 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; - m_samplesToMeasure = m_wavePeriod * (int)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_WaveHistorySize); - m_targetThroughputRatio = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_Bias) / 100.0; - m_targetSignalToNoiseRatio = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_TargetSignalToNoiseRatio) / 100.0; - m_maxChangePerSecond = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSecond); - m_maxChangePerSample = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSample); - m_sampleIntervalLow = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow); - m_sampleIntervalHigh = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_SampleIntervalHigh); - m_throughputErrorSmoothingFactor = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_ErrorSmoothingFactor) / 100.0; - m_gainExponent = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_GainExponent) / 100.0; - m_maxSampleError = (double)CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_MaxSampleErrorPercent) / 100.0; - m_currentControlSetting = 0; - m_totalSamples = 0; - m_lastThreadCount = 0; - m_averageThroughputNoise = 0; - m_elapsedSinceLastChange = 0; - m_completionsSinceLastChange = 0; - m_accumulatedCompletionCount = 0; - m_accumulatedSampleDuration = 0; - - m_samples = new double[m_samplesToMeasure]; - m_threadCounts = new double[m_samplesToMeasure]; - - // seed our random number generator with the CLR instance ID and the process ID, to avoid correlations with other CLR ThreadPool instances. -#ifndef DACCESS_COMPILE - m_randomIntervalGenerator.Init(((int)GetClrInstanceId() << 16) ^ (int)GetCurrentProcessId()); -#endif - m_currentSampleInterval = m_randomIntervalGenerator.Next(m_sampleIntervalLow, m_sampleIntervalHigh+1); -} - -int HillClimbing::Update(int currentThreadCount, double sampleDuration, int numCompletions, int* pNewSampleInterval) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - -#ifdef DACCESS_COMPILE - return 1; -#else - - // - // If someone changed the thread count without telling us, update our records accordingly. - // - if (currentThreadCount != m_lastThreadCount) - ForceChange(currentThreadCount, Initializing); - - // - // Update the cumulative stats for this thread count - // - m_elapsedSinceLastChange += sampleDuration; - m_completionsSinceLastChange += numCompletions; - - // - // Add in any data we've already collected about this sample - // - sampleDuration += m_accumulatedSampleDuration; - numCompletions += m_accumulatedCompletionCount; - - // - // We need to make sure we're collecting reasonably accurate data. Since we're just counting the end - // of each work item, we are goinng to be missing some data about what really happened during the - // sample interval. The count produced by each thread includes an initial work item that may have - // started well before the start of the interval, and each thread may have been running some new - // work item for some time before the end of the interval, which did not yet get counted. So - // our count is going to be off by +/- threadCount workitems. - // - // The exception is that the thread that reported to us last time definitely wasn't running any work - // at that time, and the thread that's reporting now definitely isn't running a work item now. So - // we really only need to consider threadCount-1 threads. - // - // Thus the percent error in our count is +/- (threadCount-1)/numCompletions. - // - // We cannot rely on the frequency-domain analysis we'll be doing later to filter out this error, because - // of the way it accumulates over time. If this sample is off by, say, 33% in the negative direction, - // then the next one likely will be too. The one after that will include the sum of the completions - // we missed in the previous samples, and so will be 33% positive. So every three samples we'll have - // two "low" samples and one "high" sample. This will appear as periodic variation right in the frequency - // range we're targeting, which will not be filtered by the frequency-domain translation. - // - if (m_totalSamples > 0 && ((currentThreadCount-1.0) / numCompletions) >= m_maxSampleError) - { - // not accurate enough yet. Let's accumulate the data so far, and tell the ThreadPool - // to collect a little more. - m_accumulatedSampleDuration = sampleDuration; - m_accumulatedCompletionCount = numCompletions; - *pNewSampleInterval = 10; - return currentThreadCount; - } - - // - // We've got enouugh data for our sample; reset our accumulators for next time. - // - m_accumulatedSampleDuration = 0; - m_accumulatedCompletionCount = 0; - - // - // Add the current thread count and throughput sample to our history - // - double throughput = (double)numCompletions / sampleDuration; - FireEtwThreadPoolWorkerThreadAdjustmentSample(throughput, GetClrInstanceId()); - - int sampleIndex = m_totalSamples % m_samplesToMeasure; - m_samples[sampleIndex] = throughput; - m_threadCounts[sampleIndex] = currentThreadCount; - m_totalSamples++; - - // - // Set up defaults for our metrics - // - Complex threadWaveComponent = 0; - Complex throughputWaveComponent = 0; - double throughputErrorEstimate = 0; - Complex ratio = 0; - double confidence = 0; - - HillClimbingStateTransition transition = Warmup; - - // - // How many samples will we use? It must be at least the three wave periods we're looking for, and it must also be a whole - // multiple of the primary wave's period; otherwise the frequency we're looking for will fall between two frequency bands - // in the Fourier analysis, and we won't be able to measure it accurately. - // - int sampleCount = ((int)min(m_totalSamples-1, m_samplesToMeasure) / m_wavePeriod) * m_wavePeriod; - - if (sampleCount > m_wavePeriod) - { - // - // Average the throughput and thread count samples, so we can scale the wave magnitudes later. - // - double sampleSum = 0; - double threadSum = 0; - for (int i = 0; i < sampleCount; i++) - { - sampleSum += m_samples[(m_totalSamples - sampleCount + i) % m_samplesToMeasure]; - threadSum += m_threadCounts[(m_totalSamples - sampleCount + i) % m_samplesToMeasure]; - } - double averageThroughput = sampleSum / sampleCount; - double averageThreadCount = threadSum / sampleCount; - - if (averageThroughput > 0 && averageThreadCount > 0) - { - // - // Calculate the periods of the adjacent frequency bands we'll be using to measure noise levels. - // We want the two adjacent Fourier frequency bands. - // - double adjacentPeriod1 = sampleCount / (((double)sampleCount / (double)m_wavePeriod) + 1); - double adjacentPeriod2 = sampleCount / (((double)sampleCount / (double)m_wavePeriod) - 1); - - // - // Get the three different frequency components of the throughput (scaled by average - // throughput). Our "error" estimate (the amount of noise that might be present in the - // frequency band we're really interested in) is the average of the adjacent bands. - // - throughputWaveComponent = GetWaveComponent(m_samples, sampleCount, m_wavePeriod) / averageThroughput; - throughputErrorEstimate = abs(GetWaveComponent(m_samples, sampleCount, adjacentPeriod1) / averageThroughput); - if (adjacentPeriod2 <= sampleCount) - throughputErrorEstimate = max(throughputErrorEstimate, abs(GetWaveComponent(m_samples, sampleCount, adjacentPeriod2) / averageThroughput)); - - // - // Do the same for the thread counts, so we have something to compare to. We don't measure thread count - // noise, because there is none; these are exact measurements. - // - threadWaveComponent = GetWaveComponent(m_threadCounts, sampleCount, m_wavePeriod) / averageThreadCount; - - // - // Update our moving average of the throughput noise. We'll use this later as feedback to - // determine the new size of the thread wave. - // - if (m_averageThroughputNoise == 0) - m_averageThroughputNoise = throughputErrorEstimate; - else - m_averageThroughputNoise = (m_throughputErrorSmoothingFactor * throughputErrorEstimate) + ((1.0-m_throughputErrorSmoothingFactor) * m_averageThroughputNoise); - - if (abs(threadWaveComponent) > 0) - { - // - // Adjust the throughput wave so it's centered around the target wave, and then calculate the adjusted throughput/thread ratio. - // - ratio = (throughputWaveComponent - (m_targetThroughputRatio * threadWaveComponent)) / threadWaveComponent; - transition = ClimbingMove; - } - else - { - ratio = 0; - transition = Stabilizing; - } - - // - // Calculate how confident we are in the ratio. More noise == less confident. This has - // the effect of slowing down movements that might be affected by random noise. - // - double noiseForConfidence = max(m_averageThroughputNoise, throughputErrorEstimate); - if (noiseForConfidence > 0) - confidence = (abs(threadWaveComponent) / noiseForConfidence) / m_targetSignalToNoiseRatio; - else - confidence = 1.0; //there is no noise! - - } - } - - // - // We use just the real part of the complex ratio we just calculated. If the throughput signal - // is exactly in phase with the thread signal, this will be the same as taking the magnitude of - // the complex move and moving that far up. If they're 180 degrees out of phase, we'll move - // backward (because this indicates that our changes are having the opposite of the intended effect). - // If they're 90 degrees out of phase, we won't move at all, because we can't tell whether we're - // having a negative or positive effect on throughput. - // - double move = min(1.0, max(-1.0, ratio.r)); - - // - // Apply our confidence multiplier. - // - move *= min(1.0, max(0.0, confidence)); - - // - // Now apply non-linear gain, such that values around zero are attenuated, while higher values - // are enhanced. This allows us to move quickly if we're far away from the target, but more slowly - // if we're getting close, giving us rapid ramp-up without wild oscillations around the target. - // - double gain = m_maxChangePerSecond * sampleDuration; - move = pow(fabs(move), m_gainExponent) * (move >= 0.0 ? 1 : -1) * gain; - move = min(move, m_maxChangePerSample); - - // - // If the result was positive, and CPU is > 95%, refuse the move. - // - if (move > 0.0 && ThreadpoolMgr::cpuUtilization > CpuUtilizationHigh) - move = 0.0; - - // - // Apply the move to our control setting - // - m_currentControlSetting += move; - - // - // Calculate the new thread wave magnitude, which is based on the moving average we've been keeping of - // the throughput error. This average starts at zero, so we'll start with a nice safe little wave at first. - // - int newThreadWaveMagnitude = (int)(0.5 + (m_currentControlSetting * m_averageThroughputNoise * m_targetSignalToNoiseRatio * m_threadMagnitudeMultiplier * 2.0)); - newThreadWaveMagnitude = min(newThreadWaveMagnitude, m_maxThreadWaveMagnitude); - newThreadWaveMagnitude = max(newThreadWaveMagnitude, 1); - - // - // Make sure our control setting is within the ThreadPool's limits - // - m_currentControlSetting = min(ThreadpoolMgr::MaxLimitTotalWorkerThreads-newThreadWaveMagnitude, m_currentControlSetting); - m_currentControlSetting = max(ThreadpoolMgr::MinLimitTotalWorkerThreads, m_currentControlSetting); - - // - // Calculate the new thread count (control setting + square wave) - // - int newThreadCount = (int)(m_currentControlSetting + newThreadWaveMagnitude * ((m_totalSamples / (m_wavePeriod/2)) % 2)); - - // - // Make sure the new thread count doesn't exceed the ThreadPool's limits - // - newThreadCount = min(ThreadpoolMgr::MaxLimitTotalWorkerThreads, newThreadCount); - newThreadCount = max(ThreadpoolMgr::MinLimitTotalWorkerThreads, newThreadCount); - - // - // Record these numbers for posterity - // - FireEtwThreadPoolWorkerThreadAdjustmentStats( - sampleDuration, - throughput, - threadWaveComponent.r, - throughputWaveComponent.r, - throughputErrorEstimate, - m_averageThroughputNoise, - ratio.r, - confidence, - m_currentControlSetting, - (unsigned short)newThreadWaveMagnitude, - GetClrInstanceId()); - - // - // If all of this caused an actual change in thread count, log that as well. - // - if (newThreadCount != currentThreadCount) - ChangeThreadCount(newThreadCount, transition); - - // - // Return the new thread count and sample interval. This is randomized to prevent correlations with other periodic - // changes in throughput. Among other things, this prevents us from getting confused by Hill Climbing instances - // running in other processes. - // - // If we're at minThreads, and we seem to be hurting performance by going higher, we can't go any lower to fix this. So - // we'll simply stay at minThreads much longer, and only occasionally try a higher value. - // - if (ratio.r < 0.0 && newThreadCount == ThreadpoolMgr::MinLimitTotalWorkerThreads) - *pNewSampleInterval = (int)(0.5 + m_currentSampleInterval * (10.0 * min(-ratio.r, 1.0))); - else - *pNewSampleInterval = m_currentSampleInterval; - - return newThreadCount; - -#endif //DACCESS_COMPILE -} - - -void HillClimbing::ForceChange(int newThreadCount, HillClimbingStateTransition transition) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - if (newThreadCount != m_lastThreadCount) - { - m_currentControlSetting += (newThreadCount - m_lastThreadCount); - ChangeThreadCount(newThreadCount, transition); - } -} - - -void HillClimbing::ChangeThreadCount(int newThreadCount, HillClimbingStateTransition transition) -{ - LIMITED_METHOD_CONTRACT; - - m_lastThreadCount = newThreadCount; - m_currentSampleInterval = m_randomIntervalGenerator.Next(m_sampleIntervalLow, m_sampleIntervalHigh+1); - double throughput = (m_elapsedSinceLastChange > 0) ? (m_completionsSinceLastChange / m_elapsedSinceLastChange) : 0; - LogTransition(newThreadCount, throughput, transition); - m_elapsedSinceLastChange = 0; - m_completionsSinceLastChange = 0; -} - - -GARY_IMPL(HillClimbingLogEntry, HillClimbingLog, HillClimbingLogCapacity); -GVAL_IMPL(int, HillClimbingLogFirstIndex); -GVAL_IMPL(int, HillClimbingLogSize); - - -void HillClimbing::LogTransition(int threadCount, double throughput, HillClimbingStateTransition transition) -{ - LIMITED_METHOD_CONTRACT; - -#ifndef DACCESS_COMPILE - int index = (HillClimbingLogFirstIndex + HillClimbingLogSize) % HillClimbingLogCapacity; - - if (HillClimbingLogSize == HillClimbingLogCapacity) - { - HillClimbingLogFirstIndex = (HillClimbingLogFirstIndex + 1) % HillClimbingLogCapacity; - HillClimbingLogSize--; //hide this slot while we update it - } - - HillClimbingLogEntry* entry = &HillClimbingLog[index]; - - entry->TickCount = GetTickCount(); - entry->Transition = transition; - entry->NewControlSetting = threadCount; - - entry->LastHistoryCount = (int)(min(m_totalSamples, m_samplesToMeasure) / m_wavePeriod) * m_wavePeriod; - entry->LastHistoryMean = (float) throughput; - - HillClimbingLogSize++; - - FireEtwThreadPoolWorkerThreadAdjustmentAdjustment( - throughput, - threadCount, - transition, - GetClrInstanceId()); - -#endif //DACCESS_COMPILE -} - -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 - - // - // Calculate the sinusoid with the given period. - // We're using the Goertzel algorithm for this. See http://en.wikipedia.org/wiki/Goertzel_algorithm. - // - double w = 2.0 * pi / period; - double cosine = cos(w); - double sine = sin(w); - double coeff = 2.0 * cosine; - double q0 = 0, q1 = 0, q2 = 0; - - for (int i = 0; i < sampleCount; i++) - { - double sample = samples[(m_totalSamples - sampleCount + i) % m_samplesToMeasure]; - - q0 = coeff * q1 - q2 + sample; - q2 = q1; - q1 = q0; - } - - return Complex(q1 - q2 * cosine, q2 * sine) / (double)sampleCount; -} - diff --git a/src/coreclr/vm/hillclimbing.h b/src/coreclr/vm/hillclimbing.h deleted file mode 100644 index 49bd023af113df..00000000000000 --- a/src/coreclr/vm/hillclimbing.h +++ /dev/null @@ -1,96 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -//========================================================================= - -// -// HillClimbing.h -// -// Defines classes for the ThreadPool's HillClimbing concurrency-optimization -// algorithm. -// - -//========================================================================= - -#ifndef _HILLCLIMBING_H -#define _HILLCLIMBING_H - -#include "complex.h" -#include "random.h" - -enum HillClimbingStateTransition -{ - Warmup, - Initializing, - RandomMove, - ClimbingMove, - ChangePoint, - Stabilizing, - Starvation, //used by ThreadpoolMgr - ThreadTimedOut, //used by ThreadpoolMgr - Undefined, -}; - - -class HillClimbing -{ -private: - int m_wavePeriod; - int m_samplesToMeasure; - double m_targetThroughputRatio; - double m_targetSignalToNoiseRatio; - double m_maxChangePerSecond; - double m_maxChangePerSample; - int m_maxThreadWaveMagnitude; - DWORD m_sampleIntervalLow; - double m_threadMagnitudeMultiplier; - DWORD m_sampleIntervalHigh; - double m_throughputErrorSmoothingFactor; - double m_gainExponent; - double m_maxSampleError; - - double m_currentControlSetting; - LONGLONG m_totalSamples; - int m_lastThreadCount; - double m_elapsedSinceLastChange; //elapsed seconds since last thread count change - double m_completionsSinceLastChange; //number of completions since last thread count change - - double m_averageThroughputNoise; - - double* m_samples; //Circular buffer of the last m_samplesToMeasure samples - double* m_threadCounts; //Thread counts effective at each of m_samples - - unsigned int m_currentSampleInterval; - CLRRandom m_randomIntervalGenerator; - - int m_accumulatedCompletionCount; - double m_accumulatedSampleDuration; - - void ChangeThreadCount(int newThreadCount, HillClimbingStateTransition transition); - void LogTransition(int threadCount, double throughput, HillClimbingStateTransition transition); - - Complex GetWaveComponent(double* samples, int sampleCount, double period); - -public: - void Initialize(); - int Update(int currentThreadCount, double sampleDuration, int numCompletions, int* pNewSampleInterval); - void ForceChange(int newThreadCount, HillClimbingStateTransition transition); -}; - -#define HillClimbingLogCapacity 200 - -struct HillClimbingLogEntry -{ - DWORD TickCount; - HillClimbingStateTransition Transition; - int NewControlSetting; - int LastHistoryCount; - float LastHistoryMean; -}; - -GARY_DECL(HillClimbingLogEntry, HillClimbingLog, HillClimbingLogCapacity); -GVAL_DECL(int, HillClimbingLogFirstIndex); -GVAL_DECL(int, HillClimbingLogSize); -typedef DPTR(HillClimbingLogEntry) PTR_HillClimbingLogEntry; - -#endif diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index f567d34be2f7da..dc710afa99b349 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -374,8 +374,6 @@ DEFINE_METASIG(SM(ArrByte_RetObj, a(b), j)) DEFINE_METASIG(SM(ArrByte_Bool_RetObj, a(b) F, j)) DEFINE_METASIG(SM(ArrByte_ArrByte_RefObj_RetObj, a(b) a(b) r(j), j)) -DEFINE_METASIG_T(SM(UInt_UInt_PtrNativeOverlapped_RetVoid, K K P(g(NATIVEOVERLAPPED)), v)) - DEFINE_METASIG(IM(Long_RetVoid, l, v)) DEFINE_METASIG(IM(IntPtr_Int_RetVoid, I i, v)) DEFINE_METASIG(IM(IntInt_RetArrByte, i i, a(b))) @@ -557,9 +555,6 @@ DEFINE_METASIG_T(SM(IntPtr_AssemblyName_RetAssemblyBase, I C(ASSEMBLY_NAME), C(A DEFINE_METASIG_T(SM(Str_AssemblyBase_IntPtr_RetIntPtr, s C(ASSEMBLYBASE) I, I)) DEFINE_METASIG_T(SM(Str_AssemblyBase_Bool_UInt_RetIntPtr, s C(ASSEMBLYBASE) F K, I)) -// ThreadPool -DEFINE_METASIG_T(SM(_ThreadPoolWaitOrTimerCallback_Bool_RetVoid, C(TPWAITORTIMER_HELPER) F, v)) - // For FailFast DEFINE_METASIG(SM(Str_RetVoid, s, v)) DEFINE_METASIG_T(SM(Str_Exception_RetVoid, s C(EXCEPTION), v)) diff --git a/src/coreclr/vm/nativeoverlapped.cpp b/src/coreclr/vm/nativeoverlapped.cpp index c3b55ae130d429..4a9edb5ff780be 100644 --- a/src/coreclr/vm/nativeoverlapped.cpp +++ b/src/coreclr/vm/nativeoverlapped.cpp @@ -15,79 +15,10 @@ #include "fcall.h" #include "nativeoverlapped.h" #include "corhost.h" -#include "win32threadpool.h" #include "comsynchronizable.h" #include "comthreadpool.h" #include "marshalnative.h" -// -//The function is called from managed code to quicly check if a packet is available. -//This is a perf-critical function. Even helper method frames are not created. We fall -//back to the VM to do heavy weight operations like creating a new CP thread. -// -FCIMPL3(void, CheckVMForIOPacket, LPOVERLAPPED* lpOverlapped, DWORD* errorCode, DWORD* numBytes) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(!ThreadpoolMgr::UsePortableThreadPoolForIO()); - -#ifndef TARGET_UNIX - Thread *pThread = GetThread(); - size_t key=0; - - //Poll and wait if GC is in progress, to avoid blocking GC for too long. - FC_GC_POLL(); - - *lpOverlapped = ThreadpoolMgr::CompletionPortDispatchWorkWithinAppDomain(pThread, errorCode, numBytes, &key); - if(*lpOverlapped == NULL) - { - return; - } - - OVERLAPPEDDATAREF overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(*lpOverlapped)); - - if (overlapped->m_callback == NULL) - { - //We're not initialized yet, go back to the Vm, and process the packet there. - ThreadpoolMgr::StoreOverlappedInfoInThread(pThread, *errorCode, *numBytes, key, *lpOverlapped); - - *lpOverlapped = NULL; - return; - } - else - { - if(!pThread->IsRealThreadPoolResetNeeded()) - { - pThread->ResetManagedThreadObjectInCoopMode(ThreadNative::PRIORITY_NORMAL); - pThread->InternalReset(TRUE, FALSE, FALSE); - if(ThreadpoolMgr::ShouldGrowCompletionPortThreadpool(ThreadpoolMgr::CPThreadCounter.DangerousGetDirtyCounts())) - { - //We may have to create a CP thread, go back to the Vm, and process the packet there. - ThreadpoolMgr::StoreOverlappedInfoInThread(pThread, *errorCode, *numBytes, key, *lpOverlapped); - *lpOverlapped = NULL; - } - } - else - { - //A more complete reset is needed (due to change in priority etc), go back to the VM, - //and process the packet there. - - ThreadpoolMgr::StoreOverlappedInfoInThread(pThread, *errorCode, *numBytes, key, *lpOverlapped); - *lpOverlapped = NULL; - } - } - - // if this will be "dispatched" to the managed callback fire the IODequeue event: - if (*lpOverlapped != NULL && ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIODequeue)) - FireEtwThreadPoolIODequeue(*lpOverlapped, OverlappedDataObject::GetOverlapped(*lpOverlapped), GetClrInstanceId()); - -#else // !TARGET_UNIX - *lpOverlapped = NULL; -#endif // !TARGET_UNIX - - return; -} -FCIMPLEND - FCIMPL1(LPOVERLAPPED, AllocateNativeOverlapped, OverlappedDataObject* overlappedUNSAFE) { FCALL_CONTRACT; diff --git a/src/coreclr/vm/nativeoverlapped.h b/src/coreclr/vm/nativeoverlapped.h index ff041cbbf0a86e..771f9fbf483270 100644 --- a/src/coreclr/vm/nativeoverlapped.h +++ b/src/coreclr/vm/nativeoverlapped.h @@ -73,7 +73,6 @@ typedef OverlappedDataObject* OVERLAPPEDDATAREF; #endif -FCDECL3(void, CheckVMForIOPacket, LPOVERLAPPED* lpOverlapped, DWORD* errorCode, DWORD* numBytes); FCDECL1(LPOVERLAPPED, AllocateNativeOverlapped, OverlappedDataObject* overlapped); FCDECL1(void, FreeNativeOverlapped, LPOVERLAPPED lpOverlapped); FCDECL1(OverlappedDataObject*, GetOverlappedFromNative, LPOVERLAPPED lpOverlapped); diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index ddaa9a3e98444e..a7cfdbc3dca33a 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -211,9 +211,6 @@ static const Entry s_QCall[] = DllImportEntry(ThreadNative_GetCurrentOSThreadId) DllImportEntry(ThreadNative_Abort) DllImportEntry(ThreadNative_ResetAbort) - DllImportEntry(ThreadPool_GetCompletedWorkItemCount) - DllImportEntry(ThreadPool_RequestWorkerThread) - DllImportEntry(ThreadPool_PerformGateActivities) #ifdef TARGET_UNIX DllImportEntry(WaitHandle_CorWaitOnePrioritizedNative) #endif diff --git a/src/coreclr/vm/threadpoolrequest.cpp b/src/coreclr/vm/threadpoolrequest.cpp deleted file mode 100644 index fb31321f31a9fe..00000000000000 --- a/src/coreclr/vm/threadpoolrequest.cpp +++ /dev/null @@ -1,690 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -//========================================================================= - -// -// ThreadPoolRequest.cpp -// - -// -// -//========================================================================= - -#include "common.h" -#include "comdelegate.h" -#include "comthreadpool.h" -#include "threadpoolrequest.h" -#include "win32threadpool.h" -#include "class.h" -#include "object.h" -#include "field.h" -#include "excep.h" -#include "eeconfig.h" -#include "corhost.h" -#include "nativeoverlapped.h" -#include "appdomain.inl" - -BYTE PerAppDomainTPCountList::s_padding[MAX_CACHE_LINE_SIZE - sizeof(LONG)]; -// Make this point to unmanaged TP in case, no appdomains have initialized yet. -// Cacheline aligned, hot variable -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) LONG PerAppDomainTPCountList::s_ADHint = -1; - -// Move out of from preceding variables' cache line -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) UnManagedPerAppDomainTPCount PerAppDomainTPCountList::s_unmanagedTPCount; -//The list of all per-appdomain work-request counts. -ArrayListStatic PerAppDomainTPCountList::s_appDomainIndexList; - -void PerAppDomainTPCountList::InitAppDomainIndexList() -{ - LIMITED_METHOD_CONTRACT; - - if (!ThreadpoolMgr::UsePortableThreadPool()) - { - s_appDomainIndexList.Init(); - } -} - - -//--------------------------------------------------------------------------- -//AddNewTPIndex adds and returns a per-appdomain TP entry whenever a new appdomain -//is created. Our list count should be equal to the max number of appdomains created -//in the system. -// -//Assumptions: -//This function needs to be called under the SystemDomain lock. -//The ArrayListStatic data dtructure allows traversing of the counts without a -//lock, but addition to the list requires synchronization. -// -TPIndex PerAppDomainTPCountList::AddNewTPIndex() -{ - STANDARD_VM_CONTRACT; - - if (ThreadpoolMgr::UsePortableThreadPool()) - { - return TPIndex(); - } - - DWORD count = s_appDomainIndexList.GetCount(); - DWORD i = FindFirstFreeTpEntry(); - - if (i == UNUSED_THREADPOOL_INDEX) - i = count; - - TPIndex index(i+1); - if(count > i) - { - - IPerAppDomainTPCount * pAdCount = dac_cast(s_appDomainIndexList.Get(i)); - pAdCount->SetTPIndex(index); - return index; - } - -#ifdef _MSC_VER - // Disable this warning - we intentionally want __declspec(align()) to insert trailing padding for us -#pragma warning(disable:4316) // Object allocated on the heap may not be aligned for this type. -#endif - ManagedPerAppDomainTPCount * pAdCount = new ManagedPerAppDomainTPCount(index); -#ifdef _MSC_VER -#pragma warning(default:4316) // Object allocated on the heap may not be aligned for this type. -#endif - pAdCount->ResetState(); - - IfFailThrow(s_appDomainIndexList.Append(pAdCount)); - - return index; -} - -DWORD PerAppDomainTPCountList::FindFirstFreeTpEntry() -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - DWORD DwnumADs = s_appDomainIndexList.GetCount(); - DWORD Dwi; - IPerAppDomainTPCount * pAdCount; - DWORD DwfreeIndex = UNUSED_THREADPOOL_INDEX; - - for (Dwi=0;Dwi < DwnumADs;Dwi++) - { - pAdCount = dac_cast(s_appDomainIndexList.Get(Dwi)); - _ASSERTE(pAdCount); - - if(pAdCount->IsTPIndexUnused()) - { - DwfreeIndex = Dwi; - STRESS_LOG1(LF_THREADPOOL, LL_INFO1000, "FindFirstFreeTpEntry: reusing index %d\n", DwfreeIndex + 1); - break; - } - } - - return DwfreeIndex; -} - -//--------------------------------------------------------------------------- -//ResetAppDomainIndex: Resets the AppDomain ID and the per-appdomain -// thread pool counts -// -//Arguments: -//index - The index into the s_appDomainIndexList for the AppDomain we're -// trying to clear (the AD being unloaded) -// -//Assumptions: -//This function needs to be called from the AD unload thread after all domain -//bound objects have been finalized when it's safe to recycle the TPIndex. -//ClearAppDomainRequestsActive can be called from this function because no -// managed code is running (If managed code is running, this function needs -//to be called under a managed per-appdomain lock). -// -void PerAppDomainTPCountList::ResetAppDomainIndex(TPIndex index) -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_TRIGGERS; - } - 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); - - STRESS_LOG2(LF_THREADPOOL, LL_INFO1000, "ResetAppDomainIndex: index %d pAdCount %p\n", index.m_dwIndex, pAdCount); - - pAdCount->ResetState(); - pAdCount->SetTPIndexUnused(); -} - -//--------------------------------------------------------------------------- -//AreRequestsPendingInAnyAppDomains checks to see if there any requests pending -//in other appdomains. It also checks for pending unmanaged work requests. -//This function is called at end of thread quantum to see if the thread needs to -//transition into a different appdomain. This function may also be called by -//the scheduler to check for any unscheduled work. -// -bool PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains() -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - DWORD DwnumADs = s_appDomainIndexList.GetCount(); - DWORD Dwi; - IPerAppDomainTPCount * pAdCount; - bool fRequestsPending = false; - - for (Dwi=0;Dwi < DwnumADs;Dwi++) - { - - pAdCount = dac_cast(s_appDomainIndexList.Get(Dwi)); - _ASSERTE(pAdCount); - - if(pAdCount->IsRequestPending()) - { - fRequestsPending = true; - break; - } - } - - if(s_unmanagedTPCount.IsRequestPending()) - { - fRequestsPending = true; - } - - return fRequestsPending; -} - - -//--------------------------------------------------------------------------- -//GetAppDomainIndexForThreadpoolDispatch is essentailly the -//"AppDomain Scheduler". This function makes fairness/policy decisions as to -//which appdomain the thread needs to enter to. This function needs to guarantee -//that all appdomain work requests are processed fairly. At this time all -//appdomain requests and the unmanaged work requests are treated with the same -//priority. -// -//Return Value: -//The appdomain ID in which to dispatch the worker thread,nmanaged work items -//need to be processed. -// -LONG PerAppDomainTPCountList::GetAppDomainIndexForThreadpoolDispatch() -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - LONG hint = s_ADHint; - DWORD count = s_appDomainIndexList.GetCount(); - IPerAppDomainTPCount * pAdCount; - DWORD Dwi; - - - if (hint != -1) - { - pAdCount = dac_cast(s_appDomainIndexList.Get(hint)); - } - else - { - pAdCount = &s_unmanagedTPCount; - } - - //temphint ensures that the check for appdomains proceeds in a pure round robin fashion. - LONG temphint = hint; - - _ASSERTE( pAdCount); - - if (pAdCount->TakeActiveRequest()) - goto HintDone; - - //If there is no work in any appdomains, check the unmanaged queue, - hint = -1; - - for (Dwi=0;Dwi(s_appDomainIndexList.Get(temphint)); - if (pAdCount->TakeActiveRequest()) - { - hint = temphint; - goto HintDone; - } - - temphint++; - - _ASSERTE( temphint <= (LONG)count); - - if(temphint == (LONG)count) - { - temphint = 0; - } - } - - if (hint == -1 && !s_unmanagedTPCount.TakeActiveRequest()) - { - //no work! - return 0; - } - -HintDone: - - if((hint+1) < (LONG)count) - { - s_ADHint = hint+1; - } - else - { - s_ADHint = -1; - } - - if (hint == -1) - { - return hint; - } - else - { - return (hint+1); - } -} - - -void UnManagedPerAppDomainTPCount::SetAppDomainRequestsActive() -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - -#ifndef DACCESS_COMPILE - LONG count = VolatileLoad(&m_outstandingThreadRequestCount); - while (count < (LONG)ThreadpoolMgr::NumberOfProcessors) - { - LONG prevCount = InterlockedCompareExchange(&m_outstandingThreadRequestCount, count+1, count); - if (prevCount == count) - { - ThreadpoolMgr::MaybeAddWorkingWorker(); - ThreadpoolMgr::EnsureGateThreadRunning(); - break; - } - count = prevCount; - } -#endif -} - -bool FORCEINLINE UnManagedPerAppDomainTPCount::TakeActiveRequest() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - LONG count = VolatileLoad(&m_outstandingThreadRequestCount); - - while (count > 0) - { - LONG prevCount = InterlockedCompareExchange(&m_outstandingThreadRequestCount, count-1, count); - if (prevCount == count) - return true; - count = prevCount; - } - - return false; -} - - -FORCEINLINE void ReleaseWorkRequest(WorkRequest *workRequest) { ThreadpoolMgr::RecycleMemory( workRequest, ThreadpoolMgr::MEMTYPE_WorkRequest ); } -typedef Wrapper< WorkRequest *, DoNothing, ReleaseWorkRequest > WorkRequestHolder; - -void UnManagedPerAppDomainTPCount::QueueUnmanagedWorkRequest(LPTHREAD_START_ROUTINE function, PVOID context) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END;; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - -#ifndef DACCESS_COMPILE - WorkRequestHolder pWorkRequest; - - //Note, ideally we would want to use our own queues instead of those in - //the thread pool class. However, the queus in thread pool class have - //caching support, that shares memory with other commonly used structures - //in the VM thread pool implementation. So, we decided to leverage those. - - pWorkRequest = ThreadpoolMgr::MakeWorkRequest(function, context); - - //MakeWorkRequest should throw if unable to allocate memory - _ASSERTE(pWorkRequest != NULL); - PREFIX_ASSUME(pWorkRequest != NULL); - - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolEnqueue)) - FireEtwThreadPoolEnqueue(pWorkRequest, GetClrInstanceId()); - - m_lock.Init(LOCK_TYPE_DEFAULT); - - { - SpinLock::Holder slh(&m_lock); - - ThreadpoolMgr::EnqueueWorkRequest(pWorkRequest); - pWorkRequest.SuppressRelease(); - m_NumRequests++; - } - - SetAppDomainRequestsActive(); -#endif //DACCESS_COMPILE -} - -PVOID UnManagedPerAppDomainTPCount::DeQueueUnManagedWorkRequest(bool* lastOne) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - *lastOne = true; - - WorkRequest * pWorkRequest = ThreadpoolMgr::DequeueWorkRequest(); - - if (pWorkRequest) - { - m_NumRequests--; - - if(m_NumRequests > 0) - *lastOne = false; - } - - return (PVOID) pWorkRequest; -} - -//--------------------------------------------------------------------------- -//DispatchWorkItem manages dispatching of unmanaged work requests. It keeps -//processing unmanaged requests for the "Quanta". Essentially this function is -//a tight loop of dequeueing unmanaged work requests and dispatching them. -// -void UnManagedPerAppDomainTPCount::DispatchWorkItem(bool* foundWork, bool* wasNotRecalled) -{ - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - -#ifndef DACCESS_COMPILE - *foundWork = false; - *wasNotRecalled = true; - - bool enableWorkerTracking = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking) ? true : false; - - DWORD startTime; - DWORD endTime; - - startTime = GetTickCount(); - - //For all practical puposes, the unmanaged part of thread pool is treated - //as a special appdomain for thread pool purposes. The same logic as the - //one in managed code for dispatching thread pool requests is repeated here. - //Namely we continue to process requests until eithere there are none, or - //the "Quanta has expired". See threadpool.cs for the managed counterpart. - - WorkRequest * pWorkRequest=NULL; - LPTHREAD_START_ROUTINE wrFunction; - LPVOID wrContext; - - bool firstIteration = true; - bool lastOne = false; - - while (*wasNotRecalled) - { - m_lock.Init(LOCK_TYPE_DEFAULT); - { - SpinLock::Holder slh(&m_lock); - pWorkRequest = (WorkRequest*) DeQueueUnManagedWorkRequest(&lastOne); - } - - if (NULL == pWorkRequest) - break; - - if (firstIteration && !lastOne) - SetAppDomainRequestsActive(); - - firstIteration = false; - *foundWork = true; - - if (GCHeapUtilities::IsGCInProgress(TRUE)) - { - // GC is imminent, so wait until GC is complete before executing next request. - // this reduces in-flight objects allocated right before GC, easing the GC's work - GCHeapUtilities::WaitForGCCompletion(TRUE); - } - - PREFIX_ASSUME(pWorkRequest != NULL); - _ASSERTE(pWorkRequest); - - wrFunction = pWorkRequest->Function; - wrContext = pWorkRequest->Context; - - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolDequeue)) - FireEtwThreadPoolDequeue(pWorkRequest, GetClrInstanceId()); - - ThreadpoolMgr::FreeWorkRequest(pWorkRequest); - - if (enableWorkerTracking) - { - ThreadpoolMgr::ReportThreadStatus(true); - (wrFunction) (wrContext); - ThreadpoolMgr::ReportThreadStatus(false); - } - else - { - (wrFunction) (wrContext); - } - - ThreadpoolMgr::NotifyWorkItemCompleted(); - if (ThreadpoolMgr::ShouldAdjustMaxWorkersActive()) - { - DangerousNonHostedSpinLockTryHolder tal(&ThreadpoolMgr::ThreadAdjustmentLock); - if (tal.Acquired()) - { - ThreadpoolMgr::AdjustMaxWorkersActive(); - } - else - { - // the lock is held by someone else, so they will take care of this for us. - } - } - *wasNotRecalled = ThreadpoolMgr::ShouldWorkerKeepRunning(); - - Thread *pThread = GetThreadNULLOk(); - if (pThread) - { - _ASSERTE(!pThread->IsAbortRequested()); - pThread->InternalReset(); - } - - endTime = GetTickCount(); - - if ((endTime - startTime) >= TP_QUANTUM) - { - break; - } - } - - // if we're exiting for any reason other than the queue being empty, then we need to make sure another thread - // will visit us later. - if (NULL != pWorkRequest) - { - SetAppDomainRequestsActive(); - } - -#endif //DACCESS_COMPILE -} - - -void ManagedPerAppDomainTPCount::SetAppDomainRequestsActive() -{ - //This function should either be called by managed code or during AD unload, but before - //the TpIndex is set to unused. - // - // Note that there is a separate count in managed code that stays in sync with this one over time. - // The manage count is incremented before this one, and this one is decremented before the managed - // one. - // - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - _ASSERTE(m_index.m_dwIndex != UNUSED_THREADPOOL_INDEX); - -#ifndef DACCESS_COMPILE - LONG count = VolatileLoad(&m_numRequestsPending); - while (true) - { - LONG prev = InterlockedCompareExchange(&m_numRequestsPending, count+1, count); - if (prev == count) - { - ThreadpoolMgr::MaybeAddWorkingWorker(); - ThreadpoolMgr::EnsureGateThreadRunning(); - break; - } - count = prev; - } -#endif -} - -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. - - _ASSERTE(m_index.m_dwIndex != UNUSED_THREADPOOL_INDEX); - - LONG count = VolatileLoad(&m_numRequestsPending); - while (count > 0) - { - LONG prev = InterlockedCompareExchange(&m_numRequestsPending, 0, count); - if (prev == count) - break; - count = prev; - } -} - -bool ManagedPerAppDomainTPCount::TakeActiveRequest() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - LONG count = VolatileLoad(&m_numRequestsPending); - while (count > 0) - { - LONG prev = InterlockedCompareExchange(&m_numRequestsPending, count-1, count); - if (prev == count) - return true; - count = prev; - } - return false; -} - -#ifndef DACCESS_COMPILE - -//--------------------------------------------------------------------------- -//DispatchWorkItem makes sure the right exception handling frames are setup, -//the thread is transitioned into the correct appdomain, and the right managed -//callback is called. -// -void ManagedPerAppDomainTPCount::DispatchWorkItem(bool* foundWork, bool* wasNotRecalled) -{ - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - *foundWork = false; - *wasNotRecalled = true; - - HRESULT hr; - Thread * pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(&hr); - if (pThread == NULL) - { - return; - } - } - - { - CONTRACTL - { - MODE_PREEMPTIVE; - THROWS; - GC_TRIGGERS; - } - CONTRACTL_END; - - GCX_COOP(); - - // - // NOTE: there is a potential race between the time we retrieve the app - // domain pointer, and the time which this thread enters the domain. - // - // To solve the race, we rely on the fact that there is a thread sync (via - // GC) between releasing an app domain's handle, and destroying the - // app domain. Thus it is important that we not go into preemptive gc mode - // in that window. - // - - { - // This TPIndex may have been recycled since we chose it for workitem dispatch. - // If so, the new AppDomain will necessarily have zero requests - // pending (because the destruction of the previous AD that used this TPIndex - // will have reset this object). We don't want to call into such an AppDomain. - // TODO: fix this another way! - // if (IsRequestPending()) - { - ManagedThreadBase::ThreadPool(QueueUserWorkItemManagedCallback, wasNotRecalled); - } - - if (pThread->IsAbortRequested()) - { - // thread was aborted, and may not have had a chance to tell us it has work. - ThreadpoolMgr::SetAppDomainRequestsActive(); - ThreadpoolMgr::QueueUserWorkItem(NULL, - NULL, - 0, - FALSE); - } - } - - *foundWork = true; - } -} - -#endif // !DACCESS_COMPILE diff --git a/src/coreclr/vm/threadpoolrequest.h b/src/coreclr/vm/threadpoolrequest.h deleted file mode 100644 index f3cd4c49ccac60..00000000000000 --- a/src/coreclr/vm/threadpoolrequest.h +++ /dev/null @@ -1,269 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -//========================================================================= - -// -// ThreadPoolRequest.h -// - -// -// This file contains definitions of classes needed to mainain per-appdomain -// thread pool work requests. This is needed as unmanaged and managed work -// requests are allocted, managed and dispatched in drastically different ways. -// However, the scheduler need be aware of these differences, and it should -// simply talk to a common interface for managing work request counts. -// -//========================================================================= - -#ifndef _THREADPOOL_REQUEST_H -#define _THREADPOOL_REQUEST_H - -#include "util.hpp" - -#define TP_QUANTUM 2 -#define UNUSED_THREADPOOL_INDEX (DWORD)-1 - -//-------------------------------------------------------------------------- -//IPerAppDomainTPCount is an interface for implementing per-appdomain thread -//pool state. It's implementation should include logic to maintain work-counts, -//notify thread pool class when work arrives or no work is left. Finally -//there is logic to dipatch work items correctly in the right domain. -// -//Notes: -//This class was designed to support both the managed and unmanaged uses -//of thread pool. The unmananged part may directly be used through com -//interfaces. The differences between the actual management of counts and -//dispatching of work is quite different between the two. This interface -//hides these differences to the thread scheduler implemented by the thread pool -//class. -// - -class IPerAppDomainTPCount{ -public: - virtual void ResetState() = 0; - virtual BOOL IsRequestPending() = 0; - - //This functions marks the beginning of requests queued for the domain. - //It needs to notify the scheduler of work-arrival among other things. - virtual void SetAppDomainRequestsActive() = 0; - - //This functions marks the end of requests queued for this domain. - virtual void ClearAppDomainRequestsActive() = 0; - - //Clears the "active" flag if it was set, and returns whether it was set. - virtual bool TakeActiveRequest() = 0; - - //Takes care of dispatching requests in the right domain. - virtual void DispatchWorkItem(bool* foundWork, bool* wasNotRecalled) = 0; - virtual void SetTPIndexUnused() = 0; - virtual BOOL IsTPIndexUnused() = 0; - virtual void SetTPIndex(TPIndex index) = 0; -}; - -typedef DPTR(IPerAppDomainTPCount) PTR_IPerAppDomainTPCount; - -#ifdef _MSC_VER -// Disable this warning - we intentionally want __declspec(align()) to insert padding for us -#pragma warning(disable: 4324) // structure was padded due to __declspec(align()) -#endif - -//-------------------------------------------------------------------------- -//ManagedPerAppDomainTPCount maintains per-appdomain thread pool state. -//This class maintains the count of per-appdomain work-items queued by -//ThreadPool.QueueUserWorkItem. It also dispatches threads in the appdomain -//correctly by setting up the right exception handling frames etc. -// -//Note: The counts are not accurate, and neither do they need to be. The -//actual work queue is in managed (implemented in threadpool.cs). This class -//just provides heuristics to the thread pool scheduler, along with -//synchronization to indicate start/end of requests to the scheduler. -class ManagedPerAppDomainTPCount : public IPerAppDomainTPCount { -public: - - ManagedPerAppDomainTPCount(TPIndex index) {ResetState(); m_index = index;} - - inline void ResetState() - { - LIMITED_METHOD_CONTRACT; - VolatileStore(&m_numRequestsPending, (LONG)0); - } - - inline BOOL IsRequestPending() - { - LIMITED_METHOD_CONTRACT; - - LONG count = VolatileLoad(&m_numRequestsPending); - return count > 0; - } - - void SetAppDomainRequestsActive(); - void ClearAppDomainRequestsActive(); - bool TakeActiveRequest(); - - inline void SetTPIndex(TPIndex index) - { - LIMITED_METHOD_CONTRACT; - //This function should be called during appdomain creation when no managed code - //has started running yet. That implies, no requests should be pending - //or dispatched to this structure yet. - - _ASSERTE(m_index.m_dwIndex == UNUSED_THREADPOOL_INDEX); - - m_index = index; - } - - inline BOOL IsTPIndexUnused() - { - LIMITED_METHOD_CONTRACT; - if (m_index.m_dwIndex == UNUSED_THREADPOOL_INDEX) - { - return TRUE; - } - - return FALSE; - } - - inline void SetTPIndexUnused() - { - WRAPPER_NO_CONTRACT; - m_index.m_dwIndex = UNUSED_THREADPOOL_INDEX; - } - - void DispatchWorkItem(bool* foundWork, bool* wasNotRecalled); - -private: - TPIndex m_index; - struct DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) { - BYTE m_padding1[MAX_CACHE_LINE_SIZE - sizeof(LONG)]; - // Only use with VolatileLoad+VolatileStore+InterlockedCompareExchange - LONG m_numRequestsPending; - BYTE m_padding2[MAX_CACHE_LINE_SIZE]; - }; -}; - -//-------------------------------------------------------------------------- -//UnManagedPerAppDomainTPCount maintains the thread pool state/counts for -//unmanaged work requests. From thread pool point of view we treat unmanaged -//requests as a special "appdomain". This helps in scheduling policies, and -//follow same fairness policies as requests in other appdomains. -class UnManagedPerAppDomainTPCount : public IPerAppDomainTPCount { -public: - - UnManagedPerAppDomainTPCount() - { - LIMITED_METHOD_CONTRACT; - ResetState(); - } - - inline void ResetState() - { - LIMITED_METHOD_CONTRACT; - m_NumRequests = 0; - VolatileStore(&m_outstandingThreadRequestCount, (LONG)0); - } - - inline BOOL IsRequestPending() - { - LIMITED_METHOD_CONTRACT; - return VolatileLoad(&m_outstandingThreadRequestCount) != (LONG)0 ? TRUE : FALSE; - } - - void SetAppDomainRequestsActive(); - - inline void ClearAppDomainRequestsActive() - { - LIMITED_METHOD_CONTRACT; - VolatileStore(&m_outstandingThreadRequestCount, (LONG)0); - } - - bool TakeActiveRequest(); - - void QueueUnmanagedWorkRequest(LPTHREAD_START_ROUTINE function, PVOID context); - PVOID DeQueueUnManagedWorkRequest(bool* lastOne); - - void DispatchWorkItem(bool* foundWork, bool* wasNotRecalled); - - inline void SetTPIndexUnused() - { - WRAPPER_NO_CONTRACT; - _ASSERT(FALSE); - } - - inline BOOL IsTPIndexUnused() - { - WRAPPER_NO_CONTRACT; - _ASSERT(FALSE); - return FALSE; - } - - inline void SetTPIndex(TPIndex index) - { - WRAPPER_NO_CONTRACT; - _ASSERT(FALSE); - } - - inline ULONG GetNumRequests() - { - LIMITED_METHOD_CONTRACT; - return VolatileLoad(&m_NumRequests); - } - -private: - SpinLock m_lock; - ULONG m_NumRequests; - struct DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) { - BYTE m_padding1[MAX_CACHE_LINE_SIZE - sizeof(LONG)]; - // Only use with VolatileLoad+VolatileStore+InterlockedCompareExchange - LONG m_outstandingThreadRequestCount; - BYTE m_padding2[MAX_CACHE_LINE_SIZE]; - }; -}; - -#ifdef _MSC_VER -#pragma warning(default: 4324) // structure was padded due to __declspec(align()) -#endif - -//-------------------------------------------------------------------------- -//PerAppDomainTPCountList maintains the collection of per-appdomain thread -//pool states. Per appdomain counts are added to the list during appdomain -//creation inside the sdomain lock. The counts are reset during appdomain -//unload after all the threads have -//This class maintains the count of per-appdomain work-items queued by -//ThreadPool.QueueUserWorkItem. It also dispatches threads in the appdomain -//correctly by setting up the right exception handling frames etc. -// -//Note: The counts are not accurate, and neither do they need to be. The -//actual work queue is in managed (implemented in threadpool.cs). This class -//just provides heuristics to the thread pool scheduler, along with -//synchronization to indicate start/end of requests to the scheduler. -class PerAppDomainTPCountList{ -public: - static void InitAppDomainIndexList(); - static void ResetAppDomainIndex(TPIndex index); - static bool AreRequestsPendingInAnyAppDomains(); - static LONG GetAppDomainIndexForThreadpoolDispatch(); - static TPIndex AddNewTPIndex(); - - inline static IPerAppDomainTPCount* GetPerAppdomainCount(TPIndex index) - { - return dac_cast(s_appDomainIndexList.Get(index.m_dwIndex-1)); - } - - inline static UnManagedPerAppDomainTPCount* GetUnmanagedTPCount() - { - return &s_unmanagedTPCount; - } - -private: - static DWORD FindFirstFreeTpEntry(); - - static BYTE s_padding[MAX_CACHE_LINE_SIZE - sizeof(LONG)]; - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static LONG s_ADHint; - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static UnManagedPerAppDomainTPCount s_unmanagedTPCount; - - //The list of all per-appdomain work-request counts. - static ArrayListStatic s_appDomainIndexList; -}; - -#endif //_THREADPOOL_REQUEST_H diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 91b2f55d9ea33a..e1bc975673db28 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -20,7 +20,6 @@ #include "eeprofinterfaces.h" #include "eeconfig.h" #include "corhost.h" -#include "win32threadpool.h" #include "jitinterface.h" #include "eventtrace.h" #include "comutilnative.h" @@ -34,7 +33,6 @@ #include "appdomain.inl" #include "vmholder.h" #include "exceptmacros.h" -#include "win32threadpool.h" #ifdef FEATURE_COMINTEROP #include "runtimecallablewrapper.h" @@ -136,8 +134,6 @@ PTR_ThreadLocalModule ThreadLocalBlock::GetTLMIfExists(MethodTable* pMT) BOOL Thread::s_fCleanFinalizedThread = FALSE; -UINT64 Thread::s_workerThreadPoolCompletionCountOverflow = 0; -UINT64 Thread::s_ioThreadPoolCompletionCountOverflow = 0; UINT64 Thread::s_monitorLockContentionCountOverflow = 0; CrstStatic g_DeadlockAwareCrst; @@ -1541,8 +1537,6 @@ Thread::Thread() m_AbortRequestLock = 0; m_fRudeAbortInitiated = FALSE; - m_pIOCompletionContext = NULL; - #ifdef _DEBUG m_fRudeAborted = FALSE; m_dwAbortPoint = 0; @@ -1600,8 +1594,6 @@ Thread::Thread() m_sfEstablisherOfActualHandlerFrame.Clear(); #endif // FEATURE_EH_FUNCLETS - m_workerThreadPoolCompletionCount = 0; - m_ioThreadPoolCompletionCount = 0; m_monitorLockContentionCount = 0; m_pDomain = SystemDomain::System()->DefaultDomain(); @@ -1776,12 +1768,6 @@ void Thread::InitThread() ThrowOutOfMemory(); } } - - ret = Thread::AllocateIOCompletionContext(); - if (!ret) - { - ThrowOutOfMemory(); - } } // Allocate all the handles. When we are kicking of a new thread, we can call @@ -1976,33 +1962,6 @@ BOOL Thread::HasStarted() return FALSE; } -BOOL Thread::AllocateIOCompletionContext() -{ - WRAPPER_NO_CONTRACT; - PIOCompletionContext pIOC = new (nothrow) IOCompletionContext; - - if(pIOC != NULL) - { - pIOC->lpOverlapped = NULL; - m_pIOCompletionContext = pIOC; - return TRUE; - } - else - { - return FALSE; - } -} - -VOID Thread::FreeIOCompletionContext() -{ - WRAPPER_NO_CONTRACT; - if (m_pIOCompletionContext != NULL) - { - PIOCompletionContext pIOC = (PIOCompletionContext) m_pIOCompletionContext; - delete pIOC; - m_pIOCompletionContext = NULL; - } -} void Thread::HandleThreadStartupFailure() { @@ -2676,8 +2635,6 @@ Thread::~Thread() m_EventWait.CloseEvent(); } - FreeIOCompletionContext(); - if (m_OSContext) delete m_OSContext; @@ -5384,12 +5341,6 @@ BOOL ThreadStore::RemoveThread(Thread *target) if (target->IsBackground()) s_pThreadStore->m_BackgroundThreadCount--; - InterlockedExchangeAdd64( - (LONGLONG *)&Thread::s_workerThreadPoolCompletionCountOverflow, - target->m_workerThreadPoolCompletionCount); - InterlockedExchangeAdd64( - (LONGLONG *)&Thread::s_ioThreadPoolCompletionCountOverflow, - target->m_ioThreadPoolCompletionCount); InterlockedExchangeAdd64( (LONGLONG *)&Thread::s_monitorLockContentionCountOverflow, target->m_monitorLockContentionCount); @@ -7581,13 +7532,6 @@ void ManagedThreadBase::KickOff(ADCallBackFcnType pTarget, LPVOID args) ManagedThreadBase_FullTransition(pTarget, args, ManagedThread); } -// The IOCompletion, QueueUserWorkItem, RegisterWaitForSingleObject cases in the ThreadPool -void ManagedThreadBase::ThreadPool(ADCallBackFcnType pTarget, LPVOID args) -{ - WRAPPER_NO_CONTRACT; - ManagedThreadBase_FullTransition(pTarget, args, ThreadPoolThread); -} - // The Finalizer thread establishes exception handling at its base, but defers all the AppDomain // transitions. void ManagedThreadBase::FinalizerBase(ADCallBackFcnType pTarget) @@ -7904,40 +7848,6 @@ UINT64 Thread::GetTotalCount(SIZE_T threadLocalCountOffset, UINT64 *overflowCoun return total; } -UINT64 Thread::GetTotalThreadPoolCompletionCount() -{ - CONTRACTL { - NOTHROW; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPoolForIO()); - - bool usePortableThreadPool = ThreadpoolMgr::UsePortableThreadPool(); - - // enumerate all threads, summing their local counts. - ThreadStoreLockHolder tsl; - - UINT64 total = GetIOThreadPoolCompletionCountOverflow(); - if (!usePortableThreadPool) - { - total += GetWorkerThreadPoolCompletionCountOverflow(); - } - - Thread *pThread = NULL; - while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL) - { - if (!usePortableThreadPool) - { - total += pThread->m_workerThreadPoolCompletionCount; - } - total += pThread->m_ioThreadPoolCompletionCount; - } - - return total; -} - INT32 Thread::ResetManagedThreadObject(INT32 nPriority) { CONTRACTL { diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index 9b5d497734cc2f..b51804a471705d 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -3432,10 +3432,6 @@ class Thread #endif // defined(PROFILING_SUPPORTED) || defined(PROFILING_SUPPORTED_DATA) private: - UINT32 m_workerThreadPoolCompletionCount; - static UINT64 s_workerThreadPoolCompletionCountOverflow; - UINT32 m_ioThreadPoolCompletionCount; - static UINT64 s_ioThreadPoolCompletionCountOverflow; UINT32 m_monitorLockContentionCount; static UINT64 s_monitorLockContentionCountOverflow; @@ -3489,38 +3485,6 @@ class Thread static UINT64 GetTotalCount(SIZE_T threadLocalCountOffset, UINT64 *overflowCount); public: - static void IncrementWorkerThreadPoolCompletionCount(Thread *pThread) - { - WRAPPER_NO_CONTRACT; - IncrementCount(pThread, offsetof(Thread, m_workerThreadPoolCompletionCount), &s_workerThreadPoolCompletionCountOverflow); - } - - static UINT64 GetWorkerThreadPoolCompletionCountOverflow() - { - WRAPPER_NO_CONTRACT; - return GetOverflowCount(&s_workerThreadPoolCompletionCountOverflow); - } - - static UINT64 GetTotalWorkerThreadPoolCompletionCount() - { - WRAPPER_NO_CONTRACT; - return GetTotalCount(offsetof(Thread, m_workerThreadPoolCompletionCount), &s_workerThreadPoolCompletionCountOverflow); - } - - static void IncrementIOThreadPoolCompletionCount(Thread *pThread) - { - WRAPPER_NO_CONTRACT; - IncrementCount(pThread, offsetof(Thread, m_ioThreadPoolCompletionCount), &s_ioThreadPoolCompletionCountOverflow); - } - - static UINT64 GetIOThreadPoolCompletionCountOverflow() - { - WRAPPER_NO_CONTRACT; - return GetOverflowCount(&s_ioThreadPoolCompletionCountOverflow); - } - - static UINT64 GetTotalThreadPoolCompletionCount(); - static void IncrementMonitorLockContentionCount(Thread *pThread) { WRAPPER_NO_CONTRACT; @@ -4187,20 +4151,6 @@ class Thread m_fAllowProfilerCallbacks = fValue; } -private: - // - //This context is used for optimizations on I/O thread pool thread. In case the - //overlapped structure is from a different appdomain, it is stored in this structure - //to be processed later correctly by entering the right domain. - PVOID m_pIOCompletionContext; - BOOL AllocateIOCompletionContext(); - VOID FreeIOCompletionContext(); -public: - inline PVOID GetIOCompletionContext() - { - return m_pIOCompletionContext; - } - private: // Inside a host, we don't own a thread handle, and we avoid DuplicateHandle call. // If a thread is dying after we obtain the thread handle, our SuspendThread may fail @@ -6005,10 +5955,6 @@ struct ManagedThreadBase static void KickOff(ADCallBackFcnType pTarget, LPVOID args); - // The IOCompletion, QueueUserWorkItem, RegisterWaitForSingleObject cases in - // the ThreadPool - static void ThreadPool(ADCallBackFcnType pTarget, LPVOID args); - // The Finalizer thread uses this path static void FinalizerBase(ADCallBackFcnType pTarget); }; diff --git a/src/coreclr/vm/tieredcompilation.cpp b/src/coreclr/vm/tieredcompilation.cpp index 4e4da913e86641..3b15100a34fb66 100644 --- a/src/coreclr/vm/tieredcompilation.cpp +++ b/src/coreclr/vm/tieredcompilation.cpp @@ -10,7 +10,6 @@ #include "common.h" #include "excep.h" #include "log.h" -#include "win32threadpool.h" #include "threadsuspend.h" #include "tieredcompilation.h" diff --git a/src/coreclr/vm/vars.hpp b/src/coreclr/vm/vars.hpp index 628cb97deb49a8..4698d840a84c12 100644 --- a/src/coreclr/vm/vars.hpp +++ b/src/coreclr/vm/vars.hpp @@ -621,25 +621,6 @@ GVAL_DECL(SIZE_T, g_runtimeVirtualSize); #define MAXULONGLONG UI64(0xffffffffffffffff) #endif -struct TPIndex -{ - DWORD m_dwIndex; - TPIndex () - : m_dwIndex(0) - {} - explicit TPIndex (DWORD id) - : m_dwIndex(id) - {} - BOOL operator==(const TPIndex& tpindex) const - { - return m_dwIndex == tpindex.m_dwIndex; - } - BOOL operator!=(const TPIndex& tpindex) const - { - return m_dwIndex != tpindex.m_dwIndex; - } -}; - // Every Module is assigned a ModuleIndex, regardless of whether the Module is domain // neutral or domain specific. When a domain specific Module is unloaded, its ModuleIndex // can be reused. diff --git a/src/coreclr/vm/win32threadpool.cpp b/src/coreclr/vm/win32threadpool.cpp deleted file mode 100644 index 80e5e5db5d7855..00000000000000 --- a/src/coreclr/vm/win32threadpool.cpp +++ /dev/null @@ -1,4276 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - - -/*++ - -Module Name: - - Win32ThreadPool.cpp - -Abstract: - - This module implements Threadpool support using Win32 APIs - - -Revision History: - December 1999 - Created - ---*/ - -#include "common.h" -#include "log.h" -#include "threadpoolrequest.h" -#include "win32threadpool.h" -#include "delegateinfo.h" -#include "eeconfig.h" -#include "dbginterface.h" -#include "corhost.h" -#include "eventtrace.h" -#include "threads.h" -#include "appdomain.inl" -#include "nativeoverlapped.h" -#include "hillclimbing.h" -#include "configuration.h" - - -#ifndef TARGET_UNIX -#ifndef DACCESS_COMPILE - -// APIs that must be accessed through dynamic linking. -typedef int (WINAPI *NtQueryInformationThreadProc) ( - HANDLE ThreadHandle, - THREADINFOCLASS ThreadInformationClass, - PVOID ThreadInformation, - ULONG ThreadInformationLength, - PULONG ReturnLength); -NtQueryInformationThreadProc g_pufnNtQueryInformationThread = NULL; - -typedef int (WINAPI *NtQuerySystemInformationProc) ( - SYSTEM_INFORMATION_CLASS SystemInformationClass, - PVOID SystemInformation, - ULONG SystemInformationLength, - PULONG ReturnLength OPTIONAL); -NtQuerySystemInformationProc g_pufnNtQuerySystemInformation = NULL; - -typedef HANDLE (WINAPI * CreateWaitableTimerExProc) ( - LPSECURITY_ATTRIBUTES lpTimerAttributes, - LPCTSTR lpTimerName, - DWORD dwFlags, - DWORD dwDesiredAccess); -CreateWaitableTimerExProc g_pufnCreateWaitableTimerEx = NULL; - -typedef BOOL (WINAPI * SetWaitableTimerExProc) ( - HANDLE hTimer, - const LARGE_INTEGER *lpDueTime, - LONG lPeriod, - PTIMERAPCROUTINE pfnCompletionRoutine, - LPVOID lpArgToCompletionRoutine, - void* WakeContext, //should be PREASON_CONTEXT, but it's not defined for us (and we don't use it) - ULONG TolerableDelay); -SetWaitableTimerExProc g_pufnSetWaitableTimerEx = NULL; - -#endif // !DACCESS_COMPILE -#endif // !TARGET_UNIX - -BOOL ThreadpoolMgr::InitCompletionPortThreadpool = FALSE; -HANDLE ThreadpoolMgr::GlobalCompletionPort; // used for binding io completions on file handles - -SVAL_IMPL(ThreadpoolMgr::ThreadCounter,ThreadpoolMgr,CPThreadCounter); - -SVAL_IMPL_INIT(LONG,ThreadpoolMgr,MaxLimitTotalCPThreads,1000); // = MaxLimitCPThreadsPerCPU * number of CPUS -SVAL_IMPL(LONG,ThreadpoolMgr,MinLimitTotalCPThreads); -SVAL_IMPL(LONG,ThreadpoolMgr,MaxFreeCPThreads); // = MaxFreeCPThreadsPerCPU * Number of CPUS - -// Cacheline aligned, hot variable -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) SVAL_IMPL(ThreadpoolMgr::ThreadCounter, ThreadpoolMgr, WorkerCounter); - -SVAL_IMPL(LONG,ThreadpoolMgr,MinLimitTotalWorkerThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS -SVAL_IMPL(LONG,ThreadpoolMgr,MaxLimitTotalWorkerThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS - -SVAL_IMPL(LONG,ThreadpoolMgr,cpuUtilization); - -HillClimbing ThreadpoolMgr::HillClimbingInstance; - -// Cacheline aligned, 3 hot variables updated in a group -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) LONG ThreadpoolMgr::PriorCompletedWorkRequests = 0; -DWORD ThreadpoolMgr::PriorCompletedWorkRequestsTime; -DWORD ThreadpoolMgr::NextCompletedWorkRequestsTime; - -LARGE_INTEGER ThreadpoolMgr::CurrentSampleStartTime; - -unsigned int ThreadpoolMgr::WorkerThreadSpinLimit; -bool ThreadpoolMgr::IsHillClimbingDisabled; -int ThreadpoolMgr::ThreadAdjustmentInterval; - -#define INVALID_HANDLE ((HANDLE) -1) -#define NEW_THREAD_THRESHOLD 7 // Number of requests outstanding before we start a new thread -#define CP_THREAD_PENDINGIO_WAIT 5000 // polling interval when thread is retired but has a pending io -#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 - -Volatile ThreadpoolMgr::Initialization = 0; // indicator of whether the threadpool is initialized. - -bool ThreadpoolMgr::s_usePortableThreadPool = false; -bool ThreadpoolMgr::s_usePortableThreadPoolForIO = 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 - -SPTR_IMPL(WorkRequest,ThreadpoolMgr,WorkRequestHead); // Head of work request queue -SPTR_IMPL(WorkRequest,ThreadpoolMgr,WorkRequestTail); // Head of work request queue - -//unsigned int ThreadpoolMgr::LastCpuSamplingTime=0; // last time cpu utilization was sampled by gate thread -unsigned int ThreadpoolMgr::LastCPThreadCreation=0; // last time a completion port thread was created -unsigned int ThreadpoolMgr::NumberOfProcessors; // = NumberOfWorkerThreads - no. of blocked threads - - -CrstStatic ThreadpoolMgr::WorkerCriticalSection; -CLREvent * ThreadpoolMgr::RetiredCPWakeupEvent; // wakeup event for completion port threads -CrstStatic ThreadpoolMgr::WaitThreadsCriticalSection; -ThreadpoolMgr::LIST_ENTRY ThreadpoolMgr::WaitThreadsHead; - -CLRLifoSemaphore* ThreadpoolMgr::WorkerSemaphore; -CLRLifoSemaphore* ThreadpoolMgr::RetiredWorkerSemaphore; - -// Cacheline aligned, hot variable -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) LONG ThreadpoolMgr::GateThreadStatus=GATE_THREAD_STATUS_NOT_RUNNING; - -// Move out of from preceding variables' cache line -DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) ThreadpoolMgr::RecycledListsWrapper ThreadpoolMgr::RecycledLists; - -BOOL ThreadpoolMgr::IsApcPendingOnWaitThread = FALSE; - -#ifndef DACCESS_COMPILE - -// Macros for inserting/deleting from doubly linked list - -#define InitializeListHead(ListHead) (\ - (ListHead)->Flink = (ListHead)->Blink = (ListHead)) - -// -// these are named the same as slightly different macros in the NT headers -// -#undef RemoveHeadList -#undef RemoveEntryList -#undef InsertTailList -#undef InsertHeadList - -#define RemoveHeadList(ListHead,FirstEntry) \ - {\ - FirstEntry = (LIST_ENTRY*) (ListHead)->Flink;\ - ((LIST_ENTRY*)FirstEntry->Flink)->Blink = (ListHead);\ - (ListHead)->Flink = FirstEntry->Flink;\ - } - -#define RemoveEntryList(Entry) {\ - LIST_ENTRY* _EX_Entry;\ - _EX_Entry = (Entry);\ - ((LIST_ENTRY*) _EX_Entry->Blink)->Flink = _EX_Entry->Flink;\ - ((LIST_ENTRY*) _EX_Entry->Flink)->Blink = _EX_Entry->Blink;\ - } - -#define InsertTailList(ListHead,Entry) \ - (Entry)->Flink = (ListHead);\ - (Entry)->Blink = (ListHead)->Blink;\ - ((LIST_ENTRY*)(ListHead)->Blink)->Flink = (Entry);\ - (ListHead)->Blink = (Entry); - -#define InsertHeadList(ListHead,Entry) {\ - LIST_ENTRY* _EX_Flink;\ - LIST_ENTRY* _EX_ListHead;\ - _EX_ListHead = (LIST_ENTRY*)(ListHead);\ - _EX_Flink = (LIST_ENTRY*) _EX_ListHead->Flink;\ - (Entry)->Flink = _EX_Flink;\ - (Entry)->Blink = _EX_ListHead;\ - _EX_Flink->Blink = (Entry);\ - _EX_ListHead->Flink = (Entry);\ - } - -#define IsListEmpty(ListHead) \ - ((ListHead)->Flink == (ListHead)) - -#define SetLastHRError(hr) \ - if (HRESULT_FACILITY(hr) == FACILITY_WIN32)\ - SetLastError(HRESULT_CODE(hr));\ - else \ - SetLastError(ERROR_INVALID_DATA);\ - -/************************************************************************/ - -void ThreadpoolMgr::RecycledListsWrapper::Initialize( unsigned int numProcs ) -{ - CONTRACTL - { - THROWS; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - pRecycledListPerProcessor = new RecycledListInfo[numProcs][MEMTYPE_COUNT]; -} - -//--// - -void ThreadpoolMgr::EnsureInitialized() -{ - CONTRACTL - { - THROWS; // EnsureInitializedSlow can throw - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - if (IsInitialized()) - return; - - EnsureInitializedSlow(); -} - -NOINLINE void ThreadpoolMgr::EnsureInitializedSlow() -{ - CONTRACTL - { - THROWS; // Initialize can throw - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - DWORD dwSwitchCount = 0; - -retry: - if (InterlockedCompareExchange(&Initialization, 1, 0) == 0) - { - if (Initialize()) - Initialization = -1; - else - { - Initialization = 0; - COMPlusThrowOM(); - } - } - else // someone has already begun initializing. - { - // wait until it finishes - while (Initialization != -1) - { - __SwitchToThread(0, ++dwSwitchCount); - goto retry; - } - } -} - -DWORD GetDefaultMaxLimitWorkerThreads(DWORD minLimit) -{ - CONTRACTL - { - MODE_ANY; - GC_NOTRIGGER; - NOTHROW; - } - CONTRACTL_END; - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - // - // We determine the max limit for worker threads as follows: - // - // 1) It must be at least MinLimitTotalWorkerThreads - // 2) It must be no greater than (half the virtual address space)/(thread stack size) - // 3) It must be <= MaxPossibleWorkerThreads - // - // TODO: what about CP threads? Can they follow a similar plan? How do we allocate - // thread counts between the two kinds of threads? - // - SIZE_T stackReserveSize = 0; - Thread::GetProcessDefaultStackSize(&stackReserveSize, NULL); - - ULONGLONG halfVirtualAddressSpace; - - MEMORYSTATUSEX memStats; - memStats.dwLength = sizeof(memStats); - if (GlobalMemoryStatusEx(&memStats)) - { - halfVirtualAddressSpace = memStats.ullTotalVirtual / 2; - } - else - { - //assume the normal Win32 32-bit virtual address space - halfVirtualAddressSpace = 0x000000007FFE0000ull / 2; - } - - ULONGLONG limit = halfVirtualAddressSpace / stackReserveSize; - limit = max(limit, (ULONGLONG)minLimit); - limit = min(limit, (ULONGLONG)ThreadpoolMgr::ThreadCounter::MaxPossibleCount); - - _ASSERTE(FitsIn(limit)); - return (DWORD)limit; -} - -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); -} - -BOOL ThreadpoolMgr::Initialize() -{ - CONTRACTL - { - THROWS; - MODE_ANY; - GC_NOTRIGGER; - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - BOOL bRet = FALSE; - BOOL bExceptionCaught = FALSE; - - NumberOfProcessors = GetCurrentProcessCpuCount(); - InitPlatformVariables(); - - EX_TRY - { - if (!UsePortableThreadPool()) - { - WorkerThreadSpinLimit = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit); - IsHillClimbingDisabled = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_Disable) != 0; - ThreadAdjustmentInterval = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow); - - WaitThreadsCriticalSection.Init(CrstThreadpoolWaitThreads); - } - if (!UsePortableThreadPoolForIO()) - { - WorkerCriticalSection.Init(CrstThreadpoolWorker); - } - - if (!UsePortableThreadPool()) - { - // initialize WaitThreadsHead - InitializeListHead(&WaitThreadsHead); - } - - if (!UsePortableThreadPoolForIO()) - { - RetiredCPWakeupEvent = new CLREvent(); - RetiredCPWakeupEvent->CreateAutoEvent(FALSE); - _ASSERTE(RetiredCPWakeupEvent->IsValid()); - } - - if (!UsePortableThreadPool()) - { - WorkerSemaphore = new CLRLifoSemaphore(); - WorkerSemaphore->Create(0, ThreadCounter::MaxPossibleCount); - - RetiredWorkerSemaphore = new CLRLifoSemaphore(); - RetiredWorkerSemaphore->Create(0, ThreadCounter::MaxPossibleCount); - } - -#ifndef TARGET_UNIX - //ThreadPool_CPUGroup - if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) - RecycledLists.Initialize( CPUGroupInfo::GetNumActiveProcessors() ); - else - RecycledLists.Initialize( g_SystemInfo.dwNumberOfProcessors ); -#else // !TARGET_UNIX - RecycledLists.Initialize( PAL_GetTotalCpuCount() ); -#endif // !TARGET_UNIX - } - EX_CATCH - { - if (!UsePortableThreadPoolForIO() && RetiredCPWakeupEvent) - { - delete RetiredCPWakeupEvent; - RetiredCPWakeupEvent = NULL; - } - - // Note: It is fine to call Destroy on uninitialized critical sections - if (!UsePortableThreadPool()) - { - WaitThreadsCriticalSection.Destroy(); - } - if (!UsePortableThreadPoolForIO()) - { - WorkerCriticalSection.Destroy(); - } - - bExceptionCaught = TRUE; - } - EX_END_CATCH(SwallowAllExceptions); - - if (bExceptionCaught) - { - goto end; - } - - 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); - - ThreadCounter::Counts counts; - counts.NumActive = 0; - counts.NumWorking = 0; - counts.NumRetired = 0; - counts.MaxWorking = MinLimitTotalWorkerThreads; - WorkerCounter.counts.AsLongLong = counts.AsLongLong; - } - - if (!UsePortableThreadPoolForIO()) - { - // initialize CP thread settings - MinLimitTotalCPThreads = NumberOfProcessors; - - // 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; - counts.MaxWorking = MinLimitTotalCPThreads; - CPThreadCounter.counts.AsLongLong = counts.AsLongLong; - -#ifndef TARGET_UNIX - { - GlobalCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, - NULL, - 0, /*ignored for invalid handle value*/ - NumberOfProcessors); - } -#endif // !TARGET_UNIX - } - - if (!UsePortableThreadPool()) - { - HillClimbingInstance.Initialize(); - } - - bRet = TRUE; -end: - return bRet; -} - -void ThreadpoolMgr::InitPlatformVariables() -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - -#ifndef TARGET_UNIX - HINSTANCE hNtDll; - HINSTANCE hCoreSynch = nullptr; - { - CONTRACT_VIOLATION(GCViolation|FaultViolation); - hNtDll = CLRLoadLibrary(W("ntdll.dll")); - _ASSERTE(hNtDll); - if (!UsePortableThreadPool()) - { - hCoreSynch = CLRLoadLibrary(W("api-ms-win-core-synch-l1-1-0.dll")); - _ASSERTE(hCoreSynch); - } - } - - // These APIs must be accessed via dynamic binding since they may be removed in future - // OS versions. - g_pufnNtQueryInformationThread = (NtQueryInformationThreadProc)GetProcAddress(hNtDll,"NtQueryInformationThread"); - g_pufnNtQuerySystemInformation = (NtQuerySystemInformationProc)GetProcAddress(hNtDll,"NtQuerySystemInformation"); - - if (!UsePortableThreadPool()) - { - // These APIs are only supported on newer Windows versions - g_pufnCreateWaitableTimerEx = (CreateWaitableTimerExProc)GetProcAddress(hCoreSynch, "CreateWaitableTimerExW"); - g_pufnSetWaitableTimerEx = (SetWaitableTimerExProc)GetProcAddress(hCoreSynch, "SetWaitableTimerEx"); - } -#endif -} - -bool ThreadpoolMgr::CanSetMinIOCompletionThreads(DWORD ioCompletionThreads) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(UsePortableThreadPool()); - _ASSERTE(!UsePortableThreadPoolForIO()); - - 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(!UsePortableThreadPoolForIO()); - _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) -{ - CONTRACTL - { - THROWS; // Crst can throw and toggle GC mode - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - BOOL result = FALSE; - - // 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); - - bool usePortableThreadPool = UsePortableThreadPool(); - if (( - usePortableThreadPool || - ( - MaxWorkerThreads >= (DWORD)MinLimitTotalWorkerThreads && - MaxWorkerThreads != 0 - ) - ) && - MaxIOCompletionThreads >= (DWORD)MinLimitTotalCPThreads && - MaxIOCompletionThreads != 0) - { - if (!usePortableThreadPool && GetForceMaxWorkerThreadsValue() == 0) - { - MaxLimitTotalWorkerThreads = min(MaxWorkerThreads, (DWORD)ThreadCounter::MaxPossibleCount); - - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - while (counts.MaxWorking > MaxLimitTotalWorkerThreads) - { - ThreadCounter::Counts newCounts = counts; - newCounts.MaxWorking = MaxLimitTotalWorkerThreads; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - if (oldCounts == counts) - counts = newCounts; - else - counts = oldCounts; - } - } - - MaxLimitTotalCPThreads = min(MaxIOCompletionThreads, (DWORD)ThreadCounter::MaxPossibleCount); - - result = TRUE; - } - - return result; - } - -/************************************************************************/ -BOOL ThreadpoolMgr::SetMaxThreads(DWORD MaxWorkerThreads, - DWORD MaxIOCompletionThreads) -{ - CONTRACTL - { - THROWS; // SetMaxThreadsHelper can throw and toggle GC mode - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - EnsureInitialized(); - - return SetMaxThreadsHelper(MaxWorkerThreads, MaxIOCompletionThreads); -} - -BOOL ThreadpoolMgr::GetMaxThreads(DWORD* MaxWorkerThreads, - DWORD* MaxIOCompletionThreads) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (!MaxWorkerThreads || !MaxIOCompletionThreads) - { - SetLastHRError(ERROR_INVALID_DATA); - return FALSE; - } - - EnsureInitialized(); - - *MaxWorkerThreads = UsePortableThreadPool() ? 1 : (DWORD)MaxLimitTotalWorkerThreads; - *MaxIOCompletionThreads = MaxLimitTotalCPThreads; - return TRUE; -} - -BOOL ThreadpoolMgr::SetMinThreads(DWORD MinWorkerThreads, - DWORD MinIOCompletionThreads) -{ - CONTRACTL - { - THROWS; // Crst can throw and toggle GC mode - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - EnsureInitialized(); - - // 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); - - BOOL init_result = FALSE; - - bool usePortableThreadPool = UsePortableThreadPool(); - if (( - usePortableThreadPool || - ( - MinWorkerThreads >= 0 && - MinWorkerThreads <= (DWORD) MaxLimitTotalWorkerThreads - ) - ) && - MinIOCompletionThreads >= 0 && - MinIOCompletionThreads <= (DWORD) MaxLimitTotalCPThreads) - { - if (!usePortableThreadPool && GetForceMinWorkerThreadsValue() == 0) - { - MinLimitTotalWorkerThreads = max(1, min(MinWorkerThreads, (DWORD)ThreadCounter::MaxPossibleCount)); - - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - while (counts.MaxWorking < MinLimitTotalWorkerThreads) - { - ThreadCounter::Counts newCounts = counts; - newCounts.MaxWorking = MinLimitTotalWorkerThreads; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - if (oldCounts == counts) - { - counts = newCounts; - - // if we increased the limit, and there are pending workitems, we need - // to dispatch a thread to process the work. - if (newCounts.MaxWorking > oldCounts.MaxWorking && - PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains()) - { - MaybeAddWorkingWorker(); - } - } - else - { - counts = oldCounts; - } - } - } - - MinLimitTotalCPThreads = max(1, min(MinIOCompletionThreads, (DWORD)ThreadCounter::MaxPossibleCount)); - - init_result = TRUE; - } - - return init_result; -} - -BOOL ThreadpoolMgr::GetMinThreads(DWORD* MinWorkerThreads, - DWORD* MinIOCompletionThreads) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (!MinWorkerThreads || !MinIOCompletionThreads) - { - SetLastHRError(ERROR_INVALID_DATA); - return FALSE; - } - - EnsureInitialized(); - - *MinWorkerThreads = UsePortableThreadPool() ? 1 : (DWORD)MinLimitTotalWorkerThreads; - *MinIOCompletionThreads = MinLimitTotalCPThreads; - return TRUE; -} - -BOOL ThreadpoolMgr::GetAvailableThreads(DWORD* AvailableWorkerThreads, - DWORD* AvailableIOCompletionThreads) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (!AvailableWorkerThreads || !AvailableIOCompletionThreads) - { - SetLastHRError(ERROR_INVALID_DATA); - return FALSE; - } - - EnsureInitialized(); - - if (UsePortableThreadPool()) - { - *AvailableWorkerThreads = 0; - } - else - { - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - - if (MaxLimitTotalWorkerThreads < counts.NumActive) - *AvailableWorkerThreads = 0; - else - *AvailableWorkerThreads = MaxLimitTotalWorkerThreads - counts.NumWorking; - } - - ThreadCounter::Counts counts = CPThreadCounter.GetCleanCounts(); - if (MaxLimitTotalCPThreads < counts.NumActive) - *AvailableIOCompletionThreads = counts.NumActive - counts.NumWorking; - else - *AvailableIOCompletionThreads = MaxLimitTotalCPThreads - counts.NumWorking; - return TRUE; -} - -INT32 ThreadpoolMgr::GetThreadCount() -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (!IsInitialized()) - { - return 0; - } - - INT32 workerThreadCount = UsePortableThreadPool() ? 0 : WorkerCounter.DangerousGetDirtyCounts().NumActive; - return workerThreadCount + CPThreadCounter.DangerousGetDirtyCounts().NumActive; -} - -void QueueUserWorkItemHelp(LPTHREAD_START_ROUTINE Function, PVOID Context) -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_ANY; - /* Cannot use contract here because of SEH - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END;*/ - - _ASSERTE(!ThreadpoolMgr::UsePortableThreadPool()); - - Function(Context); - - Thread *pThread = GetThreadNULLOk(); - if (pThread) - { - _ASSERTE(!pThread->IsAbortRequested()); - pThread->InternalReset(); - } -} - -// -// WorkingThreadCounts tracks the number of worker threads currently doing user work, and the maximum number of such threads -// since the last time TakeMaxWorkingThreadCount was called. This information is for diagnostic purposes only, -// and is tracked only if the CLR config value INTERNAL_ThreadPool_EnableWorkerTracking is non-zero (this feature is off -// by default). -// -union WorkingThreadCounts -{ - struct - { - int currentWorking : 16; - int maxWorking : 16; - }; - - LONG asLong; -}; - -WorkingThreadCounts g_workingThreadCounts; - -// -// If worker tracking is enabled (see above) then this is called immediately before and after a worker thread executes -// each work item. -// -void ThreadpoolMgr::ReportThreadStatus(bool isWorking) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(IsInitialized()); // can't be here without requesting a thread first - _ASSERTE(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking)); - - while (true) - { - WorkingThreadCounts currentCounts, newCounts; - currentCounts.asLong = VolatileLoad(&g_workingThreadCounts.asLong); - - newCounts = currentCounts; - - if (isWorking) - newCounts.currentWorking++; - - if (newCounts.currentWorking > newCounts.maxWorking) - newCounts.maxWorking = newCounts.currentWorking; - - if (!isWorking) - newCounts.currentWorking--; - - if (currentCounts.asLong == InterlockedCompareExchange(&g_workingThreadCounts.asLong, newCounts.asLong, currentCounts.asLong)) - break; - } -} - -// -// Returns the max working count since the previous call to TakeMaxWorkingThreadCount, and resets WorkingThreadCounts.maxWorking. -// -int TakeMaxWorkingThreadCount() -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - _ASSERTE(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking)); - while (true) - { - WorkingThreadCounts currentCounts, newCounts; - currentCounts.asLong = VolatileLoad(&g_workingThreadCounts.asLong); - - newCounts = currentCounts; - newCounts.maxWorking = 0; - - if (currentCounts.asLong == InterlockedCompareExchange(&g_workingThreadCounts.asLong, newCounts.asLong, currentCounts.asLong)) - { - // If we haven't updated the counts since the last call to TakeMaxWorkingThreadCount, then we never updated maxWorking. - // In that case, the number of working threads for the whole period since the last TakeMaxWorkingThreadCount is the - // current number of working threads. - return currentCounts.maxWorking == 0 ? currentCounts.currentWorking : currentCounts.maxWorking; - } - } -} - - -/************************************************************************/ - -BOOL ThreadpoolMgr::QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, - PVOID Context, - DWORD Flags, - BOOL UnmanagedTPRequest) -{ - CONTRACTL - { - THROWS; // EnsureInitialized, EnqueueWorkRequest can throw OOM - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE_ALL_BUILDS(!UsePortableThreadPool()); - - EnsureInitialized(); - - - if (Flags == CALL_OR_QUEUE) - { - // we've been asked to call this directly if the thread pressure is not too high - - int MinimumAvailableCPThreads = (NumberOfProcessors < 3) ? 3 : NumberOfProcessors; - - ThreadCounter::Counts counts = CPThreadCounter.GetCleanCounts(); - if ((MaxLimitTotalCPThreads - counts.NumActive) >= MinimumAvailableCPThreads ) - { - QueueUserWorkItemHelp(Function, Context); - return TRUE; - } - - } - - if (UnmanagedTPRequest) - { - UnManagedPerAppDomainTPCount* pADTPCount; - pADTPCount = PerAppDomainTPCountList::GetUnmanagedTPCount(); - pADTPCount->QueueUnmanagedWorkRequest(Function, Context); - } - else - { - // caller has already registered its TPCount; this call is just to adjust the thread count - } - - return TRUE; -} - - -bool ThreadpoolMgr::ShouldWorkerKeepRunning() -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - // - // Maybe this thread should retire now. Let's see. - // - bool shouldThisThreadKeepRunning = true; - - // Dirty read is OK here; the worst that can happen is that we won't retire this time. In the - // case where we might retire, we have to succeed a CompareExchange, which will have the effect - // of validating this read. - ThreadCounter::Counts counts = WorkerCounter.DangerousGetDirtyCounts(); - while (true) - { - if (counts.NumActive <= counts.MaxWorking) - { - shouldThisThreadKeepRunning = true; - break; - } - - ThreadCounter::Counts newCounts = counts; - newCounts.NumWorking--; - newCounts.NumActive--; - newCounts.NumRetired++; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - - if (oldCounts == counts) - { - shouldThisThreadKeepRunning = false; - break; - } - - counts = oldCounts; - } - - return shouldThisThreadKeepRunning; -} - -DangerousNonHostedSpinLock ThreadpoolMgr::ThreadAdjustmentLock; - - -// -// This method must only be called if ShouldAdjustMaxWorkersActive has returned true, *and* -// ThreadAdjustmentLock is held. -// -void ThreadpoolMgr::AdjustMaxWorkersActive() -{ - CONTRACTL - { - NOTHROW; - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - _ASSERTE(ThreadAdjustmentLock.IsHeld()); - - LARGE_INTEGER startTime = CurrentSampleStartTime; - LARGE_INTEGER endTime; - QueryPerformanceCounter(&endTime); - - static LARGE_INTEGER freq; - if (freq.QuadPart == 0) - QueryPerformanceFrequency(&freq); - - double elapsed = (double)(endTime.QuadPart - startTime.QuadPart) / freq.QuadPart; - - // - // It's possible for the current sample to be reset while we're holding - // ThreadAdjustmentLock. This will result in a very short sample, possibly - // with completely bogus counts. We'll try to detect this by checking the sample - // interval; if it's very short, then we try again later. - // - if (elapsed*1000.0 >= (ThreadAdjustmentInterval/2)) - { - DWORD currentTicks = GetTickCount(); - LONG totalNumCompletions = (LONG)Thread::GetTotalWorkerThreadPoolCompletionCount(); - LONG numCompletions = totalNumCompletions - VolatileLoad(&PriorCompletedWorkRequests); - ThreadCounter::Counts currentCounts = WorkerCounter.GetCleanCounts(); - - int newMax = HillClimbingInstance.Update( - currentCounts.MaxWorking, - elapsed, - numCompletions, - &ThreadAdjustmentInterval); - - while (newMax != currentCounts.MaxWorking) - { - ThreadCounter::Counts newCounts = currentCounts; - newCounts.MaxWorking = newMax; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, currentCounts); - if (oldCounts == currentCounts) - { - // - // 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 retire themselves. - // - if (newMax > oldCounts.MaxWorking) - MaybeAddWorkingWorker(); - - break; - } - else - { - // we failed - maybe try again - if (oldCounts.MaxWorking > currentCounts.MaxWorking && - oldCounts.MaxWorking >= newMax) - { - // someone (probably the gate thread) increased the thread count more than - // we are about to do. Don't interfere. - break; - } - - currentCounts = oldCounts; - } - } - - PriorCompletedWorkRequests = totalNumCompletions; - NextCompletedWorkRequestsTime = currentTicks + ThreadAdjustmentInterval; - MemoryBarrier(); // flush previous writes (especially NextCompletedWorkRequestsTime) - PriorCompletedWorkRequestsTime = currentTicks; - CurrentSampleStartTime = endTime;; - } -} - - -void ThreadpoolMgr::MaybeAddWorkingWorker() -{ - CONTRACTL - { - NOTHROW; - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - // counts volatile read paired with CompareExchangeCounts loop set - ThreadCounter::Counts counts = WorkerCounter.DangerousGetDirtyCounts(); - ThreadCounter::Counts newCounts; - while (true) - { - newCounts = counts; - newCounts.NumWorking = max(counts.NumWorking, min(counts.NumWorking + 1, counts.MaxWorking)); - newCounts.NumActive = max(counts.NumActive, newCounts.NumWorking); - newCounts.NumRetired = max(0, counts.NumRetired - (newCounts.NumActive - counts.NumActive)); - - if (newCounts == counts) - return; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - - if (oldCounts == counts) - break; - - counts = oldCounts; - } - - int toUnretire = counts.NumRetired - newCounts.NumRetired; - int toCreate = (newCounts.NumActive - counts.NumActive) - toUnretire; - int toRelease = (newCounts.NumWorking - counts.NumWorking) - (toUnretire + toCreate); - - _ASSERTE(toUnretire >= 0); - _ASSERTE(toCreate >= 0); - _ASSERTE(toRelease >= 0); - _ASSERTE(toUnretire + toCreate + toRelease <= 1); - - if (toUnretire > 0) - { - RetiredWorkerSemaphore->Release(toUnretire); - } - - if (toRelease > 0) - WorkerSemaphore->Release(toRelease); - - while (toCreate > 0) - { - if (CreateWorkerThread()) - { - toCreate--; - } - else - { - // - // Uh-oh, we promised to create a new thread, but the creation failed. We have to renege on our - // promise. This may possibly result in no work getting done for a while, but the gate thread will - // eventually notice that no completions are happening and force the creation of a new thread. - // Of course, there's no guarantee *that* will work - but hopefully enough time will have passed - // to allow whoever's using all the memory right now to release some. - // - - // counts volatile read paired with CompareExchangeCounts loop set - counts = WorkerCounter.DangerousGetDirtyCounts(); - while (true) - { - // - // If we said we would create a thread, we also said it would be working. So we need to - // decrement both NumWorking and NumActive by the number of threads we will no longer be creating. - // - newCounts = counts; - newCounts.NumWorking -= toCreate; - newCounts.NumActive -= toCreate; - - ThreadCounter::Counts oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - - if (oldCounts == counts) - break; - - counts = oldCounts; - } - - toCreate = 0; - } - } -} - -BOOL ThreadpoolMgr::PostQueuedCompletionStatus(LPOVERLAPPED lpOverlapped, - LPOVERLAPPED_COMPLETION_ROUTINE Function) -{ - CONTRACTL - { - THROWS; // EnsureInitialized can throw OOM - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - -#ifndef TARGET_UNIX - EnsureInitialized(); - - _ASSERTE(GlobalCompletionPort != NULL); - - if (!InitCompletionPortThreadpool) - InitCompletionPortThreadpool = TRUE; - - GrowCompletionPortThreadpoolIfNeeded(); - - // In order to allow external ETW listeners to correlate activities that use our IO completion port - // as a dispatch mechanism, we have to ensure the runtime's calls to ::PostQueuedCompletionStatus - // and ::GetQueuedCompletionStatus are "annotated" with ETW events representing to operations - // performed. - // There are currently 2 codepaths that post to the GlobalCompletionPort: - // 1. the managed API ThreadPool.UnsafeQueueNativeOverlapped(), calling CorPostQueuedCompletionStatus() - // which already fires the ETW event as needed - // 2. the managed API ThreadPool.RegisterWaitForSingleObject which needs to fire the ETW event - // at the time the managed API is called (on the orignial user thread), and not when the ::PQCS - // is called (from the dedicated wait thread). - // If additional codepaths appear they need to either fire the ETW event before calling this or ensure - // we do not fire an unmatched "dequeue" event in ThreadpoolMgr::CompletionPortThreadStart - // The current possible values for Function: - // - BindIoCompletionCallbackStub for ThreadPool.UnsafeQueueNativeOverlapped - // - WaitIOCompletionCallback for ThreadPool.RegisterWaitForSingleObject - - return ::PostQueuedCompletionStatus(GlobalCompletionPort, - 0, - (ULONG_PTR) Function, - lpOverlapped); -#else - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; -#endif // !TARGET_UNIX -} - - -void ThreadpoolMgr::WaitIOCompletionCallback( - DWORD dwErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped) -{ - CONTRACTL - { - THROWS; - MODE_ANY; - } - CONTRACTL_END; - - if (dwErrorCode == ERROR_SUCCESS) - DWORD ret = AsyncCallbackCompletion((PVOID)lpOverlapped); -} - -extern void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped); - - -// This is either made by a worker thread or a CP thread -// indicated by threadTypeStatus -void ThreadpoolMgr::EnsureGateThreadRunning() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (UsePortableThreadPool()) - { - GCX_COOP(); - MethodDescCallSite(METHOD__THREAD_POOL__ENSURE_GATE_THREAD_RUNNING).Call(NULL); - return; - } - - while (true) - { - switch (GateThreadStatus) - { - case GATE_THREAD_STATUS_REQUESTED: - // - // No action needed; the gate thread is running, and someone else has already registered a request - // for it to stay. - // - return; - - case GATE_THREAD_STATUS_WAITING_FOR_REQUEST: - // - // Prevent the gate thread from exiting, if it hasn't already done so. If it has, we'll create it on the next iteration of - // this loop. - // - InterlockedCompareExchange(&GateThreadStatus, GATE_THREAD_STATUS_REQUESTED, GATE_THREAD_STATUS_WAITING_FOR_REQUEST); - break; - - case GATE_THREAD_STATUS_NOT_RUNNING: - // - // We need to create a new gate thread - // - if (InterlockedCompareExchange(&GateThreadStatus, GATE_THREAD_STATUS_REQUESTED, GATE_THREAD_STATUS_NOT_RUNNING) == GATE_THREAD_STATUS_NOT_RUNNING) - { - if (!CreateGateThread()) - { - // - // If we failed to create the gate thread, someone else will need to try again later. - // - GateThreadStatus = GATE_THREAD_STATUS_NOT_RUNNING; - } - return; - } - break; - - default: - _ASSERTE(!"Invalid value of ThreadpoolMgr::GateThreadStatus"); - } - } -} - -bool ThreadpoolMgr::NeedGateThreadForIOCompletions() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPoolForIO()); - - 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); - - // - // Switch to WAITING_FOR_REQUEST, and see if we had a request since the last check. - // - LONG previousStatus = InterlockedExchange(&GateThreadStatus, GATE_THREAD_STATUS_WAITING_FOR_REQUEST); - - if (previousStatus == GATE_THREAD_STATUS_WAITING_FOR_REQUEST) - { - // - // No recent requests for the gate thread. Check to see if we're still needed. - // - - // - // 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(). - // - 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 = PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains(); - - // - // If worker tracking is enabled, we need to fire periodic ETW events with active worker counts. This is - // done by the gate thread. - // We don't have to do anything special with EnsureGateThreadRunning() here, because this is only needed - // once work has been added to the queue for the first time (which is covered above). - // - bool needGateThreadForWorkerTracking = - 0 != CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking); - - if (!(needGateThreadForCompletionPort || - needGateThreadForWorkerThreads || - needGateThreadForWorkerTracking)) - { - // - // It looks like we shouldn't be running. But another thread may now tell us to run. If so, they will set GateThreadStatus - // back to GATE_THREAD_STATUS_REQUESTED. - // - previousStatus = InterlockedCompareExchange(&GateThreadStatus, GATE_THREAD_STATUS_NOT_RUNNING, GATE_THREAD_STATUS_WAITING_FOR_REQUEST); - if (previousStatus == GATE_THREAD_STATUS_WAITING_FOR_REQUEST) - return false; - } - } - - - _ASSERTE(GateThreadStatus == GATE_THREAD_STATUS_WAITING_FOR_REQUEST || - GateThreadStatus == GATE_THREAD_STATUS_REQUESTED); - return true; -} - - - -//************************************************************************ -void ThreadpoolMgr::EnqueueWorkRequest(WorkRequest* workRequest) -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - AppendWorkRequest(workRequest); -} - -WorkRequest* ThreadpoolMgr::DequeueWorkRequest() -{ - WorkRequest* entry = NULL; - CONTRACT(WorkRequest*) - { - NOTHROW; - GC_NOTRIGGER; - MODE_PREEMPTIVE; - - POSTCONDITION(CheckPointer(entry, NULL_OK)); - } CONTRACT_END; - - _ASSERTE(!UsePortableThreadPool()); - - entry = RemoveWorkRequest(); - - RETURN entry; -} - -void ThreadpoolMgr::ExecuteWorkRequest(bool* foundWork, bool* wasNotRecalled) -{ - CONTRACTL - { - THROWS; // QueueUserWorkItem can throw - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - IPerAppDomainTPCount* pAdCount; - - LONG index = PerAppDomainTPCountList::GetAppDomainIndexForThreadpoolDispatch(); - - if (index == 0) - { - *foundWork = false; - *wasNotRecalled = true; - return; - } - - if (index == -1) - { - pAdCount = PerAppDomainTPCountList::GetUnmanagedTPCount(); - } - else - { - - pAdCount = PerAppDomainTPCountList::GetPerAppdomainCount(TPIndex((DWORD)index)); - _ASSERTE(pAdCount); - } - - pAdCount->DispatchWorkItem(foundWork, wasNotRecalled); -} - -//-------------------------------------------------------------------------- -//This function informs the thread scheduler that the first requests has been -//queued on an appdomain, or it's the first unmanaged TP request. -//Arguments: -// UnmanagedTP: Indicates that the request arises from the unmanaged -//part of Thread Pool. -//Assumptions: -// This function must be called under a per-appdomain lock or the -//correct lock under unmanaged TP queue. -// -BOOL ThreadpoolMgr::SetAppDomainRequestsActive(BOOL UnmanagedTP) -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - BOOL fShouldSignalEvent = FALSE; - - IPerAppDomainTPCount* pAdCount; - - if(UnmanagedTP) - { - pAdCount = PerAppDomainTPCountList::GetUnmanagedTPCount(); - _ASSERTE(pAdCount); - } - else - { - Thread* pCurThread = GetThread(); - AppDomain* pAppDomain = pCurThread->GetDomain(); - _ASSERTE(pAppDomain); - - TPIndex tpindex = pAppDomain->GetTPIndex(); - pAdCount = PerAppDomainTPCountList::GetPerAppdomainCount(tpindex); - - _ASSERTE(pAdCount); - } - - pAdCount->SetAppDomainRequestsActive(); - - return fShouldSignalEvent; -} - -void ThreadpoolMgr::ClearAppDomainRequestsActive(BOOL UnmanagedTP, LONG id) -//-------------------------------------------------------------------------- -//This function informs the thread scheduler that the kast request has been -//dequeued on an appdomain, or it's the last unmanaged TP request. -//Arguments: -// UnmanagedTP: Indicates that the request arises from the unmanaged -//part of Thread Pool. -// id: Indicates the id of the appdomain. The id is needed as this -//function can be called (indirectly) from the appdomain unload thread from -//unmanaged code to clear per-appdomain state during rude unload. -//Assumptions: -// This function must be called under a per-appdomain lock or the -//correct lock under unmanaged TP queue. -// -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - IPerAppDomainTPCount* pAdCount; - - if(UnmanagedTP) - { - pAdCount = PerAppDomainTPCountList::GetUnmanagedTPCount(); - _ASSERTE(pAdCount); - } - else - { - Thread* pCurThread = GetThread(); - AppDomain* pAppDomain = pCurThread->GetDomain(); - _ASSERTE(pAppDomain); - - TPIndex tpindex = pAppDomain->GetTPIndex(); - - pAdCount = PerAppDomainTPCountList::GetPerAppdomainCount(tpindex); - - _ASSERTE(pAdCount); - } - - pAdCount->ClearAppDomainRequestsActive(); -} - - -// Remove a block from the appropriate recycleList and return. -// If recycleList is empty, fall back to new. -LPVOID ThreadpoolMgr::GetRecycledMemory(enum MemType memType) -{ - LPVOID result = NULL; - CONTRACT(LPVOID) - { - THROWS; - GC_NOTRIGGER; - MODE_ANY; - INJECT_FAULT(COMPlusThrowOM()); - POSTCONDITION(CheckPointer(result)); - } CONTRACT_END; - - if(RecycledLists.IsInitialized()) - { - RecycledListInfo& list = RecycledLists.GetRecycleMemoryInfo( memType ); - - result = list.Remove(); - } - - if(result == NULL) - { - switch (memType) - { - case MEMTYPE_DelegateInfo: - result = new DelegateInfo; - break; - case MEMTYPE_AsyncCallback: - result = new AsyncCallback; - break; - case MEMTYPE_WorkRequest: - result = new WorkRequest; - break; - default: - _ASSERTE(!"Unknown Memtype"); - result = NULL; - break; - } - } - - RETURN result; -} - -// Insert freed block in recycle list. If list is full, return to system heap -void ThreadpoolMgr::RecycleMemory(LPVOID mem, enum MemType memType) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - if(RecycledLists.IsInitialized()) - { - RecycledListInfo& list = RecycledLists.GetRecycleMemoryInfo( memType ); - - if(list.CanInsert()) - { - list.Insert( mem ); - return; - } - } - - switch (memType) - { - case MEMTYPE_DelegateInfo: - delete (DelegateInfo*) mem; - break; - case MEMTYPE_AsyncCallback: - delete (AsyncCallback*) mem; - break; - case MEMTYPE_WorkRequest: - delete (WorkRequest*) mem; - break; - default: - _ASSERTE(!"Unknown Memtype"); - - } -} - -Thread* ThreadpoolMgr::CreateUnimpersonatedThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpArgs, BOOL *pIsCLRThread) -{ - STATIC_CONTRACT_NOTHROW; - if (GetThreadNULLOk()) { STATIC_CONTRACT_GC_TRIGGERS;} else {DISABLED(STATIC_CONTRACT_GC_NOTRIGGER);} - STATIC_CONTRACT_MODE_ANY; - /* cannot use contract because of SEH - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END;*/ - - Thread* pThread = NULL; - - if (g_fEEStarted) { - *pIsCLRThread = TRUE; - } - else - *pIsCLRThread = FALSE; - if (*pIsCLRThread) { - EX_TRY - { - pThread = SetupUnstartedThread(); - } - EX_CATCH - { - pThread = NULL; - } - EX_END_CATCH(SwallowAllExceptions); - if (pThread == NULL) { - return NULL; - } - } - DWORD threadId; - BOOL bOK = FALSE; - HANDLE threadHandle = NULL; - - if (*pIsCLRThread) { - // CreateNewThread takes care of reverting any impersonation - so dont do anything here. - bOK = pThread->CreateNewThread(0, // default stack size - lpStartAddress, - lpArgs, //arguments - W(".NET ThreadPool Worker")); - } - else { -#ifndef TARGET_UNIX - HandleHolder token; - BOOL bReverted = FALSE; - bOK = RevertIfImpersonated(&bReverted, &token); - if (bOK != TRUE) - return NULL; -#endif // !TARGET_UNIX - threadHandle = CreateThread(NULL, // security descriptor - 0, // default stack size - lpStartAddress, - lpArgs, - CREATE_SUSPENDED, - &threadId); - - SetThreadName(threadHandle, W(".NET ThreadPool Worker")); -#ifndef TARGET_UNIX - UndoRevert(bReverted, token); -#endif // !TARGET_UNIX - } - - if (*pIsCLRThread && !bOK) - { - pThread->DecExternalCount(FALSE); - pThread = NULL; - } - - if (*pIsCLRThread) { - return pThread; - } - else - return (Thread*)threadHandle; -} - - -BOOL ThreadpoolMgr::CreateWorkerThread() -{ - CONTRACTL - { - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - NOTHROW; - MODE_ANY; // We may try to add a worker thread while queuing a work item thru an fcall - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - Thread *pThread; - BOOL fIsCLRThread; - if ((pThread = CreateUnimpersonatedThread(WorkerThreadStart, NULL, &fIsCLRThread)) != NULL) - { - if (fIsCLRThread) { - pThread->ChooseThreadCPUGroupAffinity(); - pThread->StartThread(); - } - else { - DWORD status; - status = ResumeThread((HANDLE)pThread); - _ASSERTE(status != (DWORD) (-1)); - CloseHandle((HANDLE)pThread); // we don't need this anymore - } - - return TRUE; - } - - return FALSE; -} - - -DWORD WINAPI ThreadpoolMgr::WorkerThreadStart(LPVOID lpArgs) -{ - ClrFlsSetThreadType (ThreadType_Threadpool_Worker); - - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - _ASSERTE_ALL_BUILDS(!UsePortableThreadPool()); - - Thread *pThread = NULL; - DWORD dwSwitchCount = 0; - BOOL fThreadInit = FALSE; - - ThreadCounter::Counts counts, oldCounts, newCounts; - bool foundWork = true, wasNotRecalled = true; - - counts = WorkerCounter.GetCleanCounts(); - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolWorkerThreadStart)) - FireEtwThreadPoolWorkerThreadStart(counts.NumActive, counts.NumRetired, GetClrInstanceId()); - -#ifdef FEATURE_COMINTEROP - BOOL fCoInited = FALSE; - // Threadpool threads should be initialized as MTA. If we are unable to do so, - // return failure. - { - fCoInited = SUCCEEDED(::CoInitializeEx(NULL, COINIT_MULTITHREADED)); - if (!fCoInited) - { - goto Exit; - } - } -#endif // FEATURE_COMINTEROP -Work: - - if (!fThreadInit) { - if (g_fEEStarted) { - pThread = SetupThreadNoThrow(); - if (pThread == NULL) { - __SwitchToThread(0, ++dwSwitchCount); - goto Work; - } - - // converted to CLRThread and added to ThreadStore, pick an group affinity for this thread - pThread->ChooseThreadCPUGroupAffinity(); - - #ifdef FEATURE_COMINTEROP - if (pThread->SetApartment(Thread::AS_InMTA) != Thread::AS_InMTA) - { - // counts volatile read paired with CompareExchangeCounts loop set - counts = WorkerCounter.DangerousGetDirtyCounts(); - while (true) - { - newCounts = counts; - newCounts.NumActive--; - newCounts.NumWorking--; - oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - if (oldCounts == counts) - break; - counts = oldCounts; - } - goto Exit; - } - #endif // FEATURE_COMINTEROP - - pThread->SetBackground(TRUE); - fThreadInit = TRUE; - } - } - - GCX_PREEMP_NO_DTOR(); - _ASSERTE(pThread == NULL || !pThread->PreemptiveGCDisabled()); - - // make sure there's really work. If not, go back to sleep - - // counts volatile read paired with CompareExchangeCounts loop set - counts = WorkerCounter.DangerousGetDirtyCounts(); - while (true) - { - _ASSERTE(counts.NumActive > 0); - _ASSERTE(counts.NumWorking > 0); - - newCounts = counts; - - bool retired; - - if (counts.NumActive > counts.MaxWorking) - { - newCounts.NumActive--; - newCounts.NumRetired++; - retired = true; - } - else - { - retired = false; - - if (foundWork) - break; - } - - newCounts.NumWorking--; - - oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - - if (oldCounts == counts) - { - if (retired) - goto Retire; - else - goto WaitForWork; - } - - counts = oldCounts; - } - - if (GCHeapUtilities::IsGCInProgress(TRUE)) - { - // GC is imminent, so wait until GC is complete before executing next request. - // this reduces in-flight objects allocated right before GC, easing the GC's work - GCHeapUtilities::WaitForGCCompletion(TRUE); - } - - { - ThreadpoolMgr::UpdateLastDequeueTime(); - ThreadpoolMgr::ExecuteWorkRequest(&foundWork, &wasNotRecalled); - } - - if (foundWork) - { - // Reset TLS etc. for next WorkRequest. - if (pThread == NULL) - pThread = GetThreadNULLOk(); - - if (pThread) - { - _ASSERTE(!pThread->IsAbortRequested()); - pThread->InternalReset(); - } - } - - if (wasNotRecalled) - goto Work; - -Retire: - - counts = WorkerCounter.GetCleanCounts(); - FireEtwThreadPoolWorkerThreadRetirementStart(counts.NumActive, counts.NumRetired, GetClrInstanceId()); - - // It's possible that some work came in just before we decremented the active thread count, in which - // case whoever queued that work may be expecting us to pick it up - so they would not have signalled - // the worker semaphore. If there are other threads waiting, they will never be woken up, because - // whoever queued the work expects that it's already been picked up. The solution is to signal the semaphore - // if there's any work available. - if (PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains()) - MaybeAddWorkingWorker(); - - while (true) - { -RetryRetire: - if (RetiredWorkerSemaphore->Wait(WorkerTimeout)) - { - foundWork = true; - - counts = WorkerCounter.GetCleanCounts(); - FireEtwThreadPoolWorkerThreadRetirementStop(counts.NumActive, counts.NumRetired, GetClrInstanceId()); - goto Work; - } - - if (!IsIoPending()) - { - // - // We're going to exit. There's a nasty race here. We're about to decrement NumRetired, - // since we're going to exit. Once we've done that, nobody will expect this thread - // to be waiting for RetiredWorkerSemaphore. But between now and then, other threads still - // think we're waiting on the semaphore, and they will happily do the following to try to - // wake us up: - // - // 1) Decrement NumRetired - // 2) Increment NumActive - // 3) Increment NumWorking - // 4) Signal RetiredWorkerSemaphore - // - // We will not receive that signal. If we don't do something special here, - // we will decrement NumRetired an extra time, and leave the world thinking there - // are fewer retired threads, and more working threads than reality. - // - // What can we do about this? First, we *need* to decrement NumRetired. If someone did it before us, - // it might go negative. This is the easiest way to tell that we've encountered this race. In that case, - // we will simply not commit the decrement, swallow the signal that was sent, and proceed as if we - // got WAIT_OBJECT_0 in the wait above. - // - // If we don't hit zero while decrementing NumRetired, we still may have encountered this race. But - // if we don't hit zero, then there's another retired thread that will pick up this signal. So it's ok - // to exit. - // - - // counts volatile read paired with CompareExchangeCounts loop set - counts = WorkerCounter.DangerousGetDirtyCounts(); - while (true) - { - if (counts.NumRetired == 0) - goto RetryRetire; - - newCounts = counts; - newCounts.NumRetired--; - - oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - if (oldCounts == counts) - { - counts = newCounts; - break; - } - counts = oldCounts; - } - - FireEtwThreadPoolWorkerThreadRetirementStop(counts.NumActive, counts.NumRetired, GetClrInstanceId()); - goto Exit; - } - } - -WaitForWork: - - // It's possible that we decided we had no work just before some work came in, - // but reduced the worker count *after* the work came in. In this case, we might - // miss the notification of available work. So we make a sweep through the ADs here, - // and wake up a thread (maybe this one!) if there is work to do. - if (PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains()) - { - foundWork = true; - MaybeAddWorkingWorker(); - } - - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolWorkerThreadWait)) - FireEtwThreadPoolWorkerThreadWait(counts.NumActive, counts.NumRetired, GetClrInstanceId()); - -RetryWaitForWork: - if (WorkerSemaphore->Wait(WorkerTimeout, WorkerThreadSpinLimit, NumberOfProcessors)) - { - foundWork = true; - goto Work; - } - - if (!IsIoPending()) - { - // - // We timed out, and are about to exit. This puts us in a very similar situation to the - // retirement case above - someone may think we're still waiting, and go ahead and: - // - // 1) Increment NumWorking - // 2) Signal WorkerSemaphore - // - // The solution is much like retirement; when we're decrementing NumActive, we need to make - // sure it doesn't drop below NumWorking. If it would, then we need to go back and wait - // again. - // - - DangerousNonHostedSpinLockHolder tal(&ThreadAdjustmentLock); - - // counts volatile read paired with CompareExchangeCounts loop set - counts = WorkerCounter.DangerousGetDirtyCounts(); - while (true) - { - if (counts.NumActive == counts.NumWorking) - { - goto RetryWaitForWork; - } - - newCounts = counts; - newCounts.NumActive--; - - // if we timed out while active, then Hill Climbing needs to be told that we need fewer threads - newCounts.MaxWorking = max(MinLimitTotalWorkerThreads, min(newCounts.NumActive, newCounts.MaxWorking)); - - oldCounts = WorkerCounter.CompareExchangeCounts(newCounts, counts); - - if (oldCounts == counts) - { - HillClimbingInstance.ForceChange(newCounts.MaxWorking, ThreadTimedOut); - goto Exit; - } - - counts = oldCounts; - } - } - else - { - goto RetryWaitForWork; - } - -Exit: - -#ifdef FEATURE_COMINTEROP - if (pThread) { - pThread->SetApartment(Thread::AS_Unknown); - pThread->CoUninitialize(); - } - - // Couninit the worker thread - if (fCoInited) - { - CoUninitialize(); - } -#endif - - if (pThread) { - pThread->ClearThreadCPUGroupAffinity(); - - DestroyThread(pThread); - } - - _ASSERTE(!IsIoPending()); - - counts = WorkerCounter.GetCleanCounts(); - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolWorkerThreadStop)) - FireEtwThreadPoolWorkerThreadStop(counts.NumActive, counts.NumRetired, GetClrInstanceId()); - - return ERROR_SUCCESS; -} - -// 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) -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_MODE_PREEMPTIVE; - /* cannot use contract because of SEH - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_PREEMPTIVE; - } - CONTRACTL_END;*/ - - DWORD status = WAIT_TIMEOUT; - EX_TRY - { - status = ev->Wait(sleepTime,FALSE); - } - EX_CATCH - { - } - EX_END_CATCH(SwallowAllExceptions) - return status; -} - -/************************************************************************/ - -BOOL ThreadpoolMgr::RegisterWaitForSingleObject(PHANDLE phNewWaitObject, - HANDLE hWaitObject, - WAITORTIMERCALLBACK Callback, - PVOID Context, - ULONG timeout, - DWORD dwFlag ) -{ - CONTRACTL - { - THROWS; - MODE_ANY; - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - EnsureInitialized(); - - ThreadCB* threadCB; - { - CrstHolder csh(&WaitThreadsCriticalSection); - - threadCB = FindWaitThread(); - } - - *phNewWaitObject = NULL; - - if (threadCB) - { - WaitInfo* waitInfo = new (nothrow) WaitInfo; - - if (waitInfo == NULL) - return FALSE; - - waitInfo->waitHandle = hWaitObject; - waitInfo->Callback = Callback; - waitInfo->Context = Context; - waitInfo->timeout = timeout; - waitInfo->flag = dwFlag; - waitInfo->threadCB = threadCB; - waitInfo->state = 0; - waitInfo->refCount = 1; // safe to do this since no wait has yet been queued, so no other thread could be modifying this - waitInfo->ExternalCompletionEvent = INVALID_HANDLE; - waitInfo->ExternalEventSafeHandle = NULL; - - waitInfo->timer.startTime = GetTickCount(); - waitInfo->timer.remainingTime = timeout; - - *phNewWaitObject = waitInfo; - - // We fire the "enqueue" ETW event here, to "mark" the thread that had called the API, rather than the - // thread that will PostQueuedCompletionStatus (the dedicated WaitThread). - // This event correlates with ThreadPoolIODequeue in ThreadpoolMgr::AsyncCallbackCompletion - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIOEnqueue)) - FireEtwThreadPoolIOEnqueue((LPOVERLAPPED)waitInfo, reinterpret_cast(Callback), (dwFlag & WAIT_SINGLE_EXECUTION) == 0, GetClrInstanceId()); - - BOOL status = QueueUserAPC((PAPCFUNC)InsertNewWaitForSelf, threadCB->threadHandle, (size_t) waitInfo); - - if (status == FALSE) - { - *phNewWaitObject = NULL; - delete waitInfo; - } - - return status; - } - - return FALSE; -} - - -// Returns a wait thread that can accommodate another wait request. The -// caller is responsible for synchronizing access to the WaitThreadsHead -ThreadpoolMgr::ThreadCB* ThreadpoolMgr::FindWaitThread() -{ - CONTRACTL - { - THROWS; // CreateWaitThread can throw - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - do - { - for (LIST_ENTRY* Node = (LIST_ENTRY*) WaitThreadsHead.Flink ; - Node != &WaitThreadsHead ; - Node = (LIST_ENTRY*)Node->Flink) - { - _ASSERTE(offsetof(WaitThreadInfo,link) == 0); - - ThreadCB* threadCB = ((WaitThreadInfo*) Node)->threadCB; - - if (threadCB->NumWaitHandles < MAX_WAITHANDLES) // this test and following ... - - { - InterlockedIncrement(&threadCB->NumWaitHandles); // ... increment are protected by WaitThreadsCriticalSection. - // but there might be a concurrent decrement in DeactivateWait - // or InsertNewWaitForSelf, hence the interlock - return threadCB; - } - } - - // if reached here, there are no wait threads available, so need to create a new one - if (!CreateWaitThread()) - return NULL; - - - // Now loop back - } while (TRUE); - -} - -BOOL ThreadpoolMgr::CreateWaitThread() -{ - CONTRACTL - { - THROWS; // CLREvent::CreateAutoEvent can throw OOM - GC_TRIGGERS; - MODE_ANY; - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - DWORD threadId; - - if (g_fEEShutDown & ShutDown_Finalize2){ - // The process is shutting down. Shutdown thread has ThreadStore lock, - // wait thread is blocked on the lock. - return FALSE; - } - - NewHolder waitThreadInfo(new (nothrow) WaitThreadInfo); - if (waitThreadInfo == NULL) - return FALSE; - - NewHolder threadCB(new (nothrow) ThreadCB); - - if (threadCB == NULL) - { - return FALSE; - } - - threadCB->startEvent.CreateAutoEvent(FALSE); - HANDLE threadHandle = Thread::CreateUtilityThread(Thread::StackSize_Small, WaitThreadStart, (LPVOID)threadCB, W(".NET ThreadPool Wait"), CREATE_SUSPENDED, &threadId); - - if (threadHandle == NULL) - { - threadCB->startEvent.CloseEvent(); - return FALSE; - } - - waitThreadInfo.SuppressRelease(); - threadCB.SuppressRelease(); - threadCB->threadHandle = threadHandle; - threadCB->threadId = threadId; // may be useful for debugging otherwise not used - threadCB->NumWaitHandles = 0; - threadCB->NumActiveWaits = 0; - for (int i=0; i< MAX_WAITHANDLES; i++) - { - InitializeListHead(&(threadCB->waitPointer[i])); - } - - waitThreadInfo->threadCB = threadCB; - - DWORD status = ResumeThread(threadHandle); - - { - // We will QueueUserAPC on the newly created thread. - // Let us wait until the thread starts running. - GCX_PREEMP(); - DWORD timeout=500; - while (TRUE) { - if (g_fEEShutDown & ShutDown_Finalize2){ - // The process is shutting down. Shutdown thread has ThreadStore lock, - // wait thread is blocked on the lock. - return FALSE; - } - DWORD wait_status = threadCB->startEvent.Wait(timeout, FALSE); - if (wait_status == WAIT_OBJECT_0) { - break; - } - } - } - threadCB->startEvent.CloseEvent(); - - // check to see if setup succeeded - if (threadCB->threadHandle == NULL) - return FALSE; - - InsertHeadList(&WaitThreadsHead,&waitThreadInfo->link); - - _ASSERTE(status != (DWORD) (-1)); - - return (status != (DWORD) (-1)); - -} - -// Executed as an APC on a WaitThread. Add the wait specified in pArg to the list of objects it is waiting on -void ThreadpoolMgr::InsertNewWaitForSelf(WaitInfo* pArgs) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - WaitInfo* waitInfo = pArgs; - - // the following is safe since only this thread is allowed to change the state - if (!(waitInfo->state & WAIT_DELETE)) - { - waitInfo->state = (WAIT_REGISTERED | WAIT_ACTIVE); - } - else - { - // some thread unregistered the wait - DeleteWait(waitInfo); - return; - } - - - ThreadCB* threadCB = waitInfo->threadCB; - - _ASSERTE(threadCB->NumActiveWaits <= threadCB->NumWaitHandles); - - int index = FindWaitIndex(threadCB, waitInfo->waitHandle); - _ASSERTE(index >= 0 && index <= threadCB->NumActiveWaits); - - if (index == threadCB->NumActiveWaits) - { - threadCB->waitHandle[threadCB->NumActiveWaits] = waitInfo->waitHandle; - threadCB->NumActiveWaits++; - } - else - { - // this is a duplicate waithandle, so the increment in FindWaitThread - // wasn't strictly necessary. This will avoid unnecessary thread creation. - InterlockedDecrement(&threadCB->NumWaitHandles); - } - - _ASSERTE(offsetof(WaitInfo, link) == 0); - InsertTailList(&(threadCB->waitPointer[index]), (&waitInfo->link)); - - return; -} - -// returns the index of the entry that matches waitHandle or next free entry if not found -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) - return i; - - // else not found - return threadCB->NumActiveWaits; -} - - -// if no wraparound that the timer is expired if duetime is less than current time -// if wraparound occurred, then the timer expired if dueTime was greater than last time or dueTime is less equal to current time -#define TimeExpired(last,now,duetime) ((last) <= (now) ? \ - ((duetime) <= (now) && (duetime) >= (last)): \ - ((duetime) >= (last) || (duetime) <= (now))) - -#define TimeInterval(end,start) ((end) > (start) ? ((end) - (start)) : ((0xffffffff - (start)) + (end) + 1)) - -// Returns the minimum of the remaining time to reach a timeout among all the waits -DWORD ThreadpoolMgr::MinimumRemainingWait(LIST_ENTRY* waitInfo, unsigned int numWaits) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - unsigned int min = (unsigned int) -1; - DWORD currentTime = GetTickCount(); - - for (unsigned i=0; i < numWaits ; i++) - { - WaitInfo* waitInfoPtr = (WaitInfo*) (waitInfo[i].Flink); - PVOID waitInfoHead = &(waitInfo[i]); - do - { - if (waitInfoPtr->timeout != INFINITE) - { - // compute remaining time - DWORD elapsedTime = TimeInterval(currentTime,waitInfoPtr->timer.startTime ); - - __int64 remainingTime = (__int64) (waitInfoPtr->timeout) - (__int64) elapsedTime; - - // update remaining time - waitInfoPtr->timer.remainingTime = remainingTime > 0 ? (int) remainingTime : 0; - - // ... and min - if (waitInfoPtr->timer.remainingTime < min) - min = waitInfoPtr->timer.remainingTime; - } - - waitInfoPtr = (WaitInfo*) (waitInfoPtr->link.Flink); - - } while ((PVOID) waitInfoPtr != waitInfoHead); - - } - return min; -} - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (disable : 4716) -#else -#pragma warning (disable : 4715) -#endif -#endif -#ifdef _PREFAST_ -#pragma warning(push) -#pragma warning(disable:22008) // "Prefast integer overflow check on (0 + lval) is bogus. Tried local disable without luck, doing whole method." -#endif - -DWORD WINAPI ThreadpoolMgr::WaitThreadStart(LPVOID lpArgs) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - ClrFlsSetThreadType (ThreadType_Wait); - - _ASSERTE_ALL_BUILDS(!UsePortableThreadPool()); - - ThreadCB* threadCB = (ThreadCB*) lpArgs; - Thread* pThread = SetupThreadNoThrow(); - - if (pThread == NULL) - { - _ASSERTE(threadCB->threadHandle != NULL); - threadCB->threadHandle = NULL; - } - - threadCB->startEvent.Set(); - - if (pThread == NULL) - { - return 0; - } - - { - // wait threads never die. (Why?) - for (;;) - { - DWORD status; - DWORD timeout = 0; - - if (threadCB->NumActiveWaits == 0) - { - status = ClrSleepEx(INFINITE,TRUE); - _ASSERTE(status == WAIT_IO_COMPLETION); - } - else if (IsWaitThreadAPCPending()) - { - //Do a sleep if an APC is pending, This was done to solve the corner case where the wait is signaled, - //and APC to deregiter the wait never fires. That scenario leads to an infinite loop. This check would - //allow the thread to enter alertable wait and thus cause the APC to fire. - - ResetWaitThreadAPCPending(); - status = ClrSleepEx(0,TRUE); - continue; - } - else - { - // compute minimum timeout. this call also updates the remainingTime field for each wait - timeout = MinimumRemainingWait(threadCB->waitPointer,threadCB->NumActiveWaits); - - status = WaitForMultipleObjectsEx( threadCB->NumActiveWaits, - threadCB->waitHandle, - FALSE, // waitall - timeout, - TRUE ); // alertable - - _ASSERTE( (status == WAIT_TIMEOUT) || - (status == WAIT_IO_COMPLETION) || - //It could be that there are no waiters at this point, - //as the APC to deregister the wait may have run. - (status == WAIT_OBJECT_0) || - (status >= WAIT_OBJECT_0 && status < (DWORD)(WAIT_OBJECT_0 + threadCB->NumActiveWaits)) || - (status == WAIT_FAILED)); - - //It could be that the last waiter also got deregistered. - if (threadCB->NumActiveWaits == 0) - { - continue; - } - } - - if (status == WAIT_IO_COMPLETION) - continue; - - if (status == WAIT_TIMEOUT) - { - for (int i=0; i< threadCB->NumActiveWaits; i++) - { - WaitInfo* waitInfo = (WaitInfo*) (threadCB->waitPointer[i]).Flink; - PVOID waitInfoHead = &(threadCB->waitPointer[i]); - - do - { - _ASSERTE(waitInfo->timer.remainingTime >= timeout); - - WaitInfo* wTemp = (WaitInfo*) waitInfo->link.Flink; - - if (waitInfo->timer.remainingTime == timeout) - { - ProcessWaitCompletion(waitInfo,i,TRUE); - } - - waitInfo = wTemp; - - } while ((PVOID) waitInfo != waitInfoHead); - } - } - else if (status >= WAIT_OBJECT_0 && status < (DWORD)(WAIT_OBJECT_0 + threadCB->NumActiveWaits)) - { - unsigned index = status - WAIT_OBJECT_0; - WaitInfo* waitInfo = (WaitInfo*) (threadCB->waitPointer[index]).Flink; - PVOID waitInfoHead = &(threadCB->waitPointer[index]); - BOOL isAutoReset; - - // Setting to unconditional TRUE is inefficient since we will re-enter the wait and release - // the next waiter, but short of using undocumented NT apis is the only solution. - // Querying the state with a WaitForSingleObject is not an option as it will reset an - // auto reset event if it has been signalled since the previous wait. - isAutoReset = TRUE; - - do - { - WaitInfo* wTemp = (WaitInfo*) waitInfo->link.Flink; - ProcessWaitCompletion(waitInfo,index,FALSE); - - waitInfo = wTemp; - - } while (((PVOID) waitInfo != waitInfoHead) && !isAutoReset); - - // If an app registers a recurring wait for an event that is always signalled (!), - // then no apc's will be executed since the thread never enters the alertable state. - // This can be fixed by doing the following: - // SleepEx(0,TRUE); - // However, it causes an unnecessary context switch. It is not worth penalizing well - // behaved apps to protect poorly written apps. - - - } - else - { - _ASSERTE(status == WAIT_FAILED); - // wait failed: application error - // find out which wait handle caused the wait to fail - for (int i = 0; i < threadCB->NumActiveWaits; i++) - { - DWORD subRet = WaitForSingleObject(threadCB->waitHandle[i], 0); - - if (subRet != WAIT_FAILED) - continue; - - // remove all waits associated with this wait handle - - WaitInfo* waitInfo = (WaitInfo*) (threadCB->waitPointer[i]).Flink; - PVOID waitInfoHead = &(threadCB->waitPointer[i]); - - do - { - WaitInfo* temp = (WaitInfo*) waitInfo->link.Flink; - - DeactivateNthWait(waitInfo,i); - - - // Note, we cannot cleanup here since there is no way to suppress finalization - // we will just leak, and rely on the finalizer to clean up the memory - //if (InterlockedDecrement(&waitInfo->refCount) == 0) - // DeleteWait(waitInfo); - - - waitInfo = temp; - - } while ((PVOID) waitInfo != waitInfoHead); - - break; - } - } - } - } - - //This is unreachable...so no return required. -} -#ifdef _PREFAST_ -#pragma warning(pop) -#endif - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (default : 4716) -#else -#pragma warning (default : 4715) -#endif -#endif - -void ThreadpoolMgr::ProcessWaitCompletion(WaitInfo* waitInfo, - unsigned index, - BOOL waitTimedOut - ) -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_PREEMPTIVE; - /* cannot use contract because of SEH - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END;*/ - - AsyncCallback* asyncCallback = NULL; - EX_TRY{ - if ( waitInfo->flag & WAIT_SINGLE_EXECUTION) - { - DeactivateNthWait (waitInfo,index) ; - } - else - { // reactivate wait by resetting timer - waitInfo->timer.startTime = GetTickCount(); - } - - asyncCallback = MakeAsyncCallback(); - if (asyncCallback) - { - asyncCallback->wait = waitInfo; - asyncCallback->waitTimedOut = waitTimedOut; - - InterlockedIncrement(&waitInfo->refCount); - -#ifndef TARGET_UNIX - if (FALSE == PostQueuedCompletionStatus((LPOVERLAPPED)asyncCallback, (LPOVERLAPPED_COMPLETION_ROUTINE)WaitIOCompletionCallback)) -#else // TARGET_UNIX - if (FALSE == QueueUserWorkItem(AsyncCallbackCompletion, asyncCallback, QUEUE_ONLY)) -#endif // !TARGET_UNIX - ReleaseAsyncCallback(asyncCallback); - } - } - EX_CATCH { - if (asyncCallback) - ReleaseAsyncCallback(asyncCallback); - - EX_RETHROW; - } - EX_END_CATCH(SwallowAllExceptions); -} - - -DWORD WINAPI ThreadpoolMgr::AsyncCallbackCompletion(PVOID pArgs) -{ - CONTRACTL - { - THROWS; - MODE_PREEMPTIVE; - GC_TRIGGERS; - } - CONTRACTL_END; - - Thread * pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - HRESULT hr = ERROR_SUCCESS; - - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(&hr); - - if (pThread == NULL) - { - return hr; - } - } - - { - AsyncCallback * asyncCallback = (AsyncCallback*) pArgs; - - WaitInfo * waitInfo = asyncCallback->wait; - - AsyncCallbackHolder asyncCBHolder; - asyncCBHolder.Assign(asyncCallback); - - // We fire the "dequeue" ETW event here, before executing the user code, to enable correlation with - // the ThreadPoolIOEnqueue fired in ThreadpoolMgr::RegisterWaitForSingleObject - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIODequeue)) - FireEtwThreadPoolIODequeue(waitInfo, reinterpret_cast(waitInfo->Callback), GetClrInstanceId()); - - // the user callback can throw, the host must be prepared to handle it. - // SQL is ok, since they have a top-level SEH handler. However, there's - // no easy way to verify it - - ((WAITORTIMERCALLBACKFUNC) waitInfo->Callback) - ( waitInfo->Context, asyncCallback->waitTimedOut != FALSE); - -#ifndef TARGET_UNIX - Thread::IncrementIOThreadPoolCompletionCount(pThread); -#endif - } - - return ERROR_SUCCESS; -} - -void ThreadpoolMgr::DeactivateWait(WaitInfo* waitInfo) -{ - LIMITED_METHOD_CONTRACT; - - ThreadCB* threadCB = waitInfo->threadCB; - DWORD endIndex = threadCB->NumActiveWaits-1; - DWORD index; - - for (index = 0; index <= endIndex; index++) - { - LIST_ENTRY* head = &(threadCB->waitPointer[index]); - LIST_ENTRY* current = head; - do { - if (current->Flink == (PVOID) waitInfo) - goto FOUND; - - current = (LIST_ENTRY*) current->Flink; - - } while (current != head); - } - -FOUND: - _ASSERTE(index <= endIndex); - - DeactivateNthWait(waitInfo, index); -} - - -void ThreadpoolMgr::DeactivateNthWait(WaitInfo* waitInfo, DWORD index) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - ThreadCB* threadCB = waitInfo->threadCB; - - if (waitInfo->link.Flink != waitInfo->link.Blink) - { - RemoveEntryList(&(waitInfo->link)); - } - else - { - - ULONG EndIndex = threadCB->NumActiveWaits -1; - - // Move the remaining ActiveWaitArray left. - - ShiftWaitArray( threadCB, index+1, index,EndIndex - index ) ; - - // repair the blink and flink of the first and last elements in the list - for (unsigned int i = 0; i< EndIndex-index; i++) - { - WaitInfo* firstWaitInfo = (WaitInfo*) threadCB->waitPointer[index+i].Flink; - WaitInfo* lastWaitInfo = (WaitInfo*) threadCB->waitPointer[index+i].Blink; - firstWaitInfo->link.Blink = &(threadCB->waitPointer[index+i]); - lastWaitInfo->link.Flink = &(threadCB->waitPointer[index+i]); - } - // initialize the entry just freed - InitializeListHead(&(threadCB->waitPointer[EndIndex])); - - threadCB->NumActiveWaits-- ; - InterlockedDecrement(&threadCB->NumWaitHandles); - } - - waitInfo->state &= ~WAIT_ACTIVE ; - -} - -void ThreadpoolMgr::DeleteWait(WaitInfo* waitInfo) -{ - CONTRACTL - { - if (waitInfo->ExternalEventSafeHandle != NULL) { THROWS;} else { NOTHROW; } - MODE_ANY; - if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - } - CONTRACTL_END; - - if(waitInfo->Context && (waitInfo->flag & WAIT_FREE_CONTEXT)) { - DelegateInfo* pDelegate = (DelegateInfo*) waitInfo->Context; - - // Since the delegate release destroys a handle, we need to be in - // co-operative mode - { - GCX_COOP(); - pDelegate->Release(); - } - - RecycleMemory( pDelegate, MEMTYPE_DelegateInfo ); - } - - if (waitInfo->flag & WAIT_INTERNAL_COMPLETION) - { - waitInfo->InternalCompletionEvent.Set(); - return; // waitInfo will be deleted by the thread that's waiting on this event - } - else if (waitInfo->ExternalCompletionEvent != INVALID_HANDLE) - { - SetEvent(waitInfo->ExternalCompletionEvent); - } - else if (waitInfo->ExternalEventSafeHandle != NULL) - { - // Release the safe handle and the GC handle holding it - ReleaseWaitInfo(waitInfo); - } - - delete waitInfo; - - -} - - - -/************************************************************************/ -BOOL ThreadpoolMgr::UnregisterWaitEx(HANDLE hWaitObject,HANDLE Event) -{ - CONTRACTL - { - THROWS; //NOTHROW; - if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - _ASSERTE(IsInitialized()); // cannot call unregister before first registering - - const BOOL Blocking = (Event == (HANDLE) -1); - WaitInfo* waitInfo = (WaitInfo*) hWaitObject; - - if (!hWaitObject) - { - return FALSE; - } - - // we do not allow callbacks to run in the wait thread, hence the assert - _ASSERTE(GetCurrentThreadId() != waitInfo->threadCB->threadId); - - - if (Blocking) - { - waitInfo->InternalCompletionEvent.CreateAutoEvent(FALSE); - waitInfo->flag |= WAIT_INTERNAL_COMPLETION; - - } - else - { - waitInfo->ExternalCompletionEvent = (Event ? Event : INVALID_HANDLE); - _ASSERTE((waitInfo->flag & WAIT_INTERNAL_COMPLETION) == 0); - // we still want to block until the wait has been deactivated - waitInfo->PartialCompletionEvent.CreateAutoEvent(FALSE); - } - - BOOL status = QueueDeregisterWait(waitInfo->threadCB->threadHandle, waitInfo); - - - if (status == 0) - { - STRESS_LOG1(LF_THREADPOOL, LL_ERROR, "Queue APC failed in UnregisterWaitEx %x", status); - - if (Blocking) - waitInfo->InternalCompletionEvent.CloseEvent(); - else - waitInfo->PartialCompletionEvent.CloseEvent(); - return FALSE; - } - - if (!Blocking) - { - waitInfo->PartialCompletionEvent.Wait(INFINITE,TRUE); - waitInfo->PartialCompletionEvent.CloseEvent(); - // we cannot do DeleteWait in DeregisterWait, since the DeleteWait could happen before - // we close the event. So, the code has been moved here. - if (InterlockedDecrement(&waitInfo->refCount) == 0) - { - DeleteWait(waitInfo); - } - } - - else // i.e. blocking - { - _ASSERTE(waitInfo->flag & WAIT_INTERNAL_COMPLETION); - _ASSERTE(waitInfo->ExternalEventSafeHandle == NULL); - - waitInfo->InternalCompletionEvent.Wait(INFINITE,TRUE); - waitInfo->InternalCompletionEvent.CloseEvent(); - delete waitInfo; // if WAIT_INTERNAL_COMPLETION is not set, waitInfo will be deleted in DeleteWait - } - return TRUE; -} - - -void ThreadpoolMgr::DeregisterWait(WaitInfo* pArgs) -{ - WRAPPER_NO_CONTRACT; - - WaitInfo* waitInfo = pArgs; - - if ( ! (waitInfo->state & WAIT_REGISTERED) ) - { - // set state to deleted, so that it does not get registered - waitInfo->state |= WAIT_DELETE ; - - // since the wait has not even been registered, we dont need an interlock to decrease the RefCount - waitInfo->refCount--; - - if (waitInfo->PartialCompletionEvent.IsValid()) - { - waitInfo->PartialCompletionEvent.Set(); - } - return; - } - - if (waitInfo->state & WAIT_ACTIVE) - { - DeactivateWait(waitInfo); - } - - if ( waitInfo->PartialCompletionEvent.IsValid()) - { - waitInfo->PartialCompletionEvent.Set(); - return; // we cannot delete the wait here since the PartialCompletionEvent - // may not have been closed yet. so, we return and rely on the waiter of PartialCompletionEvent - // to do the close - } - - if (InterlockedDecrement(&waitInfo->refCount) == 0) - { - DeleteWait(waitInfo); - } - return; -} - - -/* This gets called in a finalizer thread ONLY IF an app does not deregister the - the wait. Note that just because the registeredWaitHandle is collected by GC - does not mean it is safe to delete the wait. The refcount tells us when it is - safe. -*/ -void ThreadpoolMgr::WaitHandleCleanup(HANDLE hWaitObject) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - _ASSERTE(IsInitialized()); // cannot call cleanup before first registering - - WaitInfo* waitInfo = (WaitInfo*) hWaitObject; - _ASSERTE(waitInfo->refCount > 0); - - DWORD result = QueueDeregisterWait(waitInfo->threadCB->threadHandle, waitInfo); - - if (result == 0) - STRESS_LOG1(LF_THREADPOOL, LL_ERROR, "Queue APC failed in WaitHandleCleanup %x", result); - -} - -BOOL ThreadpoolMgr::CreateGateThread() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - HANDLE threadHandle = Thread::CreateUtilityThread(Thread::StackSize_Small, GateThreadStart, NULL, W(".NET ThreadPool Gate")); - - if (threadHandle) - { - CloseHandle(threadHandle); //we don't need this anymore - return TRUE; - } - - return FALSE; -} - - - -/************************************************************************/ - -BOOL ThreadpoolMgr::BindIoCompletionCallback(HANDLE FileHandle, - LPOVERLAPPED_COMPLETION_ROUTINE Function, - ULONG Flags, - DWORD& errCode) -{ - CONTRACTL - { - THROWS; // EnsureInitialized can throw - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - -#ifndef TARGET_UNIX - - errCode = S_OK; - - EnsureInitialized(); - - - _ASSERTE(GlobalCompletionPort != NULL); - - if (!InitCompletionPortThreadpool) - InitCompletionPortThreadpool = TRUE; - - GrowCompletionPortThreadpoolIfNeeded(); - - HANDLE h = CreateIoCompletionPort(FileHandle, - GlobalCompletionPort, - (ULONG_PTR) Function, - NumberOfProcessors); - if (h == NULL) - { - errCode = GetLastError(); - return FALSE; - } - - _ASSERTE(h == GlobalCompletionPort); - - return TRUE; -#else // TARGET_UNIX - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; -#endif // !TARGET_UNIX -} - -#ifndef TARGET_UNIX -BOOL ThreadpoolMgr::CreateCompletionPortThread(LPVOID lpArgs) -{ - CONTRACTL - { - NOTHROW; - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - Thread *pThread; - BOOL fIsCLRThread; - if ((pThread = CreateUnimpersonatedThread(CompletionPortThreadStart, lpArgs, &fIsCLRThread)) != NULL) - { - LastCPThreadCreation = GetTickCount(); // record this for use by logic to spawn additional threads - - if (fIsCLRThread) { - pThread->ChooseThreadCPUGroupAffinity(); - pThread->StartThread(); - } - else { - DWORD status; - status = ResumeThread((HANDLE)pThread); - _ASSERTE(status != (DWORD) (-1)); - CloseHandle((HANDLE)pThread); // we don't need this anymore - } - - ThreadCounter::Counts counts = CPThreadCounter.GetCleanCounts(); - FireEtwIOThreadCreate_V1(counts.NumActive + counts.NumRetired, counts.NumRetired, GetClrInstanceId()); - - return TRUE; - } - - - return FALSE; -} - -DWORD WINAPI ThreadpoolMgr::CompletionPortThreadStart(LPVOID lpArgs) -{ - ClrFlsSetThreadType (ThreadType_Threadpool_IOCompletion); - - CONTRACTL - { - THROWS; - if (GetThreadNULLOk()) { MODE_PREEMPTIVE;} else { DISABLED(MODE_ANY);} - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - } - CONTRACTL_END; - - _ASSERTE_ALL_BUILDS(!UsePortableThreadPoolForIO()); - - DWORD numBytes=0; - size_t key=0; - - LPOVERLAPPED pOverlapped = NULL; - DWORD errorCode; - PIOCompletionContext context; - BOOL fIsCompletionContext; - - const DWORD CP_THREAD_WAIT = 15000; /* milliseconds */ - - _ASSERTE(GlobalCompletionPort != NULL); - - BOOL fThreadInit = FALSE; - Thread *pThread = NULL; - - DWORD cpThreadWait = 0; - - if (g_fEEStarted) { - pThread = SetupThreadNoThrow(); - if (pThread == NULL) { - return 0; - } - - // converted to CLRThread and added to ThreadStore, pick an group affinity for this thread - pThread->ChooseThreadCPUGroupAffinity(); - - fThreadInit = TRUE; - } - -#ifdef FEATURE_COMINTEROP - // Threadpool threads should be initialized as MTA. If we are unable to do so, - // return failure. - BOOL fCoInited = FALSE; - { - fCoInited = SUCCEEDED(::CoInitializeEx(NULL, COINIT_MULTITHREADED)); - if (!fCoInited) - { - goto Exit; - } - } - - if (pThread && pThread->SetApartment(Thread::AS_InMTA) != Thread::AS_InMTA) - { - // @todo: should we log the failure - goto Exit; - } -#endif // FEATURE_COMINTEROP - - ThreadCounter::Counts oldCounts; - ThreadCounter::Counts newCounts; - - cpThreadWait = CP_THREAD_WAIT; - for (;; ) - { -Top: - if (!fThreadInit) { - if (g_fEEStarted) { - pThread = SetupThreadNoThrow(); - if (pThread == NULL) { - break; - } - - // converted to CLRThread and added to ThreadStore, pick an group affinity for this thread - pThread->ChooseThreadCPUGroupAffinity(); - -#ifdef FEATURE_COMINTEROP - if (pThread->SetApartment(Thread::AS_InMTA) != Thread::AS_InMTA) - { - // @todo: should we log the failure - goto Exit; - } -#endif // FEATURE_COMINTEROP - - fThreadInit = TRUE; - } - } - - GCX_PREEMP_NO_DTOR(); - - // - // We're about to wait on the IOCP; mark ourselves as no longer "working." - // - while (true) - { - ThreadCounter::Counts oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - ThreadCounter::Counts newCounts = oldCounts; - newCounts.NumWorking--; - - // - // If we've only got one thread left, it won't be allowed to exit, because we need to keep - // one thread listening for completions. So there's no point in having a timeout; it will - // only use power unnecessarily. - // - cpThreadWait = (newCounts.NumActive == 1) ? INFINITE : CP_THREAD_WAIT; - - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - - errorCode = S_OK; - - if (lpArgs == NULL) - { - CONTRACT_VIOLATION(ThrowsViolation); - - context = NULL; - fIsCompletionContext = FALSE; - - if (pThread == NULL) - { - pThread = GetThreadNULLOk(); - } - - if (pThread) - { - context = (PIOCompletionContext) pThread->GetIOCompletionContext(); - - if (context->lpOverlapped != NULL) - { - errorCode = context->ErrorCode; - numBytes = context->numBytesTransferred; - pOverlapped = context->lpOverlapped; - key = context->key; - - context->lpOverlapped = NULL; - fIsCompletionContext = TRUE; - } - } - - if((context == NULL) || (!fIsCompletionContext)) - { - _ASSERTE (context == NULL || context->lpOverlapped == NULL); - - BOOL status = GetQueuedCompletionStatus( - GlobalCompletionPort, - &numBytes, - (PULONG_PTR)&key, - &pOverlapped, - cpThreadWait - ); - - if (status == 0) - errorCode = GetLastError(); - } - } - else - { - QueuedStatus *CompletionStatus = (QueuedStatus*)lpArgs; - numBytes = CompletionStatus->numBytes; - key = (size_t)CompletionStatus->key; - pOverlapped = CompletionStatus->pOverlapped; - errorCode = CompletionStatus->errorCode; - delete CompletionStatus; - lpArgs = NULL; // one-time deal for initial CP packet - } - - // We fire IODequeue events whether the IO completion was retrieved in the above call to - // GetQueuedCompletionStatus or during an earlier call (e.g. in GateThreadStart, and passed here in lpArgs, - // or in CompletionPortDispatchWorkWithinAppDomain, and passed here through StoreOverlappedInfoInThread) - - // For the purposes of activity correlation we only fire ETW events here, if needed OR if not fired at a higher - // abstraction level (e.g. ThreadpoolMgr::RegisterWaitForSingleObject) - // Note: we still fire the event for managed async IO, despite the fact we don't have a paired IOEnqueue event - // for this case. We do this to "mark" the end of the previous workitem. When we provide full support at the higher - // abstraction level for managed IO we can remove the IODequeues fired here - if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ThreadPoolIODequeue) - && !AreEtwIOQueueEventsSpeciallyHandled((LPOVERLAPPED_COMPLETION_ROUTINE)key) && pOverlapped != NULL) - { - FireEtwThreadPoolIODequeue(pOverlapped, OverlappedDataObject::GetOverlappedForTracing(pOverlapped), GetClrInstanceId()); - } - - bool enterRetirement; - - while (true) - { - // - // When we reach this point, this thread is "active" but not "working." Depending on the result of the call to GetQueuedCompletionStatus, - // and the state of the rest of the IOCP threads, we need to figure out whether to de-activate (exit) this thread, retire this thread, - // or transition to "working." - // - - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - enterRetirement = false; - - if (errorCode == WAIT_TIMEOUT) - { - // - // We timed out, and are going to try to exit or retire. - // - newCounts.NumActive--; - - // - // We need at least one free thread, or we have no way of knowing if completions are being queued. - // - if (newCounts.NumWorking == newCounts.NumActive) - { - newCounts = oldCounts; - newCounts.NumWorking++; //not really working, but we'll decremented it at the top - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - goto Top; - else - continue; - } - - // - // We can't exit a thread that has pending I/O - we'll "retire" it instead. - // - if (IsIoPending()) - { - enterRetirement = true; - newCounts.NumRetired++; - } - } - else - { - // - // We have work to do - // - newCounts.NumWorking++; - } - - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - - if (errorCode == WAIT_TIMEOUT) - { - if (!enterRetirement) - { - goto Exit; - } - else - { - // now in "retired mode" waiting for pending io to complete - FireEtwIOThreadRetire_V1(newCounts.NumActive + newCounts.NumRetired, newCounts.NumRetired, GetClrInstanceId()); - - for (;;) - { - DWORD status = SafeWait(RetiredCPWakeupEvent,CP_THREAD_PENDINGIO_WAIT,FALSE); - _ASSERTE(status == WAIT_TIMEOUT || status == WAIT_OBJECT_0); - - if (status == WAIT_TIMEOUT) - { - if (IsIoPending()) - { - continue; - } - else - { - // We can now exit; decrement the retired count. - while (true) - { - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - newCounts.NumRetired--; - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - goto Exit; - } - } - else - { - // put back into rotation -- we need a thread - while (true) - { - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - newCounts.NumRetired--; - newCounts.NumActive++; - newCounts.NumWorking++; //we're not really working, but we'll decrement this before waiting for work. - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - FireEtwIOThreadUnretire_V1(newCounts.NumActive + newCounts.NumRetired, newCounts.NumRetired, GetClrInstanceId()); - goto Top; - } - } - } - } - - // we should not reach this point unless we have work to do - _ASSERTE(errorCode != WAIT_TIMEOUT && !enterRetirement); - - // if we have no more free threads, start the gate thread - if (newCounts.NumWorking >= newCounts.NumActive) - EnsureGateThreadRunning(); - - - // We can not assert here. If stdin/stdout/stderr of child process are redirected based on - // async io, GetQueuedCompletionStatus returns when child process operates on its stdin/stdout/stderr. - // Parent process does not issue any ReadFile/WriteFile, and hence pOverlapped is going to be NULL. - //_ASSERTE(pOverlapped != NULL); - - if (pOverlapped != NULL) - { - _ASSERTE(key != 0); // should be a valid function address - - if (key != 0) - { - if (GCHeapUtilities::IsGCInProgress(TRUE)) - { - //Indicate that this thread is free, and waiting on GC, not doing any user work. - //This helps in threads not getting injected when some threads have woken up from the - //GC event, and some have not. - while (true) - { - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - newCounts.NumWorking--; - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - - // GC is imminent, so wait until GC is complete before executing next request. - // this reduces in-flight objects allocated right before GC, easing the GC's work - GCHeapUtilities::WaitForGCCompletion(TRUE); - - while (true) - { - // counts volatile read paired with CompareExchangeCounts loop set - oldCounts = CPThreadCounter.DangerousGetDirtyCounts(); - newCounts = oldCounts; - newCounts.NumWorking++; - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - break; - } - - if (newCounts.NumWorking >= newCounts.NumActive) - EnsureGateThreadRunning(); - } - else - { - GrowCompletionPortThreadpoolIfNeeded(); - } - - { - CONTRACT_VIOLATION(ThrowsViolation); - - ((LPOVERLAPPED_COMPLETION_ROUTINE) key)(errorCode, numBytes, pOverlapped); - } - - Thread::IncrementIOThreadPoolCompletionCount(pThread); - - if (pThread == NULL) - { - pThread = GetThreadNULLOk(); - } - - if (pThread) - { - _ASSERTE(!pThread->IsAbortRequested()); - pThread->InternalReset(); - } - } - else - { - // Application bug - can't do much, just ignore it - } - - } - - } // for (;;) - -Exit: - - oldCounts = CPThreadCounter.GetCleanCounts(); - - // we should never destroy or retire all IOCP threads, because then we won't have any threads to notice incoming completions. - _ASSERTE(oldCounts.NumActive > 0); - - FireEtwIOThreadTerminate_V1(oldCounts.NumActive + oldCounts.NumRetired, oldCounts.NumRetired, GetClrInstanceId()); - -#ifdef FEATURE_COMINTEROP - if (pThread) { - pThread->SetApartment(Thread::AS_Unknown); - pThread->CoUninitialize(); - } - // Couninit the worker thread - if (fCoInited) - { - CoUninitialize(); - } -#endif - - if (pThread) { - pThread->ClearThreadCPUGroupAffinity(); - - DestroyThread(pThread); - } - - return 0; -} - -LPOVERLAPPED ThreadpoolMgr::CompletionPortDispatchWorkWithinAppDomain( - Thread* pThread, - DWORD* pErrorCode, - DWORD* pNumBytes, - size_t* pKey) -// -//This function is called just after dispatching the previous BindIO callback -//to Managed code. This is a perf optimization to do a quick call to -//GetQueuedCompletionStatus with a timeout of 0 ms. If there is work in the -//same appdomain, dispatch it back immediately. If not stick it in a well known -//place, and reenter the target domain. The timeout of zero is chosen so as to -//not delay appdomain unloads. -// -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_MODE_ANY; - - LPOVERLAPPED lpOverlapped=NULL; - - BOOL status=FALSE; - OVERLAPPEDDATAREF overlapped=NULL; - BOOL ManagedCallback=FALSE; - - *pErrorCode = S_OK; - - - //Very Very Important! - //Do not change the timeout for GetQueuedCompletionStatus to a non-zero value. - //Selecting a non-zero value can cause the thread to block, and lead to expensive context switches. - //In real life scenarios, we have noticed a packet to be not available immediately, but very shortly - //(after few 100's of instructions), and falling back to the VM is good in that case as compared to - //taking a context switch. Changing the timeout to non-zero can lead to perf degrades, that are very - //hard to diagnose. - - status = ::GetQueuedCompletionStatus( - GlobalCompletionPort, - pNumBytes, - (PULONG_PTR)pKey, - &lpOverlapped, - 0); - - DWORD lastError = GetLastError(); - - if (status == 0) - { - if (lpOverlapped != NULL) - { - *pErrorCode = lastError; - } - else - { - return NULL; - } - } - - if (((LPOVERLAPPED_COMPLETION_ROUTINE) *pKey) != BindIoCompletionCallbackStub) - { - //_ASSERTE(FALSE); - } - else - { - ManagedCallback = TRUE; - overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(lpOverlapped)); - } - - if (ManagedCallback) - { - _ASSERTE(*pKey != 0); // should be a valid function address - - if (*pKey ==0) - { - //Application Bug. - return NULL; - } - } - else - { - //Just retruned back from managed code, a Thread structure should exist. - _ASSERTE (pThread); - - //Oops, this is an overlapped fom a different appdomain. STick it in - //the thread. We will process it later. - - StoreOverlappedInfoInThread(pThread, *pErrorCode, *pNumBytes, *pKey, lpOverlapped); - - lpOverlapped = NULL; - } - -#ifndef DACCESS_COMPILE - return lpOverlapped; -#endif -} - -void ThreadpoolMgr::StoreOverlappedInfoInThread(Thread* pThread, DWORD dwErrorCode, DWORD dwNumBytes, size_t key, LPOVERLAPPED lpOverlapped) -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_MODE_ANY; - - _ASSERTE(pThread); - - PIOCompletionContext context; - - context = (PIOCompletionContext) pThread->GetIOCompletionContext(); - - _ASSERTE(context); - - context->ErrorCode = dwErrorCode; - context->numBytesTransferred = dwNumBytes; - context->lpOverlapped = lpOverlapped; - context->key = key; -} - -BOOL ThreadpoolMgr::ShouldGrowCompletionPortThreadpool(ThreadCounter::Counts counts) -{ - CONTRACTL - { - GC_NOTRIGGER; - NOTHROW; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - if (counts.NumWorking >= counts.NumActive - && (counts.NumActive == 0 || !GCHeapUtilities::IsGCInProgress(TRUE)) - ) - { - // adjust limit if needed - if (counts.NumRetired == 0) - { - if (counts.NumActive + counts.NumRetired < MaxLimitTotalCPThreads && - (counts.NumActive < MinLimitTotalCPThreads || cpuUtilization < CpuUtilizationLow)) - { - // add one more check to make sure that we haven't fired off a new - // thread since the last time time we checked the cpu utilization. - // However, don't bother if we haven't reached the MinLimit (2*number of cpus) - if ((counts.NumActive < MinLimitTotalCPThreads) || - SufficientDelaySinceLastSample(LastCPThreadCreation,counts.NumActive)) - { - return TRUE; - } - } - } - - if (counts.NumRetired > 0) - return TRUE; - } - return FALSE; -} - -void ThreadpoolMgr::GrowCompletionPortThreadpoolIfNeeded() -{ - CONTRACTL - { - if (GetThreadNULLOk()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - NOTHROW; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - ThreadCounter::Counts oldCounts, newCounts; - while (true) - { - oldCounts = CPThreadCounter.GetCleanCounts(); - newCounts = oldCounts; - - if(!ShouldGrowCompletionPortThreadpool(oldCounts)) - { - break; - } - else - { - if (oldCounts.NumRetired > 0) - { - // wakeup retired thread instead - RetiredCPWakeupEvent->Set(); - return; - } - else - { - // create a new thread. New IOCP threads start as "active" and "working" - newCounts.NumActive++; - newCounts.NumWorking++; - if (oldCounts == CPThreadCounter.CompareExchangeCounts(newCounts, oldCounts)) - { - if (!CreateCompletionPortThread(NULL)) - { - // if thread creation failed, we have to adjust the counts back down. - 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; - } - } - return; - } - } - } - } -} -#endif // !TARGET_UNIX - -// Returns true if there is pending io on the thread. -BOOL ThreadpoolMgr::IsIoPending() -{ - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - -#ifndef TARGET_UNIX - int Status; - ULONG IsIoPending; - - if (g_pufnNtQueryInformationThread) - { - Status =(int) (*g_pufnNtQueryInformationThread)(GetCurrentThread(), - ThreadIsIoPending, - &IsIoPending, - sizeof(IsIoPending), - NULL); - - - if ((Status < 0) || IsIoPending) - return TRUE; - else - return FALSE; - } - return TRUE; -#else - return FALSE; -#endif // !TARGET_UNIX -} - -#ifndef TARGET_UNIX - -#ifdef HOST_64BIT -#pragma warning (disable : 4716) -#else -#pragma warning (disable : 4715) -#endif - -int ThreadpoolMgr::GetCPUBusyTime_NT(PROCESS_CPU_INFORMATION* pOldInfo) -{ - LIMITED_METHOD_CONTRACT; - - PROCESS_CPU_INFORMATION newUsage; - newUsage.idleTime.QuadPart = 0; - newUsage.kernelTime.QuadPart = 0; - newUsage.userTime.QuadPart = 0; - - if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) - { -#if !defined(FEATURE_NATIVEAOT) && !defined(TARGET_UNIX) - FILETIME newIdleTime, newKernelTime, newUserTime; - - CPUGroupInfo::GetSystemTimes(&newIdleTime, &newKernelTime, &newUserTime); - newUsage.idleTime.u.LowPart = newIdleTime.dwLowDateTime; - newUsage.idleTime.u.HighPart = newIdleTime.dwHighDateTime; - newUsage.kernelTime.u.LowPart = newKernelTime.dwLowDateTime; - newUsage.kernelTime.u.HighPart = newKernelTime.dwHighDateTime; - newUsage.userTime.u.LowPart = newUserTime.dwLowDateTime; - newUsage.userTime.u.HighPart = newUserTime.dwHighDateTime; -#endif - } - else - { - (*g_pufnNtQuerySystemInformation)(SystemProcessorPerformanceInformation, - pOldInfo->usageBuffer, - pOldInfo->usageBufferSize, - NULL); - - SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* pInfoArray = pOldInfo->usageBuffer; - DWORD_PTR pmask = pOldInfo->affinityMask; - - int proc_no = 0; - while (pmask) - { - if (pmask & 1) - { //should be good: 1CPU 28823 years, 256CPUs 100+years - newUsage.idleTime.QuadPart += pInfoArray[proc_no].IdleTime.QuadPart; - newUsage.kernelTime.QuadPart += pInfoArray[proc_no].KernelTime.QuadPart; - newUsage.userTime.QuadPart += pInfoArray[proc_no].UserTime.QuadPart; - } - - pmask >>=1; - proc_no++; - } - } - - __int64 cpuTotalTime, cpuBusyTime; - - cpuTotalTime = (newUsage.userTime.QuadPart - pOldInfo->userTime.QuadPart) + - (newUsage.kernelTime.QuadPart - pOldInfo->kernelTime.QuadPart); - cpuBusyTime = cpuTotalTime - - (newUsage.idleTime.QuadPart - pOldInfo->idleTime.QuadPart); - - // Preserve reading - pOldInfo->idleTime = newUsage.idleTime; - pOldInfo->kernelTime = newUsage.kernelTime; - pOldInfo->userTime = newUsage.userTime; - - __int64 reading = 0; - - if (cpuTotalTime > 0) - reading = ((cpuBusyTime * 100) / cpuTotalTime); - - _ASSERTE(FitsIn(reading)); - return (int)reading; -} - -#else // !TARGET_UNIX - -int ThreadpoolMgr::GetCPUBusyTime_NT(PAL_IOCP_CPU_INFORMATION* pOldInfo) -{ - return PAL_GetCPUBusyTime(pOldInfo); -} - -#endif // !TARGET_UNIX - -// -// A timer that ticks every GATE_THREAD_DELAY milliseconds. -// On platforms that support it, we use a coalescable waitable timer object. -// For other platforms, we use Sleep, via __SwitchToThread. -// -class GateThreadTimer -{ -#ifndef TARGET_UNIX - HANDLE m_hTimer; - -public: - GateThreadTimer() - : m_hTimer(NULL) - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - if (g_pufnCreateWaitableTimerEx && g_pufnSetWaitableTimerEx) - { - m_hTimer = g_pufnCreateWaitableTimerEx(NULL, NULL, 0, TIMER_ALL_ACCESS); - if (m_hTimer) - { - // - // Set the timer to fire GATE_THREAD_DELAY milliseconds from now, then every GATE_THREAD_DELAY milliseconds thereafter. - // We also set the tolerance to GET_THREAD_DELAY_TOLERANCE, allowing the OS to coalesce this timer. - // - LARGE_INTEGER dueTime; - dueTime.QuadPart = MILLI_TO_100NANO(-(LONGLONG)GATE_THREAD_DELAY); //negative value indicates relative time - if (!g_pufnSetWaitableTimerEx(m_hTimer, &dueTime, GATE_THREAD_DELAY, NULL, NULL, NULL, GATE_THREAD_DELAY_TOLERANCE)) - { - CloseHandle(m_hTimer); - m_hTimer = NULL; - } - } - } - } - - ~GateThreadTimer() - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - if (m_hTimer) - { - CloseHandle(m_hTimer); - m_hTimer = NULL; - } - } - -#endif // !TARGET_UNIX - -public: - void Wait() - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - -#ifndef TARGET_UNIX - if (m_hTimer) - WaitForSingleObject(m_hTimer, INFINITE); - else -#endif // !TARGET_UNIX - __SwitchToThread(GATE_THREAD_DELAY, CALLER_LIMITS_SPINNING); - } -}; - -DWORD WINAPI ThreadpoolMgr::GateThreadStart(LPVOID lpArgs) -{ - ClrFlsSetThreadType (ThreadType_Gate); - - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - _ASSERTE(GateThreadStatus == GATE_THREAD_STATUS_REQUESTED); - - GateThreadTimer timer; - - // TODO: do we need to do this? - timer.Wait(); // delay getting initial CPU reading - -#ifndef TARGET_UNIX - PROCESS_CPU_INFORMATION prevCPUInfo; - - if (!g_pufnNtQuerySystemInformation) - { - _ASSERT(!"NtQuerySystemInformation API not available!"); - return 0; - } - -#ifndef TARGET_UNIX - //GateThread can start before EESetup, so ensure CPU group information is initialized; - CPUGroupInfo::EnsureInitialized(); -#endif // !TARGET_UNIX - // initialize CPU usage information structure; - prevCPUInfo.idleTime.QuadPart = 0; - prevCPUInfo.kernelTime.QuadPart = 0; - prevCPUInfo.userTime.QuadPart = 0; - - PREFIX_ASSUME(NumberOfProcessors < 65536); - prevCPUInfo.numberOfProcessors = NumberOfProcessors; - - /* In following cases, affinity mask can be zero - * 1. hosted, the hosted process already uses multiple cpu groups. - * thus, during CLR initialization, GetCurrentProcessCpuCount() returns 64, and GC threads - * are created to fill up the initial CPU group. ==> use g_SystemInfo.dwNumberOfProcessors - * 2. GCCpuGroups=1, CLR creates GC threads for all processors in all CPU groups - * thus, the threadpool thread would use a whole CPU group (if Thread_UseAllCpuGroups is not set). - * ==> use g_SystemInfo.dwNumberOfProcessors. - * 3. !defined(TARGET_UNIX), GetCurrentProcessCpuCount() - * returns g_SystemInfo.dwNumberOfProcessors ==> use g_SystemInfo.dwNumberOfProcessors; - * Other cases: - * 1. Normal case: the mask is all or a subset of all processors in a CPU group; - * 2. GCCpuGroups=1 && Thread_UseAllCpuGroups = 1, the mask is not used - */ - prevCPUInfo.affinityMask = GetCurrentProcessCpuMask(); - if (prevCPUInfo.affinityMask == 0) - { // create a mask that has g_SystemInfo.dwNumberOfProcessors; - DWORD_PTR mask = 0, maskpos = 1; - for (unsigned int i=0; i < g_SystemInfo.dwNumberOfProcessors; i++) - { - mask |= maskpos; - maskpos <<= 1; - } - prevCPUInfo.affinityMask = mask; - } - - // in some cases GetCurrentProcessCpuCount() returns a number larger than - // g_SystemInfo.dwNumberOfProcessor when there are CPU groups, use the larger - // one to create buffer. This buffer must be cleared with 0's to get correct - // CPU usage statistics - int elementsNeeded = NumberOfProcessors > g_SystemInfo.dwNumberOfProcessors ? - NumberOfProcessors : g_SystemInfo.dwNumberOfProcessors; - - // - // When CLR threads are not using all groups, GetCPUBusyTime_NT will read element X from - // the "prevCPUInfo.usageBuffer" array if and only if "prevCPUInfo.affinityMask" contains a - // set bit in bit position X. This implies that GetCPUBusyTime_NT would read past the end - // of the "usageBuffer" array if the index of the highest set bit in "affinityMask" was - // ever larger than the index of the last array element. - // - // If necessary, expand "elementsNeeded" to ensure that the index of the last array element - // is always at least as large as the index of the highest set bit in the "affinityMask". - // - // This expansion is necessary in any case where the mask returned by GetCurrentProcessCpuMask - // (which is just a wrapper around the Win32 GetProcessAffinityMask API) contains set bits - // at indices greater than or equal to the larger of the basline CPU count values (i.e., - // ThreadpoolMgr::NumberOfProcessors and g_SystemInfo.dwNumberOfProcessors) that were - // captured earlier on (during ThreadpoolMgr::Initialize and during EEStartupHelper, - // respectively). Note that in the relevant scenario (i.e., when CLR threads are not using - // all groups) the mask and CPU counts are all collected via "group-unaware" APIs and are - // all "group-local" values as a result. - // - // Expansion is necessary in at least the following cases: - // - // - If the baseline CPU counts were captured while running in groups that contain fewer - // CPUs (in a multi-group system with heterogenous CPU counts across groups), but this code - // is now running in a group that contains a larger number of CPUs. - // - // - If CPUs were hot-added to the system and then added to the current process affinity - // mask at some point after the baseline CPU counts were captured. - // - if (!CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) - { - int elementsNeededToCoverMask = 0; - DWORD_PTR remainingMask = prevCPUInfo.affinityMask; - while (remainingMask != 0) - { - elementsNeededToCoverMask++; - remainingMask >>= 1; - } - - if (elementsNeeded < elementsNeededToCoverMask) - { - elementsNeeded = elementsNeededToCoverMask; - } - } - - if (!ClrSafeInt::multiply(elementsNeeded, sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION), - prevCPUInfo.usageBufferSize)) - return 0; - - prevCPUInfo.usageBuffer = (SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *)alloca(prevCPUInfo.usageBufferSize); - if (prevCPUInfo.usageBuffer == NULL) - return 0; - - memset((void *)prevCPUInfo.usageBuffer, 0, prevCPUInfo.usageBufferSize); //must clear it with 0s - - GetCPUBusyTime_NT(&prevCPUInfo); -#else // !TARGET_UNIX - PAL_IOCP_CPU_INFORMATION prevCPUInfo; - memset(&prevCPUInfo, 0, sizeof(prevCPUInfo)); - - GetCPUBusyTime_NT(&prevCPUInfo); // ignore return value the first time -#endif // !TARGET_UNIX - - BOOL IgnoreNextSample = FALSE; - - do - { - timer.Wait(); - - if(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking)) - FireEtwThreadPoolWorkingThreadCount(TakeMaxWorkingThreadCount(), GetClrInstanceId()); - -#ifdef DEBUGGING_SUPPORTED - // if we are stopped at a debug breakpoint, go back to sleep - if (CORDebuggerAttached() && g_pDebugInterface->IsStopped()) - continue; -#endif // DEBUGGING_SUPPORTED - - if (!GCHeapUtilities::IsGCInProgress(FALSE) ) - { - if (IgnoreNextSample) - { - IgnoreNextSample = FALSE; - int cpuUtilizationTemp = GetCPUBusyTime_NT(&prevCPUInfo); // updates prevCPUInfo as side effect - // don't artificially drive down average if cpu is high - if (cpuUtilizationTemp <= CpuUtilizationLow) - cpuUtilization = CpuUtilizationLow + 1; - else - cpuUtilization = cpuUtilizationTemp; - } - else - { - cpuUtilization = GetCPUBusyTime_NT(&prevCPUInfo); // updates prevCPUInfo as side effect - } - } - else - { - int cpuUtilizationTemp = GetCPUBusyTime_NT(&prevCPUInfo); // updates prevCPUInfo as side effect - // don't artificially drive down average if cpu is high - if (cpuUtilizationTemp <= CpuUtilizationLow) - cpuUtilization = CpuUtilizationLow + 1; - else - cpuUtilization = cpuUtilizationTemp; - IgnoreNextSample = TRUE; - } - - PerformGateActivities(cpuUtilization); - } - while (ShouldGateThreadKeepRunning()); - - return 0; -} - -void ThreadpoolMgr::PerformGateActivities(int cpuUtilization) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPoolForIO()); - - 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(); - - if (oldCounts.NumActive == oldCounts.NumWorking && - oldCounts.NumRetired == 0 && - oldCounts.NumActive < MaxLimitTotalCPThreads && - !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) - { - errorCode = GetLastError(); - } - - if (errorCode != WAIT_TIMEOUT) - { - 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) - { - __SwitchToThread(GATE_THREAD_DELAY, CALLER_LIMITS_SPINNING); - } - } - while (CompletionStatus == NULL); - - 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; - } - - // loop, retrying until thread is created. - while (!CreateCompletionPortThread((LPVOID)CompletionStatus)) - { - __SwitchToThread(GATE_THREAD_DELAY, CALLER_LIMITS_SPINNING); - } - } - } - 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) - { - RetiredCPWakeupEvent->Set(); - } - } - } -#endif // !TARGET_UNIX - - if (!UsePortableThreadPool() && - 0 == CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection)) - { - if (PerAppDomainTPCountList::AreRequestsPendingInAnyAppDomains() && SufficientDelaySinceLastDequeue()) - { - 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 - { - bool breakIntoDebugger = (0 != CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_DebugBreakOnWorkerStarvation)); - if (breakIntoDebugger) - { - OutputDebugStringW(W("The CLR ThreadPool detected work queue starvation!")); - DebugBreak(); - } - - 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; - } - } - } - } -} - -// called by logic to spawn a new completion port thread. -// return false if not enough time has elapsed since the last -// time we sampled the cpu utilization. -BOOL ThreadpoolMgr::SufficientDelaySinceLastSample(unsigned int LastThreadCreationTime, - unsigned NumThreads, // total number of threads of that type (worker or CP) - double throttleRate // the delay is increased by this percentage for each extra thread - ) -{ - LIMITED_METHOD_CONTRACT; - - unsigned dwCurrentTickCount = GetTickCount(); - - unsigned delaySinceLastThreadCreation = dwCurrentTickCount - LastThreadCreationTime; - - unsigned minWaitBetweenThreadCreation = GATE_THREAD_DELAY; - - if (throttleRate > 0.0) - { - _ASSERTE(throttleRate <= 1.0); - - unsigned adjustedThreadCount = NumThreads > NumberOfProcessors ? (NumThreads - NumberOfProcessors) : 0; - - minWaitBetweenThreadCreation = (unsigned) (GATE_THREAD_DELAY * pow((1.0 + throttleRate),(double)adjustedThreadCount)); - } - // the amount of time to wait should grow up as the number of threads is increased - - return (delaySinceLastThreadCreation > minWaitBetweenThreadCreation); - -} - - -// 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" -BOOL ThreadpoolMgr::SufficientDelaySinceLastDequeue() -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - #define DEQUEUE_DELAY_THRESHOLD (GATE_THREAD_DELAY * 2) - - unsigned delay = GetTickCount() - VolatileLoad(&LastDequeueTime); - unsigned tooLong; - - if(cpuUtilization < CpuUtilizationLow) - { - tooLong = GATE_THREAD_DELAY; - } - else - { - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - unsigned numThreads = counts.MaxWorking; - tooLong = numThreads * DEQUEUE_DELAY_THRESHOLD; - } - - return (delay > tooLong); - -} - - -#ifdef _MSC_VER -#ifdef HOST_64BIT -#pragma warning (default : 4716) -#else -#pragma warning (default : 4715) -#endif -#endif - -#endif // !DACCESS_COMPILE diff --git a/src/coreclr/vm/win32threadpool.h b/src/coreclr/vm/win32threadpool.h deleted file mode 100644 index fe215efb7ced85..00000000000000 --- a/src/coreclr/vm/win32threadpool.h +++ /dev/null @@ -1,1030 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - - -/*++ - -Module Name: - - Win32ThreadPool.h - -Abstract: - - This module is the header file for thread pools using Win32 APIs. - -Revision History: - - ---*/ - -#ifndef _WIN32THREADPOOL_H -#define _WIN32THREADPOOL_H - -#include "delegateinfo.h" -#include "util.hpp" -#include "nativeoverlapped.h" -#include "hillclimbing.h" - -#define MAX_WAITHANDLES 64 - -#define MAX_CACHED_EVENTS 40 // upper limit on number of wait events cached - -#define WAIT_REGISTERED 0x01 -#define WAIT_ACTIVE 0x02 -#define WAIT_DELETE 0x04 - -#define WAIT_SINGLE_EXECUTION 0x00000001 -#define WAIT_FREE_CONTEXT 0x00000002 -#define WAIT_INTERNAL_COMPLETION 0x00000004 - -#define QUEUE_ONLY 0x00000000 // do not attempt to call on the thread -#define CALL_OR_QUEUE 0x00000001 // call on the same thread if not too busy, else queue - -const int MaxLimitThreadsPerCPU=250; // upper limit on number of cp threads per CPU -const int MaxFreeCPThreadsPerCPU=2; // upper limit on number of free cp threads per CPU - -const int CpuUtilizationHigh=95; // remove threads when above this -const int CpuUtilizationLow =80; // inject more threads if below this - -#ifndef TARGET_UNIX -extern HANDLE (WINAPI *g_pufnCreateIoCompletionPort)(HANDLE FileHandle, - HANDLE ExistingCompletionPort, - ULONG_PTR CompletionKey, - DWORD NumberOfConcurrentThreads); - -extern int (WINAPI *g_pufnNtQueryInformationThread) (HANDLE ThreadHandle, - THREADINFOCLASS ThreadInformationClass, - PVOID ThreadInformation, - ULONG ThreadInformationLength, - PULONG ReturnLength); - -extern int (WINAPI * g_pufnNtQuerySystemInformation) (SYSTEM_INFORMATION_CLASS SystemInformationClass, - PVOID SystemInformation, - ULONG SystemInformationLength, - PULONG ReturnLength OPTIONAL); -#endif // !TARGET_UNIX - -#define FILETIME_TO_INT64(t) (*(__int64*)&(t)) -#define MILLI_TO_100NANO(x) ((x) * 10000) // convert from milliseond to 100 nanosecond unit - -/** - * This type is supposed to be private to ThreadpoolMgr. - * It's at global scope because Strike needs to be able to access its - * definition. - */ -struct WorkRequest { - WorkRequest* next; - LPTHREAD_START_ROUTINE Function; - PVOID Context; - -}; - -typedef struct _IOCompletionContext -{ - DWORD ErrorCode; - DWORD numBytesTransferred; - LPOVERLAPPED lpOverlapped; - size_t key; -} IOCompletionContext, *PIOCompletionContext; - -typedef DPTR(WorkRequest) PTR_WorkRequest; -class ThreadpoolMgr -{ - friend class ClrDataAccess; - friend struct DelegateInfo; - friend class ThreadPoolNative; - friend class UnManagedPerAppDomainTPCount; - friend class ManagedPerAppDomainTPCount; - friend class PerAppDomainTPCountList; - friend class HillClimbing; - friend struct _DacGlobals; - -public: - struct ThreadCounter - { - static const int MaxPossibleCount = 0x7fff; - - // padding to ensure we get our own cache line - BYTE padding1[MAX_CACHE_LINE_SIZE]; - - union Counts - { - struct - { - // - // Note: these are signed rather than unsigned to allow us to detect under/overflow. - // - int MaxWorking : 16; //Determined by HillClimbing; adjusted elsewhere for timeouts, etc. - int NumActive : 16; //Active means working or waiting on WorkerSemaphore. These are "warm/hot" threads. - int NumWorking : 16; //Trying to get work from various queues. Not waiting on either semaphore. - int NumRetired : 16; //Not trying to get work; waiting on RetiredWorkerSemaphore. These are "cold" threads. - - // Note: the only reason we need "retired" threads at all is that it allows some threads to eventually time out - // even if other threads are getting work. If we ever make WorkerSemaphore a true LIFO semaphore, we will no longer - // need the concept of "retirement" - instead, the very "coldest" threads will naturally be the first to time out. - }; - - LONGLONG AsLongLong; - - bool operator==(Counts other) {LIMITED_METHOD_CONTRACT; return AsLongLong == other.AsLongLong;} - } counts; - - // padding to ensure we get our own cache line - BYTE padding2[MAX_CACHE_LINE_SIZE]; - - Counts GetCleanCounts() - { - LIMITED_METHOD_CONTRACT; -#ifdef HOST_64BIT - // VolatileLoad x64 bit read is atomic - return DangerousGetDirtyCounts(); -#else // !HOST_64BIT - // VolatileLoad may result in torn read - Counts result; -#ifndef DACCESS_COMPILE - result.AsLongLong = InterlockedCompareExchange64(&counts.AsLongLong, 0, 0); - ValidateCounts(result); -#else - result.AsLongLong = 0; //prevents prefast warning for DAC builds -#endif - return result; -#endif // !HOST_64BIT - } - - // - // This does a non-atomic read of the counts. The returned value is suitable only - // for use inside of a read-compare-exchange loop, where the compare-exhcange must succeed - // before any action is taken. Use GetCleanWorkerCounts for other needs, but keep in mind - // it's much slower. - // - Counts DangerousGetDirtyCounts() - { - LIMITED_METHOD_CONTRACT; - Counts result; -#ifndef DACCESS_COMPILE - result.AsLongLong = VolatileLoad(&counts.AsLongLong); -#else - result.AsLongLong = 0; //prevents prefast warning for DAC builds -#endif - return result; - } - - - Counts CompareExchangeCounts(Counts newCounts, Counts oldCounts) - { - LIMITED_METHOD_CONTRACT; - Counts result; -#ifndef DACCESS_COMPILE - result.AsLongLong = InterlockedCompareExchange64(&counts.AsLongLong, newCounts.AsLongLong, oldCounts.AsLongLong); - if (result == oldCounts) - { - // can only do validation on success; if we failed, it may have been due to a previous - // dirty read, which may contain invalid values. - ValidateCounts(result); - ValidateCounts(newCounts); - } -#else - result.AsLongLong = 0; //prevents prefast warning for DAC builds -#endif - return result; - } - - private: - static void ValidateCounts(Counts counts) - { - LIMITED_METHOD_CONTRACT; - _ASSERTE(counts.MaxWorking > 0); - _ASSERTE(counts.NumActive >= 0); - _ASSERTE(counts.NumWorking >= 0); - _ASSERTE(counts.NumRetired >= 0); - _ASSERTE(counts.NumWorking <= counts.NumActive); - } - }; - -public: - - static void ReportThreadStatus(bool isWorking); - - // enumeration of different kinds of memory blocks that are recycled - enum MemType - { - MEMTYPE_AsyncCallback = 0, - MEMTYPE_DelegateInfo = 1, - MEMTYPE_WorkRequest = 2, - MEMTYPE_COUNT = 3, - }; - -#ifndef DACCESS_COMPILE - static void StaticInitialize() - { - WRAPPER_NO_CONTRACT; - - s_usePortableThreadPool = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UsePortableThreadPool) != 0; -#ifdef TARGET_WINDOWS - s_usePortableThreadPoolForIO = - s_usePortableThreadPool && - CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ThreadPool_UsePortableThreadPoolForIO) != 0; -#else // !TARGET_WINDOWS - s_usePortableThreadPoolForIO = s_usePortableThreadPool; -#endif // TARGET_WINDOWS - } -#endif // !DACCESS_COMPILE - - static bool UsePortableThreadPool() - { - LIMITED_METHOD_CONTRACT; - return s_usePortableThreadPool; - } - - static bool UsePortableThreadPoolForIO() - { - LIMITED_METHOD_CONTRACT; - return s_usePortableThreadPoolForIO; - } - - static BOOL Initialize(); - - static BOOL SetMaxThreadsHelper(DWORD MaxWorkerThreads, - DWORD MaxIOCompletionThreads); - - static bool CanSetMinIOCompletionThreads(DWORD ioCompletionThreads); - static bool CanSetMaxIOCompletionThreads(DWORD ioCompletionThreads); - - static BOOL SetMaxThreads(DWORD MaxWorkerThreads, - DWORD MaxIOCompletionThreads); - - static BOOL GetMaxThreads(DWORD* MaxWorkerThreads, - DWORD* MaxIOCompletionThreads); - - static BOOL SetMinThreads(DWORD MinWorkerThreads, - DWORD MinIOCompletionThreads); - - static BOOL GetMinThreads(DWORD* MinWorkerThreads, - DWORD* MinIOCompletionThreads); - - static BOOL GetAvailableThreads(DWORD* AvailableWorkerThreads, - DWORD* AvailableIOCompletionThreads); - - static INT32 GetThreadCount(); - - static BOOL QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, - PVOID Context, - ULONG Flags, - BOOL UnmanagedTPRequest=TRUE); - - static BOOL PostQueuedCompletionStatus(LPOVERLAPPED lpOverlapped, - LPOVERLAPPED_COMPLETION_ROUTINE Function); - - inline static BOOL IsCompletionPortInitialized() - { - LIMITED_METHOD_CONTRACT; - return GlobalCompletionPort != NULL; - } - - static BOOL RegisterWaitForSingleObject(PHANDLE phNewWaitObject, - HANDLE hWaitObject, - WAITORTIMERCALLBACK Callback, - PVOID Context, - ULONG timeout, - DWORD dwFlag); - - static BOOL UnregisterWaitEx(HANDLE hWaitObject,HANDLE CompletionEvent); - static void WaitHandleCleanup(HANDLE hWaitObject); - - static BOOL WINAPI BindIoCompletionCallback(HANDLE FileHandle, - LPOVERLAPPED_COMPLETION_ROUTINE Function, - ULONG Flags, - DWORD& errorCode); - - static void WINAPI WaitIOCompletionCallback(DWORD dwErrorCode, - DWORD numBytesTransferred, - LPOVERLAPPED lpOverlapped); - - static BOOL SetAppDomainRequestsActive(BOOL UnmanagedTP = FALSE); - static void ClearAppDomainRequestsActive(BOOL UnmanagedTP = FALSE, LONG index = -1); - - static inline void UpdateLastDequeueTime() - { - LIMITED_METHOD_CONTRACT; - VolatileStore(&LastDequeueTime, (unsigned int)GetTickCount()); - } - - static void RecycleMemory(LPVOID mem, enum MemType memType); - -#ifndef TARGET_UNIX - static LPOVERLAPPED CompletionPortDispatchWorkWithinAppDomain(Thread* pThread, DWORD* pErrorCode, DWORD* pNumBytes, size_t* pKey); - static void StoreOverlappedInfoInThread(Thread* pThread, DWORD dwErrorCode, DWORD dwNumBytes, size_t key, LPOVERLAPPED lpOverlapped); -#endif // !TARGET_UNIX - - // Enable filtering of correlation ETW events for cases handled at a higher abstraction level - -#ifndef DACCESS_COMPILE - static FORCEINLINE BOOL AreEtwIOQueueEventsSpeciallyHandled(LPOVERLAPPED_COMPLETION_ROUTINE Function) - { - // We handle registered waits at a higher abstraction level - return Function == ThreadpoolMgr::WaitIOCompletionCallback; - } -#endif - -private: - -#ifndef DACCESS_COMPILE - - inline static void FreeWorkRequest(WorkRequest* workRequest) - { - RecycleMemory( workRequest, MEMTYPE_WorkRequest ); //delete workRequest; - } - - inline static WorkRequest* MakeWorkRequest(LPTHREAD_START_ROUTINE function, PVOID context) - { - CONTRACTL - { - THROWS; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END;; - - WorkRequest* wr = (WorkRequest*) GetRecycledMemory(MEMTYPE_WorkRequest); - _ASSERTE(wr); - if (NULL == wr) - return NULL; - wr->Function = function; - wr->Context = context; - wr->next = NULL; - return wr; - } - -#endif // #ifndef DACCESS_COMPILE - - typedef struct { - DWORD numBytes; - ULONG_PTR *key; - LPOVERLAPPED pOverlapped; - DWORD errorCode; - } QueuedStatus; - - typedef DPTR(struct _LIST_ENTRY) PTR_LIST_ENTRY; - typedef struct _LIST_ENTRY { - struct _LIST_ENTRY *Flink; - struct _LIST_ENTRY *Blink; - } LIST_ENTRY, *PLIST_ENTRY; - - struct WaitInfo; - - typedef struct { - HANDLE threadHandle; - DWORD threadId; - CLREvent startEvent; - LONG NumWaitHandles; // number of wait objects registered to the thread <=64 - LONG NumActiveWaits; // number of objects, thread is actually waiting on (this may be less than - // NumWaitHandles since the thread may not have activated some waits - HANDLE waitHandle[MAX_WAITHANDLES]; // array of wait handles (copied from waitInfo since - // we need them to be contiguous) - LIST_ENTRY waitPointer[MAX_WAITHANDLES]; // array of doubly linked list of corresponding waitinfo - } ThreadCB; - - - typedef struct { - ULONG startTime; // time at which wait was started - // endTime = startTime+timeout - ULONG remainingTime; // endTime - currentTime - } WaitTimerInfo; - - struct WaitInfo { - LIST_ENTRY link; // Win9x does not allow duplicate waithandles, so we need to - // group all waits on a single waithandle using this linked list - HANDLE waitHandle; - WAITORTIMERCALLBACK Callback; - PVOID Context; - ULONG timeout; - WaitTimerInfo timer; - DWORD flag; - DWORD state; - ThreadCB* threadCB; - LONG refCount; // when this reaches 0, the waitInfo can be safely deleted - CLREvent PartialCompletionEvent; // used to synchronize deactivation of a wait - CLREvent InternalCompletionEvent; // only one of InternalCompletion or ExternalCompletion is used - // but I cant make a union since CLREvent has a non-default constructor - HANDLE ExternalCompletionEvent; // they are signalled when all callbacks have completed (refCount=0) - OBJECTHANDLE ExternalEventSafeHandle; - - } ; - - // structure used to maintain global information about wait threads. Protected by WaitThreadsCriticalSection - typedef struct WaitThreadTag { - LIST_ENTRY link; - ThreadCB* threadCB; - } WaitThreadInfo; - - - struct AsyncCallback{ - WaitInfo* wait; - BOOL waitTimedOut; - } ; - -#ifndef DACCESS_COMPILE - - static VOID - AcquireAsyncCallback(AsyncCallback *pAsyncCB) - { - LIMITED_METHOD_CONTRACT; - } - - static VOID - ReleaseAsyncCallback(AsyncCallback *pAsyncCB) - { - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - WaitInfo *waitInfo = pAsyncCB->wait; - ThreadpoolMgr::RecycleMemory((LPVOID*)pAsyncCB, ThreadpoolMgr::MEMTYPE_AsyncCallback); - - // if this was a single execution, we now need to stop rooting registeredWaitHandle - // in a GC handle. This will cause the finalizer to pick it up and call the cleanup - // routine. - if ( (waitInfo->flag & WAIT_SINGLE_EXECUTION) && (waitInfo->flag & WAIT_FREE_CONTEXT)) - { - - DelegateInfo* pDelegate = (DelegateInfo*) waitInfo->Context; - - _ASSERTE(pDelegate->m_registeredWaitHandle); - - { - GCX_COOP(); - StoreObjectInHandle(pDelegate->m_registeredWaitHandle, NULL); - } - } - - if (InterlockedDecrement(&waitInfo->refCount) == 0) - ThreadpoolMgr::DeleteWait(waitInfo); - - } - - typedef Holder AsyncCallbackHolder; - inline static AsyncCallback* MakeAsyncCallback() - { - WRAPPER_NO_CONTRACT; - return (AsyncCallback*) GetRecycledMemory(MEMTYPE_AsyncCallback); - } - - static VOID ReleaseInfo(OBJECTHANDLE& hndSafeHandle, - HANDLE hndNativeHandle) - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_TRIGGERS; - } - CONTRACTL_END - -// Use of EX_TRY, GCPROTECT etc in the same function is causing prefast to complain about local variables with -// same name masking each other (#246). The error could not be suppressed with "#pragma PREFAST_SUPPRESS" -#ifndef _PREFAST_ - - if (hndSafeHandle != NULL) - { - - SAFEHANDLEREF refSH = NULL; - - GCX_COOP(); - GCPROTECT_BEGIN(refSH); - - { - EX_TRY - { - // Read the GC handle - refSH = (SAFEHANDLEREF) ObjectToOBJECTREF(ObjectFromHandle(hndSafeHandle)); - - // Destroy the GC handle - DestroyHandle(hndSafeHandle); - - if (refSH != NULL) - { - SafeHandleHolder h(&refSH); - - HANDLE hEvent = refSH->GetHandle(); - if (hEvent != INVALID_HANDLE_VALUE) - { - SetEvent(hEvent); - } - } - } - EX_CATCH - { - } - EX_END_CATCH(SwallowAllExceptions); - } - - GCPROTECT_END(); - - hndSafeHandle = NULL; - } -#endif - } - -#endif // #ifndef DACCESS_COMPILE - - typedef struct { - LIST_ENTRY link; - HANDLE Handle; - } WaitEvent ; - - static VOID AcquireWaitInfo(WaitInfo *pInfo) - { - } - static VOID ReleaseWaitInfo(WaitInfo *pInfo) - { - WRAPPER_NO_CONTRACT; -#ifndef DACCESS_COMPILE - ReleaseInfo(pInfo->ExternalEventSafeHandle, - pInfo->ExternalCompletionEvent); -#endif - } - - typedef Holder WaitInfoHolder; - - // Definitions and data structures to support recycling of high-frequency - // memory blocks. We use a spin-lock to access the list - - class RecycledListInfo - { - static const unsigned int MaxCachedEntries = 40; - - struct Entry - { - Entry* next; - }; - - Volatile lock; // this is the spin lock - DWORD count; // count of number of elements in the list - Entry* root; // ptr to first element of recycled list -#ifndef HOST_64BIT - DWORD filler; // Pad the structure to a multiple of the 16. -#endif - - //--// - -public: - RecycledListInfo() - { - LIMITED_METHOD_CONTRACT; - - lock = 0; - root = NULL; - count = 0; - } - - FORCEINLINE bool CanInsert() - { - LIMITED_METHOD_CONTRACT; - - return count < MaxCachedEntries; - } - - FORCEINLINE LPVOID Remove() - { - LIMITED_METHOD_CONTRACT; - - if(root == NULL) return NULL; // No need for acquiring the lock, there's nothing to remove. - - AcquireLock(); - - Entry* ret = (Entry*)root; - - if(ret) - { - root = ret->next; - count -= 1; - } - - ReleaseLock(); - - return ret; - } - - FORCEINLINE void Insert( LPVOID mem ) - { - LIMITED_METHOD_CONTRACT; - - AcquireLock(); - - Entry* entry = (Entry*)mem; - - entry->next = root; - - root = entry; - count += 1; - - ReleaseLock(); - } - - private: - FORCEINLINE void AcquireLock() - { - LIMITED_METHOD_CONTRACT; - - unsigned int rounds = 0; - - DWORD dwSwitchCount = 0; - - while(lock != 0 || InterlockedExchange( &lock, 1 ) != 0) - { - YieldProcessorNormalized(); // indicate to the processor that we are spinning - - rounds++; - - if((rounds % 32) == 0) - { - __SwitchToThread( 0, ++dwSwitchCount ); - } - } - } - - FORCEINLINE void ReleaseLock() - { - LIMITED_METHOD_CONTRACT; - - lock = 0; - } - }; - - // - // It's critical that we ensure these pointers are allocated by the linker away from - // variables that are modified a lot at runtime. - // - // The use of the CacheGuard is a temporary solution, - // the thread pool has to be refactor away from static variable and - // toward a single global structure, where we can control the locality of variables. - // - class RecycledListsWrapper - { - DWORD CacheGuardPre[MAX_CACHE_LINE_SIZE/sizeof(DWORD)]; - - RecycledListInfo (*pRecycledListPerProcessor)[MEMTYPE_COUNT]; // RecycledListInfo [numProc][MEMTYPE_COUNT] - - DWORD CacheGuardPost[MAX_CACHE_LINE_SIZE/sizeof(DWORD)]; - - public: - void Initialize( unsigned int numProcs ); - - FORCEINLINE bool IsInitialized() - { - LIMITED_METHOD_CONTRACT; - - return pRecycledListPerProcessor != NULL; - } - -#ifndef DACCESS_COMPILE - FORCEINLINE RecycledListInfo& GetRecycleMemoryInfo( enum MemType memType ) - { - LIMITED_METHOD_CONTRACT; - - DWORD processorNumber = 0; - -#ifndef TARGET_UNIX - if (CPUGroupInfo::CanEnableThreadUseAllCpuGroups()) - processorNumber = CPUGroupInfo::CalculateCurrentProcessorNumber(); - else - // Turns out GetCurrentProcessorNumber can return a value greater than the number of processors reported by - // GetSystemInfo, if we're running in WOW64 on a machine with >32 processors. - processorNumber = GetCurrentProcessorNumber()%NumberOfProcessors; -#else // !TARGET_UNIX - if (PAL_HasGetCurrentProcessorNumber()) - { - // On linux, GetCurrentProcessorNumber which uses sched_getcpu() can return a value greater than the number - // of processors reported by sysconf(_SC_NPROCESSORS_ONLN) when using OpenVZ kernel. - processorNumber = GetCurrentProcessorNumber()%NumberOfProcessors; - } -#endif // !TARGET_UNIX - return pRecycledListPerProcessor[processorNumber][memType]; - } -#endif // DACCESS_COMPILE - }; - -#define GATE_THREAD_STATUS_NOT_RUNNING 0 // There is no gate thread -#define GATE_THREAD_STATUS_REQUESTED 1 // There is a gate thread, and someone has asked it to stick around recently -#define GATE_THREAD_STATUS_WAITING_FOR_REQUEST 2 // There is a gate thread, but nobody has asked it to stay. It may die soon - - // Private methods - - static Thread* CreateUnimpersonatedThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpArgs, BOOL *pIsCLRThread); - - static BOOL CreateWorkerThread(); - - static void EnqueueWorkRequest(WorkRequest* wr); - - static WorkRequest* DequeueWorkRequest(); - - static void ExecuteWorkRequest(bool* foundWork, bool* wasNotRecalled); - -#ifndef DACCESS_COMPILE - - inline static void AppendWorkRequest(WorkRequest* entry) - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - if (WorkRequestTail) - { - _ASSERTE(WorkRequestHead != NULL); - WorkRequestTail->next = entry; - } - else - { - _ASSERTE(WorkRequestHead == NULL); - WorkRequestHead = entry; - } - - WorkRequestTail = entry; - _ASSERTE(WorkRequestTail->next == NULL); - } - - inline static WorkRequest* RemoveWorkRequest() - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - WorkRequest* entry = NULL; - if (WorkRequestHead) - { - entry = WorkRequestHead; - WorkRequestHead = entry->next; - if (WorkRequestHead == NULL) - WorkRequestTail = NULL; - } - return entry; - } - -public: - static void EnsureInitialized(); -private: - static void EnsureInitializedSlow(); - -public: - static void InitPlatformVariables(); - - inline static BOOL IsInitialized() - { - LIMITED_METHOD_CONTRACT; - return Initialization == -1; - } - - static void MaybeAddWorkingWorker(); - - static void NotifyWorkItemCompleted() - { - WRAPPER_NO_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - Thread::IncrementWorkerThreadPoolCompletionCount(GetThreadNULLOk()); - UpdateLastDequeueTime(); - } - - static bool ShouldAdjustMaxWorkersActive() - { - WRAPPER_NO_CONTRACT; - _ASSERTE(!UsePortableThreadPool()); - - DWORD priorTime = PriorCompletedWorkRequestsTime; - MemoryBarrier(); // read fresh value for NextCompletedWorkRequestsTime below - DWORD requiredInterval = NextCompletedWorkRequestsTime - priorTime; - DWORD elapsedInterval = GetTickCount() - priorTime; - if (elapsedInterval >= requiredInterval) - { - ThreadCounter::Counts counts = WorkerCounter.GetCleanCounts(); - if (counts.NumActive <= counts.MaxWorking) - return !IsHillClimbingDisabled; - } - - return false; - } - - static void AdjustMaxWorkersActive(); - static bool ShouldWorkerKeepRunning(); - - static DWORD SafeWait(CLREvent * ev, DWORD sleepTime, BOOL alertable); - - static DWORD WINAPI WorkerThreadStart(LPVOID lpArgs); - - static BOOL AddWaitRequest(HANDLE waitHandle, WaitInfo* waitInfo); - - - static ThreadCB* FindWaitThread(); // returns a wait thread that can accommodate another wait request - - static BOOL CreateWaitThread(); - - static void WINAPI InsertNewWaitForSelf(WaitInfo* pArg); - - static int FindWaitIndex(const ThreadCB* threadCB, const HANDLE waitHandle); - - static DWORD MinimumRemainingWait(LIST_ENTRY* waitInfo, unsigned int numWaits); - - static void ProcessWaitCompletion( WaitInfo* waitInfo, - unsigned index, // array index - BOOL waitTimedOut); - - static DWORD WINAPI WaitThreadStart(LPVOID lpArgs); - - static DWORD WINAPI AsyncCallbackCompletion(PVOID pArgs); - - static void DeactivateWait(WaitInfo* waitInfo); - static void DeactivateNthWait(WaitInfo* waitInfo, DWORD index); - - static void DeleteWait(WaitInfo* waitInfo); - - - inline static void ShiftWaitArray( ThreadCB* threadCB, - ULONG SrcIndex, - ULONG DestIndex, - ULONG count) - { - LIMITED_METHOD_CONTRACT; - memmove(&threadCB->waitHandle[DestIndex], - &threadCB->waitHandle[SrcIndex], - count * sizeof(HANDLE)); - memmove(&threadCB->waitPointer[DestIndex], - &threadCB->waitPointer[SrcIndex], - count * sizeof(LIST_ENTRY)); - } - - static void WINAPI DeregisterWait(WaitInfo* pArgs); - -#ifndef TARGET_UNIX - // holds the aggregate of system cpu usage of all processors - typedef struct _PROCESS_CPU_INFORMATION - { - LARGE_INTEGER idleTime; - LARGE_INTEGER kernelTime; - LARGE_INTEGER userTime; - DWORD_PTR affinityMask; - int numberOfProcessors; - SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* usageBuffer; - int usageBufferSize; - } PROCESS_CPU_INFORMATION; - - static int GetCPUBusyTime_NT(PROCESS_CPU_INFORMATION* pOldInfo); - static BOOL CreateCompletionPortThread(LPVOID lpArgs); - static DWORD WINAPI CompletionPortThreadStart(LPVOID lpArgs); -public: - inline static bool HaveNativeWork() - { - LIMITED_METHOD_CONTRACT; - return WorkRequestHead != NULL; - } - - static void GrowCompletionPortThreadpoolIfNeeded(); - static BOOL ShouldGrowCompletionPortThreadpool(ThreadCounter::Counts counts); -#else - static int GetCPUBusyTime_NT(PAL_IOCP_CPU_INFORMATION* pOldInfo); - -#endif // !TARGET_UNIX - - static void PerformGateActivities(int cpuUtilization); - static bool NeedGateThreadForIOCompletions(); - -private: - static BOOL IsIoPending(); - - static BOOL CreateGateThread(); - static void EnsureGateThreadRunning(); - static bool ShouldGateThreadKeepRunning(); - static DWORD WINAPI GateThreadStart(LPVOID lpArgs); - 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 - ); - static BOOL SufficientDelaySinceLastDequeue(); - - static LPVOID GetRecycledMemory(enum MemType memType); - - inline static DWORD QueueDeregisterWait(HANDLE waitThread, WaitInfo* waitInfo) - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - } - CONTRACTL_END; - - _ASSERTE(!UsePortableThreadPool()); - - DWORD result = QueueUserAPC(reinterpret_cast(DeregisterWait), waitThread, reinterpret_cast(waitInfo)); - SetWaitThreadAPCPending(); - return result; - } - - - inline static void SetWaitThreadAPCPending() {IsApcPendingOnWaitThread = TRUE;} - inline static void ResetWaitThreadAPCPending() {IsApcPendingOnWaitThread = FALSE;} - inline static BOOL IsWaitThreadAPCPending() {return IsApcPendingOnWaitThread;} - -#endif // #ifndef DACCESS_COMPILE - // Private variables - - static Volatile Initialization; // indicator of whether the threadpool is initialized. - - static bool s_usePortableThreadPool; - static bool s_usePortableThreadPoolForIO; - - SVAL_DECL(LONG,MinLimitTotalWorkerThreads); // same as MinLimitTotalCPThreads - SVAL_DECL(LONG,MaxLimitTotalWorkerThreads); // same as MaxLimitTotalCPThreads - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static unsigned int LastDequeueTime; // used to determine if work items are getting thread starved - - static HillClimbing HillClimbingInstance; - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static LONG PriorCompletedWorkRequests; - static DWORD PriorCompletedWorkRequestsTime; - static DWORD NextCompletedWorkRequestsTime; - - static LARGE_INTEGER CurrentSampleStartTime; - - static unsigned int WorkerThreadSpinLimit; - static bool IsHillClimbingDisabled; - static int ThreadAdjustmentInterval; - - SPTR_DECL(WorkRequest,WorkRequestHead); // Head of work request queue - SPTR_DECL(WorkRequest,WorkRequestTail); // Head of work request queue - - static unsigned int LastCPThreadCreation; // last time a completion port thread was created - static unsigned int NumberOfProcessors; // = NumberOfWorkerThreads - no. of blocked threads - - static BOOL IsApcPendingOnWaitThread; // Indicates if an APC is pending on the wait thread - - // This needs to be non-hosted, because worker threads can run prior to EE startup. - static DangerousNonHostedSpinLock ThreadAdjustmentLock; - -public: - static CrstStatic WorkerCriticalSection; - -private: - static const DWORD WorkerTimeout = 20 * 1000; - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) SVAL_DECL(ThreadCounter,WorkerCounter); - - // - // WorkerSemaphore is an UnfairSemaphore because: - // 1) Threads enter and exit this semaphore very frequently, and thus benefit greatly from the spinning done by UnfairSemaphore - // 2) There is no functional reason why any particular thread should be preferred when waking workers. This only impacts performance, - // and un-fairness helps performance in this case. - // - static CLRLifoSemaphore* WorkerSemaphore; - - // - // RetiredWorkerSemaphore is a regular CLRSemaphore, not an UnfairSemaphore, because if a thread waits on this semaphore is it almost certainly - // NOT going to be released soon, so the spinning done in UnfairSemaphore only burns valuable CPU time. However, if UnfairSemaphore is ever - // implemented in terms of a Win32 IO Completion Port, we should reconsider this. The IOCP's LIFO unblocking behavior could help keep working set - // down, by constantly re-using the same small set of retired workers rather than round-robining between all of them as CLRSemaphore will do. - // If we go that route, we should add a "no-spin" option to UnfairSemaphore.Wait to avoid wasting CPU. - // - static CLRLifoSemaphore* RetiredWorkerSemaphore; - - static CLREvent * RetiredCPWakeupEvent; - - static CrstStatic WaitThreadsCriticalSection; - static LIST_ENTRY WaitThreadsHead; // queue of wait threads, each thread can handle upto 64 waits - - static BOOL InitCompletionPortThreadpool; // flag indicating whether completion port threadpool has been initialized - static HANDLE GlobalCompletionPort; // used for binding io completions on file handles - -public: - SVAL_DECL(ThreadCounter,CPThreadCounter); - -private: - SVAL_DECL(LONG,MaxLimitTotalCPThreads); // = MaxLimitCPThreadsPerCPU * number of CPUS - SVAL_DECL(LONG,MinLimitTotalCPThreads); - SVAL_DECL(LONG,MaxFreeCPThreads); // = MaxFreeCPThreadsPerCPU * Number of CPUS - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static LONG GateThreadStatus; // See GateThreadStatus enumeration - - SVAL_DECL(LONG,cpuUtilization); - - DECLSPEC_ALIGN(MAX_CACHE_LINE_SIZE) static RecycledListsWrapper RecycledLists; -}; - - - - -#endif // _WIN32THREADPOOL_H 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 26512a4e81c636..4564af6f9ca89d 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 @@ -105,8 +105,6 @@ private static void GateThreadStart() int cpuUtilization = (int)cpuUtilizationReader.CurrentUtilization; threadPoolInstance._cpuUtilization = cpuUtilization; - bool needGateThreadForRuntime = ThreadPool.PerformRuntimeSpecificGateActivities(cpuUtilization); - if (!disableStarvationDetection && threadPoolInstance._pendingBlockingAdjustment == PendingBlockingAdjustment.None && threadPoolInstance._separated.numRequestedWorkers > 0 && @@ -164,8 +162,7 @@ private static void GateThreadStart() } } - if (!needGateThreadForRuntime && - threadPoolInstance._separated.numRequestedWorkers <= 0 && + if (threadPoolInstance._separated.numRequestedWorkers <= 0 && threadPoolInstance._pendingBlockingAdjustment == PendingBlockingAdjustment.None && Interlocked.Decrement(ref threadPoolInstance._separated.gateThreadRunningState) <= GetRunningStateForNumRuns(0)) { @@ -334,7 +331,5 @@ public bool HasBlockingAdjustmentDelayElapsed(int currentTimeMs, bool wasSignale } } } - - internal static void EnsureGateThreadRunning() => GateThread.EnsureRunning(ThreadPoolInstance); } } 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 57f25de034c249..7adfe5a0b9ca42 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -29,6 +29,12 @@ internal sealed partial class PortableThreadPool private const int CpuUtilizationHigh = 95; private const int CpuUtilizationLow = 80; +#if CORECLR +#pragma warning disable CA1823 + private static readonly bool s_initialized = ThreadPool.EnsureConfigInitialized(); +#pragma warning restore CA1823 +#endif + private static readonly short ForcedMinWorkerThreads = AppContextConfigHelper.GetInt16Config("System.Threading.ThreadPool.MinThreads", 0, false); private static readonly short ForcedMaxWorkerThreads = @@ -151,9 +157,7 @@ public bool SetMinThreads(int workerThreads, int ioCompletionThreads) return false; } - if (ThreadPool.UsePortableThreadPoolForIO - ? ioCompletionThreads > _legacy_maxIOCompletionThreads - : !ThreadPool.CanSetMinIOCompletionThreads(ioCompletionThreads)) + if (ioCompletionThreads > _legacy_maxIOCompletionThreads) { return false; } @@ -163,14 +167,7 @@ public bool SetMinThreads(int workerThreads, int ioCompletionThreads) return false; } - if (ThreadPool.UsePortableThreadPoolForIO) - { - _legacy_minIOCompletionThreads = (short)Math.Max(1, ioCompletionThreads); - } - else - { - ThreadPool.SetMinIOCompletionThreads(ioCompletionThreads); - } + _legacy_minIOCompletionThreads = (short)Math.Max(1, ioCompletionThreads); short newMinThreads = (short)Math.Max(1, workerThreads); if (newMinThreads == _minThreads) @@ -243,9 +240,7 @@ public bool SetMaxThreads(int workerThreads, int ioCompletionThreads) return false; } - if (ThreadPool.UsePortableThreadPoolForIO - ? ioCompletionThreads < _legacy_minIOCompletionThreads - : !ThreadPool.CanSetMaxIOCompletionThreads(ioCompletionThreads)) + if (ioCompletionThreads < _legacy_minIOCompletionThreads) { return false; } @@ -255,14 +250,7 @@ public bool SetMaxThreads(int workerThreads, int ioCompletionThreads) return false; } - if (ThreadPool.UsePortableThreadPoolForIO) - { - _legacy_maxIOCompletionThreads = (short)Math.Min(ioCompletionThreads, MaxPossibleThreadCount); - } - else - { - ThreadPool.SetMaxIOCompletionThreads(ioCompletionThreads); - } + _legacy_maxIOCompletionThreads = (short)Math.Min(ioCompletionThreads, MaxPossibleThreadCount); short newMaxThreads = (short)Math.Min(workerThreads, MaxPossibleThreadCount); if (newMaxThreads == _maxThreads) 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 05de84a64cbdb2..e216cbb78d55c2 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 @@ -109,11 +109,7 @@ internal void RestartTimeout() /// internal PortableThreadPool.WaitThread? WaitThread { get; set; } -#if CORECLR - private bool UnregisterPortable(WaitHandle waitObject) -#else public bool Unregister(WaitHandle waitObject) -#endif { // 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 diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs index da8e83c5b009e9..024110ea93b925 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs @@ -14,8 +14,6 @@ public static partial class ThreadPool [SupportedOSPlatform("windows")] public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) { - Debug.Assert(UsePortableThreadPoolForIO); - if (overlapped == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped); 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 c831d39e28466f..39e1d6453263e7 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 @@ -17,8 +17,6 @@ internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkIt public static partial class ThreadPool { - internal static bool UsePortableThreadPoolForIO => true; - // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that // the runtime may use the thread for processing other work internal static bool YieldFromDispatchLoop => false; @@ -75,15 +73,6 @@ public static void GetAvailableThreads(out int workerThreads, out int completion /// 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 -#pragma warning disable IDE0060 - internal static bool PerformRuntimeSpecificGateActivities(int cpuUtilization) => false; -#pragma warning restore IDE0060 - internal static void NotifyWorkItemProgress() => PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs index 097277e894d9db..f80107fcf1a66a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs @@ -17,8 +17,7 @@ private static ThreadPoolBoundHandle BindHandleCore(SafeHandle handle) try { Debug.Assert(OperatingSystem.IsWindows()); - // ThreadPool.BindHandle will always return true, otherwise, it throws. See the underlying FCall - // implementation in ThreadPoolNative::CorBindIoCompletionCallback to see the implementation. + // ThreadPool.BindHandle will always return true, otherwise, it throws. bool succeeded = ThreadPool.BindHandle(handle); Debug.Assert(succeeded); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs index 55e6fcae7bf176..21a1e6bf0acb90 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs @@ -1682,7 +1682,7 @@ public static long PendingWorkItemCount get { ThreadPoolWorkQueue workQueue = s_workQueue; - return ThreadPoolWorkQueue.LocalCount + workQueue.GlobalCount + PendingUnmanagedWorkItemCount; + return ThreadPoolWorkQueue.LocalCount + workQueue.GlobalCount; } } } 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 2816f0ab6a858e..0e7892d36f3d92 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 @@ -7,7 +7,7 @@ namespace System.Threading { // - // Unix-specific implementation of Timer + // Portable implementation of Timer // internal sealed partial class TimerQueue : IThreadPoolWorkItem { diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs index c8e04b4dcd9566..29e2eb99d1c6e5 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Browser.Mono.cs @@ -131,7 +131,7 @@ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapp ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped); } - // OS doesn't signal handle, so do it here (CoreCLR does this assignment in ThreadPoolNative::CorPostQueuedCompletionStatus) + // OS doesn't signal handle, so do it here overlapped->InternalLow = (IntPtr)0; // Both types of callbacks are executed on the same thread pool return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (nint)overlapped, preferLocal: false); diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs index fb700981fb26e3..6a6dd5b82e0060 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs @@ -13,7 +13,5 @@ public static partial class ThreadPool internal static void ReportThreadStatus(bool isWorking) { } - - private static long PendingUnmanagedWorkItemCount => 0; } } diff --git a/src/mono/mono/eventpipe/ep-rt-mono.h b/src/mono/mono/eventpipe/ep-rt-mono.h index dc07922690cce5..348c7cbd3526a2 100644 --- a/src/mono/mono/eventpipe/ep-rt-mono.h +++ b/src/mono/mono/eventpipe/ep-rt-mono.h @@ -1002,15 +1002,6 @@ ep_rt_config_value_get_output_streaming (void) return enable; } -static -inline -bool -ep_rt_config_value_get_use_portable_thread_pool (void) -{ - // Only supports portable thread pool. - return true; -} - static inline uint32_t diff --git a/src/native/eventpipe/ep-rt.h b/src/native/eventpipe/ep-rt.h index 96ab9b94663809..086975834d820f 100644 --- a/src/native/eventpipe/ep-rt.h +++ b/src/native/eventpipe/ep-rt.h @@ -402,10 +402,6 @@ inline bool ep_rt_config_value_get_output_streaming (void); -static -bool -ep_rt_config_value_get_use_portable_thread_pool (void); - /* * EventPipeSampleProfiler. */