Disconnect stale automation peers from UIA to fix memory leak in virtualized ItemsControls#11657
Open
amarinov-msft wants to merge 1 commit into
Open
Conversation
…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>
Contributor
There was a problem hiding this comment.
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’sElementProxyfrom UIA viaUiaDisconnectProvider. - Updated
UpdateChildrenInternalto disconnect removed children even when noStructureChangedlisteners 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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
UiaDisconnectProvideron removed children's ElementProxy CCWs duringUpdateChildrenInternal, 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
ElementNotAvailableExceptionwhen 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
OutOfMemoryExceptionand 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.
UpdateChildrenInternalinAutomationPeer.cs- a single code path that runs when automation children are replaced.UiaDisconnectProvideris 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.UpdateChildrenInternaldetects removed children, which only happens when automation is active and the tree structure changes.FindAllcall may now receiveElementNotAvailableExceptionwhen 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