-
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
Revert "[ios] fix memory leak in CollectionView cells (#15831)" #24310
Conversation
This reverts commit 05697a6.
Hey there @filipnavara! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
/azp run |
Azure Pipelines successfully started running 2 pipeline(s), but failed to run 1 pipeline(s). |
[Fact("Cells Do Not Leak")] | ||
public async Task CellsDoNotLeak() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed a commit to restore the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I intentionally left it out because it’s guaranteed to fail. See the post at the top of the PR for rationale.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…but thanks for starting the CI, the big question is if the other tests fail, and it should give us the answer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this used to be the circular reference (in 05697a6 ~June 2023):
CollectionView
-> handlers / etc. ->TemplatedCell
->LabelHandler
->Label
->CollectionView
But now we have 10e3fc4, so there is no strong reference from Label
-> CollectionView
.
If the old test is not valid, it seems like we should make sure some other test checks this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the old test is not valid, it seems like we should make sure some other test checks this.
Agreed. I wanted to have a full test run first to see if anything else fails. I'll have to think about bit on how to structure the test (or whether there is an existing test coverage).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the test to ensure that CollectionView
can get collected. The VerticalCell
is still referenced, so it means there's no (strong) cycle.
… can be collected
await AssertionExtensions.WaitForGC(labels.ToArray()); | ||
await AssertionExtensions.WaitForGC(collectionViewReference); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After these changes, when would the labels
go away? Should we update this to check all the VerticalCell
instances go away instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the VerticalCell
was not created synthetically and held in local variable then they should be gone with the collection view.
That would essentially mean rewriting the test to do what ClearingItemsSourceClearsBindingContext
does and at the end checking that dropping reference to collection view also allows collecting the cells (and then the attached labels).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(The ClearingItemsSourceClearsBindingContext
test was moved/renamed - it was originally added in https://github.com/dotnet/maui/pull/14619/files#diff-a1aed29ff55ec6fe5da9c27f12285c91406be414338738b0bcc3106ec4aa675f)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's already tested by this:
maui/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.cs
Lines 106 to 169 in 639fcb0
[Fact] | |
public async Task ItemsSourceDoesNotLeak() | |
{ | |
SetupBuilder(); | |
var weakReferences = new List<WeakReference>(); | |
{ | |
var labels = new List<Label>(); | |
IList logicalChildren = null; | |
var collectionView = new CollectionView | |
{ | |
Header = new Label { Text = "Header" }, | |
Footer = new Label { Text = "Footer" }, | |
ItemTemplate = new DataTemplate(() => | |
{ | |
var label = new Label(); | |
labels.Add(label); | |
return label; | |
}), | |
}; | |
var navPage = new NavigationPage(new ContentPage { Title = "Page 1" }); | |
await CreateHandlerAndAddToWindow<WindowHandlerStub>(new Window(navPage), async handler => | |
{ | |
await navPage.PushAsync(new ContentPage { Content = collectionView }); | |
var data = new ObservableCollection<string>() | |
{ | |
"Item 1", | |
"Item 2", | |
"Item 3" | |
}; | |
weakReferences.Add(new(data)); | |
collectionView.ItemsSource = data; | |
await Task.Delay(100); | |
Assert.NotEmpty(labels); | |
foreach (var label in labels) | |
{ | |
weakReferences.Add(new(label)); | |
weakReferences.Add(new(label.Handler)); | |
weakReferences.Add(new(label.Handler.PlatformView)); | |
} | |
// Get ItemsView._logicalChildren | |
var flags = BindingFlags.NonPublic | BindingFlags.Instance; | |
logicalChildren = typeof(Element).GetField("_internalChildren", flags).GetValue(collectionView) as IList; | |
Assert.NotNull(logicalChildren); | |
// Replace with cloned collection | |
collectionView.ItemsSource = new ObservableCollection<string>(data); | |
await Task.Delay(100); | |
await navPage.PopAsync(); | |
}); | |
Assert.NotNull(logicalChildren); | |
Assert.True(logicalChildren.Count <= 5, "_logicalChildren should not grow in size!"); | |
} | |
await AssertionExtensions.WaitForGC([.. weakReferences]); | |
} |
/azp run |
Azure Pipelines successfully started running 3 pipeline(s). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this fix will somewhat regress what @jonathanpeppers did when the user navigates away from the page with the CV on it, because in those cases the UNBIND isn't going to get called
I tested with this sample here
https://github.com/Dani3654/CollectionViewMemoryLeaks/blob/main/CollectionViewMemoryLeaks/CollectionViewMemoryLeaks/Views/CollectionViewSamplePage.xaml
On the main branch the memory doesn't seem to really increase. Occasionally by 1 MB or so
But with this PR it steadily increases at a much quicker rate
I don't think this will be the case on net9 because in net9 we're going to disconnect from every handler when the user navigates away from the page
I'll look at it tomorrow. In our app we disconnect the handlers too. That said, the current status quo is that we get constant crashes which prevents us from releasing the update to our app (without building custom build of MAUI). The assumption of the added test was that there's no longer a cycle preventing the It may just be a case of the behavior described in dotnet/runtime#104272 (comment) where it takes several GC cycles to collect everything. |
Yea, not sure how much it works around this but I added a long set of collects to try and force it. Here's a branch that has that scenario added to our sandbox I added a big stretch of collects But perhaps it's still just the delayed collect you're referring to |
Unfortunately I am still pretty sure that it's the delayed GC which needs a couple of cycles. I added a "GC" button to the original sample that simply calls This is the original sample running on MAUI 8.0.60: The stairs in the Instruments graph refer to the GC collects. Here's the same scenario with this PR: Eventually the memory gets collected with enough GC cycles (~ 9 in this case; it's NativeAOT build so it may vary a bit from the regular MonoAOT build). It's theoretically possible to keep the |
Closing this. We can do less invasive fix: #24304 (comment). The less invasive fix will likely consist of:
|
Fixes #24304.
This reverts commit 05697a6 and updates the test.
The test was unfortunately testing the wrong thing - that manually created
VerticalCell
doesn't hold on to the handler and platform views. What we really want to test is thatVerticalCell
created throughUICollectionView
doesn't prevent the collection view from being collected.