Bytes Leaked msg when running via JUnit

Started by louhy, May 02, 2020, 21:21:51

Previous topic - Next topic

louhy

When running a trivial JUnit 5 test (using -javaagent:lib/lwjglx-debug-1.0.0.jar VM option), I get the log below.
This: [LWJGL] 16 bytes leaked, thread 1 (main) I interpret as a callback not being de-registered.
As you can see in the test (2nd block below), I did call glfwFreeCallbacks(window) via demo.exit() (third block below).
Log remains the same if I move the init() and exit() calls into createWindow() within the test.

But when I run this "Demo" app normally without JUnit, I don't get this output. (I didn't forget to add the lwjglx-debug VM option.)

The only reason I can think of is that this has something to do with additional multi-threading when running JUnit - but can anyone shed some light into what's going on here and/or suggest a solution?

Quote[LWJGL] Version: 3.2.3 build 13
[LWJGL]     OS: Linux v5.0.0-32-generic
[LWJGL]    JRE: 11.0.6 amd64
[LWJGL]    JVM: OpenJDK 64-Bit Server VM v11.0.6+8-b765.40 by JetBrains s.r.o
[LWJGL] Loading JNI library: lwjgl
[LWJGL]    Module: org.lwjgl
[LWJGL]    Using SharedLibraryLoader...
[LWJGL]    Found at: /tmp/lwjgllouhy/3.2.3-build-13/liblwjgl.so
[LWJGL]    Loaded from org.lwjgl.librarypath: /tmp/lwjgllouhy/3.2.3-build-13/liblwjgl.so
[LWJGL] Java 9 stack walker enabled
[LWJGL] Warning: Failed to instantiate memory allocator: org.lwjgl.system.jemalloc.JEmallocAllocator. Using the system default.
[LWJGL] MemoryUtil allocator: DebugAllocator
[LWJGL] Loading library: glfw
[LWJGL]    Module: org.lwjgl.glfw
[LWJGL]    Using SharedLibraryLoader...
[LWJGL]    Found at: /tmp/lwjgllouhy/3.2.3-build-13/libglfw.so
[LWJGL]    Loaded from org.lwjgl.librarypath: /tmp/lwjgllouhy/3.2.3-build-13/libglfw.so
createWindow()
[info ][1] Destroying OpenGL context for window[2]
[LWJGL] Loading JNI library: lwjgl_opengl
[LWJGL]    Module: org.lwjgl.opengl
[LWJGL]    Using SharedLibraryLoader...
[LWJGL]    Found at: /tmp/lwjgllouhy/3.2.3-build-13/liblwjgl_opengl.so
[LWJGL]    Loaded from org.lwjgl.librarypath: /tmp/lwjgllouhy/3.2.3-build-13/liblwjgl_opengl.so
[LWJGL] Loading library: libGL.so.1
[LWJGL]    Module: org.lwjgl.opengl
[LWJGL]    libGL.so.1 not found in org.lwjgl.librarypath=/tmp/lwjgllouhy/3.2.3-build-13
[LWJGL]    Loaded from system paths: /usr/lib/x86_64-linux-gnu/libGL.so.1

[LWJGL] 16 bytes leaked, thread 1 (main), address: 0x7F376374A000
   at org.lwjgl.system.Callback.create(Callback.java:136)
   at org.lwjgl.system.CallbackI.address(CallbackI.java:34)
   at org.lwjgl.system.MemoryUtil.memAddressSafe(MemoryUtil.java:853)
   at org.lwjgl.glfw.GLFW.glfwSetKeyCallback(GLFW.java:3646)
   at org.lwjglx.debug.$Proxy$242.glfwSetKeyCallback21(Unknown Source)
   at org.demo.Demo.init(Demo.java:73)
   at org.demo.DemoTest.setUp(DemoTest.java:14)
   at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:515)
   at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
   at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.invokeMethodInExtensionContext(ClassTestDescriptor.java:436)
   at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.lambda$synthesizeBeforeEachMethodAdapter$14(ClassTestDescriptor.java:424)
   at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeEachMethods$2(TestMethodTestDescriptor.java:136)
   at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
   at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(TestMethodTestDescriptor.java:156)
   at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeEachMethods(TestMethodTestDescriptor.java:135)
   at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:110)
   at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
   at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:105)
   at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
   at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:95)
   at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:71)
   at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
   at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
   at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:110)
   at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
   at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:95)
   at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:71)
   at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
   at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
   at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:110)
   at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
   at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:95)
   at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:71)
   at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
   at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
   at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
   at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
   at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
   at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
   at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
   at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
   at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
   at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
   at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
   at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Disconnected from the target VM, address: '127.0.0.1:48681', transport: 'socket'

Process finished with exit code 0

louhy

Test:
package org.demo;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class DemoTest {

    private Demo demo;

    @BeforeEach
    void setUp() {
        demo = new Demo();
        demo.init();
    }

    @AfterEach
    void exit() {
        demo.exit();
    }

    @Test
    void createWindow() throws Exception {
        long winHandle = demo.createWindow();
        System.out.println("createWindow()");
    }
}


App:
package org.demo;

import org.lwjgl.Version;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;
import org.lwjgl.system.MemoryStack;

import java.nio.IntBuffer;

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

public class Demo {

    private long window;

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

    public void run() {
        System.out.println("LWJGL " + Version.getVersion());
        init();
        loop();
        exit();
    }

    public void exit() {
        glfwFreeCallbacks(window);
        glfwDestroyWindow(window);

        glfwTerminate();
        glfwSetErrorCallback(null).free();
        GL.setCapabilities(null);
    }

    private void loop() {
        GL.createCapabilities();

        glClearColor(0.0f, 0.0f, 0.5f, 0.0f);

        while (!glfwWindowShouldClose(window)) {
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            render();
            glfwSwapBuffers(window);
            glfwPollEvents();
        }
    }

    private void render() {
        glColor3f(1.0f, 1.0f, 1.0f);
        glBegin(GL_LINES);
        glVertex2f(-1f, -1f);
        glVertex2f(1, 1);
        glEnd();
    }

    protected void init() {
        GLFWErrorCallback.createPrint(System.err).set();

        if (!glfwInit())
            throw new IllegalStateException("Unable to initialize GLFW");

        glfwDefaultWindowHints();
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);

        createWindow();

        glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
            if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE)
                glfwSetWindowShouldClose(window, true);
        });

        try (MemoryStack stack = stackPush()) {
            IntBuffer pWidth = stack.mallocInt(1);
            IntBuffer pHeight = stack.mallocInt(1);

            glfwGetWindowSize(window, pWidth, pHeight);

            GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());

            glfwSetWindowPos(
                    window,
                    (vidmode.width() - pWidth.get(0)) / 2,
                    (vidmode.height() - pHeight.get(0)) / 2
            );
        }

        glfwMakeContextCurrent(window);
        glfwSwapInterval(1);
        glfwShowWindow(window);
    }

    protected long createWindow() {
        window = glfwCreateWindow(500, 500, "Test Window", NULL, NULL);
        if (window == NULL)
            throw new RuntimeException("Failed to create the GLFW window");
        return window;
    }
}


spasi

The test creates two GLFW windows. Once in the init() method and once in the createWindow() test method. The callback is registered in init(), on the first window. When exit() is called, window has already been overridden and glfwFreeCallbacks() doesn't find anything to free.

louhy

Right... guess I have to rethink how I test concisely, thanks.

louhy

Okay, part 2. I tried running the demo here with the same debugger tool:
https://github.com/LWJGL/lwjgl3-demos/blob/master/src/org/lwjgl/demo/opengl/glfw/Multithreaded.java

This is the end of the log output:

[info ] Destroying OpenGL context for window[1]
[LWJGL] 16 bytes leaked, thread 1 (main), address: 0x7F43FE105000
	at org.lwjgl.system.Callback.create(Callback.java:136)
	at org.lwjgl.system.Callback.<init>(Callback.java:86)
	at org.lwjgl.glfw.GLFWErrorCallback.<init>(GLFWErrorCallback.java:60)
	at org.lwjglx.debug.glfw.GLFW$1.<init>(GLFW.java:50)
	at org.lwjglx.debug.glfw.GLFW.ensureErrorCallback(GLFW.java:50)
	at org.lwjglx.debug.glfw.GLFW.glfwSetErrorCallback(GLFW.java:65)
	at org.lwjglx.debug.$Proxy$4.glfwSetErrorCallback4(Unknown Source)
	at org.demo.Multithreaded.init(Multithreaded.java:62)
	at org.demo.Multithreaded.run(Multithreaded.java:44)
	at org.demo.Multithreaded.main(Multithreaded.java:154)
[LWJGL] 1872 bytes leaked, thread 14 (Thread-1), address: 0x7F435C1F0FB0
	at org.lwjgl.system.ThreadLocalUtil.setEnv(ThreadLocalUtil.java:164)
	at org.lwjgl.opengl.GL.setCapabilities(GL.java:243)
	at org.lwjgl.opengl.GL.createCapabilities(GL.java:468)
	at org.lwjgl.opengl.GL.createCapabilities(GL.java:322)
	at org.lwjglx.debug.opengl.GL.createCapabilities(GL.java:33)
	at org.lwjglx.debug.$Proxy$4.createCapabilities18(Unknown Source)
	at org.demo.Multithreaded.renderLoop(Multithreaded.java:102)
	at org.demo.Multithreaded$3.run(Multithreaded.java:144)
	at java.base/java.lang.Thread.run(Thread.java:834)

Process finished with exit code 0

KaiHH

The second error with GL.createCapabilities() is a legitimate error in all demos not calling GL.setCapabilities(null). I'll fix that. Thanks!
The first error with glfwSetErrorCallback() is due to LWJGLX/debug _always_ pre-registering a GLFW error callback/handler to syserr all errors emitted by GLFW, in the case that the user will not do this. And if the user did do this, then it will simply be delegated to the user callback. Since the GLFW error callback is a global callback which should be registered as the very first GLFW call (even outside of glfwInit()/glfwTerminate()), this callback will always remain in the system. There is currently no LWJGLX/debug API to explicitly free such callbacks registered by LWJGLX/debug itself.

louhy

Cool, so I was on the right track with the second error. I had already modified my own test to setCapabilities(null) to prevent that. Thanks for the feedback.

louhy

Actually, checking my own code again, I seem to be able to prevent the 16 byte leak (or at least the reporting of it) by adding the last free() call shown here.

    protected void exit() {
        glfwFreeCallbacks(windowHandle);
        glfwDestroyWindow(windowHandle);
        glfwTerminate();
        GL.setCapabilities(null);
        glfwSetErrorCallback(null).free();
    }