From: Eduardo Velarde <32459232+eduardo-vp@users.noreply.github.com> Date: Wed, 9 Aug 2023 01:01:00 +0000 (-0700) Subject: Enable IOPack, IOEnqueue, and IODequeue on Windows (#88894) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055536~431 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=41f58f65d27ccef704e98eab727137cceae7e5ac;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Enable IOPack, IOEnqueue, and IODequeue on Windows (#88894) Enable IOPack, IOEnqueue and IODequeue for the Windows Threadpool (the one NativeAOT uses by default). --- diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped.cs index 45f17fe..29446e4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped.cs @@ -185,12 +185,10 @@ namespace System.Threading #if FEATURE_PERFTRACING #if !((TARGET_BROWSER || TARGET_WASI) && !FEATURE_WASM_THREADS) -#if !NATIVEAOT // TODO shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase if (NativeRuntimeEventSource.Log.IsEnabled()) NativeRuntimeEventSource.Log.ThreadPoolIOPack(pNativeOverlapped); #endif #endif -#endif NativeOverlapped* pRet = pNativeOverlapped; pNativeOverlapped = null; 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 index cad89f3..0c40f75 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs @@ -3,6 +3,7 @@ using Microsoft.Win32.SafeHandles; using System.Diagnostics; +using System.Diagnostics.Tracing; using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -48,6 +49,9 @@ namespace System.Threading _gcHandle.Free(); throw new OutOfMemoryException(); } + + if (NativeRuntimeEventSource.Log.IsEnabled()) + NativeRuntimeEventSource.Log.ThreadPoolIOEnqueue(this); } #pragma warning disable IDE0060 // Remove unused parameter @@ -91,6 +95,9 @@ namespace System.Threading } } + if (NativeRuntimeEventSource.Log.IsEnabled()) + NativeRuntimeEventSource.Log.ThreadPoolIODequeue(this); + _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(_callbackHelper!, timedOut); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.WindowsThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.WindowsThreadPool.cs index 26098e1..cff6943 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.WindowsThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.WindowsThreadPool.cs @@ -3,6 +3,7 @@ using Microsoft.Win32.SafeHandles; using System.Diagnostics; +using System.Diagnostics.Tracing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; @@ -54,6 +55,9 @@ namespace System.Threading Win32ThreadPoolNativeOverlapped* overlapped = Win32ThreadPoolNativeOverlapped.Allocate(callback, state, pinData, preAllocated: null, flowExecutionContext); overlapped->Data._boundHandle = this; + if (NativeRuntimeEventSource.Log.IsEnabled()) + NativeRuntimeEventSource.Log.ThreadPoolIOEnqueue(Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(overlapped)); + Interop.Kernel32.StartThreadpoolIo(_threadPoolHandle!); return Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(overlapped); @@ -82,6 +86,9 @@ namespace System.Threading data._boundHandle = this; + if (NativeRuntimeEventSource.Log.IsEnabled()) + NativeRuntimeEventSource.Log.ThreadPoolIOEnqueue(Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(preAllocated._overlappedWindowsThreadPool)); + Interop.Kernel32.StartThreadpoolIo(_threadPoolHandle!); return Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(preAllocated._overlappedWindowsThreadPool); @@ -154,6 +161,9 @@ namespace System.Threading boundHandle.Release(); + if (NativeRuntimeEventSource.Log.IsEnabled()) + NativeRuntimeEventSource.Log.ThreadPoolIODequeue(Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(overlapped)); + Win32ThreadPoolNativeOverlapped.CompleteWithCallback(ioResult, (uint)numberOfBytesTransferred, overlapped); ThreadPool.IncrementCompletedWorkItemCount(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WindowsThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WindowsThreadPool.cs index d0637d2..cc2b534 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WindowsThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WindowsThreadPool.cs @@ -3,6 +3,7 @@ using Microsoft.Win32.SafeHandles; using System.Diagnostics; +using System.Diagnostics.Tracing; using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -187,8 +188,13 @@ namespace System.Threading return registeredWaitHandle; } - private static unsafe void NativeOverlappedCallback(nint overlappedPtr) => + private static unsafe void NativeOverlappedCallback(nint overlappedPtr) + { + if (NativeRuntimeEventSource.Log.IsEnabled()) + NativeRuntimeEventSource.Log.ThreadPoolIODequeue((NativeOverlapped*)overlappedPtr); + IOCompletionCallbackHelper.PerformSingleIOCompletionCallback(0, 0, (NativeOverlapped*)overlappedPtr); + } [SupportedOSPlatform("windows")] public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) @@ -200,6 +206,10 @@ namespace System.Threading // OS doesn't signal handle, so do it here overlapped->InternalLow = (IntPtr)0; + + if (NativeRuntimeEventSource.Log.IsEnabled()) + NativeRuntimeEventSource.Log.ThreadPoolIOEnqueue(overlapped); + // Both types of callbacks are executed on the same thread pool return ThreadPool.UnsafeQueueUserWorkItem(NativeOverlappedCallback, (nint)overlapped, preferLocal: false); } diff --git a/src/tests/tracing/eventlistener/EventListenerThreadPool.cs b/src/tests/tracing/eventlistener/EventListenerThreadPool.cs index 291dbb3..7e225cd 100644 --- a/src/tests/tracing/eventlistener/EventListenerThreadPool.cs +++ b/src/tests/tracing/eventlistener/EventListenerThreadPool.cs @@ -10,36 +10,52 @@ namespace Tracing.Tests { internal sealed class RuntimeEventListener : EventListener { - public volatile int TPWorkerThreadStartCount = 0; - public volatile int TPWorkerThreadStopCount = 0; public volatile int TPWorkerThreadWaitCount = 0; + public volatile int TPIOPack = 0; + public volatile int TPIOEnqueue = 0; + public volatile int TPIODequeue = 0; - public ManualResetEvent TPWaitEvent = new ManualResetEvent(false); + public int TPIOPackGoal = 0; + public int TPIOEnqueueGoal = 1; + public int TPIODequeueGoal = 1; + + public ManualResetEvent TPWaitWorkerThreadEvent = new ManualResetEvent(false); + public ManualResetEvent TPWaitIOPackEvent = new ManualResetEvent(false); + public ManualResetEvent TPWaitIOEnqueueEvent = new ManualResetEvent(false); + public ManualResetEvent TPWaitIODequeueEvent = new ManualResetEvent(false); protected override void OnEventSourceCreated(EventSource source) { if (source.Name.Equals("Microsoft-Windows-DotNETRuntime")) { - EnableEvents(source, EventLevel.Informational, (EventKeywords)0x10000); + EnableEvents(source, EventLevel.Verbose, (EventKeywords)0x10000); } } protected override void OnEventWritten(EventWrittenEventArgs eventData) { - if (eventData.EventName.Equals("ThreadPoolWorkerThreadStart")) + if (eventData.EventName.Equals("ThreadPoolWorkerThreadWait")) { - Interlocked.Increment(ref TPWorkerThreadStartCount); - TPWaitEvent.Set(); + Interlocked.Increment(ref TPWorkerThreadWaitCount); + TPWaitWorkerThreadEvent.Set(); } - else if (eventData.EventName.Equals("ThreadPoolWorkerThreadStop")) + else if (eventData.EventName.Equals("ThreadPoolIOPack")) { - Interlocked.Increment(ref TPWorkerThreadStopCount); - TPWaitEvent.Set(); + Interlocked.Increment(ref TPIOPack); + if (TPIOPack == TPIOPackGoal) + TPWaitIOPackEvent.Set(); } - else if (eventData.EventName.Equals("ThreadPoolWorkerThreadWait")) + else if (eventData.EventName.Equals("ThreadPoolIOEnqueue")) { - Interlocked.Increment(ref TPWorkerThreadWaitCount); - TPWaitEvent.Set(); + Interlocked.Increment(ref TPIOEnqueue); + if (TPIOEnqueue == TPIOEnqueueGoal) + TPWaitIOEnqueueEvent.Set(); + } + else if (eventData.EventName.Equals("ThreadPoolIODequeue")) + { + Interlocked.Increment(ref TPIODequeue); + if (TPIODequeue == TPIODequeueGoal) + TPWaitIODequeueEvent.Set(); } } } @@ -50,6 +66,7 @@ namespace Tracing.Tests { using (RuntimeEventListener listener = new RuntimeEventListener()) { + // This should fire at least one ThreadPoolWorkerThreadWait int someNumber = 0; Task[] tasks = new Task[100]; for (int i = 0; i < tasks.Length; i++) @@ -57,23 +74,57 @@ namespace Tracing.Tests tasks[i] = Task.Run(() => { someNumber += 1; }); } - listener.TPWaitEvent.WaitOne(TimeSpan.FromMinutes(3)); + if (TestLibrary.Utilities.IsWindows) + { + // This part is Windows-specific, it should fire an IOPack, IOEnqueue and IODequeue event + listener.TPIOPackGoal += 1; + listener.TPIOEnqueueGoal += 1; + listener.TPIODequeueGoal += 1; + + Overlapped overlapped = new Overlapped(); + unsafe + { + NativeOverlapped* nativeOverlapped = overlapped.Pack(null); + ThreadPool.UnsafeQueueNativeOverlapped(nativeOverlapped); + } + } + + // RegisterWaitForSingleObject should fire an IOEnqueue and IODequeue event + ManualResetEvent manualResetEvent = new ManualResetEvent(false); + WaitOrTimerCallback work = (x, timedOut) => { int y = (int)x; }; + ThreadPool.RegisterWaitForSingleObject(manualResetEvent, work, 1, 100, true); + manualResetEvent.Set(); + + ManualResetEvent[] waitEvents = new ManualResetEvent[] {listener.TPWaitIOPackEvent, + listener.TPWaitIOEnqueueEvent, + listener.TPWaitIODequeueEvent}; - if (listener.TPWorkerThreadStartCount > 0 || - listener.TPWorkerThreadStopCount > 0 || - listener.TPWorkerThreadWaitCount > 0) + WaitHandle.WaitAll(waitEvents, TimeSpan.FromMinutes(1)); + + if (!TestLibrary.Utilities.IsNativeAot) { - Console.WriteLine("Test Passed."); - return 100; + listener.TPWaitWorkerThreadEvent.WaitOne(TimeSpan.FromMinutes(1)); + if (listener.TPWorkerThreadWaitCount == 0) + { + Console.WriteLine("Test Failed: Did not see the expected event."); + Console.WriteLine($"ThreadPoolWorkerThreadWaitCount: {listener.TPWorkerThreadWaitCount}"); + return -1; + } } - else + + if (!(listener.TPIOPack >= listener.TPIOPackGoal && + listener.TPIOEnqueue >= listener.TPIOEnqueueGoal && + listener.TPIODequeue >= listener.TPIODequeueGoal)) { - Console.WriteLine("Test Failed: Did not see any of the expected events."); - Console.WriteLine($"ThreadPoolWorkerThreadStartCount: {listener.TPWorkerThreadStartCount}"); - Console.WriteLine($"ThreadPoolWorkerThreadStopCount: {listener.TPWorkerThreadStopCount}"); - Console.WriteLine($"ThreadPoolWorkerThreadWaitCount: {listener.TPWorkerThreadWaitCount}"); + Console.WriteLine("Test Failed: Did not see all of the expected events."); + Console.WriteLine($"ThreadPoolIOPack: {listener.TPIOPack}"); + Console.WriteLine($"ThreadPoolIOEnqueue: {listener.TPIOEnqueue}"); + Console.WriteLine($"ThreadPoolIODequeue: {listener.TPIODequeue}"); return -1; } + + Console.WriteLine("Test Passed."); + return 100; } } } diff --git a/src/tests/tracing/eventlistener/EventListenerThreadPool.csproj b/src/tests/tracing/eventlistener/EventListenerThreadPool.csproj index 203b64d..ee3e8eb 100644 --- a/src/tests/tracing/eventlistener/EventListenerThreadPool.csproj +++ b/src/tests/tracing/eventlistener/EventListenerThreadPool.csproj @@ -10,5 +10,6 @@ +