Enable IOPack, IOEnqueue, and IODequeue on Windows (#88894)
authorEduardo Velarde <32459232+eduardo-vp@users.noreply.github.com>
Wed, 9 Aug 2023 01:01:00 +0000 (18:01 -0700)
committerGitHub <noreply@github.com>
Wed, 9 Aug 2023 01:01:00 +0000 (18:01 -0700)
Enable IOPack, IOEnqueue and IODequeue for the Windows Threadpool (the one NativeAOT uses by default).

src/libraries/System.Private.CoreLib/src/System/Threading/Overlapped.cs
src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs
src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.WindowsThreadPool.cs
src/libraries/System.Private.CoreLib/src/System/Threading/WindowsThreadPool.cs
src/tests/tracing/eventlistener/EventListenerThreadPool.cs
src/tests/tracing/eventlistener/EventListenerThreadPool.csproj

index 45f17fe..29446e4 100644 (file)
@@ -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;
index cad89f3..0c40f75 100644 (file)
@@ -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);
         }
 
index 26098e1..cff6943 100644 (file)
@@ -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();
 
index d0637d2..cc2b534 100644 (file)
@@ -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);
         }
index 291dbb3..7e225cd 100644 (file)
@@ -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;
             }
         }
     }
index 203b64d..ee3e8eb 100644 (file)
@@ -10,5 +10,6 @@
   <ItemGroup>
     <Compile Include="EventListenerThreadPool.cs" />
     <ProjectReference Include="../common/common.csproj" />
+    <ProjectReference Include="$(TestSourceDir)Common\CoreCLRTestLibrary\CoreCLRTestLibrary.csproj" />
   </ItemGroup>
 </Project>