diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Threading.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Threading.cs index 14863bf1e4f749..b323cacc6bcaee 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Threading.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Threading.cs @@ -10,6 +10,7 @@ internal static partial class Interop internal static partial class Kernel32 { internal const int WAIT_FAILED = unchecked((int)0xFFFFFFFF); + internal const int WAIT_IO_COMPLETION = 0x000000C0; [LibraryImport(Libraries.Kernel32)] internal static partial uint WaitForMultipleObjectsEx(uint nCount, IntPtr lpHandles, BOOL bWaitAll, uint dwMilliseconds, BOOL bAlertable); @@ -17,6 +18,9 @@ internal static partial class Kernel32 [LibraryImport(Libraries.Kernel32)] internal static partial uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); + [LibraryImport(Libraries.Kernel32)] + internal static partial uint WaitForSingleObjectEx(IntPtr hHandle, uint dwMilliseconds, BOOL bAlertable); + [LibraryImport(Libraries.Kernel32)] internal static partial uint SignalObjectAndWait(IntPtr hObjectToSignal, IntPtr hObjectToWaitOn, uint dwMilliseconds, BOOL bAlertable); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Windows.cs index d4d01971437637..26ab6b4278b2bf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Windows.cs @@ -49,20 +49,46 @@ private static unsafe int WaitForMultipleObjectsIgnoringSyncContext(IntPtr* pHan Thread currentThread = Thread.CurrentThread; currentThread.SetWaitSleepJoinState(); -#if NATIVEAOT - int result; - if (reentrantWait) + long startTime = 0; + if (millisecondsTimeout != -1) { - Debug.Assert(!waitAll); - result = RuntimeImports.RhCompatibleReentrantWaitAny(false, millisecondsTimeout, numHandles, pHandles); + startTime = Environment.TickCount64; } - else + + int result; + while (true) { - result = (int)Interop.Kernel32.WaitForMultipleObjectsEx((uint)numHandles, (IntPtr)pHandles, waitAll ? Interop.BOOL.TRUE : Interop.BOOL.FALSE, (uint)millisecondsTimeout, Interop.BOOL.FALSE); - } +#if NATIVEAOT + if (reentrantWait) + { + Debug.Assert(!waitAll); + result = RuntimeImports.RhCompatibleReentrantWaitAny(true, millisecondsTimeout, numHandles, pHandles); + } + else + { + result = (int)Interop.Kernel32.WaitForMultipleObjectsEx((uint)numHandles, (IntPtr)pHandles, waitAll ? Interop.BOOL.TRUE : Interop.BOOL.FALSE, (uint)millisecondsTimeout, Interop.BOOL.TRUE); + } #else - int result = (int)Interop.Kernel32.WaitForMultipleObjectsEx((uint)numHandles, (IntPtr)pHandles, waitAll ? Interop.BOOL.TRUE : Interop.BOOL.FALSE, (uint)millisecondsTimeout, Interop.BOOL.FALSE); + result = (int)Interop.Kernel32.WaitForMultipleObjectsEx((uint)numHandles, (IntPtr)pHandles, waitAll ? Interop.BOOL.TRUE : Interop.BOOL.FALSE, (uint)millisecondsTimeout, Interop.BOOL.TRUE); #endif + + if (result != Interop.Kernel32.WAIT_IO_COMPLETION) + break; + + // Handle APC completion by adjusting timeout and retrying + if (millisecondsTimeout != -1) + { + long currentTime = Environment.TickCount64; + long elapsed = currentTime - startTime; + if (elapsed >= millisecondsTimeout) + { + result = Interop.Kernel32.WAIT_TIMEOUT; + break; + } + millisecondsTimeout -= (int)elapsed; + startTime = currentTime; + } + } currentThread.ClearWaitSleepJoinState(); if (result == Interop.Kernel32.WAIT_FAILED) @@ -102,8 +128,35 @@ private static int SignalAndWaitCore(IntPtr handleToSignal, IntPtr handleToWaitO { Debug.Assert(millisecondsTimeout >= -1); - int ret = (int)Interop.Kernel32.SignalObjectAndWait(handleToSignal, handleToWaitOn, (uint)millisecondsTimeout, Interop.BOOL.FALSE); + long startTime = 0; + if (millisecondsTimeout != -1) + { + startTime = Environment.TickCount64; + } + + // Signal the object and wait for the first time + int ret = (int)Interop.Kernel32.SignalObjectAndWait(handleToSignal, handleToWaitOn, (uint)millisecondsTimeout, Interop.BOOL.TRUE); + + // Handle APC completion by retrying with WaitForSingleObjectEx (without signaling again) + while (ret == Interop.Kernel32.WAIT_IO_COMPLETION) + { + if (millisecondsTimeout != -1) + { + long currentTime = Environment.TickCount64; + long elapsed = currentTime - startTime; + if (elapsed >= millisecondsTimeout) + { + ret = Interop.Kernel32.WAIT_TIMEOUT; + break; + } + millisecondsTimeout -= (int)elapsed; + startTime = currentTime; + } + + // For retries, only wait on the handle (don't signal again) + ret = (int)Interop.Kernel32.WaitForSingleObjectEx(handleToWaitOn, (uint)millisecondsTimeout, Interop.BOOL.TRUE); + } if (ret == Interop.Kernel32.WAIT_FAILED) { ThrowWaitFailedException(Interop.Kernel32.GetLastError()); diff --git a/src/tests/baseservices/threading/regressions/115178/115178.cs b/src/tests/baseservices/threading/regressions/115178/115178.cs index 5a9fdd46265c4d..34aba004040a9d 100644 --- a/src/tests/baseservices/threading/regressions/115178/115178.cs +++ b/src/tests/baseservices/threading/regressions/115178/115178.cs @@ -284,12 +284,17 @@ private static void RunTestInterruptInfiniteWait() } [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsWindows))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/118233", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNativeAot))] public static int TestEntryPoint() { RunTestUsingInfiniteWait(); RunTestUsingTimedWait(); - RunTestInterruptInfiniteWait(); + + // Thread.Interrupt is not implemented on NativeAOT - https://github.com/dotnet/runtime/issues/69919 + if (!TestLibrary.Utilities.IsNativeAot) + { + RunTestInterruptInfiniteWait(); + } + return result; } }