-
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
[controls] fix memory leak with CarouselView
& INotifyCollectionChanged
#18267
[controls] fix memory leak with CarouselView
& INotifyCollectionChanged
#18267
Conversation
return new ObservableItemsSource(itemsSource as IEnumerable, container, notifier); | ||
return new ObservableItemsSource(itemsSource, container, notifier); |
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.
This as
was just extra, the itemsSource
is already IEnumerable
.
_collectionChanged = CollectionChanged; | ||
_proxy.Subscribe((INotifyCollectionChanged)itemSource, _collectionChanged); |
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.
We store CollectionChanged
in a field, so it will remain alive as long as the ObservableItemsSource
.
Details here: https://github.com/dotnet/maui/wiki/Memory-Leaks#when-we-cant-use-weakeventmanager
The new test fails on iOS & Windows, looking into it. |
This one ended up being a big mess: different problems on each platform... If this is green, I'll rebase and explain in a better PR description/commit message. |
…anged` Context: dotnet#17726 In investigating dotnet#17726, I found a memory leak with `CarouselView`: 1. Have a long-lived `INotifyCollectionChanged` like `ObservableCollection`, that lives the life of the application. 2. Set `CarouselView.ItemsSource` to the collection. 3. `CarouselView` will live forever, even if the page is popped, etc. I further expanded upon `MemoryTests` to assert more things for each control, and was able to reproduce this issue. To fully solve this, I had to fix issues on each platform. ~~ Android ~~ `ObservableItemsSource` subscribes to the `INotifyCollectionChanged` event, making it live forever in this case. To fix this, I used the same technique in 58a42e5: `WeakNotifyCollectionChangedProxy`. `ObservableItemsSource` is used for `ItemsView`-like controls, so this may fix other leaks as well. ~~ Windows ~~ Windows has the same issue as Android, but for the following types: * `CarouselViewHandler` subscribes to `INotifyCollectionChanged` * `ObservableItemTemplateCollection` subscribes to `INotifyCollectionChanged` These could be fixed with `WeakNotifyCollectionChangedProxy`. Then I found another issue with `ItemTemplateContext` These three types are used for other `ItemsView`-like controls, so this may fix other leaks as well. ~~ iOS/Catalyst ~~ These platforms suffered from multiple circular references: * `CarouselViewController` -> `CarouselView` -> `CarouselViewHandler` -> `CarouselViewController` * `ItemsViewController` -> `CarouselView` -> `CarouselViewHandler` -> `ItemsViewController` (`CarouselViewController` subclasses this) * `CarouselViewLayout` -> `CarouselView` -> `CarouselViewHandler` -> `CarouselViewLayout` The analyzer added in 2e65626 did not yet catch these because I only added the analyzer so far for `Microsoft.Maui.dll` and these issues were in `Microsoft.Maui.Controls.dll`. In a future PR, we can proactively try to add the analyzer to all projects. ~~ Conclusion ~~ Unfortunately, this does not fully solve dotnet#17726, as there is at least one further leak on Android from my testing. But this is a good step forward.
fdb2bef
to
b1d8176
Compare
CarouselView
& INotifyCollectionChanged
CarouselView
& INotifyCollectionChanged
Context: #17726
In investigating #17726, I found a memory leak with
CarouselView
:Have a long-lived
INotifyCollectionChanged
likeObservableCollection
, that lives the life of the application.Set
CarouselView.ItemsSource
to the collection.CarouselView
will live forever, even if the page is popped, etc.I further expanded upon
MemoryTests
to assert more things for eachcontrol, and was able to reproduce this issue.
To fully solve this, I had to fix issues on each platform.
Android
ObservableItemsSource
subscribes to theINotifyCollectionChanged
event, making it live forever in this case.
To fix this, I used the same technique in 58a42e5:
WeakNotifyCollectionChangedProxy
.ObservableItemsSource
is used forItemsView
-like controls, so this may fix other leaks as well.Windows
Windows has the same issue as Android, but for the following types:
CarouselViewHandler
subscribes toINotifyCollectionChanged
ObservableItemTemplateCollection
subscribes toINotifyCollectionChanged
These could be fixed with
WeakNotifyCollectionChangedProxy
.Then I found another issue with
ItemTemplateContext
, it holds a strongreference to
BindableObject Container
, theCarouselView
. I foundthis kept the
CarouselView
alive indefinitely, but changing to aWeakReference
solved this issue.These three types are used for other
ItemsView
-like controls, so thismay fix other leaks as well.
iOS/Catalyst
These platforms suffered from multiple circular references:
CarouselViewController
->CarouselView
->CarouselViewHandler
->CarouselViewController
ItemsViewController
->CarouselView
->CarouselViewHandler
->ItemsViewController
(CarouselViewController
subclasses this)CarouselViewLayout
->CarouselView
->CarouselViewHandler
->CarouselViewLayout
The analyzer added in 2e65626 did not yet catch these because I only
added the analyzer so far for
Microsoft.Maui.dll
and these issues werein
Microsoft.Maui.Controls.dll
. In a future PR, we can proactively tryto add the analyzer to all projects.
Conclusion
Unfortunately, this does not fully solve #17726, as there is at least
one further leak on Android from my testing. But this is a good step
forward.