Skip to content

XTaskQueueSubmitDelayedCallback can dispatch work before the requested delay elapses #971

@jhugard

Description

@jhugard

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:

  1. 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.

  2. 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.

  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions