maui
maui copied to clipboard
Many controls (mainly iOS) are not collected by GC
Description
I'm working on an app for iOS. The app creates new instances of pages and view models every time a new page is pushed to the navigation stack. After a while the app gets very slow and crashes. So I started to comment out parts of XAML and to trigger GC and found that the pages were no longer cleaned up if they had certain controls.
To find out which controls causes problems I created a simple test programme that does nothing more than adding one single control to a page and pushes that page onto the navigation stack. By clicking a button the garbage collector can be forced to work. The program outputs a debug message when the constructor and finaliser of the opened page is called.
On iOS there are many controls where there is no finaliser call of the page, resulting in memory leaks.
On iOS there is no finaliser log output (~xxx Page) for these controls:
- [x] BoxView: https://github.com/dotnet/maui/pull/18434
- [x] Frame: https://github.com/dotnet/maui/pull/18552
- [x] Ellipse: https://github.com/dotnet/maui/pull/18434
- [x] Line: https://github.com/dotnet/maui/pull/18434
- [x] Path: https://github.com/dotnet/maui/pull/18434
- [x] Polygon: https://github.com/dotnet/maui/pull/18434
- [x] Polyline: https://github.com/dotnet/maui/pull/18434
- [x] Rectangle: https://github.com/dotnet/maui/pull/18434
- [x] ImageButton: https://github.com/dotnet/maui/pull/18602
- [ ] RadioButton
- [ ] SearchBar: https://github.com/dotnet/maui/pull/16383
- [x] Slider: https://github.com/dotnet/maui/pull/18681
- [x] Stepper: https://github.com/dotnet/maui/pull/18663
- [x] Switch: https://github.com/dotnet/maui/pull/18682
- [x] ActivityIndicator: https://github.com/dotnet/maui/pull/18434
- [x] CarouselView: https://github.com/dotnet/maui/pull/18267
- [x] IndicatorView: https://github.com/dotnet/maui/pull/18434
- [ ] ListView
- [x] TableView: https://github.com/dotnet/maui/pull/18718
On Windows there is no finaliser log output (~xxx Page) for these controls:
- [x] WebView: https://github.com/dotnet/maui/pull/18810
On Android there is no finaliser log output (~xxx Page) for these controls:
- [x] CarouselView: https://github.com/dotnet/maui/pull/18584
Steps to Reproduce
- Clone repository.
- Run App.
- Press any button to open a new page.
- Notice "xxx Page" output (output from constructor).
- Navigate back.
- Press button "GC.Collect"
- Notice "~xxx Page" output (output from finaliser)
Link to public reproduction project repository
https://github.com/heyThorsten/GCTest
Version with bug
8.0.0-rc.2.9373
Is this a regression from previous behavior?
Not sure, did not test other versions
Last version that worked well
Unknown/Other
Affected platforms
iOS, Android, Windows
Affected platform versions
iOS 16.4 (Simulator), Android 10.0 (Emulator), Windows 10.0.19041.0
Did you find any workaround?
No response
Relevant log output
JUST AN EXAMPLE (WebView Page finaliser call is missing):
Label Page
ScrollView Page
WebView Page
Ellipse Page
GC.Collect()
GC.Collect()
~Ellipse Page
~Label Page
~ScrollView Page
Verified this on Visual Studio Enterprise 17.8.0 Preview 5.0(8.0.0-rc.2.9373). Repro on Windows 11, Android 14.0-API34 and iOS 16.4 with below Project: GCTest.zip
Wow good catch. 👍
This is a serious issue and should be fixed with .net 8 ! @jsuarezruiz Please take a look if you got any free time
@jonathanpeppers #13520 #14108
There is a PR open for CarouselView: https://github.com/dotnet/maui/pull/18267
Some of these could have also been discovered here: https://github.com/dotnet/maui/pull/18318
There is a test we can add additional controls to start validating these:
https://github.com/dotnet/maui/blob/3c0ff6735c6b259a17cdef31f7ebce8dea1e0a30/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs#L39-L54
Thank you, @heyThorsten, for creating this sample!
I turned the above list of controls into a checklist. Updated which have fixes in dotnet/maui/main or open PRs so far.
Can you add the "Border" item to the checklist ? Because it seems that I met some trouble with this item.
For example, when I use a list of items which are defined into a border (nearly 100 items).. the application crash on some rendering / navigating... but no crash when I remove the border.
I don't have any log with this (sorry).
the application crash on some rendering / navigating... but no crash when I remove the border.
@Yokaichan the symptoms you are describing here does not necessarily sound like a memory leak. We already have memory tests in-place to be sure Border doesn't leak: https://github.com/dotnet/maui/pull/15946
Can you get the full stack trace of the crash and file a new issue?
I just noticed that Border on iOS has memory leak when:
<Border>
<Border.StrokeShape>
<RoundRectangle CornerRadius="8"
StrokeThickness="0" />
</Border.StrokeShape>
</Border>
Try add that to the unit tests @jonathanpeppers , I've spent all day trying to figure out why this component we've created is leaking. If I remove lines 62-66 it does not leak anymore.
Edit: I just tried to simplify it even more, it doesn't matter what kind of Shape you give it, it leaks as soon as you set a StrokeShape to whatever shape you'd like.
It looks like NavigationPage itself leaks on iOS too.
Easy to reproduce. Just do something like:
Application.Current.MainPage = new NavigationPage(new PageA());
...
Application.Current.MainPage = new PageB();
...
Application.Current.MainPage = new PageC();
You'll find that the NavigationPage isn't collected, but PageB is collected once it is replaced by PageC.
Let me know if you'd rather see another issue opened for this. Thought you might just want to add it to the list.
This one might be particularly nasty if the leak keeps the NavgationPage's entire nav stack in memory.
@haavamoa's discovery highlights a weakness in the methods currently at work to detect these leaks.
While a great start, it's not enough to load an empty (or near-empty) control into a test. Each of these controls have broader APIs that, when exercised, might also introduce leaks.
I've cobbled together a detection mechanism that capitalizes on the cascading nature of these leaks (i.e. propagating to the host page) to provide near-immediate feedback when such leaks occur. It centrally tracks page navigation events, forces garbage collection, and then reports on the collection status of the previous page.
The project, inspired by @heyThorsten's in-app testing approach, demonstrates how memory testing techniques from the MAUI wiki can be applied to instrument a MAUI app to actively detect page-level memory leaks during development.
I've broken the 3 remaining controls for this issue out into separate tracking issues.
@Yokaichan @haavamoa @AdamEssenmacher Can you log new issues with repros so we can track them alongside this effort?
Thank you for all the work you did on this @heyThorsten, we all really appreciate it!