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] fix memory leak with Page + Layout #14108

Merged
merged 1 commit into from
Mar 29, 2023

Conversation

jonathanpeppers
Copy link
Member

@jonathanpeppers jonathanpeppers commented Mar 21, 2023

Further fixes #13520

7d0af63 did not appear to be a complete fix for #13520, as when I retested the customer sample, I found I needed to remove a VerticalStackLayout for the problem to go away.

I could reproduce the issue in a test by doing:

var page = new ContentPage { Title = "Page 2", Content = new VerticalStackLayout { new Label() } };

After lots of debugging, I found the underlying issue was that MauiView on iOS held a reference to the cross-platform IView.

After changing the backing field for this to be a WeakReference<IView> the above problem appears to be solved.

I also removed LayoutView.CrossplatformArrange/Measure as we can just use the View property directly instead.

@jsuarezruiz jsuarezruiz added the legacy-area-perf Startup / Runtime performance label Mar 22, 2023
@jonathanpeppers jonathanpeppers marked this pull request as ready for review March 22, 2023 17:23
Comment on lines -71 to -72
internal Func<double, double, Size>? CrossPlatformMeasure { get; set; }
internal Func<Rect, Size>? CrossPlatformArrange { get; set; }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed these, because you can just call View.CrossPlatformMeasure() directly.

Otherwise we might need to unset them in places, like in 7d0af63.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed these, because you can just call View.CrossPlatformMeasure() directly.

The design intent here was for the handlers to be able to directly modify the xplat measure/arrange methods directly when necessary. It's not currently used on iOS (mostly for accidental historical reasons), but the other platforms do make use of it. The intent was to keep the designs symmetric on all the platforms.

(The main place you can see this at work in the Android and Windows ScrollViewHandlers.)

So I'd rather not remove these. Or, if there's a compelling memory-leak reason to remove them, we'll need to work out a way to inject custom xplat measure/arrange methods from the handlers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design intent here was for the handlers to be able to directly modify the xplat measure/arrange methods directly when necessary.

Is this design documented? So other people will know about it?

Or, if there's a compelling memory-leak reason to remove them, we'll need to work out a way to inject custom xplat measure/arrange methods from the handlers.

The leak appears to return if I put them back. It makes the shell & navigation DoNotLeak tests fail. Will share a *.gcdump file tomorrow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this design documented? So other people will know about it?

Clearly not well enough, if at all. 🙂

@hartez
Copy link
Contributor

hartez commented Mar 22, 2023

After lots of debugging, I found the underlying issue was that MauiView on iOS held a reference to the cross-platform IView.

Why is the MauiView still around? What's holding a reference to it? I'm not clear on why the MauiView itself can't be collected along with the View (the VerticalStackLayout?) that it's referencing.

Further fixes: dotnet#13520

7d0af63 did not appear to be a complete fix for dotnet#13520, as when I
retested the customer sample, I found I needed to remove a
`VerticalStackLayout` for the problem to go away.

I could reproduce the issue in a test by doing:

    var page = new ContentPage { Title = "Page 2", Content = new VerticalStackLayout { new Label() } };

After lots of debugging, I found the underlying issue was that
`MauiView` on iOS held a reference to the cross-platform `IView`.

After changing the backing field for this to be a `WeakReference<IView>`
the above problem appears to be solved.

I also removed `LayoutView.CrossplatformArrange/Measure` as we can just
use the `View` property directly instead.
@jonathanpeppers
Copy link
Member Author

Why is the MauiView still around?

The *.gcdump file I'm able to get from Mono just says MauiView > Other Roots. I can make a new one tomorrow and share it here. Other Roots is probably a GCHandle that is holding it until its Objective-C refcount is 0. In 7d0af63, calling RemoveFromSuperview() fixed a similar issue.

It may be that we are still leaking MauiView's all the time -- and this is only slightly better in that Page's on iOS can go away now. When removing CrossplatformArrange/Measure and making View weak, the MauiView no longer holds onto the cross-platform IView.

I believe there could be some core problem here that isn't understood. But I've not been able to reproduce a real issue here: https://github.com/jonathanpeppers/MemoryLeaksOniOS

@jonathanpeppers
Copy link
Member Author

Here is the path to the leak with the changes reverted:

image

I changed the ContentPage to FooPage : ContentPage, to make it easier to spot. (ContentPage's are in the test runner UI)

You can unzip & open this in VS: my-dev-port_20230323_094815.zip

@jonathanpeppers
Copy link
Member Author

jonathanpeppers commented Mar 23, 2023

I've been doing more testing and I think this is what was going on:

  1. VerticalStackLayout -> LayoutHandler : MAUI views hold a strong reference to handlers
    a. NOTE: VerticalStackLayout.Parent is a ContentPage which is the main leak
  2. LayoutHandler -> LayoutView: handlers hold a strong reference to native view
  3. LayoutView -> VerticalStackLayout: LayoutView held a strong reference to the MAUI view due to CrossplatformLayout/Arrange and the View property.

I made no. 3 a weak reference, and this appears to break the cycle and all of the above can go away... Note that cycles are only a problem for NSObject subclasses -- there is not an issue like this with plain C# objects or Android Java.Lang.Object subclasses.

I just retested with the fix in place, and I don't see VerticalStackLayout, LayoutHandler, or LayoutView staying around from the page I'm testing.

@jonathanpeppers
Copy link
Member Author

Ok, I have a minimal repro of a leak on iOS.

The core issue was doing something like this:

class MyViewSubclass : UIView
{
    public UIView? Parent { get; set; }

    public void Add(MyViewSubclass subview)
    {
        subview.Parent = this;
        AddSubview(subview);
    }
}

//...

var parent = new MyViewSubclass();
var view = new MyViewSubclass();
parent.Add(view);

Appears to leak unless you do:

view.Parent = null;

@jonathanpeppers
Copy link
Member Author

@hartez do you have any other concerns here?

@PureWeen PureWeen merged commit cbce20c into dotnet:main Mar 29, 2023
@jonathanpeppers jonathanpeppers deleted the ios-page-leaks-act-2 branch March 29, 2023 20:55
@jonathanpeppers jonathanpeppers added memory-leak 💦 Memory usage grows / objects live forever and removed legacy-area-perf Startup / Runtime performance labels Jul 12, 2023
@github-actions github-actions bot locked and limited conversation to collaborators Dec 12, 2023
@samhouts samhouts added the fixed-in-8.0.0-preview.3.8149 Look for this fix in 8.0.0-preview.3.8149! label Aug 2, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
fixed-in-8.0.0-preview.3.8149 Look for this fix in 8.0.0-preview.3.8149! memory-leak 💦 Memory usage grows / objects live forever platform/iOS 🍎
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Every MAUI Page generates a Memory Leak on iOS and macCatalyst
5 participants