From c93a6235aa0da3a2324fdd3efffb6ea35e65b454 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 5 Feb 2017 15:52:18 +0300 Subject: [PATCH 1/2] Make sure that Renderer is disposed when TopLevel is closed --- src/Avalonia.Controls/TopLevel.cs | 4 ++- tests/Avalonia.LeakTests/ControlTests.cs | 28 ++++++++++++++++++ .../Avalonia.UnitTests.csproj | 1 + .../Avalonia.UnitTests/MockRendererFactory.cs | 29 +++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.UnitTests/MockRendererFactory.cs diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index f314629d02..1853d67019 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -184,7 +184,7 @@ namespace Avalonia.Controls /// /// Gets the renderer for the window. /// - public IRenderer Renderer { get; } + public IRenderer Renderer { get; private set; } /// /// Gets the access key handler for the window. @@ -381,6 +381,8 @@ namespace Avalonia.Controls private void HandleClosed() { Closed?.Invoke(this, EventArgs.Empty); + Renderer?.Dispose(); + Renderer = null; _applicationLifecycle.OnExit -= OnApplicationExiting; } diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 1e257a698c..23670257ff 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -11,6 +11,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Diagnostics; using Avalonia.Layout; +using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.UnitTests; @@ -301,6 +302,33 @@ namespace Avalonia.LeakTests } } + + [Fact] + public void RendererIsDisposed() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var renderer = new Mock(); + renderer.Setup(x => x.Dispose()); + var impl = new Mock(); + impl.SetupGet(x => x.Scaling).Returns(1); + impl.SetupProperty(x => x.Closed); + impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed()); + + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new MockWindowingPlatform(() => impl.Object)); + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new MockRendererFactory(renderer.Object)); + var window = new Window() + { + Content = new Button() + }; + window.Show(); + window.Close(); + renderer.Verify(r => r.Dispose()); + } + } + private static void PurgeMoqReferences() { // Moq holds onto references in its mock of IRenderer in case we want to check if a method has been called; diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index 7a84243953..f7878ab91a 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -55,6 +55,7 @@ + diff --git a/tests/Avalonia.UnitTests/MockRendererFactory.cs b/tests/Avalonia.UnitTests/MockRendererFactory.cs new file mode 100644 index 0000000000..e62012b13c --- /dev/null +++ b/tests/Avalonia.UnitTests/MockRendererFactory.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Rendering; + +namespace Avalonia.UnitTests +{ + public class MockRendererFactory : IRendererFactory + { + private readonly Func _cb; + + public MockRendererFactory(Func cb = null) + { + _cb = cb; + } + + public MockRendererFactory(IRenderer renderer) : this((_, __) => renderer) + { + + } + + public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) + { + return _cb?.Invoke(root, renderLoop); + } + } +} From f6ea72872afd3d17ca8cc4150f0b7687e389554c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 5 Feb 2017 15:57:21 +0300 Subject: [PATCH 2/2] Properly handle disposal for Win32 and GTK3 --- src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs | 6 +++-- src/Windows/Avalonia.Win32/WindowImpl.cs | 28 ++++++++++++++++-------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs index 10ff735476..aadbe16d1d 100644 --- a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -75,7 +75,7 @@ namespace Avalonia.Gtk3 private bool OnDestroy(IntPtr gtkwidget, IntPtr userdata) { - Closed?.Invoke(); + Dispose(); return false; } @@ -210,7 +210,9 @@ namespace Avalonia.Gtk3 public void Dispose() { - Closed?.Invoke(); + //We are calling it here, since signal handler will be detached + if (!GtkWidget.IsClosed) + Closed?.Invoke(); foreach(var d in Disposables.AsEnumerable().Reverse()) d.Dispose(); Disposables.Clear(); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5f59413ba2..d9b200169f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -182,9 +182,18 @@ namespace Avalonia.Win32 public void Dispose() { - s_instances.Remove(this); - _framebuffer.Dispose(); - UnmanagedMethods.DestroyWindow(_hwnd); + _framebuffer?.Dispose(); + _framebuffer = null; + if (_hwnd != IntPtr.Zero) + { + UnmanagedMethods.DestroyWindow(_hwnd); + _hwnd = IntPtr.Zero; + } + if (_className != null) + { + UnmanagedMethods.UnregisterClass(_className, UnmanagedMethods.GetModuleHandle(null)); + _className = null; + } } public void Hide() @@ -418,12 +427,13 @@ namespace Avalonia.Win32 return IntPtr.Zero; case UnmanagedMethods.WindowsMessage.WM_DESTROY: - if (Closed != null) - { - UnmanagedMethods.UnregisterClass(_className, UnmanagedMethods.GetModuleHandle(null)); - Closed(); - } - + //Window doesn't exist anymore + _hwnd = IntPtr.Zero; + //Remove root reference to this class, so unmanaged delegate can be collected + s_instances.Remove(this); + Closed?.Invoke(); + //Free other resources + Dispose(); return IntPtr.Zero; case UnmanagedMethods.WindowsMessage.WM_DPICHANGED: