Remove RxJS from PlaybackObserver to fix potential leak #1135
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
While testing the future v4.0.0, I noticed a memory leak which way well also affect previous RxPlayer versions.
The leak was that the
PlaybackObserver
may be still emitting even after playback has been stopped. The actual impacts are non-removed DOM event listeners, unnecessary logic running every seconds or so, and noisy logsAfter a lot of console.log-based investigation (as RxJS make the call stack inexploitable as well as step-by-step breakpoints), it seems that the issue is either due to a RxJS bug or of a poor comprehension I have of the
shareReplay
operator (I would go for the former, but I'm biased!).Basically, the leaking Observable (the one that is still running despite not being needed anymore) did not have any subscriber left. It had a
shareReplay
associated to it, with abufferSize
attribute set to1
and much more importantly arefCount
attribute set totrue
.The way I understand the
refCount
option is that the Observable will be shared (it is ashareReplay
after all) until no subscriber are left (the reference counter drops to0
) at which point the Observable will be unsubscribed from. This is also what I understand from the code documentation here:https://github.com/ReactiveX/rxjs/blob/47fa8d555754b18887baf15e22eb3dd91bf8bfea/src/internal/operators/shareReplay.ts#L41-L43
Yet it didn't seem to be the case. In my situation, no subscriber seemed to be left yet the Observable was still performing its side-effects, such as event listening and logs.
As no subscriber was left, nothing else happened, so the damage was limited.
Moreover, removing the
shareReplay
operator seemed to properly unsubscribe the Observable on which it was applied, which may be another proof that no subscriber was left (though it may not be by itself, but I can assure you that it was the case).Because it is complex to set-up reproduction steps, because I already found an open issue on the
shareReplay
operator and because we want to stop using RxJS anyway altogether in the long term, I preferred spending my time today rewriting thePlaybackObserver
so it does not depend on aReplaySubject
and even on RxJS.This was much easier than what I would have thought.
At least two subtle differences exists in behavior with the previous
PlaybackObserver
though:Previously the
PlaybackObserver
started emitting its first event on the first subscription, and (theoretically) restarted emitting if re-subscribed after being totally unsubscribed from anyone.Now, the
PlaybackObserver
is started right away on instanciation.Previously, the
PlaybackObserver
(theoretically!!!!!) stopped emitting when all subscribers unsubscribed from it.Now, there is an explicit
stop
method, called by thepublic_api.ts
file (which is the file creating thePlaybackObserver
in the first place, so it makes sense).