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.
Summary
WaitTimerpermitsStart()andCancel()to overlapTerminate()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
WaitTimerownership boundary and affects every backend that implements the shared wrapper contract.Expected behavior
Start(),Cancel(), andTerminate()must have a coherent concurrent lifetime contract:Start()andCancel()calls must observe that the timer has been terminated and must not access retired state.Terminate()must not deadlock against a concurrent or re-entrantStart()orCancel(), including the case where a timer callback tears down its own timer.No public API signature change is required.
Actual behavior
A
Start()orCancel()call can obtain a reference to the timer implementation while another thread beginsTerminate(). 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()orCancel()at the instant it has observed the timer implementation whileTerminate()runs concurrently on another thread. That interleaving is internal to theWaitTimerwrapper and is not reachable through the publicXTaskQueueAPI 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
WaitTimerownership contract itself and should be addressed independently of the backend-specific strand and teardown concerns.