Summary
Callbacks submitted through XTaskQueueSubmitDelayedCallback can become
dispatchable before their requested delay has elapsed. The contract of this API
is that the callback will not be invoked until at least delayMs milliseconds
have passed, but under certain runtime conditions the callback surfaces early —
sometimes immediately.
This affects any code that relies on XTaskQueueSubmitDelayedCallback for
timed waits, retry back-off, debouncing, or deferred continuations. The
symptoms appear as spurious early timeouts, premature retry loops, and
timing-sensitive async failures that are difficult to reproduce in isolation.
Public API affected
STDAPI XTaskQueueSubmitDelayedCallback(
_In_ XTaskQueueHandle queue,
_In_ XTaskQueuePort port,
_In_ uint32_t delayMs,
_In_opt_ void* callbackContext,
_In_ XTaskQueueCallback* callback);
No signature change is required — the issue is a runtime violation of the
existing delay guarantee.
Expected behavior
A callback submitted with delayMs = N must not become dispatchable until at
least N milliseconds have elapsed since submission, regardless of:
- whether other composite queues sharing the same underlying port are
terminated concurrently
- whether the internal delayed-callback timer has been retargeted for an
earlier entry since submission
- whether two delayed callbacks happen to land on the same or nearly the same
deadline
Actual behavior
Three distinct failure modes violate the delay contract:
-
Termination early-promotion — When a composite queue is terminated via
XTaskQueueTerminate, its pending delayed entries are flushed. If another
composite queue shares the same delayed port, the termination path can make
the sibling's not-yet-due entry dispatchable immediately.
-
Stale timer notification — A platform timer callback that was enqueued
for an earlier deadline can arrive after the timer has been retargeted for a
later entry. The old code treated any timer fire as proof that the current
armed deadline had elapsed, promoting the later entry too early.
-
Equal-deadline aliasing — When two delayed callbacks are submitted with
the same or very close delay values, the old promotion logic matched pending
entries by exact timestamp identity. This made correctness depend on whether
two uint64_t due times happened to be bitwise equal rather than on the
simpler question of whether the callback's deadline had actually passed.
In consumer code the common external symptom is the same in all three cases:
a timed operation reports completion or timeout before the requested wait
period has elapsed.
Reproduction conditions
The failures are timing-sensitive and become easier to trigger under:
- composite queue topologies where multiple queues share a single delayed port
(the common pattern for GDK title task queues)
- workloads with frequent
XTaskQueueTerminate calls while sibling queues
still have pending delayed work
- bursts of delayed submissions with identical or near-identical delay values
- platforms where the OS timer subsystem and
steady_clock use different
underlying clock sources (Win32 threadpool timers vs. QPC-based steady_clock)
Impact
- Violates the documented delay contract of
XTaskQueueSubmitDelayedCallback.
- Can produce incorrect behavior in any consumer that depends on a minimum wait
before a callback fires — including retry timing, connection timeout logic,
and deferred state-machine transitions.
- Becomes more likely as composite queue density and termination frequency
increase, which is the typical pattern for multiplayer title workloads.
Affected area
The root cause is in the delayed-callback scheduling and promotion logic:
Source/Task/TaskQueue.cpp — pending-entry promotion during timer callbacks
and queue termination
Source/Task/WaitTimer_win32.cpp, WaitTimer_stl.cpp,
Source/Task/iOS/ios_WaitTimer.mm — platform timer backends that supply the
time base for delay tracking
Summary
Callbacks submitted through
XTaskQueueSubmitDelayedCallbackcan becomedispatchable before their requested delay has elapsed. The contract of this API
is that the callback will not be invoked until at least
delayMsmillisecondshave passed, but under certain runtime conditions the callback surfaces early —
sometimes immediately.
This affects any code that relies on
XTaskQueueSubmitDelayedCallbackfortimed waits, retry back-off, debouncing, or deferred continuations. The
symptoms appear as spurious early timeouts, premature retry loops, and
timing-sensitive async failures that are difficult to reproduce in isolation.
Public API affected
No signature change is required — the issue is a runtime violation of the
existing delay guarantee.
Expected behavior
A callback submitted with
delayMs = Nmust not become dispatchable until atleast N milliseconds have elapsed since submission, regardless of:
terminated concurrently
earlier entry since submission
deadline
Actual behavior
Three distinct failure modes violate the delay contract:
Termination early-promotion — When a composite queue is terminated via
XTaskQueueTerminate, its pending delayed entries are flushed. If anothercomposite queue shares the same delayed port, the termination path can make
the sibling's not-yet-due entry dispatchable immediately.
Stale timer notification — A platform timer callback that was enqueued
for an earlier deadline can arrive after the timer has been retargeted for a
later entry. The old code treated any timer fire as proof that the current
armed deadline had elapsed, promoting the later entry too early.
Equal-deadline aliasing — When two delayed callbacks are submitted with
the same or very close delay values, the old promotion logic matched pending
entries by exact timestamp identity. This made correctness depend on whether
two
uint64_tdue times happened to be bitwise equal rather than on thesimpler question of whether the callback's deadline had actually passed.
In consumer code the common external symptom is the same in all three cases:
a timed operation reports completion or timeout before the requested wait
period has elapsed.
Reproduction conditions
The failures are timing-sensitive and become easier to trigger under:
(the common pattern for GDK title task queues)
XTaskQueueTerminatecalls while sibling queuesstill have pending delayed work
steady_clockuse differentunderlying clock sources (Win32 threadpool timers vs. QPC-based steady_clock)
Impact
XTaskQueueSubmitDelayedCallback.before a callback fires — including retry timing, connection timeout logic,
and deferred state-machine transitions.
increase, which is the typical pattern for multiplayer title workloads.
Affected area
The root cause is in the delayed-callback scheduling and promotion logic:
Source/Task/TaskQueue.cpp— pending-entry promotion during timer callbacksand queue termination
Source/Task/WaitTimer_win32.cpp,WaitTimer_stl.cpp,Source/Task/iOS/ios_WaitTimer.mm— platform timer backends that supply thetime base for delay tracking