Skip to content

Disconnect stale automation peers from UIA to fix memory leak in virtualized ItemsControls#11657

Open
amarinov-msft wants to merge 1 commit into
dotnet:mainfrom
amarinov-msft:amarinov-fix/automation-peer-disconnect-stale
Open

Disconnect stale automation peers from UIA to fix memory leak in virtualized ItemsControls#11657
amarinov-msft wants to merge 1 commit into
dotnet:mainfrom
amarinov-msft:amarinov-fix/automation-peer-disconnect-stale

Conversation

@amarinov-msft
Copy link
Copy Markdown

@amarinov-msft amarinov-msft commented May 21, 2026

Fixes #11337

Description

When UIA clients enumerate a virtualized ItemsControl (e.g. DataGrid), ElementProxy CCWs are created for each automation peer. Previously, when children were replaced (e.g. due to collection rebinding), the old peers were never disconnected from UIA Core. The COM references held by UIA kept the CCW ref count > 0, pinning the managed peers and their entire visual sub-trees in memory indefinitely.

This fix calls UiaDisconnectProvider on removed children's ElementProxy CCWs during UpdateChildrenInternal, causing UIA Core to release its COM references. This allows the CCW ref count to drop to zero so the managed peers can be garbage collected.

Note: UIA clients may observe ElementNotAvailableException when accessing properties of stale elements after disconnection. This is standard UIA behavior that well-behaved clients already handle.

Customer Impact

WPF applications using virtualized ItemsControl (e.g., DataGrid, ListView) that are exposed to UI Automation clients (screen readers, automated testing tools, accessibility inspectors) experience an unbounded memory leak of approximately 260 MB/min in the customer's repro scenario (200k-row DataGrid with periodic rebinding). The leak occurs because stale AutomationPeer instances are never disconnected from UIA Core - their ElementProxy CCW ref counts never reach zero, permanently pinning the managed peers and their visual sub-trees in memory. This leads to OutOfMemoryException and application crashes in long-running scenarios. Any application with accessibility enabled (which is the default when assistive technology or test automation is present) is affected.

Regression

No.

Testing

Tested using the customer`s repro scenario:
Base line memory leak rate: +260 MB/min
With fix: +15.3 MB/min (94% reduction)

Risk

Low overall.

  1. Narrow scope: The change affects only UpdateChildrenInternal in AutomationPeer.cs - a single code path that runs when automation children are replaced.
  2. Standard API usage: UiaDisconnectProvider is the documented Windows API for this exact purpose — telling UIA Core to release COM references to providers that are no longer valid. This is the same mechanism used by other frameworks (e.g., WinForms) to manage automation peer lifetimes.
  3. Non-recursive by design: The disconnect is intentionally non-recursive to avoid disconnecting shared container peers in virtualized controls that may have been recycled to serve new items.
  4. No behavioral change for apps without UIA clients: The disconnect logic only executes when UpdateChildrenInternal detects removed children, which only happens when automation is active and the tree structure changes.
  5. Expected UIA client-side behavior change: UIA clients that hold stale element references from a previous FindAll call may now receive ElementNotAvailableException when accessing properties of disconnected elements. This is standard UIA contract behavior that well-behaved clients (including Narrator, NVDA, and JAWS) already handle. Poorly written test automation tools that do not catch this exception may surface errors they previously did not encounter.
Microsoft Reviewers: Open in CodeFlow

…ualized ItemsControls

When UIA clients enumerate a virtualized ItemsControl (e.g. DataGrid),
ElementProxy CCWs are created for each automation peer. Previously,
when children were replaced (e.g. due to collection rebinding), the
old peers were never disconnected from UIA Core. The COM references
held by UIA kept the CCW ref count > 0, pinning the managed peers and
their entire visual sub-trees in memory indefinitely.

This fix calls UiaDisconnectProvider on removed children's ElementProxy
CCWs during UpdateChildrenInternal, causing UIA Core to release its
COM references. This allows the CCW ref count to drop to zero so the
managed peers can be garbage collected.

Key changes:
- Add DisconnectPeerFromUia() that disconnects a peer by calling
  UiaDisconnectProvider on its ElementProxy (non-recursive to avoid
  disconnecting shared container peers in virtualized controls)
- Move the StructureChanged event check after disconnect logic in
  UpdateChildrenInternal so disconnection happens regardless of event
  registration
- P/Invoke UiaDisconnectProvider from UIAutomationCore.dll

Note: UIA clients may observe ElementNotAvailableException when
accessing properties of stale elements after disconnection. This is
standard UIA behavior that well-behaved clients already handle.

Fixes dotnet#11337

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses a memory leak in WPF UI Automation (UIA) for virtualized ItemsControl scenarios by explicitly disconnecting UIA providers for removed automation peers, allowing COM references held by UIA Core to be released and enabling GC of stale peers and their visual subtrees.

Changes:

  • Added a helper to disconnect an AutomationPeer’s ElementProxy from UIA via UiaDisconnectProvider.
  • Updated UpdateChildrenInternal to disconnect removed children even when no StructureChanged listeners are registered.
  • Added the required interop import to call into UIAutomationCore.dll.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1838 to +1843
if (proxyWeakRef?.Target is ElementProxy proxy)
{
UiaDisconnectProvider(proxy);
}

peer._elementProxyWeakReference = null;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR metadata: Label to tag PRs, to facilitate with triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WPF leaks ElementProxy instances when UI Automation is used

2 participants