-
Notifications
You must be signed in to change notification settings - Fork 2
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
Consider warning about IPlatformViewHandler
#12
Comments
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
Ok, I think I see the pattern that isn't currently caught by the analyzer: class Foo
{
UIButton bar = new();
public Foo()
{
bar.TouchUpInside += OnTouch;
}
void OnTouch(object sender, EventArgs e) { }
}
I'll need to test in isolation to verify this leaks every time. It definitely leaks if No idea how the analyzer could check for this yet... I guess warn on any event? That would warn on all usage of |
Ok, the simple example above, https://github.com/jonathanpeppers/MemoryLeaksOniOS/compare/UIButtonEvents So, this must only happen with MAUI |
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.
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: dotnet#18365 Adding a parameter to the test: [Theory("Handler Does Not Leak")] [InlineData(typeof(Switch))] public async Task HandlerDoesNotLeak(Type type) Shows a memory leak in `Switch`, caused by the cycle * `SwitchHandler` -> * `UISwitch.ValueChanged` event -> * `SwitchHandler` I could solve this problem by creating a `SwitchProxy` 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(Switch))] public async Task HandlerDoesNotLeak(Type type) Shows a memory leak in `Switch`, caused by the cycle * `SwitchHandler` -> * `UISwitch.ValueChanged` event -> * `SwitchHandler` I could solve this problem by creating a `SwitchProxy` 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(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
This problem came up with MAUI's
Frame
class, asFrameRenderer
is not anNSObject
but somehow produced a cycle that I could cause problems with at runtime.Examples:
Frame
,VisualElementRenderer
dotnet/maui#18552ImageButton
dotnet/maui#18602Stepper
dotnet/maui#18663Slider
dotnet/maui#18681The text was updated successfully, but these errors were encountered: