Skip to content
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

iOS PopModalAsync causes memory leaks #21453

Closed
era-maui opened this issue Mar 26, 2024 · 14 comments · Fixed by #23164
Closed

iOS PopModalAsync causes memory leaks #21453

era-maui opened this issue Mar 26, 2024 · 14 comments · Fixed by #23164
Assignees
Labels
area-controls-modal fixed-in-8.0.70 fixed-in-9.0.0-preview.6.24327.7 legacy-area-perf Startup / Runtime performance memory-leak 💦 Memory usage grows / objects live forever migration-compatibility Xamarin.Forms to .NET MAUI Migration, Upgrade Assistant, Try-Convert partner/cat 😻 this is an issue that impacts one of our partners or a customer our advisory team is engaged with platform/iOS 🍎 s/triaged Issue has been reviewed t/bug Something isn't working t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.)
Milestone

Comments

@era-maui
Copy link

Description

Whne you push a modal page that is wrapped in a NavigationPage
Navigation.PushModalAsync(new NavigationPage(new ModularPage())); on iOS we can see that the Page is never released from the memory. We are using Perfiew for debugging.

Here is a GCDump where we open and close a modal page multiple times
20240326_110245_0.zip

I am adding a sample which has both solutions outcommented. Seems counterintuitive that we need to call DisconnectHandler on existing pages and controls when popping a page from the Navigation stack.

NavigationSample.zip

Steps to Reproduce

  1. Open sample app
  2. Start profiling
  3. Press the "Click me" button
  4. Press "Close" button
  5. Repeat steps 3 and 4
  6. MAke a memory GCdump

Link to public reproduction project repository

No response

Version with bug

8.0.6 SR1

Is this a regression from previous behavior?

Yes, this used to work in Xamarin.Forms

Last version that worked well

Unknown/Other

Affected platforms

iOS

Affected platform versions

No response

Did you find any workaround?

Seems that this can only be fixed by running disconnectHanler on each element on the Page including the page

page.Handler?.DisconnectHandler();

MyLayout.Handler?.DisconnectHandler();

Handler?.DisconnectHandler();

or creating a custom handler for the NavigationPage and running

ViewController.Dispose();
var viewController = ViewController as PageViewController;
viewController.CurrentView = null;

Relevant log output

No response

@era-maui era-maui added the t/bug Something isn't working label Mar 26, 2024
@jsuarezruiz jsuarezruiz added platform/iOS 🍎 legacy-area-perf Startup / Runtime performance labels Mar 26, 2024
@PureWeen PureWeen added the memory-leak 💦 Memory usage grows / objects live forever label Mar 26, 2024
@samhouts samhouts added the migration-compatibility Xamarin.Forms to .NET MAUI Migration, Upgrade Assistant, Try-Convert label Mar 28, 2024
@AdamEssenmacher
Copy link

Possible duplicate or related:

#20094
#20119

@PureWeen PureWeen added this to the .NET 8 SR6 milestone Apr 25, 2024
@Zhanglirong-Winnie Zhanglirong-Winnie added the s/triaged Issue has been reviewed label Apr 28, 2024
@Eilon Eilon added the t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.) label May 10, 2024
@mattleibow
Copy link
Member

/similarissues

Copy link
Contributor

Hi I'm an AI powered bot that finds similar issues based off the issue title.

Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one and thumbs upping the other issue to help us prioritize it. Thank you!

Open similar issues:

Closed similar issues:

Note: You can give me feedback by thumbs upping or thumbs downing this comment.

@samhouts samhouts added the partner/cat 😻 this is an issue that impacts one of our partners or a customer our advisory team is engaged with label May 30, 2024
@PureWeen
Copy link
Member

Can you test with the latest nightly build?
https:/dotnet/maui/wiki/Nightly-Builds

@brentpbc
Copy link

brentpbc commented Jun 4, 2024

I just tried with 8.0.60-ci.net8.24303.1 which seems to be the latest on https://dev.azure.com/xamarin/public/_artifacts/feed/maui-nightly

image

But creating a modal page with a NavigationPage still stops the page from being collected by the garbage collector on a real iOS device. A plain modal with no NavigationPage does get cleaned up after GC Collect.

GC.Collect();
GC.WaitForPendingFinalizers(); 

image

See attached test app. To test tap one of the page buttons, then navigate back and tap the Collect GC button. The isAlive should return to false and the text should change from red to black if it has been successfully cleaned up by the garbage collector. The destructors on the BasePage and BaseViewModel classes should also emit a Debug.Writeline() to the console when they are cleaned up.

MemoryTestApp.zip

@jonathanpeppers
Copy link
Member

NavigationPage has circular references, which would be unrelated to modals:

You can just do this to see it:

// The original NavigationPage & children leak
Application.Current.MainPage = new NavigationPage(new Page1());
Application.Current.MainPage = new Page2();

@jonathanpeppers
Copy link
Member

Does the problem still occur with the 8.0.60 service release? The issue with NavigationPage was resolved there.

@jonathanpeppers jonathanpeppers added the s/try-latest-version Please try to reproduce the potential issue on the latest public version label Jun 18, 2024
@PureWeen PureWeen added s/try-latest-version Please try to reproduce the potential issue on the latest public version and removed s/try-latest-version Please try to reproduce the potential issue on the latest public version labels Jun 18, 2024
@era-maui
Copy link
Author

@jonathanpeppers I have tried the latest 8.0.60 version , however I can see that the memory is still not released. There are a couple problems I can see. First and foremost, NavigationRenderer has a hard reference to the Current Element
public VisualElement Element { get => _viewHandlerWrapper.Element ?? _element; }
This means that the page has a circular reference as the NavigationPage.Handler has a reference to the Renderer. So what we do currently is

public class CustomNavigationHandler : NavigationRenderer
{
	protected override void Dispose(bool disposing)
	{
		base.Dispose(disposing);
		SetElement(null);
	}
}

Secondly, there is a more common behavior that we can see. Currently, Handlers disposure is not connected to anything. As Microsoft documentation says, the developers "should" decide when to disconnect the handler of each Element. However, because each VisualElement has a handler with a hard reference, the page will not be release until page.Handler?.DisconnectHandler() is called.

My question is, is it still true that this is the way the framework is indented? This would mean that each MAUI dev would need to in one way or another extend the Navigation service and release elements manually. We have tried previously to hook this into Unloaded as specified in docs, but Unloaded is being called when we navigate away from a page, either in its Navigation stack or in a TabbedPage, making Unloaded unreliable event for page disposure.

@era-maui era-maui reopened this Jun 19, 2024
@dotnet-policy-service dotnet-policy-service bot removed the s/try-latest-version Please try to reproduce the potential issue on the latest public version label Jun 19, 2024
@jonathanpeppers
Copy link
Member

@era-maui does the problem go away if you use the stock NavigationPage as-is?

After #22810, NavigationPage is able to go away in the test and attached sample app. In your example, what type lives forever?

As for the architectural questions, I'm on the .NET for Android team, so I don't know why some of the decisions were made. I'm just trying to help fix some of these issues. Thanks!

@brentpbc
Copy link

Hi @jonathanpeppers the Memory leak still exists in 8.0.60 on iOS, see attached repo. Tap "Modal with Navigation Page" > Tap Done > Tap/spam Collect GC, the page is never collected.

MemoryTestApp 8.0.60.zip

Screenshot 2024-06-20 at 9 38 59 am

@era-maui
Copy link
Author

@jonathanpeppers Understandable. I tested with a regular NavigationPage and the results were the same. I looked at the test from the commit you referenced. Are we sure that CreateHandlerAndAddToWindow is equivalent to await Navigation.PushModalAsyncFixed(new NavigationPage(page)); ? Does it engage the NavigationRenderer the same way?

@jonathanpeppers
Copy link
Member

I'm testing your latest sample today, thanks for sharing. It's possible there is something we missed.

@jonathanpeppers
Copy link
Member

I think this will fix it:

PureWeen added a commit that referenced this issue Jun 21, 2024
Fixes: #21453
Context: #22810

In #22810, a leak in `NavigationPage` was fixed for the case:

    Application.Current.MainPage = new NavigationPage(new Page1());
    Application.Current.MainPage = new Page2();

However, it does *not* work for the case:

    await Navigation.PushModalAsync(new NavigationPage(new Page1()));
    await Navigation.PopModalAsync();

I could reproduce this problem in `MemoryTests.cs`.

There were still a few cycles in `NavigationRenderer`:

* `NavigationRenderer` -> `VisualElement _element` ->
`NavigationRenderer`

* `NavigationRenderer` -> `Page Current` -> `NavigationRenderer`

* `NavigationRenderer` -> `ViewHandlerDelegator<NavigationPage>
_viewHandlerWrapper` -> `TElement _element` -> `NavigationRenderer`

After fixing these cycles, the test passes. The customer's sample also
seems to work:


![image](https:/dotnet/maui/assets/840039/51cac98c-fb33-4e88-9fd5-112d7a04817c)

`ViewHandlerDelegator` was slightly tricky, as I had to make a
`_tempElement` variable and *unset* it immediately after use.
@brentpbc
Copy link

Thanks @jonathanpeppers !

Redth added a commit that referenced this issue Jun 25, 2024
* Size and SizeF should not throw on NaN

* Fix Release Versioning

* Upgrade from 1.5.1 to 1.5.4

* SwipeView Fix #22580 (#22741)

Co-authored-by: Javier Suárez <[email protected]>

* Update vscode extension recommendations

* [XC] Fix SimplifyTypeExtensionVisitor (#23043)

* Add test

* Fix the visitor

* Add SR6 to issue template (#23071)

* Make sure the main branch is using .NET 8 SDK (#23077)

* Make sure the main branch is using .NET 8 SDK

The main branch needs to use the .NET 8 SDK because we are building the .NET 7 TFMs as well. The .NET 9 SDK does not support .NET 7 TFMs anymore.

* no previews!

* Setup preview versioning for SR6.1

* Remove compat appium tests

Since we're moving in a different direction for moving these over in #22635 these projects (and the Issue11853 test, since it's in the other PR) can be deleted.

* [main] Update arcade and xharness (#22981)

* Update arcade

* Update dotnet-tools.json

* Update xharness
# Conflicts:
#	.config/dotnet-tools.json
#	eng/Version.Details.xml
#	eng/Versions.props

* Remove more references to removed projects

* Make titlebar button foreground colors use app theme

* Update dependencies from https:/dotnet/xharness build 20240612.3 (#23088)

Microsoft.DotNet.XHarness.CLI , Microsoft.DotNet.XHarness.TestRunners.Common , Microsoft.DotNet.XHarness.TestRunners.Xunit
 From Version 9.0.0-prerelease.24311.2 -> To Version 9.0.0-prerelease.24312.3

Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>

* Add additional logging for PopLifeCycle

* Remove more legacy pipeline bits

* [iOS] Fixed NRE after calling ViewCell.ForceUpdateSize (#23094)

* [iOS] Fixed NRE after calling ViewCell.ForceUpdateSize

* - add test

---------

Co-authored-by: Shane Neuville <[email protected]>

* Add x:DataType to the carousel view UI tests (#23113)

* Bump to Android 34.0.113

Context: https:/dotnet/android/releases/34.0.113
Changes: dotnet/android@34.0.79...34.0.113

I noticed the Android workload version used in dotnet/maui/main was a couple service releases old.

We should use the latest one, as it has a memory leak fix for `Post()`.

* Squashed commit of the following: (#22874)

commit db8a3bd
Author: Matthew Leibowitz <[email protected]>
Date:   Mon Jun 10 13:46:16 2024 +0800

    Add android

commit 0bd2d65
Author: Matthew Leibowitz <[email protected]>
Date:   Mon Jun 10 12:16:34 2024 +0800

    oops!

commit 647e5f5
Author: Matthew Leibowitz <[email protected]>
Date:   Sat Jun 8 18:15:40 2024 +0800

    Almost

commit c3ea650
Author: Matthew Leibowitz <[email protected]>
Date:   Sat Jun 8 16:17:58 2024 +0800

    sadfasdf

commit d9a398f
Author: Matthew Leibowitz <[email protected]>
Date:   Sat Jun 8 13:56:34 2024 +0800

    screenshots

commit 4b3c01f
Merge: 595ff03 e94b364
Author: Matthew Leibowitz <[email protected]>
Date:   Sat Jun 8 13:54:52 2024 +0800

    Merge branch 'main' into dev/gif-in-release

commit 595ff03
Author: Matthew Leibowitz <[email protected]>
Date:   Thu Jun 6 21:00:52 2024 +0800

    Update ImageUITests.cs

commit 2c20fe0
Author: Matthew Leibowitz <[email protected]>
Date:   Thu Jun 6 19:05:23 2024 +0800

    Make some tests

commit 1e82e8b
Author: Matthew Leibowitz <[email protected]>
Date:   Thu Jun 6 18:44:18 2024 +0800

    Take 2?

commit d576c82
Author: Matthew Leibowitz <[email protected]>
Date:   Thu Jun 6 07:11:17 2024 +0800

    Fix animated gifs in Release builds

* Fix loaded so it fires on second subscription (#23095)

* Fix loaded so it fires on second subscription

* Update VisualElement.cs

* Make sure the view is still alive after posting (#23114)

I saw in some of my local testing the shape view may
actually be disposed if you write code or when you
run the device tests.

When we come back after the UI loop, make sure the
platform view is still alive.

This should not happen if the view is attached to the
UI since it is still being used, but if you happen to
navigate or close some popup it may occur.

* [iOS] TapGestureRecognizer should not fire when view is not enabled (#23049)

* Don't receive touch when view is disabled

* Add UITest for Single Tap enabled/disabled

This removes the first pass unit test version which isn't really testing the scenario properly.

* Fix element automation id

* Don't fire tap for windows if control disabled

* Make failure case text less confusing

* [Tests] Update to Appium 5.0.0 (#23118)

* Update to appium 5.0.0

Their driver has some removed methods (LaunchApp/CloseApp) which seem to be now implemented for windows.

Also removed an extra explicit ref to system.drawing.common since it comes in transitively from appium.webdriver package (and the version was causing issues since the newer appium.webdriver uses a newer version).

* Fix windows launchapp/closeapp for newer appium

In Appium's dotnet driver in v5.0.0 they removed `LaunchApp` from the driver: appium/dotnet-client#766

The deprecation suggests using `ActivateApp` as an alternative, but that throws an error saying it's not implemented on the windows driver.

Curiously, `CloseApp` which was also marked as deprecated was _moved_ from the base appium driver to the `WindowsDriver` here: appium/dotnet-client#773

I think the same treatment should have been done to the `LaunchApp` method since there's no alternative in windows.

For now the workaround is to invoke the command manually:
`windowsDriver.ExecuteScript("windows: launchApp", [_app.GetAppId()]);`

* Use correct interface type in FrameRenderer (#23124)

* Use correct interface type in FrameRenderer (#23124) (#23146)

* Squashed commit of the following: (#19629)

commit f6c3bfa
Merge: d0b3f84 3143629
Author: Matthew Leibowitz <[email protected]>
Date:   Mon Jun 17 19:58:40 2024 +0800

    Merge remote-tracking branch 'origin/main' into dev/macos-actionsheet

commit d0b3f84
Author: Matthew Leibowitz <[email protected]>
Date:   Tue Jun 11 05:00:38 2024 +0800

    Fix the older macOS testing

commit 3dc6b4d
Author: Matthew Leibowitz <[email protected]>
Date:   Mon Jun 10 23:40:40 2024 +0800

    macOS 13 things

commit e3e48e4
Author: Matthew Leibowitz <[email protected]>
Date:   Sat Jun 8 02:15:55 2024 +0800

    docs

commit 693d79c
Merge: 3dbce49 a3c872d
Author: Matthew Leibowitz <[email protected]>
Date:   Sat Jun 8 02:08:31 2024 +0800

    Merge remote-tracking branch 'origin/main' into dev/macos-actionsheet

commit 3dbce49
Merge: 17ea9c6 93a1bc4
Author: Matthew Leibowitz <[email protected]>
Date:   Sat Jun 8 02:06:16 2024 +0800

    Merge remote-tracking branch 'origin/main' into dev/macos-actionsheet

commit 17ea9c6
Author: Matthew Leibowitz <[email protected]>
Date:   Sat Jun 8 02:05:42 2024 +0800

    Fix the tests

commit 026da41
Author: Matthew Leibowitz <[email protected]>
Date:   Fri Jun 7 03:56:26 2024 +0800

    fixes

commit d2b85d5
Author: Matthew Leibowitz <[email protected]>
Date:   Thu Jun 6 23:58:33 2024 +0800

    namespaces

commit 9fa77cd
Merge: 3f9596b 9d71d32
Author: Matthew Leibowitz <[email protected]>
Date:   Thu Jun 6 23:57:00 2024 +0800

    Merge branch 'main' into dev/macos-actionsheet

    # Conflicts:
    #	src/Controls/samples/Controls.Sample.UITests/Test.cs
    #	src/Controls/src/Core/Platform/AlertManager/AlertManager.iOS.cs
    #	src/Controls/tests/TestCases.Shared.Tests/Tests/Concepts/AlertsGalleryTests.cs
    #	src/Controls/tests/TestCases/Concepts/AlertsGalleryPage.cs
    #	src/TestUtils/src/UITest.Appium/AppiumCatalystApp.cs
    #	src/TestUtils/src/UITest.Appium/HelperExtensions.cs

commit 3f9596b
Author: Matthew Leibowitz <[email protected]>
Date:   Sat Dec 30 09:29:31 2023 +0200

    Add some UI tests

    Done: iOS/macOS/Android. TODO: Windows

commit 68c930f
Author: Matthew Leibowitz <[email protected]>
Date:   Tue Dec 19 18:15:51 2023 +0200

    macOS does not use PopoverPresentationController

    Fixes #18156

* [ios/catalyst] fix more cycles in `NavigationPage`

Fixes: #21453
Context: #22810

In #22810, a leak in `NavigationPage` was fixed for the case:

    Application.Current.MainPage = new NavigationPage(new Page1());
    Application.Current.MainPage = new Page2();

However, it does *not* work for the case:

    await Navigation.PushModalAsync(new NavigationPage(new Page1()));
    await Navigation.PopModalAsync();

I could reproduce this problem in `MemoryTests.cs`.

There were still a few cycles in `NavigationRenderer`:

* `NavigationRenderer` -> `VisualElement _element` -> `NavigationRenderer`

* `NavigationRenderer` -> `Page Current` -> `NavigationRenderer`

* `NavigationRenderer` -> `ViewHandlerDelegator<NavigationPage> _viewHandlerWrapper` ->  `TElement _element` -> `NavigationRenderer`

After fixing these cycles, the test passes.

`ViewHandlerDelegator` was slightly tricky, as I had to make a
`_tempElement` variable and *unset* it immediately after use.

* Only change ViewHandlerDelegator for iOS/Catalyst

* [ios] fix leak in ListView *Cells (#23143)

* [ios] fix leak in ListView *Cells

Fixes: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2064274

After fixing #22867 for `CollectionView`, there was still a problem with
`ListView`. The following types were leaking in the sample app:

* `UIKit.UITableView`
* `Microsoft.Maui.Controls.Handlers.Compatibility.ViewCellRenderer.ViewTableCell`
* `Microsoft.Maui.Platform.MauiLabel`
* `Microsoft.Maui.Platform.MauiImageView`
* `Microsoft.Maui.Platform.WrapperView`

After a lot of debugging, we found that `Cell` was holding a reference
to the `UITableView`. This pointed *up* in the hierarchy, creating
a cycle.

I updated an existing device test to ensure the problem is solved.

* Fix missing handlers in tests

* Remove assertion

* Fix UITableView reference

* Update a different test

* Can't enumerate while adding

* Missed a null check

* [Housekeeping] Added UI Test to validate project template (#18567)

* Added template UI Test

* Added pending snapshot

* Updated snapshot

* Generate snapshots for all the platforms

* Added pending snapshots

* Updated snapshot

* More changes

* Updated droid snapshot

* More changes

* Updated snapshots

* Added sample path

* Update Issue19509.cs

* Update Issue19509.cs

---------

Co-authored-by: Shane Neuville <[email protected]>

* Optimize resetting gesture recognizers (#19987)

* reduce LINQ usage

* Remove useless check that will always be false

* use HashSet

* reduce enumerations and casting

* reduce LINQ usage

* only cast once

* add benchmarks

* fix build

---------

Co-authored-by: Edward Miller <[email protected]>

* [Windows] Fix ListView insert not working properly (#22746)

* Add test

* Remove force layout update during collection changed event

* Adjust await

* Update ListViewTests.Windows.cs

---------

Co-authored-by: Mike Corsaro <[email protected]>

* Fix <ApplicationTitle> encoding in maui templates (#22084)

* Makes <ApplicationTitle> use a custom symbol instead of the default name parameter to ensure the name is XML encoded where necessaru
* Adds new tests for various encodings and special characters
---------

Co-authored-by: Eilon Lipton <[email protected]>

* Fixed a null check #15102 (#23170)

* [ios/catalyst] fix memory leak in TabbedPage (#23166)

* [ios/catalyst] fix memory leak in TabbedPage

Context: #23164

Just the same way as `NavigationPage` in #23164, `TabbedPage` also has
a memory leak caused by the cycle:

* `TabbedPage` -> `TabbedRenderer` -> `VisualElement _element;` -> `TabbedPage

I could add a new `[Theory]` in `MemoryTests.cs` to see the issue.

This PR fixes the memory leak by breaking the cycle in `TabbedRenderer`.

* Ignore test on Windows

I also cleaned up the Task.Delay()

* [iOS] Set PlatformGraphicsView to transparent input (#23208)

* Avoid JavaCast and exceptions and ask Java (#23215)

* Wire RefreshView up to our xplat layout workflow (#23169)

* Use better layout/measure path with refreshview

* - fix naming

* - set RefreshView content to maui compatible container

* - add test

* - fix null operator

* Update Issue23029.xaml.cs

* - fix content panel so it removes previous content

* - add additional check

* Fix Merge

---------

Co-authored-by: Matthew Leibowitz <[email protected]>
Co-authored-by: MartyIX <[email protected]>
Co-authored-by: Jakub Florkowski <[email protected]>
Co-authored-by: Javier Suárez <[email protected]>
Co-authored-by: redth <[email protected]>
Co-authored-by: Šimon Rozsíval <[email protected]>
Co-authored-by: Rui Marinho <[email protected]>
Co-authored-by: Mike Corsaro <[email protected]>
Co-authored-by: dotnet-maestro[bot] <42748379+dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Vitaly Knyazev <[email protected]>
Co-authored-by: Jonathan Peppers <[email protected]>
Co-authored-by: Jonathan Dick <[email protected]>
Co-authored-by: Edward Miller <[email protected]>
Co-authored-by: Edward Miller <[email protected]>
Co-authored-by: Mike Corsaro <[email protected]>
Co-authored-by: Michael Yanni <[email protected]>
Co-authored-by: Eilon Lipton <[email protected]>
Co-authored-by: Tim Miller <[email protected]>
@github-actions github-actions bot locked and limited conversation to collaborators Jul 24, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-controls-modal fixed-in-8.0.70 fixed-in-9.0.0-preview.6.24327.7 legacy-area-perf Startup / Runtime performance memory-leak 💦 Memory usage grows / objects live forever migration-compatibility Xamarin.Forms to .NET MAUI Migration, Upgrade Assistant, Try-Convert partner/cat 😻 this is an issue that impacts one of our partners or a customer our advisory team is engaged with platform/iOS 🍎 s/triaged Issue has been reviewed t/bug Something isn't working t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.)
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

10 participants