-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Memory leaks caused by shareReplay when shared observable is synchronous or continuous #5931
Comments
Hi @MaximSagan, I just spent some time looking into this. However, I haven't touched Angular since AngularJS 😬 , so forgive me if I'm missing something here. Unless I'm missing something, I think that the only thing that seems problematic is the fact that the I think that it's understandable that I just opened #5932, which I think that should solve (at least) the issue with the memory leaks produced by streams that complete synchronously. |
Hey @josepot, I really appreciate your quick response and PR.
Maybe I'm missing something, but I thought the point of |
Hey @MaximSagan, sorry for confusing you. My bad, you are right: the fact that the observable produced by |
Hi @MaximSagan, I've been able to test the changes of my PR against your example, and those changes seem to fix the memory leak. Could you please confirm that's the case, please? |
@josepot Works perfectly in my example! Thanks so much! |
Okay... so looking into this there's a few things:
TL;DR: the default use of By default, People have consistently been fooled by this, and there have been more than a few issues opened. At one point it was proposed that we move to a reference counted method (what you were expecting), however the community was split... 50/50 on that decision, and it would break a lot of code, so we introduced a configuration object to allow users to opt-in to the behavior you're expecting. What you wanted to do, on that never-ending interval was I've updated your example to use Further reading: We're making all of the sharing operators a lot more explicit in their configuration and unifying them at the same time in an upcoming release. Hopefully this helps get rid of some of the mistakes/confusion people are making around this. I'm very sorry that this has cost you time and effort, and I really do appreciate all of the effort you've put in. This has been a tricky API to manage, from a community standpoint. |
I appreciate the comprehensive response. It's good to know that RxJS 7 solves this issue in my first example.
This is a fair enough statement to make, but I'm not sure that people are going to listen. I often see people using I know that you've already reviewed the PR I made (#5934), where I identified that at a lower level, the memory leak is due to |
I am confused by this:
This option exists in v6.6.3. Are you saying this option has a bug in v6.6.3 but works as intended in v7 or should it work as above in v6.6.3 as well? In which case why do you need to upgrade to v7 to fix this? Regarding this comment:
I agree with @MaximSagan, I don't think this is an edge case. We are using it to cache computationally-intensive logic when using aurelia-store (a redux like state store based on BehaviorSubject). I assumed shareReplay({ bufferSize: 1, refCount: true }) would unsubscribe the source. |
…5254) This addresses 2 non-trivial sources of memory leaks observed when filtering tags in the Time Series dashboard. a) CardObserver used to track Elements that were destroyed via ngOnDestroy and stored them in a `Set<Element>`. Now with a WeakSet instead, we cover cases when the browser's IntersectionObserverEntry is not handled for some reason. b) The rxjs operator "shareReplay" has some known gotchas [1], [2]. When more observables subscribe to a source containing `shareReplay(1)` in its operator chain, the source remains subscribed even after the other observables unsubscribe. This change uses the `takeUntil(ngUnsubscribe$)` pattern to ensure any component using a `shareReplay` will not keep it subscribed after the component is gone. Alternatively we could use `refCount`, but this opts for the `takeUntil` pattern already used widely in the codebase. Manually checked that the dashboard still operates properly, and in Angular's dev mode, using a specific demo logdir, adding and clearing a "." tag filter has these characteristics: Before this change: 5.8 MB, 5.5K DOM nodes added each cycle After this change: 0.05 MB, <10 DOM nodes added each cycle Googlers, see b/196996936 [1] ReactiveX/rxjs#5034 [2] ReactiveX/rxjs#5931
…ensorflow#5254) This addresses 2 non-trivial sources of memory leaks observed when filtering tags in the Time Series dashboard. a) CardObserver used to track Elements that were destroyed via ngOnDestroy and stored them in a `Set<Element>`. Now with a WeakSet instead, we cover cases when the browser's IntersectionObserverEntry is not handled for some reason. b) The rxjs operator "shareReplay" has some known gotchas [1], [2]. When more observables subscribe to a source containing `shareReplay(1)` in its operator chain, the source remains subscribed even after the other observables unsubscribe. This change uses the `takeUntil(ngUnsubscribe$)` pattern to ensure any component using a `shareReplay` will not keep it subscribed after the component is gone. Alternatively we could use `refCount`, but this opts for the `takeUntil` pattern already used widely in the codebase. Manually checked that the dashboard still operates properly, and in Angular's dev mode, using a specific demo logdir, adding and clearing a "." tag filter has these characteristics: Before this change: 5.8 MB, 5.5K DOM nodes added each cycle After this change: 0.05 MB, <10 DOM nodes added each cycle Googlers, see b/196996936 [1] ReactiveX/rxjs#5034 [2] ReactiveX/rxjs#5931
…ensorflow#5254) This addresses 2 non-trivial sources of memory leaks observed when filtering tags in the Time Series dashboard. a) CardObserver used to track Elements that were destroyed via ngOnDestroy and stored them in a `Set<Element>`. Now with a WeakSet instead, we cover cases when the browser's IntersectionObserverEntry is not handled for some reason. b) The rxjs operator "shareReplay" has some known gotchas [1], [2]. When more observables subscribe to a source containing `shareReplay(1)` in its operator chain, the source remains subscribed even after the other observables unsubscribe. This change uses the `takeUntil(ngUnsubscribe$)` pattern to ensure any component using a `shareReplay` will not keep it subscribed after the component is gone. Alternatively we could use `refCount`, but this opts for the `takeUntil` pattern already used widely in the codebase. Manually checked that the dashboard still operates properly, and in Angular's dev mode, using a specific demo logdir, adding and clearing a "." tag filter has these characteristics: Before this change: 5.8 MB, 5.5K DOM nodes added each cycle After this change: 0.05 MB, <10 DOM nodes added each cycle Googlers, see b/196996936 [1] ReactiveX/rxjs#5034 [2] ReactiveX/rxjs#5931
Bug Report
Current Behavior
Under certain common conditions, use of the
shareReplay
operator will cause memory leaks. This is similar to what was documented in #5034, but although that issue was marked as resolved by #5044, it can be observed in the linked example that the problem persists to a considerable degree.Reproduction
https://stackblitz.com/edit/angular-a27w1c
https://angular-a27w1c.stackblitz.io/
AsyncSingleComponent
was indeed destroyed, theSyncComponent
and theAsyncContinuousComponent
have stuck around.Expected behavior
Each of the three components should have their class instances garbage-collected.
Environment
The text was updated successfully, but these errors were encountered: