dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
21.98k stars 1.72k forks source link

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

Open vsfeedback opened 2 weeks ago

vsfeedback commented 2 weeks ago

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)

github-actions[bot] commented 2 weeks ago

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.

mattsetaro commented 2 weeks ago

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 commented 2 weeks ago

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

mattleibow commented 1 week ago

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
  {

  }
}
jaosnz-rep commented 4 days ago

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