core icon indicating copy to clipboard operation
core copied to clipboard

Unsubscribe from "stream" does not clean up internal subscriptions

Open woppa684 opened this issue 7 years ago • 8 comments

I'm submitting a ... (check one with "x")

[x] bug report => check the FAQ and search github for a similar issue or PR before submitting
[ ] support request => check the FAQ and search github for a similar issue before submitting
[ ] feature request

Current behavior When I unsubscribe from the Observable returned by a "stream" method call, an internal subscription stays in memory.

Expected/desired behavior When I unsubscribe from a stream also the internal stuff is unsubscribed.

Reproduction of the problem http://plnkr.co/edit/fIi2Xl0XVpl5eXQYT7GP?p=preview

I tried to make a plnkr that actually did what I wanted to show but it was not very easy. This plnkr subscribes to a single string each 3 seconds and unsubscribes a second later. To then see the memory leak, I made heap snapshots (after running gc of course) in Chrome. The number of "Subscriber" objects keeps increasing. The gc root path of all the objects that remain in memory look like:

destination in MergeMapSubscriber @1247973
  parent in InnerSubscriber @1247983
    destination in SwitchMapSubscriber @1247989
      generatorOrNext in system / Context @1247995
        context in () @1248001
          _complete in SafeSubscriber @1248005
            destination in Subscriber @1247827
              [18] in Array @943305
                observers in EventEmitter @943251
                  onLangChange in TranslateStore @939611
                    store in TranslateService @939609
                       translate in AppComponent @941593

So it seems like the stream method does not clean up the subscription to onLangChange. I'm not sure whether this is a shortcoming of ngx translate or the rxjs switchMap/concat implementation by the way!

What is the expected behavior? No memory leak!

  • ngx-translate version: 8.0.0

  • Angular version: 5.0.2

  • Browser: [all? | Chrome]

woppa684 avatar Nov 22 '17 13:11 woppa684

Did you try to complete it instead of just unsubscribing ? I know that it's not a fix, but it could be a workaround for now

ocombe avatar Nov 22 '17 13:11 ocombe

Tried it in the plnkr now. Unfortunately I have the same results ... The workaround for now is to do a get and manually subscribe/unsubscribe to the onLangChange.

woppa684 avatar Nov 22 '17 13:11 woppa684

Thanks for the report as I was struggling with large memory leak issues in a medium-size app for some days now.

In fact, because of this memory leak you reported, the component (and ALL its children) subscribing (through async pipe in my case) to the observable from the .stream() call wasn't properly removed from memory (even after a garbage collector).

It can cause very large memory leak when you have some big nested components.

By the way thanks for the workaround, I have written a simple helper function which I use everywhere I was using .stream() previously :

export function streamTranslate(translateService: TranslateService, keys: string | Array<string>, params?: any): Observable<any> { return translateService.onLangChange.pipe( startWith({}), switchMap(() => params ? translateService.get(keys, params) : translateService.get(keys)) ); }

tzoratto avatar Apr 26 '18 16:04 tzoratto

Hi, Is this issue still valid? It's better use the workaround or this bug was fixed?

ghost avatar Nov 20 '20 09:11 ghost

Hi guys, i have the same problem right now with ngx-translate v13. For real i'll must unsubscribe manually stream ? because the get method not change when onLangChange emit the new value and we must use the stream method. For now i use the tzoratto fix and manually unsubscribe observale in ondestroy event. Any updates?

Thanks for the report as I was struggling with large memory leak issues in a medium-size app for some days now.

In fact, because of this memory leak you reported, the component (and ALL its children) subscribing (through async pipe in my case) to the observable from the .stream() call wasn't properly removed from memory (even after a garbage collector).

It can cause very large memory leak when you have some big nested components.

By the way thanks for the workaround, I have written a simple helper function which I use everywhere I was using .stream() previously :

export function streamTranslate(translateService: TranslateService, keys: string | Array<string>, params?: any): Observable<any> { return translateService.onLangChange.pipe( startWith({}), switchMap(() => params ? translateService.get(keys, params) : translateService.get(keys)) ); }

reiuz avatar May 31 '21 12:05 reiuz

Hi,

The problem is still present (2022)?

lufo88ita avatar Feb 16 '22 12:02 lufo88ita

same problem, any update (2023)?

wenpeng-song avatar Sep 15 '23 02:09 wenpeng-song