Skip to content

fix(flet-charts): prevent unbounded browser memory growth in MatplotlibChart#6473

Merged
FeodorFitsner merged 2 commits intorelease/v0.85.0from
web-memory-crash
May 7, 2026
Merged

fix(flet-charts): prevent unbounded browser memory growth in MatplotlibChart#6473
FeodorFitsner merged 2 commits intorelease/v0.85.0from
web-memory-crash

Conversation

@FeodorFitsner
Copy link
Copy Markdown
Contributor

@FeodorFitsner FeodorFitsner commented May 7, 2026

Summary

  • Replace the generic flet.canvas.Canvas + capture() flow used by MatplotlibChart with a dedicated MatplotlibChartCanvas widget that composites matplotlib diff frames in CPU memory. Holds at most one ui.Image at a time, avoiding the Picture.toImage allocations that accumulate on Flutter web (CanvasKit/WASM) and aren't reclaimed by the JS GC.
  • Without this fix, the matplotlib animation example grew to ~22 GB browser memory in 2–3 minutes and crashed the tab.

What's in the PR

New widget

  • MatplotlibChartCanvas (Python control + Flutter widget) with apply_full(bytes) / apply_diff(bytes) / clear() invoke methods. Decodes PNGs, keeps a Uint8List backbuffer, composites diff frames where alpha != 0, and shows the result via a single ui.Image rebuilt with decodeImageFromPixels.

Backend / chart wiring

  • MatplotlibChart switches from fc.Canvas shapes + capture() to the new widget. Rubberband (zoom selection) becomes a positioned Container overlay in a Stack.
  • TimerFletAsyncio subclass captures the main asyncio loop at construction so FuncAnimation keeps working when _timer_start() is invoked from a worker thread (used by our threaded render path).
  • Render runs on asyncio.to_thread on native runtimes so the asyncio loop stays free for pointer events. Pyodide / WASM falls back to inline rendering with aggressive yields, since to_thread runs synchronously there.
  • threading.Lock serializes worker-thread renders against main-thread figure.savefig to prevent the print_figure manager=None race that otherwise crashes Download.
  • Receive loop coalesces stale binary frames and stale draw JSON requests so pan/zoom doesn't visibly play back buffered motion.

flet core (flet.canvas.Canvas) image disposal

  • Dispose old _capturedImage / shape _image / intermediate Picture and Codec after replacement (post-frame for safety).
  • Gapless playback in drawImage: keep the cached image visible while new bytes decode, instead of going blank for a frame.

Safari fix

  • Defensive Uint8List.fromList(bytes) copy before instantiateImageCodec. Safari's WASM runtime can free buffers across async boundaries and otherwise throws EncodingError: Loading error.

Test plan

  • examples/extensions/charts/matplotlib_chart/animate in Edge — memory stays flat, animation smooth.
  • Same in Safari — no EncodingError, memory flat.
  • examples/extensions/charts/matplotlib_chart/toolbar — pan/zoom smooth, Download saves the file.
  • Native desktop run — animation, interaction, Download all work; Download blocks briefly on render-in-flight (expected).

Summary by Sourcery

Replace MatplotlibChart’s generic Canvas-based rendering with a dedicated image-stream canvas to stop unbounded browser memory growth and keep interactions responsive across platforms.

New Features:

  • Introduce MatplotlibChartCanvas widget to display WebAgg-style matplotlib image streams with full and diff frame support.
  • Expose MatplotlibChartCanvas and its resize event through the flet-charts Python API and Flutter extension registry.

Bug Fixes:

  • Prevent unbounded browser memory growth from Picture.toImage allocations during matplotlib animations, especially on Flutter web.
  • Fix race between worker-thread renders and main-thread figure.savefig that could crash downloads.
  • Ensure Safari no longer throws image decoding errors when rendering matplotlib charts.
  • Avoid visible playback of buffered pan/zoom motions by coalescing stale render frames and draw requests.
  • Eliminate blank-frame flicker when rapidly updating Canvas images by keeping the previous image visible until the new one is ready.
  • Dispose previously captured images, intermediate pictures, codecs, and cached Canvas images to avoid leaks.

Enhancements:

  • Run matplotlib renders on worker threads on native runtimes while keeping the main asyncio loop free for input handling, with a safe fallback path for Pyodide/WASM.
  • Add a TimerFletAsyncio implementation so matplotlib FuncAnimation timers can be started from worker threads without event loop errors.
  • Move Matplotlib rubberband selection from Canvas drawing to a Flutter overlay for simpler composition.
  • Serialize matplotlib drawing operations with a lock to coordinate threaded rendering and main-thread operations like saving figures.

…ibChart

Replace the generic Canvas+capture flow with a dedicated MatplotlibChartCanvas
widget that composites matplotlib diff frames in CPU memory. Holds at most
one ui.Image at a time, avoiding the Picture.toImage allocations that
accumulate on Flutter web (CanvasKit/WASM) and aren't reclaimed by the GC.

Also includes:
- Thread-safe TimerFletAsyncio so FuncAnimation works while we render in
  asyncio.to_thread on native runtimes
- Lock to serialize render thread with figure.savefig (download)
- Coalesce stale binary frames and draw requests during pan/zoom
- Defensive Uint8List copy before instantiateImageCodec to fix Safari
  "EncodingError: Loading error." on async PNG decode
- Dispose ui.Image resources and add gapless playback in flet.canvas.Canvas
- Pyodide fallback that renders inline (no real threads)
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 7, 2026

Deploying flet-examples with  Cloudflare Pages  Cloudflare Pages

Latest commit: 204e353
Status: ✅  Deploy successful!
Preview URL: https://efbdb9cd.flet-examples.pages.dev
Branch Preview URL: https://web-memory-crash.flet-examples.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

We've reviewed this pull request using the Sourcery rules engine

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 7, 2026

Deploying flet-website-v2 with  Cloudflare Pages  Cloudflare Pages

Latest commit: 204e353
Status: ✅  Deploy successful!
Preview URL: https://fd08f26a.flet-website-v2.pages.dev
Branch Preview URL: https://web-memory-crash.flet-website-v2.pages.dev

View logs

@FeodorFitsner FeodorFitsner merged commit 0bb1817 into release/v0.85.0 May 7, 2026
69 of 82 checks passed
@FeodorFitsner FeodorFitsner deleted the web-memory-crash branch May 7, 2026 01:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant