maui icon indicating copy to clipboard operation
maui copied to clipboard

.Net 8 MAUI Memory Leaks after navigating to multiple pages and then navigating to root page.

Open vsfeedback opened this issue 1 year ago • 5 comments

This issue has been moved from a ticket on Developer Community.


[severity:I'm unable to use this version] .Net 8 MAUI Memory Leaks after navigating to multiple pages and then navigating to root page.

We are seeing that due to memory leaks there is increase in memory usage each time we navigate from one page to another page and when we pop to root page then to the memory usage is not decreased. We have migrated app from Xamarin Forms to MAUI. This has impacted our app release plan. Any help from you would be appreciated.


Original Comments

Feedback Bot on 8/22/2024, 06:33 PM:

(private comment, text removed)


Original Solutions

(no solutions)

vsfeedback avatar Aug 23 '24 17:08 vsfeedback

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!

Closed similar issues:

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

github-actions[bot] avatar Aug 23 '24 17:08 github-actions[bot]

We are having a very similar issue on our end, here is a link to that ticket as well: https://github.com/dotnet/maui/issues/24478

I was able to write a unit test in the MAUI source that reproduces the issue:

ShellNavigatingTests.cs:

[Fact]
public async void MemoryLeak()
{
	Routing.RegisterRoute("page1/page2", typeof(DummyPage));
	Routing.RegisterRoute("page1/page2/page3", typeof(DumberPage));

	var shell = new TestShell(
		CreateShellItem(shellSectionRoute: "page1")
	);
	
	int ctr = 0;
	while (true)
	{
		await shell.GoToAsync("page1/page2/page3");
		Assert.True(shell.CurrentPage is DumberPage);

		await shell.GoToAsync("..");
		Assert.True(shell.CurrentPage is DummyPage);
		
		if (++ctr % 1000 == 0)
		{
			_testOutputHelper.WriteLine("Total memory: " + GC.GetTotalMemory(false).ToString("N0"));
		}
	}
}

class DummyPage : Page
{
		
}

class DumberPage : Page
{
	
}

After running this test for even 30 seconds you can see the memory usage goes up linearly. Currently investigating root cause.

mattsetaro avatar Aug 28 '24 12:08 mattsetaro

After some debugging, I was able to find the following code which is causing a memory leak:

Page.cs
...
internal void OnAppearing(Action action)
{
    if (_hasAppeared)
    action();
    else
    {
    EventHandler eventHandler = null;
    eventHandler = (_, __) =>
    {
	    this.Appearing -= eventHandler;
	    action();
    };
    
    this.Appearing += eventHandler;
    }
}

Page.Appearing is not being unsubscribed properly: Screenshot 2024-08-28 at 10 26 23 AM

mattsetaro avatar Aug 28 '24 14:08 mattsetaro

Is this still happening in the latest releases? I am testing the code in main and I am not seeing these types of numbers (I got 3 max) and I am using this sample code: https://github.com/mattsetaro/MauiMemoryLeak/tree/main/MauiApp1

In that sample, I tried 8.0.3, 8.0.41 and 8.0.82 and was not really able to see any change. If I ran it as is, the size grew from 3 "mb" to 10 "mb" and then the GC collected it all. And, if I put this code in the OnAppearing that was logging, I see that it never changes even after running for a minute:

GC.Collect();
GC.WaitForPendingFinalizers();
await Task.Yield();

But, I was able to repro it in the test code:

public class ShellNavigatingTests : ShellTestBase
{
  readonly ITestOutputHelper _testOutputHelper;
  
  public ShellNavigatingTests(ITestOutputHelper testOutputHelper)
  {
	  _testOutputHelper = testOutputHelper;
  }
  
  [Fact]
  public async Task MemoryLeak()
  {
	  Routing.RegisterRoute("page1/page2", typeof(DummyPage));
	  Routing.RegisterRoute("page1/page2/page3", typeof(DumberPage));
  
	  var shell = new TestShell(
		  CreateShellItem(shellSectionRoute: "page1")
	  );
  
	  int ctr = 0;
	  while (ctr < 5_000)
	  {
		  GC.Collect();
		  GC.WaitForPendingFinalizers();
		  await Task.Yield();
  
		  await shell.GoToAsync("page1/page2/page3");
		  Assert.True(shell.CurrentPage is DumberPage);
  
		  await shell.GoToAsync("..");
		  Assert.True(shell.CurrentPage is DummyPage);
  
		  if (++ctr % 500 == 0)
		  {
			  _testOutputHelper.WriteLine("Total memory: " + GC.GetTotalMemory(false).ToString("N0"));
		  }
	  }
  }
  
  class DummyPage : Page
  {
  
  }
  
  class DumberPage : Page
  {
  
  }
}

mattleibow avatar Sep 02 '24 19:09 mattleibow

I can repro this issue at Windows platform on the latest 17.12.0 Preview 1.0(8.0.82 & 8.0.80). Sample project: MauiApp8 (1).zip

jaosnz-rep avatar Sep 09 '24 07:09 jaosnz-rep

I can repro this issue at Windows platform on the latest 17.12.0 Preview 1.0(8.0.82 & 8.0.80). Sample project: MauiApp8 (1).zip

This sample isn't really correct.

This sample is pushing pages onto the stack so those pages aren't ever going to collect unless they are popped off the stack

for example, if I pop all the pages then the memory collects

Image

PureWeen avatar Nov 20 '24 18:11 PureWeen

Hi @vsfeedback. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.