30

Is there any tool or technique to detect "left behind" or "currently alive" observables, subscriptions.

Just recently discovered a pretty nasty memory leak where components were kept alive due to missing "unsubscribe" calls. I read about "takeUntil" approach which seems pretty good. https://stackoverflow.com/a/41177163/2050306

However I'm still wondering is there any tool (browser extension etc.) for that. Augury does not cover this area as far as I know.

All input is greatly appreciated.

robert
  • 5,742
  • 7
  • 28
  • 37
  • 1
    It's a pretty good question. I guess the approach would be to treat it like an e2e test scenario and stub `subscribe()` with pass-through, then within the stub to monitor subscriptions. There's a flag on the observable which indicates 'closed', which would cover subscriptions that complete and therefore do not need an explicit unsubscribe. –  Feb 12 '19 at 21:24
  • @ischenkodv your edit is faulty - [Augury](https://augury.rangle.io) is the tool robert is referring to. Please revert. –  Feb 12 '19 at 21:29
  • @HiramK.Hackenbacker Yes, Augury the extension. That e2e test scenario is interesting do you have any working code for that? – robert Feb 12 '19 at 22:15
  • @HiramK.Hackenbacker sorry, reverted. – ischenkodv Feb 13 '19 at 11:24
  • Have a look at this library: https://github.com/acutmore/leaks/blob/master/leaks.js – jo_va Feb 16 '19 at 01:06
  • @jo_va looks promising unfortunately no luck making it work with Angular 7. – robert Feb 16 '19 at 18:11
  • RxJava subscription leaks are particularly insidious in Android applications as well. If all else fails you could take inspiration from this library I wrote to debug Rx leaks on Android: https://github.com/mykwillis/RxLeakCheck – Myk Willis Apr 11 '19 at 01:29
  • @MykWillis Thank you for your input I looked into RxLeakCheck.java – robert Apr 12 '19 at 20:18

1 Answers1

25

Disclaimer: I'm the author of the tool I mention below.

This can be accomplished by keeping a list where new subscriptions are added to, and removing subscriptions from this list once it is unsubscribed.

The troublesome part is observing subscriptions. A straightforward way to achieve this is by monkey-patching the Observable#subscribe() method, that is, replacing the Observable prototype method.

This is the overall approach of observable-profiler, a development tool which hooks into an Observable library (i.e rxjs) and prints leaking subscriptions in console.

A simple way to use the profiler is start tracking once the app is bootstraped, then stop tracking after a time:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { Observable } from 'rxjs';
import { setup, track, printSubscribers } from 'observable-profiler';

setup(Observable);
platformBrowserDynamic([])
    .bootstrapModule(AppModule)
    .then(ref => {
        track();
        window.stopProfiler = () => {
            ref.destroy();
            const subscribers = track(false);
            printSubscribers({
                subscribers,
            });
        }
    });

Just call stopProfiler() in devtools console once you want a report.

André Werlang
  • 5,839
  • 1
  • 35
  • 49
  • 1
    Thank you. This looks really promising. Will come back with my findings shortly. – robert Apr 12 '19 at 20:22
  • 1
    @robert feedback is appreciated! – André Werlang Apr 12 '19 at 22:20
  • @AndréWerlang does your tool work correctly with using pipe with takeUntil(...) for unsubscribing? I used your tool and I got many errors from Angular Material components. – Tomasz Nast May 14 '19 at 22:29
  • @AndréWerlang and it doesn't work correctly with streams closed with pipe(take(1) / first()), they are considered as not closed by your tool – Tomasz Nast May 14 '19 at 23:05
  • @Tomasz these operators you mention unsubscribe from the stream once the underlying stream emits, and the tool only reports as error when they don't. I have found leaks on a third-party component and in angular itself, so that could be the case. Provide a repo/repro and I'll take a look. – André Werlang May 15 '19 at 00:26
  • This tool does not work for me in angular 7/8: i get errors at index.js:202 Error at new SubscriptionSource (index.js:79) at EventEmitter.subscribe (index.js:112) at EventEmitter.push../node_modules/@angular/core/fesm5/core.js.EventEmitter.subscribe (core.js:22123) at TooltipDirective.push../node_modules/ngx-bootstrap/tooltip/fesm5/ngx-bootstrap-tooltip.js.TooltipDirective.ngOnInit (ngx-bootstrap-tooltip.js:332) at checkAndUpdateDirectiveInline (core.js:19341) at checkAndUpdateNodeInline (core.js:27597) at checkAndUpdateNode (core.js:27559) ... – Spikolynn Nov 26 '19 at 16:37
  • 1
    @Spikolynn please use GitHub's issue tracker to report issues. Also, add a repro. – André Werlang Nov 26 '19 at 16:40