16

After trying to understand why some of my Web application's objects were retained in memory by Chrome I think I may have narrowed it down to a case of a MediaRecorder object being retained by the browser even though the application has let go of all references to it, explicit and implicit.

Please observe the following minimal example that reproduces the issue:

new MediaRecorder(new MediaStream())

After evaluating the above expression -- a MediaRecorder object being created with no references (variables or properties) to it being assigned -- I profiled the heap usage using the Memory tab in both Chrome and Chromium, and the presence of the MediaRecorder object on the heap isn't transient -- the object is there for as long as I care to locate it on the heap. It doesn't disappear.

Why is it retained? Testing with a trivial class like Object instead -- evaluating new Object(), for example, behaves as expected -- garbage collection eventually collects the anonymous object. But not so with MediaRecorder. This smells an awful lot like a bug to me.

Firefox, in comparison, releases the MediaRecorder object in due time, considering the garbage collection of course, at least it isn't even on the heap seconds after I execute the statement above, not when I take the snapshot of it.

The media recorder object is not referenced by anything and should have the shortest lifetime it may ever have, yet it is there in memory afterwards in the snapshot I take after the console is cleared (the developers behind Developer Tools for Chrome recommend clearing the console before taking a heap snapshot because the former may hold on to objects that otherwise would be released).

I can't find any methods in the MediaRecorder class that would indicate to me one can disassociate it from a stream or otherwise "close" it. Short of making sure there are no obvious or not-so-obvious (e.g. through event listeners) references to it, I can only hope that an anonymous object isn't retained, and such objects usually aren't, but MediaRecorder objects seem to be. There doesn't seem to exist any lever for me to pull to dispose of one, so to speak.

You can see in the screenshot below that what objects retain the media recorder aren't exactly part of my scripts, they rather seem to be related to some internal browser state:

Screenshot of Memory pane in the Chrome Developer Tools

The window icon next to the object selected in the "Constructor" column has the tooltip "User object [is] reachable from window". The snippet above is the only code I run in the tab, why would the object be "reachable from window", and if it is, it surely cannot be any references I manage?

So why is the object retained? The bigger problem here is that if my application initiates many recordings and creates a new media recorder object for each, these objects will just keep piling up in memory, a de-facto case of memory leaking.

Like I said, I ran the same statement in Firefox 62.0.2 and there the behavior is as I would expect -- the single MediaRecorder object I created seems to go out of scope (as it should being how it should have no references to it) shortly after it is created.

(Chrome version 69.0.3497.100, x64 on Windows 10)

amn
  • 6,917
  • 6
  • 48
  • 75

3 Answers3

0

This was confirmed to be an issue with Chromium (and by implication Google Chrome and probably other Chromium derivatives, including Electron):

https://bugs.chromium.org/p/chromium/issues/detail?id=899722

At the time of writing this the issue is flagged as "available", which I presume communicates that it's acknowledged and effectively waiting for someone to step in and fix.

amn
  • 6,917
  • 6
  • 48
  • 75
-2

According to the specification:

A MediaStream object is said to be active when it has at least one MediaStreamTrack that has not ended. A MediaStream that does not have any tracks or only has tracks that are ended is inactive.

When you create a new MediaStream, unless you stop all its tracks, it remains in active state.

So, to unset this object, you need to explicitly get rid of its MediaStreamTrack objects, getting a reference to the stream e.g:

var myRecorder;
navigator.mediaDevices.getUserMedia({ 
  video: true,
  audio: true
}).then(function create_media_recorder(stream) {
  myRecorder = new MediaRecorder(stream);
  console.clear();
});

var myStream = myRecorder.stream;
myStream.getTracks().forEach(function(el) {
    el.stop()
}

after that, you should see the recording icon going away - The media stream is now inactive.

Check these API's for further reference

https://developer.mozilla.org/en-US/docs/Web/API/MediaStream

https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack

Victor Lia Fook
  • 430
  • 4
  • 15
  • After answering I realised I was assuming that after the recorder was inactive and after clearing the refs to it, the garbage collector would clean it. The snapshot didnt agree with me though. – Victor Lia Fook Nov 05 '18 at 06:28
  • 1
    The problem with your answer is that a) somehow you seem to imply that an "active" stream would be the cause of the associated MediaRecorder object remaining on the heap, while I don't see how you could make such a conclusion -- how do you arrive at it -- not the least because the specification doesn't even attempt to elaborate on what an "active" stream would entail here, and b) the issue I have described happens to persist even for a case of a stream with no tracks at all, like `new MediaRecorder(new MediaStream())`, so my educated guess here is the "active" state has little to do with it. – amn Nov 05 '18 at 08:42
  • 2
    Well, my comment made before yours indicates what happened. I made an assumption based on previous experience but it didn't hold true when I actually went to the console/snapshots checks. My guess was that with alive streams in the MediaRecorder the GC wouldnt pick it up. After I realised that even "empty" media recorders wouldnt be picked, I put the comment in. I'm sorry for that. – Victor Lia Fook Nov 05 '18 at 09:06
-4

Make sure you have "Preserve log upon navigation" unchecked in your browser's settings, otherwise console.clear() does nothing.

https://developer.mozilla.org/en-US/docs/Web/API/Console/clear

EDIT:

Also, I doubt this is a memory leak since unreachable objects are automatically removed. It's more likely MediaRecorder is saving objects to the global scope.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#Garbage_collection

"4.3.1 Life-cycle and Media Flow" should clear things up a bit.

https://www.w3.org/TR/mediacapture-streams/#stream-api

Zypps987
  • 406
  • 5
  • 18
  • 1
    Thanks, I have never had it checked in the first place. This probably should not be an answer, as the situation I described isn't really about `console.clear` -- that's just a minimally reproducible test case. Maybe you can add what you wrote as a comment instead? – amn Oct 31 '18 at 10:21
  • NOTE: Sorry for double comment, I screwed up the editing of my original comment so I had to delete it. I see a few potential solutions to this problem. Option 1. create a new instance of MediaRecorder and assign it to a variable, allowing you to overwrite the existing instance each time you want to record. Option 2. call MediaRecorder as a function, something tells me the new keyword is what's really screwing this up. Here's an example showing off Option 1. https://github.com/mdn/web-dictaphone/blob/gh-pages/scripts/app.js – Zypps987 Oct 31 '18 at 11:40
  • What do you mean by "overwrite the existing instance"? Keep in mind that I need to be able to record using different streams and MIME types, so I can't just reuse one single `MediaRecorder` object because any such object only encodes the stream it was supplied during creation, using the MIME type and other options supplied during creation as well. I _can_ circumvent the bug by using an object pool that uses a tuple consisting of a stream ID and options for keys, but this is about whether Chrome exhibits a bug. It may also be a feature, though, or a design decision. But it's not a good one. – amn Oct 31 '18 at 16:49
  • It's also not possible to create media recorder objects without the `new` facility, to my knowledge. I've just tried `MediaRecorder(...);` and I get an exception in Chrome. – amn Oct 31 '18 at 16:50
  • What about when you assign a new MediaRecorder object to a variable? Same behavior occurs? – Zypps987 Oct 31 '18 at 17:07
  • They just pile up -- creating a `MediaRecorder` object has Chrome retain that object in memory, regardless. In fact, I have just discovered that even creating a `MediaRecorder` object with an inactive stream without any tracks like `new MediaRecorder(new MediaStream())` _also_ leaves the object in memory, you don't even have to have a typical audio+video stream! – amn Oct 31 '18 at 17:15