Skip to content

Commit

Permalink
Check for pending IO in the portable thread pool's worker threads (#8…
Browse files Browse the repository at this point in the history
…2245)

* Check for pending IO in the portable thread pool's worker threads

- When Resource Monitor is attached, some async IO operations are bound to the thread that issued it even though the IO handle is bound to an IOCP. If the thread exits, the async IO operation is aborted. This can lead to hangs or unexpected exceptions.
- Added a check that was missing in the portable thread pool implementation to prevent exiting a worker thread when it has pending IO

Fixes #82207
  • Loading branch information
kouvel authored Mar 1, 2023
1 parent cdd71fe commit 0a15c3b
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,9 @@ internal enum ThreadPriority : int
[LibraryImport(Libraries.Kernel32)]
[return:MarshalAs(UnmanagedType.Bool)]
internal static partial bool SetThreadPriority(SafeWaitHandle hThread, int nPriority);

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool GetThreadIOPendingFlag(nint hThread, out BOOL lpIOIsPending);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1854,6 +1854,9 @@
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SystemTimeToFileTime.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.SystemTimeToFileTime.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.Threading.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.Threading.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.TimeZone.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.TimeZone.cs</Link>
</Compile>
Expand Down Expand Up @@ -2502,8 +2505,8 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.WaitThread.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.WorkerThread.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.WorkerTracking.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.CpuUtilizationReader.Unix.cs" Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.CpuUtilizationReader.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.Unix.cs" Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelLifoSemaphore.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelLifoSemaphore.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.cs" Condition="('$(TargetsBrowser)' != 'true' and '$(TargetsWasi)' != 'true') or '$(FeatureWasmThreads)' == 'true'" />
Expand Down Expand Up @@ -2537,9 +2540,6 @@
</ItemGroup>
<ItemGroup Condition="'$(FeatureCoreCLR)' != 'true' and '$(TargetsWindows)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\WaitHandle.Windows.cs" />
<Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.Threading.cs">
<Link>Interop\Windows\Kernel32\Interop.Threading.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)System\IParsable.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ namespace System.Threading
{
internal sealed partial class PortableThreadPool
{
private static partial class WorkerThread
{
private static bool IsIOPending => false;
}

private struct CpuUtilizationReader
{
private Interop.Sys.ProcessCpuInformation _cpuInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ namespace System.Threading
{
internal sealed partial class PortableThreadPool
{
private static partial class WorkerThread
{
private static bool IsIOPending
{
get
{
bool success =
Interop.Kernel32.GetThreadIOPendingFlag(Interop.Kernel32.GetCurrentThread(), out Interop.BOOL isIOPending);
Debug.Assert(success);
return !success || isIOPending != Interop.BOOL.FALSE;
}
}
}

private struct CpuUtilizationReader
{
public long _idleTime;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal sealed partial class PortableThreadPool
/// <summary>
/// The worker thread infastructure for the CLR thread pool.
/// </summary>
private static class WorkerThread
private static partial class WorkerThread
{
private const int SemaphoreSpinCountDefaultBaseline = 70;
#if !TARGET_ARM64 && !TARGET_ARM && !TARGET_LOONGARCH64
Expand Down Expand Up @@ -115,6 +115,12 @@ private static void WorkerThreadStart()
}
}

// The thread cannot exit if it has IO pending, otherwise the IO may be canceled
if (IsIOPending)
{
continue;
}

threadAdjustmentLock.Acquire();
try
{
Expand Down

0 comments on commit 0a15c3b

Please sign in to comment.