-
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
Many controls (mainly iOS) are not collected by GC #18365
Comments
Verified this on Visual Studio Enterprise 17.8.0 Preview 5.0(8.0.0-rc.2.9373). Repro on Windows 11, Android 14.0-API34 and iOS 16.4 with below Project: |
Wow good catch. 👍 |
This comment was marked as off-topic.
This comment was marked as off-topic.
There is a PR open for Some of these could have also been discovered here: #18318 There is a test we can add additional controls to start validating these: maui/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs Lines 39 to 54 in 3c0ff67
|
Context: dotnet#18365 I could reproduce a leak in `Frame` by adding a parameterized test: [InlineData(typeof(Frame))] public async Task HandlerDoesNotLeak(Type type) `FrameRenderer` has a circular reference via its base type, `VisualElementRenderer`: * `Frame` -> * `FrameRenderer` / `VisualElementRenderer` -> * `Frame` (via `VisualElementRenderer._virtualView`) To solve this issue, I made `_virtualView` a `WeakReference`, but only on iOS or MacCatalyst platforms. We don't necessarily need this treatment for Windows or Android. My initial attempt threw a `NullReferenceException`, because the `Element` property is accessed before `SetVirtualView()` returns. I had to create a `_tempElement` field to hold the value temporarily, clearing it to avoid a circular reference. The Roslyn analyzer didn't catch this cycle because it doesn't warn about `IPlatformViewHandler`, only `NSObject`'s. Will investigate further on this example here: jonathanpeppers/memory-analyzers#12
Context: dotnet#18365 I could reproduce a leak in `Frame` by adding a parameterized test: [InlineData(typeof(Frame))] public async Task HandlerDoesNotLeak(Type type) `FrameRenderer` has a circular reference via its base type, `VisualElementRenderer`: * `Frame` -> * `FrameRenderer` / `VisualElementRenderer` -> * `Frame` (via `VisualElementRenderer._virtualView`) To solve this issue, I made `_virtualView` a `WeakReference`, but only on iOS or MacCatalyst platforms. We don't necessarily need this treatment for Windows or Android. My initial attempt threw a `NullReferenceException`, because the `Element` property is accessed before `SetVirtualView()` returns. I had to create a `_tempElement` field to hold the value temporarily, clearing it to avoid a circular reference. The Roslyn analyzer didn't catch this cycle because it doesn't warn about `IPlatformViewHandler`, only `NSObject`'s. Will investigate further on this example here: jonathanpeppers/memory-analyzers#12
Context: dotnet#18365 Adding a parameter to the test: [Theory("Handler Does Not Leak")] [InlineData(typeof(ImageButton))] public async Task HandlerDoesNotLeak(Type type) Shows a memory leak in `ImageButton`, caused by the cycle * `ImageButtonHandler` -> * `UIButton` events like `TouchUpInside` -> * `ImageButtonHandler` I could solve this problem by creating a `ImageButtonProxy` class -- the same pattern I've used in other PRs to avoid cycles. This makes an intermediate type to handle the events and breaks the cycle. Still thinking if the analyzer could have caught this, issue filed at: jonathanpeppers/memory-analyzers#12
Context: dotnet#18365 Adding a parameter to the test: [Theory("Handler Does Not Leak")] [InlineData(typeof(ImageButton))] public async Task HandlerDoesNotLeak(Type type) Shows a memory leak in `ImageButton`, caused by the cycle * `ImageButtonHandler` -> * `UIButton` events like `TouchUpInside` -> * `ImageButtonHandler` I could solve this problem by creating a `ImageButtonProxy` class -- the same pattern I've used in other PRs to avoid cycles. This makes an intermediate type to handle the events and breaks the cycle. Still thinking if the analyzer could have caught this, issue filed at: jonathanpeppers/memory-analyzers#12
Context: dotnet#18365 Adding a parameter to the test: [Theory("Handler Does Not Leak")] [InlineData(typeof(Stepper))] public async Task HandlerDoesNotLeak(Type type) Shows a memory leak in `Stepper`, caused by the cycle * `StepperHandler` -> * `UIStepper.ValueChanged` event -> * `StepperHandler` I could solve this problem by creating a `StepperProxy` class -- the same pattern I've used in other PRs to avoid cycles. This makes an intermediate type to handle the events and breaks the cycle. Still thinking if the analyzer could have caught this, issue filed at: jonathanpeppers/memory-analyzers#12
Context: #18365 Adding a parameter to the test: [Theory("Handler Does Not Leak")] [InlineData(typeof(Stepper))] public async Task HandlerDoesNotLeak(Type type) Shows a memory leak in `Stepper`, caused by the cycle * `StepperHandler` -> * `UIStepper.ValueChanged` event -> * `StepperHandler` I could solve this problem by creating a `StepperProxy` class -- the same pattern I've used in other PRs to avoid cycles. This makes an intermediate type to handle the events and breaks the cycle. Still thinking if the analyzer could have caught this, issue filed at: jonathanpeppers/memory-analyzers#12
* [ios] fix memory leak in `ImageButton` Context: #18365 Adding a parameter to the test: [Theory("Handler Does Not Leak")] [InlineData(typeof(ImageButton))] public async Task HandlerDoesNotLeak(Type type) Shows a memory leak in `ImageButton`, caused by the cycle * `ImageButtonHandler` -> * `UIButton` events like `TouchUpInside` -> * `ImageButtonHandler` I could solve this problem by creating a `ImageButtonProxy` class -- the same pattern I've used in other PRs to avoid cycles. This makes an intermediate type to handle the events and breaks the cycle. Still thinking if the analyzer could have caught this, issue filed at: jonathanpeppers/memory-analyzers#12 * [android] fix memory leak in `ImageButton` Context: a270ebd Context: 1bbe79d Context: material-components/material-components-android#2063 Reviewing a GC dump of the device tests, I noticed a `System.Action` keeping the `ImageButton` alive: Microsoft.Maui.Controls.ImageButton System.Action Java.Lang.Thread.RunnableImplementor So next, I looked for `System.Action` and found the path on the `ReferencedTypes` tab: System.Action Microsoft.Maui.Platform.ImageButtonExtensions.[]c__DisplayClass4_0 Google.Android.Material.ImageView.ShapeableImageView Microsoft.Maui.Controls.ImageButton Which led me to the code: public static async void UpdatePadding(this ShapeableImageView platformButton, IImageButton imageButton) { platformButton.SetContentPadding(imageButton); platformButton.Post(() => { platformButton.SetContentPadding(imageButton); }); platformButton.SetContentPadding(imageButton); } ?!? Why is this code calling `SetContentPadding` three times? Reviewing the commit history: * a270ebd * 1bbe79d * material-components/material-components-android#2063 I could comment out the code and the leak is solved, but I found I could also change the code to use `await Task.Yield()` for the same result.
Thank you, @heyThorsten, for creating this sample! I turned the above list of controls into a checklist. Updated which have fixes in dotnet/maui/main or open PRs so far. |
Context: dotnet#18365 Adding a parameter to the test: [Theory("Handler Does Not Leak")] [InlineData(typeof(Slider))] public async Task HandlerDoesNotLeak(Type type) Shows a memory leak in `Slider`, caused by the cycle * `SliderHandler` -> * `UISlider` events -> * `SliderHandler` I could solve this problem by creating a `SliderProxy` class -- the same pattern I've used in other PRs to avoid cycles. This makes an intermediate type to handle the events and breaks the cycle. Still thinking if the analyzer could have caught this, issue filed at: jonathanpeppers/memory-analyzers#12
Context: #18365 I could reproduce a leak in `Frame` by adding a parameterized test: [InlineData(typeof(Frame))] public async Task HandlerDoesNotLeak(Type type) `FrameRenderer` has a circular reference via its base type, `VisualElementRenderer`: * `Frame` -> * `FrameRenderer` / `VisualElementRenderer` -> * `Frame` (via `VisualElementRenderer._virtualView`) To solve this issue, I made `_virtualView` a `WeakReference`, but only on iOS or MacCatalyst platforms. We don't necessarily need this treatment for Windows or Android. My initial attempt threw a `NullReferenceException`, because the `Element` property is accessed before `SetVirtualView()` returns. I had to create a `_tempElement` field to hold the value temporarily, clearing it to avoid a circular reference. The Roslyn analyzer didn't catch this cycle because it doesn't warn about `IPlatformViewHandler`, only `NSObject`'s. Will investigate further on this example here: jonathanpeppers/memory-analyzers#12
Context: dotnet#18365 I could reproduce a leak in `TableView` by adding a parameterized test: [InlineData(typeof(TableView))] public async Task HandlerDoesNotLeak(Type type) `TableViewModelRenderer` has two cycles: * `TableView` -> * `TableViewRenderer` -> * `TableViewModelRenderer` -> * `TableView` via `View` field * `UITableView.Source` -> * `TableViewModelRenderer` -> * `UITableView` via `Table` field I left the `Table` and `View` fields in place to not break any public APIs. They are now unused and marked `[Obsolete]`. I used `WeakReference<T>` to solve these two cycles and the test now passes. The analyzer warned about these locations, but we don't have the analyzer enabled on `Microsoft.Maui.Controls` yet: dotnet#18318 (comment)
Context: dotnet#18365 Context: https://github.com/heyThorsten/GCTest Testing the sample app, I could see a leak in `WebView` on Windows. A memory snapshot showed: Microsoft.Maui.Handlers.WebViewHandler Windows.Foundation.TypedEventHandler<Microsoft.UI.Xaml.Controls.WebView2, Microsoft.UI.Xaml.Controls.CoreWebView2InitializedEventArgs> WinRT._EventSource_global__Windows_Foundation_TypedEventHandler_global__Microsoft_UI_Xaml_Controls_WebView2__global__Microsoft_UI_Xaml_Controls_CoreWebView2InitializedEventArgs_+EventState Windows.Foundation.TypedEventHandler<Microsoft.UI.Xaml.Controls.WebView2, Microsoft.UI.Xaml.Controls.CoreWebView2InitializedEventArgs> [Dependent Handle, Microsoft.Maui.Platform.LayoutPanel <0x2079E7A8D00>] Microsoft.UI.Xaml.RoutedEventHandler WinRT._EventSource_global__Microsoft_UI_Xaml_RoutedEventHandler+EventState Microsoft.UI.Xaml.RoutedEventHandler [Dependent Handle, Microsoft.Maui.Platform.LayoutPanel <0x2079E7A8D00>] I could this same leak in `WebView` by updating `MemoryTests` to do: if (view is WebView webView) { webView.Source = new HtmlWebViewSource { Html = "<p>hi</p>" }; await Task.Delay(1000); } After a bit of debugging, I found that events on `WebView` seem to suffer from the same "circular reference" problem we see on iOS. This makes sense, as it is an unmanaged/native control. (Might be COM?) So we have the cycle: * `WebViewHandler` -> * `WebView2` -> * Various `WebView2` events: * `CoreWebView2Initialized` * `HistoryChanged` * `NavigationStarting` * `NavigationCompleted` * `WebViewHandler` Using the same pattern we've been using for iOS: creating a `WebView2Proxy` type solves the issue. Note that I didn't have to do this for all events, just the ones subscribing to `WebView2`-related events. I also don't see the `WebView2Proxy` object living forever, so the issue does appear to be a cycle.
Can you add the "Border" item to the checklist ? Because it seems that I met some trouble with this item. For example, when I use a list of items which are defined into a border (nearly 100 items).. the application crash on some rendering / navigating... but no crash when I remove the border. I don't have any log with this (sorry). |
@Yokaichan the symptoms you are describing here does not necessarily sound like a memory leak. We already have memory tests in-place to be sure Can you get the full stack trace of the crash and file a new issue? |
Context: #18365 I could reproduce a leak in `TableView` by adding a parameterized test: [InlineData(typeof(TableView))] public async Task HandlerDoesNotLeak(Type type) `TableViewModelRenderer` has two cycles: * `TableView` -> * `TableViewRenderer` -> * `TableViewModelRenderer` -> * `TableView` via `View` field * `UITableView.Source` -> * `TableViewModelRenderer` -> * `UITableView` via `Table` field I left the `Table` and `View` fields in place to not break any public APIs. They are now unused and marked `[Obsolete]`. I used `WeakReference<T>` to solve these two cycles and the test now passes. The analyzer warned about these locations, but we don't have the analyzer enabled on `Microsoft.Maui.Controls` yet: #18318 (comment)
Context: #18365 Context: https://github.com/heyThorsten/GCTest Testing the sample app, I could see a leak in `WebView` on Windows. A memory snapshot showed: Microsoft.Maui.Handlers.WebViewHandler Windows.Foundation.TypedEventHandler<Microsoft.UI.Xaml.Controls.WebView2, Microsoft.UI.Xaml.Controls.CoreWebView2InitializedEventArgs> WinRT._EventSource_global__Windows_Foundation_TypedEventHandler_global__Microsoft_UI_Xaml_Controls_WebView2__global__Microsoft_UI_Xaml_Controls_CoreWebView2InitializedEventArgs_+EventState Windows.Foundation.TypedEventHandler<Microsoft.UI.Xaml.Controls.WebView2, Microsoft.UI.Xaml.Controls.CoreWebView2InitializedEventArgs> [Dependent Handle, Microsoft.Maui.Platform.LayoutPanel <0x2079E7A8D00>] Microsoft.UI.Xaml.RoutedEventHandler WinRT._EventSource_global__Microsoft_UI_Xaml_RoutedEventHandler+EventState Microsoft.UI.Xaml.RoutedEventHandler [Dependent Handle, Microsoft.Maui.Platform.LayoutPanel <0x2079E7A8D00>] I could this same leak in `WebView` by updating `MemoryTests` to do: if (view is WebView webView) { webView.Source = new HtmlWebViewSource { Html = "<p>hi</p>" }; await Task.Delay(1000); } After a bit of debugging, I found that events on `WebView` seem to suffer from the same "circular reference" problem we see on iOS. This makes sense, as it is an unmanaged/native control. (Might be COM?) So we have the cycle: * `WebViewHandler` -> * `WebView2` -> * Various `WebView2` events: * `CoreWebView2Initialized` * `HistoryChanged` * `NavigationStarting` * `NavigationCompleted` * `WebViewHandler` Using the same pattern we've been using for iOS: creating a `WebView2Proxy` type solves the issue. Note that I didn't have to do this for all events, just the ones subscribing to `WebView2`-related events. I also don't see the `WebView2Proxy` object living forever, so the issue does appear to be a cycle.
I just noticed that Border on iOS has memory leak when: <Border>
<Border.StrokeShape>
<RoundRectangle CornerRadius="8"
StrokeThickness="0" />
</Border.StrokeShape>
</Border> Try add that to the unit tests @jonathanpeppers , I've spent all day trying to figure out why this component we've created is leaking. If I remove lines 62-66 it does not leak anymore. Edit: |
It looks like Easy to reproduce. Just do something like:
You'll find that the NavigationPage isn't collected, but PageB is collected once it is replaced by PageC. Let me know if you'd rather see another issue opened for this. Thought you might just want to add it to the list. This one might be particularly nasty if the leak keeps the NavgationPage's entire nav stack in memory. |
@haavamoa's discovery highlights a weakness in the methods currently at work to detect these leaks. While a great start, it's not enough to load an empty (or near-empty) control into a test. Each of these controls have broader APIs that, when exercised, might also introduce leaks. I've cobbled together a detection mechanism that capitalizes on the cascading nature of these leaks (i.e. propagating to the host page) to provide near-immediate feedback when such leaks occur. It centrally tracks page navigation events, forces garbage collection, and then reports on the collection status of the previous page. The project, inspired by @heyThorsten's in-app testing approach, demonstrates how memory testing techniques from the MAUI wiki can be applied to instrument a MAUI app to actively detect page-level memory leaks during development. |
I've broken the 3 remaining controls for this issue out into separate tracking issues. @Yokaichan @haavamoa @AdamEssenmacher Thank you for all the work you did on this @heyThorsten, we all really appreciate it! |
Description
I'm working on an app for iOS. The app creates new instances of pages and view models every time a new page is pushed to the navigation stack. After a while the app gets very slow and crashes.
So I started to comment out parts of XAML and to trigger GC and found that the pages were no longer cleaned up if they had certain controls.
To find out which controls causes problems I created a simple test programme that does nothing more than adding one single control to a page and pushes that page onto the navigation stack. By clicking a button the garbage collector can be forced to work. The program outputs a debug message when the constructor and finaliser of the opened page is called.
On iOS there are many controls where there is no finaliser call of the page, resulting in memory leaks.
On iOS there is no finaliser log output (~xxx Page) for these controls:
Frame
,VisualElementRenderer
#18552ImageButton
#18602Slider
#18681Stepper
#18663Switch
#18682CarouselView
&INotifyCollectionChanged
#18267TableView
#18718On Windows there is no finaliser log output (~xxx Page) for these controls:
WebView
#18810On Android there is no finaliser log output (~xxx Page) for these controls:
CarouselView
#18584Steps to Reproduce
Link to public reproduction project repository
https://github.com/heyThorsten/GCTest
Version with bug
8.0.0-rc.2.9373
Is this a regression from previous behavior?
Not sure, did not test other versions
Last version that worked well
Unknown/Other
Affected platforms
iOS, Android, Windows
Affected platform versions
iOS 16.4 (Simulator), Android 10.0 (Emulator), Windows 10.0.19041.0
Did you find any workaround?
No response
Relevant log output
The text was updated successfully, but these errors were encountered: