Skip to content

video-6336: media element leak causes error on chrome 92 #1530

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

Merged
merged 8 commits into from
Jul 26, 2021

Conversation

makarandp0
Copy link
Contributor

@makarandp0 makarandp0 commented Jul 24, 2021

chrome 92 started limiting the WebMediaPlayers. This breaks lot of webrtc applications. This fix ensures that 1) we do not leak media elements created internally and 2) we clear the srcObject before removing media elements.

The application that use RemoteAudioTrack.detach() method need to ensure that they clear srcObject on the returned elements as well.

related github issues: #1367 & #1528

Contributing to Twilio

All third-party contributors acknowledge that any contributions they provide will be made under the same open-source license that the open-source project is provided under.

  • I acknowledge that all my contributions will be made under the project's license.

@makarandp0 makarandp0 requested a review from charliesantos July 24, 2021 21:56
@makarandp0 makarandp0 self-assigned this Jul 24, 2021
@@ -118,24 +119,29 @@ class MediaTrack extends Track {
* @private
*/
_initialize() {
const self = this;
try {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need to keep this try catch block?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so. This fix ensures that we clean up the dummy element. but customers would still see the problem if they manage to create 75+ elements (possible on large rooms). In such cases its good to log the error for further confirmation and of the issue

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we throw a TwilioError instead? That way, it follows our convention when an error happens.

this._dummyEl.oncanplay = null;
this._dummyEl = null;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one @makarandp0 . So we are only detaching elements, not properly disposing them. I've learned that we also need to pause the element first, then, call load after setting the srcObject to null. Something like

this._detachElementInternal(this._dummyEl);
this._dummyEl.pause();
this._dummyEl.remove();
this._dummyEl.srcObject = null;
this._dummyEl.oncanplay = null;
this._dummyEl.load();
this._dummyEl = null;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, I will update and test per your suggestion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After experimenting a bit, I found that just setting srcObject to null is sufficient to avoid this issue. I will keep this simple and just reset srcObject as suggested as a workaround:

? () => playIfPausedAndNotBackgrounded(el, this._log)
: null;
this._elShims.set(el, shimMediaElement(el, onUnintentionallyPaused));
try {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this try catch block for debugging? Should we remove this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep this block, since the issue will still happen when 75+ elements are created.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we throw a TwilioError instead? That way, it follows our convention when an error happens.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TwilioErrors are heavy - require associated documentation and such. I think this error is fine for now. I am also hoping that this issue will get fixed by chrome as lot of apps are affected by it. https://bugs.chromium.org/p/chromium/issues/detail?id=1232649

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I found that the try block is not very useful as the error happens async and not caught by the block :(. I will remove the try block.

@@ -145,27 +151,35 @@ class MediaTrack extends Track {
_end() {
this._log.debug('Ended');
if (this._dummyEl) {
this._detachElement(this._dummyEl);
this._detachElementInternal(this._dummyEl);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to create a separate _detachElementInternal method? So on _end, calling _detachElementInternal will not update internal properties such as _attachments and _elShims right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need seperate function because _detachElement is no-op for _dummyEl as its not added to this._attachments
It was a bug that caused us to never detach _dummyEl

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good one.

@makarandp0 makarandp0 requested a review from charliesantos July 26, 2021 18:52
@@ -145,8 +145,10 @@ class MediaTrack extends Track {
_end() {
this._log.debug('Ended');
if (this._dummyEl) {
this._detachElement(this._dummyEl);
this._dummyEl.remove();
this._dummyEl.srcObject = null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to double check. Since we're not calling detachElement anymore, is setting srcObject to null enough, and we don't need to call removeTrack?

    const mediaStream = this._dummyEl.srcObject;
    if (mediaStream instanceof this._MediaStream) {
      mediaStream.removeTrack(this.processedTrack || this.mediaStreamTrack);
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats correct, removeTrack applies only for elements attached by application - since they may already have a mediaStream, in that case we add/remove track from it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants