diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 2632a686aff7..c9c6140ea56d 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -288,7 +288,6 @@ Common\Interop\Windows\OleAut32\Interop.VariantClear.cs - diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index 64ba59f672ec..c643a566d10b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -275,17 +275,17 @@ private bool SetApartmentStateUnchecked(ApartmentState state, bool throwOnError) #else // FEATURE_COMINTEROP_APARTMENT_SUPPORT private static bool SetApartmentStateUnchecked(ApartmentState state, bool throwOnError) { - if (state != ApartmentState.Unknown) - { + if (state != ApartmentState.Unknown) + { if (throwOnError) { throw new PlatformNotSupportedException(SR.PlatformNotSupported_ComInterop); } return false; - } + } - return true; + return true; } #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT 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 deleted file mode 100644 index 279abdf1baa2..000000000000 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.Windows.cs +++ /dev/null @@ -1,56 +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.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); - } - - // 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) - { - ArgumentNullException.ThrowIfNull(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/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index 031ff0cda2c8..e13fdeef3111 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 @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. /*============================================================================= @@ -18,30 +18,6 @@ namespace System.Threading { - internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem - { - void IThreadPoolWorkItem.Execute() => CompleteWait(); - - // Entry point from unmanaged code - private void CompleteWait() - { - PortableThreadPool.CompleteWait(_registeredWaitHandle, _timedOut); - } - } - - internal sealed class UnmanagedThreadPoolWorkItem : IThreadPoolWorkItem - { - private readonly IntPtr _callback; - private readonly IntPtr _state; - - public UnmanagedThreadPoolWorkItem(IntPtr callback, IntPtr state) - { - _callback = callback; - _state = state; - } - - unsafe void IThreadPoolWorkItem.Execute() => ((delegate* unmanaged)_callback)(_state); - } public static partial class ThreadPool { @@ -52,12 +28,6 @@ internal static bool EnsureConfigInitialized() private static readonly bool s_initialized = InitializeConfig(); - // Indicates whether the thread pool should yield the thread from the dispatch loop to the runtime periodically so that - // the runtime may use the thread for processing other work - internal static bool YieldFromDispatchLoop => false; - - private static readonly bool IsWorkerTrackingEnabledInConfig = GetEnableWorkerTracking(); - private static unsafe bool InitializeConfig() { int configVariableIndex = 1; @@ -100,116 +70,5 @@ private static unsafe bool InitializeConfig() out bool isBoolean, out char* appContextConfigName); - private static bool GetEnableWorkerTracking() => - AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false); - - public static bool SetMaxThreads(int workerThreads, int completionPortThreads) - { - return PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads); - } - - 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) - { - return PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads, completionPortThreads); - } - - 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. - /// - /// - /// For a thread pool implementation that may have different types of threads, the count includes all types. - /// - public static int ThreadCount - { - get - { - return PortableThreadPool.ThreadPoolInstance.ThreadCount; - } - } - - /// - /// Gets the number of work items that have been processed so far. - /// - /// - /// For a thread pool implementation that may have different types of work items, the count includes all types. - /// - public static long CompletedWorkItemCount - { - get - { - return PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount; - } - } - - private static RegisteredWaitHandle RegisterWaitForSingleObject( - WaitHandle waitObject, - WaitOrTimerCallback callBack, - object? state, - uint millisecondsTimeOutInterval, - bool executeOnlyOnce, - bool flowExecutionContext) - { - ArgumentNullException.ThrowIfNull(waitObject); - ArgumentNullException.ThrowIfNull(callBack); - - RegisteredWaitHandle registeredWaitHandle = new RegisteredWaitHandle( - waitObject, - new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext), - (int)millisecondsTimeOutInterval, - !executeOnlyOnce); - - PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle); - - return registeredWaitHandle; - } - - internal static void RequestWorkerThread() - { - PortableThreadPool.ThreadPoolInstance.RequestWorker(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int currentTimeMs) - { - return - PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete( - threadLocalCompletionCountObject, - currentTimeMs); - } - - internal static void ReportThreadStatus(bool isWorking) - { - PortableThreadPool.ThreadPoolInstance.ReportThreadStatus(isWorking); - } - - internal static void NotifyWorkItemProgress() - { - PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); - } - - internal static bool NotifyThreadBlocked() => PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked(); - - internal static void NotifyThreadUnblocked() - { - PortableThreadPool.ThreadPoolInstance.NotifyThreadUnblocked(); - } - - internal static object? GetOrCreateThreadLocalCompletionCountObject() => - PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject(); } } diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets index 88c0a7a3d8e9..4948020c8496 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets @@ -43,6 +43,7 @@ The .NET Foundation licenses this file to you under the MIT license. true false false + true false @@ -272,6 +273,7 @@ The .NET Foundation licenses this file to you under the MIT license. + 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 3ba50b40df75..1e3c31218839 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 @@ -26,12 +26,10 @@ - false - true + true - false - true + true true @@ -233,7 +231,6 @@ - @@ -251,7 +248,6 @@ - @@ -282,29 +278,13 @@ Interop\Windows\Kernel32\Interop.GetTickCount64.cs - - Interop\Windows\Kernel32\Interop.ThreadPool.cs - - - - Interop\Windows\Kernel32\Interop.Timer.cs - Interop\Windows\Kernel32\Interop.DynamicLoad.cs - - Interop\Windows\Kernel32\Interop.ThreadPoolIO.cs - - - - - - - - - + + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs deleted file mode 100644 index 2fe7d1dd2c5d..000000000000 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.NativeAot.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================================= -** -** -** -** Purpose: Class for creating and managing a threadpool -** -** -=============================================================================*/ - -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; - -namespace System.Threading -{ - public static partial class ThreadPool - { - [Conditional("unnecessary")] - internal static void ReportThreadStatus(bool isWorking) - { - } - } -} 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 deleted file mode 100644 index 20011637e77e..000000000000 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs +++ /dev/null @@ -1,445 +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 Microsoft.Win32.SafeHandles; -using System.Diagnostics; -using System.Runtime; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; - -namespace System.Threading -{ - // - // Windows-specific implementation of ThreadPool - // -#if !FEATURE_WASM_THREADS - [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] -#endif - public sealed class RegisteredWaitHandle : MarshalByRefObject - { - private readonly Lock _lock; - private SafeWaitHandle _waitHandle; - private readonly _ThreadPoolWaitOrTimerCallback _callbackHelper; - private readonly uint _millisecondsTimeout; - private bool _repeating; - private bool _unregistering; - - // Handle to this object to keep it alive - private GCHandle _gcHandle; - - // Pointer to the TP_WAIT structure - private IntPtr _tpWait; - - internal unsafe RegisteredWaitHandle(SafeWaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper, - uint millisecondsTimeout, bool repeating) - { - _lock = new Lock(); - - // Protect the handle from closing while we are waiting on it (VSWhidbey 285642) - waitHandle.DangerousAddRef(); - _waitHandle = waitHandle; - - _callbackHelper = callbackHelper; - _millisecondsTimeout = millisecondsTimeout; - _repeating = repeating; - - // Allocate _gcHandle and _tpWait as the last step and make sure they are never leaked - _gcHandle = GCHandle.Alloc(this); - - _tpWait = Interop.Kernel32.CreateThreadpoolWait(&RegisteredWaitCallback, (IntPtr)_gcHandle, IntPtr.Zero); - - if (_tpWait == IntPtr.Zero) - { - _gcHandle.Free(); - throw new OutOfMemoryException(); - } - } - - [UnmanagedCallersOnly] - internal static void RegisteredWaitCallback(IntPtr instance, IntPtr context, IntPtr wait, uint waitResult) - { - var wrapper = ThreadPoolCallbackWrapper.Enter(); - GCHandle handle = (GCHandle)context; - RegisteredWaitHandle registeredWaitHandle = (RegisteredWaitHandle)handle.Target!; - Debug.Assert((handle == registeredWaitHandle._gcHandle) && (wait == registeredWaitHandle._tpWait)); - - bool timedOut = (waitResult == (uint)Interop.Kernel32.WAIT_TIMEOUT); - registeredWaitHandle.PerformCallback(timedOut); - ThreadPool.IncrementCompletedWorkItemCount(); - wrapper.Exit(); - } - - private void PerformCallback(bool timedOut) - { - bool lockAcquired; - var spinner = new SpinWait(); - - // Prevent the race condition with Unregister and the previous PerformCallback call, which may still be - // holding the _lock. - while (!(lockAcquired = _lock.TryAcquire(0)) && !Volatile.Read(ref _unregistering)) - { - spinner.SpinOnce(); - } - - // If another thread is running Unregister, no need to restart the timer or clean up - if (lockAcquired) - { - try - { - if (!_unregistering) - { - if (_repeating) - { - // Allow this wait to fire again. Restart the timer before executing the callback. - RestartWait(); - } - else - { - // This wait will not be fired again. Free the GC handle to allow the GC to collect this object. - Debug.Assert(_gcHandle.IsAllocated); - _gcHandle.Free(); - } - } - } - finally - { - _lock.Release(); - } - } - - _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(_callbackHelper, timedOut); - } - - internal unsafe void RestartWait() - { - long timeout; - long* pTimeout = null; // Null indicates infinite timeout - - if (_millisecondsTimeout != Timeout.UnsignedInfinite) - { - timeout = -10000L * _millisecondsTimeout; - pTimeout = &timeout; - } - - // We can use DangerousGetHandle because of DangerousAddRef in the constructor - Interop.Kernel32.SetThreadpoolWait(_tpWait, _waitHandle.DangerousGetHandle(), (IntPtr)pTimeout); - } - - public bool Unregister(WaitHandle waitObject) - { - // Hold the lock during the synchronous part of Unregister (as in CoreCLR) - using (LockHolder.Hold(_lock)) - { - if (!_unregistering) - { - // Ensure callbacks will not call SetThreadpoolWait anymore - _unregistering = true; - - // Cease queueing more callbacks - Interop.Kernel32.SetThreadpoolWait(_tpWait, IntPtr.Zero, IntPtr.Zero); - - // Should we wait for callbacks synchronously? Note that we treat the zero handle as the asynchronous case. - SafeWaitHandle? safeWaitHandle = waitObject?.SafeWaitHandle; - bool blocking = ((safeWaitHandle != null) && (safeWaitHandle.DangerousGetHandle() == new IntPtr(-1))); - - if (blocking) - { - FinishUnregistering(); - } - else - { - // Wait for callbacks and dispose resources asynchronously - ThreadPool.QueueUserWorkItem(FinishUnregisteringAsync, safeWaitHandle); - } - - return true; - } - } - return false; - } - - private void FinishUnregistering() - { - Debug.Assert(_unregistering); - - // Wait for outstanding wait callbacks to complete - Interop.Kernel32.WaitForThreadpoolWaitCallbacks(_tpWait, false); - - // Now it is safe to dispose resources - Interop.Kernel32.CloseThreadpoolWait(_tpWait); - _tpWait = IntPtr.Zero; - - if (_gcHandle.IsAllocated) - { - _gcHandle.Free(); - } - - Debug.Assert(_waitHandle != null); - _waitHandle.DangerousRelease(); - _waitHandle = null; - - GC.SuppressFinalize(this); - } - - private void FinishUnregisteringAsync(object? waitObject) - { - FinishUnregistering(); - - // Signal the provided wait object - SafeWaitHandle? safeWaitHandle = (SafeWaitHandle?)waitObject; - - if ((safeWaitHandle != null) && !safeWaitHandle.IsInvalid) - { - Interop.Kernel32.SetEvent(safeWaitHandle); - } - } - - ~RegisteredWaitHandle() - { - // If _gcHandle is allocated, it points to this object, so this object must not be collected by the GC - Debug.Assert(!_gcHandle.IsAllocated); - - // If this object gets resurrected and another thread calls Unregister, that creates a race condition. - // Do not block the finalizer thread. If another thread is running Unregister, it will clean up for us. - // The _lock may be null in case of OOM in the constructor. - if ((_lock != null) && _lock.TryAcquire(0)) - { - try - { - if (!_unregistering) - { - _unregistering = true; - - if (_tpWait != IntPtr.Zero) - { - // There must be no in-flight callbacks; just dispose resources - Interop.Kernel32.CloseThreadpoolWait(_tpWait); - _tpWait = IntPtr.Zero; - } - - if (_waitHandle != null) - { - _waitHandle.DangerousRelease(); - _waitHandle = null; - } - } - } - finally - { - _lock.Release(); - } - } - } - } - - public static partial class ThreadPool - { - internal const bool IsWorkerTrackingEnabledInConfig = false; - - // 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 - /// TppComputeDefaultMaxThreads(TppMaxGlobalPool). - /// - /// - /// Note that Windows 8 and 8.1 used a different value: Math.Max(4 * Environment.ProcessorCount, 512). - /// - private static readonly int MaxThreadCount = Math.Max(8 * Environment.ProcessorCount, 768); - - private static IntPtr s_work; - - private class ThreadCountHolder - { - internal ThreadCountHolder() => Interlocked.Increment(ref s_threadCount); - ~ThreadCountHolder() => Interlocked.Decrement(ref s_threadCount); - } - - [ThreadStatic] - private static ThreadCountHolder t_threadCountHolder; - private static int s_threadCount; - - [StructLayout(LayoutKind.Sequential)] - private struct WorkingThreadCounter - { - private readonly Internal.PaddingFor32 pad1; - - public volatile int Count; - - private readonly Internal.PaddingFor32 pad2; - } - - // The number of threads executing work items in the Dispatch method - private static WorkingThreadCounter s_workingThreadCounter; - - private static readonly ThreadInt64PersistentCounter s_completedWorkItemCounter = new ThreadInt64PersistentCounter(); - - [ThreadStatic] - private static object? t_completionCountObject; - - internal static void InitializeForThreadPoolThread() => t_threadCountHolder = new ThreadCountHolder(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void IncrementCompletedWorkItemCount() => ThreadInt64PersistentCounter.Increment(GetOrCreateThreadLocalCompletionCountObject()); - - internal static object GetOrCreateThreadLocalCompletionCountObject() => - t_completionCountObject ?? CreateThreadLocalCompletionCountObject(); - - [MethodImpl(MethodImplOptions.NoInlining)] - private static object CreateThreadLocalCompletionCountObject() - { - Debug.Assert(t_completionCountObject == null); - - object threadLocalCompletionCountObject = s_completedWorkItemCounter.CreateThreadLocalCountObject(); - t_completionCountObject = threadLocalCompletionCountObject; - return threadLocalCompletionCountObject; - } - - public static bool SetMaxThreads(int workerThreads, int completionPortThreads) - { - // Not supported at present - return false; - } - - public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) - { - // Note that worker threads and completion port threads share the same thread pool. - // The total number of threads cannot exceed MaxThreadCount. - workerThreads = MaxThreadCount; - completionPortThreads = MaxThreadCount; - } - - public static bool SetMinThreads(int workerThreads, int completionPortThreads) - { - // Not supported at present - return false; - } - - public static void GetMinThreads(out int workerThreads, out int completionPortThreads) - { - workerThreads = 0; - completionPortThreads = 0; - } - - public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) - { - // Make sure we return a non-negative value if thread pool defaults are changed - int availableThreads = Math.Max(MaxThreadCount - s_workingThreadCounter.Count, 0); - - workerThreads = availableThreads; - completionPortThreads = availableThreads; - } - - /// - /// Gets the number of thread pool threads that currently exist. - /// - /// - /// For a thread pool implementation that may have different types of threads, the count includes all types. - /// - public static int ThreadCount => s_threadCount; - - /// - /// Gets the number of work items that have been processed so far. - /// - /// - /// For a thread pool implementation that may have different types of work items, the count includes all types. - /// - public static long CompletedWorkItemCount => s_completedWorkItemCounter.Count; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void NotifyWorkItemProgress() => IncrementCompletedWorkItemCount(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int _ /*currentTimeMs*/) - { - ThreadInt64PersistentCounter.Increment(threadLocalCompletionCountObject); - return true; - } - - internal static bool NotifyThreadBlocked() { return false; } - internal static void NotifyThreadUnblocked() { } - - [UnmanagedCallersOnly] - private static void DispatchCallback(IntPtr instance, IntPtr context, IntPtr work) - { - var wrapper = ThreadPoolCallbackWrapper.Enter(); - Debug.Assert(s_work == work); - Interlocked.Increment(ref s_workingThreadCounter.Count); - ThreadPoolWorkQueue.Dispatch(); - Interlocked.Decrement(ref s_workingThreadCounter.Count); - // We reset the thread after executing each callback - wrapper.Exit(resetThread: false); - } - - internal static unsafe void RequestWorkerThread() - { - if (s_work == IntPtr.Zero) - { - IntPtr work = Interop.Kernel32.CreateThreadpoolWork(&DispatchCallback, IntPtr.Zero, IntPtr.Zero); - if (work == IntPtr.Zero) - throw new OutOfMemoryException(); - - if (Interlocked.CompareExchange(ref s_work, work, IntPtr.Zero) != IntPtr.Zero) - Interop.Kernel32.CloseThreadpoolWork(work); - } - - Interop.Kernel32.SubmitThreadpoolWork(s_work); - } - - private static RegisteredWaitHandle RegisterWaitForSingleObject( - WaitHandle waitObject, - WaitOrTimerCallback callBack, - object state, - uint millisecondsTimeOutInterval, - bool executeOnlyOnce, - bool flowExecutionContext) - { - ArgumentNullException.ThrowIfNull(waitObject); - ArgumentNullException.ThrowIfNull(callBack); - - var callbackHelper = new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext); - var registeredWaitHandle = new RegisteredWaitHandle(waitObject.SafeWaitHandle, callbackHelper, millisecondsTimeOutInterval, !executeOnlyOnce); - - registeredWaitHandle.RestartWait(); - return registeredWaitHandle; - } - - private static unsafe void NativeOverlappedCallback(nint overlappedPtr) => - IOCompletionCallbackHelper.PerformSingleIOCompletionCallback(0, 0, (NativeOverlapped*)overlappedPtr); - - [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 - overlapped->InternalLow = (IntPtr)0; - // Both types of callbacks are executed on the same thread pool - return UnsafeQueueUserWorkItem(NativeOverlappedCallback, (nint)overlapped, preferLocal: false); - } - - [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/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.NativeAot.cs similarity index 100% rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.cs rename to src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.NativeAot.cs diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolPreAllocatedOverlapped.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolPreAllocatedOverlapped.cs deleted file mode 100644 index e57f82aa24a5..000000000000 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolPreAllocatedOverlapped.cs +++ /dev/null @@ -1,64 +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, IDeferredDisposable - { - internal readonly unsafe Win32ThreadPoolNativeOverlapped* _overlapped; - private DeferredDisposableLifetime _lifetime; - - [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 unsafe PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext) - { - ArgumentNullException.ThrowIfNull(callback); - - _overlapped = Win32ThreadPoolNativeOverlapped.Allocate(callback, state, pinData, this, flowExecutionContext); - } - - internal bool AddRef() - { - return _lifetime.AddRef(); - } - - internal void Release() - { - _lifetime.Release(this); - } - - public void Dispose() - { - _lifetime.Dispose(this); - GC.SuppressFinalize(this); - } - - ~PreAllocatedOverlapped() - { - Dispose(); - } - - unsafe void IDeferredDisposable.OnFinalRelease(bool disposed) - { - if (_overlapped != null) - { - if (disposed) - Win32ThreadPoolNativeOverlapped.Free(_overlapped); - else - *Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(_overlapped) = default(NativeOverlapped); - } - } - - internal unsafe bool IsUserObject(byte[]? buffer) => _overlapped->IsUserObject(buffer); - } -} diff --git a/src/coreclr/vm/comthreadpool.cpp b/src/coreclr/vm/comthreadpool.cpp index 2d941b130991..daa8d48d3a30 100644 --- a/src/coreclr/vm/comthreadpool.cpp +++ b/src/coreclr/vm/comthreadpool.cpp @@ -43,23 +43,22 @@ FCIMPL4(INT32, ThreadPoolNative::GetNextConfigUInt32Value, case 2: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_ForceMaxWorkerThreads, false, W("System.Threading.ThreadPool.MaxThreads"))) { return 3; } FALLTHROUGH; case 3: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DisableStarvationDetection, true, W("System.Threading.ThreadPool.DisableStarvationDetection"))) { return 4; } FALLTHROUGH; case 4: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_DebugBreakOnWorkerStarvation, true, W("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation"))) { return 5; } FALLTHROUGH; - case 5: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_EnableWorkerTracking, true, W("System.Threading.ThreadPool.EnableWorkerTracking"))) { return 6; } FALLTHROUGH; - case 6: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit, false, W("System.Threading.ThreadPool.UnfairSemaphoreSpinLimit"))) { return 7; } FALLTHROUGH; + case 5: if (TryGetConfig(CLRConfig::INTERNAL_ThreadPool_UnfairSemaphoreSpinLimit, false, W("System.Threading.ThreadPool.UnfairSemaphoreSpinLimit"))) { return 6; } FALLTHROUGH; - case 7: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Disable, true, W("System.Threading.ThreadPool.HillClimbing.Disable"))) { return 8; } FALLTHROUGH; - case 8: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WavePeriod, false, W("System.Threading.ThreadPool.HillClimbing.WavePeriod"))) { return 9; } FALLTHROUGH; - case 9: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_TargetSignalToNoiseRatio, false, W("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio"))) { return 10; } FALLTHROUGH; - case 10: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_ErrorSmoothingFactor, false, W("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor"))) { return 11; } FALLTHROUGH; - case 11: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveMagnitudeMultiplier, false, W("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier"))) { return 12; } FALLTHROUGH; - case 12: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxWaveMagnitude, false, W("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude"))) { return 13; } FALLTHROUGH; - case 13: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveHistorySize, false, W("System.Threading.ThreadPool.HillClimbing.WaveHistorySize"))) { return 14; } FALLTHROUGH; - case 14: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Bias, false, W("System.Threading.ThreadPool.HillClimbing.Bias"))) { return 15; } FALLTHROUGH; - case 15: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSecond, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond"))) { return 16; } FALLTHROUGH; - case 16: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSample, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample"))) { return 17; } FALLTHROUGH; - case 17: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxSampleErrorPercent, false, W("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent"))) { return 18; } FALLTHROUGH; - case 18: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow"))) { return 19; } FALLTHROUGH; - case 19: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalHigh, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh"))) { return 20; } FALLTHROUGH; - case 20: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_GainExponent, false, W("System.Threading.ThreadPool.HillClimbing.GainExponent"))) { return 21; } FALLTHROUGH; + case 6: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Disable, true, W("System.Threading.ThreadPool.HillClimbing.Disable"))) { return 7; } FALLTHROUGH; + case 7: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WavePeriod, false, W("System.Threading.ThreadPool.HillClimbing.WavePeriod"))) { return 8; } FALLTHROUGH; + case 8: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_TargetSignalToNoiseRatio, false, W("System.Threading.ThreadPool.HillClimbing.TargetSignalToNoiseRatio"))) { return 9; } FALLTHROUGH; + case 9: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_ErrorSmoothingFactor, false, W("System.Threading.ThreadPool.HillClimbing.ErrorSmoothingFactor"))) { return 10; } FALLTHROUGH; + case 10: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveMagnitudeMultiplier, false, W("System.Threading.ThreadPool.HillClimbing.WaveMagnitudeMultiplier"))) { return 11; } FALLTHROUGH; + case 11: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxWaveMagnitude, false, W("System.Threading.ThreadPool.HillClimbing.MaxWaveMagnitude"))) { return 12; } FALLTHROUGH; + case 12: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_WaveHistorySize, false, W("System.Threading.ThreadPool.HillClimbing.WaveHistorySize"))) { return 13; } FALLTHROUGH; + case 13: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_Bias, false, W("System.Threading.ThreadPool.HillClimbing.Bias"))) { return 14; } FALLTHROUGH; + case 14: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSecond, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSecond"))) { return 15; } FALLTHROUGH; + case 15: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxChangePerSample, false, W("System.Threading.ThreadPool.HillClimbing.MaxChangePerSample"))) { return 16; } FALLTHROUGH; + case 16: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_MaxSampleErrorPercent, false, W("System.Threading.ThreadPool.HillClimbing.MaxSampleErrorPercent"))) { return 17; } FALLTHROUGH; + case 17: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalLow, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalLow"))) { return 18; } FALLTHROUGH; + case 18: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_SampleIntervalHigh, false, W("System.Threading.ThreadPool.HillClimbing.SampleIntervalHigh"))) { return 19; } FALLTHROUGH; + case 19: if (TryGetConfig(CLRConfig::INTERNAL_HillClimbing_GainExponent, false, W("System.Threading.ThreadPool.HillClimbing.GainExponent"))) { return 20; } FALLTHROUGH; default: *configValueRef = 0; diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Windows.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Windows.xml new file mode 100644 index 000000000000..29c26957ff6e --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Windows.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs similarity index 90% rename from src/coreclr/nativeaot/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs rename to src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs index d6bf95212d4e..5d8f7916bca0 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs @@ -6,7 +6,7 @@ namespace Microsoft.Win32.SafeHandles { - internal class SafeThreadPoolIOHandle : SafeHandle + internal sealed class SafeThreadPoolIOHandle : SafeHandle { public SafeThreadPoolIOHandle() : base(IntPtr.Zero, true) 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 c7a6eed48f32..33fc345a9950 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 @@ -57,6 +57,7 @@ + @@ -1217,6 +1218,9 @@ + + + @@ -1578,7 +1582,7 @@ Common\Interop\Windows\Kernel32\Interop.CancelIoEx.cs - + Common\Interop\Windows\Kernel32\Interop.CompletionPort.cs @@ -1596,6 +1600,15 @@ Common\Interop\Windows\Kernel32\Interop.CreateFile.cs + + Interop\Windows\Kernel32\Interop.Timer.cs + + + Interop\Windows\Kernel32\Interop.ThreadPoolIO.cs + + + Interop\Windows\Kernel32\Interop.ThreadPool.cs + Common\Interop\Windows\NtDll\Interop.NtCreateFile.cs @@ -2019,6 +2032,7 @@ + @@ -2057,8 +2071,14 @@ + + + + + + @@ -2373,7 +2393,7 @@ - + @@ -2526,9 +2546,9 @@ - - - + + + @@ -2546,13 +2566,18 @@ - - + + + + + - - + + + + diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/CompleteWaitThreadPoolWorkItem.cs similarity index 53% rename from src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/CompleteWaitThreadPoolWorkItem.cs index 6a6dd5b82e00..cc08d82e6340 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/ThreadPool.Mono.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/CompleteWaitThreadPoolWorkItem.cs @@ -1,17 +1,15 @@ // 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; -using System.Diagnostics; namespace System.Threading { - public static partial class ThreadPool + internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem { - [Conditional("unnecessary")] - internal static void ReportThreadStatus(bool isWorking) - { - } + void IThreadPoolWorkItem.Execute() => PortableThreadPool.CompleteWait(_registeredWaitHandle, _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 7adfe5a0b9ca..9ada201e134c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -457,5 +457,27 @@ private bool OnGen2GCCallback() _memoryUsageBytes = Math.Min(gcMemoryInfo.MemoryLoadBytes, gcMemoryInfo.HighMemoryLoadThresholdBytes); return true; // continue receiving gen 2 GC callbacks } + + internal static RegisteredWaitHandle RegisterWaitForSingleObject( + WaitHandle waitObject, + WaitOrTimerCallback callBack, + object? state, + uint millisecondsTimeOutInterval, + bool executeOnlyOnce, + bool flowExecutionContext) + { + ArgumentNullException.ThrowIfNull(waitObject); + ArgumentNullException.ThrowIfNull(callBack); + + RegisteredWaitHandle registeredWaitHandle = new RegisteredWaitHandle( + waitObject, + new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext), + (int)millisecondsTimeOutInterval, + !executeOnlyOnce); + + PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredWaitHandle); + + return registeredWaitHandle; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Portable.cs new file mode 100644 index 000000000000..c481ac6cefc0 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Portable.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Threading +{ + /// + /// Represents pre-allocated state for native overlapped I/O operations. + /// + /// + public sealed partial class PreAllocatedOverlapped : IDisposable, IDeferredDisposable + { + internal ThreadPoolBoundHandleOverlapped? _overlappedPortableCore; + + private static PreAllocatedOverlapped UnsafeCreatePortableCore(IOCompletionCallback callback, object? state, object? pinData) => + new PreAllocatedOverlapped(callback, state, pinData, flowExecutionContext: false); + + private bool AddRefPortableCore() + { + return _lifetime.AddRef(); + } + + private void ReleasePortableCore() + { + _lifetime.Release(this); + } + + private void DisposePortableCore() + { + _lifetime.Dispose(this); + GC.SuppressFinalize(this); + } + + private unsafe void IDeferredDisposableOnFinalReleasePortableCore(bool disposed) + { + if (_overlappedPortableCore != null) // protect against ctor throwing exception and leaving field uninitialized + { + if (disposed) + { + Overlapped.Free(_overlappedPortableCore._nativeOverlapped); + } + else + { + _overlappedPortableCore._boundHandle = null; + _overlappedPortableCore._completed = false; + *_overlappedPortableCore._nativeOverlapped = default; + } + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Unix.cs similarity index 82% rename from src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Unix.cs index 01a7aa93f012..d638e6773f2c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Unix.cs @@ -1,17 +1,16 @@ // 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 { /// /// Represents pre-allocated state for native overlapped I/O operations. /// /// - public sealed class PreAllocatedOverlapped : IDisposable, IDeferredDisposable + public sealed partial class PreAllocatedOverlapped : IDisposable, IDeferredDisposable { - internal readonly ThreadPoolBoundHandleOverlapped _overlapped; - private DeferredDisposableLifetime _lifetime; - /// /// Initializes a new instance of the class, specifying /// a delegate that is invoked when each asynchronous I/O operation is complete, a user-provided @@ -52,6 +51,8 @@ public sealed class PreAllocatedOverlapped : IDisposable, IDeferredDisposable { } + private DeferredDisposableLifetime _lifetime; + /// /// Initializes a new instance of the class, specifying /// a delegate that is invoked when each asynchronous I/O operation is complete, a user-provided @@ -89,54 +90,35 @@ public sealed class PreAllocatedOverlapped : IDisposable, IDeferredDisposable /// [CLSCompliant(false)] public static PreAllocatedOverlapped UnsafeCreate(IOCompletionCallback callback, object? state, object? pinData) => - new PreAllocatedOverlapped(callback, state, pinData, flowExecutionContext: false); + UnsafeCreatePortableCore(callback, state, pinData); - private PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext) + private unsafe PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext) { + // This construction is duplicated in PreAllocatedOverlapped.Windows.cs + // It has to either be duplicated or remove the 'readonly' part of _overlappedPortableCore ArgumentNullException.ThrowIfNull(callback); - _overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, this, flowExecutionContext); - } - - internal bool AddRef() - { - return _lifetime.AddRef(); + _overlappedPortableCore = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, this, flowExecutionContext); } - internal void Release() - { - _lifetime.Release(this); - } + internal bool AddRef() => AddRefPortableCore(); /// /// Frees the resources associated with this instance. /// public void Dispose() { - _lifetime.Dispose(this); - GC.SuppressFinalize(this); + DisposePortableCore(); } - ~PreAllocatedOverlapped() + internal void Release() { - Dispose(); + ReleasePortableCore(); } unsafe void IDeferredDisposable.OnFinalRelease(bool disposed) { - if (_overlapped != null) // protect against ctor throwing exception and leaving field uninitialized - { - if (disposed) - { - Overlapped.Free(_overlapped._nativeOverlapped); - } - else - { - _overlapped._boundHandle = null; - _overlapped._completed = false; - *_overlapped._nativeOverlapped = default; - } - } + IDeferredDisposableOnFinalReleasePortableCore(disposed); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Windows.cs new file mode 100644 index 000000000000..32149a729549 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.Windows.cs @@ -0,0 +1,159 @@ +// 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 +{ + /// + /// Represents pre-allocated state for native overlapped I/O operations. + /// + /// + public sealed partial class PreAllocatedOverlapped : IDisposable, IDeferredDisposable + { + private DeferredDisposableLifetime _lifetime; + + /// + /// Initializes a new instance of the class, specifying + /// a delegate that is invoked when each asynchronous I/O operation is complete, a user-provided + /// object providing context, and managed objects that serve as buffers. + /// + /// + /// An delegate that represents the callback method + /// invoked when each asynchronous I/O operation completes. + /// + /// + /// A user-provided object that distinguishes instance produced from this + /// object from other instances. Can be . + /// + /// + /// An object or array of objects representing the input or output buffer for the operations. Each + /// object represents a buffer, for example an array of bytes. Can be . + /// + /// + /// The new instance can be passed to + /// , to produce + /// a instance that can be passed to the operating system in overlapped + /// I/O operations. A single instance can only be used for + /// a single native I/O operation at a time. However, the state stored in the + /// instance can be reused for subsequent native operations. + /// + /// The buffers specified in are pinned until is called. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData) : + this(callback, state, pinData, flowExecutionContext: true) + { + } + + /// + /// Initializes a new instance of the class, specifying + /// a delegate that is invoked when each asynchronous I/O operation is complete, a user-provided + /// object providing context, and managed objects that serve as buffers. + /// + /// + /// An delegate that represents the callback method + /// invoked when each asynchronous I/O operation completes. + /// + /// + /// A user-provided object that distinguishes instance produced from this + /// object from other instances. Can be . + /// + /// + /// An object or array of objects representing the input or output buffer for the operations. Each + /// object represents a buffer, for example an array of bytes. Can be . + /// + /// + /// The new instance can be passed to + /// , to produce + /// a instance that can be passed to the operating system in overlapped + /// I/O operations. A single instance can only be used for + /// a single native I/O operation at a time. However, the state stored in the + /// instance can be reused for subsequent native operations. ExecutionContext is not flowed to the invocation + /// of the callback. + /// + /// The buffers specified in are pinned until is called. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public static PreAllocatedOverlapped UnsafeCreate(IOCompletionCallback callback, object? state, object? pinData) => + ThreadPool.UseWindowsThreadPool ? UnsafeCreateWindowsThreadPool(callback, state, pinData) : UnsafeCreatePortableCore(callback, state, pinData); + + private unsafe PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext) + { + if (ThreadPool.UseWindowsThreadPool) + { + ArgumentNullException.ThrowIfNull(callback); + + _overlappedWindowsThreadPool = Win32ThreadPoolNativeOverlapped.Allocate(callback, state, pinData, this, flowExecutionContext); + } + else + { + // This construction is duplicated in PreAllocatedOverlapped.Unix.cs + // It has to either be duplicated or remove the 'readonly' part of _overlappedPortableCore + ArgumentNullException.ThrowIfNull(callback); + + _overlappedPortableCore = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, this, flowExecutionContext); + } + } + + internal bool AddRef() => ThreadPool.UseWindowsThreadPool ? AddRefWindowsThreadPool() : AddRefPortableCore(); + + internal void Release() + { + if (ThreadPool.UseWindowsThreadPool) + { + ReleaseWindowsThreadPool(); + } + else + { + ReleasePortableCore(); + } + } + + /// + /// Frees the resources associated with this instance. + /// + public void Dispose() + { + if (ThreadPool.UseWindowsThreadPool) + { + DisposeWindowsThreadPool(); + } + else + { + DisposePortableCore(); + } + } + + ~PreAllocatedOverlapped() + { + Dispose(); + } + + unsafe void IDeferredDisposable.OnFinalRelease(bool disposed) + { + if (ThreadPool.UseWindowsThreadPool) + { + IDeferredDisposableOnFinalReleaseWindowsThreadPool(disposed); + } + else + { + IDeferredDisposableOnFinalReleasePortableCore(disposed); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.WindowsThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.WindowsThreadPool.cs new file mode 100644 index 000000000000..0ad038cf84a8 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.WindowsThreadPool.cs @@ -0,0 +1,44 @@ +// 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 partial class PreAllocatedOverlapped : IDisposable, IDeferredDisposable + { + internal readonly unsafe Win32ThreadPoolNativeOverlapped* _overlappedWindowsThreadPool; + + private static PreAllocatedOverlapped UnsafeCreateWindowsThreadPool(IOCompletionCallback callback, object? state, object? pinData) => + new PreAllocatedOverlapped(callback, state, pinData, flowExecutionContext: false); + + private bool AddRefWindowsThreadPool() + { + return _lifetime.AddRef(); + } + + private void ReleaseWindowsThreadPool() + { + _lifetime.Release(this); + } + + internal unsafe bool IsUserObject(byte[]? buffer) => _overlappedWindowsThreadPool->IsUserObject(buffer); + + private void DisposeWindowsThreadPool() + { + _lifetime.Dispose(this); + GC.SuppressFinalize(this); + } + + private unsafe void IDeferredDisposableOnFinalReleaseWindowsThreadPool(bool disposed) + { + if (_overlappedWindowsThreadPool != null) + { + if (disposed) + Win32ThreadPoolNativeOverlapped.Free(_overlappedWindowsThreadPool); + else + *Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(_overlappedWindowsThreadPool) = default(NativeOverlapped); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs index 359a9062d5db..b5672e243282 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; @@ -15,66 +15,9 @@ namespace System.Threading #endif public sealed partial class RegisteredWaitHandle : MarshalByRefObject { - internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper, - int millisecondsTimeout, bool repeating) - { - Thread.ThrowIfNoThreadStart(); - Handle = waitHandle.SafeWaitHandle; - Callback = callbackHelper; - TimeoutDurationMs = millisecondsTimeout; - Repeating = repeating; - if (!IsInfiniteTimeout) - { - RestartTimeout(); - } - } - private static AutoResetEvent? s_cachedEvent; - - private static AutoResetEvent RentEvent() => - Interlocked.Exchange(ref s_cachedEvent, null) ?? - new AutoResetEvent(false); - - private static void ReturnEvent(AutoResetEvent resetEvent) - { - if (Interlocked.CompareExchange(ref s_cachedEvent, resetEvent, null) != null) - { - resetEvent.Dispose(); - } - } - private static readonly LowLevelLock s_callbackLock = new LowLevelLock(); - /// - /// The callback to execute when the wait on either times out or completes. - /// - internal _ThreadPoolWaitOrTimerCallback Callback { get; } - - /// - /// The that was registered. - /// - internal SafeWaitHandle Handle { get; } - - /// - /// The time this handle times out at in ms. - /// - internal int TimeoutTimeMs { get; private set; } - - internal int TimeoutDurationMs { get; } - - internal bool IsInfiniteTimeout => TimeoutDurationMs == -1; - - internal void RestartTimeout() - { - Debug.Assert(!IsInfiniteTimeout); - TimeoutTimeMs = Environment.TickCount + TimeoutDurationMs; - } - - /// - /// Whether or not the wait is a repeating wait. - /// - internal bool Repeating { get; } - /// /// The the user passed in via . /// @@ -109,7 +52,44 @@ internal void RestartTimeout() /// internal PortableThreadPool.WaitThread? WaitThread { get; set; } - public bool Unregister(WaitHandle waitObject) + internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper, + int millisecondsTimeout, bool repeating) + { +#if WINDOWS + Debug.Assert(!ThreadPool.UseWindowsThreadPool); +#endif + GC.SuppressFinalize(this); + + Thread.ThrowIfNoThreadStart(); + _waitHandle = waitHandle.SafeWaitHandle; + _callbackHelper = callbackHelper; + _signedMillisecondsTimeout = millisecondsTimeout; + _repeating = repeating; + if (!IsInfiniteTimeout) + { + RestartTimeout(); + } + } + + private static AutoResetEvent RentEvent() => + Interlocked.Exchange(ref s_cachedEvent, null) ?? + new AutoResetEvent(false); + + private static void ReturnEvent(AutoResetEvent resetEvent) + { + if (Interlocked.CompareExchange(ref s_cachedEvent, resetEvent, null) != null) + { + resetEvent.Dispose(); + } + } + + internal void RestartTimeout() + { + Debug.Assert(!IsInfiniteTimeout); + TimeoutTimeMs = Environment.TickCount + TimeoutDurationMs; + } + + private bool UnregisterPortableCore(WaitHandle waitObject) { // The registered wait handle must have been registered by this time, otherwise the instance is not handed out to // the caller of the public variants of RegisterWaitForSingleObject @@ -205,7 +185,7 @@ private void SignalUserWaitHandle() /// Perform the registered callback if the has not been signaled. /// /// Whether or not the wait timed out. - internal void PerformCallback(bool timedOut) + internal void PerformCallbackPortableCore(bool timedOut) { #if DEBUG s_callbackLock.Acquire(); @@ -219,7 +199,7 @@ internal void PerformCallback(bool timedOut) } #endif - _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Callback, timedOut); + _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Callback!, timedOut); CompleteCallbackRequest(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Unix.cs new file mode 100644 index 000000000000..57adb4298663 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Unix.cs @@ -0,0 +1,71 @@ +// 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.Versioning; +using Microsoft.Win32.SafeHandles; + +namespace System.Threading +{ + /// + /// An object representing the registration of a via . + /// + public sealed partial class RegisteredWaitHandle : MarshalByRefObject + { + private SafeWaitHandle? _waitHandle; + private readonly _ThreadPoolWaitOrTimerCallback? _callbackHelper; + private readonly int _signedMillisecondsTimeout; + private bool _repeating; + + /// + /// The callback to execute when the wait on either times out or completes. + /// + internal _ThreadPoolWaitOrTimerCallback? Callback + { + get => _callbackHelper; + } + + /// + /// The that was registered. + /// + internal SafeWaitHandle Handle + { + get + { + Debug.Assert(_waitHandle != null); + return _waitHandle; + } + } + + /// + /// The time this handle times out at in ms. + /// + internal int TimeoutTimeMs { get; private set; } + + internal int TimeoutDurationMs + { + get => _signedMillisecondsTimeout; + } + + internal bool IsInfiniteTimeout => TimeoutDurationMs == -1; + + /// + /// Whether or not the wait is a repeating wait. + /// + internal bool Repeating + { + get => _repeating; + } + + public bool Unregister(WaitHandle waitObject) => UnregisterPortableCore(waitObject); + + /// + /// Perform the registered callback if the has not been signaled. + /// + /// Whether or not the wait timed out. + internal void PerformCallback(bool timedOut) + { + PerformCallbackPortableCore(timedOut); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Windows.cs new file mode 100644 index 000000000000..7f1d1309f105 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Windows.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Win32.SafeHandles; +using System.Diagnostics; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace System.Threading +{ + public sealed partial class RegisteredWaitHandle : MarshalByRefObject + { + private SafeWaitHandle? _waitHandle; + private readonly _ThreadPoolWaitOrTimerCallback? _callbackHelper; + private readonly uint _millisecondsTimeout; + private readonly int _signedMillisecondsTimeout; + private bool _repeating; + + /// + /// The callback to execute when the wait on either times out or completes. + /// + internal _ThreadPoolWaitOrTimerCallback? Callback + { + get => _callbackHelper; + } + + /// + /// The that was registered. + /// + internal SafeWaitHandle Handle + { + get + { + Debug.Assert(_waitHandle != null); + return _waitHandle; + } + } + + /// + /// The time this handle times out at in ms. + /// + internal int TimeoutTimeMs { get; private set; } + + internal int TimeoutDurationMs + { + get => _signedMillisecondsTimeout; + } + + internal bool IsInfiniteTimeout => TimeoutDurationMs == -1; + + /// + /// Whether or not the wait is a repeating wait. + /// + internal bool Repeating + { + get => _repeating; + } + + public bool Unregister(WaitHandle waitObject) => + ThreadPool.UseWindowsThreadPool ? + UnregisterWindowsThreadPool(waitObject) : + UnregisterPortableCore(waitObject); + + /// + /// Perform the registered callback if the has not been signaled. + /// + /// Whether or not the wait timed out. + internal void PerformCallback(bool timedOut) + { + if (ThreadPool.UseWindowsThreadPool) + { + PerformCallbackWindowsThreadPool(timedOut); + } + else + { + PerformCallbackPortableCore(timedOut); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs new file mode 100644 index 000000000000..cad89f3f0709 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs @@ -0,0 +1,220 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Win32.SafeHandles; +using System.Diagnostics; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace System.Threading +{ + // + // Windows-specific implementation of ThreadPool + // + public sealed partial class RegisteredWaitHandle : MarshalByRefObject + { + private readonly object? _lock; + private bool _unregistering; + + // Handle to this object to keep it alive + private GCHandle _gcHandle; + + // Pointer to the TP_WAIT structure + private IntPtr _tpWait; + + internal unsafe RegisteredWaitHandle(SafeWaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper, + uint millisecondsTimeout, bool repeating) + { + Debug.Assert(ThreadPool.UseWindowsThreadPool); + + _lock = new object(); + + waitHandle.DangerousAddRef(); + _waitHandle = waitHandle; + + _callbackHelper = callbackHelper; + _millisecondsTimeout = millisecondsTimeout; + _repeating = repeating; + + // Allocate _gcHandle and _tpWait as the last step and make sure they are never leaked + _gcHandle = GCHandle.Alloc(this); + + _tpWait = Interop.Kernel32.CreateThreadpoolWait(&RegisteredWaitCallback, (IntPtr)_gcHandle, IntPtr.Zero); + + if (_tpWait == IntPtr.Zero) + { + _gcHandle.Free(); + throw new OutOfMemoryException(); + } + } + +#pragma warning disable IDE0060 // Remove unused parameter + [UnmanagedCallersOnly] + internal static void RegisteredWaitCallback(IntPtr instance, IntPtr context, IntPtr wait, uint waitResult) + { + var wrapper = ThreadPoolCallbackWrapper.Enter(); + + GCHandle handle = (GCHandle)context; + RegisteredWaitHandle registeredWaitHandle = (RegisteredWaitHandle)handle.Target!; + Debug.Assert((handle == registeredWaitHandle._gcHandle) && (wait == registeredWaitHandle._tpWait)); + + bool timedOut = (waitResult == (uint)Interop.Kernel32.WAIT_TIMEOUT); + registeredWaitHandle.PerformCallbackWindowsThreadPool(timedOut); + ThreadPool.IncrementCompletedWorkItemCount(); + + wrapper.Exit(); + } +#pragma warning restore IDE0060 + + private void PerformCallbackWindowsThreadPool(bool timedOut) + { + // Prevent the race condition with Unregister and the previous PerformCallback call, which may still be + // holding the _lock. + // If another thread is running Unregister, no need to restart the timer or clean up + lock (_lock!) + { + if (!_unregistering) + { + if (_repeating) + { + // Allow this wait to fire again. Restart the timer before executing the callback. + RestartWait(); + } + else + { + // This wait will not be fired again. Free the GC handle to allow the GC to collect this object. + Debug.Assert(_gcHandle.IsAllocated); + _gcHandle.Free(); + } + } + } + + _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(_callbackHelper!, timedOut); + } + + internal unsafe void RestartWait() + { + long timeout; + long* pTimeout = null; // Null indicates infinite timeout + + if (_millisecondsTimeout != Timeout.UnsignedInfinite) + { + timeout = -10000L * _millisecondsTimeout; + pTimeout = &timeout; + } + + // We can use DangerousGetHandle because of DangerousAddRef in the constructor + Interop.Kernel32.SetThreadpoolWait(_tpWait, _waitHandle!.DangerousGetHandle(), (IntPtr)pTimeout); + } + + private bool UnregisterWindowsThreadPool(WaitHandle waitObject) + { + // Hold the lock during the synchronous part of Unregister (as in CoreCLR) + lock(_lock!) + { + if (!_unregistering) + { + // Ensure callbacks will not call SetThreadpoolWait anymore + _unregistering = true; + + // Cease queueing more callbacks + Interop.Kernel32.SetThreadpoolWait(_tpWait, IntPtr.Zero, IntPtr.Zero); + + // Should we wait for callbacks synchronously? Note that we treat the zero handle as the asynchronous case. + SafeWaitHandle? safeWaitHandle = waitObject?.SafeWaitHandle; + bool blocking = ((safeWaitHandle != null) && (safeWaitHandle.DangerousGetHandle() == new IntPtr(-1))); + + if (blocking) + { + FinishUnregistering(); + } + else + { + // Wait for callbacks and dispose resources asynchronously + ThreadPool.QueueUserWorkItem(FinishUnregisteringAsync, safeWaitHandle); + } + + return true; + } + } + return false; + } + + private void FinishUnregistering() + { + Debug.Assert(_unregistering); + + // Wait for outstanding wait callbacks to complete + Interop.Kernel32.WaitForThreadpoolWaitCallbacks(_tpWait, false); + + // Now it is safe to dispose resources + Interop.Kernel32.CloseThreadpoolWait(_tpWait); + _tpWait = IntPtr.Zero; + + if (_gcHandle.IsAllocated) + { + _gcHandle.Free(); + } + + Debug.Assert(_waitHandle != null); + _waitHandle.DangerousRelease(); + _waitHandle = null; + + GC.SuppressFinalize(this); + } + + private void FinishUnregisteringAsync(object? waitObject) + { + FinishUnregistering(); + + // Signal the provided wait object + SafeWaitHandle? safeWaitHandle = (SafeWaitHandle?)waitObject; + + if ((safeWaitHandle != null) && !safeWaitHandle.IsInvalid) + { + Interop.Kernel32.SetEvent(safeWaitHandle); + } + } + + ~RegisteredWaitHandle() + { + Debug.Assert(ThreadPool.UseWindowsThreadPool); + // If _gcHandle is allocated, it points to this object, so this object must not be collected by the GC + Debug.Assert(!_gcHandle.IsAllocated); + + // If this object gets resurrected and another thread calls Unregister, that creates a race condition. + // Do not block the finalizer thread. If another thread is running Unregister, it will clean up for us. + // The _lock may be null in case of OOM in the constructor. + if ((_lock != null) && Monitor.TryEnter(_lock)) + { + try + { + if (!_unregistering) + { + _unregistering = true; + + if (_tpWait != IntPtr.Zero) + { + // There must be no in-flight callbacks; just dispose resources + Interop.Kernel32.CloseThreadpoolWait(_tpWait); + _tpWait = IntPtr.Zero; + } + + if (_waitHandle != null) + { + _waitHandle.DangerousRelease(); + _waitHandle = null; + } + } + } + finally + { + Monitor.Exit(_lock); + } + } + } + } + +} 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 deleted file mode 100644 index 1d4e0c171496..000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Unix.cs +++ /dev/null @@ -1,25 +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.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 index 024110ea93b9..d1e959ce89c3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.Windows.cs @@ -10,9 +10,8 @@ namespace System.Threading { public static partial class ThreadPool { - [CLSCompliant(false)] [SupportedOSPlatform("windows")] - public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) + private static unsafe bool UnsafeQueueNativeOverlappedPortableCore(NativeOverlapped* overlapped) { if (overlapped == null) { @@ -28,14 +27,14 @@ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapp [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] [SupportedOSPlatform("windows")] - public static bool BindHandle(IntPtr osHandle) + private static bool BindHandlePortableCore(IntPtr osHandle) { PortableThreadPool.ThreadPoolInstance.RegisterForIOCompletionNotifications(osHandle); return true; } [SupportedOSPlatform("windows")] - public static bool BindHandle(SafeHandle osHandle) + private static bool BindHandlePortableCore(SafeHandle osHandle) { ArgumentNullException.ThrowIfNull(osHandle); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs similarity index 53% rename from src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs index a9c4e038129a..3c5888a6f80d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs @@ -1,93 +1,87 @@ // 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 { - // - // Portable implementation of ThreadPool - // - - internal sealed partial class CompleteWaitThreadPoolWorkItem : IThreadPoolWorkItem - { - void IThreadPoolWorkItem.Execute() => PortableThreadPool.CompleteWait(_registeredWaitHandle, _timedOut); - } - public static partial class ThreadPool { - // 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 -#if !(TARGET_BROWSER && FEATURE_WASM_THREADS) - internal static bool YieldFromDispatchLoop => false; -#endif - #if NATIVEAOT private const bool IsWorkerTrackingEnabledInConfig = false; #else private static readonly bool IsWorkerTrackingEnabledInConfig = - AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", false); + AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", "DOTNET_ThreadPool_EnableWorkerTracking"); #endif - // Threadpool specific initialization of a new thread. Used by OS-provided threadpools. No-op for portable threadpool. - internal static void InitializeForThreadPoolThread() { } +#if !(TARGET_BROWSER && FEATURE_WASM_THREADS) + // 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; +#endif -#pragma warning disable IDE0060 - internal static bool CanSetMinIOCompletionThreads(int ioCompletionThreads) => false; - internal static bool CanSetMaxIOCompletionThreads(int ioCompletionThreads) => false; -#pragma warning restore IDE0060 +#if !CORECLR + internal static bool EnsureConfigInitialized() => true; +#endif - [Conditional("unnecessary")] - internal static void SetMinIOCompletionThreads(int ioCompletionThreads) { } - [Conditional("unnecessary")] - internal static void SetMaxIOCompletionThreads(int ioCompletionThreads) { } + internal static object GetOrCreateThreadLocalCompletionCountObject() => + PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject(); public static bool SetMaxThreads(int workerThreads, int completionPortThreads) => PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads); - public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) => + + 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) => + + 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. - /// - /// - /// For a thread pool implementation that may have different types of threads, the count includes all types. - /// - public static int ThreadCount => PortableThreadPool.ThreadPoolInstance.ThreadCount; + public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) + { + PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads); + } - /// - /// Gets the number of work items that have been processed by the thread pool so far. - /// - /// - /// For a thread pool implementation that may have different types of work items, the count includes all types. - /// - public static long CompletedWorkItemCount => PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void NotifyWorkItemProgress() + { + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); + } - /// - /// This method is called to request a new thread pool worker to handle pending work. - /// - internal static void RequestWorkerThread() => PortableThreadPool.ThreadPoolInstance.RequestWorker(); + internal static bool NotifyThreadBlocked() => + PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked(); - internal static void NotifyWorkItemProgress() => PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); + internal static void NotifyThreadUnblocked() + { + PortableThreadPool.ThreadPoolInstance.NotifyThreadUnblocked(); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool NotifyWorkItemComplete(object? threadLocalCompletionCountObject, int currentTimeMs) => + internal static bool NotifyWorkItemComplete(object threadLocalCompletionCountObject, int currentTimeMs) => PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(threadLocalCompletionCountObject, currentTimeMs); - internal static bool NotifyThreadBlocked() => PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked(); - internal static void NotifyThreadUnblocked() => PortableThreadPool.ThreadPoolInstance.NotifyThreadUnblocked(); + /// + /// This method is called to request a new thread pool worker to handle pending work. + /// + internal static unsafe void RequestWorkerThread() + { + PortableThreadPool.ThreadPoolInstance.RequestWorker(); + } - internal static object GetOrCreateThreadLocalCompletionCountObject() => - PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject(); + internal static void ReportThreadStatus(bool isWorking) + { + PortableThreadPool.ThreadPoolInstance.ReportThreadStatus(isWorking); + } - private static RegisteredWaitHandle RegisterWaitForSingleObject( + internal static RegisteredWaitHandle RegisterWaitForSingleObject( WaitHandle waitObject, WaitOrTimerCallback callBack, object? state, @@ -95,17 +89,51 @@ public static partial class ThreadPool bool executeOnlyOnce, bool flowExecutionContext) { - ArgumentNullException.ThrowIfNull(waitObject); - ArgumentNullException.ThrowIfNull(callBack); - Thread.ThrowIfNoThreadStart(); - RegisteredWaitHandle registeredHandle = new RegisteredWaitHandle( - waitObject, - new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext), - (int)millisecondsTimeOutInterval, - !executeOnlyOnce); - PortableThreadPool.ThreadPoolInstance.RegisterWaitHandle(registeredHandle); - return registeredHandle; + return PortableThreadPool.RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, flowExecutionContext); + } + + [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); + + /// + /// Gets the number of thread pool threads that currently exist. + /// + /// + /// For a thread pool implementation that may have different types of threads, the count includes all types. + /// + public static int ThreadCount + { + get + { + return PortableThreadPool.ThreadPoolInstance.ThreadCount; + } } + + /// + /// Gets the number of work items that have been processed so far. + /// + /// + /// For a thread pool implementation that may have different types of work items, the count includes all types. + /// + public static long CompletedWorkItemCount + { + get + { + return PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount; + } + } + } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs new file mode 100644 index 000000000000..6882b0482c01 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs @@ -0,0 +1,220 @@ +// 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 + { + internal static bool UseWindowsThreadPool { get; } = + AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.UseWindowsThreadPool", "DOTNET_ThreadPool_UseWindowsThreadPool"); + +#if NATIVEAOT + private const bool IsWorkerTrackingEnabledInConfig = false; +#else + private static readonly bool IsWorkerTrackingEnabledInConfig = + UseWindowsThreadPool ? false : AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", "DOTNET_ThreadPool_EnableWorkerTracking"); +#endif + + // 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 => UseWindowsThreadPool; + + [CLSCompliant(false)] + [SupportedOSPlatform("windows")] + public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) => + ThreadPool.UseWindowsThreadPool ? + WindowsThreadPool.UnsafeQueueNativeOverlapped(overlapped) : + UnsafeQueueNativeOverlappedPortableCore(overlapped); + + [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Use ThreadPool.BindHandle(SafeHandle) instead.")] + [SupportedOSPlatform("windows")] + public static bool BindHandle(IntPtr osHandle) => + ThreadPool.UseWindowsThreadPool ? + WindowsThreadPool.BindHandle(osHandle) : + BindHandlePortableCore(osHandle); + + [SupportedOSPlatform("windows")] + public static bool BindHandle(SafeHandle osHandle) => + ThreadPool.UseWindowsThreadPool ? + WindowsThreadPool.BindHandle(osHandle) : + BindHandlePortableCore(osHandle); + +#if !CORECLR + internal static bool EnsureConfigInitialized() => true; +#endif + + internal static void InitializeForThreadPoolThread() + { + if (ThreadPool.UseWindowsThreadPool) + { + WindowsThreadPool.InitializeForThreadPoolThread(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void IncrementCompletedWorkItemCount() => WindowsThreadPool.IncrementCompletedWorkItemCount(); + + internal static object GetOrCreateThreadLocalCompletionCountObject() => + ThreadPool.UseWindowsThreadPool ? + WindowsThreadPool.GetOrCreateThreadLocalCompletionCountObject() : + PortableThreadPool.ThreadPoolInstance.GetOrCreateThreadLocalCompletionCountObject(); + + public static bool SetMaxThreads(int workerThreads, int completionPortThreads) => + ThreadPool.UseWindowsThreadPool ? + WindowsThreadPool.SetMaxThreads(workerThreads, completionPortThreads) : + PortableThreadPool.ThreadPoolInstance.SetMaxThreads(workerThreads, completionPortThreads); + + public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) + { + if (ThreadPool.UseWindowsThreadPool) + { + WindowsThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads); + } + else + { + PortableThreadPool.ThreadPoolInstance.GetMaxThreads(out workerThreads, out completionPortThreads); + } + } + + public static bool SetMinThreads(int workerThreads, int completionPortThreads) => + ThreadPool.UseWindowsThreadPool ? + WindowsThreadPool.SetMinThreads(workerThreads, completionPortThreads) : + PortableThreadPool.ThreadPoolInstance.SetMinThreads(workerThreads, completionPortThreads); + + public static void GetMinThreads(out int workerThreads, out int completionPortThreads) + { + if (ThreadPool.UseWindowsThreadPool) + { + WindowsThreadPool.GetMinThreads(out workerThreads, out completionPortThreads); + } + else + { + PortableThreadPool.ThreadPoolInstance.GetMinThreads(out workerThreads, out completionPortThreads); + } + } + + public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) + { + if (ThreadPool.UseWindowsThreadPool) + { + WindowsThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads); + } + else + { + PortableThreadPool.ThreadPoolInstance.GetAvailableThreads(out workerThreads, out completionPortThreads); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void NotifyWorkItemProgress() + { + if (ThreadPool.UseWindowsThreadPool) + { + WindowsThreadPool.NotifyWorkItemProgress(); + } + else + { + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemProgress(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool NotifyWorkItemComplete(object threadLocalCompletionCountObject, int currentTimeMs) => + ThreadPool.UseWindowsThreadPool ? + WindowsThreadPool.NotifyWorkItemComplete(threadLocalCompletionCountObject, currentTimeMs) : + PortableThreadPool.ThreadPoolInstance.NotifyWorkItemComplete(threadLocalCompletionCountObject, currentTimeMs); + + internal static bool NotifyThreadBlocked() => + ThreadPool.UseWindowsThreadPool ? + WindowsThreadPool.NotifyThreadBlocked() : + PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked(); + + internal static void NotifyThreadUnblocked() + { + if (ThreadPool.UseWindowsThreadPool) + { + WindowsThreadPool.NotifyThreadUnblocked(); + } + else + { + PortableThreadPool.ThreadPoolInstance.NotifyThreadUnblocked(); + } + } + + /// + /// This method is called to request a new thread pool worker to handle pending work. + /// + internal static unsafe void RequestWorkerThread() + { + if (ThreadPool.UseWindowsThreadPool) + { + WindowsThreadPool.RequestWorkerThread(); + } + else + { + PortableThreadPool.ThreadPoolInstance.RequestWorker(); + } + } + + internal static void ReportThreadStatus(bool isWorking) + { + Debug.Assert(!ThreadPool.UseWindowsThreadPool); + PortableThreadPool.ThreadPoolInstance.ReportThreadStatus(isWorking); + } + + /// + /// Gets the number of thread pool threads that currently exist. + /// + /// + /// For a thread pool implementation that may have different types of threads, the count includes all types. + /// + public static int ThreadCount + { + get + { + return ThreadPool.UseWindowsThreadPool ? WindowsThreadPool.ThreadCount : PortableThreadPool.ThreadPoolInstance.ThreadCount; + } + } + + /// + /// Gets the number of work items that have been processed so far. + /// + /// + /// For a thread pool implementation that may have different types of work items, the count includes all types. + /// + public static long CompletedWorkItemCount + { + get + { + return ThreadPool.UseWindowsThreadPool ? WindowsThreadPool.CompletedWorkItemCount : PortableThreadPool.ThreadPoolInstance.CompletedWorkItemCount; + } + } + + private static RegisteredWaitHandle RegisterWaitForSingleObject( + WaitHandle waitObject, + WaitOrTimerCallback callBack, + object? state, + uint millisecondsTimeOutInterval, + bool executeOnlyOnce, + bool flowExecutionContext) + { + if (ThreadPool.UseWindowsThreadPool) + { + return WindowsThreadPool.RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, flowExecutionContext); + } + else + { + return PortableThreadPool.RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, flowExecutionContext); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Portable.cs new file mode 100644 index 000000000000..5ce4e29d2ce5 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Portable.cs @@ -0,0 +1,109 @@ +// 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.InteropServices; + +namespace System.Threading +{ + // + // Implementation of ThreadPoolBoundHandle that sits on top of the CLR's ThreadPool and Overlapped infrastructure + // + + /// + /// Represents an I/O handle that is bound to the system thread pool and enables low-level + /// components to receive notifications for asynchronous I/O operations. + /// + public sealed partial class ThreadPoolBoundHandle : IDisposable + { + private unsafe NativeOverlapped* AllocateNativeOverlappedPortableCore(IOCompletionCallback callback, object? state, object? pinData) => + AllocateNativeOverlappedPortableCore(callback, state, pinData, flowExecutionContext: true); + + private unsafe NativeOverlapped* UnsafeAllocateNativeOverlappedPortableCore(IOCompletionCallback callback, object? state, object? pinData) => + AllocateNativeOverlappedPortableCore(callback, state, pinData, flowExecutionContext: false); + + private unsafe NativeOverlapped* AllocateNativeOverlappedPortableCore(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext) + { + ArgumentNullException.ThrowIfNull(callback); + ObjectDisposedException.ThrowIf(_isDisposed, this); + + ThreadPoolBoundHandleOverlapped overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, preAllocated: null, flowExecutionContext); + overlapped._boundHandle = this; + return overlapped._nativeOverlapped; + } + + private unsafe NativeOverlapped* AllocateNativeOverlappedPortableCore(PreAllocatedOverlapped preAllocated) + { + ArgumentNullException.ThrowIfNull(preAllocated); + ObjectDisposedException.ThrowIf(_isDisposed, this); + + preAllocated.AddRef(); + try + { + ThreadPoolBoundHandleOverlapped overlapped = preAllocated._overlappedPortableCore!; + + if (overlapped._boundHandle != null) + throw new ArgumentException(SR.Argument_PreAllocatedAlreadyAllocated, nameof(preAllocated)); + + overlapped._boundHandle = this; + + return overlapped._nativeOverlapped; + } + catch + { + preAllocated.Release(); + throw; + } + } + + private unsafe void FreeNativeOverlappedPortableCore(NativeOverlapped* overlapped) + { + ArgumentNullException.ThrowIfNull(overlapped); + + // Note: we explicitly allow FreeNativeOverlapped calls after the ThreadPoolBoundHandle has been Disposed. + + ThreadPoolBoundHandleOverlapped wrapper = GetOverlappedWrapper(overlapped); + + if (wrapper._boundHandle != this) + throw new ArgumentException(SR.Argument_NativeOverlappedWrongBoundHandle, nameof(overlapped)); + + if (wrapper._preAllocated != null) + wrapper._preAllocated.Release(); + else + Overlapped.Free(overlapped); + } + + private static unsafe object? GetNativeOverlappedStatePortableCore(NativeOverlapped* overlapped) + { + ArgumentNullException.ThrowIfNull(overlapped); + + ThreadPoolBoundHandleOverlapped wrapper = GetOverlappedWrapper(overlapped); + Debug.Assert(wrapper._boundHandle != null); + return wrapper._userState; + } + + private static unsafe ThreadPoolBoundHandleOverlapped GetOverlappedWrapper(NativeOverlapped* overlapped) + { + ThreadPoolBoundHandleOverlapped wrapper; + try + { + wrapper = (ThreadPoolBoundHandleOverlapped)Overlapped.Unpack(overlapped); + } + catch (NullReferenceException ex) + { + throw new ArgumentException(SR.Argument_NativeOverlappedAlreadyFree, nameof(overlapped), ex); + } + + return wrapper; + } + + private void DisposePortableCore() + { + // .NET Native's version of ThreadPoolBoundHandle that wraps the Win32 ThreadPool holds onto + // native resources so it needs to be disposable. To match the contract, we are also disposable. + // We also implement a disposable state to mimic behavior between this implementation and + // .NET Native's version (code written against us, will also work against .NET Native's version). + _isDisposed = true; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Unix.cs index 6f809959dbca..9643edf7f2d8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Unix.cs @@ -6,15 +6,250 @@ namespace System.Threading { - public sealed partial class ThreadPoolBoundHandle + // + // Implementation of ThreadPoolBoundHandle that sits on top of the CLR's ThreadPool and Overlapped infrastructure + // + + /// + /// Represents an I/O handle that is bound to the system thread pool and enables low-level + /// components to receive notifications for asynchronous I/O operations. + /// + public sealed partial class ThreadPoolBoundHandle : IDisposable { - private static ThreadPoolBoundHandle BindHandleCore(SafeHandle handle) + private readonly SafeHandle _handle; + private bool _isDisposed; + + private ThreadPoolBoundHandle(SafeHandle handle) { - Debug.Assert(handle != null); - Debug.Assert(!handle.IsClosed); - Debug.Assert(!handle.IsInvalid); + _handle = handle; + } + + /// + /// Gets the bound operating system handle. + /// + /// + /// A object that holds the bound operating system handle. + /// + public SafeHandle Handle => _handle; + + /// + /// Returns a for the specific handle, + /// which is bound to the system thread pool. + /// + /// + /// A object that holds the operating system handle. The + /// handle must have been opened for overlapped I/O on the unmanaged side. + /// + /// + /// for , which + /// is bound to the system thread pool. + /// + /// + /// is . + /// + /// + /// has been disposed. + /// + /// -or- + /// + /// does not refer to a valid I/O handle. + /// + /// -or- + /// + /// refers to a handle that has not been opened + /// for overlapped I/O. + /// + /// -or- + /// + /// refers to a handle that has already been bound. + /// + /// + /// This method should be called once per handle. + /// + /// -or- + /// + /// does not take ownership of , + /// it remains the responsibility of the caller to call . + /// + public static ThreadPoolBoundHandle BindHandle(SafeHandle handle) + { + ArgumentNullException.ThrowIfNull(handle); + + if (handle.IsClosed || handle.IsInvalid) + throw new ArgumentException(SR.Argument_InvalidHandle, nameof(handle)); throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO); } + + /// + /// Returns an unmanaged pointer to a structure, specifying + /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided + /// object providing context, and managed objects that serve as buffers. + /// + /// + /// An delegate that represents the callback method + /// invoked when the asynchronous I/O operation completes. + /// + /// + /// A user-provided object that distinguishes this from other + /// instances. Can be . + /// + /// + /// An object or array of objects representing the input or output buffer for the operation. Each + /// object represents a buffer, for example an array of bytes. Can be . + /// + /// + /// An unmanaged pointer to a structure. + /// + /// + /// + /// The unmanaged pointer returned by this method can be passed to the operating system in + /// overlapped I/O operations. The structure is fixed in + /// physical memory until is called. + /// + /// + /// The buffer or buffers specified in must be the same as those passed + /// to the unmanaged operating system function that performs the asynchronous I/O. + /// + /// + /// The buffers specified in are pinned for the duration of + /// the I/O operation. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) => + AllocateNativeOverlappedPortableCore(callback, state, pinData); + + /// + /// Returns an unmanaged pointer to a structure, specifying + /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided + /// object providing context, and managed objects that serve as buffers. + /// + /// + /// An delegate that represents the callback method + /// invoked when the asynchronous I/O operation completes. + /// + /// + /// A user-provided object that distinguishes this from other + /// instances. Can be . + /// + /// + /// An object or array of objects representing the input or output buffer for the operation. Each + /// object represents a buffer, for example an array of bytes. Can be . + /// + /// + /// An unmanaged pointer to a structure. + /// + /// + /// + /// The unmanaged pointer returned by this method can be passed to the operating system in + /// overlapped I/O operations. The structure is fixed in + /// physical memory until is called. + /// + /// + /// The buffer or buffers specified in must be the same as those passed + /// to the unmanaged operating system function that performs the asynchronous I/O. + /// + /// + /// is not flowed to the invocation of the callback. + /// + /// + /// The buffers specified in are pinned for the duration of + /// the I/O operation. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) => + UnsafeAllocateNativeOverlappedPortableCore(callback, state, pinData); + + /// + /// Returns an unmanaged pointer to a structure, using the callback, + /// state, and buffers associated with the specified object. + /// + /// + /// A object from which to create the NativeOverlapped pointer. + /// + /// + /// An unmanaged pointer to a structure. + /// + /// + /// + /// The unmanaged pointer returned by this method can be passed to the operating system in + /// overlapped I/O operations. The structure is fixed in + /// physical memory until is called. + /// + /// + /// + /// is . + /// + /// + /// is currently in use for another I/O operation. + /// + /// + /// This method was called after the was disposed, or + /// this method was called after was disposed. + /// + /// + [CLSCompliant(false)] + public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated) => AllocateNativeOverlappedPortableCore(preAllocated); + + /// + /// Frees the unmanaged memory associated with a structure + /// allocated by the method. + /// + /// + /// An unmanaged pointer to the structure to be freed. + /// + /// + /// + /// You must call the method exactly once + /// on every unmanaged pointer allocated using the + /// method. + /// If you do not call the method, you will + /// leak memory. If you call the method more + /// than once on the same unmanaged pointer, memory will be corrupted. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped) => FreeNativeOverlappedPortableCore(overlapped); + + /// + /// Returns the user-provided object specified when the instance was + /// allocated using the . + /// + /// + /// An unmanaged pointer to the structure from which to return the + /// associated user-provided object. + /// + /// + /// A user-provided object that distinguishes this + /// from other instances, otherwise, if one was + /// not specified when the instance was allocated using . + /// + /// + /// is . + /// + [CLSCompliant(false)] + public static unsafe object? GetNativeOverlappedState(NativeOverlapped* overlapped) => GetNativeOverlappedStatePortableCore(overlapped); + + public void Dispose() => DisposePortableCore(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs index f80107fcf1a6..97d77dd4f206 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.Windows.cs @@ -1,18 +1,54 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.Win32.SafeHandles; namespace System.Threading { - public sealed partial class ThreadPoolBoundHandle + /// + /// Represents an I/O handle that is bound to the system thread pool and enables low-level + /// components to receive notifications for asynchronous I/O operations. + /// + public sealed partial class ThreadPoolBoundHandle : IDisposable, IDeferredDisposable { + private readonly SafeHandle _handle; + private readonly SafeThreadPoolIOHandle? _threadPoolHandle; + private DeferredDisposableLifetime _lifetime; + private bool _isDisposed; + + private ThreadPoolBoundHandle(SafeHandle handle, SafeThreadPoolIOHandle threadPoolHandle) + { + _threadPoolHandle = threadPoolHandle; + _handle = handle; + } + + private ThreadPoolBoundHandle(SafeHandle handle) + { + Debug.Assert(!ThreadPool.UseWindowsThreadPool); + + _handle = handle; + GC.SuppressFinalize(this); + } + + /// + /// Gets the bound operating system handle. + /// + /// + /// A object that holds the bound operating system handle. + /// + public SafeHandle Handle => _handle; + private static ThreadPoolBoundHandle BindHandleCore(SafeHandle handle) { - Debug.Assert(handle != null); - Debug.Assert(!handle.IsClosed); - Debug.Assert(!handle.IsInvalid); + ArgumentNullException.ThrowIfNull(handle); + + if (handle.IsClosed || handle.IsInvalid) + throw new ArgumentException(SR.Argument_InvalidHandle, nameof(handle)); try { @@ -37,5 +73,262 @@ private static ThreadPoolBoundHandle BindHandleCore(SafeHandle handle) return new ThreadPoolBoundHandle(handle); } + + /// + /// Returns a for the specific handle, + /// which is bound to the system thread pool. + /// + /// + /// A object that holds the operating system handle. The + /// handle must have been opened for overlapped I/O on the unmanaged side. + /// + /// + /// for , which + /// is bound to the system thread pool. + /// + /// + /// is . + /// + /// + /// has been disposed. + /// + /// -or- + /// + /// does not refer to a valid I/O handle. + /// + /// -or- + /// + /// refers to a handle that has not been opened + /// for overlapped I/O. + /// + /// -or- + /// + /// refers to a handle that has already been bound. + /// + /// + /// This method should be called once per handle. + /// + /// -or- + /// + /// does not take ownership of , + /// it remains the responsibility of the caller to call . + /// + public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle) => + ThreadPool.UseWindowsThreadPool ? BindHandleWindowsThreadPool(handle) : BindHandleCore(handle); + + /// + /// Returns an unmanaged pointer to a structure, specifying + /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided + /// object providing context, and managed objects that serve as buffers. + /// + /// + /// An delegate that represents the callback method + /// invoked when the asynchronous I/O operation completes. + /// + /// + /// A user-provided object that distinguishes this from other + /// instances. Can be . + /// + /// + /// An object or array of objects representing the input or output buffer for the operation. Each + /// object represents a buffer, for example an array of bytes. Can be . + /// + /// + /// An unmanaged pointer to a structure. + /// + /// + /// + /// The unmanaged pointer returned by this method can be passed to the operating system in + /// overlapped I/O operations. The structure is fixed in + /// physical memory until is called. + /// + /// + /// The buffer or buffers specified in must be the same as those passed + /// to the unmanaged operating system function that performs the asynchronous I/O. + /// + /// + /// The buffers specified in are pinned for the duration of + /// the I/O operation. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) => + ThreadPool.UseWindowsThreadPool ? + AllocateNativeOverlappedWindowsThreadPool(callback, state, pinData) : + AllocateNativeOverlappedPortableCore(callback, state, pinData); + + /// + /// Returns an unmanaged pointer to a structure, specifying + /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided + /// object providing context, and managed objects that serve as buffers. + /// + /// + /// An delegate that represents the callback method + /// invoked when the asynchronous I/O operation completes. + /// + /// + /// A user-provided object that distinguishes this from other + /// instances. Can be . + /// + /// + /// An object or array of objects representing the input or output buffer for the operation. Each + /// object represents a buffer, for example an array of bytes. Can be . + /// + /// + /// An unmanaged pointer to a structure. + /// + /// + /// + /// The unmanaged pointer returned by this method can be passed to the operating system in + /// overlapped I/O operations. The structure is fixed in + /// physical memory until is called. + /// + /// + /// The buffer or buffers specified in must be the same as those passed + /// to the unmanaged operating system function that performs the asynchronous I/O. + /// + /// + /// is not flowed to the invocation of the callback. + /// + /// + /// The buffers specified in are pinned for the duration of + /// the I/O operation. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) => + ThreadPool.UseWindowsThreadPool ? + UnsafeAllocateNativeOverlappedWindowsThreadPool(callback, state, pinData) : + UnsafeAllocateNativeOverlappedPortableCore(callback, state, pinData); + + /// + /// Returns an unmanaged pointer to a structure, using the callback, + /// state, and buffers associated with the specified object. + /// + /// + /// A object from which to create the NativeOverlapped pointer. + /// + /// + /// An unmanaged pointer to a structure. + /// + /// + /// + /// The unmanaged pointer returned by this method can be passed to the operating system in + /// overlapped I/O operations. The structure is fixed in + /// physical memory until is called. + /// + /// + /// + /// is . + /// + /// + /// is currently in use for another I/O operation. + /// + /// + /// This method was called after the was disposed, or + /// this method was called after was disposed. + /// + /// + [CLSCompliant(false)] + public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated) => + ThreadPool.UseWindowsThreadPool ? + AllocateNativeOverlappedWindowsThreadPool(preAllocated) : + AllocateNativeOverlappedPortableCore(preAllocated); + + /// + /// Frees the unmanaged memory associated with a structure + /// allocated by the method. + /// + /// + /// An unmanaged pointer to the structure to be freed. + /// + /// + /// + /// You must call the method exactly once + /// on every unmanaged pointer allocated using the + /// method. + /// If you do not call the method, you will + /// leak memory. If you call the method more + /// than once on the same unmanaged pointer, memory will be corrupted. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped) + { + if (ThreadPool.UseWindowsThreadPool) + { + FreeNativeOverlappedWindowsThreadPool(overlapped); + } + else + { + FreeNativeOverlappedPortableCore(overlapped); + } + } + + /// + /// Returns the user-provided object specified when the instance was + /// allocated using the . + /// + /// + /// An unmanaged pointer to the structure from which to return the + /// associated user-provided object. + /// + /// + /// A user-provided object that distinguishes this + /// from other instances, otherwise, if one was + /// not specified when the instance was allocated using . + /// + /// + /// is . + /// + [CLSCompliant(false)] + public static unsafe object? GetNativeOverlappedState(NativeOverlapped* overlapped) => + ThreadPool.UseWindowsThreadPool ? + GetNativeOverlappedStateWindowsThreadPool(overlapped) : + GetNativeOverlappedStatePortableCore(overlapped); + + public void Dispose() + { + if (ThreadPool.UseWindowsThreadPool) + { + DisposeWindowsThreadPool(); + } + else + { + DisposePortableCore(); + } + } + + ~ThreadPoolBoundHandle() + { + Debug.Assert(ThreadPool.UseWindowsThreadPool); + FinalizeWindowsThreadPool(); + } + + void IDeferredDisposable.OnFinalRelease(bool disposed) + { + if (ThreadPool.UseWindowsThreadPool) + { + IDeferredDisposableOnFinalReleaseWindowsThreadPool(disposed); + } + } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolBoundHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.WindowsThreadPool.cs similarity index 74% rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolBoundHandle.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.WindowsThreadPool.cs index f6e56c38081f..26098e17f2e8 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolBoundHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.WindowsThreadPool.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Win32.SafeHandles; @@ -13,24 +13,9 @@ namespace System.Threading // // Implementation of ThreadPoolBoundHandle that sits on top of the Win32 ThreadPool // - public sealed class ThreadPoolBoundHandle : IDisposable, IDeferredDisposable + public sealed partial class ThreadPoolBoundHandle : IDisposable, IDeferredDisposable { - private readonly SafeHandle _handle; - private readonly SafeThreadPoolIOHandle _threadPoolHandle; - private DeferredDisposableLifetime _lifetime; - - private ThreadPoolBoundHandle(SafeHandle handle, SafeThreadPoolIOHandle threadPoolHandle) - { - _threadPoolHandle = threadPoolHandle; - _handle = handle; - } - - public SafeHandle Handle - { - get { return _handle; } - } - - public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle) + private static unsafe ThreadPoolBoundHandle BindHandleWindowsThreadPool(SafeHandle handle) { ArgumentNullException.ThrowIfNull(handle); @@ -53,15 +38,13 @@ public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle) return new ThreadPoolBoundHandle(handle, threadPoolHandle); } - [CLSCompliant(false)] - public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) => - AllocateNativeOverlapped(callback, state, pinData, flowExecutionContext: true); + private unsafe NativeOverlapped* AllocateNativeOverlappedWindowsThreadPool(IOCompletionCallback callback, object? state, object? pinData) => + AllocateNativeOverlappedWindowsThreadPool(callback, state, pinData, flowExecutionContext: true); - [CLSCompliant(false)] - public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) => - AllocateNativeOverlapped(callback, state, pinData, flowExecutionContext: false); + private unsafe NativeOverlapped* UnsafeAllocateNativeOverlappedWindowsThreadPool(IOCompletionCallback callback, object? state, object? pinData) => + AllocateNativeOverlappedWindowsThreadPool(callback, state, pinData, flowExecutionContext: false); - private unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object state, object pinData, bool flowExecutionContext) + private unsafe NativeOverlapped* AllocateNativeOverlappedWindowsThreadPool(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext) { ArgumentNullException.ThrowIfNull(callback); @@ -71,7 +54,7 @@ public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle) Win32ThreadPoolNativeOverlapped* overlapped = Win32ThreadPoolNativeOverlapped.Allocate(callback, state, pinData, preAllocated: null, flowExecutionContext); overlapped->Data._boundHandle = this; - Interop.Kernel32.StartThreadpoolIo(_threadPoolHandle); + Interop.Kernel32.StartThreadpoolIo(_threadPoolHandle!); return Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(overlapped); } @@ -82,8 +65,7 @@ public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle) } } - [CLSCompliant(false)] - public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated) + private unsafe NativeOverlapped* AllocateNativeOverlappedWindowsThreadPool(PreAllocatedOverlapped preAllocated) { ArgumentNullException.ThrowIfNull(preAllocated); @@ -94,15 +76,15 @@ public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle) addedRefToThis = AddRef(); addedRefToPreAllocated = preAllocated.AddRef(); - Win32ThreadPoolNativeOverlapped.OverlappedData data = preAllocated._overlapped->Data; + Win32ThreadPoolNativeOverlapped.OverlappedData data = preAllocated._overlappedWindowsThreadPool->Data; if (data._boundHandle != null) throw new ArgumentException(SR.Argument_PreAllocatedAlreadyAllocated, nameof(preAllocated)); data._boundHandle = this; - Interop.Kernel32.StartThreadpoolIo(_threadPoolHandle); + Interop.Kernel32.StartThreadpoolIo(_threadPoolHandle!); - return Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(preAllocated._overlapped); + return Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(preAllocated._overlappedWindowsThreadPool); } catch { @@ -114,8 +96,7 @@ public static unsafe ThreadPoolBoundHandle BindHandle(SafeHandle handle) } } - [CLSCompliant(false)] - public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped) + private unsafe void FreeNativeOverlappedWindowsThreadPool(NativeOverlapped* overlapped) { ArgumentNullException.ThrowIfNull(overlapped); @@ -124,7 +105,7 @@ public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped) if (!data._completed) { - Interop.Kernel32.CancelThreadpoolIo(_threadPoolHandle); + Interop.Kernel32.CancelThreadpoolIo(_threadPoolHandle!); Release(); } @@ -137,8 +118,7 @@ public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped) Win32ThreadPoolNativeOverlapped.Free(threadPoolOverlapped); } - [CLSCompliant(false)] - public static unsafe object GetNativeOverlappedState(NativeOverlapped* overlapped) + private static unsafe object? GetNativeOverlappedStateWindowsThreadPool(NativeOverlapped* overlapped) { ArgumentNullException.ThrowIfNull(overlapped); @@ -148,7 +128,7 @@ public static unsafe object GetNativeOverlappedState(NativeOverlapped* overlappe return data._state; } - private static unsafe Win32ThreadPoolNativeOverlapped.OverlappedData GetOverlappedData(Win32ThreadPoolNativeOverlapped* overlapped, ThreadPoolBoundHandle expectedBoundHandle) + private static unsafe Win32ThreadPoolNativeOverlapped.OverlappedData GetOverlappedData(Win32ThreadPoolNativeOverlapped* overlapped, ThreadPoolBoundHandle? expectedBoundHandle) { Win32ThreadPoolNativeOverlapped.OverlappedData data = overlapped->Data; @@ -165,9 +145,10 @@ private static unsafe Win32ThreadPoolNativeOverlapped.OverlappedData GetOverlapp private static unsafe void OnNativeIOCompleted(IntPtr instance, IntPtr context, IntPtr overlappedPtr, uint ioResult, nuint numberOfBytesTransferred, IntPtr ioPtr) { var wrapper = ThreadPoolCallbackWrapper.Enter(); + Win32ThreadPoolNativeOverlapped* overlapped = (Win32ThreadPoolNativeOverlapped*)overlappedPtr; - ThreadPoolBoundHandle boundHandle = overlapped->Data._boundHandle; + ThreadPoolBoundHandle? boundHandle = overlapped->Data._boundHandle; if (boundHandle == null) throw new InvalidOperationException(SR.Argument_NativeOverlappedAlreadyFree); @@ -175,6 +156,7 @@ private static unsafe void OnNativeIOCompleted(IntPtr instance, IntPtr context, Win32ThreadPoolNativeOverlapped.CompleteWithCallback(ioResult, (uint)numberOfBytesTransferred, overlapped); ThreadPool.IncrementCompletedWorkItemCount(); + wrapper.Exit(); } @@ -188,13 +170,13 @@ private void Release() _lifetime.Release(this); } - public void Dispose() + private void DisposeWindowsThreadPool() { _lifetime.Dispose(this); GC.SuppressFinalize(this); } - ~ThreadPoolBoundHandle() + private void FinalizeWindowsThreadPool() { // // During shutdown, don't automatically clean up, because this instance may still be @@ -204,10 +186,10 @@ public void Dispose() Dispose(); } - void IDeferredDisposable.OnFinalRelease(bool disposed) + private void IDeferredDisposableOnFinalReleaseWindowsThreadPool(bool disposed) { if (disposed) - _threadPoolHandle.Dispose(); + _threadPoolHandle!.Dispose(); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.cs deleted file mode 100644 index e4ec1fb94149..000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.cs +++ /dev/null @@ -1,331 +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; -using System.Runtime.InteropServices; - -namespace System.Threading -{ - // - // Implementation of ThreadPoolBoundHandle that sits on top of the CLR's ThreadPool and Overlapped infrastructure - // - - /// - /// Represents an I/O handle that is bound to the system thread pool and enables low-level - /// components to receive notifications for asynchronous I/O operations. - /// - public sealed partial class ThreadPoolBoundHandle : IDisposable - { - private readonly SafeHandle _handle; - private bool _isDisposed; - - private ThreadPoolBoundHandle(SafeHandle handle) - { - _handle = handle; - } - - /// - /// Gets the bound operating system handle. - /// - /// - /// A object that holds the bound operating system handle. - /// - public SafeHandle Handle => _handle; - - /// - /// Returns a for the specific handle, - /// which is bound to the system thread pool. - /// - /// - /// A object that holds the operating system handle. The - /// handle must have been opened for overlapped I/O on the unmanaged side. - /// - /// - /// for , which - /// is bound to the system thread pool. - /// - /// - /// is . - /// - /// - /// has been disposed. - /// - /// -or- - /// - /// does not refer to a valid I/O handle. - /// - /// -or- - /// - /// refers to a handle that has not been opened - /// for overlapped I/O. - /// - /// -or- - /// - /// refers to a handle that has already been bound. - /// - /// - /// This method should be called once per handle. - /// - /// -or- - /// - /// does not take ownership of , - /// it remains the responsibility of the caller to call . - /// - public static ThreadPoolBoundHandle BindHandle(SafeHandle handle) - { - ArgumentNullException.ThrowIfNull(handle); - - if (handle.IsClosed || handle.IsInvalid) - throw new ArgumentException(SR.Argument_InvalidHandle, nameof(handle)); - - return BindHandleCore(handle); - } - - /// - /// Returns an unmanaged pointer to a structure, specifying - /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided - /// object providing context, and managed objects that serve as buffers. - /// - /// - /// An delegate that represents the callback method - /// invoked when the asynchronous I/O operation completes. - /// - /// - /// A user-provided object that distinguishes this from other - /// instances. Can be . - /// - /// - /// An object or array of objects representing the input or output buffer for the operation. Each - /// object represents a buffer, for example an array of bytes. Can be . - /// - /// - /// An unmanaged pointer to a structure. - /// - /// - /// - /// The unmanaged pointer returned by this method can be passed to the operating system in - /// overlapped I/O operations. The structure is fixed in - /// physical memory until is called. - /// - /// - /// The buffer or buffers specified in must be the same as those passed - /// to the unmanaged operating system function that performs the asynchronous I/O. - /// - /// - /// The buffers specified in are pinned for the duration of - /// the I/O operation. - /// - /// - /// - /// is . - /// - /// - /// This method was called after the was disposed. - /// - [CLSCompliant(false)] - public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) => - AllocateNativeOverlapped(callback, state, pinData, flowExecutionContext: true); - - /// - /// Returns an unmanaged pointer to a structure, specifying - /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided - /// object providing context, and managed objects that serve as buffers. - /// - /// - /// An delegate that represents the callback method - /// invoked when the asynchronous I/O operation completes. - /// - /// - /// A user-provided object that distinguishes this from other - /// instances. Can be . - /// - /// - /// An object or array of objects representing the input or output buffer for the operation. Each - /// object represents a buffer, for example an array of bytes. Can be . - /// - /// - /// An unmanaged pointer to a structure. - /// - /// - /// - /// The unmanaged pointer returned by this method can be passed to the operating system in - /// overlapped I/O operations. The structure is fixed in - /// physical memory until is called. - /// - /// - /// The buffer or buffers specified in must be the same as those passed - /// to the unmanaged operating system function that performs the asynchronous I/O. - /// - /// - /// is not flowed to the invocation of the callback. - /// - /// - /// The buffers specified in are pinned for the duration of - /// the I/O operation. - /// - /// - /// - /// is . - /// - /// - /// This method was called after the was disposed. - /// - [CLSCompliant(false)] - public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) => - AllocateNativeOverlapped(callback, state, pinData, flowExecutionContext: false); - - private unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext) - { - ArgumentNullException.ThrowIfNull(callback); - ObjectDisposedException.ThrowIf(_isDisposed, this); - - ThreadPoolBoundHandleOverlapped overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, preAllocated: null, flowExecutionContext); - overlapped._boundHandle = this; - return overlapped._nativeOverlapped; - } - - /// - /// Returns an unmanaged pointer to a structure, using the callback, - /// state, and buffers associated with the specified object. - /// - /// - /// A object from which to create the NativeOverlapped pointer. - /// - /// - /// An unmanaged pointer to a structure. - /// - /// - /// - /// The unmanaged pointer returned by this method can be passed to the operating system in - /// overlapped I/O operations. The structure is fixed in - /// physical memory until is called. - /// - /// - /// - /// is . - /// - /// - /// is currently in use for another I/O operation. - /// - /// - /// This method was called after the was disposed, or - /// this method was called after was disposed. - /// - /// - [CLSCompliant(false)] - public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated) - { - ArgumentNullException.ThrowIfNull(preAllocated); - ObjectDisposedException.ThrowIf(_isDisposed, this); - - preAllocated.AddRef(); - try - { - ThreadPoolBoundHandleOverlapped overlapped = preAllocated._overlapped; - - if (overlapped._boundHandle != null) - throw new ArgumentException(SR.Argument_PreAllocatedAlreadyAllocated, nameof(preAllocated)); - - overlapped._boundHandle = this; - - return overlapped._nativeOverlapped; - } - catch - { - preAllocated.Release(); - throw; - } - } - - /// - /// Frees the unmanaged memory associated with a structure - /// allocated by the method. - /// - /// - /// An unmanaged pointer to the structure to be freed. - /// - /// - /// - /// You must call the method exactly once - /// on every unmanaged pointer allocated using the - /// method. - /// If you do not call the method, you will - /// leak memory. If you call the method more - /// than once on the same unmanaged pointer, memory will be corrupted. - /// - /// - /// - /// is . - /// - /// - /// This method was called after the was disposed. - /// - [CLSCompliant(false)] - public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped) - { - ArgumentNullException.ThrowIfNull(overlapped); - - // Note: we explicitly allow FreeNativeOverlapped calls after the ThreadPoolBoundHandle has been Disposed. - - ThreadPoolBoundHandleOverlapped wrapper = GetOverlappedWrapper(overlapped); - - if (wrapper._boundHandle != this) - throw new ArgumentException(SR.Argument_NativeOverlappedWrongBoundHandle, nameof(overlapped)); - - if (wrapper._preAllocated != null) - wrapper._preAllocated.Release(); - else - Overlapped.Free(overlapped); - } - - /// - /// Returns the user-provided object specified when the instance was - /// allocated using the . - /// - /// - /// An unmanaged pointer to the structure from which to return the - /// associated user-provided object. - /// - /// - /// A user-provided object that distinguishes this - /// from other instances, otherwise, if one was - /// not specified when the instance was allocated using . - /// - /// - /// is . - /// - [CLSCompliant(false)] - public static unsafe object? GetNativeOverlappedState(NativeOverlapped* overlapped) - { - ArgumentNullException.ThrowIfNull(overlapped); - - ThreadPoolBoundHandleOverlapped wrapper = GetOverlappedWrapper(overlapped); - Debug.Assert(wrapper._boundHandle != null); - return wrapper._userState; - } - - private static unsafe ThreadPoolBoundHandleOverlapped GetOverlappedWrapper(NativeOverlapped* overlapped) - { - ThreadPoolBoundHandleOverlapped wrapper; - try - { - wrapper = (ThreadPoolBoundHandleOverlapped)Overlapped.Unpack(overlapped); - } - catch (NullReferenceException ex) - { - throw new ArgumentException(SR.Argument_NativeOverlappedAlreadyFree, nameof(overlapped), ex); - } - - return wrapper; - } - - public void Dispose() - { - // .NET Native's version of ThreadPoolBoundHandle that wraps the Win32 ThreadPool holds onto - // native resources so it needs to be disposable. To match the contract, we are also disposable. - // We also implement a disposable state to mimic behavior between this implementation and - // .NET Native's version (code written against us, will also work against .NET Native's version). - _isDisposed = true; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.cs new file mode 100644 index 000000000000..664049c61ed4 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolCallbackWrapper.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Threading +{ + internal struct ThreadPoolCallbackWrapper + { + private Thread _currentThread; + + public static ThreadPoolCallbackWrapper Enter() + { + Thread currentThread = Thread.CurrentThread; + if (!currentThread.IsThreadPoolThread) + { + currentThread.IsThreadPoolThread = true; +#if TARGET_WINDOWS + // when using the Windows Threadpool, this is needed to increment the ThreadCount + ThreadPool.InitializeForThreadPoolThread(); +#endif + } + return new ThreadPoolCallbackWrapper + { + _currentThread = currentThread, + }; + } + + public void Exit(bool resetThread = true) + { + if (resetThread) + { + _currentThread.ResetThreadPoolThread(); + } + } + } +} 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 597cd4b46f2b..afdb8fb7d6bc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs @@ -935,7 +935,7 @@ internal static bool Dispatch() // us to return the thread to the pool or not. // int currentTickCount = Environment.TickCount; - if (!ThreadPool.NotifyWorkItemComplete(threadLocalCompletionCountObject, currentTickCount)) + 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. Tell the caller that this thread was requested to stop 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 40983768c823..346259aa412c 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 @@ -20,15 +20,10 @@ internal sealed partial class TimerQueue : IThreadPoolWorkItem /// private static readonly AutoResetEvent s_timerEvent = new AutoResetEvent(false); + // this means that it's in the s_scheduledTimers collection, not that it's the one which would run on the next TimeoutCallback private bool _isScheduled; private long _scheduledDueTimeMs; -#pragma warning disable IDE0060 - private TimerQueue(int id) - { - } -#pragma warning restore IDE0060 - private static List InitializeScheduledTimerManager_Locked() { Debug.Assert(s_scheduledTimers == null); @@ -50,7 +45,7 @@ private static List InitializeScheduledTimerManager_Locked() return timers; } - private bool SetTimer(uint actualDuration) + private bool SetTimerPortable(uint actualDuration) { Debug.Assert((int)actualDuration >= 0); long dueTimeMs = TickCount64 + (int)actualDuration; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Unix.cs index 005fa12ca6f2..140d136a12cb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Unix.cs @@ -6,5 +6,13 @@ namespace System.Threading internal sealed partial class TimerQueue { private static long TickCount64 => Environment.TickCount64; + +#pragma warning disable IDE0060 + private TimerQueue(int id) + { + } +#pragma warning restore IDE0060 + + private bool SetTimer(uint actualDuration) => SetTimerPortable(actualDuration); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Windows.cs index 228cae763e26..48babb49981e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Windows.cs @@ -7,6 +7,11 @@ namespace System.Threading { internal sealed partial class TimerQueue { + private TimerQueue(int id) + { + _id = id; + } + private static long TickCount64 { get @@ -33,5 +38,10 @@ private static long TickCount64 } } } + + private bool SetTimer(uint actualDuration) => + ThreadPool.UseWindowsThreadPool ? + SetTimerWindowsThreadPool(actualDuration) : + SetTimerPortable(actualDuration); } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.WindowsThreadPool.cs similarity index 79% rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.WindowsThreadPool.cs index 1f11b66ac14a..438e53db4eea 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.WindowsThreadPool.cs @@ -16,13 +16,9 @@ internal partial class TimerQueue private IntPtr _nativeTimer; private readonly int _id; - private TimerQueue(int id) - { - _id = id; - } - +#pragma warning disable IDE0060 // Remove unused parameter [UnmanagedCallersOnly] - private static unsafe void TimerCallback(void* instance, void* context, void* timer) + private static unsafe void TimerCallbackWindowsThreadPool(void* instance, void* context, void* timer) { int id = (int)context; var wrapper = ThreadPoolCallbackWrapper.Enter(); @@ -30,12 +26,13 @@ private static unsafe void TimerCallback(void* instance, void* context, void* ti ThreadPool.IncrementCompletedWorkItemCount(); wrapper.Exit(); } +#pragma warning restore IDE0060 - private unsafe bool SetTimer(uint actualDuration) + private unsafe bool SetTimerWindowsThreadPool(uint actualDuration) { if (_nativeTimer == IntPtr.Zero) { - _nativeTimer = Interop.Kernel32.CreateThreadpoolTimer(&TimerCallback, (IntPtr)_id, IntPtr.Zero); + _nativeTimer = Interop.Kernel32.CreateThreadpoolTimer(&TimerCallbackWindowsThreadPool, (IntPtr)_id, IntPtr.Zero); if (_nativeTimer == IntPtr.Zero) throw new OutOfMemoryException(); } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs similarity index 78% rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs index 173a23775987..a3c8567d4bbb 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.ExecutionContextCallbackArgs.cs @@ -5,12 +5,12 @@ namespace System.Threading { internal partial struct Win32ThreadPoolNativeOverlapped { - private unsafe class ExecutionContextCallbackArgs + private sealed unsafe class ExecutionContextCallbackArgs { internal uint _errorCode; internal uint _bytesWritten; internal Win32ThreadPoolNativeOverlapped* _overlapped; - internal OverlappedData _data; + internal OverlappedData? _data; } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.OverlappedData.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.OverlappedData.cs similarity index 89% rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.OverlappedData.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.OverlappedData.cs index 798f11584fbd..065e67fede2e 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.OverlappedData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.OverlappedData.cs @@ -8,13 +8,13 @@ namespace System.Threading { internal partial struct Win32ThreadPoolNativeOverlapped { - internal class OverlappedData + internal sealed class OverlappedData { - internal GCHandle[] _pinnedData; + internal GCHandle[]? _pinnedData; internal IOCompletionCallback? _callback; internal object? _state; internal ExecutionContext? _executionContext; - internal ThreadPoolBoundHandle _boundHandle; + internal ThreadPoolBoundHandle? _boundHandle; internal PreAllocatedOverlapped? _preAllocated; internal bool _completed; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.cs similarity index 92% rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.cs index 26f453b499f2..02abf282c675 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Win32ThreadPoolNativeOverlapped.cs @@ -12,10 +12,10 @@ internal partial struct Win32ThreadPoolNativeOverlapped { // Per-thread cache of the args object, so we don't have to allocate a new one each time. [ThreadStatic] - private static ExecutionContextCallbackArgs t_executionContextCallbackArgs; + private static ExecutionContextCallbackArgs? t_executionContextCallbackArgs; - private static ContextCallback s_executionContextCallback; - private static OverlappedData[] s_dataArray; + private static ContextCallback? s_executionContextCallback; + private static OverlappedData[]? s_dataArray; private static int s_dataCount; // Current number of valid entries in _dataArray private static IntPtr s_freeList; // Lock-free linked stack of free ThreadPoolNativeOverlapped instances. @@ -25,10 +25,10 @@ internal partial struct Win32ThreadPoolNativeOverlapped internal OverlappedData Data { - get { return s_dataArray[_dataIndex]; } + get { return s_dataArray![_dataIndex]; } } - internal static unsafe Win32ThreadPoolNativeOverlapped* Allocate(IOCompletionCallback callback, object state, object pinData, PreAllocatedOverlapped preAllocated, bool flowExecutionControl) + internal static unsafe Win32ThreadPoolNativeOverlapped* Allocate(IOCompletionCallback callback, object? state, object? pinData, PreAllocatedOverlapped? preAllocated, bool flowExecutionControl) { Win32ThreadPoolNativeOverlapped* overlapped = AllocateNew(); try @@ -98,7 +98,7 @@ internal OverlappedData Data // If we haven't stored this object in the array yet, do so now. Then we need to make another pass through // the loop, in case another thread resized the array before we made this update. - if (s_dataArray[dataIndex] == null) + if (s_dataArray![dataIndex] == null) { // Full fence so this write can't move past subsequent reads. Interlocked.Exchange(ref dataArray![dataIndex], data); @@ -112,7 +112,7 @@ internal OverlappedData Data } } - private void SetData(IOCompletionCallback callback, object state, object pinData, PreAllocatedOverlapped preAllocated, bool flowExecutionContext) + private void SetData(IOCompletionCallback callback, object? state, object? pinData, PreAllocatedOverlapped? preAllocated, bool flowExecutionContext) { Debug.Assert(callback != null); @@ -196,12 +196,12 @@ internal static unsafe void CompleteWithCallback(uint errorCode, uint bytesWritt return; } - ContextCallback callback = s_executionContextCallback; + ContextCallback? callback = s_executionContextCallback; if (callback == null) s_executionContextCallback = callback = OnExecutionContextCallback; // Get an args object from the per-thread cache. - ExecutionContextCallbackArgs args = t_executionContextCallbackArgs; + ExecutionContextCallbackArgs? args = t_executionContextCallbackArgs; args ??= new ExecutionContextCallbackArgs(); t_executionContextCallbackArgs = null; @@ -217,12 +217,12 @@ internal static unsafe void CompleteWithCallback(uint errorCode, uint bytesWritt private static unsafe void OnExecutionContextCallback(object? state) { Debug.Assert(state != null); - ExecutionContextCallbackArgs args = (ExecutionContextCallbackArgs)state; + ExecutionContextCallbackArgs args = (ExecutionContextCallbackArgs)state!; uint errorCode = args._errorCode; uint bytesWritten = args._bytesWritten; Win32ThreadPoolNativeOverlapped* overlapped = args._overlapped; - OverlappedData data = args._data; + OverlappedData data = args._data!; // Put the args object back in the per-thread cache, now that we're done with it. args._data = null; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WindowsThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WindowsThreadPool.cs new file mode 100644 index 000000000000..d0637d2fd62f --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WindowsThreadPool.cs @@ -0,0 +1,222 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Win32.SafeHandles; +using System.Diagnostics; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace System.Threading +{ + // + // Windows-specific implementation of ThreadPool + // + internal static class WindowsThreadPool + { + /// + /// The maximum number of threads in the default thread pool on Windows 10 as computed by + /// TppComputeDefaultMaxThreads(TppMaxGlobalPool). + /// + /// + /// Note that Windows 8 and 8.1 used a different value: Math.Max(4 * Environment.ProcessorCount, 512). + /// + private static readonly int MaxThreadCount = Math.Max(8 * Environment.ProcessorCount, 768); + + private static IntPtr s_work; + + private sealed class ThreadCountHolder + { + internal ThreadCountHolder() => Interlocked.Increment(ref s_threadCount); + ~ThreadCountHolder() => Interlocked.Decrement(ref s_threadCount); + } + + [ThreadStatic] + private static ThreadCountHolder? t_threadCountHolder; + private static int s_threadCount; + + [StructLayout(LayoutKind.Sequential)] + private struct WorkingThreadCounter + { + private readonly Internal.PaddingFor32 pad1; + + public volatile int Count; + + private readonly Internal.PaddingFor32 pad2; + } + + // The number of threads executing work items in the Dispatch method + private static WorkingThreadCounter s_workingThreadCounter; + + private static readonly ThreadInt64PersistentCounter s_completedWorkItemCounter = new ThreadInt64PersistentCounter(); + + [ThreadStatic] + private static object? t_completionCountObject; + + internal static void InitializeForThreadPoolThread() => t_threadCountHolder = new ThreadCountHolder(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void IncrementCompletedWorkItemCount() => ThreadInt64PersistentCounter.Increment(GetOrCreateThreadLocalCompletionCountObject()); + + internal static object GetOrCreateThreadLocalCompletionCountObject() => + t_completionCountObject ?? CreateThreadLocalCompletionCountObject(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static object CreateThreadLocalCompletionCountObject() + { + Debug.Assert(t_completionCountObject == null); + + object threadLocalCompletionCountObject = s_completedWorkItemCounter.CreateThreadLocalCountObject(); + t_completionCountObject = threadLocalCompletionCountObject; + return threadLocalCompletionCountObject; + } + +#pragma warning disable IDE0060 // Remove unused parameter + public static bool SetMaxThreads(int workerThreads, int completionPortThreads) + { + // Not supported at present + return false; + } +#pragma warning restore IDE0060 + + public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) + { + // Note that worker threads and completion port threads share the same thread pool. + // The total number of threads cannot exceed MaxThreadCount. + workerThreads = MaxThreadCount; + completionPortThreads = MaxThreadCount; + } + +#pragma warning disable IDE0060 // Remove unused parameter + public static bool SetMinThreads(int workerThreads, int completionPortThreads) + { + // Not supported at present + return false; + } +#pragma warning restore IDE0060 + + public static void GetMinThreads(out int workerThreads, out int completionPortThreads) + { + workerThreads = 0; + completionPortThreads = 0; + } + + public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) + { + // Make sure we return a non-negative value if thread pool defaults are changed + int availableThreads = Math.Max(MaxThreadCount - s_workingThreadCounter.Count, 0); + + workerThreads = availableThreads; + completionPortThreads = availableThreads; + } + + /// + /// Gets the number of thread pool threads that currently exist. + /// + /// + /// For a thread pool implementation that may have different types of threads, the count includes all types. + /// + public static int ThreadCount => s_threadCount; + + /// + /// Gets the number of work items that have been processed so far. + /// + /// + /// For a thread pool implementation that may have different types of work items, the count includes all types. + /// + public static long CompletedWorkItemCount => s_completedWorkItemCounter.Count; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void NotifyWorkItemProgress() => IncrementCompletedWorkItemCount(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool NotifyWorkItemComplete(object threadLocalCompletionCountObject, int _ /*currentTimeMs*/) + { + ThreadInt64PersistentCounter.Increment(threadLocalCompletionCountObject); + return true; + } + + internal static bool NotifyThreadBlocked() { return false; } + internal static void NotifyThreadUnblocked() { } + + [UnmanagedCallersOnly] + private static void DispatchCallback(IntPtr instance, IntPtr context, IntPtr work) + { + var wrapper = ThreadPoolCallbackWrapper.Enter(); + + Debug.Assert(s_work == work); + Interlocked.Increment(ref s_workingThreadCounter.Count); + ThreadPoolWorkQueue.Dispatch(); + Interlocked.Decrement(ref s_workingThreadCounter.Count); + + // We reset the thread after executing each callback + wrapper.Exit(resetThread: false); + } + + internal static unsafe void RequestWorkerThread() + { + if (s_work == IntPtr.Zero) + { + IntPtr work = Interop.Kernel32.CreateThreadpoolWork(&DispatchCallback, IntPtr.Zero, IntPtr.Zero); + if (work == IntPtr.Zero) + throw new OutOfMemoryException(); + + if (Interlocked.CompareExchange(ref s_work, work, IntPtr.Zero) != IntPtr.Zero) + Interop.Kernel32.CloseThreadpoolWork(work); + } + + Interop.Kernel32.SubmitThreadpoolWork(s_work); + } + + internal static RegisteredWaitHandle RegisterWaitForSingleObject( + WaitHandle waitObject, + WaitOrTimerCallback callBack, + object? state, + uint millisecondsTimeOutInterval, + bool executeOnlyOnce, + bool flowExecutionContext) + { + ArgumentNullException.ThrowIfNull(waitObject); + ArgumentNullException.ThrowIfNull(callBack); + + var callbackHelper = new _ThreadPoolWaitOrTimerCallback(callBack, state, flowExecutionContext); + var registeredWaitHandle = new RegisteredWaitHandle(waitObject.SafeWaitHandle, callbackHelper, millisecondsTimeOutInterval, !executeOnlyOnce); + + registeredWaitHandle.RestartWait(); + return registeredWaitHandle; + } + + private static unsafe void NativeOverlappedCallback(nint overlappedPtr) => + IOCompletionCallbackHelper.PerformSingleIOCompletionCallback(0, 0, (NativeOverlapped*)overlappedPtr); + + [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 + overlapped->InternalLow = (IntPtr)0; + // Both types of callbacks are executed on the same thread pool + return ThreadPool.UnsafeQueueUserWorkItem(NativeOverlappedCallback, (nint)overlapped, preferLocal: false); + } + + [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 + } + +#pragma warning disable IDE0060 // Remove unused parameter + [SupportedOSPlatform("windows")] + public static bool BindHandle(SafeHandle osHandle) + { + throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle + } +#pragma warning restore IDE0060 + } +} diff --git a/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj b/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj index 3ec268186391..4fe527aacc03 100644 --- a/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj +++ b/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj @@ -51,6 +51,14 @@ System.Reflection.NullabilityInfoContext.IsSupported + + + System.Threading.ThreadPool.UseWindowsThreadPool + + + System.Threading.ThreadPool.UseWindowsThreadPool + + diff --git a/src/libraries/System.Runtime/tests/TrimmingTests/UseWindowsThreadPoolFalse.cs b/src/libraries/System.Runtime/tests/TrimmingTests/UseWindowsThreadPoolFalse.cs new file mode 100644 index 000000000000..b6f89fc16b55 --- /dev/null +++ b/src/libraries/System.Runtime/tests/TrimmingTests/UseWindowsThreadPoolFalse.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Globalization; +using System.Threading; + +/// +/// Ensures setting UseWindowsThreadPool = false still works in a trimmed app. +/// +class Program +{ + static int Main(string[] args) + { + // SetMinThreads should work when using PortableThreadPool, this call should return true + if (!ThreadPool.SetMinThreads(1, 1)) + { + return -1; + } + + // SetMaxThreads should work when using PortableThreadPool, this call should return true + if (!ThreadPool.SetMaxThreads(10, 10)) + { + return -1; + } + + return 100; + } +} \ No newline at end of file diff --git a/src/libraries/System.Runtime/tests/TrimmingTests/UseWindowsThreadPoolTrue.cs b/src/libraries/System.Runtime/tests/TrimmingTests/UseWindowsThreadPoolTrue.cs new file mode 100644 index 000000000000..f0aeb5632fa7 --- /dev/null +++ b/src/libraries/System.Runtime/tests/TrimmingTests/UseWindowsThreadPoolTrue.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Globalization; +using System.Threading; + +/// +/// Ensures setting UseWindowsThreadPool = true still works in a trimmed app. +/// +class Program +{ + static int Main(string[] args) + { + // SetMinThreads is not supported in WindowsThreadPool class, this call should return false + if (ThreadPool.SetMinThreads(1, 1)) + { + return -1; + } + + // SetMaxThreads is not supported in WindowsThreadPool class, this call should return false + if (ThreadPool.SetMaxThreads(10, 10)) + { + return -1; + } + + return 100; + } +} \ No newline at end of file diff --git a/src/libraries/System.Threading.ThreadPool/System.Threading.ThreadPool.sln b/src/libraries/System.Threading.ThreadPool/System.Threading.ThreadPool.sln index 0dd5540a6fec..4e130a65939c 100644 --- a/src/libraries/System.Threading.ThreadPool/System.Threading.ThreadPool.sln +++ b/src/libraries/System.Threading.ThreadPool/System.Threading.ThreadPool.sln @@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{1445280F-1F0 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{6A32653B-8FAC-4B01-A0E0-E6379DE90A1A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Threading.ThreadPool.WindowsThreadPool.Tests", "tests\WindowsThreadPool\System.Threading.ThreadPool.WindowsThreadPool.Tests.csproj", "{2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -288,22 +290,41 @@ Global {71ACA28C-A4F1-4A07-A1B3-39DB86C11A75}.Checked|arm64.ActiveCfg = Debug|Any CPU {71ACA28C-A4F1-4A07-A1B3-39DB86C11A75}.Checked|x64.ActiveCfg = Debug|Any CPU {71ACA28C-A4F1-4A07-A1B3-39DB86C11A75}.Checked|x86.ActiveCfg = Debug|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Debug|x64.ActiveCfg = Debug|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Debug|x64.Build.0 = Debug|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Debug|x86.ActiveCfg = Debug|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Debug|x86.Build.0 = Debug|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Release|Any CPU.Build.0 = Release|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Release|x64.ActiveCfg = Release|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Release|x64.Build.0 = Release|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Release|x86.ActiveCfg = Release|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Release|x86.Build.0 = Release|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Checked|Any CPU.Build.0 = Debug|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Checked|x64.ActiveCfg = Debug|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Checked|x64.Build.0 = Debug|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Checked|x86.ActiveCfg = Debug|Any CPU + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE}.Checked|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {BEACD8A1-3C09-450E-931F-561D44986BFB} = {3358539E-11F4-4294-8D94-A9A31E9F47F5} - {53A0D29E-8633-4FF8-AB0E-86EF331DD379} = {3358539E-11F4-4294-8D94-A9A31E9F47F5} {CD477FC8-AF27-46A2-A451-4A2D4C11600D} = {DB513386-C3B1-4891-92E1-1BE5D38562DC} - {71ACA28C-A4F1-4A07-A1B3-39DB86C11A75} = {DB513386-C3B1-4891-92E1-1BE5D38562DC} {54E0AB0F-EB35-49AB-9A38-7C09FD7EC373} = {1445280F-1F07-45C3-93BC-D4025720B5FE} + {3156246A-939D-4232-90B3-FDE03A14BA90} = {6A32653B-8FAC-4B01-A0E0-E6379DE90A1A} {B5732A3E-15DB-4BAA-A44C-41A7C29C68F0} = {1445280F-1F07-45C3-93BC-D4025720B5FE} {3A5BA59D-C1B6-492F-BF2C-FDE2C274E9B3} = {1445280F-1F07-45C3-93BC-D4025720B5FE} - {3156246A-939D-4232-90B3-FDE03A14BA90} = {6A32653B-8FAC-4B01-A0E0-E6379DE90A1A} {E8E31BA1-DC94-40F1-9606-6922941582DC} = {6A32653B-8FAC-4B01-A0E0-E6379DE90A1A} {08FEC21B-875E-499A-B5CA-7D921B7A484B} = {6A32653B-8FAC-4B01-A0E0-E6379DE90A1A} {54134937-585D-48D5-AB60-79493BE2C9E7} = {6A32653B-8FAC-4B01-A0E0-E6379DE90A1A} + {53A0D29E-8633-4FF8-AB0E-86EF331DD379} = {3358539E-11F4-4294-8D94-A9A31E9F47F5} + {71ACA28C-A4F1-4A07-A1B3-39DB86C11A75} = {DB513386-C3B1-4891-92E1-1BE5D38562DC} + {2EBEBCB5-9AC0-49E7-BCE3-C8A73E068CAE} = {DB513386-C3B1-4891-92E1-1BE5D38562DC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6124532E-4272-44A6-954B-A7B0FF9D694A} diff --git a/src/libraries/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj b/src/libraries/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj index d3359ebc0229..0cb21c9d3849 100644 --- a/src/libraries/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj +++ b/src/libraries/System.Threading.ThreadPool/tests/System.Threading.ThreadPool.Tests.csproj @@ -14,4 +14,7 @@ + + + diff --git a/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs b/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs index dfef12aa200b..a9b09f53895d 100644 --- a/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs +++ b/src/libraries/System.Threading.ThreadPool/tests/ThreadPoolTests.cs @@ -42,7 +42,7 @@ static ThreadPoolTests() // Tests concurrent calls to ThreadPool.SetMinThreads. Invoked from the static constructor. private static void ConcurrentInitializeTest() { - RemoteExecutor.Invoke(() => + RemoteExecutor.Invoke((usePortableThreadPool) => { int processorCount = Environment.ProcessorCount; var countdownEvent = new CountdownEvent(processorCount); @@ -51,7 +51,10 @@ private static void ConcurrentInitializeTest() { countdownEvent.Signal(); countdownEvent.Wait(ThreadTestHelpers.UnexpectedTimeoutMilliseconds); - Assert.True(ThreadPool.SetMinThreads(processorCount, processorCount)); + if (Boolean.Parse(usePortableThreadPool)) + { + Assert.True(ThreadPool.SetMinThreads(processorCount, processorCount)); + } }; var waitForThreadArray = new Action[processorCount]; @@ -66,7 +69,7 @@ private static void ConcurrentInitializeTest() { waitForThread(); } - }).Dispose(); + }, UsePortableThreadPool.ToString()).Dispose(); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] @@ -97,7 +100,7 @@ public static void GetAvailableThreadsTest() Assert.True(c <= maxc); } - [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))] + [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))] [ActiveIssue("https://github.com/mono/mono/issues/15164", TestRuntimes.Mono)] public static void SetMinMaxThreadsTest() { @@ -161,7 +164,7 @@ public static void SetMinMaxThreadsTest() }).Dispose(); } - [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))] + [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))] public static void SetMinMaxThreadsTest_ChangedInDotNetCore() { RemoteExecutor.Invoke(() => @@ -222,7 +225,7 @@ private static void VerifyMaxThreads(int expectedMaxw, int expectedMaxc) Assert.Equal(expectedMaxc, maxc); } - [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))] + [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))] public static void SetMinThreadsTo0Test() { RemoteExecutor.Invoke(() => @@ -537,7 +540,8 @@ public void MetricsTest() Assert.True(totalWorkCountToQueue >= 1); waitForWorkStart = true; scheduleWork(); - Assert.True(ThreadPool.ThreadCount >= totalWorkCountToQueue); + int threadCountLowerBound = UsePortableThreadPool ? totalWorkCountToQueue : 1; + Assert.True(ThreadPool.ThreadCount >= threadCountLowerBound); int runningWorkItemCount = queuedWorkCount; @@ -701,7 +705,7 @@ public static void WorkQueueDepletionTest() done.CheckedWait(); } - [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))] + [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))] public static void WorkerThreadStateResetTest() { RemoteExecutor.Invoke(() => @@ -785,7 +789,7 @@ public static void WorkerThreadStateResetTest() }).Dispose(); } - [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))] + [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))] public static void SettingMinWorkerThreadsWillCreateThreadsUpToMinimum() { RemoteExecutor.Invoke(() => @@ -1105,7 +1109,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) } } - [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported))] + [ConditionalFact(nameof(IsThreadingAndRemoteExecutorSupported), nameof(UsePortableThreadPool))] public void ThreadPoolMinMaxThreadsEventTest() { // The ThreadPoolMinMaxThreads event is fired when the ThreadPool is created @@ -1158,5 +1162,13 @@ public void ThreadPoolMinMaxThreadsEventTest() public static bool IsThreadingAndRemoteExecutorSupported => PlatformDetection.IsThreadingSupported && RemoteExecutor.IsSupported; + + private static bool GetUseWindowsThreadPool() + { + AppContext.TryGetSwitch("System.Threading.ThreadPool.UseWindowsThreadPool", out bool useWindowsThreadPool); + return useWindowsThreadPool; + } + + private static bool UsePortableThreadPool { get; } = !GetUseWindowsThreadPool(); } } diff --git a/src/libraries/System.Threading.ThreadPool/tests/WindowsThreadPool/System.Threading.ThreadPool.WindowsThreadPool.Tests.csproj b/src/libraries/System.Threading.ThreadPool/tests/WindowsThreadPool/System.Threading.ThreadPool.WindowsThreadPool.Tests.csproj new file mode 100644 index 000000000000..f39bea77d0fb --- /dev/null +++ b/src/libraries/System.Threading.ThreadPool/tests/WindowsThreadPool/System.Threading.ThreadPool.WindowsThreadPool.Tests.csproj @@ -0,0 +1,21 @@ + + + true + + $(NetCoreAppCurrent)-windows + true + + + + + + + + + + + + + diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index a46986579e04..a9b7ee6424b2 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -267,7 +267,6 @@ - CommonSystem\Collections\Generic\ArrayBuilder.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 2ad06cd7b497..67dc4147f7e4 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 @@ -161,5 +161,11 @@ public static bool BindHandle(SafeHandle osHandle) { throw new PlatformNotSupportedException(SR.Arg_PlatformNotSupported); // Replaced by ThreadPoolBoundHandle.BindHandle } + + [Conditional("unnecessary")] + internal static void ReportThreadStatus(bool isWorking) + { + + } } } diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs index 0682f3ef246c..f4c7f64f89fe 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/TimerQueue.Browser.Mono.cs @@ -16,6 +16,7 @@ namespace System.Threading // internal partial class TimerQueue { + private static long TickCount64 => Environment.TickCount64; private static List? s_scheduledTimers; private static List? s_scheduledTimersToFire; private static long s_shortestDueTimeMs = long.MaxValue; @@ -23,7 +24,6 @@ internal partial class TimerQueue // this means that it's in the s_scheduledTimers collection, not that it's the one which would run on the next TimeoutCallback private bool _isScheduled; private long _scheduledDueTimeMs; - private TimerQueue(int _) { }