Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

animationFrameScheduler stops working unexpectedly #7018

Open
EoinGriffin-AI opened this issue Jul 8, 2022 · 12 comments
Open

animationFrameScheduler stops working unexpectedly #7018

EoinGriffin-AI opened this issue Jul 8, 2022 · 12 comments
Assignees
Labels
7.x Issues and PRs for version 7.x 8.x Issues and PRs for version 8.x bug Confirmed bug

Comments

@EoinGriffin-AI
Copy link

EoinGriffin-AI commented Jul 8, 2022

Describe the bug

These operators in RxJS (7.5.4) just... stop working sometimes. What's going on? How can I fix this?

Update: I also tried this with auditTime instead and the same issue occurs, so I believe it's more related to animationFrameScheduler and not debounceTime

import { animationFrameScheduler, Observable, OperatorFunction } from 'rxjs';
import { debounceTime, tap } from 'rxjs/operators';


export function debounceAnimated<T>(): OperatorFunction<T, T> {
  return (source$: Observable<T>) => source$.pipe(
    tap(() => console.log('A')),
    debounceTime(0, animationFrameScheduler),
    tap(() => console.log('B')),
  );
}

This operator works just fine most of the time. I see in my app that the action using this operator works correctly, and in the console I see a bunch of A's followed by a bunch of B's, as expected when the debounceTime fires.

The problem is that sometimes this operator just... breaks somehow. I get a whole bunch of A's when I perform the user action (in this case using the mouse scroll to zoom in and out of a chart), but the B's stop completely and downstream observable subscriptions stop working too. The application doesn't recover when this happens. Everything else seems to be fine, CPU usage is low, and other components that don't use this operator continue to work normally.



For a bit more context, this feature has been working fine for a few years, but a recent RxJS upgrade (we upgraded from 6.6.2 to 7.5.4 to get a necessary bugfix) broke the previous implementation of this operator and so now I'm trying to find some alternative. The previous implementation used this code, but it no longer debounces anything when written like this: debounce(() => EMPTY.pipe(observeOn(scheduler)))

I also posted this same question on StackOverflow, in case anyone wants to follow there too.
https://stackoverflow.com/questions/72916854/rxjs-debouncetime-operator-with-animationframescheduler-stops-working-unexpected

Expected behavior

debounceTime with animationFrameScheduler should continue to work indefinitely as long as there are active subscribers.

Reproduction code

In my case this code is run many times. Several Observables (maybe 10?) use this custom debounceAnimated operator and it gets his about 50 times and so logs 'A' 50 times on a single mouse scroll event. Obviously I don't want to re-render my UI 50 times, which is why I need this to work.

import { animationFrameScheduler, Observable, OperatorFunction } from 'rxjs';
import { debounceTime, tap } from 'rxjs/operators';


export function debounceAnimated<T>(): OperatorFunction<T, T> {
  return (source$: Observable<T>) => source$.pipe(
    tap(() => console.log('A')),
    debounceTime(0, animationFrameScheduler),
    tap(() => console.log('B')),
  );
}

Reproduction URL

No response

Version

7.5.4

Environment

Chrome Version 103.0.5060.66 (Official Build) (64-bit)
Windows 10
Angular 13.2.6
Typescript 4.5.5

Additional context

No response

@EoinGriffin-AI EoinGriffin-AI changed the title debounceTime operator with animationFrameScheduler stops working unexpectedly animationFrameScheduler stops working unexpectedly Jul 8, 2022
@EoinGriffin-AI
Copy link
Author

A bit more info:
When I set a breakpoint in the animationFrameScheduler flush() function it stops getting called when this bug occurs.

@EoinGriffin-AI
Copy link
Author

EoinGriffin-AI commented Jul 9, 2022

Alright, some more information, and a workaround.
I added this setInterval to manually flush the scheduler, and it works! If the bug occurs, then within 5 seconds this will completely flush the scheduler and things will start working correctly again. From scrolling in and out a bunch of times, it looks like a bunch of actions pile up in the animationFrameScheduler but nothing is handling them so they just lay dormant.

But why does this work? Why is this necessary? Sorry but this is where my understanding of the inner working of RxJS is limited.
Can someone please advise? This is a kind of ugly workaround. I'm going to shorten the delay to 100ms or something, but it's still not great.

import { animationFrameScheduler, Observable, OperatorFunction } from 'rxjs';
import { debounceTime } from 'rxjs/operators';


setInterval(() => {
  while (animationFrameScheduler.actions.length > 0) {
    animationFrameScheduler.flush();
  }
}, 5000);

export function debounceAnimated<T>(): OperatorFunction<T, T> {
  return (source$: Observable<T>) => source$.pipe(
    debounceTime(0, animationFrameScheduler)
  );
}

@fdeslandes-wk
Copy link

I think this bug has the same root cause as #7017 .

As I mentionned there, the bug seem to have been caused by this code change

@c-goldschmidt
Copy link

c-goldschmidt commented Aug 5, 2022

Another similar problem that may be related:

we're using the animationFrameScheduler as a heartbeat. this used to work just fine:

hartBeat$ = scheduled(of(null), animationFrameScheduler).pipe(
    repeat(),
    share(),
);

with rxjs at version 7.5.6, this only gives a single beat now.

curiously, the fix for this is using the deprecated style of of using the scheduler argument:

hartBeat$ = of(null, animationFrameScheduler).pipe(
    repeat(),
    share(),
);

@benlesh
Copy link
Member

benlesh commented Sep 7, 2022

@c-goldschmidt interesting that's faster. of(null, scheduler) is the same as schedule([null], scheduler).

Honestly, though, I'd recommend using the animationFrames() API instead for your use case.

@benlesh
Copy link
Member

benlesh commented Sep 7, 2022

@EoinGriffin-AI FYI, I'd recommend trying debounce with animationFrames.

import { animationFrames, debounce } from 'rxjs';

source$.pipe(debounce(animationFrames()))

Otherwise, this looks like a bug related to #7017

@benlesh benlesh added bug Confirmed bug 7.x Issues and PRs for version 7.x 8.x Issues and PRs for version 8.x labels Sep 7, 2022
@EoinGriffin-AI
Copy link
Author

EoinGriffin-AI commented Sep 7, 2022

@benlesh Looks like that worked! Thanks!

It's noticeably slower than the setInterval workaround I implemented though. Maybe that's due to the bug you mentioned.
I'll play around with this a bit and see if I can get some better performance by restructuring my subscriptions.

benlesh added a commit to benlesh/rxjs that referenced this issue Sep 8, 2022
…heduler

+ Changes the check for existing action ids to simply check the last action in the queue to see if its id matches. Previously we were doing an O(n) loop on each execution of an action to check to see if the scheduling id needed to be recycled. This is problematic in AsapScheduler and AnimationFrameScheduler, where we're not reusing an interval. Since AsapScheduler and AnimationFrameScheduler reuse the most recent action id until their scheduled microtask or animation frame fires, the last action in the actions queue array is all we really need to check (rather than checking them all with `some`). O(1) vs O(n).
+ Refactors a weird conditional gaff from `if ((X && A) || (!X && B))` to just be `if (X ? A : B)`

resolves ReactiveX#7017
related ReactiveX#7018
related ReactiveX#6674
@benlesh
Copy link
Member

benlesh commented Sep 9, 2022

Yeah, the schedulers are due for a major overhaul in version 8. Long overdue, actually.

The fact that your solution is "faster" is a little disturbing though. I'll need to do more digging.

@benlesh
Copy link
Member

benlesh commented Sep 9, 2022

@EoinGriffin-AI thank you for getting me to look into this. We were creating way too many Subscription instances in that code. There's a PR up now to resolve that issue at #7060

@benlesh benlesh self-assigned this Sep 9, 2022
benlesh added a commit to benlesh/rxjs that referenced this issue Sep 9, 2022
Leverages a single id variable instead of creating a new child Subscription for each scheduled animation frame. This is important because animation frames are scheduled very rapidly and this helps prevent CPU and GC thrashing.

related ReactiveX#7018
benlesh added a commit that referenced this issue Sep 25, 2022
Leverages a single id variable instead of creating a new child Subscription for each scheduled animation frame. This is important because animation frames are scheduled very rapidly and this helps prevent CPU and GC thrashing.

related #7018
benlesh added a commit that referenced this issue Sep 25, 2022
…sapScheduler (#7059)

* refactor(schedulers): Appropriately type action ids

* fix(animationFrameScheduler): improve performance of animationFrameScheduler

+ Changes the check for existing action ids to simply check the last action in the queue to see if its id matches. Previously we were doing an O(n) loop on each execution of an action to check to see if the scheduling id needed to be recycled. This is problematic in AsapScheduler and AnimationFrameScheduler, where we're not reusing an interval. Since AsapScheduler and AnimationFrameScheduler reuse the most recent action id until their scheduled microtask or animation frame fires, the last action in the actions queue array is all we really need to check (rather than checking them all with `some`). O(1) vs O(n).
+ Refactors a weird conditional gaff from `if ((X && A) || (!X && B))` to just be `if (X ? A : B)`

resolves #7017
related #7018
related #6674

* chore: update api_guardian

* refactor(QueueAction): Have requestActionId return 0

Changes this to return `0` as a compromise given it was returning `void` in the past.
crapStone pushed a commit to Calciumdibromid/CaBr2 that referenced this issue Oct 5, 2022
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [rxjs](https://rxjs.dev) ([source](https://github.com/reactivex/rxjs)) | dependencies | patch | [`7.5.6` -> `7.5.7`](https://renovatebot.com/diffs/npm/rxjs/7.5.6/7.5.7) |

---

### Release Notes

<details>
<summary>reactivex/rxjs</summary>

### [`v7.5.7`](https://github.com/reactivex/rxjs/blob/HEAD/CHANGELOG.md#&#8203;757-httpsgithubcomreactivexrxjscompare756757-2022-09-25)

[Compare Source](ReactiveX/rxjs@7.5.6...7.5.7)

##### Bug Fixes

-   **schedulers:** improve performance of animationFrameScheduler and asapScheduler ([#&#8203;7059](ReactiveX/rxjs#7059)) ([c93aa60](ReactiveX/rxjs@c93aa60)), closes [#&#8203;7017](ReactiveX/rxjs#7017), related to [#&#8203;7018](ReactiveX/rxjs#7018) and [#&#8203;6674](ReactiveX/rxjs#6674)

##### Performance Improvements

-   **animationFrames:** uses fewer Subscription instances ([#&#8203;7060](ReactiveX/rxjs#7060)) ([2d57b38](ReactiveX/rxjs@2d57b38)), closes [#&#8203;7018](ReactiveX/rxjs#7018)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox.

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzMi4yMDIuNCIsInVwZGF0ZWRJblZlciI6IjMyLjIwOC4wIn0=-->

Co-authored-by: cabr2-bot <cabr2.help@gmail.com>
Reviewed-on: https://codeberg.org/Calciumdibromid/CaBr2/pulls/1562
Reviewed-by: Epsilon_02 <epsilon_02@noreply.codeberg.org>
Co-authored-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org>
Co-committed-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org>
@jscheid
Copy link

jscheid commented Oct 10, 2022

@benlesh thanks for all your work on this. I'm just wondering where this issue is at, I can see there are commits referencing this that were deployed in 7.5.7 but I'm still having similar problems as @EoinGriffin-AI and still stuck on 7.4.0. Is that what you would expect?

@jakovljevic-mladen
Copy link
Member

@jscheid, please check this comment that is related to this issue as well.

crapStone pushed a commit to Calciumdibromid/CaBr2 that referenced this issue Dec 23, 2022
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [rxjs](https://rxjs.dev) ([source](https://github.com/reactivex/rxjs)) | dependencies | minor | [`7.5.7` -> `7.8.0`](https://renovatebot.com/diffs/npm/rxjs/7.5.7/7.8.0) |

---

### Release Notes

<details>
<summary>reactivex/rxjs</summary>

### [`v7.8.0`](https://github.com/reactivex/rxjs/blob/HEAD/CHANGELOG.md#&#8203;780-httpsgithubcomreactivexrxjscompare770780-2022-12-15)

[Compare Source](ReactiveX/rxjs@7.7.0...7.8.0)

##### Features

-   **buffer:** `closingNotifier` now supports any `ObservableInput` ([#&#8203;7073](ReactiveX/rxjs#7073)) ([61b877a](ReactiveX/rxjs@61b877a))
-   **delayWhen:** `delayWhen`'s `delayDurationSelector` now supports any `ObservableInput` ([#&#8203;7049](ReactiveX/rxjs#7049)) ([dfd95db](ReactiveX/rxjs@dfd95db))
-   **sequenceEqual:** `compareTo` now supports any `ObservableInput` ([#&#8203;7102](ReactiveX/rxjs#7102)) ([d501961](ReactiveX/rxjs@d501961))
-   **share:** `ShareConfig` factory properties now supports any `ObservableInput` ([#&#8203;7093](ReactiveX/rxjs#7093)) ([cc3995a](ReactiveX/rxjs@cc3995a))
-   **skipUntil:** `notifier` now supports any `ObservableInput` ([#&#8203;7091](ReactiveX/rxjs#7091)) ([60d6c40](ReactiveX/rxjs@60d6c40))
-   **window:** `windowBoundaries` now supports any `ObservableInput` ([#&#8203;7088](ReactiveX/rxjs#7088)) ([8c4347c](ReactiveX/rxjs@8c4347c))

### [`v7.7.0`](https://github.com/reactivex/rxjs/blob/HEAD/CHANGELOG.md#&#8203;770-httpsgithubcomreactivexrxjscompare760770-2022-12-15)

[Compare Source](ReactiveX/rxjs@7.6.0...7.7.0)

##### Features

-   **distinct:** `flush` argument now supports any `ObservableInput` ([#&#8203;7081](ReactiveX/rxjs#7081)) ([74c9ebd](ReactiveX/rxjs@74c9ebd))
-   **repeatWhen:** `notifier` supports `ObservableInput` ([#&#8203;7103](ReactiveX/rxjs#7103)) ([8f1b976](ReactiveX/rxjs@8f1b976))
-   **retryWhen:** `notifier` now supports any `ObservableInput` ([#&#8203;7105](ReactiveX/rxjs#7105)) ([794f806](ReactiveX/rxjs@794f806))
-   **sample:** `notifier` now supports any `ObservableInput` ([#&#8203;7104](ReactiveX/rxjs#7104)) ([b18c2eb](ReactiveX/rxjs@b18c2eb))

### [`v7.6.0`](https://github.com/reactivex/rxjs/blob/HEAD/CHANGELOG.md#&#8203;760-httpsgithubcomreactivexrxjscompare757760-2022-12-03)

[Compare Source](ReactiveX/rxjs@7.5.7...7.6.0)

##### Bug Fixes

-   **schedulers:** no longer cause TypeScript build failures when Node types aren't included ([c1a07b7](ReactiveX/rxjs@c1a07b7))
-   **types:** Improved subscribe and tap type overloads ([#&#8203;6718](ReactiveX/rxjs#6718)) ([af1a9f4](ReactiveX/rxjs@af1a9f4)), closes [#&#8203;6717](ReactiveX/rxjs#6717)

##### Features

-   **onErrorResumeNextWith:** renamed `onErrorResumeNext` and exported from the top level. (`onErrorResumeNext` operator is stil available, but deprecated) ([#&#8203;6755](ReactiveX/rxjs#6755)) ([51e3b2c](ReactiveX/rxjs@51e3b2c))

#### [7.5.7](ReactiveX/rxjs@7.5.6...7.5.7) (2022-09-25)

##### Bug Fixes

-   **schedulers:** improve performance of animationFrameScheduler and asapScheduler ([#&#8203;7059](ReactiveX/rxjs#7059)) ([c93aa60](ReactiveX/rxjs@c93aa60)), closes [#&#8203;7017](ReactiveX/rxjs#7017), related to [#&#8203;7018](ReactiveX/rxjs#7018) and [#&#8203;6674](ReactiveX/rxjs#6674)

##### Performance Improvements

-   **animationFrames:** uses fewer Subscription instances ([#&#8203;7060](ReactiveX/rxjs#7060)) ([2d57b38](ReactiveX/rxjs@2d57b38)), closes [#&#8203;7018](ReactiveX/rxjs#7018)

#### [7.5.6](ReactiveX/rxjs@7.5.5...7.5.6) (2022-07-11)

##### Bug Fixes

-   **share:** No longer results in a bad-state observable in an edge case where a synchronous source was shared and refCounted, and the result is subscribed to twice in a row synchronously. ([#&#8203;7005](ReactiveX/rxjs#7005)) ([5d4c1d9](ReactiveX/rxjs@5d4c1d9))
-   **share & connect:** `share` and `connect` no longer bundle scheduling code by default ([#&#8203;6873](ReactiveX/rxjs#6873)) ([9948dc2](ReactiveX/rxjs@9948dc2)), closes [#&#8203;6872](ReactiveX/rxjs#6872)
-   **exhaustAll:** Result will now complete properly when flattening all synchronous observables. ([#&#8203;6911](ReactiveX/rxjs#6911)) ([3c1c6b8](ReactiveX/rxjs@3c1c6b8)), closes [#&#8203;6910](ReactiveX/rxjs#6910)
-   **TypeScript:** Now compatible with TypeScript 4.6 type checks ([#&#8203;6895](ReactiveX/rxjs#6895)) ([fce9aa1](ReactiveX/rxjs@fce9aa1))

#### [7.5.5](ReactiveX/rxjs@7.5.4...7.5.5) (2022-03-08)

##### Bug Fixes

-   **package:** add types to exports ([#&#8203;6802](ReactiveX/rxjs#6802)) ([3750f75](ReactiveX/rxjs@3750f75))
-   **package:** add `require` export condition ([#&#8203;6821](ReactiveX/rxjs#6821)) ([c8955e4](ReactiveX/rxjs@c8955e4))
-   **timeout:** no longer will timeout when receiving the first value synchronously ([#&#8203;6865](ReactiveX/rxjs#6865)) ([2330c96](ReactiveX/rxjs@2330c96)), closes [#&#8203;6862](ReactiveX/rxjs#6862)

##### Performance Improvements

-   Don't clone observers unless you have to ([#&#8203;6842](ReactiveX/rxjs#6842)) ([3289d20](ReactiveX/rxjs@3289d20))

#### [7.5.4](ReactiveX/rxjs@7.5.3...7.5.4) (2022-02-09)

##### Performance Improvements

-   removed code that would `bind` functions passed with observers to `subscribe`. ([#&#8203;6815](ReactiveX/rxjs#6815)) ([fb375a0](ReactiveX/rxjs@fb375a0)), closes [#&#8203;6783](ReactiveX/rxjs#6783)

#### [7.5.3](ReactiveX/rxjs@7.5.2...7.5.3) (2022-02-08)

##### Bug Fixes

-   **subscribe:** allow interop with Monio and other libraries that patch function bind ([0ab91eb](ReactiveX/rxjs@0ab91eb)), closes [#&#8203;6783](ReactiveX/rxjs#6783)

#### [7.5.2](ReactiveX/rxjs@7.5.1...7.5.2) (2022-01-11)

##### Bug Fixes

-   operators that ignore input values now use `unknown` rather than `any`, which should resolve issues with eslint no-unsafe-argument ([#&#8203;6738](ReactiveX/rxjs#6738)) ([67cb317](ReactiveX/rxjs@67cb317)), closes [#&#8203;6536](ReactiveX/rxjs#6536)
-   **ajax:** crossDomain flag deprecated and properly reported to consumers ([#&#8203;6710](ReactiveX/rxjs#6710)) ([7fd0575](ReactiveX/rxjs@7fd0575)), closes [#&#8203;6663](ReactiveX/rxjs#6663)

#### [7.5.1](ReactiveX/rxjs@7.5.0...7.5.1) (2021-12-28)

##### Bug Fixes

-   export supporting interfaces from top-level `rxjs` site. ([#&#8203;6733](ReactiveX/rxjs#6733)) ([299a1e1](ReactiveX/rxjs@299a1e1))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNC40OC4wIiwidXBkYXRlZEluVmVyIjoiMzQuNzAuNCJ9-->

Co-authored-by: cabr2-bot <cabr2.help@gmail.com>
Reviewed-on: https://codeberg.org/Calciumdibromid/CaBr2/pulls/1668
Reviewed-by: Epsilon_02 <epsilon_02@noreply.codeberg.org>
Co-authored-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org>
Co-committed-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org>
@kievsash
Copy link

kievsash commented Oct 31, 2023

checking

const s1 = new Subject();

const s2 = s1.pipe(map(x => (x + 1)));

combineLatest([s1, s2]).pipe(debounce((i) => animationFrames())).subscribe((x) => console.log('1: ', x))

s1.next(2) // initial;

s1.next(4)

but it doesnt consolelog any values in rxjs 7.8.1
Playground: https://playcode.io/1648054

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
7.x Issues and PRs for version 7.x 8.x Issues and PRs for version 8.x bug Confirmed bug
Projects
None yet
Development

No branches or pull requests

7 participants