From 057ceed0eedc7fdc716fa1fcb6c9daa3f2ad830d Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sat, 8 Jan 2022 11:49:50 -0800 Subject: [PATCH 01/13] Add support for Windows IO completions to the portable thread pool - Added an implementation for BindHandle - Polling for IO completions is done in batches on separate threads similarly to what is done on Unixes - Added a high-priority work item queue to have IO completion work items run at higher priority - Removed the time-sensitive work item queue, used the high-priority queue instead --- .../System.Private.CoreLib.csproj | 6 +- .../src/System/Threading/Overlapped.cs | 28 +- .../Threading/ThreadPool.CoreCLR.Windows.cs | 75 ++++ .../System/Threading/ThreadPool.CoreCLR.cs | 162 ++++---- src/coreclr/inc/clrconfigvalues.h | 1 + .../src/System.Private.CoreLib.csproj | 13 +- .../src/System/Threading/Overlapped.cs | 55 --- ...llocatedOverlapped.PlatformNotSupported.cs | 32 -- .../src/System/Threading/ThreadPool.CoreRT.cs | 30 -- .../System/Threading/ThreadPool.Windows.cs | 38 +- src/coreclr/vm/comthreadpool.cpp | 40 +- src/coreclr/vm/comthreadpool.h | 4 - src/coreclr/vm/corelib.h | 3 - src/coreclr/vm/ecalllist.h | 3 - src/coreclr/vm/nativeoverlapped.cpp | 1 + src/coreclr/vm/threads.cpp | 2 + src/coreclr/vm/win32threadpool.cpp | 157 +++---- src/coreclr/vm/win32threadpool.h | 34 +- .../Kernel32/Interop.CompletionPort.cs | 22 +- .../System.Private.CoreLib.Shared.projitems | 10 +- .../LowLevelLifoSemaphore.Windows.cs | 4 +- ...ntSource.PortableThreadPool.NativeSinks.cs | 25 ++ ...veRuntimeEventSource.PortableThreadPool.cs | 25 ++ .../Overlapped._IOCompletionCallback.cs | 69 ++++ .../PortableThreadPool.IO.Windows.cs | 186 +++++++++ .../PortableThreadPool.WaitThread.cs | 3 +- .../System/Threading/PortableThreadPool.cs | 92 ++++- .../Threading/PreAllocatedOverlapped.cs} | 2 - .../Threading/ThreadInt64PersistentCounter.cs | 31 +- .../Threading/ThreadPool.Portable.Unix.cs | 25 ++ .../Threading/ThreadPool.Portable.Windows.cs | 56 +++ .../System/Threading/ThreadPool.Portable.cs | 45 +- .../Threading/ThreadPoolBoundHandle.Unix.cs} | 0 .../ThreadPoolBoundHandle.Windows.cs} | 0 .../Threading/ThreadPoolBoundHandle.cs} | 0 .../ThreadPoolBoundHandleOverlapped.cs} | 0 .../System/Threading/ThreadPoolWorkQueue.cs | 386 ++++++++++-------- .../System/Threading/TimerQueue.Portable.cs | 2 +- .../src/System/ThrowHelper.cs | 9 + .../System.Private.CoreLib.csproj | 4 +- .../src/System/Threading/Overlapped.cs | 56 --- ...=> PreAllocatedOverlapped.Browser.Mono.cs} | 0 .../Threading/ThreadPool.Browser.Mono.cs | 29 ++ .../src/System/Threading/ThreadPool.Mono.cs | 29 -- .../ThreadPoolBoundHandle.Browser.Mono.cs} | 0 45 files changed, 1101 insertions(+), 693 deletions(-) create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs delete mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.PlatformNotSupported.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped._IOCompletionCallback.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs rename src/{coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs => libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs} (98%) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Unix.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs rename src/{coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.Unix.cs => libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Unix.cs} (100%) rename src/{coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.Windows.cs => libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs} (100%) rename src/{coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.cs => libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.cs} (100%) rename src/{coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandleOverlapped.cs => libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandleOverlapped.cs} (100%) rename src/mono/System.Private.CoreLib/src/System/Threading/{PreAllocatedOverlapped.cs => PreAllocatedOverlapped.Browser.Mono.cs} (100%) rename src/{libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.PlatformNotSupported.cs => mono/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Browser.Mono.cs} (100%) diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 9cbfee4fb88b0b..342668146a8730 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -232,9 +232,6 @@ - - - @@ -289,14 +286,13 @@ - Common\Interop\Windows\OleAut32\Interop.VariantClear.cs - + 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 916f10b40e541c..d65e4df83a75d7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Overlapped.cs @@ -27,28 +27,8 @@ namespace System.Threading { #region class _IOCompletionCallback - internal unsafe class _IOCompletionCallback + internal unsafe partial class _IOCompletionCallback { - private readonly IOCompletionCallback _ioCompletionCallback; - private readonly ExecutionContext _executionContext; - private uint _errorCode; // Error code - private uint _numBytes; // No. of bytes transferred - private NativeOverlapped* _pNativeOverlapped; - - internal _IOCompletionCallback(IOCompletionCallback ioCompletionCallback, ExecutionContext executionContext) - { - _ioCompletionCallback = ioCompletionCallback; - _executionContext = executionContext; - } - // Context callback: same sig for SendOrPostCallback and ContextCallback - internal static ContextCallback _ccb = new ContextCallback(IOCompletionCallback_Context); - internal static void IOCompletionCallback_Context(object? state) - { - _IOCompletionCallback? helper = (_IOCompletionCallback?)state; - Debug.Assert(helper != null, "_IOCompletionCallback cannot be null"); - helper._ioCompletionCallback(helper._errorCode, helper._numBytes, helper._pNativeOverlapped); - } - // call back helper internal static void PerformIOCompletionCallback(uint errorCode, uint numBytes, NativeOverlapped* pNativeOverlapped) { @@ -69,7 +49,7 @@ internal static void PerformIOCompletionCallback(uint errorCode, uint numBytes, helper._errorCode = errorCode; helper._numBytes = numBytes; helper._pNativeOverlapped = pNativeOverlapped; - ExecutionContext.RunInternal(helper._executionContext, _ccb, helper); + ExecutionContext.RunInternal(helper._executionContext, IOCompletionCallback_Context_Delegate, helper); } // Quickly check the VM again, to see if a packet has arrived. @@ -132,8 +112,6 @@ internal sealed unsafe class OverlappedData return AllocateNativeOverlapped(); } - internal bool IsUserObject(byte[]? buffer) => ReferenceEquals(_userObject, buffer); - [MethodImpl(MethodImplOptions.InternalCall)] private extern NativeOverlapped* AllocateNativeOverlapped(); @@ -254,8 +232,6 @@ public static unsafe void Free(NativeOverlapped* nativeOverlappedPtr!!) OverlappedData.GetOverlappedFromNative(nativeOverlappedPtr)._overlapped._overlappedData = null; OverlappedData.FreeNativeOverlapped(nativeOverlappedPtr); } - - internal bool IsUserObject(byte[]? buffer) => _overlappedData!.IsUserObject(buffer); } #endregion class Overlapped 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 new file mode 100644 index 00000000000000..02dfefc1bfdbbe --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace System.Threading +{ + public static partial class ThreadPool + { + [CLSCompliant(false)] + [SupportedOSPlatform("windows")] + public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) + { + if (overlapped == null) + { + 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; + } + + return PostQueuedCompletionStatus(overlapped); + } + + [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); + } + + [SupportedOSPlatform("windows")] + public static bool BindHandle(SafeHandle osHandle!!) + { + bool mustReleaseSafeHandle = false; + try + { + osHandle.DangerousAddRef(ref mustReleaseSafeHandle); + + if (UsePortableThreadPoolForIO) + { + PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle.DangerousGetHandle()); + return true; + } + + return BindIOCompletionCallbackNative(osHandle.DangerousGetHandle()); + } + finally + { + if (mustReleaseSafeHandle) + 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 de9126d1a346d7..b5a76017f3640d 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 @@ -162,23 +162,24 @@ public UnmanagedThreadPoolWorkItem(IntPtr callback, IntPtr state) public static partial class ThreadPool { + private static readonly byte UsePortableThreadPoolConfigValues = InitializeConfigAndDetermineUsePortableThreadPool(); + // SOS's ThreadPool command depends on this name - internal static readonly bool UsePortableThreadPool = InitializeConfigAndDetermineUsePortableThreadPool(); + internal static readonly bool UsePortableThreadPool = UsePortableThreadPoolConfigValues != 0; + + internal static readonly bool UsePortableThreadPoolForIO = UsePortableThreadPoolConfigValues > 1; - // Time-senstiive work items are those that may need to run ahead of normal work items at least periodically. For a - // runtime that does not support time-sensitive work items on the managed side, the thread pool yields the thread to the - // runtime periodically (by exiting the dispatch loop) so that the runtime may use that thread for processing - // any time-sensitive work. For a runtime that supports time-sensitive work items on the managed side, the thread pool - // does not yield the thread and instead processes time-sensitive work items queued by specific APIs periodically. - internal static bool SupportsTimeSensitiveWorkItems => UsePortableThreadPool; + // 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; // 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 bool InitializeConfigAndDetermineUsePortableThreadPool() + private static unsafe byte InitializeConfigAndDetermineUsePortableThreadPool() { - bool usePortableThreadPool = false; + byte usePortableThreadPoolConfigValues = 0; int configVariableIndex = 0; while (true) { @@ -198,10 +199,10 @@ private static unsafe bool InitializeConfigAndDetermineUsePortableThreadPool() if (appContextConfigNameUnsafe == null) { - // Special case for UsePortableThreadPool, which doesn't go into the AppContext + // Special case for UsePortableThreadPool and similar, which don't go into the AppContext Debug.Assert(configValue != 0); - Debug.Assert(isBoolean); - usePortableThreadPool = true; + Debug.Assert(!isBoolean); + usePortableThreadPoolConfigValues = (byte)configValue; continue; } @@ -216,7 +217,7 @@ private static unsafe bool InitializeConfigAndDetermineUsePortableThreadPool() } } - return usePortableThreadPool; + return usePortableThreadPoolConfigValues; } [MethodImpl(MethodImplOptions.InternalCall)] @@ -237,6 +238,7 @@ private static bool GetEnableWorkerTracking() => 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 @@ -249,6 +251,7 @@ internal static void SetMinIOCompletionThreads(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 @@ -270,11 +273,18 @@ public static bool SetMaxThreads(int workerThreads, int completionPortThreads) public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) { - GetMaxThreadsNative(out workerThreads, out completionPortThreads); - - if (UsePortableThreadPool) + if (UsePortableThreadPoolForIO) + { + PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out completionPortThreads); + } + else if (UsePortableThreadPool) + { + PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out _); + GetMaxThreadsNative(out _, out completionPortThreads); + } + else { - workerThreads = PortableThreadPool.ThreadPoolInstance.GetMaxThreads(); + GetMaxThreadsNative(out workerThreads, out completionPortThreads); } } @@ -293,21 +303,35 @@ public static bool SetMinThreads(int workerThreads, int completionPortThreads) public static void GetMinThreads(out int workerThreads, out int completionPortThreads) { - GetMinThreadsNative(out workerThreads, out completionPortThreads); - - if (UsePortableThreadPool) + if (UsePortableThreadPoolForIO) + { + PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out completionPortThreads); + } + else if (UsePortableThreadPool) { - workerThreads = PortableThreadPool.ThreadPoolInstance.GetMinThreads(); + PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out _); + GetMinThreadsNative(out _, out completionPortThreads); + } + else + { + GetMinThreadsNative(out workerThreads, out completionPortThreads); } } public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) { - GetAvailableThreadsNative(out workerThreads, out completionPortThreads); - - if (UsePortableThreadPool) + if (UsePortableThreadPoolForIO) + { + PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads); + } + else if (UsePortableThreadPool) + { + PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out _); + GetAvailableThreadsNative(out _, out completionPortThreads); + } + else { - workerThreads = PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(); + GetAvailableThreadsNative(out workerThreads, out completionPortThreads); } } @@ -317,8 +341,22 @@ public static void GetAvailableThreads(out int workerThreads, out int completion /// /// For a thread pool implementation that may have different types of threads, the count includes all types. /// - public static int ThreadCount => - (UsePortableThreadPool ? PortableThreadPool.ThreadPoolInstance.ThreadCount : 0) + GetThreadCount(); + public static int ThreadCount + { + get + { + int count = 0; + if (UsePortableThreadPool) + { + count += PortableThreadPool.ThreadPoolInstance.ThreadCount; + } + if (!UsePortableThreadPoolForIO) + { + count += GetThreadCount(); + } + return count; + } + } [MethodImpl(MethodImplOptions.InternalCall)] private static extern int GetThreadCount(); @@ -333,11 +371,15 @@ public static long CompletedWorkItemCount { get { - long count = GetCompletedWorkItemCount(); + long count = 0; if (UsePortableThreadPool) { count += PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount; } + if (!UsePortableThreadPoolForIO) + { + count += GetCompletedWorkItemCount(); + } return count; } } @@ -385,22 +427,6 @@ private static RegisteredWaitHandle RegisterWaitForSingleObject( return registeredWaitHandle; } - internal static void UnsafeQueueWaitCompletion(CompleteWaitThreadPoolWorkItem completeWaitWorkItem) - { - Debug.Assert(UsePortableThreadPool); - -#if TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows - QueueWaitCompletionNative(completeWaitWorkItem); -#else - UnsafeQueueUserWorkItemInternal(completeWaitWorkItem, preferLocal: false); -#endif - } - -#if TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void QueueWaitCompletionNative(CompleteWaitThreadPoolWorkItem completeWaitWorkItem); -#endif - internal static void RequestWorkerThread() { if (UsePortableThreadPool) @@ -419,6 +445,8 @@ internal static void RequestWorkerThread() private static void EnsureGateThreadRunning() { Debug.Assert(UsePortableThreadPool); + Debug.Assert(!UsePortableThreadPoolForIO); + PortableThreadPool.EnsureGateThreadRunning(); } @@ -430,25 +458,23 @@ private static void EnsureGateThreadRunning() internal static bool PerformRuntimeSpecificGateActivities(int cpuUtilization) { Debug.Assert(UsePortableThreadPool); + + if (UsePortableThreadPoolForIO) + { + return false; + } + return PerformRuntimeSpecificGateActivitiesNative(cpuUtilization) != Interop.BOOL.FALSE; } [GeneratedDllImport(RuntimeHelpers.QCall, EntryPoint = "ThreadPool_PerformGateActivities")] private static partial Interop.BOOL PerformRuntimeSpecificGateActivitiesNative(int cpuUtilization); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern unsafe bool PostQueuedCompletionStatus(NativeOverlapped* overlapped); - - [CLSCompliant(false)] - [SupportedOSPlatform("windows")] - public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) => - PostQueuedCompletionStatus(overlapped); - // Entry point from unmanaged code private static void UnsafeQueueUnmanagedWorkItem(IntPtr callback, IntPtr state) { - Debug.Assert(SupportsTimeSensitiveWorkItems); - UnsafeQueueTimeSensitiveWorkItemInternal(new UnmanagedThreadPoolWorkItem(callback, state)); + Debug.Assert(UsePortableThreadPool); + UnsafeQueueHighPriorityWorkItemInternal(new UnmanagedThreadPoolWorkItem(callback, state)); } // Native methods: @@ -536,33 +562,5 @@ private static extern IntPtr RegisterWaitForSingleObjectNative( bool executeOnlyOnce, RegisteredWaitHandle registeredWaitHandle ); - - [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] - [SupportedOSPlatform("windows")] - public static bool BindHandle(IntPtr osHandle) - { - return BindIOCompletionCallbackNative(osHandle); - } - - [SupportedOSPlatform("windows")] - public static bool BindHandle(SafeHandle osHandle!!) - { - bool ret = false; - bool mustReleaseSafeHandle = false; - try - { - osHandle.DangerousAddRef(ref mustReleaseSafeHandle); - ret = BindIOCompletionCallbackNative(osHandle.DangerousGetHandle()); - } - finally - { - if (mustReleaseSafeHandle) - osHandle.DangerousRelease(); - } - return ret; - } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool BindIOCompletionCallbackNative(IntPtr fileHandle); } } diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index 57107913ce274f..01561e8f7e3f6b 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -531,6 +531,7 @@ 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/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index f757e1a5db499b..829c2a570ed2b0 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -297,22 +297,23 @@ Interop\Windows\Kernel32\Interop.DynamicLoad.cs - + + Interop\Windows\Kernel32\Interop.ThreadPoolIO.cs + + + + - + - - Interop\Windows\Kernel32\Interop.ThreadPoolIO.cs - - Interop\Unix\System.Native\Interop.Abort.cs diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Overlapped.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Overlapped.cs index 2c34e68fb5a789..ad1dd5a23c663c 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Overlapped.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Overlapped.cs @@ -25,61 +25,6 @@ namespace System.Threading { - #region class _IOCompletionCallback - - internal unsafe class _IOCompletionCallback - { - private IOCompletionCallback _ioCompletionCallback; - private ExecutionContext _executionContext; - private uint _errorCode; // Error code - private uint _numBytes; // No. of bytes transferred - private NativeOverlapped* _pNativeOverlapped; - - internal _IOCompletionCallback(IOCompletionCallback ioCompletionCallback, ExecutionContext executionContext) - { - _ioCompletionCallback = ioCompletionCallback; - _executionContext = executionContext; - } - - // Context callback: same sig for SendOrPostCallback and ContextCallback - internal static ContextCallback s_ccb = new ContextCallback(IOCompletionCallback_Context); - private static void IOCompletionCallback_Context(object? state) - { - Debug.Assert(state != null, "_IOCompletionCallback cannot be null"); - _IOCompletionCallback helper = (_IOCompletionCallback)state; - helper._ioCompletionCallback(helper._errorCode, helper._numBytes, helper._pNativeOverlapped); - } - - internal static unsafe 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; - helper._errorCode = errorCode; - helper._numBytes = numBytes; - helper._pNativeOverlapped = pNativeOverlapped; - ExecutionContext.Run(helper._executionContext, s_ccb, helper); - } - - //Quickly check the VM again, to see if a packet has arrived. - //OverlappedData.CheckVMForIOPacket(out pOVERLAP, out errorCode, out numBytes); - pNativeOverlapped = null; - } while (pNativeOverlapped != null); - } - } - - #endregion class _IOCompletionCallback - #region class OverlappedData internal unsafe sealed class OverlappedData diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.PlatformNotSupported.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.PlatformNotSupported.cs deleted file mode 100644 index 781f1a94b2dadd..00000000000000 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.PlatformNotSupported.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace System.Threading -{ - public sealed class PreAllocatedOverlapped : IDisposable - { - [CLSCompliant(false)] - public PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData) : - this(callback, state, pinData, flowExecutionContext: true) - { - } - - [CLSCompliant(false)] - public static PreAllocatedOverlapped UnsafeCreate(IOCompletionCallback callback, object? state, object? pinData) => - new PreAllocatedOverlapped(callback, state, pinData, flowExecutionContext: false); - - private PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext) - { - if (callback == null) - throw new ArgumentNullException(nameof(callback)); - - throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO); - } - - public void Dispose() - { - } - } -} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreRT.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreRT.cs index 4d4e661160eb4e..1811bfad804062 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreRT.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreRT.cs @@ -24,36 +24,6 @@ internal static void ReportThreadStatus(bool isWorking) { } - private static unsafe void NativeOverlappedCallback(object? obj) - { - Debug.Assert(obj != null); - NativeOverlapped* overlapped = (NativeOverlapped*)(IntPtr)obj; - _IOCompletionCallback.PerformIOCompletionCallback(0, 0, overlapped); - } - - [CLSCompliant(false)] - [SupportedOSPlatform("windows")] - public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) - { - // OS doesn't signal handle, so do it here (CoreCLR does this assignment in ThreadPoolNative::CorPostQueuedCompletionStatus) - overlapped->InternalLow = (IntPtr)0; - // Both types of callbacks are executed on the same thread pool - return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (IntPtr)overlapped); - } - - [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] - [SupportedOSPlatform("windows")] - public static bool BindHandle(IntPtr osHandle) - { - throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle - } - - [SupportedOSPlatform("windows")] - public static bool BindHandle(SafeHandle osHandle) - { - throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle - } - 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 44536e1145e646..c357b243ac782d 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 @@ -235,7 +235,13 @@ public static partial class ThreadPool { internal const bool IsWorkerTrackingEnabledInConfig = false; - internal const bool SupportsTimeSensitiveWorkItems = false; // the timer currently doesn't queue time-sensitive work + // 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. + // + // Windows thread pool threads need to yield back to the thread pool periodically, otherwise those threads may be + // considered to be doing long-running work and change thread pool heuristics, such as slowing or halting thread + // injection. + internal static bool YieldFromDispatchLoop => true; /// /// The maximum number of threads in the default thread pool on Windows 10 as computed by @@ -405,5 +411,35 @@ private static RegisteredWaitHandle RegisterWaitForSingleObject( registeredWaitHandle.RestartWait(); return registeredWaitHandle; } + + private static unsafe void NativeOverlappedCallback(object? obj) + { + Debug.Assert(obj != null); + NativeOverlapped* overlapped = (NativeOverlapped*)(IntPtr)obj; + _IOCompletionCallback.PerformSingleIOCompletionCallback(overlapped, 0, 0); + } + + [CLSCompliant(false)] + [SupportedOSPlatform("windows")] + public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) + { + // OS doesn't signal handle, so do it here (CoreCLR does this assignment in ThreadPoolNative::CorPostQueuedCompletionStatus) + overlapped->InternalLow = (IntPtr)0; + // Both types of callbacks are executed on the same thread pool + return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (IntPtr)overlapped); + } + + [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] + [SupportedOSPlatform("windows")] + public static bool BindHandle(IntPtr osHandle) + { + throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle + } + + [SupportedOSPlatform("windows")] + public static bool BindHandle(SafeHandle osHandle) + { + throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle + } } } diff --git a/src/coreclr/vm/comthreadpool.cpp b/src/coreclr/vm/comthreadpool.cpp index ddb84a1f8d45a2..9cf77c6c3b201b 100644 --- a/src/coreclr/vm/comthreadpool.cpp +++ b/src/coreclr/vm/comthreadpool.cpp @@ -164,9 +164,9 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, switch (configVariableIndex) { case 0: - // Special case for UsePortableThreadPool, which doesn't go into the AppContext - *configValueRef = 1; - *isBooleanRef = true; + // Special case for UsePortableThreadPool and similar, which don't go into the AppContext + *configValueRef = ThreadpoolMgr::UsePortableThreadPoolForIO() ? 2 : 1; + *isBooleanRef = false; *appContextConfigNameRef = NULL; return 1; @@ -227,6 +227,7 @@ FCIMPLEND FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorSetMaxThreads,DWORD workerThreads, DWORD completionPortThreads) { FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); BOOL bRet = FALSE; HELPER_METHOD_FRAME_BEGIN_RET_0(); @@ -241,6 +242,7 @@ FCIMPLEND FCIMPL2(VOID, ThreadPoolNative::CorGetMaxThreads,DWORD* workerThreads, DWORD* completionPortThreads) { FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); ThreadpoolMgr::GetMaxThreads(workerThreads,completionPortThreads); return; @@ -251,6 +253,7 @@ FCIMPLEND FCIMPL2(FC_BOOL_RET, ThreadPoolNative::CorSetMinThreads,DWORD workerThreads, DWORD completionPortThreads) { FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); BOOL bRet = FALSE; HELPER_METHOD_FRAME_BEGIN_RET_0(); @@ -265,6 +268,7 @@ FCIMPLEND FCIMPL2(VOID, ThreadPoolNative::CorGetMinThreads,DWORD* workerThreads, DWORD* completionPortThreads) { FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); ThreadpoolMgr::GetMinThreads(workerThreads,completionPortThreads); return; @@ -275,6 +279,7 @@ FCIMPLEND FCIMPL2(VOID, ThreadPoolNative::CorGetAvailableThreads,DWORD* workerThreads, DWORD* completionPortThreads) { FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); ThreadpoolMgr::GetAvailableThreads(workerThreads,completionPortThreads); return; @@ -285,6 +290,8 @@ FCIMPLEND FCIMPL0(INT32, ThreadPoolNative::GetThreadCount) { FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); + return ThreadpoolMgr::GetThreadCount(); } FCIMPLEND @@ -293,6 +300,7 @@ FCIMPLEND extern "C" INT64 QCALLTYPE ThreadPool_GetCompletedWorkItemCount() { QCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); INT64 result = 0; @@ -553,27 +561,6 @@ FCIMPL5(LPVOID, ThreadPoolNative::CorRegisterWaitForSingleObject, } FCIMPLEND -#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows -FCIMPL1(void, ThreadPoolNative::CorQueueWaitCompletion, Object* completeWaitWorkItemObjectUNSAFE) -{ - FCALL_CONTRACT; - _ASSERTE_ALL_BUILDS(__FILE__, ThreadpoolMgr::UsePortableThreadPool()); - - OBJECTREF completeWaitWorkItemObject = ObjectToOBJECTREF(completeWaitWorkItemObjectUNSAFE); - HELPER_METHOD_FRAME_BEGIN_1(completeWaitWorkItemObject); - - _ASSERTE(completeWaitWorkItemObject != NULL); - - OBJECTHANDLE completeWaitWorkItemObjectHandle = GetAppDomain()->CreateHandle(completeWaitWorkItemObject); - ThreadpoolMgr::PostQueuedCompletionStatus( - (LPOVERLAPPED)completeWaitWorkItemObjectHandle, - ThreadpoolMgr::ManagedWaitIOCompletionCallback); - - HELPER_METHOD_FRAME_END(); -} -FCIMPLEND -#endif // TARGET_WINDOWS - VOID QueueUserWorkItemManagedCallback(PVOID pArg) { CONTRACTL @@ -591,7 +578,6 @@ VOID QueueUserWorkItemManagedCallback(PVOID pArg) *wasNotRecalled = dispatch.Call_RetBool(NULL); } - extern "C" BOOL QCALLTYPE ThreadPool_RequestWorkerThread() { QCALL_CONTRACT; @@ -629,7 +615,7 @@ extern "C" BOOL QCALLTYPE ThreadPool_PerformGateActivities(INT32 cpuUtilization) BEGIN_QCALL; - _ASSERTE_ALL_BUILDS(__FILE__, ThreadpoolMgr::UsePortableThreadPool()); + _ASSERTE_ALL_BUILDS(__FILE__, ThreadpoolMgr::UsePortableThreadPool() && !ThreadpoolMgr::UsePortableThreadPoolForIO()); ThreadpoolMgr::PerformGateActivities(cpuUtilization); needGateThread = ThreadpoolMgr::NeedGateThreadForIOCompletions(); @@ -805,6 +791,7 @@ void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorBindIoCompletionCallback, HANDLE fileHandle) { FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); BOOL retVal = FALSE; @@ -836,6 +823,7 @@ FCIMPLEND FCIMPL1(FC_BOOL_RET, ThreadPoolNative::CorPostQueuedCompletionStatus, LPOVERLAPPED lpOverlapped) { FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); OVERLAPPEDDATAREF overlapped = ObjectToOVERLAPPEDDATAREF(OverlappedDataObject::GetOverlapped(lpOverlapped)); diff --git a/src/coreclr/vm/comthreadpool.h b/src/coreclr/vm/comthreadpool.h index 074d4dab3f0c70..1b700cd91fd810 100644 --- a/src/coreclr/vm/comthreadpool.h +++ b/src/coreclr/vm/comthreadpool.h @@ -50,10 +50,6 @@ class ThreadPoolNative UINT32 timeout, CLR_BOOL executeOnlyOnce, Object* registeredWaitObjectUNSAFE); -#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows - static FCDECL1(void, CorQueueWaitCompletion, Object* completeWaitWorkItemObjectUNSAFE); -#endif - static FCDECL1(FC_BOOL_RET, CorPostQueuedCompletionStatus, LPOVERLAPPED lpOverlapped); static FCDECL2(FC_BOOL_RET, CorUnregisterWait, LPVOID WaitHandle, Object * objectToNotify); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index a3d89952bd0a18..ee95e5fd8a6d47 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -935,9 +935,6 @@ DEFINE_CLASS(THREAD_POOL, Threading, ThreadPo DEFINE_METHOD(THREAD_POOL, ENSURE_GATE_THREAD_RUNNING, EnsureGateThreadRunning, SM_RetVoid) DEFINE_METHOD(THREAD_POOL, UNSAFE_QUEUE_UNMANAGED_WORK_ITEM, UnsafeQueueUnmanagedWorkItem, SM_IntPtr_IntPtr_RetVoid) -DEFINE_CLASS(COMPLETE_WAIT_THREAD_POOL_WORK_ITEM, Threading, CompleteWaitThreadPoolWorkItem) -DEFINE_METHOD(COMPLETE_WAIT_THREAD_POOL_WORK_ITEM, COMPLETE_WAIT, CompleteWait, IM_RetVoid) - DEFINE_CLASS(TIMESPAN, System, TimeSpan) diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 08ab8e71c868a4..18c983ff40d628 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -415,9 +415,6 @@ FCFuncStart(gThreadPoolFuncs) FCFuncElement("GetThreadCount", ThreadPoolNative::GetThreadCount) FCFuncElement("GetPendingUnmanagedWorkItemCount", ThreadPoolNative::GetPendingUnmanagedWorkItemCount) FCFuncElement("RegisterWaitForSingleObjectNative", ThreadPoolNative::CorRegisterWaitForSingleObject) -#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows - FCFuncElement("QueueWaitCompletionNative", ThreadPoolNative::CorQueueWaitCompletion) -#endif FCFuncElement("BindIOCompletionCallbackNative", ThreadPoolNative::CorBindIoCompletionCallback) FCFuncElement("SetMaxThreadsNative", ThreadPoolNative::CorSetMaxThreads) FCFuncElement("GetMaxThreadsNative", ThreadPoolNative::CorGetMaxThreads) diff --git a/src/coreclr/vm/nativeoverlapped.cpp b/src/coreclr/vm/nativeoverlapped.cpp index 9440f8d21c298d..c6c3bb6103354b 100644 --- a/src/coreclr/vm/nativeoverlapped.cpp +++ b/src/coreclr/vm/nativeoverlapped.cpp @@ -28,6 +28,7 @@ FCIMPL3(void, CheckVMForIOPacket, LPOVERLAPPED* lpOverlapped, DWORD* errorCode, DWORD* numBytes) { FCALL_CONTRACT; + _ASSERTE_ALL_BUILDS(__FILE__, !ThreadpoolMgr::UsePortableThreadPoolForIO()); #ifndef TARGET_UNIX Thread *pThread = GetThread(); diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 9a60e4589c3c7c..aac691f4208823 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -7939,6 +7939,8 @@ UINT64 Thread::GetTotalThreadPoolCompletionCount() } CONTRACTL_END; + _ASSERTE(!ThreadpoolMgr::UsePortableThreadPoolForIO()); + bool usePortableThreadPool = ThreadpoolMgr::UsePortableThreadPool(); // enumerate all threads, summing their local counts. diff --git a/src/coreclr/vm/win32threadpool.cpp b/src/coreclr/vm/win32threadpool.cpp index 3e16f18a2d83b9..4d5a66f61f5211 100644 --- a/src/coreclr/vm/win32threadpool.cpp +++ b/src/coreclr/vm/win32threadpool.cpp @@ -113,6 +113,7 @@ int ThreadpoolMgr::ThreadAdjustmentInterval; 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 @@ -364,7 +365,10 @@ BOOL ThreadpoolMgr::Initialize() WaitThreadsCriticalSection.Init(CrstThreadpoolWaitThreads); } - WorkerCriticalSection.Init(CrstThreadpoolWorker); + if (!UsePortableThreadPoolForIO()) + { + WorkerCriticalSection.Init(CrstThreadpoolWorker); + } TimerQueueCriticalSection.Init(CrstThreadpoolTimerQueue); if (!UsePortableThreadPool()) @@ -376,9 +380,12 @@ BOOL ThreadpoolMgr::Initialize() // initialize TimerQueue InitializeListHead(&TimerQueue); - RetiredCPWakeupEvent = new CLREvent(); - RetiredCPWakeupEvent->CreateAutoEvent(FALSE); - _ASSERTE(RetiredCPWakeupEvent->IsValid()); + if (!UsePortableThreadPoolForIO()) + { + RetiredCPWakeupEvent = new CLREvent(); + RetiredCPWakeupEvent->CreateAutoEvent(FALSE); + _ASSERTE(RetiredCPWakeupEvent->IsValid()); + } if (!UsePortableThreadPool()) { @@ -401,7 +408,7 @@ BOOL ThreadpoolMgr::Initialize() } EX_CATCH { - if (RetiredCPWakeupEvent) + if (!UsePortableThreadPoolForIO() && RetiredCPWakeupEvent) { delete RetiredCPWakeupEvent; RetiredCPWakeupEvent = NULL; @@ -412,7 +419,10 @@ BOOL ThreadpoolMgr::Initialize() { WaitThreadsCriticalSection.Destroy(); } - WorkerCriticalSection.Destroy(); + if (!UsePortableThreadPoolForIO()) + { + WorkerCriticalSection.Destroy(); + } TimerQueueCriticalSection.Destroy(); bExceptionCaught = TRUE; @@ -443,27 +453,30 @@ BOOL ThreadpoolMgr::Initialize() WorkerCounter.counts.AsLongLong = counts.AsLongLong; } - // initialize CP thread settings - MinLimitTotalCPThreads = NumberOfProcessors; + 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); + // 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; + 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); - } + { + GlobalCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, + NULL, + 0, /*ignored for invalid handle value*/ + NumberOfProcessors); + } #endif // !TARGET_UNIX + } if (!UsePortableThreadPool()) { @@ -517,6 +530,7 @@ bool ThreadpoolMgr::CanSetMinIOCompletionThreads(DWORD ioCompletionThreads) { WRAPPER_NO_CONTRACT; _ASSERTE(UsePortableThreadPool()); + _ASSERTE(!UsePortableThreadPoolForIO()); EnsureInitialized(); @@ -529,6 +543,7 @@ bool ThreadpoolMgr::CanSetMaxIOCompletionThreads(DWORD ioCompletionThreads) { WRAPPER_NO_CONTRACT; _ASSERTE(UsePortableThreadPool()); + _ASSERTE(!UsePortableThreadPoolForIO()); _ASSERTE(ioCompletionThreads != 0); EnsureInitialized(); @@ -549,6 +564,8 @@ BOOL ThreadpoolMgr::SetMaxThreadsHelper(DWORD MaxWorkerThreads, } 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. @@ -603,6 +620,8 @@ BOOL ThreadpoolMgr::SetMaxThreads(DWORD MaxWorkerThreads, } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPoolForIO()); + EnsureInitialized(); return SetMaxThreadsHelper(MaxWorkerThreads, MaxIOCompletionThreads); @@ -612,6 +631,7 @@ BOOL ThreadpoolMgr::GetMaxThreads(DWORD* MaxWorkerThreads, DWORD* MaxIOCompletionThreads) { LIMITED_METHOD_CONTRACT; + _ASSERTE(!UsePortableThreadPoolForIO()); if (!MaxWorkerThreads || !MaxIOCompletionThreads) { @@ -637,6 +657,8 @@ BOOL ThreadpoolMgr::SetMinThreads(DWORD MinWorkerThreads, } 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. @@ -697,6 +719,7 @@ BOOL ThreadpoolMgr::GetMinThreads(DWORD* MinWorkerThreads, DWORD* MinIOCompletionThreads) { LIMITED_METHOD_CONTRACT; + _ASSERTE(!UsePortableThreadPoolForIO()); if (!MinWorkerThreads || !MinIOCompletionThreads) { @@ -715,6 +738,7 @@ BOOL ThreadpoolMgr::GetAvailableThreads(DWORD* AvailableWorkerThreads, DWORD* AvailableIOCompletionThreads) { LIMITED_METHOD_CONTRACT; + _ASSERTE(!UsePortableThreadPoolForIO()); if (!AvailableWorkerThreads || !AvailableIOCompletionThreads) { @@ -749,6 +773,7 @@ BOOL ThreadpoolMgr::GetAvailableThreads(DWORD* AvailableWorkerThreads, INT32 ThreadpoolMgr::GetThreadCount() { WRAPPER_NO_CONTRACT; + _ASSERTE(!UsePortableThreadPoolForIO()); if (!IsInitialized()) { @@ -1159,6 +1184,8 @@ BOOL ThreadpoolMgr::PostQueuedCompletionStatus(LPOVERLAPPED lpOverlapped, } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPoolForIO()); + #ifndef TARGET_UNIX EnsureInitialized(); @@ -1212,77 +1239,6 @@ void ThreadpoolMgr::WaitIOCompletionCallback( DWORD ret = AsyncCallbackCompletion((PVOID)lpOverlapped); } -#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows - -void WINAPI ThreadpoolMgr::ManagedWaitIOCompletionCallback( - DWORD dwErrorCode, - DWORD dwNumberOfBytesTransfered, - LPOVERLAPPED lpOverlapped) -{ - Thread *pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - ClrFlsSetThreadType(ThreadType_Threadpool_Worker); - pThread = SetupThreadNoThrow(); - if (pThread == NULL) - { - return; - } - } - - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - if (dwErrorCode != ERROR_SUCCESS) - { - return; - } - - _ASSERTE(lpOverlapped != NULL); - - { - GCX_COOP(); - ManagedThreadBase::ThreadPool(ManagedWaitIOCompletionCallback_Worker, lpOverlapped); - } - - Thread::IncrementIOThreadPoolCompletionCount(pThread); -} - -void ThreadpoolMgr::ManagedWaitIOCompletionCallback_Worker(LPVOID state) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - _ASSERTE(state != NULL); - - OBJECTHANDLE completeWaitWorkItemObjectHandle = (OBJECTHANDLE)state; - OBJECTREF completeWaitWorkItemObject = ObjectFromHandle(completeWaitWorkItemObjectHandle); - _ASSERTE(completeWaitWorkItemObject != NULL); - - GCPROTECT_BEGIN(completeWaitWorkItemObject); - - DestroyHandle(completeWaitWorkItemObjectHandle); - completeWaitWorkItemObjectHandle = NULL; - - MethodDescCallSite completeAwait(METHOD__COMPLETE_WAIT_THREAD_POOL_WORK_ITEM__COMPLETE_WAIT, &completeWaitWorkItemObject); - ARG_SLOT args[] = { ObjToArgSlot(completeWaitWorkItemObject) }; - completeAwait.Call(args); - - GCPROTECT_END(); -} - -#endif // TARGET_WINDOWS - extern void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, DWORD numBytesTransferred, LPOVERLAPPED lpOverlapped); @@ -1293,6 +1249,7 @@ extern void WINAPI BindIoCompletionCallbackStub(DWORD ErrorCode, void ThreadpoolMgr::EnsureGateThreadRunning() { LIMITED_METHOD_CONTRACT; + _ASSERTE(!UsePortableThreadPoolForIO()); if (UsePortableThreadPool()) { @@ -1346,6 +1303,7 @@ void ThreadpoolMgr::EnsureGateThreadRunning() bool ThreadpoolMgr::NeedGateThreadForIOCompletions() { LIMITED_METHOD_CONTRACT; + _ASSERTE(!UsePortableThreadPoolForIO()); if (!InitCompletionPortThreadpool) { @@ -3049,7 +3007,6 @@ BOOL ThreadpoolMgr::BindIoCompletionCallback(HANDLE FileHandle, ULONG Flags, DWORD& errCode) { - CONTRACTL { THROWS; // EnsureInitialized can throw @@ -3058,6 +3015,8 @@ BOOL ThreadpoolMgr::BindIoCompletionCallback(HANDLE FileHandle, } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPoolForIO()); + #ifndef TARGET_UNIX errCode = S_OK; @@ -3102,6 +3061,8 @@ BOOL ThreadpoolMgr::CreateCompletionPortThread(LPVOID lpArgs) } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPoolForIO()); + Thread *pThread; BOOL fIsCLRThread; if ((pThread = CreateUnimpersonatedThread(CompletionPortThreadStart, lpArgs, &fIsCLRThread)) != NULL) @@ -3141,6 +3102,8 @@ DWORD WINAPI ThreadpoolMgr::CompletionPortThreadStart(LPVOID lpArgs) } CONTRACTL_END; + _ASSERTE_ALL_BUILDS(__FILE__, !UsePortableThreadPoolForIO()); + DWORD numBytes=0; size_t key=0; @@ -3664,6 +3627,8 @@ BOOL ThreadpoolMgr::ShouldGrowCompletionPortThreadpool(ThreadCounter::Counts cou } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPoolForIO()); + if (counts.NumWorking >= counts.NumActive && (counts.NumActive == 0 || !GCHeapUtilities::IsGCInProgress(TRUE)) ) @@ -3701,6 +3666,8 @@ void ThreadpoolMgr::GrowCompletionPortThreadpoolIfNeeded() } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPoolForIO()); + ThreadCounter::Counts oldCounts, newCounts; while (true) { @@ -4142,6 +4109,8 @@ void ThreadpoolMgr::PerformGateActivities(int cpuUtilization) } CONTRACTL_END; + _ASSERTE(!UsePortableThreadPoolForIO()); + ThreadpoolMgr::cpuUtilization = cpuUtilization; #ifndef TARGET_UNIX diff --git a/src/coreclr/vm/win32threadpool.h b/src/coreclr/vm/win32threadpool.h index 00d53d73a59f31..88453a002df0ba 100644 --- a/src/coreclr/vm/win32threadpool.h +++ b/src/coreclr/vm/win32threadpool.h @@ -227,9 +227,17 @@ class ThreadpoolMgr 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 +#endif // !DACCESS_COMPILE static bool UsePortableThreadPool() { @@ -237,6 +245,12 @@ class ThreadpoolMgr return s_usePortableThreadPool; } + static bool UsePortableThreadPoolForIO() + { + LIMITED_METHOD_CONTRACT; + return s_usePortableThreadPoolForIO; + } + static BOOL Initialize(); static BOOL SetMaxThreadsHelper(DWORD MaxWorkerThreads, @@ -295,13 +309,6 @@ class ThreadpoolMgr DWORD numBytesTransferred, LPOVERLAPPED lpOverlapped); -#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows - static void WINAPI ManagedWaitIOCompletionCallback(DWORD dwErrorCode, - DWORD dwNumberOfBytesTransfered, - LPOVERLAPPED lpOverlapped); -#endif - - static BOOL SetAppDomainRequestsActive(BOOL UnmanagedTP = FALSE); static void ClearAppDomainRequestsActive(BOOL UnmanagedTP = FALSE, LONG index = -1); @@ -347,11 +354,7 @@ class ThreadpoolMgr static FORCEINLINE BOOL AreEtwIOQueueEventsSpeciallyHandled(LPOVERLAPPED_COMPLETION_ROUTINE Function) { // We handle registered waits at a higher abstraction level - return (Function == ThreadpoolMgr::WaitIOCompletionCallback -#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows - || Function == ThreadpoolMgr::ManagedWaitIOCompletionCallback -#endif - ); + return Function == ThreadpoolMgr::WaitIOCompletionCallback; } #endif @@ -906,10 +909,6 @@ class ThreadpoolMgr unsigned index, // array index BOOL waitTimedOut); -#ifdef TARGET_WINDOWS // the IO completion thread pool is currently only available on Windows - static void ManagedWaitIOCompletionCallback_Worker(LPVOID state); -#endif - static DWORD WINAPI WaitThreadStart(LPVOID lpArgs); static DWORD WINAPI AsyncCallbackCompletion(PVOID pArgs); @@ -1026,6 +1025,7 @@ class ThreadpoolMgr 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 diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CompletionPort.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CompletionPort.cs index e33002e4b09144..58e4fd72e2cabd 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CompletionPort.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CompletionPort.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.InteropServices; +using System.Threading; internal static partial class Interop { @@ -13,10 +14,29 @@ internal static partial class Kernel32 [GeneratedDllImport(Libraries.Kernel32, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool PostQueuedCompletionStatus(IntPtr CompletionPort, int dwNumberOfBytesTransferred, UIntPtr CompletionKey, IntPtr lpOverlapped); + internal static partial bool PostQueuedCompletionStatus(IntPtr CompletionPort, uint dwNumberOfBytesTransferred, UIntPtr CompletionKey, IntPtr lpOverlapped); [GeneratedDllImport(Libraries.Kernel32, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static partial bool GetQueuedCompletionStatus(IntPtr CompletionPort, out int lpNumberOfBytes, out UIntPtr CompletionKey, out IntPtr lpOverlapped, int dwMilliseconds); + + [GeneratedDllImport(Libraries.Kernel32, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static unsafe partial bool GetQueuedCompletionStatusEx( + IntPtr CompletionPort, + OVERLAPPED_ENTRY* lpCompletionPortEntries, + int ulCount, + out int ulNumEntriesRemoved, + int dwMilliseconds, + [MarshalAs(UnmanagedType.Bool)] bool fAlertable); + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct OVERLAPPED_ENTRY + { + public UIntPtr lpCompletionKey; + public NativeOverlapped* lpOverlapped; + public UIntPtr Internal; + public uint dwNumberOfBytesTransferred; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 924204e24008c7..8bf268cecd0035 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1055,6 +1055,7 @@ + @@ -2307,7 +2308,8 @@ - + + @@ -2315,6 +2317,7 @@ + @@ -2323,7 +2326,12 @@ + + + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Windows.cs index 34fec19463a816..7b6e7e7bf1427b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Windows.cs @@ -26,9 +26,9 @@ private void Create(int maximumSignalCount) Interop.Kernel32.CreateIoCompletionPort(new IntPtr(-1), IntPtr.Zero, UIntPtr.Zero, maximumSignalCount); if (_completionPort == IntPtr.Zero) { - int error = Marshal.GetLastPInvokeError(); + int hr = Marshal.GetHRForLastWin32Error(); var exception = new OutOfMemoryException(); - exception.HResult = error; + exception.HResult = hr; throw exception; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/NativeRuntimeEventSource.PortableThreadPool.NativeSinks.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NativeRuntimeEventSource.PortableThreadPool.NativeSinks.cs index 3b202732596a7d..011f3dc45a4dd6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/NativeRuntimeEventSource.PortableThreadPool.NativeSinks.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NativeRuntimeEventSource.PortableThreadPool.NativeSinks.cs @@ -158,6 +158,19 @@ private unsafe void ThreadPoolIOEnqueue( LogThreadPoolIOEnqueue(NativeOverlapped, Overlapped, MultiDequeues, ClrInstanceID); } + [NonEvent] + [MethodImpl(MethodImplOptions.NoInlining)] + public unsafe void ThreadPoolIOEnqueue(NativeOverlapped* nativeOverlapped) + { + if (IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)) + { + ThreadPoolIOEnqueue( + (IntPtr)nativeOverlapped, + (IntPtr)OverlappedData.GetOverlappedFromNative(nativeOverlapped).GetHashCode(), + false); + } + } + // TODO: This event is fired for minor compat with CoreCLR in this case. Consider removing this method and use // FrameworkEventSource's thread transfer send/receive events instead at callers. [NonEvent] @@ -179,6 +192,18 @@ private unsafe void ThreadPoolIODequeue( LogThreadPoolIODequeue(NativeOverlapped, Overlapped, ClrInstanceID); } + [NonEvent] + [MethodImpl(MethodImplOptions.NoInlining)] + public unsafe void ThreadPoolIODequeue(NativeOverlapped* nativeOverlapped) + { + if (IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)) + { + ThreadPoolIODequeue( + (IntPtr)nativeOverlapped, + (IntPtr)OverlappedData.GetOverlappedFromNative(nativeOverlapped).GetHashCode()); + } + } + // TODO: This event is fired for minor compat with CoreCLR in this case. Consider removing this method and use // FrameworkEventSource's thread transfer send/receive events instead at callers. [NonEvent] diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/NativeRuntimeEventSource.PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NativeRuntimeEventSource.PortableThreadPool.cs index d22844dcc4ba0d..08bc3de170d4ef 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/NativeRuntimeEventSource.PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NativeRuntimeEventSource.PortableThreadPool.cs @@ -270,6 +270,19 @@ private unsafe void ThreadPoolIOEnqueue( WriteEventCore(63, 4, data); } + [NonEvent] + [MethodImpl(MethodImplOptions.NoInlining)] + public unsafe void ThreadPoolIOEnqueue(NativeOverlapped* nativeOverlapped) + { + if (IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)) + { + ThreadPoolIOEnqueue( + (IntPtr)nativeOverlapped, + (IntPtr)OverlappedData.GetOverlappedFromNative(nativeOverlapped).GetHashCode(), + false); + } + } + // TODO: This event is fired for minor compat with CoreCLR in this case. Consider removing this method and use // FrameworkEventSource's thread transfer send/receive events instead at callers. [NonEvent] @@ -305,6 +318,18 @@ private unsafe void ThreadPoolIODequeue( WriteEventCore(64, 3, data); } + [NonEvent] + [MethodImpl(MethodImplOptions.NoInlining)] + public unsafe void ThreadPoolIODequeue(NativeOverlapped* nativeOverlapped) + { + if (IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)) + { + ThreadPoolIODequeue( + (IntPtr)nativeOverlapped, + (IntPtr)OverlappedData.GetOverlappedFromNative(nativeOverlapped).GetHashCode()); + } + } + // TODO: This event is fired for minor compat with CoreCLR in this case. Consider removing this method and use // FrameworkEventSource's thread transfer send/receive events instead at callers. [NonEvent] diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped._IOCompletionCallback.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped._IOCompletionCallback.cs new file mode 100644 index 00000000000000..2b3e0ef604a469 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped._IOCompletionCallback.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Threading +{ + internal unsafe partial class _IOCompletionCallback + { + private readonly IOCompletionCallback _ioCompletionCallback; + private readonly ExecutionContext _executionContext; + private uint _errorCode; // Error code + private uint _numBytes; // No. of bytes transferred + private NativeOverlapped* _pNativeOverlapped; + + public _IOCompletionCallback(IOCompletionCallback ioCompletionCallback, ExecutionContext executionContext) + { + _ioCompletionCallback = ioCompletionCallback; + _executionContext = executionContext; + } + + // Context callback: same sig for SendOrPostCallback and ContextCallback + private static readonly ContextCallback IOCompletionCallback_Context_Delegate = IOCompletionCallback_Context; + private static void IOCompletionCallback_Context(object? state) + { + _IOCompletionCallback helper = (_IOCompletionCallback)state!; + Debug.Assert(helper != null, "_IOCompletionCallback cannot be null"); + helper._ioCompletionCallback(helper._errorCode, helper._numBytes, helper._pNativeOverlapped); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void PerformSingleIOCompletionCallback(NativeOverlapped* pNativeOverlapped, uint numBytes) + { + Debug.Assert(pNativeOverlapped != null); + + // When the error code is not provided in the callback, it is in the InternalLow field + PerformSingleIOCompletionCallback(pNativeOverlapped, (uint)(nint)pNativeOverlapped->InternalLow, numBytes); + } + + public static void PerformSingleIOCompletionCallback(NativeOverlapped* pNativeOverlapped, uint errorCode, uint numBytes) + { + Debug.Assert(pNativeOverlapped != null); + + OverlappedData overlapped = OverlappedData.GetOverlappedFromNative(pNativeOverlapped); + object? callback = overlapped._callback; + if (callback is IOCompletionCallback iocb) + { + // We got here because of UnsafePack (or) Pack with EC flow suppressed + iocb(errorCode, numBytes, pNativeOverlapped); + return; + } + + if (callback == null) + { + // A callback was not provided + return; + } + + // We got here because of Pack + Debug.Assert(callback is _IOCompletionCallback); + var helper = (_IOCompletionCallback)callback; + helper._errorCode = errorCode; + helper._numBytes = numBytes; + helper._pNativeOverlapped = pNativeOverlapped; + ExecutionContext.RunInternal(helper._executionContext, IOCompletionCallback_Context_Delegate, helper); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs new file mode 100644 index 00000000000000..cf77df95c8b70b --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs @@ -0,0 +1,186 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Threading +{ + internal sealed partial class PortableThreadPool + { + private static readonly int ProcessorsPerPoller = + AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.ProcessorsPerPollerThread", 12, false); + + private static nint CreateIOCompletionPort() + { + nint port = Interop.Kernel32.CreateIoCompletionPort(new IntPtr(-1), IntPtr.Zero, UIntPtr.Zero, 0); + if (port == 0) + { + int hr = Marshal.GetHRForLastWin32Error(); + Environment.FailFast($"Failed to create an IO completion port. HR: {hr}"); + } + + return port; + } + + public void RegisterForIOCompletionNotifications(nint handle) + { + Debug.Assert(_ioPort != 0); + + if (_ioCompletionPollers == null) + { + EnsureIOCompletionPollers(); + } + + nint port = Interop.Kernel32.CreateIoCompletionPort(handle, _ioPort, UIntPtr.Zero, 0); + if (port == 0) + { + ThrowHelper.ThrowApplicationException(Marshal.GetHRForLastWin32Error()); + } + + Debug.Assert(port == _ioPort); + } + + public unsafe void QueueNativeOverlapped(NativeOverlapped* nativeOverlapped) + { + Debug.Assert(nativeOverlapped != null); + Debug.Assert(_ioPort != 0); + + if (_ioCompletionPollers == null) + { + EnsureIOCompletionPollers(); + } + + if (NativeRuntimeEventSource.Log.IsEnabled()) + { + NativeRuntimeEventSource.Log.ThreadPoolIOEnqueue(nativeOverlapped); + } + + if (!Interop.Kernel32.PostQueuedCompletionStatus(_ioPort, 0, UIntPtr.Zero, (IntPtr)nativeOverlapped)) + { + ThrowHelper.ThrowApplicationException(Marshal.GetHRForLastWin32Error()); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void EnsureIOCompletionPollers() + { + _threadAdjustmentLock.Acquire(); + try + { + if (_ioCompletionPollers != null) + { + return; + } + + int pollerCount = (Environment.ProcessorCount - 1) / ProcessorsPerPoller + 1; + IOCompletionPoller[] pollers = new IOCompletionPoller[pollerCount]; + for (int i = 0; i < pollerCount; ++i) + { + pollers[i] = new IOCompletionPoller(_ioPort); + } + + _ioCompletionPollers = pollers; + } + catch (Exception ex) + { + Environment.FailFast("Failed to initialize IO completion pollers.", ex); + } + finally + { + _threadAdjustmentLock.Release(); + } + } + + private sealed unsafe class IOCompletionPoller + { + private const int NativeEventCapacity = +#if DEBUG + 32; +#else + 1024; +#endif + + private static readonly Action ProcessEventDelegate = ProcessEvent; + + private readonly nint _port; + private readonly Interop.Kernel32.OVERLAPPED_ENTRY* _nativeEvents; + private readonly ThreadPoolTypedWorkItemQueue _events; + private readonly Thread _thread; + + public IOCompletionPoller(nint port) + { + Debug.Assert(port != 0); + _port = port; + + _nativeEvents = + (Interop.Kernel32.OVERLAPPED_ENTRY*) + Marshal.AllocHGlobal(NativeEventCapacity * Marshal.SizeOf()); + _events = new ThreadPoolTypedWorkItemQueue(ProcessEventDelegate); + + // Thread pool threads must start in the default execution context without transferring the context, so + // using UnsafeStart() instead of Start() + _thread = new Thread(Poll, SmallStackSizeBytes) + { + IsThreadPoolThread = true, + IsBackground = true, + Priority = ThreadPriority.Highest, + Name = ".NET ThreadPool IO" + }; + _thread.UnsafeStart(); + } + + public int QueuedEventCount => _events.Count; + + private void Poll() + { + while ( + Interop.Kernel32.GetQueuedCompletionStatusEx( + _port, + _nativeEvents, + NativeEventCapacity, + out int nativeEventCount, + Timeout.Infinite, + false)) + { + Debug.Assert(nativeEventCount > 0); + Debug.Assert(nativeEventCount <= NativeEventCapacity); + + for (int i = 0; i < nativeEventCount; ++i) + { + Interop.Kernel32.OVERLAPPED_ENTRY* nativeEvent = &_nativeEvents[i]; + _events.BatchEnqueue(new Event(nativeEvent->lpOverlapped, nativeEvent->dwNumberOfBytesTransferred)); + } + + _events.CompleteBatchEnqueue(); + } + + ThrowHelper.ThrowApplicationException(Marshal.GetHRForLastWin32Error()); + } + + private static void ProcessEvent(Event e) + { + if (NativeRuntimeEventSource.Log.IsEnabled()) + { + NativeRuntimeEventSource.Log.ThreadPoolIODequeue(e.nativeOverlapped); + } + + _IOCompletionCallback.PerformSingleIOCompletionCallback(e.nativeOverlapped, e.bytesTransferred); + } + + private readonly struct Event + { + public readonly NativeOverlapped* nativeOverlapped; + public readonly uint bytesTransferred; + + public Event(NativeOverlapped* nativeOverlapped, uint bytesTransferred) + { + this.nativeOverlapped = nativeOverlapped; + this.bytesTransferred = bytesTransferred; + } + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs index ad932e0cf7a49c..c5932c93e2ee9e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WaitThread.cs @@ -384,7 +384,8 @@ private void QueueWaitCompletion(RegisteredWaitHandle registeredHandle, bool tim UnregisterWait(registeredHandle, blocking: false); // We shouldn't block the wait thread on the unregistration. } - ThreadPool.UnsafeQueueWaitCompletion(new CompleteWaitThreadPoolWorkItem(registeredHandle, timedOut)); + ThreadPool.UnsafeQueueHighPriorityWorkItemInternal( + new CompleteWaitThreadPoolWorkItem(registeredHandle, timedOut)); } /// 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 5d1fb2f659b2a7..d3a0863f4a980f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -45,6 +45,8 @@ internal sealed partial class PortableThreadPool private int _cpuUtilization; // SOS's ThreadPool command depends on this name private short _minThreads; private short _maxThreads; + private short _legacy_minIOCompletionThreads; + private short _legacy_maxIOCompletionThreads; [StructLayout(LayoutKind.Explicit, Size = Internal.PaddingHelpers.CACHE_LINE_SIZE * 6)] private struct CacheLineSeparated @@ -79,6 +81,11 @@ private struct CacheLineSeparated private long _memoryUsageBytes; private long _memoryLimitBytes; +#if TARGET_WINDOWS + private readonly nint _ioPort; + private IOCompletionPoller[]? _ioCompletionPollers; +#endif + private readonly LowLevelLock _threadAdjustmentLock = new LowLevelLock(); private CacheLineSeparated _separated; // SOS's ThreadPool command depends on this name @@ -101,7 +108,14 @@ private PortableThreadPool() _maxThreads = _minThreads; } + _legacy_minIOCompletionThreads = 1; + _legacy_maxIOCompletionThreads = 1000; + _separated.counts.NumThreadsGoal = _minThreads; + +#if TARGET_WINDOWS + _ioPort = CreateIOCompletionPort(); +#endif } private static bool HasForcedMinThreads => @@ -122,19 +136,38 @@ public bool SetMinThreads(int workerThreads, int ioCompletionThreads) _threadAdjustmentLock.Acquire(); try { - if (workerThreads > _maxThreads || !ThreadPool.CanSetMinIOCompletionThreads(ioCompletionThreads)) + if (workerThreads > _maxThreads) + { + return false; + } + + if (ThreadPool.UsePortableThreadPoolForIO + ? ioCompletionThreads > _legacy_maxIOCompletionThreads + : !ThreadPool.CanSetMinIOCompletionThreads(ioCompletionThreads)) { return false; } - ThreadPool.SetMinIOCompletionThreads(ioCompletionThreads); + if (HasForcedMinThreads && workerThreads != ForcedMinWorkerThreads) + { + return false; + } - if (HasForcedMinThreads) + if (ThreadPool.UsePortableThreadPoolForIO) + { + _legacy_minIOCompletionThreads = (short)Math.Max(1, ioCompletionThreads); + } + else { - return workerThreads == ForcedMinWorkerThreads; + ThreadPool.SetMinIOCompletionThreads(ioCompletionThreads); + } + + short newMinThreads = (short)Math.Max(1, workerThreads); + if (newMinThreads == _minThreads) + { + return true; } - short newMinThreads = (short)Math.Max(1, Math.Min(workerThreads, MaxPossibleThreadCount)); _minThreads = newMinThreads; if (_numBlockedThreads > 0) { @@ -170,7 +203,11 @@ public bool SetMinThreads(int workerThreads, int ioCompletionThreads) return true; } - public int GetMinThreads() => Volatile.Read(ref _minThreads); + public void GetMinThreads(out int workerThreads, out int ioCompletionThreads) + { + workerThreads = Volatile.Read(ref _minThreads); + ioCompletionThreads = _legacy_minIOCompletionThreads; + } public bool SetMaxThreads(int workerThreads, int ioCompletionThreads) { @@ -182,19 +219,38 @@ public bool SetMaxThreads(int workerThreads, int ioCompletionThreads) _threadAdjustmentLock.Acquire(); try { - if (workerThreads < _minThreads || !ThreadPool.CanSetMaxIOCompletionThreads(ioCompletionThreads)) + if (workerThreads < _minThreads) { return false; } - ThreadPool.SetMaxIOCompletionThreads(ioCompletionThreads); + if (ThreadPool.UsePortableThreadPoolForIO + ? ioCompletionThreads < _legacy_minIOCompletionThreads + : !ThreadPool.CanSetMaxIOCompletionThreads(ioCompletionThreads)) + { + return false; + } - if (HasForcedMaxThreads) + if (HasForcedMaxThreads && workerThreads != ForcedMaxWorkerThreads) { - return workerThreads == ForcedMaxWorkerThreads; + return false; + } + + if (ThreadPool.UsePortableThreadPoolForIO) + { + _legacy_maxIOCompletionThreads = (short)Math.Min(ioCompletionThreads, MaxPossibleThreadCount); + } + else + { + ThreadPool.SetMaxIOCompletionThreads(ioCompletionThreads); } short newMaxThreads = (short)Math.Min(workerThreads, MaxPossibleThreadCount); + if (newMaxThreads == _maxThreads) + { + return true; + } + _maxThreads = newMaxThreads; if (_separated.counts.NumThreadsGoal > newMaxThreads) { @@ -208,17 +264,17 @@ public bool SetMaxThreads(int workerThreads, int ioCompletionThreads) } } - public int GetMaxThreads() => Volatile.Read(ref _maxThreads); + public void GetMaxThreads(out int workerThreads, out int ioCompletionThreads) + { + workerThreads = Volatile.Read(ref _maxThreads); + ioCompletionThreads = _legacy_maxIOCompletionThreads; + } - public int GetAvailableThreads() + public void GetAvailableThreads(out int workerThreads, out int ioCompletionThreads) { ThreadCounts counts = _separated.counts.VolatileRead(); - int count = _maxThreads - counts.NumProcessingWork; - if (count < 0) - { - return 0; - } - return count; + workerThreads = Math.Max(0, _maxThreads - counts.NumProcessingWork); + ioCompletionThreads = _legacy_maxIOCompletionThreads; } public int ThreadCount => _separated.counts.VolatileRead().NumExistingThreads; diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs similarity index 98% rename from src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs index 90e43c63c28ba6..d863ba062a428f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs @@ -136,7 +136,5 @@ unsafe void IDeferredDisposable.OnFinalRelease(bool disposed) } } } - - internal bool IsUserObject(byte[]? buffer) => _overlapped.IsUserObject(buffer); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs index b56276d22f30da..9eebb89c895f6d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadInt64PersistentCounter.cs @@ -24,6 +24,13 @@ public static void Increment(object threadLocalCountObject) Unsafe.As(threadLocalCountObject).Increment(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(object threadLocalCountObject, uint count) + { + Debug.Assert(threadLocalCountObject is ThreadLocalNode); + Unsafe.As(threadLocalCountObject).Add(count); + } + public object CreateThreadLocalCountObject() { var node = new ThreadLocalNode(this); @@ -108,13 +115,29 @@ public void Increment() return; } - OnIncrementOverflow(); + OnAddOverflow(1); + } + + public void Add(uint count) + { + Debug.Assert(count != 0); + + uint newCount = _count + count; + if (newCount >= count) + { + _count = newCount; + return; + } + + OnAddOverflow(count); } [MethodImpl(MethodImplOptions.NoInlining)] - private void OnIncrementOverflow() + private void OnAddOverflow(uint count) { - // Accumulate the count for this increment into the overflow count and reset the thread-local count + Debug.Assert(count != 0); + + // Accumulate the count for this add into the overflow count and reset the thread-local count // The lock, in coordination with other places that read these values, ensures that both changes below become // visible together @@ -122,8 +145,8 @@ private void OnIncrementOverflow() s_lock.Acquire(); try { + counter._overflowCount += (long)_count + count; _count = 0; - counter._overflowCount += (long)uint.MaxValue + 1; } finally { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Unix.cs new file mode 100644 index 00000000000000..1d4e0c171496fb --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Unix.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace System.Threading +{ + public static partial class ThreadPool + { + [CLSCompliant(false)] + [SupportedOSPlatform("windows")] + public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) => + throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO); + + [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] + [SupportedOSPlatform("windows")] + public static bool BindHandle(IntPtr osHandle) => + throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO); + + [SupportedOSPlatform("windows")] + public static bool BindHandle(SafeHandle osHandle) => + throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO); + } +} 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 new file mode 100644 index 00000000000000..6219045425cf34 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace System.Threading +{ + public static partial class ThreadPool + { + [CLSCompliant(false)] + [SupportedOSPlatform("windows")] + public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) + { + Debug.Assert(UsePortableThreadPoolForIO); + + if (overlapped == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped); + } + + // OS doesn't signal handle, so do it here + overlapped->InternalLow = IntPtr.Zero; + + PortableThreadPool.ThreadPoolInstance.QueueNativeOverlapped(overlapped); + return true; + } + + [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] + [SupportedOSPlatform("windows")] + public static bool BindHandle(IntPtr osHandle) + { + PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle); + return true; + } + + [SupportedOSPlatform("windows")] + public static bool BindHandle(SafeHandle osHandle!!) + { + bool mustReleaseSafeHandle = false; + try + { + osHandle.DangerousAddRef(ref mustReleaseSafeHandle); + PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle.DangerousGetHandle()); + return true; + } + finally + { + if (mustReleaseSafeHandle) + osHandle.DangerousRelease(); + } + } + } +} 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 6a2af49dc19d8f..b85726bbc0ea7d 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,12 +17,11 @@ internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkIt public static partial class ThreadPool { - // Time-sensitive work items are those that may need to run ahead of normal work items at least periodically. For a - // runtime that does not support time-sensitive work items on the managed side, the thread pool yields the thread to the - // runtime periodically (by exiting the dispatch loop) so that the runtime may use that thread for processing - // any time-sensitive work. For a runtime that supports time-sensitive work items on the managed side, the thread pool - // does not yield the thread and instead processes time-sensitive work items queued by specific APIs periodically. - internal const bool SupportsTimeSensitiveWorkItems = true; + 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; #if CORERT private const bool IsWorkerTrackingEnabledInConfig = false; @@ -35,8 +34,8 @@ public static partial class ThreadPool internal static void InitializeForThreadPoolThread() { } #pragma warning disable IDE0060 - internal static bool CanSetMinIOCompletionThreads(int ioCompletionThreads) => true; - internal static bool CanSetMaxIOCompletionThreads(int ioCompletionThreads) => true; + internal static bool CanSetMinIOCompletionThreads(int ioCompletionThreads) => false; + internal static bool CanSetMaxIOCompletionThreads(int ioCompletionThreads) => false; #pragma warning restore IDE0060 [Conditional("unnecessary")] @@ -46,29 +45,14 @@ internal static void SetMaxIOCompletionThreads(int ioCompletionThreads) { } public static bool SetMaxThreads(int workerThreads, int completionPortThreads) => PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads); - - public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) - { - // Note that worker threads and completion port threads share the same thread pool. - // The total number of threads cannot exceed MaxPossibleThreadCount. - workerThreads = PortableThreadPool.ThreadPoolInstance.GetMaxThreads(); - completionPortThreads = 1; - } - + public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) => + PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out completionPortThreads); public static bool SetMinThreads(int workerThreads, int completionPortThreads) => PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads, completionPortThreads); - - public static void GetMinThreads(out int workerThreads, out int completionPortThreads) - { - workerThreads = PortableThreadPool.ThreadPoolInstance.GetMinThreads(); - completionPortThreads = 1; - } - - public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) - { - workerThreads = PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(); - completionPortThreads = 0; - } + public static void GetMinThreads(out int workerThreads, out int completionPortThreads) => + PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out completionPortThreads); + public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) => + PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads); /// /// Gets the number of thread pool threads that currently exist. @@ -128,8 +112,5 @@ private static RegisteredWaitHandle RegisterWaitForSingleObject( PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredHandle); return registeredHandle; } - - internal static void UnsafeQueueWaitCompletion(CompleteWaitThreadPoolWorkItem completeWaitWorkItem) => - UnsafeQueueUserWorkItemInternal(completeWaitWorkItem, preferLocal: false); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Unix.cs similarity index 100% rename from src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.Unix.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Unix.cs diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs similarity index 100% rename from src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.Windows.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.cs similarity index 100% rename from src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.cs diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandleOverlapped.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandleOverlapped.cs similarity index 100% rename from src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandleOverlapped.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandleOverlapped.cs 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 4a7a166562252a..f3defabf81f6db 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs @@ -13,12 +13,12 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Tracing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading.Tasks; -using Microsoft.Win32.SafeHandles; namespace System.Threading { @@ -387,11 +387,13 @@ public int Count } } - internal bool loggingEnabled; - private bool _dispatchTimeSensitiveWorkFirst; - internal readonly ConcurrentQueue workItems = new ConcurrentQueue(); // SOS's ThreadPool command depends on this name - internal readonly ConcurrentQueue? timeSensitiveWorkQueue = - ThreadPool.SupportsTimeSensitiveWorkItems ? new ConcurrentQueue() : null; + private bool _loggingEnabled; + private bool _dispatchNormalPriorityWorkFirst; + private int _mayHaveHighPriorityWorkItems; + + // SOS's ThreadPool command depends on the following names + internal readonly ConcurrentQueue workItems = new ConcurrentQueue(); + internal readonly ConcurrentQueue highPriorityWorkItems = new ConcurrentQueue(); [StructLayout(LayoutKind.Sequential)] private struct CacheLineSeparated @@ -426,9 +428,9 @@ public void RefreshLoggingEnabled() { if (!FrameworkEventSource.Log.IsEnabled()) { - if (loggingEnabled) + if (_loggingEnabled) { - loggingEnabled = false; + _loggingEnabled = false; } return; } @@ -439,7 +441,7 @@ public void RefreshLoggingEnabled() [MethodImpl(MethodImplOptions.NoInlining)] public void RefreshLoggingEnabledFull() { - loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer); + _loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -463,43 +465,18 @@ internal void MarkThreadRequestSatisfied() Interlocked.MemoryBarrier(); } - public void EnqueueTimeSensitiveWorkItem(IThreadPoolWorkItem timeSensitiveWorkItem) - { - Debug.Assert(ThreadPool.SupportsTimeSensitiveWorkItems); - - if (loggingEnabled && FrameworkEventSource.Log.IsEnabled()) - { - FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(timeSensitiveWorkItem); - } - - timeSensitiveWorkQueue!.Enqueue(timeSensitiveWorkItem); - EnsureThreadRequested(); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public IThreadPoolWorkItem? TryDequeueTimeSensitiveWorkItem() - { - Debug.Assert(ThreadPool.SupportsTimeSensitiveWorkItems); - - bool success = timeSensitiveWorkQueue!.TryDequeue(out IThreadPoolWorkItem? timeSensitiveWorkItem); - Debug.Assert(success == (timeSensitiveWorkItem != null)); - return timeSensitiveWorkItem; - } - public void Enqueue(object callback, bool forceGlobal) { Debug.Assert((callback is IThreadPoolWorkItem) ^ (callback is Task)); - if (loggingEnabled && FrameworkEventSource.Log.IsEnabled()) + if (_loggingEnabled && FrameworkEventSource.Log.IsEnabled()) FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(callback); - ThreadPoolWorkQueueThreadLocals? tl = null; - if (!forceGlobal) - tl = ThreadPoolWorkQueueThreadLocals.threadLocals; - - if (null != tl) + ThreadPoolWorkQueueThreadLocals? tl; + if (!forceGlobal && (tl = ThreadPoolWorkQueueThreadLocals.threadLocals) != null) { tl.workStealingQueue.LocalPush(callback); + tl.workState |= ThreadPoolWorkQueueThreadLocals.WorkState.MayHaveLocalWorkItems; } else { @@ -509,6 +486,21 @@ public void Enqueue(object callback, bool forceGlobal) EnsureThreadRequested(); } + public void EnqueueAtHighPriority(object workItem) + { + Debug.Assert((workItem is IThreadPoolWorkItem) ^ (workItem is Task)); + + if (_loggingEnabled && FrameworkEventSource.Log.IsEnabled()) + FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(workItem); + + highPriorityWorkItems.Enqueue(workItem); + + // If the change below is seen by another thread, ensure that the enqueued work item will also be visible + Volatile.Write(ref _mayHaveHighPriorityWorkItems, 1); + + EnsureThreadRequested(); + } + internal static bool LocalFindAndPop(object callback) { ThreadPoolWorkQueueThreadLocals? tl = ThreadPoolWorkQueueThreadLocals.threadLocals; @@ -517,45 +509,86 @@ internal static bool LocalFindAndPop(object callback) public object? Dequeue(ThreadPoolWorkQueueThreadLocals tl, ref bool missedSteal) { - WorkStealingQueue localWsq = tl.workStealingQueue; - object? callback; + // Check for local work items + object? workItem; + ThreadPoolWorkQueueThreadLocals.WorkState tlWorkState = tl.workState; + if ((tlWorkState & ThreadPoolWorkQueueThreadLocals.WorkState.MayHaveLocalWorkItems) != 0) + { + workItem = tl.workStealingQueue.LocalPop(); + if (workItem != null) + { + return workItem; + } - if ((callback = localWsq.LocalPop()) == null && // first try the local queue - !workItems.TryDequeue(out callback)) // then try the global queue + Debug.Assert(tlWorkState == tl.workState); + tl.workState = tlWorkState &= ~ThreadPoolWorkQueueThreadLocals.WorkState.MayHaveLocalWorkItems; + } + + // Check for high-priority work items + if ((tlWorkState & ThreadPoolWorkQueueThreadLocals.WorkState.IsProcessingHighPriorityWorkItems) != 0) { - // finally try to steal from another thread's local queue - WorkStealingQueue[] queues = WorkStealingQueueList.Queues; - int c = queues.Length; - Debug.Assert(c > 0, "There must at least be a queue for this thread."); - int maxIndex = c - 1; - uint i = tl.random.NextUInt32() % (uint)c; - while (c > 0) + if (highPriorityWorkItems.TryDequeue(out workItem)) { - i = (i < maxIndex) ? i + 1 : 0; - WorkStealingQueue otherQueue = queues[i]; - if (otherQueue != localWsq && otherQueue.CanSteal) - { - callback = otherQueue.TrySteal(ref missedSteal); - if (callback != null) - { - return callback; - } - } - c--; + return workItem; } - Debug.Assert(callback == null); + Debug.Assert(tlWorkState == tl.workState); + tl.workState = tlWorkState &= ~ThreadPoolWorkQueueThreadLocals.WorkState.IsProcessingHighPriorityWorkItems; + } + else if ( + _mayHaveHighPriorityWorkItems != 0 && + Interlocked.CompareExchange(ref _mayHaveHighPriorityWorkItems, 0, 1) != 0 && + TryStartProcessingHighPriorityWorkItemsAndDequeue(tl, out workItem)) + { + return workItem; + } + + // Check for global work items + if (workItems.TryDequeue(out workItem)) + { + return workItem; + } -#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be a constant in some runtimes. - // No work in the normal queues, check for time-sensitive work items - if (ThreadPool.SupportsTimeSensitiveWorkItems) + // Try to steal from other threads' local work items + WorkStealingQueue localWsq = tl.workStealingQueue; + WorkStealingQueue[] queues = WorkStealingQueueList.Queues; + int c = queues.Length; + Debug.Assert(c > 0, "There must at least be a queue for this thread."); + int maxIndex = c - 1; + uint i = tl.random.NextUInt32() % (uint)c; + while (c > 0) + { + i = (i < maxIndex) ? i + 1 : 0; + WorkStealingQueue otherQueue = queues[i]; + if (otherQueue != localWsq && otherQueue.CanSteal) { - callback = TryDequeueTimeSensitiveWorkItem(); + workItem = otherQueue.TrySteal(ref missedSteal); + if (workItem != null) + { + return workItem; + } } -#pragma warning restore CS0162 + c--; + } + + return null; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private bool TryStartProcessingHighPriorityWorkItemsAndDequeue( + ThreadPoolWorkQueueThreadLocals tl, + [MaybeNullWhen(false)] out object workItem) + { + Debug.Assert((tl.workState & ThreadPoolWorkQueueThreadLocals.WorkState.IsProcessingHighPriorityWorkItems) == 0); + + if (!highPriorityWorkItems.TryDequeue(out workItem)) + { + return false; } - return callback; + tl.workState |= ThreadPoolWorkQueueThreadLocals.WorkState.IsProcessingHighPriorityWorkItems; + _mayHaveHighPriorityWorkItems = 1; + return true; } public static long LocalCount @@ -571,13 +604,11 @@ public static long LocalCount } } - public long GlobalCount => - (ThreadPool.SupportsTimeSensitiveWorkItems ? timeSensitiveWorkQueue!.Count : 0) + workItems.Count; + public long GlobalCount => (long)highPriorityWorkItems.Count + workItems.Count; // Time in ms for which ThreadPoolWorkQueue.Dispatch keeps executing normal work items before either returning from - // Dispatch (if SupportsTimeSensitiveWorkItems is false), or checking for and dispatching a time-sensitive work item - // before continuing with normal work items - private const uint DispatchQuantumMs = 30; + // Dispatch (if YieldFromDispatchLoop is true), or performing periodic activities + public const uint DispatchQuantumMs = 30; /// /// Dispatches work items to this thread. @@ -596,21 +627,17 @@ internal static bool Dispatch() object? workItem = null; { -#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be a constant in some runtimes. - if (ThreadPool.SupportsTimeSensitiveWorkItems) + // Alternate between checking for high-prioriy and normal-priority work first, that way both sets of work + // items get a chance to run in situations where worker threads are starved and work items that run also + // take over the thread, sustaining starvation. For example, when worker threads are continually starved, + // high-priority work items may always be queued and normal-priority work items may not get a chance to run. + bool dispatchNormalPriorityWorkFirst = workQueue._dispatchNormalPriorityWorkFirst; + if (dispatchNormalPriorityWorkFirst && + (tl.workState & ThreadPoolWorkQueueThreadLocals.WorkState.MayHaveLocalWorkItems) == 0) { - // Alternate between checking for time-sensitive work or other work first, that way both sets of work items - // get a chance to run in situations where worker threads are starved and work items that run also take over - // the thread, sustaining starvation. For example, if time-sensitive work is always checked last here, timer - // callbacks may not run when worker threads are continually starved. - bool dispatchTimeSensitiveWorkFirst = workQueue._dispatchTimeSensitiveWorkFirst; - workQueue._dispatchTimeSensitiveWorkFirst = !dispatchTimeSensitiveWorkFirst; - if (dispatchTimeSensitiveWorkFirst) - { - workItem = workQueue.TryDequeueTimeSensitiveWorkItem(); - } + workQueue._dispatchNormalPriorityWorkFirst = !dispatchNormalPriorityWorkFirst; + workQueue.workItems.TryDequeue(out workItem); } -#pragma warning restore CS0162 if (workItem == null) { @@ -642,6 +669,8 @@ internal static bool Dispatch() // parallelization may be necessary here for correctness (aside from perf) if the work item blocks for some // reason that may have a dependency on other queued work items. workQueue.EnsureThreadRequested(); + + // After this point, this method is no longer responsible for ensuring thread requests } // Has the desire for logging changed since the last time we entered? @@ -662,7 +691,6 @@ internal static bool Dispatch() // // Loop until our quantum expires or there is no work. // - bool returnValue; while (true) { if (workItem == null) @@ -672,27 +700,16 @@ internal static bool Dispatch() if (workItem == null) { - // - // No work. - // If we missed a steal, though, there may be more work in the queue. - // Instead of looping around and trying again, we'll just request another thread. Hopefully the thread - // that owns the contended work-stealing queue will pick up its own workitems in the meantime, - // which will be more efficient than this thread doing it anyway. - // - if (missedSteal) - { - // Ensure a thread is requested before returning - returnValue = true; - break; - } - - // Tell the VM we're returning normally, not because Hill Climbing asked us to return. + // May have missed a steal, but this method is not responsible for ensuring thread requests anymore. See + // the dequeue before the loop. return true; } } - if (workQueue.loggingEnabled && FrameworkEventSource.Log.IsEnabled()) + if (workQueue._loggingEnabled && FrameworkEventSource.Log.IsEnabled()) + { FrameworkEventSource.Log.ThreadPoolDequeueWorkObject(workItem); + } // // Execute the workitem outside of any finally blocks, so that it can be aborted if needed. @@ -733,12 +750,11 @@ internal static bool Dispatch() if (!ThreadPool.NotifyWorkItemComplete(threadLocalCompletionCountObject, currentTickCount)) { // This thread is being parked and may remain inactive for a while. Transfer any thread-local work items - // to ensure that they would not be heavily delayed. + // to ensure that they would not be heavily delayed. Tell the caller that this thread was requested to stop + // processing work items. tl.TransferLocalWork(); - - // Ensure a thread is requested before returning - returnValue = false; - break; + tl.ResetWorkItemProcessingState(); + return false; } // Check if the dispatch quantum has expired @@ -749,14 +765,12 @@ internal static bool Dispatch() // The quantum expired, do any necessary periodic activities -#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be a constant in some runtimes. - if (!ThreadPool.SupportsTimeSensitiveWorkItems) + if (ThreadPool.YieldFromDispatchLoop) { - // The runtime-specific thread pool implementation does not support managed time-sensitive work, need to - // return to the VM to let it perform its own time-sensitive work. Tell the VM we're returning normally. - // Ensure a thread is requested before returning. - returnValue = true; - break; + // The runtime-specific thread pool implementation requires the Dispatch loop to return to the VM + // periodically to let it perform its own work + tl.ResetWorkItemProcessingState(); + return true; } // This method will continue to dispatch work items. Refresh the start tick count for the next dispatch @@ -765,16 +779,7 @@ internal static bool Dispatch() // Periodically refresh whether logging is enabled workQueue.RefreshLoggingEnabled(); - - // Consistent with CoreCLR currently, only one time-sensitive work item is run periodically between quantums - // of time spent running work items in the normal thread pool queues, until the normal queues are depleted. - // These are basically lower-priority but time-sensitive work items. - workItem = workQueue.TryDequeueTimeSensitiveWorkItem(); -#pragma warning restore CS0162 } - - workQueue.EnsureThreadRequested(); - return returnValue; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -818,6 +823,7 @@ internal sealed class ThreadPoolWorkQueueThreadLocals [ThreadStatic] public static ThreadPoolWorkQueueThreadLocals? threadLocals; + public WorkState workState; public readonly ThreadPoolWorkQueue workQueue; public readonly ThreadPoolWorkQueue.WorkStealingQueue workStealingQueue; public readonly Thread currentThread; @@ -833,12 +839,16 @@ public ThreadPoolWorkQueueThreadLocals(ThreadPoolWorkQueue tpq) threadLocalCompletionCountObject = ThreadPool.GetOrCreateThreadLocalCompletionCountObject(); } + public void ResetWorkItemProcessingState() => workState &= ~WorkState.IsProcessingHighPriorityWorkItems; + public void TransferLocalWork() { while (workStealingQueue.LocalPop() is object cb) { workQueue.Enqueue(cb, forceGlobal: true); } + + workState &= ~WorkState.MayHaveLocalWorkItems; } ~ThreadPoolWorkQueueThreadLocals() @@ -850,6 +860,90 @@ public void TransferLocalWork() ThreadPoolWorkQueue.WorkStealingQueueList.Remove(workStealingQueue); } } + + [Flags] + public enum WorkState + { + MayHaveLocalWorkItems = 1 << 0, + IsProcessingHighPriorityWorkItems = 1 << 1 + } + } + + internal sealed class ThreadPoolTypedWorkItemQueue : IThreadPoolWorkItem + { + private int _isScheduledForProcessing; + private readonly ConcurrentQueue _workItems = new ConcurrentQueue(); + private readonly Action _callback; + + public ThreadPoolTypedWorkItemQueue(Action callback) => _callback = callback; + + public int Count => _workItems.Count; + + public void Enqueue(T workItem) + { + BatchEnqueue(workItem); + CompleteBatchEnqueue(); + } + + public void BatchEnqueue(T workItem) => _workItems.Enqueue(workItem); + public void CompleteBatchEnqueue() => ScheduleForProcessing(); + + private void ScheduleForProcessing() + { + // Only one thread is requested at a time to avoid over-parallelization. Currently where this type is used, queued + // work is expected to be processed at high priority. The implementation could be modified to support different + // priorities if necessary. + if (Interlocked.CompareExchange(ref _isScheduledForProcessing, 1, 0) == 0) + { + ThreadPool.UnsafeQueueHighPriorityWorkItemInternal(this); + } + } + + void IThreadPoolWorkItem.Execute() + { + Debug.Assert(_isScheduledForProcessing != 0); + + // This change needs to be visible to other threads that may enqueue work items before a work item is attempted to + // be dequeued by the current thread. In particular, if an enqueuer queues a work item and does not schedule for + // processing, and the current thread is the last thread processing work items, the current thread must see the work + // item queued by the enqueuer. + _isScheduledForProcessing = 0; + Interlocked.MemoryBarrier(); + if (!_workItems.TryDequeue(out T? workItem)) + { + return; + } + + ScheduleForProcessing(); + + ThreadPoolWorkQueueThreadLocals tl = ThreadPoolWorkQueueThreadLocals.threadLocals!; + Debug.Assert(tl != null); + Thread currentThread = tl.currentThread; + Debug.Assert(currentThread == Thread.CurrentThread); + uint completedCount = 0; + int startTimeMs = Environment.TickCount; + while (true) + { + _callback(workItem); + + if (++completedCount == uint.MaxValue || + (tl.workState & ThreadPoolWorkQueueThreadLocals.WorkState.MayHaveLocalWorkItems) != 0 || + (uint)(Environment.TickCount - startTimeMs) >= ThreadPoolWorkQueue.DispatchQuantumMs / 2 || + !_workItems.TryDequeue(out workItem)) + { + break; + } + + // Return to clean ExecutionContext and SynchronizationContext. This may call user code (AsyncLocal value + // change notifications). + ExecutionContext.ResetThreadPoolThread(currentThread); + + // Reset thread state after all user code for the work item has completed + currentThread.ResetThreadPoolThread(); + } + + ThreadInt64PersistentCounter.Add(tl.threadLocalCompletionCountObject!, completedCount); + } } public delegate void WaitCallback(object? state); @@ -1290,28 +1384,10 @@ public static bool UnsafeQueueUserWorkItem(IThreadPoolWorkItem callBack, bool pr return true; } - internal static void UnsafeQueueUserWorkItemInternal(object callBack, bool preferLocal) - { - Debug.Assert((callBack is IThreadPoolWorkItem) ^ (callBack is Task)); - + internal static void UnsafeQueueUserWorkItemInternal(object callBack, bool preferLocal) => s_workQueue.Enqueue(callBack, forceGlobal: !preferLocal); - } - - internal static void UnsafeQueueTimeSensitiveWorkItem(IThreadPoolWorkItem timeSensitiveWorkItem) - { -#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be constant true in some runtimes. - if (SupportsTimeSensitiveWorkItems) - { - UnsafeQueueTimeSensitiveWorkItemInternal(timeSensitiveWorkItem); - return; - } - - UnsafeQueueUserWorkItemInternal(timeSensitiveWorkItem, preferLocal: false); -#pragma warning restore CS0162 - } - - internal static void UnsafeQueueTimeSensitiveWorkItemInternal(IThreadPoolWorkItem timeSensitiveWorkItem) => - s_workQueue.EnqueueTimeSensitiveWorkItem(timeSensitiveWorkItem); + internal static void UnsafeQueueHighPriorityWorkItemInternal(IThreadPoolWorkItem callBack) => + s_workQueue.EnqueueAtHighPriority(callBack); // This method tries to take the target callback out of the current thread's queue. internal static bool TryPopCustomWorkItem(object workItem) @@ -1323,16 +1399,11 @@ internal static bool TryPopCustomWorkItem(object workItem) // Get all workitems. Called by TaskScheduler in its debugger hooks. internal static IEnumerable GetQueuedWorkItems() { -#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be a constant in some runtimes. - if (ThreadPool.SupportsTimeSensitiveWorkItems) + // Enumerate high-priority queue + foreach (object workItem in s_workQueue.highPriorityWorkItems) { - // Enumerate time-sensitive work item queue - foreach (object workItem in s_workQueue.timeSensitiveWorkQueue!) - { - yield return workItem; - } + yield return workItem; } -#pragma warning restore CS0162 // Enumerate global queue foreach (object workItem in s_workQueue.workItems) @@ -1375,16 +1446,11 @@ internal static IEnumerable GetLocallyQueuedWorkItems() internal static IEnumerable GetGloballyQueuedWorkItems() { -#pragma warning disable CS0162 // Unreachable code detected. SupportsTimeSensitiveWorkItems may be a constant in some runtimes. - if (ThreadPool.SupportsTimeSensitiveWorkItems) + // Enumerate high-priority queue + foreach (object workItem in s_workQueue.highPriorityWorkItems) { - // Enumerate time-sensitive work item queue - foreach (object workItem in s_workQueue.timeSensitiveWorkQueue!) - { - yield return workItem; - } + yield return workItem; } -#pragma warning restore CS0162 // Enumerate global queue foreach (object workItem in s_workQueue.workItems) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs index f53047e3f692a6..373f7abcb5ae58 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 @@ -128,7 +128,7 @@ private static void TimerThread() { foreach (TimerQueue timerToFire in timersToFire) { - ThreadPool.UnsafeQueueTimeSensitiveWorkItem(timerToFire); + ThreadPool.UnsafeQueueHighPriorityWorkItemInternal(timerToFire); } timersToFire.Clear(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index 781cd38aaadb30..2035bf166da851 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -530,6 +530,14 @@ internal static void ArgumentOutOfRangeException_Enum_Value() throw new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_Enum); } + [DoesNotReturn] + internal static void ThrowApplicationException(int hr) + { + var ex = new ApplicationException(); + ex.HResult = hr; + throw ex; + } + private static Exception GetArraySegmentCtorValidationFailedException(Array? array, int offset, int count) { if (array == null) @@ -1129,6 +1137,7 @@ internal enum ExceptionArgument offset, stream, anyOf, + overlapped, } // diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 4e7cecac1a25a8..7c80f753f4a3b6 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -264,7 +264,6 @@ - @@ -288,10 +287,11 @@ + + - diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/Overlapped.cs b/src/mono/System.Private.CoreLib/src/System/Threading/Overlapped.cs index 5d8198bfad3987..6838de54ba5904 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/Overlapped.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/Overlapped.cs @@ -6,62 +6,6 @@ namespace System.Threading { - #region class _IOCompletionCallback - - internal unsafe class _IOCompletionCallback - { - private readonly IOCompletionCallback _ioCompletionCallback; - private readonly ExecutionContext _executionContext; - private uint _errorCode; // Error code - private uint _numBytes; // No. of bytes transferred - private NativeOverlapped* _pNativeOverlapped; - - internal _IOCompletionCallback(IOCompletionCallback ioCompletionCallback, ExecutionContext executionContext) - { - _ioCompletionCallback = ioCompletionCallback; - _executionContext = executionContext; - } - - // Context callback: same sig for SendOrPostCallback and ContextCallback - internal static ContextCallback s_ccb = new ContextCallback(IOCompletionCallback_Context); - private static void IOCompletionCallback_Context(object? state) - { - _IOCompletionCallback helper = (_IOCompletionCallback)state!; - Debug.Assert(helper != null, "_IOCompletionCallback cannot be null"); - helper._ioCompletionCallback(helper._errorCode, helper._numBytes, helper._pNativeOverlapped); - } - - //TODO: call from ThreadPool - internal static unsafe 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!; - helper._errorCode = errorCode; - helper._numBytes = numBytes; - helper._pNativeOverlapped = pNativeOverlapped; - ExecutionContext.Run(helper._executionContext, s_ccb, helper); - } - - //Quickly check the VM again, to see if a packet has arrived. - //OverlappedData.CheckVMForIOPacket(out pOVERLAP, out errorCode, out numBytes); - pNativeOverlapped = null; - } while (pNativeOverlapped != null); - } - } - - #endregion class _IOCompletionCallback - #region class OverlappedData internal sealed unsafe class OverlappedData diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs b/src/mono/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Browser.Mono.cs similarity index 100% rename from src/mono/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs rename to src/mono/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Browser.Mono.cs 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 043d9e1302c6bf..45381671536bb7 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 @@ -118,5 +118,34 @@ private static void Callback() _callbackQueued = false; ThreadPoolWorkQueue.Dispatch(); } + + private static unsafe void NativeOverlappedCallback(object? obj) + { + NativeOverlapped* overlapped = (NativeOverlapped*)(IntPtr)obj!; + _IOCompletionCallback.PerformSingleIOCompletionCallback(overlapped, 0, 0); + } + + [CLSCompliant(false)] + [SupportedOSPlatform("windows")] + public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) + { + // OS doesn't signal handle, so do it here (CoreCLR does this assignment in ThreadPoolNative::CorPostQueuedCompletionStatus) + overlapped->InternalLow = (IntPtr)0; + // Both types of callbacks are executed on the same thread pool + return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (IntPtr)overlapped); + } + + [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] + [SupportedOSPlatform("windows")] + public static bool BindHandle(IntPtr osHandle) + { + throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle + } + + [SupportedOSPlatform("windows")] + public static bool BindHandle(SafeHandle osHandle) + { + throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle + } } } 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 260dfee24f3e67..fb700981fb26e3 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 @@ -14,35 +14,6 @@ internal static void ReportThreadStatus(bool isWorking) { } - private static unsafe void NativeOverlappedCallback(object? obj) - { - NativeOverlapped* overlapped = (NativeOverlapped*)(IntPtr)obj!; - _IOCompletionCallback.PerformIOCompletionCallback(0, 0, overlapped); - } - - [CLSCompliant(false)] - [SupportedOSPlatform("windows")] - public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) - { - // OS doesn't signal handle, so do it here (CoreCLR does this assignment in ThreadPoolNative::CorPostQueuedCompletionStatus) - overlapped->InternalLow = (IntPtr)0; - // Both types of callbacks are executed on the same thread pool - return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (IntPtr)overlapped); - } - - [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] - [SupportedOSPlatform("windows")] - public static bool BindHandle(IntPtr osHandle) - { - throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle - } - - [SupportedOSPlatform("windows")] - public static bool BindHandle(SafeHandle osHandle) - { - throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle - } - private static long PendingUnmanagedWorkItemCount => 0; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.PlatformNotSupported.cs b/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Browser.Mono.cs similarity index 100% rename from src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.PlatformNotSupported.cs rename to src/mono/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Browser.Mono.cs From 37a66e0f7e4dfea031a2319bf0f990b441566de7 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sun, 6 Feb 2022 23:42:39 -0800 Subject: [PATCH 02/13] Translate NtStatus codes to system error codes, fix some other failures, address feedback --- .../src/System/Threading/ThreadPool.Windows.cs | 15 ++++++++------- .../Threading/Overlapped._IOCompletionCallback.cs | 9 --------- .../Threading/PortableThreadPool.IO.Windows.cs | 13 +++++++++++-- .../src/System/ThrowHelper.cs | 2 ++ .../tests/OverlappedTests.cs | 7 +++++++ .../System/Threading/ThreadPool.Browser.Mono.cs | 15 +++++++++------ 6 files changed, 37 insertions(+), 24 deletions(-) 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 c357b243ac782d..35a1726da2866c 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 @@ -412,21 +412,22 @@ private static RegisteredWaitHandle RegisterWaitForSingleObject( return registeredWaitHandle; } - private static unsafe void NativeOverlappedCallback(object? obj) - { - Debug.Assert(obj != null); - NativeOverlapped* overlapped = (NativeOverlapped*)(IntPtr)obj; - _IOCompletionCallback.PerformSingleIOCompletionCallback(overlapped, 0, 0); - } + private static unsafe void NativeOverlappedCallback(nint overlappedPtr) => + _IOCompletionCallback.PerformSingleIOCompletionCallback((NativeOverlapped*)overlappedPtr, 0, 0); [CLSCompliant(false)] [SupportedOSPlatform("windows")] public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) { + if (overlapped == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped); + } + // OS doesn't signal handle, so do it here (CoreCLR does this assignment in ThreadPoolNative::CorPostQueuedCompletionStatus) overlapped->InternalLow = (IntPtr)0; // Both types of callbacks are executed on the same thread pool - return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (IntPtr)overlapped); + return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (nint)overlapped, preferLocal: false); } [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped._IOCompletionCallback.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped._IOCompletionCallback.cs index 2b3e0ef604a469..cfb9ba8a3e4959 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped._IOCompletionCallback.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped._IOCompletionCallback.cs @@ -29,15 +29,6 @@ private static void IOCompletionCallback_Context(object? state) helper._ioCompletionCallback(helper._errorCode, helper._numBytes, helper._pNativeOverlapped); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void PerformSingleIOCompletionCallback(NativeOverlapped* pNativeOverlapped, uint numBytes) - { - Debug.Assert(pNativeOverlapped != null); - - // When the error code is not provided in the callback, it is in the InternalLow field - PerformSingleIOCompletionCallback(pNativeOverlapped, (uint)(nint)pNativeOverlapped->InternalLow, numBytes); - } - public static void PerformSingleIOCompletionCallback(NativeOverlapped* pNativeOverlapped, uint errorCode, uint numBytes) { Debug.Assert(pNativeOverlapped != null); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs index cf77df95c8b70b..43e810b7b8a5e5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs @@ -5,6 +5,7 @@ using System.Diagnostics.Tracing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Internal.Runtime.CompilerServices; namespace System.Threading { @@ -117,7 +118,7 @@ public IOCompletionPoller(nint port) _nativeEvents = (Interop.Kernel32.OVERLAPPED_ENTRY*) - Marshal.AllocHGlobal(NativeEventCapacity * Marshal.SizeOf()); + NativeMemory.Alloc((nuint)NativeEventCapacity * (nuint)Unsafe.SizeOf()); _events = new ThreadPoolTypedWorkItemQueue(ProcessEventDelegate); // Thread pool threads must start in the default execution context without transferring the context, so @@ -167,7 +168,15 @@ private static void ProcessEvent(Event e) NativeRuntimeEventSource.Log.ThreadPoolIODequeue(e.nativeOverlapped); } - _IOCompletionCallback.PerformSingleIOCompletionCallback(e.nativeOverlapped, e.bytesTransferred); + // The NtStatus code for the operation is in the InternalLow field + uint ntStatus = (uint)(nint)e.nativeOverlapped->InternalLow; + uint errorCode = Interop.Errors.ERROR_SUCCESS; + if (ntStatus != Interop.StatusOptions.STATUS_SUCCESS) + { + errorCode = Interop.NtDll.RtlNtStatusToDosError((int)ntStatus); + } + + _IOCompletionCallback.PerformSingleIOCompletionCallback(e.nativeOverlapped, errorCode, e.bytesTransferred); } private readonly struct Event diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index 2035bf166da851..406ec082ef792b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -868,6 +868,8 @@ private static string GetArgumentName(ExceptionArgument argument) return "stream"; case ExceptionArgument.anyOf: return "anyOf"; + case ExceptionArgument.overlapped: + return "overlapped"; default: Debug.Fail("The enum value is not defined, please check the ExceptionArgument Enum."); return ""; diff --git a/src/libraries/System.Threading.Overlapped/tests/OverlappedTests.cs b/src/libraries/System.Threading.Overlapped/tests/OverlappedTests.cs index d5957a2d125450..4468148eba692b 100644 --- a/src/libraries/System.Threading.Overlapped/tests/OverlappedTests.cs +++ b/src/libraries/System.Threading.Overlapped/tests/OverlappedTests.cs @@ -192,6 +192,13 @@ public static unsafe void PackPosTest1() } } + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] // ThreadPool.UnsafeQueueNativeOverlapped is not supported on Unix + public static unsafe void UnsafeQueueNativeOverlappedNegTest() + { + AssertExtensions.Throws("overlapped", () => ThreadPool.UnsafeQueueNativeOverlapped(null)); + } + internal static unsafe IOCompletionCallback MyCallback(AsyncHelper helper) { IOCompletionCallback del = delegate (uint param1, uint param2, NativeOverlapped* overlapped) 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 45381671536bb7..f4c97cfa3fb9d7 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 @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Diagnostics.CodeAnalysis; using Microsoft.Win32.SafeHandles; @@ -119,20 +120,22 @@ private static void Callback() ThreadPoolWorkQueue.Dispatch(); } - private static unsafe void NativeOverlappedCallback(object? obj) - { - NativeOverlapped* overlapped = (NativeOverlapped*)(IntPtr)obj!; - _IOCompletionCallback.PerformSingleIOCompletionCallback(overlapped, 0, 0); - } + private static unsafe void NativeOverlappedCallback(nint overlappedPtr) => + _IOCompletionCallback.PerformSingleIOCompletionCallback((NativeOverlapped*)overlappedPtr, 0, 0); [CLSCompliant(false)] [SupportedOSPlatform("windows")] public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) { + if (overlapped == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.overlapped); + } + // OS doesn't signal handle, so do it here (CoreCLR does this assignment in ThreadPoolNative::CorPostQueuedCompletionStatus) overlapped->InternalLow = (IntPtr)0; // Both types of callbacks are executed on the same thread pool - return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (IntPtr)overlapped); + return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (nint)overlapped, preferLocal: false); } [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] From 3233ae098bfb3533766b22e9c8046e4f99b06e65 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 7 Feb 2022 10:55:18 -0800 Subject: [PATCH 03/13] Fix wasm build --- .../src/System/Threading/ThreadPool.Browser.Mono.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 f4c97cfa3fb9d7..ec010ba405f790 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 @@ -26,12 +26,9 @@ public bool Unregister(WaitHandle? waitObject) public static partial class ThreadPool { - // Time-sensitive work items are those that may need to run ahead of normal work items at least periodically. For a - // runtime that does not support time-sensitive work items on the managed side, the thread pool yields the thread to the - // runtime periodically (by exiting the dispatch loop) so that the runtime may use that thread for processing - // any time-sensitive work. For a runtime that supports time-sensitive work items on the managed side, the thread pool - // does not yield the thread and instead processes time-sensitive work items queued by specific APIs periodically. - internal const bool SupportsTimeSensitiveWorkItems = false; // the timer currently doesn't queue time-sensitive work + // 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 => true; private const bool IsWorkerTrackingEnabledInConfig = false; From 7c0d7d513470d7fe74abaaa6745d1807ad47aa51 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 7 Feb 2022 11:44:44 -0800 Subject: [PATCH 04/13] Try to fix a time-sensitive test, enable some tests on Mono based on #34582 --- .../System.IO.Pipes/tests/AssemblyInfo.cs | 1 - .../tests/StreamReader/StreamReaderTests.cs | 18 ++++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.IO.Pipes/tests/AssemblyInfo.cs b/src/libraries/System.IO.Pipes/tests/AssemblyInfo.cs index 806ccf216126e3..8106929fe18971 100644 --- a/src/libraries/System.IO.Pipes/tests/AssemblyInfo.cs +++ b/src/libraries/System.IO.Pipes/tests/AssemblyInfo.cs @@ -3,5 +3,4 @@ using Xunit; -[assembly: ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [assembly: SkipOnPlatform(TestPlatforms.Browser, "System.IO.Pipes is not supported on Browser")] diff --git a/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs b/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs index fccc269868ed7e..34088f43c238ec 100644 --- a/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs +++ b/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs @@ -141,10 +141,24 @@ public async Task ReadToEndAsync_WithCancellation() File.WriteAllLines(path, Enumerable.Repeat("A very large file used for testing StreamReader cancellation. 0123456789012345678901234567890123456789.", 1_000_000)); using StreamReader reader = File.OpenText(path); - using CancellationTokenSource cts = new (TimeSpan.FromMilliseconds(50)); + using CancellationTokenSource cts = new (); var token = cts.Token; - var ex = await Assert.ThrowsAnyAsync(async () => await reader.ReadToEndAsync(token)); + var ex = await Assert.ThrowsAnyAsync(async () => + { + Task readToEndTask = reader.ReadToEndAsync(token); + + // This is a time-sensitive test where the cancellation needs to happen before the async read completes. + // A sleep may be too long a delay, so spin-wait for a very short duration before canceling. + SpinWait spinner = default; + while (!spinner.NextSpinWillYield) + { + spinner.SpinOnce(sleep1Threshold: -1); + } + + cts.Cancel(); + await readToEndTask; + }); Assert.Equal(token, ex.CancellationToken); } From 759e11a9a393c2225a25df301ee0505a91dda605 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 7 Feb 2022 13:56:16 -0800 Subject: [PATCH 05/13] Enable some more tests on Mono based on #34582 --- .../TestUtilities/System/PlatformDetection.cs | 2 +- .../tests/ConfigurationExtensionTest.cs | 3 --- .../tests/FunctionalTests/ArrayTests.cs | 2 -- .../FunctionalTests/ConfigurationTests.cs | 12 --------- .../tests/PhysicalFileProviderTests.cs | 27 ------------------- .../tests/PhysicalFilesWatcherTests.cs | 1 - .../UnitTests/ConsoleLifetimeExitTests.cs | 2 -- .../tests/UnitTests/HostBuilderTests.cs | 5 ---- .../tests/UnitTests/HostTests.cs | 10 ------- .../OptionsBuilderExtensionsTests.cs | 10 ------- .../LoggerMessageGeneratorEmitterTests.cs | 1 - .../OptionsBuilderTest.cs | 4 --- .../tests/FileSystemAclExtensionsTests.cs | 4 +-- .../tests/File/AppendAsync.cs | 1 - .../System.IO.FileSystem/tests/File/Create.cs | 2 +- .../tests/File/EncryptDecrypt.cs | 1 - .../tests/File/ReadWriteAllBytesAsync.cs | 1 - .../tests/File/ReadWriteAllLinesAsync.cs | 1 - .../tests/File/ReadWriteAllTextAsync.cs | 2 -- .../tests/FileStream/CopyToAsync.cs | 1 - .../FileStreamConformanceTests.Windows.cs | 2 -- .../FileStream/FileStreamConformanceTests.cs | 2 -- .../tests/FileStream/IsAsync.cs | 1 - .../tests/FileStream/ReadAsync.cs | 2 -- .../tests/FileStream/SafeFileHandle.cs | 1 - .../tests/FileStream/WriteAsync.cs | 2 -- .../FileStream/ctor_sfh_fa_buffer_async.cs | 1 - .../ctor_str_fm_fa_fs_buffer_async.cs | 1 - .../FileStream/ctor_str_fm_fa_fs_buffer_fo.cs | 1 - .../tests/RandomAccess/Mixed.Windows.cs | 1 - .../tests/RandomAccess/NoBuffering.Windows.cs | 1 - .../tests/RandomAccess/ReadAsync.cs | 1 - .../tests/RandomAccess/ReadScatterAsync.cs | 1 - .../tests/RandomAccess/WriteAsync.cs | 1 - .../tests/RandomAccess/WriteGatherAsync.cs | 1 - .../tests/StreamReader/StreamReaderTests.cs | 1 - 36 files changed, 4 insertions(+), 108 deletions(-) diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index f348123953688f..d76368d6d0f965 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -113,7 +113,7 @@ private static bool GetLinqExpressionsBuiltWithIsInterpretingOnly() // Drawing is not supported on non windows platforms in .NET 7.0+. public static bool IsDrawingSupported => IsWindows && IsNotWindowsNanoServer && IsNotWindowsServerCore; - public static bool IsAsyncFileIOSupported => !IsBrowser && !(IsWindows && IsMonoRuntime); // https://github.com/dotnet/runtime/issues/34582 + public static bool IsAsyncFileIOSupported => !IsBrowser; public static bool IsLineNumbersSupported => !IsNativeAot; diff --git a/src/libraries/Microsoft.Extensions.Configuration.UserSecrets/tests/ConfigurationExtensionTest.cs b/src/libraries/Microsoft.Extensions.Configuration.UserSecrets/tests/ConfigurationExtensionTest.cs index 82391a90645af6..e92a875b34ef22 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.UserSecrets/tests/ConfigurationExtensionTest.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.UserSecrets/tests/ConfigurationExtensionTest.cs @@ -52,7 +52,6 @@ private void SetSecret(string id, string key, string value) } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [ActiveIssue("https://github.com/dotnet/runtime/issues/60584", TestPlatforms.iOS | TestPlatforms.tvOS)] public void AddUserSecrets_FindsAssemblyAttribute() { @@ -68,7 +67,6 @@ public void AddUserSecrets_FindsAssemblyAttribute() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [ActiveIssue("https://github.com/dotnet/runtime/issues/60584", TestPlatforms.iOS | TestPlatforms.tvOS)] public void AddUserSecrets_FindsAssemblyAttributeFromType() { @@ -123,7 +121,6 @@ public void AddUserSecrets_DoesThrowsIfNotOptionalAndSecretDoesNotExist() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [ActiveIssue("https://github.com/dotnet/runtime/issues/60584", TestPlatforms.iOS | TestPlatforms.tvOS)] public void AddUserSecrets_With_SecretsId_Passed_Explicitly() { diff --git a/src/libraries/Microsoft.Extensions.Configuration/tests/FunctionalTests/ArrayTests.cs b/src/libraries/Microsoft.Extensions.Configuration/tests/FunctionalTests/ArrayTests.cs index d173c3e3cdb68e..c4ee3d0e231f27 100644 --- a/src/libraries/Microsoft.Extensions.Configuration/tests/FunctionalTests/ArrayTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration/tests/FunctionalTests/ArrayTests.cs @@ -47,7 +47,6 @@ public class ArrayTests : IDisposable "; [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [ActiveIssue("https://github.com/dotnet/runtime/issues/60583", TestPlatforms.iOS | TestPlatforms.tvOS)] public void DifferentConfigSources_Merged_KeysAreSorted() { @@ -77,7 +76,6 @@ public void DifferentConfigSources_Merged_KeysAreSorted() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [ActiveIssue("https://github.com/dotnet/runtime/issues/60583", TestPlatforms.iOS | TestPlatforms.tvOS)] public void DifferentConfigSources_Merged_WithOverwrites() { diff --git a/src/libraries/Microsoft.Extensions.Configuration/tests/FunctionalTests/ConfigurationTests.cs b/src/libraries/Microsoft.Extensions.Configuration/tests/FunctionalTests/ConfigurationTests.cs index 9314f4e0dd333d..7c03f5f45f634a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration/tests/FunctionalTests/ConfigurationTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration/tests/FunctionalTests/ConfigurationTests.cs @@ -207,7 +207,6 @@ public void MissingFileDoesNotIncludesAbsolutePathIfWithNoPhysicalPath() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void LoadAndCombineKeyValuePairsFromDifferentConfigurationProviders() { WriteTestFiles(); @@ -247,7 +246,6 @@ public void LoadAndCombineKeyValuePairsFromDifferentConfigurationProviders() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void LoadAndCombineKeyValuePairsFromDifferentConfigurationProvidersWithAbsolutePath() { WriteTestFiles(); @@ -288,7 +286,6 @@ public void LoadAndCombineKeyValuePairsFromDifferentConfigurationProvidersWithAb } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void CanOverrideValuesWithNewConfigurationProvider() { WriteTestFiles(); @@ -361,7 +358,6 @@ public IConfigurationProvider Build(IConfigurationBuilder builder) } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [ActiveIssue("https://github.com/dotnet/runtime/issues/60583", TestPlatforms.iOS | TestPlatforms.tvOS)] public void OnLoadErrorWillBeCalledOnJsonParseError() { @@ -391,7 +387,6 @@ public void OnLoadErrorWillBeCalledOnJsonParseError() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void OnLoadErrorWillBeCalledOnXmlParseError() { _fileSystem.WriteFile("error.xml", @"gobblygook"); @@ -419,7 +414,6 @@ public void OnLoadErrorWillBeCalledOnXmlParseError() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void OnLoadErrorWillBeCalledOnIniLoadError() { _fileSystem.WriteFile("error.ini", @"IniKey1=IniValue1 @@ -448,7 +442,6 @@ public void OnLoadErrorWillBeCalledOnIniLoadError() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void OnLoadErrorCanIgnoreErrors() { _fileSystem.WriteFile("error.json", @"{""JsonKey1"": "); @@ -821,7 +814,6 @@ await WaitForChange( } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [ActiveIssue("https://github.com/dotnet/runtime/issues/50866", TestPlatforms.Android)] public void LoadIncorrectJsonFile_ThrowException() { @@ -839,7 +831,6 @@ public void LoadIncorrectJsonFile_ThrowException() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void SetBasePathCalledMultipleTimesForEachSourceLastOneWins() { var builder = new ConfigurationBuilder(); @@ -873,7 +864,6 @@ public void SetBasePathCalledMultipleTimesForEachSourceLastOneWins() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [ActiveIssue("https://github.com/dotnet/runtime/issues/60583", TestPlatforms.iOS | TestPlatforms.tvOS)] public void GetDefaultBasePathForSources() { @@ -904,7 +894,6 @@ public void GetDefaultBasePathForSources() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public void CanEnumerateProviders() { @@ -952,7 +941,6 @@ await WaitForChange( } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void BindingDoesNotThrowIfReloadedDuringBinding() { WriteTestFiles(); diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs index 11d7c6a23d9424..d92302dc019d9e 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs @@ -226,7 +226,6 @@ public void GetFileInfoReturnsNotFoundFileInfoForRelativePathWithEmptySegmentsTh } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void CreateReadStreamSucceedsOnEmptyFile() { using (var root = new TempDirectory(GetTestFilePath())) @@ -324,7 +323,6 @@ public void GetFileInfoReturnsFileInfoWhenExclusionDisabled() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "Browser/iOS/tvOS always uses Active Polling which doesn't return the same instance between multiple calls to Watch(string)")] public void TokenIsSameForSamePath() { @@ -348,7 +346,6 @@ public void TokenIsSameForSamePath() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task TokensFiredOnFileChange() { @@ -379,7 +376,6 @@ public async Task TokensFiredOnFileChange() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task TokenCallbackInvokedOnFileChange() { @@ -416,7 +412,6 @@ public async Task TokenCallbackInvokedOnFileChange() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task WatcherWithPolling_ReturnsTrueForFileChangedWhenFileSystemWatcherDoesNotRaiseEvents() { @@ -447,7 +442,6 @@ public async Task WatcherWithPolling_ReturnsTrueForFileChangedWhenFileSystemWatc } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task WatcherWithPolling_ReturnsTrueForFileRemovedWhenFileSystemWatcherDoesNotRaiseEvents() { @@ -480,7 +474,6 @@ public async Task WatcherWithPolling_ReturnsTrueForFileRemovedWhenFileSystemWatc } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task TokensFiredOnFileDeleted() { @@ -792,7 +785,6 @@ public void GetDirectoryContentsReturnsFilesWhenExclusionDisabled() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task FileChangeTokenNotNotifiedAfterExpiry() { @@ -825,7 +817,6 @@ public async Task FileChangeTokenNotNotifiedAfterExpiry() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "Browser/iOS/tvOS always uses Active Polling which doesn't return the same instance between multiple calls to Watch(string)")] public void TokenIsSameForSamePathCaseInsensitive() { @@ -842,7 +833,6 @@ public void TokenIsSameForSamePathCaseInsensitive() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task CorrectTokensFiredForMultipleFiles() { @@ -876,7 +866,6 @@ public async Task CorrectTokensFiredForMultipleFiles() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task TokenNotAffectedByExceptions() { @@ -935,7 +924,6 @@ public void NoopChangeTokenForFilterThatNavigatesAboveRoot() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [ActiveIssue("https://github.com/dotnet/runtime/issues/58584", TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS)] public void TokenForEmptyFilter() { @@ -952,7 +940,6 @@ public void TokenForEmptyFilter() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void TokenForWhitespaceFilters() { using (var root = new TempDirectory(GetTestFilePath())) @@ -985,7 +972,6 @@ public void NoopChangeTokenForAbsolutePathFilters() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task TokenFiredOnCreation() { @@ -1011,7 +997,6 @@ public async Task TokenFiredOnCreation() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task TokenFiredOnDeletion() { @@ -1037,7 +1022,6 @@ public async Task TokenFiredOnDeletion() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task TokenFiredForFilesUnderPathEndingWithSlash() { @@ -1076,7 +1060,6 @@ public async Task TokenFiredForFilesUnderPathEndingWithSlash() } [Theory] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [InlineData("/")] [InlineData("///")] [InlineData("/\\/")] @@ -1167,7 +1150,6 @@ private async Task TokenNotFiredForInvalidPathStartingWithSlash(string slashes) } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task TokenFiredForGlobbingPatternsPointingToSubDirectory() { @@ -1201,7 +1183,6 @@ public async Task TokenFiredForGlobbingPatternsPointingToSubDirectory() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "Browser/iOS/tvOS always uses Active Polling which doesn't return the same instance between multiple calls to Watch(string)")] public void TokensWithForwardAndBackwardSlashesAreSame() { @@ -1218,7 +1199,6 @@ public void TokensWithForwardAndBackwardSlashesAreSame() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task TokensFiredForOldAndNewNamesOnRename() { @@ -1248,7 +1228,6 @@ public async Task TokensFiredForOldAndNewNamesOnRename() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task TokensFiredForNewDirectoryContentsOnRename() { @@ -1322,7 +1301,6 @@ void Fail(object state) } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task TokenNotFiredForFileNameStartingWithPeriod() { @@ -1348,7 +1326,6 @@ public async Task TokenNotFiredForFileNameStartingWithPeriod() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] // Hidden and system files only make sense on Windows. [PlatformSpecific(TestPlatforms.Windows)] public async Task TokensNotFiredForHiddenAndSystemFiles() @@ -1390,7 +1367,6 @@ public async Task TokensNotFiredForHiddenAndSystemFiles() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task TokensFiredForAllEntriesOnError() { @@ -1419,7 +1395,6 @@ public async Task TokensFiredForAllEntriesOnError() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task WildCardToken_RaisesEventsForNewFilesAdded() { @@ -1446,7 +1421,6 @@ public async Task WildCardToken_RaisesEventsForNewFilesAdded() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task WildCardToken_RaisesEventsWhenFileSystemWatcherDoesNotFire() { @@ -1618,7 +1592,6 @@ public void CreateFileWatcher_CreatesWatcherWithPollingAndActiveFlags() [Theory] [InlineData(false)] [InlineData(true)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task CanDeleteWatchedDirectory(bool useActivePolling) { diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.cs index aca2d08de7386d..9d1efb1de0b605 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.cs +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFilesWatcherTests.cs @@ -37,7 +37,6 @@ public void CreateFileChangeToken_DoesNotAllowPathsAboveRoot() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.tvOS, "System.IO.FileSystem.Watcher is not supported on Browser/iOS/tvOS")] public async Task HandlesOnRenamedEventsThatMatchRootPath() { diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/ConsoleLifetimeExitTests.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/ConsoleLifetimeExitTests.cs index 5c8159051c66c5..0e54db40045e96 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/ConsoleLifetimeExitTests.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/ConsoleLifetimeExitTests.cs @@ -94,7 +94,6 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] // SIGTERM is only handled on net6.0+, so the workaround to "clobber" the exit code is still in place on NetFramework [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void EnsureEnvironmentExitCode() { using var remoteHandle = RemoteExecutor.Invoke(async () => @@ -123,7 +122,6 @@ await Task.Run(() => /// Tests that calling Environment.Exit from the "main" thread doesn't hang the process forever. /// [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void EnsureEnvironmentExitDoesntHang() { // SIGTERM is only handled on net6.0+, so the workaround to "clobber" the exit code is still in place on .NET Framework diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostBuilderTests.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostBuilderTests.cs index abe332ab9b9dd8..c66e3ca37e6306 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostBuilderTests.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostBuilderTests.cs @@ -149,7 +149,6 @@ public void CanConfigureAppConfigurationAndRetrieveFromDI() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void CanConfigureAppConfigurationFromFile() { var hostBuilder = new HostBuilder() @@ -373,7 +372,6 @@ public void DefaultCreatesLoggerFactory() [Theory] [MemberData(nameof(ConfigureHostOptionsTestInput))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void CanConfigureHostOptionsWithOptionsOverload( BackgroundServiceExceptionBehavior testBehavior, TimeSpan testShutdown) { @@ -397,7 +395,6 @@ public void CanConfigureHostOptionsWithOptionsOverload( [Theory] [MemberData(nameof(ConfigureHostOptionsTestInput))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void CanConfigureHostOptionsWithContenxtAndOptionsOverload( BackgroundServiceExceptionBehavior testBehavior, TimeSpan testShutdown) { @@ -642,7 +639,6 @@ public void DisposingHostDisposesContentFileProvider() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void HostServicesSameServiceProviderAsInHostBuilder() { var hostBuilder = Host.CreateDefaultBuilder(); @@ -655,7 +651,6 @@ public void HostServicesSameServiceProviderAsInHostBuilder() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void HostBuilderConfigureDefaultsInterleavesMissingConfigValues() { IHostBuilder hostBuilder = new HostBuilder(); diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostTests.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostTests.cs index 8ab0fce697bc0b..f0e8662afcf93d 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostTests.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostTests.cs @@ -35,7 +35,6 @@ public async Task StopAsyncWithCancellation() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void CreateDefaultBuilder_IncludesContentRootByDefault() { var expected = Directory.GetCurrentDirectory(); @@ -48,7 +47,6 @@ public void CreateDefaultBuilder_IncludesContentRootByDefault() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void CreateDefaultBuilder_IncludesCommandLineArguments() { var expected = Directory.GetParent(Directory.GetCurrentDirectory()).FullName; // It must exist @@ -59,7 +57,6 @@ public void CreateDefaultBuilder_IncludesCommandLineArguments() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void CreateDefaultBuilder_RegistersEventSourceLogger() { var listener = new TestEventListener(); @@ -76,7 +73,6 @@ public void CreateDefaultBuilder_RegistersEventSourceLogger() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void CreateDefaultBuilder_EnablesActivityTracking() { var parentActivity = new Activity("ParentActivity"); @@ -115,7 +111,6 @@ public void CreateDefaultBuilder_EnablesActivityTracking() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void CreateDefaultBuilder_EnablesScopeValidation() { using var host = Host.CreateDefaultBuilder() @@ -130,7 +125,6 @@ public void CreateDefaultBuilder_EnablesScopeValidation() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void CreateDefaultBuilder_EnablesValidateOnBuild() { var hostBuilder = Host.CreateDefaultBuilder() @@ -187,7 +181,6 @@ public void LastCallToUseEnvironmentWins(string environment) } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [ActiveIssue("https://github.com/dotnet/runtime/issues/48696")] public async Task CreateDefaultBuilder_ConfigJsonDoesNotReload() { @@ -222,7 +215,6 @@ string SaveRandomConfig() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public async Task CreateDefaultBuilder_ConfigJsonDoesReload() { var reloadFlagConfig = new Dictionary() { { "hostbuilder:reloadConfigOnChange", "true" } }; @@ -274,7 +266,6 @@ static string SaveRandomConfig(string appSettingsPath) } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public async Task CreateDefaultBuilder_SecretsDoesReload() { var secretId = Assembly.GetExecutingAssembly().GetName().Name; @@ -318,7 +309,6 @@ static string SaveRandomSecret(string secretPath) } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void CreateDefaultBuilder_RespectShutdownTimeout() { var notDefaultTimeoutSeconds = 99; diff --git a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/OptionsBuilderExtensionsTests.cs b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/OptionsBuilderExtensionsTests.cs index 793bedf2003db9..621d94bb4c38ce 100644 --- a/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/OptionsBuilderExtensionsTests.cs +++ b/src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/OptionsBuilderExtensionsTests.cs @@ -25,7 +25,6 @@ public void ValidateOnStart_NullOptionsBuilder_Throws() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public async Task ValidateOnStart_ConfigureAndValidateThenCallValidateOnStart_ValidatesFailure() { var hostBuilder = CreateHostBuilder(services => @@ -48,7 +47,6 @@ public async Task ValidateOnStart_ConfigureAndValidateThenCallValidateOnStart_Va } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public async Task ValidateOnStart_CallFirstThenConfigureAndValidate_ValidatesFailure() { var hostBuilder = CreateHostBuilder(services => @@ -71,7 +69,6 @@ public async Task ValidateOnStart_CallFirstThenConfigureAndValidate_ValidatesFai } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public async Task ValidateOnStart_ErrorMessageSpecified_FailsWithCustomError() { var hostBuilder = CreateHostBuilder(services => @@ -101,7 +98,6 @@ internal class FakeSettings } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public async Task ValidateOnStart_NamedOptions_ValidatesFailureOnStart() { var hostBuilder = CreateHostBuilder(services => @@ -129,7 +125,6 @@ public async Task ValidateOnStart_NamedOptions_ValidatesFailureOnStart() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] private async Task ValidateOnStart_AddNamedOptionsMultipleTimesForSameType_BothGetTriggered() { bool firstOptionsBuilderTriggered = false; @@ -175,7 +170,6 @@ private async Task ValidateOnStart_AddNamedOptionsMultipleTimesForSameType_BothG } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] private async Task ValidateOnStart_AddEagerValidation_DoesValidationWhenHostStartsWithNoFailure() { bool validateCalled = false; @@ -202,7 +196,6 @@ private async Task ValidateOnStart_AddEagerValidation_DoesValidationWhenHostStar } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] private async Task ValidateOnStart_AddLazyValidation_SkipsValidationWhenHostStarts() { bool validateCalled = false; @@ -235,7 +228,6 @@ private async Task ValidateOnStart_AddLazyValidation_SkipsValidationWhenHostStar } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public async Task ValidateOnStart_AddBothLazyAndEagerValidationOnDifferentTypes_ValidatesWhenHostStartsOnlyForEagerValidations() { bool validateCalledForNested = false; @@ -278,7 +270,6 @@ public async Task ValidateOnStart_AddBothLazyAndEagerValidationOnDifferentTypes_ } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public async Task ValidateOnStart_MultipleErrorsInOneValidationCall_ValidatesFailureWithMultipleErrors() { var hostBuilder = CreateHostBuilder(services => @@ -306,7 +297,6 @@ public async Task ValidateOnStart_MultipleErrorsInOneValidationCall_ValidatesFai } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public async Task ValidateOnStart_MultipleErrorsInOneValidationCallUsingCustomErrors_FailuresContainCustomErrors() { var hostBuilder = CreateHostBuilder(services => diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs index 8ebb2ab8c0aa21..3b7079e9b83986 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs @@ -11,7 +11,6 @@ namespace Microsoft.Extensions.Logging.Generators.Tests { [ActiveIssue("https://github.com/dotnet/runtime/issues/52062", TestPlatforms.Browser)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", typeof(PlatformDetection), nameof(PlatformDetection.IsWindows), nameof(PlatformDetection.IsMonoRuntime))] public class LoggerMessageGeneratorEmitterTests { [Fact] diff --git a/src/libraries/Microsoft.Extensions.Options/tests/Microsoft.Extensions.Options.Tests/OptionsBuilderTest.cs b/src/libraries/Microsoft.Extensions.Options/tests/Microsoft.Extensions.Options.Tests/OptionsBuilderTest.cs index 7e00f530ed10a9..9050bcc2831c84 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/Microsoft.Extensions.Options.Tests/OptionsBuilderTest.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/Microsoft.Extensions.Options.Tests/OptionsBuilderTest.cs @@ -667,7 +667,6 @@ public void CanValidateMixDataAnnotations() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void ValidateOnStart_CallValidateDataAnnotations_ValidationSuccessful() { var services = new ServiceCollection(); @@ -694,7 +693,6 @@ public void ValidateOnStart_CallValidateDataAnnotations_ValidationSuccessful() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void ValidateOnStart_CallValidateAndValidateDataAnnotations_FailuresCaughtFromBothValidateAndValidateDataAnnotations() { var services = new ServiceCollection(); @@ -724,7 +722,6 @@ public void ValidateOnStart_CallValidateAndValidateDataAnnotations_FailuresCaugh } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void ValidateOnStart_CallValidateOnStartFirst_ValidatesFailuresCorrectly() { var services = new ServiceCollection(); @@ -754,7 +751,6 @@ public void ValidateOnStart_CallValidateOnStartFirst_ValidatesFailuresCorrectly( } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public void ValidateOnStart_ConfigureBasedOnDataAnnotationRestrictions_ValidationSuccessful() { var services = new ServiceCollection(); diff --git a/src/libraries/System.IO.FileSystem.AccessControl/tests/FileSystemAclExtensionsTests.cs b/src/libraries/System.IO.FileSystem.AccessControl/tests/FileSystemAclExtensionsTests.cs index 5a525cf5f60e38..ddfbd73f800e39 100644 --- a/src/libraries/System.IO.FileSystem.AccessControl/tests/FileSystemAclExtensionsTests.cs +++ b/src/libraries/System.IO.FileSystem.AccessControl/tests/FileSystemAclExtensionsTests.cs @@ -410,7 +410,7 @@ from options in Enum.GetValues() (mode == FileMode.Append || mode == FileMode.Create || mode == FileMode.CreateNew)) && !(mode == FileMode.Truncate && rights != FileSystemRights.Write) && (options != FileOptions.Encrypted && // Using FileOptions.Encrypted throws UnauthorizedAccessException when attempting to read the created file - !(options == FileOptions.Asynchronous && !PlatformDetection.IsAsyncFileIOSupported))// Async IO not supported on Windows using Mono runtime https://github.com/dotnet/runtime/issues/34582 + !(options == FileOptions.Asynchronous && !PlatformDetection.IsAsyncFileIOSupported)) select new object[] { mode, rights, share, options }; [Theory] @@ -434,7 +434,7 @@ from rights in s_readableRights from share in Enum.GetValues() from options in Enum.GetValues() where options != FileOptions.Encrypted && // Using FileOptions.Encrypted throws UnauthorizedAccessException when attempting to read the created file - !(options == FileOptions.Asynchronous && !PlatformDetection.IsAsyncFileIOSupported) // Async IO not supported on Windows using Mono runtime https://github.com/dotnet/runtime/issues/34582 + !(options == FileOptions.Asynchronous && !PlatformDetection.IsAsyncFileIOSupported) select new object[] { mode, rights, share, options }; [Theory] diff --git a/src/libraries/System.IO.FileSystem/tests/File/AppendAsync.cs b/src/libraries/System.IO.FileSystem/tests/File/AppendAsync.cs index dbe1d5197fcdfb..a4be5ccdd4e950 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/AppendAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/AppendAsync.cs @@ -70,7 +70,6 @@ public override Task TaskAlreadyCanceledAsync() } } - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class File_AppendAllLinesAsync_Encoded : File_AppendAllLinesAsync { protected override Task WriteAsync(string path, string[] content) => diff --git a/src/libraries/System.IO.FileSystem/tests/File/Create.cs b/src/libraries/System.IO.FileSystem/tests/File/Create.cs index 3b1b662a9873b8..4a01cd24e54302 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/Create.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/Create.cs @@ -363,7 +363,7 @@ public void NegativeBuffer() Assert.Throws(() => Create(GetTestFilePath(), -100)); } } - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] + public class File_Create_str_i_fo : File_Create_str_i { public override FileStream Create(string path) diff --git a/src/libraries/System.IO.FileSystem/tests/File/EncryptDecrypt.cs b/src/libraries/System.IO.FileSystem/tests/File/EncryptDecrypt.cs index d4965d1dbedb60..0c70a0406678cb 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/EncryptDecrypt.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/EncryptDecrypt.cs @@ -9,7 +9,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public partial class EncryptDecrypt : FileSystemTest { private readonly ITestOutputHelper _output; diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs index c6fbb37880b956..c7c4a4a13808bd 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs @@ -9,7 +9,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class File_ReadWriteAllBytesAsync : FileSystemTest { [Fact] diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllLinesAsync.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllLinesAsync.cs index 3114e649e6635c..50b06ead88d390 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllLinesAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllLinesAsync.cs @@ -10,7 +10,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class File_ReadWriteAllLines_EnumerableAsync : FileSystemTest { #region Utilities diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllTextAsync.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllTextAsync.cs index 4a33f94f7b8f81..660daf3437e0af 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllTextAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllTextAsync.cs @@ -9,7 +9,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class File_ReadWriteAllTextAsync : FileSystemTest { protected virtual bool IsAppend { get; } @@ -199,7 +198,6 @@ public async Task OutputIsTheSameAsForStreamWriter_OverwriteAsync(string content #endregion } - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class File_ReadWriteAllText_EncodedAsync : File_ReadWriteAllTextAsync { protected override Task WriteAsync(string path, string content) => diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/CopyToAsync.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/CopyToAsync.cs index 3a48354964b8a5..5a8a84679c5520 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/CopyToAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/CopyToAsync.cs @@ -8,7 +8,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class FileStream_CopyToAsync : FileSystemTest { [Theory] diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs index 044343f91a9f6a..9bdf5d582e2c7b 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs @@ -42,7 +42,6 @@ protected override async Task CreateConnectedStreamsAsync() } [PlatformSpecific(TestPlatforms.Windows)] // DOS device paths (\\.\ and \\?\) are a Windows concept - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class SeekableDeviceFileStreamStandaloneConformanceTests : UnbufferedAsyncFileStreamStandaloneConformanceTests { protected override string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) @@ -181,7 +180,6 @@ public struct SHARE_INFO_502 [PlatformSpecific(TestPlatforms.Windows)] // the test setup is Windows-specifc [OuterLoop("Has a very complex setup logic that in theory might have some side-effects")] [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class DeviceInterfaceTests { [Fact] diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs index d9d36053a8e112..c93d520b55b07d 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs @@ -205,7 +205,6 @@ public class BufferedSyncFileStreamStandaloneConformanceTests : FileStreamStanda protected override int BufferSize => 10; } - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser, "lots of operations aren't supported on browser")] // copied from StreamConformanceTests base class due to https://github.com/xunit/xunit/issues/2186 public class UnbufferedAsyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests { @@ -218,7 +217,6 @@ public class UnbufferedAsyncFileStreamStandaloneConformanceTests : FileStreamSta #endif } - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser, "lots of operations aren't supported on browser")] // copied from StreamConformanceTests base class due to https://github.com/xunit/xunit/issues/2186 public class BufferedAsyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests { diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/IsAsync.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/IsAsync.cs index e187468035fe60..e22428c4b39780 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/IsAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/IsAsync.cs @@ -8,7 +8,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class FileStream_IsAsync : FileSystemTest { [Fact] diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ReadAsync.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ReadAsync.cs index af30be04c8673c..8d676da9b542e2 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ReadAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ReadAsync.cs @@ -134,14 +134,12 @@ public async Task IncompleteReadCantSetPositionBeyondEndOfFile(FileShare fileSha } } - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class FileStream_ReadAsync_AsyncReads : FileStream_AsyncReads { protected override Task ReadAsync(FileStream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) => stream.ReadAsync(buffer, offset, count, cancellationToken); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class FileStream_BeginEndRead_AsyncReads : FileStream_AsyncReads { protected override Task ReadAsync(FileStream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) => diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/SafeFileHandle.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/SafeFileHandle.cs index 49b31884761ab7..8bc995a3cc2fb7 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/SafeFileHandle.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/SafeFileHandle.cs @@ -9,7 +9,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class FileStream_SafeFileHandle : FileSystemTest { [Fact] diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/WriteAsync.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/WriteAsync.cs index 86eef052df1b40..c654ed010b6f38 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/WriteAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/WriteAsync.cs @@ -321,7 +321,6 @@ public async Task WriteAsyncMiniStress() } } - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class FileStream_WriteAsync_AsyncWrites : FileStream_AsyncWrites { protected override Task WriteAsync(FileStream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) => @@ -368,7 +367,6 @@ public void CancelledTokenFastPath() } } - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class FileStream_BeginEndWrite_AsyncWrites : FileStream_AsyncWrites { protected override Task WriteAsync(FileStream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) => diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_sfh_fa_buffer_async.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_sfh_fa_buffer_async.cs index b2a8ba19a1d1f7..810287764e3293 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_sfh_fa_buffer_async.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_sfh_fa_buffer_async.cs @@ -6,7 +6,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class FileStream_ctor_sfh_fa_buffer_async : FileStream_ctor_sfh_fa_buffer { protected sealed override FileStream CreateFileStream(SafeFileHandle handle, FileAccess access, int bufferSize) diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_async.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_async.cs index a93eb4acb08159..ee4036a312fc5a 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_async.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_async.cs @@ -5,7 +5,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class FileStream_ctor_str_fm_fa_fs_buffer_async : FileStream_ctor_str_fm_fa_fs_buffer { protected sealed override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs index c35e5f37874038..f94a619bd58ec6 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs @@ -6,7 +6,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] public class FileStream_ctor_str_fm_fa_fs_buffer_fo : FileStream_ctor_str_fm_fa_fs_buffer { protected sealed override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs index cee09fe2d62b33..4c6a8ddf4b22d4 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs @@ -9,7 +9,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [PlatformSpecific(TestPlatforms.Windows)] public class RandomAccess_Mixed : FileSystemTest { diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/NoBuffering.Windows.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/NoBuffering.Windows.cs index 83b4c87bb64f52..61fcba7b1ad587 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/NoBuffering.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/NoBuffering.Windows.cs @@ -9,7 +9,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")] public class RandomAccess_NoBuffering : FileSystemTest { diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/ReadAsync.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/ReadAsync.cs index fe8f8b1111f2db..dac4ea781f0c39 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/ReadAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/ReadAsync.cs @@ -10,7 +10,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")] public class RandomAccess_ReadAsync : RandomAccess_Base> { diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/ReadScatterAsync.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/ReadScatterAsync.cs index 0a9be6c433678e..1b07b86f08ca24 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/ReadScatterAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/ReadScatterAsync.cs @@ -10,7 +10,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")] public class RandomAccess_ReadScatterAsync : RandomAccess_Base> { diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteAsync.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteAsync.cs index b816bc8541592e..ccd1f2f988893e 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteAsync.cs @@ -9,7 +9,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")] public class RandomAccess_WriteAsync : RandomAccess_Base { diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGatherAsync.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGatherAsync.cs index bf995a318fd9a4..abf945b2d46bcc 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGatherAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGatherAsync.cs @@ -11,7 +11,6 @@ namespace System.IO.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")] public class RandomAccess_WriteGatherAsync : RandomAccess_Base { diff --git a/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs b/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs index 34088f43c238ec..44906aecc0a0ee 100644 --- a/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs +++ b/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs @@ -580,7 +580,6 @@ public async Task ReadBlockAsync_RepeatsReadsUntilReadDesiredAmount() } [Theory] - [ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [InlineData(0, false)] [InlineData(0, true)] [InlineData(1, false)] From 12f40a243cdb728bf4b07b995459fb0fb120addb Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 8 Feb 2022 07:26:11 -0800 Subject: [PATCH 06/13] Miscellaneous fixes - Renamed config var for number of poller threads to indicate that they are IO pollers - Removed an unused property - Reordered IO completion callback arguments for consistency with before and other code paths - A `FileSystemWatcher` test failed twice, unable to repro locally, tried offloading background work to a separate thread --- .../src/System/Threading/ThreadPool.Windows.cs | 2 +- .../tests/FileSystemWatcher.unit.cs | 7 +++++-- .../tests/System.IO.FileSystem.Watcher.Tests.csproj | 2 ++ .../System/Threading/Overlapped._IOCompletionCallback.cs | 2 +- .../src/System/Threading/PortableThreadPool.IO.Windows.cs | 6 ++---- .../src/System/Threading/ThreadPool.Browser.Mono.cs | 2 +- 6 files changed, 12 insertions(+), 9 deletions(-) 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 35a1726da2866c..6186b7bf8bfc7a 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 @@ -413,7 +413,7 @@ private static RegisteredWaitHandle RegisterWaitForSingleObject( } private static unsafe void NativeOverlappedCallback(nint overlappedPtr) => - _IOCompletionCallback.PerformSingleIOCompletionCallback((NativeOverlapped*)overlappedPtr, 0, 0); + _IOCompletionCallback.PerformSingleIOCompletionCallback(0, 0, (NativeOverlapped*)overlappedPtr); [CLSCompliant(false)] [SupportedOSPlatform("windows")] diff --git a/src/libraries/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.unit.cs b/src/libraries/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.unit.cs index 1b343e595cc0bd..f8f308b237b511 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.unit.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.unit.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Threading.Tests; using Microsoft.DotNet.XUnitExtensions; @@ -1066,7 +1067,7 @@ public void FileSystemWatcher_ModifyFiltersConcurrentWithEvents() watcher.Filters.Add(fileTwo.Name); var cts = new CancellationTokenSource(); - Task modifier = Task.Run(() => + Thread thread = ThreadTestHelpers.CreateGuardedThread(out Action waitForThread, () => { string otherFilter = Guid.NewGuid().ToString("N"); while (!cts.IsCancellationRequested) @@ -1075,13 +1076,15 @@ public void FileSystemWatcher_ModifyFiltersConcurrentWithEvents() watcher.Filters.RemoveAt(2); } }); + thread.IsBackground = true; + thread.Start(); ExpectEvent(watcher, WatcherChangeTypes.Created, () => fileOne.Create().Dispose(), cleanup: null, expectedPath: fileOne.FullName); ExpectEvent(watcher, WatcherChangeTypes.Created, () => fileTwo.Create().Dispose(), cleanup: null, expectedPath: fileTwo.FullName); ExpectNoEvent(watcher, WatcherChangeTypes.Created, () => fileThree.Create().Dispose(), cleanup: null, expectedPath: fileThree.FullName); cts.Cancel(); - modifier.Wait(); + waitForThread(); } } } diff --git a/src/libraries/System.IO.FileSystem.Watcher/tests/System.IO.FileSystem.Watcher.Tests.csproj b/src/libraries/System.IO.FileSystem.Watcher/tests/System.IO.FileSystem.Watcher.Tests.csproj index 381f9dfc4e4c92..825f8444b9e1f4 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/tests/System.IO.FileSystem.Watcher.Tests.csproj +++ b/src/libraries/System.IO.FileSystem.Watcher/tests/System.IO.FileSystem.Watcher.Tests.csproj @@ -39,6 +39,8 @@ Link="Common\System\IO\TempDirectory.cs" /> + _events.Count; - private void Poll() { while ( @@ -176,7 +174,7 @@ private static void ProcessEvent(Event e) errorCode = Interop.NtDll.RtlNtStatusToDosError((int)ntStatus); } - _IOCompletionCallback.PerformSingleIOCompletionCallback(e.nativeOverlapped, errorCode, e.bytesTransferred); + _IOCompletionCallback.PerformSingleIOCompletionCallback(errorCode, e.bytesTransferred, e.nativeOverlapped); } private readonly struct Event 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 ec010ba405f790..4887cf220df47a 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 @@ -118,7 +118,7 @@ private static void Callback() } private static unsafe void NativeOverlappedCallback(nint overlappedPtr) => - _IOCompletionCallback.PerformSingleIOCompletionCallback((NativeOverlapped*)overlappedPtr, 0, 0); + _IOCompletionCallback.PerformSingleIOCompletionCallback(0, 0, (NativeOverlapped*)overlappedPtr); [CLSCompliant(false)] [SupportedOSPlatform("windows")] From 0a2dc4269c28b310c52a313e159a225c397db195 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Tue, 8 Feb 2022 07:35:31 -0800 Subject: [PATCH 07/13] Temporarily enable libraries tests against Mono for this PR --- eng/pipelines/runtime.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index d69b0df7de68e7..45fd1e9d45bc8f 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -1036,7 +1036,7 @@ jobs: runtimeFlavor: mono buildConfig: ${{ variables.debugOnPrReleaseOnRolling }} platforms: - # - windows_x64 + - windows_x64 - OSX_x64 - Linux_arm64 - Linux_x64 From 64cca547774729897f9568796771e874e075055c Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 11 Feb 2022 13:46:52 -0800 Subject: [PATCH 08/13] Try to fix a time-sensitive test that is occasionally failing --- .../System.IO/tests/StreamReader/StreamReaderTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs b/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs index 44906aecc0a0ee..76e9ed2d0ec137 100644 --- a/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs +++ b/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs @@ -137,8 +137,8 @@ public async Task ReadToEndAsync_WithCancellation() { string path = GetTestFilePath(); - // create large (~100MB) file - File.WriteAllLines(path, Enumerable.Repeat("A very large file used for testing StreamReader cancellation. 0123456789012345678901234567890123456789.", 1_000_000)); + // create large (~1GB) file + File.WriteAllLines(path, Enumerable.Repeat("A very large file used for testing StreamReader cancellation. 0123456789012345678901234567890123456789.", 10_000_000)); using StreamReader reader = File.OpenText(path); using CancellationTokenSource cts = new (); From 251de073ccef5853e9c560cce6ad3cb6a5f53d07 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 11 Feb 2022 17:56:31 -0800 Subject: [PATCH 09/13] Note SOS usage of a variable --- .../src/System/Threading/ThreadPool.CoreCLR.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 b5a76017f3640d..86e37a4ab8f04a 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 @@ -164,9 +164,8 @@ public static partial class ThreadPool { private static readonly byte UsePortableThreadPoolConfigValues = InitializeConfigAndDetermineUsePortableThreadPool(); - // SOS's ThreadPool command depends on this name + // SOS's ThreadPool command depends on the following names internal static readonly bool UsePortableThreadPool = UsePortableThreadPoolConfigValues != 0; - internal static readonly bool UsePortableThreadPoolForIO = UsePortableThreadPoolConfigValues > 1; // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that From 165842adbcd9128f8054e499a602fa122ec363a1 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Wed, 16 Feb 2022 22:21:09 -0800 Subject: [PATCH 10/13] Address feedback --- .../PortableThreadPool.IO.Windows.cs | 49 ++++++++++++------- .../System/Threading/ThreadPoolWorkQueue.cs | 31 ++++++++++-- 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs index 3bf57db2ddc8be..5814e0a6580abc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs @@ -104,11 +104,9 @@ private sealed unsafe class IOCompletionPoller 1024; #endif - private static readonly Action ProcessEventDelegate = ProcessEvent; - private readonly nint _port; private readonly Interop.Kernel32.OVERLAPPED_ENTRY* _nativeEvents; - private readonly ThreadPoolTypedWorkItemQueue _events; + private readonly ThreadPoolTypedWorkItemQueue _events; private readonly Thread _thread; public IOCompletionPoller(nint port) @@ -118,8 +116,8 @@ public IOCompletionPoller(nint port) _nativeEvents = (Interop.Kernel32.OVERLAPPED_ENTRY*) - NativeMemory.Alloc((nuint)NativeEventCapacity * (nuint)Unsafe.SizeOf()); - _events = new ThreadPoolTypedWorkItemQueue(ProcessEventDelegate); + NativeMemory.Alloc(NativeEventCapacity, (nuint)sizeof(Interop.Kernel32.OVERLAPPED_ENTRY)); + _events = new(default); // Thread pool threads must start in the default execution context without transferring the context, so // using UnsafeStart() instead of Start() @@ -127,9 +125,21 @@ public IOCompletionPoller(nint port) { IsThreadPoolThread = true, IsBackground = true, - Priority = ThreadPriority.Highest, Name = ".NET ThreadPool IO" }; + + // Poller threads are typically expected to be few in number and have to compete for time slices with all other + // threads that are scheduled to run. They do only a small amount of work and don't run any user code. In + // situations where frequently, a large number of threads are scheduled to run, a scheduled poller thread may be + // delayed artificially quite a bit. The poller threads are given higher priority than normal to mitigate that + // issue. It's unlikely that these threads would starve a system because in such a situation IO completions + // would stop occurring. Since the number of IO pollers is configurable, avoid having too many poller threads at + // higher priority. + if (ProcessorsPerPoller >= 4) + { + _thread.Priority = ThreadPriority.AboveNormal; + } + _thread.UnsafeStart(); } @@ -159,22 +169,25 @@ private void Poll() ThrowHelper.ThrowApplicationException(Marshal.GetHRForLastWin32Error()); } - private static void ProcessEvent(Event e) + private struct Callback : IThreadPoolTypedWorkItemQueueCallback { - if (NativeRuntimeEventSource.Log.IsEnabled()) + public void Invoke(Event e) { - NativeRuntimeEventSource.Log.ThreadPoolIODequeue(e.nativeOverlapped); - } + if (NativeRuntimeEventSource.Log.IsEnabled()) + { + NativeRuntimeEventSource.Log.ThreadPoolIODequeue(e.nativeOverlapped); + } - // The NtStatus code for the operation is in the InternalLow field - uint ntStatus = (uint)(nint)e.nativeOverlapped->InternalLow; - uint errorCode = Interop.Errors.ERROR_SUCCESS; - if (ntStatus != Interop.StatusOptions.STATUS_SUCCESS) - { - errorCode = Interop.NtDll.RtlNtStatusToDosError((int)ntStatus); - } + // The NtStatus code for the operation is in the InternalLow field + uint ntStatus = (uint)(nint)e.nativeOverlapped->InternalLow; + uint errorCode = Interop.Errors.ERROR_SUCCESS; + if (ntStatus != Interop.StatusOptions.STATUS_SUCCESS) + { + errorCode = Interop.NtDll.RtlNtStatusToDosError((int)ntStatus); + } - _IOCompletionCallback.PerformSingleIOCompletionCallback(errorCode, e.bytesTransferred, e.nativeOverlapped); + _IOCompletionCallback.PerformSingleIOCompletionCallback(errorCode, e.bytesTransferred, e.nativeOverlapped); + } } private readonly struct Event 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 f3defabf81f6db..5dbe2433ec45fc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs @@ -869,13 +869,22 @@ public enum WorkState } } - internal sealed class ThreadPoolTypedWorkItemQueue : IThreadPoolWorkItem + // A strongly typed callback for ThreadPoolTypedWorkItemQueue. + // This way we avoid the indirection of a delegate call. + internal interface IThreadPoolTypedWorkItemQueueCallback + { + // TODO: Make it static abstract when we can. + void Invoke(T item); + } + + internal sealed class ThreadPoolTypedWorkItemQueue + : IThreadPoolWorkItem where TCallback : struct, IThreadPoolTypedWorkItemQueueCallback { private int _isScheduledForProcessing; private readonly ConcurrentQueue _workItems = new ConcurrentQueue(); - private readonly Action _callback; + private readonly TCallback _callback; - public ThreadPoolTypedWorkItemQueue(Action callback) => _callback = callback; + public ThreadPoolTypedWorkItemQueue(TCallback callback) => _callback = callback; public int Count => _workItems.Count; @@ -914,6 +923,11 @@ void IThreadPoolWorkItem.Execute() return; } + // An work item was successfully dequeued, and there may be more work items to process. Schedule a work item to + // parallelize processing of work items, before processing more work items. Following this, it is the responsibility + // of the new work item and the poller thread to schedule more work items as necessary. The parallelization may be + // necessary here if the user callback as part of handling the work item blocks for some reason that may have a + // dependency on other queued work items. ScheduleForProcessing(); ThreadPoolWorkQueueThreadLocals tl = ThreadPoolWorkQueueThreadLocals.threadLocals!; @@ -924,8 +938,15 @@ void IThreadPoolWorkItem.Execute() int startTimeMs = Environment.TickCount; while (true) { - _callback(workItem); - + _callback.Invoke(workItem); + + // This work item processes queued work items until certain conditions are met, and tracks some things: + // - Keep track of the number of work items processed, it will be added to the counter later + // - Local work items take precedence over all other types of work items, process them first + // - This work item should not run for too long. It is processing a specific type of work in batch, but should + // not starve other thread pool work items. Check how long it has been since this work item has started, and + // yield to the thread pool after some time. The threshold used is half of the thread pool's dispatch quantum, + // which the thread pool uses for doing periodic work. if (++completedCount == uint.MaxValue || (tl.workState & ThreadPoolWorkQueueThreadLocals.WorkState.MayHaveLocalWorkItems) != 0 || (uint)(Environment.TickCount - startTimeMs) >= ThreadPoolWorkQueue.DispatchQuantumMs / 2 || From 81f18f002cb5c0b7b2bf119f6af858ac9776c94a Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sat, 26 Feb 2022 21:38:16 -0800 Subject: [PATCH 11/13] Revert "Temporarily enable libraries tests against Mono for this PR" This reverts commit 9a73d8e6fbac6d3bb9d70d6aab8b8b0de5b3b6c3. --- eng/pipelines/runtime.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index 45fd1e9d45bc8f..d69b0df7de68e7 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -1036,7 +1036,7 @@ jobs: runtimeFlavor: mono buildConfig: ${{ variables.debugOnPrReleaseOnRolling }} platforms: - - windows_x64 + # - windows_x64 - OSX_x64 - Linux_arm64 - Linux_x64 From 14893dea3ac70bd73c36574de41a10a38eb99022 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sat, 26 Feb 2022 21:43:13 -0800 Subject: [PATCH 12/13] Fix build after rebase --- .../src/System/Threading/PortableThreadPool.IO.Windows.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs index 5814e0a6580abc..22479abf99f7c4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs @@ -5,7 +5,6 @@ using System.Diagnostics.Tracing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using Internal.Runtime.CompilerServices; namespace System.Threading { From a310f12afc2f4c6e169bc4579abfff3976c6b07f Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Sat, 26 Feb 2022 23:30:26 -0800 Subject: [PATCH 13/13] Add checks for some DOTNET vars from Net.Sockets and implement inlining IO completion callbacks --- .../Kernel32/Interop.CompletionPort.cs | 2 +- .../PortableThreadPool.IO.Windows.cs | 137 ++++++++++++++---- 2 files changed, 108 insertions(+), 31 deletions(-) diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CompletionPort.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CompletionPort.cs index 58e4fd72e2cabd..d187b9ef51cd4a 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CompletionPort.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CompletionPort.cs @@ -18,7 +18,7 @@ internal static partial class Kernel32 [GeneratedDllImport(Libraries.Kernel32, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool GetQueuedCompletionStatus(IntPtr CompletionPort, out int lpNumberOfBytes, out UIntPtr CompletionKey, out IntPtr lpOverlapped, int dwMilliseconds); + internal static partial bool GetQueuedCompletionStatus(IntPtr CompletionPort, out uint lpNumberOfBytesTransferred, out UIntPtr CompletionKey, out IntPtr lpOverlapped, int dwMilliseconds); [GeneratedDllImport(Libraries.Kernel32, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs index 22479abf99f7c4..94e065a7b94b7e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.IO.Windows.cs @@ -10,12 +10,43 @@ namespace System.Threading { internal sealed partial class PortableThreadPool { - private static readonly int ProcessorsPerPoller = - AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.ProcessorsPerIOPollerThread", 12, false); + // Continuations of IO completions are dispatched to the ThreadPool from IO completion poller threads. This avoids + // continuations blocking/stalling the IO completion poller threads. Setting UnsafeInlineIOCompletionCallbacks allows + // continuations to run directly on the IO completion poller thread, but is inherently unsafe due to the potential for + // those threads to become stalled due to blocking. Sometimes, setting this config value may yield better latency. The + // config value is named for consistency with SocketAsyncEngine.Unix.cs. + private static readonly bool UnsafeInlineIOCompletionCallbacks = + Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS") == "1"; + + private static readonly int IOCompletionPollerCount = GetIOCompletionPollerCount(); + + private static int GetIOCompletionPollerCount() + { + // Named for consistency with SocketAsyncEngine.Unix.cs, this environment variable is checked to override the exact + // number of IO completion poller threads to use. See the comment in SocketAsyncEngine.Unix.cs about its potential + // uses. For this implementation, the ProcessorsPerIOPollerThread config option below may be preferable as it may be + // less machine-specific. + if (uint.TryParse(Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_THREAD_COUNT"), out uint count)) + { + return Math.Min((int)count, MaxPossibleThreadCount); + } + + if (UnsafeInlineIOCompletionCallbacks) + { + // In this mode, default to ProcessorCount pollers to ensure that all processors can be utilized if more work + // happens on the poller threads + return Environment.ProcessorCount; + } + + int processorsPerPoller = + AppContextConfigHelper.GetInt32Config("System.Threading.ThreadPool.ProcessorsPerIOPollerThread", 12, false); + return (Environment.ProcessorCount - 1) / processorsPerPoller + 1; + } private static nint CreateIOCompletionPort() { - nint port = Interop.Kernel32.CreateIoCompletionPort(new IntPtr(-1), IntPtr.Zero, UIntPtr.Zero, 0); + nint port = + Interop.Kernel32.CreateIoCompletionPort(new IntPtr(-1), IntPtr.Zero, UIntPtr.Zero, IOCompletionPollerCount); if (port == 0) { int hr = Marshal.GetHRForLastWin32Error(); @@ -75,9 +106,8 @@ private void EnsureIOCompletionPollers() return; } - int pollerCount = (Environment.ProcessorCount - 1) / ProcessorsPerPoller + 1; - IOCompletionPoller[] pollers = new IOCompletionPoller[pollerCount]; - for (int i = 0; i < pollerCount; ++i) + IOCompletionPoller[] pollers = new IOCompletionPoller[IOCompletionPollerCount]; + for (int i = 0; i < IOCompletionPollerCount; ++i) { pollers[i] = new IOCompletionPoller(_ioPort); } @@ -105,7 +135,7 @@ private sealed unsafe class IOCompletionPoller private readonly nint _port; private readonly Interop.Kernel32.OVERLAPPED_ENTRY* _nativeEvents; - private readonly ThreadPoolTypedWorkItemQueue _events; + private readonly ThreadPoolTypedWorkItemQueue? _events; private readonly Thread _thread; public IOCompletionPoller(nint port) @@ -113,37 +143,48 @@ public IOCompletionPoller(nint port) Debug.Assert(port != 0); _port = port; - _nativeEvents = - (Interop.Kernel32.OVERLAPPED_ENTRY*) - NativeMemory.Alloc(NativeEventCapacity, (nuint)sizeof(Interop.Kernel32.OVERLAPPED_ENTRY)); - _events = new(default); - - // Thread pool threads must start in the default execution context without transferring the context, so - // using UnsafeStart() instead of Start() - _thread = new Thread(Poll, SmallStackSizeBytes) + if (!UnsafeInlineIOCompletionCallbacks) { - IsThreadPoolThread = true, - IsBackground = true, - Name = ".NET ThreadPool IO" - }; - - // Poller threads are typically expected to be few in number and have to compete for time slices with all other - // threads that are scheduled to run. They do only a small amount of work and don't run any user code. In - // situations where frequently, a large number of threads are scheduled to run, a scheduled poller thread may be - // delayed artificially quite a bit. The poller threads are given higher priority than normal to mitigate that - // issue. It's unlikely that these threads would starve a system because in such a situation IO completions - // would stop occurring. Since the number of IO pollers is configurable, avoid having too many poller threads at - // higher priority. - if (ProcessorsPerPoller >= 4) + _nativeEvents = + (Interop.Kernel32.OVERLAPPED_ENTRY*) + NativeMemory.Alloc(NativeEventCapacity, (nuint)sizeof(Interop.Kernel32.OVERLAPPED_ENTRY)); + _events = new(default); + + // These threads don't run user code, use a smaller stack size + _thread = new Thread(Poll, SmallStackSizeBytes); + + // Poller threads are typically expected to be few in number and have to compete for time slices with all + // other threads that are scheduled to run. They do only a small amount of work and don't run any user code. + // In situations where frequently, a large number of threads are scheduled to run, a scheduled poller thread + // may be delayed artificially quite a bit. The poller threads are given higher priority than normal to + // mitigate that issue. It's unlikely that these threads would starve a system because in such a situation + // IO completions would stop occurring. Since the number of IO pollers is configurable, avoid having too + // many poller threads at higher priority. + if (IOCompletionPollerCount * 4 < Environment.ProcessorCount) + { + _thread.Priority = ThreadPriority.AboveNormal; + } + } + else { - _thread.Priority = ThreadPriority.AboveNormal; + // These threads may run user code, use the default stack size + _thread = new Thread(PollAndInlineCallbacks); } + _thread.IsThreadPoolThread = true; + _thread.IsBackground = true; + _thread.Name = ".NET ThreadPool IO"; + + // Thread pool threads must start in the default execution context without transferring the context, so + // using UnsafeStart() instead of Start() _thread.UnsafeStart(); } private void Poll() { + Debug.Assert(_nativeEvents != null); + Debug.Assert(_events != null); + while ( Interop.Kernel32.GetQueuedCompletionStatusEx( _port, @@ -159,7 +200,10 @@ private void Poll() for (int i = 0; i < nativeEventCount; ++i) { Interop.Kernel32.OVERLAPPED_ENTRY* nativeEvent = &_nativeEvents[i]; - _events.BatchEnqueue(new Event(nativeEvent->lpOverlapped, nativeEvent->dwNumberOfBytesTransferred)); + if (nativeEvent->lpOverlapped != null) // shouldn't be null since null is not posted + { + _events.BatchEnqueue(new Event(nativeEvent->lpOverlapped, nativeEvent->dwNumberOfBytesTransferred)); + } } _events.CompleteBatchEnqueue(); @@ -168,6 +212,39 @@ private void Poll() ThrowHelper.ThrowApplicationException(Marshal.GetHRForLastWin32Error()); } + private void PollAndInlineCallbacks() + { + Debug.Assert(_nativeEvents == null); + Debug.Assert(_events == null); + + while (true) + { + uint errorCode = Interop.Errors.ERROR_SUCCESS; + if (!Interop.Kernel32.GetQueuedCompletionStatus( + _port, + out uint bytesTransferred, + out _, + out nint nativeOverlappedPtr, + Timeout.Infinite)) + { + errorCode = (uint)Marshal.GetLastPInvokeError(); + } + + var nativeOverlapped = (NativeOverlapped*)nativeOverlappedPtr; + if (nativeOverlapped == null) // shouldn't be null since null is not posted + { + continue; + } + + if (NativeRuntimeEventSource.Log.IsEnabled()) + { + NativeRuntimeEventSource.Log.ThreadPoolIODequeue(nativeOverlapped); + } + + _IOCompletionCallback.PerformSingleIOCompletionCallback(errorCode, bytesTransferred, nativeOverlapped); + } + } + private struct Callback : IThreadPoolTypedWorkItemQueueCallback { public void Invoke(Event e)