Fix infinite WaitTimer spin when the delayed-callback timer is disarmed (UINT64_MAX)#1001
Merged
Merged
Conversation
…ed (UINT64_MAX) PR #983 removed the `if (dueTime == UINT64_MAX) return;` guard from the top of TaskQueuePortImpl::SubmitPendingCallbacks and routed the early (now < dueTime) path through the new RearmTimerIfDueTimeUnchanged() helper, which calls m_timer.Start(dueTime) unconditionally. Only ArmTimerIfEarlier() kept a UINT64_MAX guard. UINT64_MAX is the "nothing armed" sentinel for m_timerDue. When a timer callback fires while the timer is disarmed, SubmitPendingCallbacks re-arms the sentinel: WaitTimerImpl::Start(UINT64_MAX) builds Deadline(Deadline::duration(UINT64_MAX)), and because the STL backend's duration rep is a signed int64, UINT64_MAX becomes -1 -- a deadline 1ns before the steady_clock epoch, i.e. perpetually in the past. The wait-timer thread then fires immediately, calls SubmitPendingCallbacks, sees m_timerDue == UINT64_MAX (now < UINT64_MAX is always true), re-arms the sentinel, and fires again: an infinite fire/re-arm spin that pins the timer thread and starves every real pending delayed callback (they sit unpromoted in m_pendingList because PromoteReadyPendingCallbacks is never reached). Observed on PS5 as an intermittent hang where a delayed websocket send-completion poll is never promoted, so the operation never completes. Fix: restore the pre-#983 sentinel guard at the top of SubmitPendingCallbacks and add the same guard to RearmTimerIfDueTimeUnchanged, so the disarm sentinel can never be programmed as a real deadline.
jasonsandlin
approved these changes
Jul 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An intermittent hang caused by an infinite spin in the
XTaskQueuedelayed-callback timer.Root cause
#983("Centralize timer-arming into three helpers",385cc9a) removed theif (dueTime == UINT64_MAX) return;guard from the top ofTaskQueuePortImpl::SubmitPendingCallbacksand routed thenow < dueTimepath through the newRearmTimerIfDueTimeUnchanged()helper, which callsm_timer.Start(dueTime)unconditionally. OnlyArmTimerIfEarlier()retained aUINT64_MAXguard.UINT64_MAXis the "nothing armed" sentinel form_timerDue. When a timer callback fires while the timer is disarmed:SubmitPendingCallbacksreadsdueTime = UINT64_MAX, seesnow < UINT64_MAX(always true), and callsRearmTimerIfDueTimeUnchanged(UINT64_MAX)→m_timer.Start(UINT64_MAX).WaitTimerImpl::StartbuildsDeadline(Deadline::duration(UINT64_MAX)). With the STL backend's signed-int64duration rep,UINT64_MAX→-1→ a deadline 1 ns before thesteady_clockepoch (perpetually in the past).SubmitPendingCallbacks, re-arms the sentinel, and fires again — an infinite fire/re-arm spin.Because every iteration takes the
now < dueTimere-arm branch,PromoteReadyPendingCallbacksis never reached, so all real pending delayed callbacks are starved (stranded inm_pendingList). On PS5 this manifested as an intermittent hang: a delayed websocket send-completion poll was never promoted, so the send never completed.Fix
#983UINT64_MAXguard at the top ofSubmitPendingCallbacks.RearmTimerIfDueTimeUnchanged(mirrors the existing guard inArmTimerIfEarlier).The disarm sentinel can no longer be programmed as a real deadline.
Validation
Reproduced on PS5 (STL wait-timer backend) with tracing that showed the timer firing ~307k times on the
UINT64_MAXsentinel while real callbacks sat unpromoted. With the guard, the spin is gone: the timer-fire count returns to normal, the guard hits exactly at the sentinel, and promotions resume. A 29‑scenario GameSave PSX device suite passed with 0 failures, including every previously-hanging teardown scenario.