MixinNetwork / MixinNetwork/flutter-plugins

[desktop_multi_window] Handle leak (~3/cycle) when a sub-window is repeatedly destroyed and recreated

Open
#484 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
512
Forks
292
Avg merge
15h 8m
Merged PRs (30d)
7

Description

Summary

When a desktop_multi_window sub-window is destroyed (e.g. it receives WM_CLOSE, or dies for any reason) and then recreated, the host process leaks ~3 Windows handles per recreate cycle (linear). There is no public API to destroy a sub-window (only show()/hide()), and the engine resources are not fully reclaimed when the window closes.

Environment

  • Flutter 3.44.1 (stable), Windows 10 x64
  • desktop_multi_window: 0.3.0

Observed (measured)

Windows handle count via GetProcessHandleCount, across forced-destroy + auto-recreate cycles (PostMessage WM_CLOSE to the sub-window HWND, wait for the host app to recreate it):

cycles handles WorkingSet GDI USER
baseline 674 114.7 MB 25 39
after 15 720 (+46, ~3/cycle linear) 127.2 MB (+12.5) 25 (0) 40 (+1)
  • Recreation succeeds 15/15, process never crashes.
  • Normal operation (window kept alive, no recreation) shows no leak — handles stable (675 → 672) over 80s. So the leak is strictly tied to the destroy→recreate transition.

Analysis (from the 0.3.0 Windows source)

  • WindowController (Dart) exposes only show() / hide() — there is no close() / destroy().
  • The main channel mixin.one/desktop_multi_window handles only createWindow / getWindowDefinition / getAllWindows.
  • FlutterWindow::OnDestroy() sets flutter_controller_ = nullptr (destroying the FlutterViewController) and calls RemoveManagedFlutterWindowLater(id). The managed FlutterWindow is only erased later via CleanupRemovedWindows(), which is invoked solely on the next Create().
  • Even with that cleanup running, ~3 handles/cycle are not reclaimed — suggesting FlutterViewController/engine teardown on Windows does not fully release its thread/event handles.

Reproduction

  1. WindowController.create(...) a sub-window.
  2. Force-close it: PostMessage(hwnd, WM_CLOSE, 0, 0) to the sub-window's top-level HWND.
  3. Recreate via WindowController.create(...).
  4. Repeat. Watch GetProcessHandleCount(process) climb ~3 per cycle.

Impact

  • Typical apps: negligible — sub-windows rarely die, so they're rarely recreated.
  • Apps that auto-recreate a sub-window for resilience (e.g. an always-on-top overlay that respawns if it disappears) can accumulate handles over time if the window dies repeatedly.

Suggestions

  1. Add an explicit WindowController.close() (Dart) + closeWindow native handler that destroys the sub-window and immediately tears down its FlutterViewController and erases it from managed_flutter_windows_, instead of deferring to the next Create().
  2. Investigate whether FlutterViewController teardown itself leaks handles on Windows — if so, an upstream flutter/flutter follow-up may be warranted.

Happy to provide the full measurement harness (Win32 EnumWindows/GetProcessHandleCount/GetGuiResources) if useful.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with WindowController and the desktop_multi_window channel handlers for createWindow, then trace FlutterWindow::OnDestroy(), RemoveManagedFlutterWindowLater(), and CleanupRemovedWindows(). Reproduce repeated WM_CLOSE and recreation while measuring GetProcessHandleCount; done means destroyed sub-windows are fully reclaimed without linear handle growth, with coverage for the destroy-and-recreate path.

Written by the indexing model from the issue text.

Assessment

Tech stack
dart
Domain
desktop, operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.