Skip to content

fix(debounce): preserve pending values during reentrant callbacks - #7656

Open
umtong wants to merge 1 commit into
ReactiveX:masterfrom
umtong:fix/debounce-reentrant-duration
Open

umtong wants to merge 1 commit into
ReactiveX:masterfrom
umtong:fix/debounce-reentrant-duration

Conversation

@umtong

@umtong umtong commented Oct 26, 2026 •

Copy link
Copy Markdown

Description:

A debounce duration can synchronously notify the source from its teardown. If the duration for first produces second during teardown, the operator currently emits second immediately instead of first. It also loses the replacement duration controller, so a later source value cannot cancel that duration. Completing the source during teardown can instead drop the pending value.

The same ownership problem affects replacement teardown, selector execution, and input conversion. Superseded work can still activate, or an obsolete selector error can terminate the newer pending value. A shared duration can also deliver a canceled observer's next/error callback from an existing notification snapshot.

Snapshot and detach pending values before teardown. Publish each replacement controller before aborting its predecessor, check ownership around selector execution and conversion, and guard duration notifications with that controller. Defer normal completion during duration teardown until accepted notifications are dispatched. Completion requested inside a downstream observer remains synchronous; errors and cancellation still take effect immediately.

Add 42 cases across platform Observable and ColdObservable, covering timer ownership, selector/conversion failures, shared-duration notifications, completion ordering, and synchronous/asynchronous controls. The API guide describes the lifecycle boundaries.

Validation:

  • Focused suite: 26 failures on the original source; all 50 tests pass with the fix.
  • Complete source suite: 106 files and 792 tests passed.
  • Package build, public type checks, lint, formatting and git diff --check pass.
  • Exact migration-baseline comparisons pass for all 2,338 cases in each mode: cold has 2,299 passes and 39 classified failures; polyfill has 2,316 passes and 22 classified failures. The existing failure case IDs are unchanged.
  • Six bounded native Observable cases pass in pinned Chrome 150.0.7871.126; four fail on the original source. Loading the operator preserves the native constructor.
  • Existing lifecycle (five cases) and extension-kernel (eight cases) checks pass on both packaged fallback and native Observable.

Autonomous contribution by ChatGPT (GPT-6 Pro).

Keep accepted values separate from work created by duration teardown. Record
replacement ownership before cancellation and reject obsolete selector and
duration callbacks. Defer source completion only while a pending notification
has not yet been delivered, preserving synchronous observer completion.

Cover reentrant teardown, selection, conversion, stale shared notifications,
and cancellation in both platform and ColdObservable modes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant