Description
When using Deck or a MapboxOverlay, destroying the instance results in a memory leak. Detached DOM nodes and Deck instances are never garbage collected because internal luma.gl observers remain permanently attached.
I think there is a problem in the teardown lifecycle when _reuseDevices is enabled:
- MapboxOverlay passes
_reuseDevices: true to luma.gl. This causes WebGLDevice.destroy() to intentionally abort early so the WebGL context can be cached.
- Because the device skips teardown, it does not clean up its associated CanvasContext.
- In deck.js:289 (Deck.finalize()), deck.gl cleans up its internal canvas wrapper like this:
this._canvasContext = null;
- Because deck.gl drops the reference without explicitly calling
this._canvasContext.destroy(), the underlying Surface.destroy() method in luma.gl is never triggered.
- As a result,
_stopObservers() is never called. The CanvasObserver (which attaches a ResizeObserver and matchMedia listener) remains active, creating a strong GC root that holds the detached and Deck instance in memory forever.
I have created a codepen where I found out that the issue is not only in a MapboxOverlay but its in the Deck class.
Flavors
Expected Behavior
If I explicitly call Deck.finalize I expect that all reassures are released.
So Deck.finalize() must explicitly call destroy() on the CanvasContext before nullifying it.
Steps to Reproduce
https://codepen.io/LeLunZ/full/qEqGRYa
Click on "Run 10x" then open dev tools + run garbage collection + click on the correct vm instance on the Bottom right -> take a heap snapshot -> search for canvas, WebGL2RenderingContext, or ResizeObserver.
You will see 10 of each that don't get garbage collected.
You can also check the logs they will show "RO #3 created" but never "RO #3 disconnected"
Environment
- Framework version: Angular 20 (but also is an issue in native js)
- Browser: Chrome/Safari
- OS: Mac OS
Logs
No logs
Description
When using Deck or a MapboxOverlay, destroying the instance results in a memory leak. Detached DOM nodes and Deck instances are never garbage collected because internal luma.gl observers remain permanently attached.
I think there is a problem in the teardown lifecycle when _reuseDevices is enabled:
_reuseDevices: trueto luma.gl. This causes WebGLDevice.destroy() to intentionally abort early so the WebGL context can be cached.this._canvasContext = null;this._canvasContext.destroy(), the underlyingSurface.destroy()method in luma.gl is never triggered._stopObservers()is never called. The CanvasObserver (which attaches a ResizeObserver and matchMedia listener) remains active, creating a strong GC root that holds the detached and Deck instance in memory forever.I have created a codepen where I found out that the issue is not only in a MapboxOverlay but its in the Deck class.
Flavors
Expected Behavior
If I explicitly call
Deck.finalizeI expect that all reassures are released.So
Deck.finalize()must explicitly call destroy() on the CanvasContext before nullifying it.Steps to Reproduce
https://codepen.io/LeLunZ/full/qEqGRYa
Click on "Run 10x" then open dev tools + run garbage collection + click on the correct vm instance on the Bottom right -> take a heap snapshot -> search for canvas, WebGL2RenderingContext, or ResizeObserver.
You will see 10 of each that don't get garbage collected.
You can also check the logs they will show "RO #3 created" but never "RO #3 disconnected"
Environment
Logs
No logs