-
Notifications
You must be signed in to change notification settings - Fork 58
Description
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.

Issues
From what It looks like, there are multiple issues:
- Having
RegisterToEventsDirectlyset toTrueinGLWpfControlleads to the invocation ofEventManager.RegisterClassHandler(..)inGLWpfControl.Start(GLWpfControlSettings settings), which creates a strong reference between EventHandler and the control, which make GC keep such instance ofGLWpfControlon heap forever even if the control is effectively dead. - DxGlContext is never actually disposed by a developer, but GC won't properly handle it's disposal either because DxGlContext is missing a finalizer.
- Resources associated with
DxDeviceHandle/DxContextHandle/GlDeviceHandlearen't handled inDispose()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 (likeGraphicsContext/_sharedContextResources). - Current implementation of
DxGlContext.Dispose()makes an attempt to dispose resources located in_sharedContextResources, but henceDxGlContextis never properly disposed and finalizer would be required to release resources in this case, the finalizer wouldn't have access to resources in_sharedContextResourcesbecause it would likely use non-UI thread, which would result inSystem.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.

Source code is available here, you can run Example project to reproduce the test results