Skip to content

Memory leak in GLWpfControl (when creating multiple windows with GL WPF Controls) #126

@4nonym0us

Description

@4nonym0us

I've been besting GLWpfControl in a modal WPF Windows, which are opened/closed by user on demand. I've noticed that despite DxGlContext implementing IDisposable, it's never actually called and leads to a memory leak, which becomes more severe if GLWpfControl has short lifespan (i.e. when displayed in a modal dialog/popup).

Steps to reproduce

Modifying the Example project from the repo to instantiate, open and close multiple windows (that contain GLWpfControl) in a loop leads to permanent growth of unmanaged memory on heap, which is never reclaimed/freed.

App.xaml (remove StartupUri)

<Application
    x:Class="Example.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources />
</Application>

App.xaml.cs (change ShutdownMode, create a few windows, force GC)

using System;
using System.Threading.Tasks;
using System.Windows;

namespace Example {
    /// <summary>
    ///     Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application {
        protected override async void OnStartup(StartupEventArgs e) {
            base.OnStartup(e);

            ShutdownMode = ShutdownMode.OnExplicitShutdown;

            Test();

            while (true) {
                await Task.Delay(1000);

                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
        }

        private static void Test() {
            for (var i = 0; i < 10; i++) {
                var window = new MainWindow();
                window.Show();
                window.Close();
            }
        }
    }
}

Following screenshot displays that once the memory is allocated on Window creation, it's never released.
image

Issues

From what It looks like, there are multiple issues:

  1. Having RegisterToEventsDirectly set to True in GLWpfControl leads to the invocation of EventManager.RegisterClassHandler(..) in GLWpfControl.Start(GLWpfControlSettings settings), which creates a strong reference between EventHandler and the control, which make GC keep such instance of GLWpfControl on heap forever even if the control is effectively dead.
  2. DxGlContext is never actually disposed by a developer, but GC won't properly handle it's disposal either because DxGlContext is missing a finalizer.
  3. Resources associated with DxDeviceHandle/DxContextHandle/GlDeviceHandle aren't handled in Dispose() method, which means that even if finalizer was present, resources associated with these handles wouldn't be released anyways and all of these resource seem to be control-specific and not shared (like GraphicsContext/_sharedContextResources).
  4. Current implementation of DxGlContext.Dispose() makes an attempt to dispose resources located in _sharedContextResources, but hence DxGlContext is never properly disposed and finalizer would be required to release resources in this case, the finalizer wouldn't have access to resources in _sharedContextResources because it would likely use non-UI thread, which would result in System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'.

Discussion

Proper solution of these issues requires implementing IDisposable pattern correctly and disposing used resources.
I'm not aware of all use cases of GLWpfControl and not sure what would be the best approach for this component, but in the worst case scenario, GLWpfControl should have at least a proper finalizer.
I'm open to submit a PR with a fix, but I'd like to know what's the vision of maintainers first (whether to just add finalizer or revise the component to properly utilize IDisposable? How to handle RegisterToEventsDirectly?).

Following screenshot displays the outcome of adding a finalizer to the GLWpfControl and setting RegisterToEventsDirectly: False, which allows GC to take care of the leaked resources.
image
Source code is available here, you can run Example project to reproduce the test results

Metadata

Metadata

Assignees

No one assigned

    Labels

    4.0bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions