Skip to content

WaitTimer Start, Cancel, and Terminate operations can race #1004

Description

@jhugard

Summary

WaitTimer permits Start() and Cancel() to overlap Terminate() on the same timer instance. When termination runs concurrently with either operation, the operation can continue after the timer implementation has been destroyed.

This is a wrapper-level lifetime race. It is distinct from the STL queue teardown issue #1003 : it exists at the internal WaitTimer ownership boundary and affects every backend that implements the shared wrapper contract.

Expected behavior

Start(), Cancel(), and Terminate() must have a coherent concurrent lifetime contract:

  • An operation that observes a live timer implementation must be allowed to finish using that implementation before destruction begins.
  • Once termination takes ownership of the implementation, subsequent Start() and Cancel() calls must observe that the timer has been terminated and must not access retired state.
  • Terminate() must not deadlock against a concurrent or re-entrant Start() or Cancel(), including the case where a timer callback tears down its own timer.

No public API signature change is required.

Actual behavior

A Start() or Cancel() call can obtain a reference to the timer implementation while another thread begins Terminate(). Termination can then destroy the implementation before that operation finishes using it.

This can cause use-after-free behavior. Depending on timing and allocator behavior, the observable result may be a crash, memory corruption, or an intermittent failure that is difficult to attribute to the timer operation.

Reproduction

Reproducing the race deterministically requires pausing Start() or Cancel() at the instant it has observed the timer implementation while Terminate() runs concurrently on another thread. That interleaving is internal to the WaitTimer wrapper and is not reachable through the public XTaskQueue API alone, because live queue and callback references keep the owning port alive across public scheduling calls. Without that control the failure is timing-sensitive and surfaces intermittently as a crash or memory corruption.

Scope and impact

The affected internal wrapper is shared by the STL, Win32, and iOS timer backends. The race is therefore not limited to a particular timer primitive or operating system.

Applications are most likely to encounter it when one execution path re-arms or cancels a delayed operation while another path concurrently tears down the owning queue, request, or component.

Relationship to existing timer work

PR #975 and PR #998 focused on delayed-callback scheduling and STL backend behavior. This report concerns the shared WaitTimer ownership contract itself and should be addressed independently of the backend-specific strand and teardown concerns.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions