Skip to content

[Regression] COMWrapper Leak when targeting .NET 10 on a WinUI3 project #129191

@macy-kairospower

Description

@macy-kairospower

Description

Updated a Winui3 application from .NET8 -> .NET10 and saw massive increase in memory usage.

  • .NET8 ~250 Mb
  • .NET10 ate up to 13 Gb (Over a week or so) before we stopped the app running.

The app routes a high volume of INotifyPropertyChanged events through the DispatcherQueue which might be part of the cause.
Tested on the latest Winui3 release and the issue was still present.

Posted the issue here because it seems like a runtime issue, not a library issue.

Configuration

Windows 11 26200.8457
Target x64

<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
...
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.1839" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="2.1.3" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools.WinApp" Version="0.3.1" />

Reproduction Repo Here

https://github.com/macy-kairospower/WinUI__NET10_Leak

Regression

The app sees a buildup of ComWrappers when targeting .net10

.NET8 Memory Profile : Mostly System Types.
Image

.NET10 Memory Profile : an Explosion of Wrappers
Image

Data

The production app eats a ton of memory and it seems to bounce all over the place.
The reproduction app suffers from the same leaks, but sees a plateau of memory usage (unlike the production app)
Image

Analysis

The production application routes a large number of updates through the dispatcher queue using a modified version of the ObservableObject Class
The leak looks like it's coming from the binding, not sure if its a C#/WinRT issue or an Issue with the new GC in .NET10

namespace Observability.Common
{

    /// <summary>
    /// Extension of the observable objects class,
    /// Allows other threads to trigger binding updates
    /// </summary>
    public partial class MoreObservableObject : ObservableObject
    {

        private static DispatcherQueue Queue { get; set; }
        public static void BindGUIThread(DispatcherQueue queue)
        {
            Queue = queue;
        }
        public static void Update(Action action)
        {
            if (Queue == null)
            {
                action();
                return;
            }
            Queue.TryEnqueue(() => action());
        }
        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            Update(() => base.OnPropertyChanged(e));
        }
    }

    public class MoreObservableCollection<T> : ObservableCollection<T>
    {
        public new void Add(T item)
        {
            MoreObservableObject.Update(() => base.Add(item));
        }

        public new void Clear()
        {
            MoreObservableObject.Update(() => base.Clear());
        }

        public new void RemoveAt(int index)
        {
            MoreObservableObject.Update(() => {
                if (base.Count > index) base.RemoveAt(index);
            });
        }

    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area ownerstenet-performancePerformance related issue

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions