I discovered the worst and most insidious memory leak in BabylonJS history

In my BabylonJS project, I used the queryObjects(Promise) command and found that several thousand Promise objects were leaking memory.As shown in the figure below.



I tried to analyze the memory leak Promise objects. I guessed it was GLTF loading module, so I tried to modify the source code of the module, and it worked.The culprit from GLTFFileLoader. prototype. loadAssetContainerAsync, here is my modified code.

GLTFFileLoader.prototype.loadAssetContainerAsync = function (scene, data, rootUrl, onProgress, fileName) {

this.onParsedObservable.notifyObservers(data);

this.onParsedObservable.clear();

this._log(`Loading ${fileName || ""}`);

this._loader = this._getLoader(data);

// Prepare the asset container.

const container = new AssetContainer(scene);

// Get materials/textures when loading to add to container

const materials = [];

this.onMaterialLoadedObservable.add((material) => {

    materials.push(material);

    material.onDisposeObservable.addOnce(() => {

        let index = container.materials.indexOf(material);

        if (index > -1) {

            container.materials.splice(index, 1);

        }

        index = materials.indexOf(material);

        if (index > -1) {

            materials.splice(index, 1);

        }

    });

});

const textures = [];

this.onTextureLoadedObservable.add((texture) => {

    textures.push(texture);

    texture.onDisposeObservable.addOnce(() => {

        let index = container.textures.indexOf(texture);

        if (index > -1) {

            container.textures.splice(index, 1);

        }

        index = textures.indexOf(texture);

        if (index > -1) {

            textures.splice(index, 1);

        }

    });

});

const cameras = [];

this.onCameraLoadedObservable.add((camera) => {

    cameras.push(camera);

});

return this._loader.importMeshAsync(null, scene, true, data, rootUrl, onProgress, fileName).then((result) => {

    Array.prototype.push.apply(container.geometries, result.geometries);

    Array.prototype.push.apply(container.meshes, result.meshes);

    Array.prototype.push.apply(container.particleSystems, result.particleSystems);

    Array.prototype.push.apply(container.skeletons, result.skeletons);

    Array.prototype.push.apply(container.animationGroups, result.animationGroups);

    Array.prototype.push.apply(container.materials, materials);

    Array.prototype.push.apply(container.textures, textures);

    Array.prototype.push.apply(container.lights, result.lights);

    Array.prototype.push.apply(container.transformNodes, result.transformNodes);

    Array.prototype.push.apply(container.cameras, cameras);

    return container;

});

};

The following is a comparison of the revised data.You can see that memory has fallen by a few hundred megabytes, and there are a few thousand fewer Promise objects that are leaking memory.But there are still hundreds of Promise objects that have memory leaks.


I opened the playground project, and an empty project also had dozens of Promise objects leaking memory.As shown in the figure below.

I’m sure there are several Promise memory leaks in the BabylonJS source code, and I can’t fix them all. It’s up to you to fix them yourself.

I have two suggestions: 1. Remove the nested Promise and chain calls and use async/await instead to make the code more elegant.2. Remove unnecessary asynchronous code.I found it pointless to execute synchronous code in Promises, which could potentially cause memory leaks, and I found a lot of such code in the source code.

1 Like

Adding @bghgary

This title :joy:

4 Likes

I’ll take a look, but this might take a bit of time.

I can repro this, but it looks like a bug in the browser. I can’t explain why the promises aren’t getting collected. No one is holding on to them. If I just type Promise.resolve(10) in the console, it will leak a Promise according to queryObjects(Promise). In fact, calling queryObjects(Promise) itself also leaks a promise (a pending one). I’m going to search the bug database for Chrome/V8 for existing issues.

I really wish we can do that and I hope we can soon. The reason why we don’t is because of IE compatibility. IE doesn’t support async/await. But maybe once IE is deprecated we can start using async/await.

This isn’t pointless. Wrapping the synchronous code in a promise makes it so that the exceptions get captured in the promise. If we don’t do this, the function will throw an synchronous exception instead which is unexpected for a promise returning function. Using the async keyword automatically does this, but we can’t use it due to the previous point.

Man, hopefully that doesn’t mean you’re on the hook for IE 11 until true sunsetting… It’s only deprecated for “certain OS” on June 2022. That basically means win 10 and server, not in general.

Why not adopt the policy that M365 is doing? They’ve deprecated IE for Teams already, it’ll be deprecated across all of M365 in August 2021

1 Like

We’ll have to see how it goes. Any thoughts @Deltakosh?

Update on this. It seems the console window is holding on the the promise. If I clear the console, the ones I typed in the console are no longer being reported.

1 Like

Each time queryObjects (Promise) is executed, a pending Promise is generated.
The trend is to move away from IE, and if you really want to support it, you can also support it through polyfill.

1 Like

I’m not sure what you are trying to say here. Yes, a pending Promise is generated. But why?

You can’t support async/await through polyfill since they are keywords of the JS language. We can use TypeScript to generate code that will work in IE while still using async/await in TypeScript, but this will bloat the code a lot.

EDIT: Also, we have a polyfill for the Promise itself.

Executing the queryObjects(Promise) command produces a pending Promise, which may be a Chrome bug.
Either way, it is wise to abandon support for IE, which will only slow down BabylonJS engine development.

I’ll let @Deltakosh comment on this.

None of this will fix the memory leak issue at hand though. :slight_smile:

1 Like

I hope to fix the memory leak as soon as possible.If this problem is resolved, I believe that the performance of the BabylonJS engine will be greatly improved.

We are keeping the current support for IE as is. We will not (and we are actually not already) taking it in account for new features. Every single new feature will not be slowed down by IE compat (this is true since the beginning of 5.0)

2 Likes

Filed issue: Multiple objects leaks when loading assets with scene loader · Issue #10433 · BabylonJS/Babylon.js (github.com)

I’ve managed to figure out some of the leaks, but there are still more to investigate.

2 Likes

Half a month, any new progress? I’m here to rush you!

If you look at the issue, there is an associated PR that has been merged. :slight_smile:

I am using the latest version 5.0.0-alpha.24, but the problem is still, is it not released yet?If so, it’s time to release a new version!

5.0.0-alpha.25 should have the fix

3 Likes