I'm searching for a way to monitor RxJS subscriptions to detect memory leaks in our application. The application is run in a browser.
My approach so far was to wrap the prototypes methods Observable.subscribe and Subscription.unsubscribe but that does not work. The counter is not accurate. I guess, unsubscribe is called several times in some cases. The counter shows negative numbers.
Are you aware of any library, hack or method to monitor RxJS subscriptions application wide without altering each subscription in the productive code? A typescript transformer would also be an option.
My first approach was this here:
let subscriptionCounter = 0
Object.defineProperty(Observable.prototype, '__subscribe', { value: Observable.prototype.subscribe })
Object.defineProperty(Observable.prototype, 'subscribe', { value: function <T> (observerOrNext?: Partial<Observer<T>> | ((value: T) => void)): Subscription {
subscriptionCounter++
console.log(subscriptionCounter, 'subscribe');
return (this as Observable<T>)['__subscribe'](observerOrNext)
}})
Object.defineProperty(Subscription.prototype, '__unsubscribe', { value: Subscription.prototype.unsubscribe })
Object.defineProperty(Subscription.prototype, 'unsubscribe', { value: function() {
subscriptionCounter--
console.log(subscriptionCounter, 'unsubscribe');
(this as Subscription)['__unsubscribe']()
}})
I think it should work with the following:
This ensures that you take into account
completebut alsoerror.I have made a Stackblitz to demo this, I've made a small chain example with 3 operators:
And this gives the following output:
As you can see, we start at 0 and the first subscription gives a count of 1, while at the end, we get a total count of 0, as expected.
What's interesting to notice here is between log lines 7-10, after the first 3 values where logged. Because the
fromhas sent everything at this stage, it calls complete which will have a cascade effect and close the entire stream. So firstfromcalls thecomplete(so total is2), thenmap(total is1) and then we get to theretry, which reopens the streams above, so instead of having a total of0, we start again the 2 above subscriptions, to go all the way up to a total of 3, and once that's all done, we do3,2,1,0as expected.Another thing to do in order to check for memory leaks is to launch your app, open the dev tools and go to the
Performancetab. Then press the record button. Play with your app, just like a regular user would do and stop the recording. You'll then see something like this:Here it was just on the Stackblitz page I had open but it doesn't matter. The important bits to look for here are:
ControlValueAccessorand it was hurting my app pretty badly as the memory would increase quite quickly and slowing everything down till it crashed. See all the post I made here: https://github.com/angular/angular/issues/20007#issuecomment-682831198If you wish to learn more about the performance panel, here's the official documentation.