-
Notifications
You must be signed in to change notification settings - Fork 107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WinUI 3 Page objects are being leaked on navigate in a C#/WinRT app #413
Comments
@angelazhangmsft - relnote for RC2. |
Here's an update on the investigation. We've tightened up reference cleanup, but there is still one lingering cycle, and it's a challenging one. This object will always leak upon creation: class TestPage : Page { } In simplest terms, to create the XAML page, CSWinRT creates a CCW for TestPage, so that it can override methods on Page. So far so good. However, Page is serving double-duty in this instance. It's both the access to the base implementation for the derived object, and the lifetime management for clients in C# to access the combined object. This net effect is that there's a strong reference cycle between the XAML page & CCW of TestPage, and the CCW isn't aggregating it's lifetime with the XAML page (nor can it, it needs to be created first to create the XAML page. Correcting this leak is going to require some careful thought about how the aggregate object is assembled. |
See repro unit test: |
Servicing PR for .NET 5 - dotnet/runtime#45622. |
I'm running with .NET 5.0.2 with WinRT.Runtime built from master(copied into appx folder), and still getting memory leak. The process memory grows about 4GB. Visual Studio diagnostic tool spends an hour to get ~75M roots and hadn't completed the snapshot. Do you think this is another memory leak issue? |
Well, there may be some issue with Visual Studio debugger. Sometimes it can only find KBs of managed heap when I know there should be some MB byte arrays alive. Sometimes it finds excessive amount of roots. |
fyi - we are and have been investigating the root cause for a while now. Its proving to be a hard to pin down challenge due to the reference tracking of strong and weak references between cswinrt, xaml and .net. This is impacting WinUI and Reunion so it is being treated with utmost urgency. |
The leaks related to the WinUI scenarios have been addressed in CsWinRT 1.2.0 (affects both generated projection and runtime) and .NET runtime 5.0.2. Once WinUI and Project Reunion builds and ships their C# projections using CsWinRT 1.2.0 which they will do in an upcoming update, the leaks should disappear. |
@manodasanW WOOHOO |
Thanks so much for getting this done! We'll be sure to pull this into WinUI 3 and Project Reunion. |
If there are no objections, could we please use the following tool to evaluate WinUI/UWP performance and memory utilization issues associated with Navigation Frame and its interaction with other controls: https://github.com/Noemata/RosettaNavigation The tool supports testing NavigationCacheMode = NavigationCacheMode.Enabled and NavigationCacheMode = NavigationCacheMode.Disabled scenarios. |
Hi, I'm developing a new windows desktop application with WinUI 3 1.0 and .Net 6.0 and it looks like I'm hitting the bug that is described here. When I navigate between my pages, the memory keeps growing. I was also able to reproduce it with the test app from the first comment, the memory keeps growing until the app crashes eventually. Exception thrown at 0x751FEC52 (KernelBase.dll) in App1.exe: WinRT originate error - 0x8007000E : 'Not enough memory resources are available to complete this operation.' devenv_PTrfuUOEpf.mp4 |
Hi @marcpiechura, can you please share the source codes of this POC? |
Hi @curia-damiano, sure thing, find attached two demo projects, the "UIOnly" folder contains the example from this issue comment, ported to the latest project template. Hope that helps and thanks for looking into it. |
@marcpiechura You are not alone. I witnessed similar behaviour working with WinUI 3. The images show how each page instantiation allocates memory while idle instances' memory is not released timely and might pile up to the point of exhaustion. This is what it looks like when invoking the GC explicitly when a page is left. |
@marcpiechura, @marwalsch have the same problem with .NET 6 and Windows App SDK 1.0. The memory is not released after I leave a page, and when I come back it's increasing with the amount needed for the page. If the memory needed for that page is 50 MBs, each time I navigate back and forth, it keeps increasing with 50 MBs. The page constructor is never hit.
|
This bug doesn't seem to be resolved, I'm using WinUI 3 on SDK 1.1.1, using the navigation template supplied by the official extension provides the same memory leak issue adding a MB every new page load. This issue is also prevalent on the official WinUI 3 Gallery app. Edit: Also using .NET 6 newest SDK |
@TheAvonian I created #1213 to further investigate this remaining leak. If you happen to have a repro that we can use to investigate, can you post it there. Thanks. |
@manodasanW Just to be sure, is the Windows App SDK 1.0.0 guaranteed to ship with CWinRT 1.2.0 or newer, or does this dependency lie elsewhere? My previous observations seem inconsistent and not reliably reproducible across different scenarios, i.e. invoking the GC has little to no effect. |
@marwalsch It is unclear at the moment where the issue is, whether it is in CsWinRT, WinAppSDK / WinUI, or .NET. I will know more as we investigate this issue and after which is when I will be able to comment on shipping vehicles. |
WinAppSDK is detecting CsWinRT version, and fails the compilation on older version. |
what is the update o this ? i still see this issue |
Looks like this bug is still present in Windows SDK 10.1 using Winui 3 and the Template Studio. Something wacky this way comes… I’m having the same memory leak issue. First, let me describe the problem. I created a Navigation based WinUI 3 application via the Templates. With the wizard, I added the main, another blank page and a few other pages that don’t matter for this discussion. Of course, my first thought is it is a disposing issue. So, I implement IDisopsable and in the Dispose method calls dispose on the map control. Then I went into the shellpage Xaml and found the Frame “NavigationFrame” and added a method for the Navigating event. In this method, I check the content to see if it is a Page and if it is IDisposable. If it is, I call the Dispose method.
Straight forward stuff. And it calls dispose on the Mappage as expected. First problem is when I got to the point of calling dispose on the mapsui control it throws an exception saying I cannot access a disposed item… Interesting. So, I wrapped the call to dispose in a try, and ate the exception. Now the wacky part. I do the same test, leaving the map page 14 times and take a snapshot and... There are 7 mapPages objects in memory… That was unexpected. So, I put junk code after the call to dispose and in the error block like this:
Then I put break points on the var x. I ran the same test and all 14 times and never got to the breakpoint. But there were still only 7 instances held in memory. While my approach seems to be a failure, it leads me to believe that there is a race condition. Perhaps it’s where the Frame kills the current object before loading the new page? For my solution, I’m considering rewriting how the NavigationViewItems call to the navigation helper. Perhaps pointing them instead to a method that I can wait for the page to fully dispose. |
@Omenrevlis Are you expecting pages that implements IDisposable that something automatically will call Dispose on them? (You want to look at the finalizers instead though) Also garbage collection is lazy. Its not uncommon that objects gets left around for a while - especially if memory pressure is low |
For your first question, I have multiple answers. Yes, I would expect a solution built from the template studio where you define the pages you want in the wizard and it sets up a full MVVM solution with all the navigation wiring setup for you, to take care of appropriately releasing managed resources. And it seems to do so for not overly complicated or memory intensive pages. No, I didn't expect something to magically call the Dispose method I created. When implemented IDisposable in the MapPage, I then went to the ShellPage (built by the Template Studio) and found the Frame that loads the page. On that frame, I registered for the Navigating event. This happens when a request to navigate is sent. That's where I make sure the content is a Page and Implements IDisposable and, if so, I call the Page Dispose method. I would not have commented if it was just a matter of waiting for the garbage collector. The instances I mentioned stick around. I left my computer for over an hour and came back and took a snapshot and they were still there and the memory was still be used. Implementing a finalizer wouldn't help. Best practice is to use dispose when you can and finalizers as a last ditch effort incase someone forgot to call dispose and is used typically for unmanaged resources. Also best practice is to call dispose with your finalizer. In this case, it wouldn't matter, because .net thinks the control is already disposed. (thus proving that the navigation actually tried) While I could unwire from the Navigating event for the Frame on the ShellPage and just use a finalizer to call the dispose, it should have the same effect. It stinks as a pattern, but I could. For giggles, and because it only takes two seconds, I tried. Did the same test and 14 copies of the class are still in memory and the map control was already disposed when tried to dispose it. Only other thing on the mappage is a TextBlock bound to a class property. |
My point is you should count the number of finalizers being hit, since that's the number that matters - number of dispose calls doesn't. |
I checked how many times the finalizer was hit and it aligns with the 50% of mappages being released from memory even though my dispose of the object still says for all of them that the control was already disposed. For the remaining in memory there is the full stack of objects that a Mapsui map has. Nothing surprising since they were not disposed. Tonight I'll test the disposing of Mapsui maps to see if its the dispose of that control causing the leak. |
I've put together two sample apps to demonstrate the same scenario, one in C++/WinRT, the other in C#/WinRT:
C++/WinRT: DesktopWinUICppLeakTest.zip
C#/WinRT: DesktopWinUICSharpLeakTest.zip
In both cases, the app contains a ListView containing 1,000 items, plus a button that navigates to and from a second page also containing a ListView with 1,000 items. In the C++/WinRT case, the memory footprint of the app stays the same after successive navigations, whereas in the C#/WinRT case, the memory footprint steadily grows. A heap dump analysis indicates that the root Page object in native code is what's leaking, so it's likely the case that something in the C#/WinRT projection is holding onto the native object past when it should be.
The text was updated successfully, but these errors were encountered: