maui icon indicating copy to clipboard operation
maui copied to clipboard

Many controls (mainly iOS) are not collected by GC

Open heyThorsten opened this issue 2 years ago • 9 comments

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

2023-10-26 10_35_42-Air-von-Jens fritz box - TeamViewer

Steps to Reproduce

  1. Clone repository.
  2. Run App.
  3. Press any button to open a new page.
  4. Notice "xxx Page" output (output from constructor).
  5. Navigate back.
  6. Press button "GC.Collect"
  7. 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

heyThorsten avatar Oct 26 '23 01:10 heyThorsten

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

XamlTest avatar Oct 27 '23 07:10 XamlTest

Wow good catch. 👍

jonmdev avatar Oct 27 '23 08:10 jonmdev

This is a serious issue and should be fixed with .net 8 ! @jsuarezruiz Please take a look if you got any free time

SliemBeji-FBC avatar Oct 27 '23 08:10 SliemBeji-FBC

@jonathanpeppers #13520 #14108

borrmann avatar Oct 27 '23 14:10 borrmann

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

jonathanpeppers avatar Oct 27 '23 17:10 jonathanpeppers

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.

jonathanpeppers avatar Nov 10 '23 15:11 jonathanpeppers

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).

Yokaichan avatar Nov 17 '23 10:11 Yokaichan

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?

jonathanpeppers avatar Nov 17 '23 15:11 jonathanpeppers

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.

haavamoa avatar Jan 13 '24 23:01 haavamoa

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.

AdamEssenmacher avatar Jan 15 '24 07:01 AdamEssenmacher

@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.

AdamEssenmacher avatar Jan 15 '24 10:01 AdamEssenmacher

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!

PureWeen avatar Jan 20 '24 00:01 PureWeen