Break the reference chain so old containers can be garbage collected.#11717
Break the reference chain so old containers can be garbage collected.#11717gpaskaleva-msft wants to merge 3 commits into
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 #11337 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
amarinov-msft
left a comment
There was a problem hiding this comment.
The changes are looking good. We just need to merge #11723 here before merging this PR.
|
@gpaskaleva-msft please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement ( “Agreement” ) is agreed to by the party signing below ( “You” ), 1. Definitions. “Code” means the computer software code, whether in human-readable or machine-executable form, “Project” means any of the projects owned or managed by .NET Foundation and offered under a license “Submit” is the act of uploading, submitting, transmitting, or distributing code or other content to any “Submission” means the Code and any other copyrightable material Submitted by You, including any 2. Your Submission. You must agree to the terms of this Agreement before making a Submission to any 3. Originality of Work. You represent that each of Your Submissions is entirely Your 4. Your Employer. References to “employer” in this Agreement include Your employer or anyone else 5. Licenses. a. Copyright License. You grant .NET Foundation, and those who receive the Submission directly b. Patent License. You grant .NET Foundation, and those who receive the Submission directly or c. Other Rights Reserved. Each party reserves all rights not expressly granted in this Agreement. 6. Representations and Warranties. You represent that You are legally entitled to grant the above 7. Notice to .NET Foundation. You agree to notify .NET Foundation in writing of any facts or 8. Information about Submissions. You agree that contributions to Projects and information about 9. Governing Law/Jurisdiction. This Agreement is governed by the laws of the State of Washington, and 10. Entire Agreement/Assignment. This Agreement is the entire agreement between the parties, and .NET Foundation dedicates this Contribution License Agreement to the public domain according to the Creative Commons CC0 1. |
There was a problem hiding this comment.
Pull request overview
Fixes a UI Automation-related memory leak in WPF by ensuring stale AutomationPeer instances (and their cached child-peer graphs) are actively disconnected from UIA and have their strong reference chains broken, allowing old/recycled containers to be garbage collected.
Changes:
- Adds stale-child detection to
EnsureChildren()and disconnects removed peers during UIA traversal refreshes. - Introduces
DisconnectPeerFromUia()to clear proxy→peer references, deferUiaDisconnectProvideroutside UIA callback context, and sever peer→children chains. - Updates
ElementProxyto allow clearing its peer reference via a newClearPeer()method.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Automation/Peers/AutomationPeer.cs | Adds disconnect logic during child refresh, introduces deferred UIA disconnect helper, and breaks peer reference chains to enable GC. |
| src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementProxy.cs | Makes _peer mutable and adds ClearPeer() to sever proxy→peer strong references during disconnect. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| peer.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, | ||
| new Action(() => | ||
| { | ||
| UiaDisconnectProvider(proxy); | ||
| })); |
| [DllImport("UIAutomationCore.dll", EntryPoint = "UiaDisconnectProvider", CharSet = CharSet.Unicode)] | ||
| private static extern int UiaDisconnectProvider(IRawElementProviderSimple provider); |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
Not gonna lie, this adds quite some overhead to already massively heavy invocation this recursive method call is. |
Fixes #11337
Description
ElementProxy.cs:
_peermade non-readonly +ClearPeer()method — allowsDisconnectPeerFromUiato sever the strong proxy → peer reference immediately (for non-UIElement peers likeDataGridItemAutomationPeer)AutomationPeer.cs:
EnsureChildren()disconnect logic — detects removed peers when_childrenis refreshed during UIA traversal and callsDisconnectPeerFromUiaon them.DisconnectPeerFromUiachanges:proxy.ClearPeer()— severs proxy→peer strong referenceDispatcher.BeginInvokeforUiaDisconnectProvider— defers COM disconnect to outside UIA callback contextpeer._children = null; peer._childrenValid = false;— the critical fix that breaks the chain from disconnected peer → cached cell peers → old DataGridCell/Row containersRegression
No
Testing
Test Environment
Tests Performed
peer._children = null; peer._childrenValid = false;in DisconnectPeerFromUiaRisk
Microsoft Reviewers: Open in CodeFlow