[BUG] glfwDestroyWindow EXCEPTION_ACCESS_VIOLATION

Started by jakethesnake, August 18, 2019, 18:52:09

Previous topic - Next topic

jakethesnake

Hello again!

I'm having a strange problem with my distributed app on a certain setup.

I can't reproduce it and have only had one report of the issue.

It's a hard JVM crash, and I'm no expert at reading these logs, but from what I gather glfwDestroyWindow is causing it.

I've read all of the threads regarding such issues http://forum.lwjgl.org/index.php?topic=5566.0 and multi-threading seems to be a viable culprit and I'm not ruling that out, it just seems unlikely.

The app initializes and renders normally. It is only when the user exits that the app crashes, every time.

Attached is the error log + LWJGL output. I don't know if they are of any help.
The app is here: https://songsofsyx.itch.io/songs-of-syx

I'm not calling glfwDestroyWindow twice, I know that.
I'm 99% certain I'm not doing opengl calls from any other than the main thread. 


public static abstract class GlJob{
		protected abstract void doJob();

		public final void perform() {
			if (Thread.currentThread() == glThread && !swapping)
				doJob();
			else{
				glJob = this;
				while(glJob != null) //the glThread busy polls this variable and nulls it when complete.
					sleep(1);
			}
		}
		
	}



void dis() {

		glfwSetErrorCallback(null).free();
		if (renderer != null)
			renderer.dis();
		GlHelper.checkErrors();
		gl.dispose();
		glfwDestroyWindow(window);
		glfwTerminate();


		Printer.ln(GraphicContext.class + " was sucessfully destroyed");
	}


jakethesnake

I did some experiments after looking at the "getting started example". I added glfwSetErrorCallback(null).free(); in my disposal method and I got a hard JVM crash on this call. Removing all callback.close() calls prior to this (keyboard, mouse) fixed that crash. I don't know if this is related.

I'd really appreciate some help with this, even if it's just thoughts. I'm clueless on where to look. I've tried running all logic in the same thread and it makes no difference to me. OpenAl couldn't possibly mess things up? That's supposed to be thread safe from the docs.

I'll try packaging the get started guide and have the bug-reporter run that, see if it could be a lwjgl problem.

spasi

The code glfwSetErrorCallback(null).free() is shortcut for:

GLFWErrorCallback previousErrorCallback = glfwSetErrorCallback(null);
previousErrorCallback.free();


If no error callback was set, you'd get an NPE here, not a crash. If you're getting a crash, it likely means you have already freed the error callback.

The glfwDestroyWindow crash also sounds like a double-free bug. But there's no way to tell without source access and decompilation can only get you so far (also time-consuming and painful). The best approach is to prepare an MCVE for us to have a look at. You'll probably figure out the problem before you post it though.

Btw, when debugging GLFW/OpenGL applications, it's always a good idea to use LWJGLX/debug. It will report any obvious errors in your code.

jakethesnake

It would be difficult to make a minimized example since I can't recreate the original bug as well.

I tried running the java agent and it didn't print anything for me except for all the available extensions. Instead it created JVM crash upon exiting the app!

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  org.lwjgl.system.dyncall.DynCallback.ndcbGetUserData(J)J+0
j  org.lwjgl.system.dyncall.DynCallback.dcbGetUserData(J)J+12
j  org.lwjgl.system.Callback.free(J)V+1
j  org.lwjgl.system.Callback.free()V+4
j  org.lwjglx.debug.glfw.GLFW.glfwTerminate()V+42
j  org.lwjglx.debug.$Proxy$35.glfwTerminate26()V+10
j  snake2d.GraphicContext.dis()V+45
j  snake2d.CORE.dispose()V+98
j  snake2d.CORE.start(Lsnake2d/CORE_STATE$Constructor;)V+574
j  launcher.Launcher.start()Z+36
j  main.Syx.main([Ljava/lang/String;)V+86
v  ~StubRoutines::call_stub


Something fishy is going on with the callbacks, mock my words!

After init I've got the following callbacks setup in this order:

GLFWErrorCallback.createPrint(System.err).set();

glCallback = GlDebugger.setupDebugMessageCallback();

callbackKey = new GLFWKeyCallback() {

			@Override
			public void invoke(long window, int key, int scancode, int action, int mods) {
				if (key >= 0 && key < keymap.length && keymap[key] != null){
					if (key == KEYCODE.PRINT_SCREEN.CODE){
						if (action == 1)
							w.takeScreenShot();
						return;
					}
					keys[keysI] = key;
					keys[keysI+1] = action;
					keysI += 2;
				}
				
			}
		};
		
		GLFW.glfwSetKeyCallback(w.getWindow(), callbackKey);
		
		charCallback = new GLFWCharCallback() {
			@Override
			public void invoke(long window, int codepoint) {
				
				chars[charsI] = (char) codepoint;
				charsI ++;
				
			}
		};
		
		glfwSetCharCallback(w.getWindow(), charCallback);

callbackMouse = new GLFWMouseButtonCallback() {
			@Override
			public void invoke(long window, int button, int action, int mods) {
				
				if (button > 2){
					return;
				}
				if (MButt.ALL.get(button).isDown = action == GLFW.GLFW_PRESS){
					
					long nanoOld = MButt.ALL.get(button).nanoNow;
					MButt.ALL.get(button).nanoNow = Input.nanoNow;
					
					if (Input.nanoNow - nanoOld < 250000000 && clickCurrent < clickMax){
						clicks[clickCurrent++] = MButt.ALL.get(button + 3);
					}
					if (clickCurrent < clickMax)
						clicks[clickCurrent++] = MButt.ALL.get(button);
				}
				
			}
		};
		glfwSetMouseButtonCallback(window, callbackMouse);
		
		sCallback = new GLFWScrollCallback() {
			
			@Override
			public void invoke(long window, double xoffset, double yoffset) {
				MButt.wheelDy = (float) yoffset;
				if (clickCurrent < clickMax)
					clicks[clickCurrent] = MButt.WHEEL_SPIN;
				
			}
		};
		glfwSetScrollCallback(window, sCallback);


I then dispose the context in this order:

glfwSetErrorCallback(null).free();

		glCallback.free();
		GL.setCapabilities(null);
		Callbacks.glfwFreeCallbacks(window);
		glfwDestroyWindow(window);
		glfwTerminate();


if I remove Callbacks.glfwFreeCallbacks(window) and dispose the keyboard/mouse callbacks manually by: callback.close() the crash vanishes.

Any ideas?

spasi

Quote from: jakethesnake on August 20, 2019, 14:00:00I tried running the java agent and it didn't print anything for me except for all the available extensions. Instead it created JVM crash upon exiting the app!

I think the problem is that you call glfwSetErrorCallback(null).free() before glfwTerminate(), which LWJGLX/debug does not expect (it's usually done in the opposite order). I have reported this and the crash should be fixed soon.

Quote from: jakethesnake on August 20, 2019, 14:00:00if I remove Callbacks.glfwFreeCallbacks(window) and dispose the keyboard/mouse callbacks manually by: callback.close() the crash vanishes.

Again, it looks like a double-free. A callback has been freed while it's still registered to the GLFW window. glfwFreeCallbacks is very simple, if a Set<Type>Callback function returns non-NULL, it will try to free it. If the free call crashes, it means the callback is already freed. Try running your application in a debugger to figure out exactly which callback is causing the trouble.

jakethesnake

Hmm, forgive my ignorance, but I don't understand how they can be double-freed. Wouldn't invokePPP(window, NULL, callback) return null if it has been freed?

Anyway. I'm able to reproduce this phenomenon in the "get started example", simply by adding the same callbacks that I'm using. This causes a JVM crash for me. Maybe I'm missing something?

import org.lwjgl.*;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;

import java.nio.*;

import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryStack.*;
import static org.lwjgl.system.MemoryUtil.*;

public class Test {

	// The window handle
	private long window;
	private GLFWKeyCallback keyCallback;
	private GLFWMouseButtonCallback mouseCallback;
	private GLFWScrollCallback scrollCallback;

	public void run() {
		System.out.println("Hello LWJGL " + Version.getVersion() + "!");

		init();
		loop();

		// Free the window callbacks and destroy the window
		keyCallback.free();
		mouseCallback.free();
		scrollCallback.free();
		glfwFreeCallbacks(window);
		glfwDestroyWindow(window);

		// Terminate GLFW and free the error callback
		glfwTerminate();
		glfwSetErrorCallback(null).free();
	}
	
	private void setupCallbacks(long window) {
		keyCallback = new GLFWKeyCallback() {
			@Override
			public void invoke(long window, int key, int arg2, int action, int arg4) {
				if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
					glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
			}
		}; glfwSetKeyCallback(window, keyCallback);

		mouseCallback = new GLFWMouseButtonCallback() {
			@Override
			public void invoke(long window, int button, int action, int mods) {
				System.out.println("click!");
			}
		};
		glfwSetMouseButtonCallback(window, mouseCallback);
		
		scrollCallback = new GLFWScrollCallback() {
			
			@Override
			public void invoke(long window, double xoffset, double yoffset) {
				System.out.println("scroll!");
			}
		};
		glfwSetScrollCallback(window, scrollCallback);
	}

	private void init() {
		// Setup an error callback. The default implementation
		// will print the error message in System.err.
		GLFWErrorCallback.createPrint(System.err).set();

		// Initialize GLFW. Most GLFW functions will not work before doing this.
		if ( !glfwInit() )
			throw new IllegalStateException("Unable to initialize GLFW");

		// Configure GLFW
		glfwDefaultWindowHints(); // optional, the current window hints are already the default
		glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation
		glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // the window will be resizable

		// Create the window
		window = glfwCreateWindow(300, 300, "Hello World!", NULL, NULL);
		if ( window == NULL )
			throw new RuntimeException("Failed to create the GLFW window");

		setupCallbacks(window);

		
		// Setup a key callback. It will be called every time a key is pressed, repeated or released.
//		glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
//			if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
//				glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
//		});

		// Get the thread stack and push a new frame
		try ( MemoryStack stack = stackPush() ) {
			IntBuffer pWidth = stack.mallocInt(1); // int*
			IntBuffer pHeight = stack.mallocInt(1); // int*

			// Get the window size passed to glfwCreateWindow
			glfwGetWindowSize(window, pWidth, pHeight);

			// Get the resolution of the primary monitor
			GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());

			// Center the window
			glfwSetWindowPos(
				window,
				(vidmode.width() - pWidth.get(0)) / 2,
				(vidmode.height() - pHeight.get(0)) / 2
			);
		} // the stack frame is popped automatically

		// Make the OpenGL context current
		glfwMakeContextCurrent(window);
		// Enable v-sync
		glfwSwapInterval(1);

		// Make the window visible
		glfwShowWindow(window);
	}

	private void loop() {
		// This line is critical for LWJGL's interoperation with GLFW's
		// OpenGL context, or any context that is managed externally.
		// LWJGL detects the context that is current in the current thread,
		// creates the GLCapabilities instance and makes the OpenGL
		// bindings available for use.
		GL.createCapabilities();

		// Set the clear color
		glClearColor(1.0f, 0.0f, 0.0f, 0.0f);

		// Run the rendering loop until the user has attempted to close
		// the window or has pressed the ESCAPE key.
		while ( !glfwWindowShouldClose(window) ) {
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer

			glfwSwapBuffers(window); // swap the color buffers

			// Poll for window events. The key callback above will only be
			// invoked during this call.
			glfwPollEvents();
		}
	}

	public static void main(String[] args) {
		new Test().run();
	}

}

spasi

The glfwFreeCallbacks method is an LWJGL utility meant to simplify GLFW callback cleanup. It finds callbacks still registered with a GLFW window, removes them from the GLFW window and frees them. If you do manual cleanup, you don't have to (and shouldn't) use it. This code:

/ Free the window callbacks and destroy the window
keyCallback.free();
mouseCallback.free();
scrollCallback.free();


frees the callback resources (which is an LWJGL thing, because you can't pass Java methods as callbacks to native code), but never clears the callbacks from the GLFW window (e.g. you never call glfwSetKeyCallback(window, null)). You then use glfwFreeCallbacks, which calls glfwSetKeyCallback(window, null) internally and that method returns the still registered callback. LWJGL doesn't know that you have already freed the callback and tries to free it again, leading to a crash.

spasi

Hey, just letting you know that the issue in LWJGLX/debug has been fixed, you can update it and try again. There has also been an improvement in LWJGL; when the debug allocator is enabled, it will now throw an exception instead of crashing the JVM when you try to double-free a callback. This will be available in the next snapshot (3.2.3 build 10).

jakethesnake

Quote from: spasi on August 22, 2019, 10:21:40
Hey, just letting you know that the issue in LWJGLX/debug has been fixed, you can update it and try again. There has also been an improvement in LWJGL; when the debug allocator is enabled, it will now throw an exception instead of crashing the JVM when you try to double-free a callback. This will be available in the next snapshot (3.2.3 build 10).

Excellent!

jakethesnake

Quote from: spasi on August 22, 2019, 10:21:40
Hey, just letting you know that the issue in LWJGLX/debug has been fixed, you can update it and try again. There has also been an improvement in LWJGL; when the debug allocator is enabled, it will now throw an exception instead of crashing the JVM when you try to double-free a callback. This will be available in the next snapshot (3.2.3 build 10).

BTW. Will a double-free on the window also throw an exception?

Also, how can the window be double freed besides calling glfwDestroyWindow twice? Could it be done is some error handler somewhere?

spasi

Quote from: jakethesnake on August 22, 2019, 14:01:46BTW. Will a double-free on the window also throw an exception?

No. GLFW window handles are created internally by GLFW, LWJGL does not track them.

Quote from: jakethesnake on August 22, 2019, 14:01:46Also, how can the window be double freed besides calling glfwDestroyWindow twice? Could it be done is some error handler somewhere?

glfwTerminate also destroys all windows that have not been explicitly destroyed at that point.

jakethesnake

Ok, so after trying things back and forth, I removed the glfwDestroyWindow in the dispose routine, and it now works for the bug-reporter.

                Callbacks.glfwFreeCallbacks(window);
		//glfwDestroyWindow(window);
		glfwTerminate();
		glfwSetErrorCallback(null).free();



I have no idea why this is. This was a bug singly reported among 1600 users. What struck me was that he was on windows 7 with integrated intel graphics, 64 bits.

So, in retrospect, if you don't create windows and callbacks and dispose them during runtime, maybe it's best to just do a glfwTerminate when you're about to exit? Is there anything I don't know about that might cause problems doing it this way?

spasi

It shouldn't make a difference. The first thing glfwTerminate does is clear the monitor & joystick callbacks, then:

while (_glfw.windowListHead)
    glfwDestroyWindow((GLFWwindow*) _glfw.windowListHead);