Using Task.sleep on main crashes on Windows

Hello,

I am making a desktop app using Swift and manual rendering (with OpenGL). So naturally I have my own main loop.

I use Task.sleep to sleep between frames in the main loop. For now it's a naive fixed duration, but I intend to improve it by using delta time in the future (this is not relevant here). The main loop is asynchronous because my app events are asynchronous, so in reality I have to use @MainActor to use OpenGL in only one thread (irrelevent here as well).

On Windows, using await Task.sleep in the main method crashes. On Linux however, everything works fine, which makes me think that it's either a Windows bug, or I am hitting some UB that I am not aware of.

Here is the trace of the crash:

Process 15744 stopped
* thread #5, stop reason = Exception 0xc0000005 encountered at address 0xfffffffffd053690: User-mode data execution prevention (DEP) violation at location 0xfffffffffd053690
    frame #0: 0xfffffffffd053690
error: Invalid access to memory location.

Here are traces of all threads:

  thread #2
    frame #0: 0x00007fff2a9907c4 ntdll.dll`NtWaitForWorkViaWorkerFactory + 20
    frame #1: 0x00007fff2a942dc7 ntdll.dll`TpReleaseCleanupGroupMembers + 1863
    frame #2: 0x00007fff293c7034 kernel32.dll`BaseThreadInitThunk + 20
    frame #3: 0x00007fff2a942651 ntdll.dll`RtlUserThreadStart + 33
  thread #4
    frame #0: 0x00007fff2a9907c4 ntdll.dll`NtWaitForWorkViaWorkerFactory + 20
    frame #1: 0x00007fff2a942dc7 ntdll.dll`TpReleaseCleanupGroupMembers + 1863
    frame #2: 0x00007fff293c7034 kernel32.dll`BaseThreadInitThunk + 20
    frame #3: 0x00007fff2a942651 ntdll.dll`RtlUserThreadStart + 33
  thread #1
    frame #0: 0x00007fff2a98d3f4 ntdll.dll`NtDelayExecution + 20
    frame #1: 0x00007fff286b967e KernelBase.dll`SleepEx + 158
    frame #2: 0x00007fff00545df9 dispatch.dll`_dispatch_prohibit_transition_to_multithreaded + 10793
    frame #3: 0x00007fff005432a8 dispatch.dll`libdispatch_init + 1400
    frame #4: 0x00007fff2a94ddd5 ntdll.dll`RtlProcessFlsData + 309
    frame #5: 0x00007fff2a907498 ntdll.dll`LdrShutdownThread + 72
    frame #6: 0x00007fff2a94462e ntdll.dll`RtlExitUserThread + 62
    frame #7: 0x00007fff28351d0d ucrtbase.dll`_configthreadlocale + 493
    frame #8: 0x00007fff283a4759 ucrtbase.dll`_endthreadex + 9
    frame #9: 0x00007fff00542d22 dispatch.dll`dispatch_main + 50
    frame #10: 0x00007fff0d39b1fd swift_Concurrency.dll`swift_task_asyncMainDrainQueue + 77
    frame #11: 0x00007fff0d37e90f swift_Concurrency.dll`Swift._runAsyncMain(() async throws -> ()) -> () + 191
    frame #12: 0x00007ff71b711dd2 AsyncMainActorCrash.exe`static AsyncMainActorCrash.Main.$main() -> () + 114
    frame #13: 0x00007ff71b712217 AsyncMainActorCrash.exe`main + 23
    frame #14: 0x00007ff71b712784 AsyncMainActorCrash.exe`main + 1412
    frame #15: 0x00007fff293c7034 kernel32.dll`BaseThreadInitThunk + 20
    frame #16: 0x00007fff2a942651 ntdll.dll`RtlUserThreadStart + 33
  thread #3
    frame #0: 0x00007fff2a9907c4 ntdll.dll`NtWaitForWorkViaWorkerFactory + 20
    frame #1: 0x00007fff2a942dc7 ntdll.dll`TpReleaseCleanupGroupMembers + 1863
    frame #2: 0x00007fff293c7034 kernel32.dll`BaseThreadInitThunk + 20
    frame #3: 0x00007fff2a942651 ntdll.dll`RtlUserThreadStart + 33
  thread #8
    frame #0: 0x00007fff2a98ce94 ntdll.dll`NtRemoveIoCompletion + 20
    frame #1: 0x00007fff286d1a2f KernelBase.dll`GetQueuedCompletionStatus + 79
    frame #2: 0x00007fff0054ce0d dispatch.dll`voucher_copy + 6877
    frame #3: 0x00007fff00541c64 dispatch.dll`dispatch_workloop_set_cpupercent + 9284
    frame #4: 0x00007fff00541ba1 dispatch.dll`dispatch_workloop_set_cpupercent + 9089
    frame #5: 0x00007fff00545994 dispatch.dll`_dispatch_prohibit_transition_to_multithreaded + 9668
    frame #6: 0x00007fff28351bb2 ucrtbase.dll`_configthreadlocale + 146
    frame #7: 0x00007fff293c7034 kernel32.dll`BaseThreadInitThunk + 20
    frame #8: 0x00007fff2a942651 ntdll.dll`RtlUserThreadStart + 33
* thread #5, stop reason = Exception 0xc0000005 encountered at address 0xfffffffffd053690: User-mode data execution prevention (DEP) violation at location 0xfffffffffd053690
  * frame #0: 0xfffffffffd053690
  thread #10
    frame #0: 0x00007fff2a98cdf4 ntdll.dll`NtWaitForSingleObject + 20
    frame #1: 0x00007fff28691a8e KernelBase.dll`WaitForSingleObjectEx + 142
    frame #2: 0x00007fff0054d883 dispatch.dll`voucher_copy + 9555
    frame #3: 0x00007fff00545f90 dispatch.dll`dispatch_semaphore_wait + 64
    frame #4: 0x00007fff005457e6 dispatch.dll`_dispatch_prohibit_transition_to_multithreaded + 9238
    frame #5: 0x00007fff28351bb2 ucrtbase.dll`_configthreadlocale + 146
    frame #6: 0x00007fff293c7034 kernel32.dll`BaseThreadInitThunk + 20
    frame #7: 0x00007fff2a942651 ntdll.dll`RtlUserThreadStart + 33
  thread #7
    frame #0: 0x00007fff2a98d3f4 ntdll.dll`NtDelayExecution + 20
    frame #1: 0x00007fff286b967e KernelBase.dll`SleepEx + 158
    frame #2: 0x00007fff00545e6f dispatch.dll`_dispatch_prohibit_transition_to_multithreaded + 10911
    frame #3: 0x00007fff00545ab7 dispatch.dll`_dispatch_prohibit_transition_to_multithreaded + 9959
    frame #4: 0x00007fff28351bb2 ucrtbase.dll`_configthreadlocale + 146
    frame #5: 0x00007fff293c7034 kernel32.dll`BaseThreadInitThunk + 20
    frame #6: 0x00007fff2a942651 ntdll.dll`RtlUserThreadStart + 33
  thread #9
    frame #0: 0x00007fff2a9907c4 ntdll.dll`NtWaitForWorkViaWorkerFactory + 20
    frame #1: 0x00007fff2a942dc7 ntdll.dll`TpReleaseCleanupGroupMembers + 1863
    frame #2: 0x00007fff293c7034 kernel32.dll`BaseThreadInitThunk + 20
    frame #3: 0x00007fff2a942651 ntdll.dll`RtlUserThreadStart + 33

Here is the smallest code I could come up with to reproduce the issue:

@main
class Main {
    static var n = 0

    public static func main() async {
        print("Entered main()")

        while (true) {
            print("Frame \(n)")
            n += 1

            await Task.sleep(16666666) // XXX: this crashes "Exception 0xc0000005 encountered at address 0x1c80fcf2fc8: User-mode data execution prevention (DEP) violation at location 0x1c80fcf2fc8"

            if n >= 60 * 10 { // exit after ~10s
                print("Done")
                break
            }
        }

        print("Exited main()")
    }
}

I just wanted to post here before making an actual bug report, in case running Task.sleep is not supposed to work in the main actor. Is @MainActor properly working and supported on Windows? I see calls to "shutdown thread" and "drain main queue" so that makes me think that the main thread gets terminated early?

Thanks!

Terms of Service

Privacy Policy

Cookie Policy