Skip to content

Ensures cleanup of window event handlers - #11734

Open
Morten Nielsen (dotMorten) wants to merge 2 commits into
mainfrom
user/dotmorten/memleak_fix
Open

Ensures cleanup of window event handlers#11734
Morten Nielsen (dotMorten) wants to merge 2 commits into
mainfrom
user/dotmorten/memleak_fix

Conversation

@dotMorten

@dotMorten Morten Nielsen (dotMorten) commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

This avoids a memory leak caused by listening to Window events, either from user-code or generated code from x:bind which will subscribe to the activated event.

Fixes

Fixes #7282 and #9960

PR Type

  • Bugfix
  • Feature
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes, no API changes)
  • Build related changes
  • Documentation content changes
  • Other (please describe):

Description

Current Behavior

A closed desktop Window can remain alive when one of its events has a managed handler.

For example, an instance handling its own Closed event creates this retention chain:

DesktopWindowImpl event source
-> COM Global Interface Table registration
-> managed event callback
-> Window

DesktopWindowImpl stores handlers for Activated, Closed, SizeChanged, and VisibilityChanged in CFTMEventSource. Each handler is registered in the COM Global Interface Table. These registrations remain after the window closes and are normally revoked only when the event source is destroyed.

When a handler references the Window, this creates a cycle: the GIT registration keeps the managed callback and Window alive, which prevents DesktopWindowImpl and its event sources from being destroyed.

The issue also affects generated {x:Bind} code as reported in #7282. Bindings declared on a Window generate an Activated subscription similar to:

element1.Activated += bindings.Activated;

The generated bindings object references its Window through dataRoot, so this subscription can retain the Window and its object graph after close.

Applications can work around the issue by manually unsubscribing from every Window event, but that should not be required after the Window has permanently closed.

New Behavior

After a desktop Window closes successfully, WinUI clears its Activated, Closed, SizeChanged, and VisibilityChanged event sources before unpegging the Window.

Clearing an event source releases its GIT cookies, which revokes the native registrations and allows the managed Window, generated binding objects, and application data to be collected.

Handlers are cleared only after:

  1. The Closed event has been raised.
  2. The close has not been canceled with WindowEventArgs.Handled.
  3. The final VisibilityChanged(false) notification has been raised.

A canceled close therefore preserves all event subscriptions and existing behavior.

CFTMEventSource::Clear synchronizes with event raising and handler addition/removal before releasing its registrations.

A regression test creates a secondary Window that handles its own Closed event, closes it, forces garbage collection, and verifies that the Window is no longer alive

Customer Impact

WinUI desktop applications that repeatedly create and close secondary Windows no longer accumulate the closed Windows and their managed object graphs.

This includes applications that:

  • Subscribe a Window to one of its own events.
  • Capture a Window from an event handler.
  • Use {x:Bind} on a Window, which generates an Activated handler.
  • Store large view models or other application data beneath a secondary Window.

In the reported reproduction, each retained Window held a view model containing a 40 MB array, causing memory usage to grow by approximately 39 MB per closed Window.

Regression Potential

  • Low risk — isolated change, limited scope
  • Medium risk — touches shared components or public APIs
  • High risk — architectural or breaking API change

CFTMEventSource is shared infrastructure, but the new Clear operation is invoked here only for desktop Window event sources. The timing is intentional: handlers remain available while Closed and the final VisibilityChanged event are raised and are cleared only after the close becomes permanent.

How Has This Been Tested?

  • I have performed a self-review of my own code
  • I have added tests to cover my changes
  • Existing tests pass locally

Added VerifyClosedWindowWithSelfHandlerDoesNotLeak to MUXControlsTestApp to verify that a secondary Window handling its own Closed event is collectible after close.

Built Microsoft.UI.Xaml.dll successfully with no warnings or errors.

The original application was also tested against the locally built WinUI binaries using the unpackaged, self-contained deployment approach documented in GettingStarted.md.

Managed heap comparison

After creating and closing ten secondary Windows and forcing garbage collection:

Retained object Before After
SubWindow 10 0
SubViewModel 10 0
40 MB Int32[] 10 0
Managed heap ~401 MB ~1.08 MB

The same test was repeated with {x:Bind} enabled. No SubWindow, SubViewModel, generated bindings object, or large array remained after collection.

Additional create/close cycles remained bounded instead of growing linearly.

Screenshots (if appropriate)

Not applicable; this change has no visual impact.

This avoids a memory leak caused by listening to Window events, either from user-code or generated core from x:bind which will subscribe to the activated event.
@dotMorten
Morten Nielsen (dotMorten) requested a review from a team as a code owner August 31, 2026 20:21
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@microsoft-github-policy-service microsoft-github-policy-service Bot added the needs-triage Issue needs to be triaged by the area owners label Aug 31, 2026
@dotMorten

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
1 pipeline(s) were filtered out due to trigger conditions.

@dotMorten

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
1 pipeline(s) were filtered out due to trigger conditions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-triage Issue needs to be triaged by the area owners

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Memory leak when use x:Bind in winUI3

3 participants