ReactiveX / ReactiveX/rxjs

debounceTime + 0 + asapScheduler doesn't emit in special case

Open
#7,205 9 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
31.7k
Forks
3k
PR merge metrics
No merged PRs in 30d

Description

Describe the bug

In a special case when we have two (or more) subscriptions on one subject with emitting to three (or more) other subjects (all subscriptions with debounceTime(0, asapScheduler)), all emissions stop after couple of steps. Prepared example looks like not real, but it is a model of a real example we have in the application (listening to form control changes with debounceTime(0, asapScheduler) to have only one emit per task, and value change can cause synchronous calculation that will change other controls).
In given example if you keep only subscription to subject1 and subject2 - works good; subject1 and subject2 and subject3 - works with a bit wrong sequence; subject1 and subject2 and subject3 and subject4 - stuck.

Expected behavior

All subscriptions "give" values, no "deadlock" in the process.

Reproduction code

No response

Reproduction URL

https://stackblitz.com/edit/typescript-evwjuc?file=index.ts

Version

7.8.0

Environment

No response

Additional context

The reason is that 'activeTask' inside 'debounceTime' stuck forever and is not realized, all new messages are ignored because new message is scheduled only "if (!activeTask)" (line 107). I am not totally sure, but I would say process looks like this:

  1. first subscription to subject1 receives the value and task is scheduled by asapScheduler with id=X
  2. second subscription also schedule task with id=X
  3. 'flush' is triggered in a state when scheduler._scheduler=X and first 'tap' executed for first subscription on subject1
  4. values to other subjects emitted in this 'tap' and tasks with other id are scheduled, scheduler._scheduled become Y
  5. action scheduled on step 2 is executed and 'emit()' in 'debounceTime' called 'activeTask.unsubscribe()'. Looks like 'unsubscribe()' triggers 'recycleAsyncId' for AsapAction and there scheduler._scheduled is set to null (line 39)
  6. when 'flash' is triggered for first task from step 4 scheduler_scheduled is null, so only one task is executed and other tasks with id=Y stays is 'actions' array
  7. finally we have 'activeTask' inside each 'debounceTime' that will never released by scheduler

I would say problem is that 'local' logic of 'debounceTime' depends on 'shared' variable '_scheduled', it is not clear why. It is ok to use '_scheduler' to decide if new task id should be planned or the same (line 22, AsapAction.ts), but this variable can be changed in different places, so when 'flash' is executed it is not guaranteed value is appropriate. I guess logic is that all actions with the same id should be executed one by one, so maybe it is better to pass 'flushId' directly to 'flush' method as an argument or take 'flushId' from first 'action' in the array.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked StackBlitz reproduction, then inspect debounceTime and AsapAction.ts, especially the activeTask and scheduler paths described in the report. Done means the multi-subscription case continues emitting without a stuck task and preserves the expected sequence.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.