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.
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. |
@h3xds1nz Can you take a look at the original PR . The fix there is identical - only the hot-path cost differs, Trying to minimize the changes inside |
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