-
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
Memory leak with CarouselView #17726
Comments
@tranb3r I compared your two It will probably take a Java side GC + .NET GC for these If you do something like this, are the MAUI I was hoping to see more than one |
This problem is reproducible with iOS also with net8-rc.1 and net8-rc.2 |
I think the issue with @tranb3r's dumps might be related to: dotnet/diagnostics#4081 (comment) I think the finalizer isn't running for I will try .NET 7 & latest dotnet/maui/main, maybe I can get the dumps using the old method in .NET 7. |
I made some progress on this here, could reproduce the issue in tests: main...jonathanpeppers:maui:CarouselViewLeaks There is an issue in But there is still a further leak, I'll need to investigate more next week. My two tests here that put |
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 solve the problem, I used the same technique in 58a42e5. We can use `WeakNotifyCollectionChangedProxy` in the `ObservableItemsSource` class to prevent `CarouselView` from leaking. `ObservableItemsSource` is used for other controls, so this may fix other leaks as well. Unfortunately, this does not fully solve dotnet#17726, as there is at least one further leak on Android from my testing.
…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.
…anged` (#18267) Context: #17726 In investigating #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 #17726, as there is at least one further leak on Android from my testing. But this is a good step forward.
Fixes: dotnet#17726 Adding `CarouselView` to a page and navigating away from it keeps the page alive forever on Android. I was able to reproduce this in a test. It took me a while to actually track this one down, but the problem boils down to `MauiCarouselRecyclerView` doing: _carouselViewLayoutListener = new CarouselViewwOnGlobalLayoutListener(); _carouselViewLayoutListener.LayoutReady += LayoutReady; ViewTreeObserver.AddOnGlobalLayoutListener(_carouselViewLayoutListener); The `ViewTreeObserver` lives longer than the page, and so: * `ViewTreeObserver` -> `CarouselViewwOnGlobalLayoutListener` * `event LayoutReady` -> `MauiCarouselRecyclerView` * `MauiCarouselRecyclerView` -> `CarouselView` * `CarouselView` -> `Parent.Parent.Parent.*` -> `Page` Thus the `Page` lives forever. If we remove the `LayoutReady` event, instead: * `CarouselViewwOnGlobalLayoutListener` saves `MauiCarouselRecyclerView` in a weak reference. * It can just call `LayoutReady()` directly. * `MauiCarouselRecyclerView` is able to be GC'd now. * `MauiCarouselRecyclerView.Dispose()` already appropriately will call `ClearLayoutListener()` * Lastly, `ViewTreeObserver?.RemoveOnGlobalLayoutListener(_carouselViewLayoutListener)` With these changes, the test passes and I don't think anything will leak now. :) Other changes: * I fixed the typo in `CarouselViewwOnGlobalLayoutListener`. * I sorted the type names in the test alphabetically.
Fixes: #17726 Adding `CarouselView` to a page and navigating away from it keeps the page alive forever on Android. I was able to reproduce this in a test. It took me a while to actually track this one down, but the problem boils down to `MauiCarouselRecyclerView` doing: _carouselViewLayoutListener = new CarouselViewwOnGlobalLayoutListener(); _carouselViewLayoutListener.LayoutReady += LayoutReady; ViewTreeObserver.AddOnGlobalLayoutListener(_carouselViewLayoutListener); The `ViewTreeObserver` lives longer than the page, and so: * `ViewTreeObserver` -> `CarouselViewwOnGlobalLayoutListener` * `event LayoutReady` -> `MauiCarouselRecyclerView` * `MauiCarouselRecyclerView` -> `CarouselView` * `CarouselView` -> `Parent.Parent.Parent.*` -> `Page` Thus the `Page` lives forever. If we remove the `LayoutReady` event, instead: * `CarouselViewwOnGlobalLayoutListener` saves `MauiCarouselRecyclerView` in a weak reference. * It can just call `LayoutReady()` directly. * `MauiCarouselRecyclerView` is able to be GC'd now. * `MauiCarouselRecyclerView.Dispose()` already appropriately will call `ClearLayoutListener()` * Lastly, `ViewTreeObserver?.RemoveOnGlobalLayoutListener(_carouselViewLayoutListener)` With these changes, the test passes and I don't think anything will leak now. :) Other changes: * I fixed the typo in `CarouselViewwOnGlobalLayoutListener`. * I sorted the type names in the test alphabetically.
Description
On android with
net8-rc.1
, when navigating to a page containing aCarouselView
and then back, the memory usage grows.For comparison, no memory leak when navigating to a page containing a
CollectionView
with the sameItemsSource
andItemTemplate
.Steps to Reproduce
Go
button, it will navigate to a page with aCollectionView
.Go
button, it will navigate to a page with aCarouselView
.CarouselView
page a few times. Between the first (memory-20.gcdump) and second (memory-24.gcdump), the memory usage increases quite a lot (the page is really simple).gcdump.zip
Link to public reproduction project repository
https://github.com/tranb3r/Issues/tree/main/MauiAppCarouselViewLeak
Version with bug
8.0.0-rc.1.9171
Is this a regression from previous behavior?
Not sure, did not test other versions
Last version that worked well
Unknown/Other
Affected platforms
Android
Affected platform versions
Android 34
Did you find any workaround?
No workaround
Relevant log output
No response
The text was updated successfully, but these errors were encountered: