From 77db6a5c22cb4a53cf911b4caf57127770c70968 Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Sun, 4 Nov 2012 19:05:32 +0100 Subject: Adding Andres Colubri's Test Case (junit'fyed), which provokes a Deadlock on OSX - Adding a similar one w/o deadlock and less framework bits. Andres Colubri reported a test case in the forum: Which is now included as TestGLCanvasAWTActionDeadlock01AWT. A similar w/ less framework bits and w/o dealock is also included as TestGLCanvasAWTActionDeadlock00AWT. A followup commit will incl. a fix after further analysis. A commit at this point where TestGLCanvasAWTActionDeadlock01AWT still freezes on OSX is done to be able to reproduce the bug and see the fix as a patch. --- .../awt/TestGLCanvasAWTActionDeadlock00AWT.java | 240 ++++++++ .../awt/TestGLCanvasAWTActionDeadlock01AWT.java | 604 +++++++++++++++++++++ 2 files changed, 844 insertions(+) create mode 100644 src/test/com/jogamp/opengl/test/junit/jogl/awt/TestGLCanvasAWTActionDeadlock00AWT.java create mode 100644 src/test/com/jogamp/opengl/test/junit/jogl/awt/TestGLCanvasAWTActionDeadlock01AWT.java (limited to 'src/test/com/jogamp/opengl') diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/awt/TestGLCanvasAWTActionDeadlock00AWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/awt/TestGLCanvasAWTActionDeadlock00AWT.java new file mode 100644 index 000000000..c6b70e12e --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/awt/TestGLCanvasAWTActionDeadlock00AWT.java @@ -0,0 +1,240 @@ +/** + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package com.jogamp.opengl.test.junit.jogl.awt; + +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLEventListener; +import javax.media.opengl.awt.GLCanvas; +import com.jogamp.opengl.util.Animator; +import com.jogamp.opengl.util.AnimatorBase; +import com.jogamp.opengl.util.FPSAnimator; + +import com.jogamp.opengl.test.junit.util.UITestCase; +import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; + +import com.jogamp.opengl.test.junit.util.MiscUtils; + +import java.awt.BorderLayout; +import java.awt.Frame; +import java.awt.Insets; + +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Test; + + +public class TestGLCanvasAWTActionDeadlock00AWT extends UITestCase { + static long durationPerTest = 1000; // ms + static final int width = 512; + static final int height = 512; + + GLEventListener gle1 = null; + GLEventListener gle2 = null; + + @Test + public void test01Animator() throws InterruptedException { + testImpl(new Animator(), 0, false); + } + + @Test + public void test02FPSAnimator() throws InterruptedException { + testImpl(new FPSAnimator(30), 0, false); + } + + @Test + public void test02FPSAnimator_RestartOnAWTEDT() throws InterruptedException { + testImpl(new FPSAnimator(30), 100, false); + } + + @Test + public void test02FPSAnimator_RestartOnCurrentThread() throws InterruptedException { + testImpl(new FPSAnimator(30), 100, true); + } + + void testImpl(final AnimatorBase animator, int restartPeriod, boolean restartOnCurrentThread) throws InterruptedException { + final Frame frame1 = new Frame("Frame 1"); + gle1 = new GLEventListener() { + @Override + public void init(GLAutoDrawable drawable) { + } + + @Override + public void dispose(GLAutoDrawable drawable) { + } + + @Override + public void display(GLAutoDrawable drawable) { + frame1.setTitle("f "+frameCount+", fps "+animator.getLastFPS()); + frameCount++; + } + + @Override + public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { + } + }; + gle2 = new GearsES2(); + + Assert.assertNotNull(frame1); + { + Insets insets = frame1.getInsets(); + int w = width + insets.left + insets.right; + int h = height + insets.top + insets.bottom; + frame1.setSize(w, h); + } + frame1.setLocation(0, 0); + frame1.setTitle("Generic Title"); + + GLCanvas glCanvas = createGLCanvas(frame1); + glCanvas.addGLEventListener(gle1); + glCanvas.addGLEventListener(gle2); + + animator.setUpdateFPSFrames(60, System.err); + animator.add(glCanvas); + animator.start(); + + attachGLCanvas(frame1, glCanvas, false); + + try { + javax.swing.SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + frame1.setVisible(true); + }}); + } catch (Throwable t) { + t.printStackTrace(); + Assume.assumeNoException(t); + } + + final long sleep = 0 < restartPeriod ? restartPeriod : 100; + long togo = durationPerTest; + while( 0 < togo ) { + if(0 < restartPeriod) { + glCanvas = restart(frame1, glCanvas, restartOnCurrentThread); + } + + Thread.sleep(sleep); + + togo -= sleep; + } + + dispose(frame1, glCanvas); + animator.stop(); + + gle1 = null; + gle2 = null; + } + + void dispose(final Frame frame, final GLCanvas glCanvas) { + try { + javax.swing.SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + glCanvas.destroy(); + frame.dispose(); + }}); + } catch (Throwable t) { + t.printStackTrace(); + Assume.assumeNoException(t); + } + } + + GLCanvas restart(final Frame frame, GLCanvas glCanvas, boolean restartOnCurrentThread) throws InterruptedException { + glCanvas.disposeGLEventListener(gle1, true); + glCanvas.disposeGLEventListener(gle2, true); + detachGLCanvas(frame, glCanvas, restartOnCurrentThread); + + glCanvas = createGLCanvas(frame); + + attachGLCanvas(frame, glCanvas, restartOnCurrentThread); + glCanvas.addGLEventListener(gle1); + glCanvas.addGLEventListener(gle2); + + return glCanvas; + } + + void attachGLCanvas(final Frame frame, final GLCanvas glCanvas, boolean restartOnCurrentThread) { + System.err.println("*** attachGLCanvas.0 on-current-thread "+restartOnCurrentThread+", currentThread "+Thread.currentThread().getName()); + if( restartOnCurrentThread ) { + frame.setLayout(new BorderLayout()); + frame.add(glCanvas, BorderLayout.CENTER); + frame.validate(); + } else { + try { + javax.swing.SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + frame.setLayout(new BorderLayout()); + frame.add(glCanvas, BorderLayout.CENTER); + frame.validate(); + }}); + } catch (Throwable t) { + t.printStackTrace(); + Assume.assumeNoException(t); + } + } + System.err.println("*** attachGLCanvas.X"); + } + + void detachGLCanvas(final Frame frame, final GLCanvas glCanvas, boolean restartOnCurrentThread) { + System.err.println("*** detachGLCanvas.0 on-current-thread "+restartOnCurrentThread+", currentThread "+Thread.currentThread().getName()); + if( restartOnCurrentThread ) { + frame.remove(glCanvas); + frame.validate(); + } else { + try { + javax.swing.SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + frame.remove(glCanvas); + frame.validate(); + }}); + } catch (Throwable t) { + t.printStackTrace(); + Assume.assumeNoException(t); + } + } + System.err.println("*** detachGLCanvas.X"); + } + + int frameCount = 0; + + GLCanvas createGLCanvas(final Frame frame) { + System.err.println("*** createGLCanvas.0"); + final GLCanvas glCanvas = new GLCanvas(); + glCanvas.setBounds(0, 0, width, height); + Assert.assertNotNull(glCanvas); + System.err.println("*** createGLCanvas.X"); + return glCanvas; + } + + public static void main(String args[]) { + for(int i=0; i 1000 * fint) { + frate = (float)(fcount) / fint; + fcount = 0; + lastm = m; + System.err.println("fps: " + frate); + } + } + + void clock() { + long afterTime = System.nanoTime(); + long timeDiff = afterTime - beforeTime; + long sleepTime = (frameRatePeriod - timeDiff) - overSleepTime; + + if (sleepTime > 0) { // some time left in this cycle + try { + Thread.sleep(sleepTime / 1000000L, (int) (sleepTime % 1000000L)); + } catch (InterruptedException ex) { } + + overSleepTime = (System.nanoTime() - afterTime) - sleepTime; + + } else { // sleepTime <= 0; the frame took longer than the period + overSleepTime = 0L; + } + + beforeTime = System.nanoTime(); + } + + class SimpleListener implements GLEventListener { + @Override + public void display(GLAutoDrawable drawable) { + draw(drawable.getGL().getGL2()); + } + + @Override + public void dispose(GLAutoDrawable drawable) { } + + @Override + public void init(GLAutoDrawable drawable) { } + + @Override + public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) { } + } + + public void mouseDragged(MouseEvent ev) { + if (printEventInfo) { + System.err.println("Mouse dragged event: " + ev); + } + } + + public void mouseMoved(MouseEvent ev) { + if (printEventInfo) { + System.err.println("Mouse moved event: " + ev); + } + } + + public void keyPressed(KeyEvent ev) { + if (printEventInfo) { + System.err.println("Key pressed event: " + ev); + } + } + + public void keyReleased(KeyEvent ev) { + if (printEventInfo) { + System.err.println("Key released event: " + ev); + } + } + + public void keyTyped(KeyEvent ev) { + if (printEventInfo) { + System.err.println("Key typed event: " + ev); + } + } + + /** An Animator subclass which renders one frame at the time + * upon calls to the requestRender() method. + **/ + public class CustomAnimator extends AnimatorBase { + private Timer timer = null; + private TimerTask task = null; + private volatile boolean shouldRun; + + protected String getBaseName(String prefix) { + return "Custom" + prefix + "Animator" ; + } + + /** Creates an CustomAnimator with an initial drawable to + * animate. */ + public CustomAnimator(GLAutoDrawable drawable) { + if (drawable != null) { + add(drawable); + } + } + + public synchronized void requestRender() { + shouldRun = true; + } + + public final boolean isStarted() { + stateSync.lock(); + try { + return (timer != null); + } finally { + stateSync.unlock(); + } + } + + public final boolean isAnimating() { + stateSync.lock(); + try { + return (timer != null) && (task != null); + } finally { + stateSync.unlock(); + } + } + + private void startTask() { + if(null != task) { + return; + } + + task = new TimerTask() { + private boolean firstRun = true; + public void run() { + if (firstRun) { + Thread.currentThread().setName("OPENGL"); + firstRun = false; + } + if(CustomAnimator.this.shouldRun) { + CustomAnimator.this.animThread = Thread.currentThread(); + // display impl. uses synchronized block on the animator instance + display(); + synchronized (this) { + // done with current frame. + shouldRun = false; + } + } + } + }; + + fpsCounter.resetFPSCounter(); + shouldRun = false; + + timer.schedule(task, 0, 1); + } + + public synchronized boolean start() { + if (timer != null) { + return false; + } + stateSync.lock(); + try { + timer = new Timer(); + startTask(); + } finally { + stateSync.unlock(); + } + return true; + } + + /** Stops this CustomAnimator. */ + public synchronized boolean stop() { + if (timer == null) { + return false; + } + stateSync.lock(); + try { + shouldRun = false; + if(null != task) { + task.cancel(); + task = null; + } + if(null != timer) { + timer.cancel(); + timer = null; + } + animThread = null; + try { + Thread.sleep(20); // ~ 1/60 hz wait, since we can't ctrl stopped threads + } catch (InterruptedException e) { } + } finally { + stateSync.unlock(); + } + return true; + } + + public final boolean isPaused() { return false; } + public synchronized boolean resume() { return false; } + public synchronized boolean pause() { return false; } + } + } + + @Test + public void test00() { + TestGLCanvasAWTActionDeadlock01AWT.MiniPApplet mini; + try { + Class c = Thread.currentThread().getContextClassLoader().loadClass(TestGLCanvasAWTActionDeadlock01AWT.MiniPApplet.class.getName()); + mini = (TestGLCanvasAWTActionDeadlock01AWT.MiniPApplet) c.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (mini != null) { + mini.run(); + } + } + + public static void main(String args[]) { + for(int i=0; i