[Bug] Memory Leak: Deck.finalize() drops CanvasContext reference instead of destroying it, leaving ResizeObserver attached
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 14.6k
- Forks
- 2.3k
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 42
Description
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: trueto 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 underlyingSurface.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
- Script tag
- React
- Python/Jupyter notebook
- MapboxOverlay
- GoogleMapsOverlay
- CARTO
- ArcGIS
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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at deck.js:289 in Deck.finalize(), then inspect the CanvasContext and its destroy lifecycle. Reproduce the issue with the linked CodePen and verify that finalizing Deck releases the canvas and observers, including the ResizeObserver and matchMedia listener.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, typescript
- Domain
- frontend, performance
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100