dotnet / dotnet/wpf

WPF leaks ElementProxy instances when UI Automation is used

Open
#11,337 3 comments 3 reactions 0 assignees View on GitHub
Accessibility PR Proposed
Dominant language
C#
Stars
7.7k
Forks
1.3k
Avg merge
1d 11h
Merged PRs (30d)
61

Description

### Description

This happens with .NET 4.8 up to .NET 10.
Sample with binaries is attached which can be compiled to either platform.
This issue makes memory leak and performance trending nearly impossible if UI Automation is used.
[WPFAutomationLeak.zip](https://github.com/user-attachments/files/24312886/WPFAutomationLeak.zip)

The root cause are leaks in RefCounted CCWs (ElementProxy) where the RefCount never drops to zero:
```
000001de47c48118 (ref counted handle **2**)
-> 01be2fbc11f0 MS.Internal.Automation.ElementProxy
-> 01be2fbbe6c8 System.Windows.Automation.Peers.DataGridCellItemAutomationPeer
-> 01be692d6e60 System.Windows.EffectiveValueEntry[]
...
-> 01be690c7a38 System.Collections.Generic.List
-> 01be690c7a58 System.Windows.DependencyObject[]
-> 01be690c8310 System.Windows.Controls.Grid
```

How should one write a leak free regression tester which uses UI Automation?

### Reproduction Steps

Start
WPFUI.exe
Then start
WPFAutomationClient.exe
and watch memory growth in WPFUI.exe which is the problem. The client process leaks as well.

Image

### Expected behavior

No leaks in WPFUI process.

### Actual behavior

WPFUI leaks all UI elements because UI Automation creates ElementProxy instances which are COM Callable wrappers (CCWs) which keep the UI elements alive forever.
The client code is just calling

```
foreach (var proc in processes)
{
var handle = proc.MainWindowHandle;
for (int i = 0; i < 1000; i++)
{
var el = AutomationElement.FromHandle(handle);
var childs = el.FindAll(System.Windows.Automation.TreeScope.Descendants, Condition.TrueCondition);
Console.WriteLine($"{i}: Childs: {childs.Count}");
foreach (AutomationElement child in childs)
{
var tmp = child.Current.AutomationId;
}
}
}
```
I do not see what I can do at client side to get rid of the leak.

### Regression?

No it is there since .NET 4.8

### Known Workarounds

In WPF UI one can forcefully disconnect all Automationproviders with

```
[DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
private static extern int UiaDisconnectAllProviders();
```

This is shown in repro sample with
```
WPFUI.exe -disconnect
WPFAutomationClient.exe -disconect
```

This works to some extent but needs coordination with a shared mutex between automation client and WPF application. Otherwise UI automation will randomly fail with some exception.
With that the leak at least in the WPF UI does not happen:

Image

### Impact

Currently it is not possible to test WPF/WinForms application with UI Automation in a leak free way. This prevents early detection of memory leaks.

We have tried to work around by forecefully deleting our own UI Automation objects via [UiaDisconnectProvider ](https://learn.microsoft.com/en-us/windows/win32/api/uiautomationcoreapi/nf-uiautomationcoreapi-uiadisconnectprovider). That works only for our own UI Automation provider objects but since we leak thousands of Automation providers from WPF which do not do this we suffer since then a massive performance degradation when removing one instance from a huge list of leaked objects.
This is getting prohibitively expensive from a runtime point of view and hampers performance regression testing.

How can one call UI Automation without causing major leaks in target application?

### Other information

This seems similar to the WinForms issue which was backported .NET 4.8 in 2022

- https://github.com/dotnet/winforms/issues/3182

You can run the UI without updates `WPFUI -noupdate` to see how the RefCount of the CCWs increases with every iteration.
After e.g. 30 iterations of you find in the dump

```
el.FindAll(System.Windows.Automation.TreeScope.Descendants, Condition.TrueCondition);

000001de47c48118 (ref counted handle **30**)
-> 01be2fbc11f0 MS.Internal.Automation.ElementProxy
```

To me there seems some COM Refcount decrease at client side missing.

Could the Windows Sample be helpful?

- https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/UIAutomationCleanShutdown/cpp/UiaCleanShutdownControl/UiaCleanShutdownControl.cpp

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.