Bug 109

Summary: Two animator threads on two Frames crash
Product: [JogAmp] Jogl Reporter: Sven Gothel <sgothel>
Component: coreAssignee: Sven Gothel <sgothel>
Status: VERIFIED FIXED    
Severity: normal    
Priority: P3    
Version: 1   
Hardware: All   
OS: macosx   
Type: DEFECT SCM Refs:
Workaround: ---

Description Sven Gothel 2010-03-24 07:46:55 CET


---- Reported by dreivier 2004-09-06 07:13:16 ----

The following example crashes under Mac OS X, although it shouldn't in my opinion. I can't test if it 
works with Windows/Linux... Output is:

Using ATI workaround of dispatching display() on event thread
2004-09-06 16:10:17.755 java[16864] *** _NSAutoreleaseNoPool(): Object 0x3d1b10 of class 
NSSurface autoreleased with no pool in place - just leaking
Using ATI workaround of dispatching display() on event thread
apple.awt.EventQueueExceptionHandler Caught Throwable : java.lang.IllegalMonitorStateException: 
current thread not owner
java.lang.IllegalMonitorStateException: current thread not owner
	at net.java.games.jogl.impl.JAWT_DrawingSurface.Unlock0(Native Method)
	at net.java.games.jogl.impl.JAWT_DrawingSurface.Unlock(JAWT_DrawingSurface.java:61)
	at 
net.java.games.jogl.impl.macosx.MacOSXOnscreenGLContext.unlockSurface(MacOSXOnscreenGLContex
t.java:253)
	at 
net.java.games.jogl.impl.macosx.MacOSXOnscreenGLContext.free(MacOSXOnscreenGLContext.java:
161)
	at net.java.games.jogl.impl.GLContext.invokeGL(GLContext.java:319)
	at 
net.java.games.jogl.impl.macosx.MacOSXOnscreenGLContext.invokeGL(MacOSXOnscreenGLContext.jav
a:81)
	at net.java.games.jogl.GLCanvas.displayImpl(GLCanvas.java:208)
	at net.java.games.jogl.GLCanvas.display(GLCanvas.java:75)
	at net.java.games.jogl.GLCanvas.paint(GLCanvas.java:82)
	at sun.awt.RepaintArea.paint(RepaintArea.java:194)
	at apple.awt.ComponentModel.handleEvent(ComponentModel.java:281)
	at java.awt.Component.dispatchEventImpl(Component.java:3744)
	at java.awt.Component.dispatchEvent(Component.java:3543)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:456)
	at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:234)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:184)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:178)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:170)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:100)


==================

import java.awt.EventQueue;
import java.awt.Frame;

import net.java.games.jogl.Animator;
import net.java.games.jogl.GL;
import net.java.games.jogl.GLCanvas;
import net.java.games.jogl.GLCapabilities;
import net.java.games.jogl.GLDrawable;
import net.java.games.jogl.GLDrawableFactory;
import net.java.games.jogl.GLEventListener;
import net.java.games.jogl.GLException;
import net.java.games.jogl.GLU;
import net.java.games.jogl.impl.SingleThreadedWorkaround;


public class AnimatorTest {

    public AnimatorTest() {
 
   }

    public static void main(String[] args) {
        
        GLDrawableFactory factory = GLDrawableFactory.getFactory();
        
        Frame frame1 = new Frame("screen 1");
        GLCanvas canvas1 = factory.createGLCanvas(new GLCapabilities());
        canvas1.addGLEventListener(new MyRenderer());
        frame1.add(canvas1);
        frame1.setSize(640, 480);
        frame1.show();

        
        Frame frame2 = new Frame("screen 2");
        GLCanvas canvas2 = factory.createGLCanvas(new GLCapabilities());
        canvas2.addGLEventListener(new MyRenderer());
        frame2.add(canvas2);
        frame2.setSize(640, 480);
        frame2.show();

        
        
        MyAnimator ani1 = new MyAnimator(canvas1);
        ani1.start();

        MyAnimator ani2 = new MyAnimator(canvas2);
        ani2.start();
    }
}



class MyAnimator {
    private GLDrawable drawable;

    private Runnable runnable;

    private Thread thread;

    private boolean shouldStop;



    /** Creates a new Animator for a particular drawable. */
    public MyAnimator(GLDrawable drawable) {
        this.drawable = drawable;

        // Workaround for NVidia driver https://jogl.dev.java.net/bugs/show_bug.cgi?id=80174
        if (drawable instanceof GLCanvas) {
            //((GLCanvas) drawable).willSetRenderingThread();
        }
    }



    /** Starts this animator. */
    public synchronized void start() {
        if (thread != null) {
            throw new GLException("Already started");
        }
        if (runnable == null) {
            runnable = new Runnable() {
                public void run() {
                    boolean noException = false;
                    try {
                        // Try to get OpenGL context optimization since we know
                        // we
                        // will be rendering this one drawable continually from
                        // this thread; make the context current once instead of
                        // making it current and freeing it each frame.
                        drawable.setRenderingThread(Thread.currentThread());

                        // Since setRenderingThread is currently advisory
                        // (because
                        // of the poor JAWT implementation in the Motif AWT,
                        // which
                        // performs excessive locking) we also prevent
                        // repaint(),
                        // which is called from the AWT thread, from having an
                        // effect for better multithreading behavior. This call
                        // is
                        // not strictly necessary, but if end users write their
                        // own animation loops which update multiple drawables
                        // per
                        // tick then it may be necessary to enforce the order of
                        // updates.
                        drawable.setNoAutoRedrawMode(true);

                        while (!shouldStop) {
                            noException = false;
                            drawable.display();
                            noException = true;
                        }
                    } finally {
                        shouldStop = false;
                        drawable.setNoAutoRedrawMode(false);
                        try {
                            // The surface is already unlocked and rendering
                            // thread is already null if an exception occurred
                            // during display(), so don't disable the rendering
                            // thread again.
                            if (noException) {
                                // Destruction of the underlying GLContext may
                                // have
                                // disabled the setRenderingThread optimization
                                // out
                                // from under us
                                if (drawable.getRenderingThread() != null) {
                                    drawable.setRenderingThread(null);
                                }
                            }
                        } finally {
                            synchronized (MyAnimator.this) {
                                thread = null;
                                MyAnimator.this.notify();
                            }
                        }
                    }
                }
            };
        }
        thread = new Thread(runnable);
        thread.start();
    }



    /**
     * Stops this animator, blocking until the animation thread has finished.
     */
    public synchronized void stop() {
        shouldStop = true;
        // It's hard to tell whether the thread which calls stop() has
        // dependencies on the Animator's internal thread. Currently we
        // use a couple of heuristics to determine whether we should do
        // the blocking wait().
        if ((Thread.currentThread() == thread)
                || (SingleThreadedWorkaround.doWorkaround() && EventQueue.isDispatchThread())) {
            return;
        }
        while (shouldStop && thread != null) {
            try {
                wait();
            } catch (InterruptedException ie) {
            }
        }
    }
}



class MyRenderer implements GLEventListener {

    private int width;

    private int height;

    private boolean b;



    public MyRenderer() {

    }



    public void init(GLDrawable drawable) {

    }



    public void reshape(GLDrawable drawable, int x, int y, int width, int height) {
        this.width = width;
        this.height = height;
    }



    public synchronized void display(GLDrawable drawable) {

        final GL gl = drawable.getGL();
        final GLU glu = drawable.getGLU();

        gl.glClear(GL.GL_COLOR_BUFFER_BIT);

        gl.glLoadIdentity();
        gl.glViewport(0, 0, width, height);
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();

        glu.gluOrtho2D(0.0, width, 0.0, height);
        gl.glPushMatrix();

        if (b)
            gl.glColor4f(1.0f, 0.0f, 0.0f, 1f);
        else
            gl.glColor4f(0.0f, 1.0f, 0.0f, 1f);
        b = !b;

        gl.glBegin(GL.GL_QUADS);
        gl.glVertex2i(0, 0);
        gl.glVertex2i(0, 12);
        gl.glVertex2i(12, 12);
        gl.glVertex2i(12, 0);
        gl.glEnd();

        gl.glPopMatrix();
        
        gl.glFlush();
        gl.glFinish();
    }




    public void displayChanged(GLDrawable drawable, boolean modeChanged, boolean deviceChanged) {
    }

}



---- Additional Comments From kbr 2005-01-31 01:09:06 ----

This is reproducible with Mac OS X 10.3 and the latest JOGL sources even on
NVidia hardware. It is probably a low-level bug in the JAWT on OS X. If you use
the latest JOGL sources and specify -DJOGL_SINGLE_THREADED_WORKAROUND=true on
the command line the problem disappears, so it is likely a multithreading
problem. I'll alert one of the Apple engineers about the issue.




---- Additional Comments From kbr 2005-02-24 15:04:40 ----

Stability problems have been reported on all three of JOGL's major
supported platforms (Windows, Linux and Mac OS X) whose root cause is
multithreading-related bugs in vendors' OpenGL drivers.

On Windows, the most recent version of NVidia's drivers (66.93) when
run on a GeForce 6800 causes a blue screen of death upon exit from the
simplest JOGL demo (Gears).

On Linux, JVM crashes have been reported upon exit of certain test
cases on NVidia hardware. The same test case causes a report of an
unexpected async reply from Xlib on ATI hardware (at the last time of
testing -- I no longer have access to a Linux machine with ATI
hardware because all of their currently available drivers crash the X
server on my machine).

On Mac OS X, low-level warnings from Cocoa are printed in
multithreaded JOGL situations which can lead to JVM crashes.

All of these problems' root cause is that the current OpenGL drivers
on the market, or supporting software built on them, were not designed
to be used in a multithreaded fashion. Most C programs which use
OpenGL are either single-threaded or perform all of their OpenGL work
from a single thread. JOGL was originally designed to support OpenGL
rendering from arbitrary threads and appropriate synchronization was
introduced into the library to handle this. However, we have had to
scale back this support as stability issues have been encountered.

The most recent round of reported bugs, including the PC crash upon
exit of the JOGL demos, is serious enough that we must take drastic
measures. Support was introduced in earlier JOGL releases to move all
of the OpenGL work performed by JOGL and users' code via the
GLEventListener on to the AWT event dispatch thread. It turns out that
doing this works around all of the above reported bugs. In JOGL 1.1
b08 and 1.1 b09, code changes were made to make this single-threaded
support more correct; this checkin includes another small set of such
changes, including some to the GLPbuffer implementation, and changes
the default of the flag controlling this support to true.

-Djogl.1thread=true is now the default. -Djogl.1thread=auto restores
the behavior of previous releases, which was to enable the
single-threaded mode only with ATI cards. This auto-detection
mechanism was not robust enough and adding cases for the crashes above
was not feasible. -Djogl.1thread=false disables the single-threaded
workaround. Older synonyms for this system property,
JOGL_SINGLE_THREADED_WORKAROUND and ATI_WORKAROUND, remain in the
source base for the time being. Changing the value of any of these
system properties is not recommended.

The expected performance impact of these changes is minimal. In
earlier JOGL releases it appeared that the overhead of making a
context current and releasing it each frame was very significant and a
key differentiator in being able to match C performance. More recent
tests seem to indicate that this is no longer the case, at least with
current hardware. Regardless, we must achieve stability in order for
the library to be useful and this seems to be the best means of
achieving that goal.

We believe that the compatibility impact of these changes for existing
JOGL applications will also be minimal. For correctly-written JOGL
applications, the only visible change in behavior should be that the
values of thread-local variables accessed through the
java.lang.ThreadLocal class may change since the actual thread on
which the GLEventListener's callbacks will be executed may have
changed. Multithreaded JOGL applications performing complex
inter-thread synchronization may see subtle differences in behavior.
We hope that such applications and the developers writing them will be
able to handle this change in behavior without much trouble.

We will continue to work with graphics card vendors to improve the
stability of their OpenGL drivers. Until that happens, we believe this
change will yield the best possible improvement in stability and
portability for applications using JOGL.




--- Bug imported by sgothel@jausoft.com 2010-03-24 07:46 EDT  ---

This bug was previously known as _bug_ 109 at https://jogl.dev.java.net/bugs/show_bug.cgi?id=109