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
WindowController.create(...) a sub-window.
- Force-close it:
PostMessage(hwnd, WM_CLOSE, 0, 0) to the sub-window's top-level HWND.
- Recreate via
WindowController.create(...).
- 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
- 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().
- 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.
Summary
When a
desktop_multi_windowsub-window is destroyed (e.g. it receivesWM_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 (onlyshow()/hide()), and the engine resources are not fully reclaimed when the window closes.Environment
desktop_multi_window: 0.3.0Observed (measured)
Windows handle count via
GetProcessHandleCount, across forced-destroy + auto-recreate cycles (PostMessageWM_CLOSEto the sub-window HWND, wait for the host app to recreate it):Analysis (from the 0.3.0 Windows source)
WindowController(Dart) exposes onlyshow()/hide()— there is noclose()/destroy().mixin.one/desktop_multi_windowhandles onlycreateWindow/getWindowDefinition/getAllWindows.FlutterWindow::OnDestroy()setsflutter_controller_ = nullptr(destroying theFlutterViewController) and callsRemoveManagedFlutterWindowLater(id). The managedFlutterWindowis only erased later viaCleanupRemovedWindows(), which is invoked solely on the nextCreate().FlutterViewController/engine teardown on Windows does not fully release its thread/event handles.Reproduction
WindowController.create(...)a sub-window.PostMessage(hwnd, WM_CLOSE, 0, 0)to the sub-window's top-level HWND.WindowController.create(...).GetProcessHandleCount(process)climb ~3 per cycle.Impact
Suggestions
WindowController.close()(Dart) +closeWindownative handler that destroys the sub-window and immediately tears down itsFlutterViewControllerand erases it frommanaged_flutter_windows_, instead of deferring to the nextCreate().FlutterViewControllerteardown itself leaks handles on Windows — if so, an upstreamflutter/flutterfollow-up may be warranted.Happy to provide the full measurement harness (Win32
EnumWindows/GetProcessHandleCount/GetGuiResources) if useful.