Skip to content
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

ensure webaudio is removed from ram/cpu usage #2164

Merged
merged 9 commits into from
Jan 25, 2021

Conversation

entonbiba
Copy link
Contributor

fixes #1940

@coveralls
Copy link

coveralls commented Jan 23, 2021

Coverage Status

Coverage increased (+0.01%) to 82.454% when pulling 30526d4 on entonbiba-patch-webaudio-usage-fix into 658bd92 on master.

@entonbiba
Copy link
Contributor Author

entonbiba commented Jan 23, 2021

@katspaugh @thijstriemstra backend = null will happen as long as backend is available

if (this.backend) {
  this.backend.destroy();
  this.backend = null;
}

@sundayz
Copy link
Contributor

sundayz commented Jan 23, 2021

Your comment on #1940 says we should set this.backend to null but this commit sets this.backend.buffer to null, so which is it :)

Your research into the issue is great. if we know loading new audio allows the memory to be garbage collected, we can follow that code path and see what it is doing that we are not doing in the destroy() function.

@marizuccara
Copy link
Contributor

Hi, also the audio buffer is already set to null in the WebAudio backend (which is the only backend that uses the AudioBuffer).

I hope that setting the backend to null will resolve all your problems! :)

@sundayz
Copy link
Contributor

sundayz commented Jan 23, 2021

Here is something interesting I found that may be a piece of the puzzle...

WebAudio createSource sets a reference of the buffer on the source.

this.source.buffer = this.buffer;

WebAudio disconnects the source, but never sets it to null.

this.source.disconnect();

So if when we call destroy() a reference to the backend still exists, then a reference to the buffer will still also exist. Might be part of the problem. And could explain why calling load() again as entonbiba has shown frees the memory, because a new source is set and the old one is collected. I haven't tried setting source to null yet, and it may not be the only case where this happens.

@marizuccara
Copy link
Contributor

I found this interesting response on stackoverflow:
https://stackoverflow.com/questions/55207924/does-the-web-audio-api-clear-out-a-source-node-after-it-finishes-playing

On destroy we can call also stop() on the source node, if it's playing (in WebAudio). On onendend event, we can disconnect it, sets to null the buffer, and then the node, as you suggest

@entonbiba
Copy link
Contributor Author

@sundayz lol yes backend = null is correct, updated

Copy link
Contributor

@thijstriemstra thijstriemstra left a comment

Choose a reason for hiding this comment

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

thanks. can you also expand the existing test for destroy and add an assertion that backend is null. Also add a changelog entry please (with a reference to the ticket this pr is closing).

CHANGES.md Outdated Show resolved Hide resolved
@entonbiba
Copy link
Contributor Author

@thijstriemstra added null check,

@sundayz yes the observation about the load() is correct as its replacing backend with new. In the destroy case it would become null for this to work since even if we did .close or .disconnect it would still keep it as suspended taking up memory usage in webaudio.

@thijstriemstra
Copy link
Contributor

@entonbiba
Copy link
Contributor Author

@thijstriemstra I'll add expect(wavesurfer.backend).toBeNull(); after the destroy(); like below:

it('destroy', function(done) {
        manualDestroy = true;

        wavesurfer.once('destroy', function() {
            TestHelpers.removeElement(element);
            done();
        });
        wavesurfer.destroy();

        expect(wavesurfer.backend).toBeNull();
    });

src/wavesurfer.js Outdated Show resolved Hide resolved
Copy link
Owner

@katspaugh katspaugh left a comment

Choose a reason for hiding this comment

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

Great work!

@thijstriemstra thijstriemstra merged commit d200517 into master Jan 25, 2021
@thijstriemstra thijstriemstra deleted the entonbiba-patch-webaudio-usage-fix branch January 25, 2021 12:30
@sundayz
Copy link
Contributor

sundayz commented Jan 25, 2021

I haven't tried the new branch but in my project when I set backend = null, a little bit more memory gets collected but the 50MB arraybuffer still remains there.

@thijstriemstra
Copy link
Contributor

thijstriemstra commented Jan 25, 2021

In that case, re-open ticket closed by this pr: #1940

@entonbiba
Copy link
Contributor Author

@sundayz do you have the code you are using the create the instances ?
Just tried it again with backend = null and the results are working:

  • create
  • load
  • backend = null
    image

@sundayz
Copy link
Contributor

sundayz commented Jan 26, 2021

The file is 2000 lines long and it uses Vue so we should test in a code pen :D I will try that tomorrow. I just have a feeling that if the issue was as simple as backend = null then it would have been fixed a long time ago.

If your PR fixes the leak for most people then it's still a very good thing. I will try to reproduce the issue without Vue later.

Maria had some interesting insights and it might still be worth to set the source to null after disconnecting for example, just to make sure. But that can be done in a separate PR

@entonbiba
Copy link
Contributor Author

@sundayz if it's 2000 lines of vue then it's likely rendering a whole lot more than the audio file that's taking up memory.

The best test is to the library without any additional things. I just do create load destroy (backend=null) pr and repeat that multiple times with multiple loads at once to see the differences of usage.

ping me when the sample code is available I'll test it as well :)

@thijstriemstra
Copy link
Contributor

thijstriemstra commented Feb 24, 2021

Unfortunately this PR introduced a new issue with timing issues around backend = null. In unit tests for a wavesurfer related project I started seeing this error:

  TypeError: Cannot read property 'getCurrentTime' of null
      at WaveSurfer.getCurrentTime (node_modules/wavesurfer.js/dist/wavesurfer.js:4199:27)
      at d.value (dist/commons.js:8:71353)
      at d.value (dist/commons.js:8:69669)
      at d.value (dist/commons.js:8:74618)
      at node_modules/wavesurfer.js/dist/wavesurfer.js:3214:12
      at <Jasmine>
      at WaveSurfer.fireEvent (node_modules/wavesurfer.js/dist/wavesurfer.js:3213:28)
      at node_modules/wavesurfer.js/dist/wavesurfer.js:4133:16
      at node_modules/wavesurfer.js/dist/wavesurfer.js:3214:12
      at <Jasmine>

Commenting out the backend = null change from this PR makes the test pass again.

Basically any code in wavesurfer that relies on the backend property, e.g. wavesurfer.getCurrentTime() will now break if destroy() is called.

/**
     * Get the current playback position
     *
     * @example const currentTime = wavesurfer.getCurrentTime();
     * @return {number} Playback position in seconds
     */
    getCurrentTime() {
        return this.backend.getCurrentTime();
    }

This is not necessarily a bad thing, the instance was destroyed afterall, but should wavesurfer fix this (e.g. add a backend !== null check everywhere) or is it up to the user to check for an proper, existing wavesurfer.backend property?

@thijstriemstra
Copy link
Contributor

thijstriemstra commented Feb 24, 2021

but should wavesurfer fix this (e.g. add a backend !== null check everywhere) or is it up to the user to check for an proper, existing wavesurfer.backend property?

Decided it was a valid issue with my tests and this change brought it to light, so no worries, sorry for the noise.

@entonbiba
Copy link
Contributor Author

sounds good

sandiz pushed a commit to sandiz/wavesurfer.js that referenced this pull request Sep 1, 2021
* ensure webaudio is removed from ram/cpu usage

* update changes.md to include ticket katspaugh#1940

* added test for null

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

Successfully merging this pull request may close these issues.

Every call to wavesurferjs keeps on increasing RAM memory, without releasing it on destroy.
6 participants