This is Xamarin's bug tracking system. For product support, please use the support links listed in your Xamarin Account.
Bug 44047 - Memory leak when using SetBackButtonTitle on iOS
Summary: Memory leak when using SetBackButtonTitle on iOS
Status: VERIFIED FIXED
Alias: None
Product: Forms
Classification: Xamarin
Component: Forms (show other bugs)
Version: 2.3.2
Hardware: PC Mac OS
: --- normal
Target Milestone: ---
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2016-09-07 11:11 UTC by Falko Schindler
Modified: 2017-07-12 11:50 UTC (History)
6 users (show)

See Also:
Tags: ac
Is this bug a regression?: ---
Last known good build:


Attachments

Description Falko Schindler 2016-09-07 11:11:10 UTC
I discovered a memory leak when using NavigationPage.SetBackButtonTitle. The minimal example I came up with for reproducing the bug is as follows.

There is a master-detail page with a "Menu" (master) and a "Start" page (detail). The start page contains a button pushing a "Special" page. An finally, the special page contains a button pushing another "Empty" content page.

The start page outputs to the console when being constructed or destructed. So basically we expect to see "Constructor" and "Destructor" outputs when navigating the app. But when repeatedly navigating from menu via start and special page all the way to the empty content page and back, no destructor is called (or at least not every time). When removing the NavigationPage.SetBackButtonTitle command, however, the page is destructed as expected. 

The issue only occurs on iOS.

    public class App : Application
    {
        readonly MasterDetailPage masterDetailPage;

        public App()
        {
            masterDetailPage = new MasterDetailPage {
                Master = new ContentPage {
                    Title = "Menu",
                    Content = new Button {
                        Text = "Open start page",
                        Command = new Command(o => {
                            masterDetailPage.IsPresented = false;
                            masterDetailPage.Detail = new NavigationPage(new StartPage());
                        }),
                    },
                },
                Detail = new NavigationPage(new StartPage()),
            };

            MainPage = masterDetailPage;
        }

        public async Task PushAsync(ContentPage page)
        {
            await (masterDetailPage.Detail as NavigationPage).PushAsync(page);
        }
    }

    public class StartPage : ContentPage
    {
        public StartPage()
        {
            Title = "Start page";
            Content = new Button {
                Text = "Open special page",
                Command = new Command(async o => await (Application.Current as App).PushAsync(new SpecialContentPage())),
            };

            GC.Collect();
            Console.WriteLine("Constructor");
        }

        ~StartPage()
        {
            Console.WriteLine("Destructor");
        }
    }

    public class SpecialContentPage : ContentPage
    {
        public SpecialContentPage()
        {
            Title = "Special page";
            Content = new Button {
                Text = "Open empty sub page",
                Command = new Command(async p => await (Application.Current as App).PushAsync(new ContentPage {
                    Title = "Empty page",
                })),
            };
            NavigationPage.SetBackButtonTitle(this, "Special");
        }
    }

Example output:

2016-09-07 13:02:50.009 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
2016-09-07 13:02:54.292 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
2016-09-07 13:02:59.006 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
Thread finished: <Thread Pool> #3
2016-09-07 13:03:03.775 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
2016-09-07 13:03:08.333 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
2016-09-07 13:03:13.148 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
2016-09-07 13:03:18.559 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
2016-09-07 13:03:23.690 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
2016-09-07 13:03:23.691 SetBackButtonTitleBug.iOS[27698:5640929] Destructor
2016-09-07 13:03:31.283 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
2016-09-07 13:03:31.283 SetBackButtonTitleBug.iOS[27698:5640929] Destructor
2016-09-07 13:03:35.501 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
Thread finished: <Thread Pool> #4
2016-09-07 13:03:40.991 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
Thread finished: <Thread Pool> #2
2016-09-07 13:03:45.863 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
2016-09-07 13:03:50.881 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
2016-09-07 13:03:55.819 SetBackButtonTitleBug.iOS[27698:5640916] Constructor
Comment 1 Rui Marinho 2016-10-04 11:40:59 UTC
Still a issue in 2.3.3
Comment 2 adrianknight89 2016-10-20 16:33:55 UTC
This could be further looked at. 

However, finalizers are not guaranteed to run in a balanced way with constructors. Well, (I think?) they are eventually unless the app is terminated abruptly, but they are not balanced all the time while the app is alive.

GC.Collect() marks live objects that should be "dead" and puts them in a separate async finalization thread. It's this second thread that actually invokes finalizers (destructors).

If you want to ensure finalizers are called, then you need to do

GC.Collect(); // reclaims memory from dead objects and marks new objects dead

GC.WaitForPendingFinalizers(); // waits until finalizers are run

GC.Collect(); // reclaims memory from the newly dead objects and marks new objects dead

I have not run your code. You might want to try the above lines of code and see what happens.
Comment 3 adrianknight89 2016-10-20 16:53:18 UTC
That said, if this is only happening on iOS, it should be looked at.
Comment 4 adrianknight89 2016-11-12 05:12:35 UTC
I just ran your repro with GC.Collect(); followed by GC.WaitForPendingFinalizers(); and was able to get a more balanced constructor-destructor pattern. However, it looks like you were right that SetBackButtonTitle creates a memory leak.

See https://github.com/xamarin/Xamarin.Forms/pull/523
Comment 5 Rui Marinho 2017-02-09 16:10:07 UTC
Should be fixed in 2.3.5-pre1
Comment 6 Saurabh Paunikar 2017-07-12 11:50:00 UTC
Have verified the bug with latest forms build version 2.3.5.256-pre6.

Bug is seems to be fixed.

Attaching the screencast & logs for more detail.

Screencast : https://www.screencast.com/t/24tE3J38t

Note You need to log in before you can comment on or make changes to this bug.