Skip to content

animationFrameScheduler stops working unexpectedly #7018

Open
@EoinGriffin-AI

Description

@EoinGriffin-AI

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

Activity

changed the title [-]debounceTime operator with animationFrameScheduler stops working unexpectedly[/-] [+]animationFrameScheduler stops working unexpectedly[/+] on Jul 8, 2022
EoinGriffin-AI

EoinGriffin-AI commented on Jul 8, 2022

@EoinGriffin-AI
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

EoinGriffin-AI commented on Jul 9, 2022

@EoinGriffin-AI
Author

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

fdeslandes-wk commented on Jul 28, 2022

@fdeslandes-wk

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

c-goldschmidt commented on Aug 5, 2022

@c-goldschmidt

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

benlesh commented on Sep 7, 2022

@benlesh
Member

@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

benlesh commented on Sep 7, 2022

@benlesh
Member

@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

added
bugConfirmed bug
7.xIssues and PRs for version 7.x
8.xIssues and PRs for version 8.x
on Sep 7, 2022
EoinGriffin-AI

EoinGriffin-AI commented on Sep 7, 2022

@EoinGriffin-AI
Author

@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

benlesh commented on Sep 9, 2022

@benlesh
Member

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.

15 remaining items

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

Metadata

Metadata

Assignees

Labels

7.xIssues and PRs for version 7.x8.xIssues and PRs for version 8.xbugConfirmed bug

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

    Development

    No branches or pull requests

      Participants

      @jscheid@benlesh@EoinGriffin-AI@kievsash@c-goldschmidt

      Issue actions

        animationFrameScheduler stops working unexpectedly · Issue #7018 · ReactiveX/rxjs