Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ 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);

[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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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());
Expand Down
9 changes: 7 additions & 2 deletions src/tests/baseservices/threading/regressions/115178/115178.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Loading