-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Shell Page Memory Leak #22645
Comments
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:
|
@rolfbjarne could you take a look at this one? |
I think there is a general issue if you do: // AppShell lives forever
App.Current.MainPage = new AppShell();
App.Current.MainPage = new ContentPage(); I think But since most apps have only one shell, those would be fine:
Given, there is a real issue here, just not all apps would run into it. @ESO-ST as a workaround, could you create a single |
that would be problematic as we would have to redesign many aspects of the application, but more importantly it will not solve our memory leak problem that eventually leads to an out of memory exception, our app has a lot of pages in the Shell, and if those pages never get garbage collected, the memory will simply keep growing, slowing the app down and eventually causing an out of memory exception.
Won't the Shell pages still not be garbage collected in those cases too? so any page created, along with its children will not be garbage collected even when navigating away from it, and will keep occupying memory. |
If you only have one If you need to change |
I don't see that behavior in the repro project even when only using a single Shell instance. The finalizers of the sub pages and their elements (buttons, images, etc.) are never called. Could you please take a quick look. I updated the project to add more subpages. |
When should we expect the shell subpages and their content to be garbage collected? |
@ESO-ST if you are just navigating to a page and then navigate back (so it is popped), then those pages should go away. If you have at least one I have not had a chance to try your sample yet; I'm finishing up a leak related to |
For pages displayed as a tab, it appears the behavior is they will stay around. Xamarin.Forms had some logic that was a little different: it would keep 3 pages around on Android in a This is related to: One thing you could try is overriding Let us know if that works! |
I tried that, when we return back to those tabs they are blank now
Just to be on the same page here, this approach of re-using the same Shell instance won't work for us, the app was not designed to work that way, we would need to reset all pages and return them to their original state if we follow the single Shell approach (the pages have stateful controls/data on them), and that's something the app was not designed to do, it would be a big refactoring. The app was designed to throw the Shell away when it's done with it, and simply make a new one when it needs it, basically start from scratch, the pages need to be clean and have no states/data in them. App.Current.MainPage = new AppShell(); // we need a shell, make a new one. and so users would freely navigate between areas in the app where a shell is needed, to areas where a shell is not needed. |
Can you try this? This worked for me and the finalizer is called protected override void OnNavigatedFrom(NavigatedFromEventArgs args)
{
base.OnNavigatedFrom(args);
if (this.Parent is ShellContent shellContent)
{
FieldInfo fieldInfo = shellContent.GetType().GetField("_contentCache", BindingFlags.NonPublic | BindingFlags.Instance);
// Set the value of the private field
fieldInfo.SetValue(shellContent, null);
shellContent.RemoveLogicalChild(this);
shellContent.ContentTemplate = new DataTemplate(() => new MainPage());
}
} |
You tried it in the repro project? I tried it in the sample repro project, it didn't work, the finalizers are still not being called. While I appreciate the effort for the suggested workaround (re-use same Shell instance), we can't really apply that approach in our app, it would require a major re-work, we've tried going that route upon your suggestion and after a while it became abundantly clear it would be a major refactoring. We really must create a new Shell. I would be happy to show you the app and walk you through the hows and whys if you'd like. A workaround that we could use would be one where we would manually clean the Shell in some way once we're done with it. |
Rolling back a bit to the initial dance of swapping out the mainpage for a new shell. I tried this on your sample public ShellPage()
{
InitializeComponent();
i++;
Console.WriteLine($"ShellPage Created: {i}");
}
static int i = 0;
~ShellPage()
{
i--;
Console.WriteLine($"ShellPage destroyed: {i}");
}
private async void Button_Clicked(object sender, EventArgs e)
{
var shell = Shell.Current;
shell.Items.Clear();
shell.Items.Add(new ShellContent() { Content = new ContentPage() });
await Task.Yield();
App.Current.MainPage = new MainPage();
App.Current.MainPage.Loaded += OnLoaded;
async void OnLoaded(object sender, EventArgs e)
{
shell = null;
App.Current.MainPage.Loaded -= OnLoaded;
}
} When I do that all the pages finalized If you're wanting pages on the shell to finalize while you navigate you could apply a similar strategy. For example, if you one bottom tab1, remove and re-add everything on tab2. though maybe being able to cleanup your old shell is enough for now? |
I verified the workaround works well on the sample project, I attempted it in our app but unfortunately it doesn't work there. I'm investigating on my end in case there's something still holding a reference to the pages preventing them from being garbage collected. In our app we have a Shell TitleView, so I tested out adding a Shell TitleView to the repro sample I did the same in our app but still something else is preventing the pages from being garbage collected. |
Did you try the scenario with nightly builds to see if the fix for TitleView (#21968) fixed that bit? |
I got past the TitleView blocker by simply nulling the TitleView when exiting the shell I also tried the latest nightly build as of today in our prod app, but i still can't get PureWeen's workaround to work there. I am unable to memory profile the iOS app using dotnet gcdump, it seems that's not currently supported. I tried DotMemory from Jetbrains but that's also not supported. @PureWeen Please let me know if dotnet gcdump supports iOS, that could speed things up if im able to memory profile and see what is still holding a reference to the pages. That's why I was asking if there could be other aspects of Shell (or MAUI) that are keeping a reference to its pages, preventing them from being garbage collected, just like the TitleView was. |
@ESO-ST The iOS docs are here: But the Android doc might explain a bit better conceptually: |
I've already tried using dotnet-dsrouter, it does work but only with dotnet-trace, not with dotnet-gcdump. The docs linked also don't mention dotnet-gcdump, only dotnet-trace. Is there documentation specific to dotnet-gcdump and dsrouter? I didn't find any. |
The Do you get an error message at all? |
Ok, I've been using |
Here are my steps to use First, I build a dotnet build YourApp.csproj -f net8.0-ios -c Release -p:_BundlerDebug=true This includes the optional Mono diagnostic component in the app. You could also try a Next, we'll need to use Find find /usr/local/share/dotnet/packs | grep -E 'mlaunch$'
/usr/local/share/dotnet/packs/Microsoft.iOS.Sdk/17.2.8053/tools/lib/mlaunch/mlaunch.app/Contents/MacOS/mlaunch I picked the newest one. Then set an alias mlaunch="/usr/local/share/dotnet/packs/Microsoft.iOS.Sdk/17.2.8053/tools/lib/mlaunch/mlaunch.app/Contents/MacOS/mlaunch" Now you can just run Find a simulator to use: xcrun simctl list devices
== Devices ==
-- iOS 17.5 --
iPhone 15 (472C1E4D-B981-4C75-9638-111E9CB15E5D) (Shutdown) I just picked iPhone 15, we'll need the GUID here in the Run
Take note of the Run mlaunch --launchsim bin/Release/net8.0-ios/iossimulator-arm64/YourApp.app --device :v2:udid=472C1E4D-B981-4C75-9638-111E9CB15E5D --wait-for-exit --stdout=$(tty) --stderr=$(tty) --argument --connection-mode --argument none '--setenv:DOTNET_DiagnosticPorts=127.0.0.1:9000,nosuspend,listen' I used Once the app is running, you can do either one of:
The |
thanks @jonathanpeppers I was able to get dotnet-gcdump working using |
@ESO-ST please can you send me the gcdump so I can pass it on? |
I got the workaround to function in our app, I had to modify it a bit.
Thanks for your continued help, getting dotnet-gcdump working was invaluable here :) |
I'm running into this issue now #22951 (comment) |
@ESO-ST I don't think @22951 is related it's just the same type of exception. Do you have a repro? Or the XAML for your CarV? Does this happen if you load directly to that CarouselView and have done zero main page swaps? I'm wondering if a left over CarouselView is bound to a reused collection |
I'm trying to setup a repro project for this, mimicking what our app does for carousel. It is unrelated to the work around we did here, I can reproduce it in our app consistently as soon as i upgrade MAUI to v8.0.60+ |
@ESO-ST as the Carousel crash issue is unrelated to the memory leak and workaround. Please can you raise an issue specific to the crash once you have a repro? |
It is not advised to switch MainPage during runtime in Maui, so I wonder why all the activity on this issue. |
In my case memory leak was caused by adding a bindable property into Shell.TitleView Content like this on a child page:
TapCommand binds to command defined in VM. That causes a circular reference between Page, VM and Shell. It works fina on Android but leaks on iOS. Tested on version 8.0.70 |
@Sztub what is There is this issue that probably applies to various controls using a custom |
Description
Shell pages don't get garbage collected and end up causing a memory leak.
In the provided sample project, we have two types of pages, one that's simply a ContentPage which we will refer to as non-shell, and one that is a Shell, and all we do is switch between them using
App.Current.MainPage = new MainPage();
for the non-shell page andApp.Current.MainPage = new AppShell();
for the shell one.We have a destructor for all classes involved, the shell, the shell's ContentPage and the non-shell page. We simply output to the console when the destructors get called.
When we are on the shell page, the destructor of the non-shell page gets called properly, and hence it is garbage collected, however when we are on the non-shell page, the destructor of the shell page (or its shell) never gets called, and so leaks memory.
We can keep switching back and forth between the shell and non-shell page and we will see the memory leaking and the shell destructors never being called.
Steps to Reproduce
Link to public reproduction project repository
https://github.com/ESO-ST/WebViewCrashMidLoad/tree/MemoryLeak
Version with bug
8.0.40 SR5
Is this a regression from previous behavior?
Not sure, did not test other versions
Last version that worked well
Unknown/Other
Affected platforms
iOS
Affected platform versions
No response
Did you find any workaround?
No response
Relevant log output
No response
The text was updated successfully, but these errors were encountered: