Skip to content

Illegal glfwDestroyWindow in window close handler #46

Description

@Radbuglet

On MacOS, the following code will cause a segfault when the user quits the application using command+Q:

using Glfw;

// ...

public static void Main()
{
	using var window = new NativeWindow(1920, 1080, "My App");
	while (!window.IsClosed)
	{
		Glfw.PollEvents();
	}
	Console.WriteLine("Goodbye!");
}

The access violation happens after the NativeWindow.Closed event is finished dispatching, where the window close request callback returns control back to GLFW. Looking into the implementation of the protected NativeWindow.OnClosing handler, this seems to happen because underlying window is destroyed in the middle of the callback (see this line, which is responsible for destroying the window handle). This is illegal, with the GLFW reference documentation for glfwDestroyWindow stating that:

Reentrancy
This function must not be called from a callback.

This is a logical error, not an error in C#'s FFI. Here is the equivalent C code which exhibits the same faulty behavior:

#include <stdio.h>
#include <GLFW/glfw3.h>

static void onClosing(GLFWwindow* window)
{
	printf("Destroying %p\n", window);
	// ...
	glfwDestroyWindow(window);
}

int main(void)
{
	// Setup GLFW
	if (!glfwInit())
		return -1;

	// Setup window
	GLFWwindow* window = glfwCreateWindow(1920, 1080, "My App", NULL, NULL);

	if (!window)
		goto stop;

	glfwSetWindowCloseCallback(window, onClosing);

	// Main loop
	while (!glfwWindowShouldClose(window))
	{
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	// Cleanup
	puts("Goodbye!");
stop:
	glfwTerminate();
	return 0;
}

There's no easy way to fix this without changing the API. A non-breaking (but potentially confusing) fix could be to invisibly handle window destruction at the end of the Glfw.PollEvents method call by destroying all windows that still have the shouldClose flag, but that breaks the user expectation that the Glfw class is just raw FFI. A better (but breaking) approach would be to introduce a dedicated wrapper around Glfw.PollEvents that "handles higher-level behavior for the GLFW-NET-specific wrapper objects" i.e. implements the behavior of the previous proposal but in a different method.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions