Skip to content

Commit

Permalink
Enable IOPack, IOEnqueue, and IODequeue on Windows (#88894)
Browse files Browse the repository at this point in the history
Enable IOPack, IOEnqueue and IODequeue for the Windows Threadpool (the one NativeAOT uses by default).
  • Loading branch information
eduardo-vp authored Aug 9, 2023
1 parent a3dde4a commit 41f58f6
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,9 @@ public static void Free(NativeOverlapped* nativeOverlappedPtr)

#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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,6 +49,9 @@ internal unsafe RegisteredWaitHandle(SafeWaitHandle waitHandle, _ThreadPoolWaitO
_gcHandle.Free();
throw new OutOfMemoryException();
}

if (NativeRuntimeEventSource.Log.IsEnabled())
NativeRuntimeEventSource.Log.ThreadPoolIOEnqueue(this);
}

#pragma warning disable IDE0060 // Remove unused parameter
Expand Down Expand Up @@ -91,6 +95,9 @@ private void PerformCallbackWindowsThreadPool(bool timedOut)
}
}

if (NativeRuntimeEventSource.Log.IsEnabled())
NativeRuntimeEventSource.Log.ThreadPoolIODequeue(this);

_ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(_callbackHelper!, timedOut);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -54,6 +55,9 @@ private static unsafe ThreadPoolBoundHandle BindHandleWindowsThreadPool(SafeHand
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);
Expand Down Expand Up @@ -82,6 +86,9 @@ private static unsafe ThreadPoolBoundHandle BindHandleWindowsThreadPool(SafeHand

data._boundHandle = this;

if (NativeRuntimeEventSource.Log.IsEnabled())
NativeRuntimeEventSource.Log.ThreadPoolIOEnqueue(Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(preAllocated._overlappedWindowsThreadPool));

Interop.Kernel32.StartThreadpoolIo(_threadPoolHandle!);

return Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(preAllocated._overlappedWindowsThreadPool);
Expand Down Expand Up @@ -154,6 +161,9 @@ private static unsafe void OnNativeIOCompleted(IntPtr instance, IntPtr context,

boundHandle.Release();

if (NativeRuntimeEventSource.Log.IsEnabled())
NativeRuntimeEventSource.Log.ThreadPoolIODequeue(Win32ThreadPoolNativeOverlapped.ToNativeOverlapped(overlapped));

Win32ThreadPoolNativeOverlapped.CompleteWithCallback(ioResult, (uint)numberOfBytesTransferred, overlapped);
ThreadPool.IncrementCompletedWorkItemCount();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -187,8 +188,13 @@ internal static RegisteredWaitHandle RegisterWaitForSingleObject(
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)
Expand All @@ -200,6 +206,10 @@ public static unsafe bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapp

// 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);
}
Expand Down
99 changes: 75 additions & 24 deletions src/tests/tracing/eventlistener/EventListenerThreadPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
Expand All @@ -50,30 +66,65 @@ static int Main()
{
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++)
{
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;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
<ItemGroup>
<Compile Include="EventListenerThreadPool.cs" />
<ProjectReference Include="../common/common.csproj" />
<ProjectReference Include="$(TestSourceDir)Common\CoreCLRTestLibrary\CoreCLRTestLibrary.csproj" />
</ItemGroup>
</Project>

0 comments on commit 41f58f6

Please sign in to comment.