diff --git a/core/nbio/impl_windows.odin b/core/nbio/impl_windows.odin index de16a5a69..e1e0f2ebc 100644 --- a/core/nbio/impl_windows.odin +++ b/core/nbio/impl_windows.odin @@ -25,6 +25,11 @@ _Event_Loop :: struct { thread: win.HANDLE, completed: queue.Queue(^Operation), completed_oob: Multi_Producer_Single_Consumer, + state: enum { + Working, + Waking, + Sleeping, + }, } @(private="package") @@ -163,8 +168,11 @@ __tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> (err: General_Error) { actual_timeout := compute_timeout(l, timeout, next_timeout) if actual_timeout > 0 { - // Work may have been queued after the drain at the top of this tick, - // so check the queues once more before blocking. + sync.atomic_store_explicit(&l.state, .Sleeping, .Release) + + // There could be a race condition where we go sleeping at the same time as things get queued + // and a wakeup isn't done because the state is not .Sleeping yet. + // So after sleeping we first check our queues. for { op := (^Operation)(mpsc_dequeue(&l.queue)) if op == nil { break } @@ -196,6 +204,8 @@ __tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> (err: General_Error) { } } + sync.atomic_store_explicit(&l.state, .Working, .Relaxed) + if actual_timeout > 0 { // We may have just waited some time, lets update the current time. l.now = time.now() @@ -768,10 +778,10 @@ _associate_socket :: proc(socket: Any_Socket, l: ^Event_Loop) -> Association_Err @(private="package") _wake_up :: proc(l: ^Event_Loop) { - // Unconditional: an APC queued before the loop enters its alertable wait stays - // pending and is delivered as soon as that wait begins. Queueing it only once the - // loop is already asleep drops every wake sent while it is still awake. - win.QueueUserAPC(proc "system" (Parameter: win.ULONG_PTR) {}, l.thread, 0) + _, exchanged := sync.atomic_compare_exchange_strong(&l.state, .Sleeping, .Waking) + if exchanged { + win.QueueUserAPC(proc "system" (Parameter: win.ULONG_PTR) {}, l.thread, 0) + } } @(private="package")