-
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 in Window
#13400
Conversation
/azp run |
Azure Pipelines successfully started running 2 pipeline(s). |
2ac2129
to
3e7e522
Compare
Application.OpenWindow()
Window
Ok the test uses
xunit is moving on as if the test passed, but test code is continuing to run. Will review if we have a better way to do this. |
Will come back to this after this one lands: |
3e7e522
to
e9e8def
Compare
1034f77
to
ef2579a
Compare
@@ -30,7 +31,10 @@ IWindow IApplication.CreateWindow(IActivationState? activationState) | |||
if (activationState?.State?.TryGetValue(MauiWindowIdKey, out var requestedWindowId) ?? false) | |||
{ | |||
if (requestedWindowId != null && _requestedWindows.TryGetValue(requestedWindowId, out var w)) |
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'm testing this on Android and the TryGetValue
here seems like it's always evaluating to false.
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 added a test for this issue, and changed it to a Dictionary<string, WeakReference<Window>>
which won't cause this behavior.
I think you were getting a new string
instance and ConditionalWeakTable
doesn't work in that case.
maui/src/Controls/tests/Core.UnitTests/WindowsTests.cs
Lines 664 to 670 in 2e7c3ff
// A "cloned" key should still work | |
string key; | |
{ | |
var originalKey = table.Keys.OfType<string>().Single(); | |
key = new string(originalKey); | |
Assert.NotSame(originalKey, key); | |
} |
Fixes: dotnet#10029 I could reproduce a memory leak by doing: App.Current.OpenWindow(new Window { Page = new ContentPage() }); I found that `App.Current._requestedWindows` just held onto every `Window` object forever. Note that we *can't* use `ConditionalWeakTable` here: https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.conditionalweaktable-2 After initially trying this, I added a test showing not to use it. The problem is if the `string` is GC'd the `Window` will be lost. We can use `Dictionary<string, WeakReference<Window>>` instead. The only other change I made was to use `Guid.ToString("n")` as it removes `{`, `}`, and `-` characters from the string. After these changes, I still found `Window` objects hanging around that appeared to be held onto by many other objects. Then I reviewed: void IWindow.Destroying() { SendWindowDisppearing(); Destroying?.Invoke(this, EventArgs.Empty); OnDestroying(); Application?.RemoveWindow(this); // This wasn't here! Handler?.DisconnectHandler(); } It appears that upon `Window`'s closing, we didn't have any code that "disconnected" the MAUI handler. After this change, I could see `Window` objects properly go away. I added a unit test as well.
ef2579a
to
2e7c3ff
Compare
/backport to net7.0 |
Started backporting to net7.0: https://github.com/dotnet/maui/actions/runs/4315741361 |
@PureWeen backporting to net7.0 failed, the patch most likely resulted in conflicts: $ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: [controls] fix memory leak in `Window`
Using index info to reconstruct a base tree...
M src/Controls/src/Core/HandlerImpl/Application/Application.Impl.cs
M src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs
M src/Controls/tests/Core.UnitTests/WindowsTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/Controls/tests/Core.UnitTests/WindowsTests.cs
CONFLICT (content): Merge conflict in src/Controls/tests/Core.UnitTests/WindowsTests.cs
Auto-merging src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs
Auto-merging src/Controls/src/Core/HandlerImpl/Application/Application.Impl.cs
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0001 [controls] fix memory leak in `Window`
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128 Please backport manually! |
@PureWeen an error occurred while backporting to net7.0, please check the run log for details! Error: git am failed, most likely due to a merge conflict. |
Fixes: #10029 I could reproduce a memory leak by doing: App.Current.OpenWindow(new Window { Page = new ContentPage() }); I found that `App.Current._requestedWindows` just held onto every `Window` object forever. Note that we *can't* use `ConditionalWeakTable` here: https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.conditionalweaktable-2 After initially trying this, I added a test showing not to use it. The problem is if the `string` is GC'd the `Window` will be lost. We can use `Dictionary<string, WeakReference<Window>>` instead. The only other change I made was to use `Guid.ToString("n")` as it removes `{`, `}`, and `-` characters from the string. After these changes, I still found `Window` objects hanging around that appeared to be held onto by many other objects. Then I reviewed: ```csharp void IWindow.Destroying() { SendWindowDisppearing(); Destroying?.Invoke(this, EventArgs.Empty); OnDestroying(); Application?.RemoveWindow(this); // This wasn't here! Handler?.DisconnectHandler(); } ``` It appears that upon `Window`'s closing, we didn't have any code that "disconnected" the MAUI handler. After this change, I could see `Window` objects properly go away. I added a unit test as well. # Conflicts: # src/Controls/tests/Core.UnitTests/WindowsTests.cs
Fixes: #10029 I could reproduce a memory leak by doing: App.Current.OpenWindow(new Window { Page = new ContentPage() }); I found that `App.Current._requestedWindows` just held onto every `Window` object forever. Note that we *can't* use `ConditionalWeakTable` here: https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.conditionalweaktable-2 After initially trying this, I added a test showing not to use it. The problem is if the `string` is GC'd the `Window` will be lost. We can use `Dictionary<string, WeakReference<Window>>` instead. The only other change I made was to use `Guid.ToString("n")` as it removes `{`, `}`, and `-` characters from the string. After these changes, I still found `Window` objects hanging around that appeared to be held onto by many other objects. Then I reviewed: ```csharp void IWindow.Destroying() { SendWindowDisppearing(); Destroying?.Invoke(this, EventArgs.Empty); OnDestroying(); Application?.RemoveWindow(this); // This wasn't here! Handler?.DisconnectHandler(); } ``` It appears that upon `Window`'s closing, we didn't have any code that "disconnected" the MAUI handler. After this change, I could see `Window` objects properly go away. I added a unit test as well. # Conflicts: # src/Controls/tests/Core.UnitTests/WindowsTests.cs
Fixes: #10029 I could reproduce a memory leak by doing: App.Current.OpenWindow(new Window { Page = new ContentPage() }); I found that `App.Current._requestedWindows` just held onto every `Window` object forever. Note that we *can't* use `ConditionalWeakTable` here: https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.conditionalweaktable-2 After initially trying this, I added a test showing not to use it. The problem is if the `string` is GC'd the `Window` will be lost. We can use `Dictionary<string, WeakReference<Window>>` instead. The only other change I made was to use `Guid.ToString("n")` as it removes `{`, `}`, and `-` characters from the string. After these changes, I still found `Window` objects hanging around that appeared to be held onto by many other objects. Then I reviewed: ```csharp void IWindow.Destroying() { SendWindowDisppearing(); Destroying?.Invoke(this, EventArgs.Empty); OnDestroying(); Application?.RemoveWindow(this); // This wasn't here! Handler?.DisconnectHandler(); } ``` It appears that upon `Window`'s closing, we didn't have any code that "disconnected" the MAUI handler. After this change, I could see `Window` objects properly go away. I added a unit test as well. # Conflicts: # src/Controls/tests/Core.UnitTests/WindowsTests.cs
Fixes: #10029 I could reproduce a memory leak by doing: App.Current.OpenWindow(new Window { Page = new ContentPage() }); I found that `App.Current._requestedWindows` just held onto every `Window` object forever. Note that we *can't* use `ConditionalWeakTable` here: https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.conditionalweaktable-2 After initially trying this, I added a test showing not to use it. The problem is if the `string` is GC'd the `Window` will be lost. We can use `Dictionary<string, WeakReference<Window>>` instead. The only other change I made was to use `Guid.ToString("n")` as it removes `{`, `}`, and `-` characters from the string. After these changes, I still found `Window` objects hanging around that appeared to be held onto by many other objects. Then I reviewed: ```csharp void IWindow.Destroying() { SendWindowDisppearing(); Destroying?.Invoke(this, EventArgs.Empty); OnDestroying(); Application?.RemoveWindow(this); // This wasn't here! Handler?.DisconnectHandler(); } ``` It appears that upon `Window`'s closing, we didn't have any code that "disconnected" the MAUI handler. After this change, I could see `Window` objects properly go away. I added a unit test as well. # Conflicts: # src/Controls/tests/Core.UnitTests/WindowsTests.cs Co-authored-by: Shane Neuville <shneuvil@microsoft.com>
Fixes #10029
I could reproduce a memory leak by doing:
I found that
App.Current._requestedWindows
just held onto everyWindow
object forever.Note that we can't use
ConditionalWeakTable
here:https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.conditionalweaktable-2
After initially trying this, I added a test showing not to use it. The problem is if the
string
is GC'd theWindow
will be lost.We can use
Dictionary<string, WeakReference<Window>>
instead.The only other change I made was to use
Guid.ToString("n")
as it removes{
,}
, and-
characters from the string.After these changes, I still found
Window
objects hanging around that appeared to be held onto by many other objects.Then I reviewed:
It appears that upon
Window
's closing, we didn't have any code that "disconnected" the MAUI handler. After this change, I could seeWindow
objects properly go away.I added a unit test as well.