Skip to content

Commit

Permalink
[controls] fix memory leak in Window
Browse files Browse the repository at this point in the history
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.

This is a perfect example of where using `ConditionalWeakTable` solves
the issue:

https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.conditionalweaktable-2

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.
  • Loading branch information
jonathanpeppers committed Mar 1, 2023
1 parent 4487158 commit ef2579a
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Devices;
Expand All @@ -12,7 +13,7 @@ public partial class Application : IApplication
const string MauiWindowIdKey = "__MAUI_WINDOW_ID__";

readonly List<Window> _windows = new();
readonly Dictionary<string, Window> _requestedWindows = new();
readonly ConditionalWeakTable<string, Window> _requestedWindows = new();
ILogger<Application>? _logger;

ILogger<Application>? Logger =>
Expand All @@ -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))
{
window = w;
_requestedWindows.Remove(requestedWindowId);
}
}

// create a new one if there is no pending windows
Expand Down Expand Up @@ -86,9 +90,8 @@ internal void RemoveWindow(Window window)

public virtual void OpenWindow(Window window)
{
var id = Guid.NewGuid().ToString();

_requestedWindows[id] = window;
var id = Guid.NewGuid().ToString("n");
_requestedWindows.Add(id, window);

var state = new PersistedState
{
Expand Down
1 change: 1 addition & 0 deletions src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ void IWindow.Destroying()
OnDestroying();

Application?.RemoveWindow(this);
Handler?.DisconnectHandler();
}

void IWindow.Resumed()
Expand Down
25 changes: 24 additions & 1 deletion src/Controls/tests/Core.UnitTests/WindowsTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Maui.Graphics;
Expand Down Expand Up @@ -621,5 +622,27 @@ public void MinDimensionsArePassedToCoreCorrectly(double inW, double inH, double
Assert.Equal(outW, coreWindow.MinimumWidth);
Assert.Equal(outH, coreWindow.MinimumHeight);
}

[Fact]
public async Task WindowDoesNotLeak()
{
var application = new Application();
WeakReference reference;

// Scope for window
{
var window = new Window { Page = new ContentPage() };
reference = new WeakReference(window);
application.OpenWindow(window);
((IWindow)window).Destroying();
}

// GC
await Task.Yield();
GC.Collect();
GC.WaitForPendingFinalizers();

Assert.False(reference.IsAlive, "Window should not be alive!");
}
}
}

0 comments on commit ef2579a

Please sign in to comment.