/** * Copyright 2015 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 java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Label; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.InvocationTargetException; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; import com.jogamp.opengl.test.junit.util.AWTRobotUtil; import com.jogamp.opengl.test.junit.util.MiscUtils; import com.jogamp.opengl.test.junit.util.UITestCase; import org.junit.Assert; import com.jogamp.common.ExceptionUtils; import com.jogamp.common.util.InterruptedRuntimeException; import com.jogamp.common.util.SourcedInterruptedException; import com.jogamp.nativewindow.NativeWindowFactory; import com.jogamp.opengl.GLAutoDrawable; import com.jogamp.opengl.GLEventListener; import com.jogamp.opengl.awt.GLCanvas; import org.junit.Test; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; /** * Test to check if interrupt on AWT-EventQueue causes a malfunction in JOGL. * <p> * After tests are displaying an ever color rotating rectangle in an AWT component alone * and with an additional GearsES2 within a GLCanvas. * </p> * <p> * The AWT component is issuing an interrupt during paint on the AWT-EDT. * </p> * <p> * The reporter claims that an interrupt on the AWT-EDT shall not disturb neither AWT nor JOGL's GLCanvas * and rendering shall continue. * <ul> * <li>This seems to be true for JRE 1.8.0_60</li> * <li>This seems to be false for JRE 1.7.0_45. This JRE's AWT-EDT even dies occasionally when interrupted.</li> * </ul> * </p> * <p> * The test passes on GNU/Linux and Windows using JRE 1.8.0_60. * </p> */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestBug1225EventQueueInterruptedAWT extends UITestCase { static long durationPerTest = 1000; // ms private void setVisible(final JFrame frame, final boolean v) throws InterruptedException, InvocationTargetException { javax.swing.SwingUtilities.invokeAndWait(new Runnable() { public void run() { frame.pack(); // frame.setSize(new Dimension(800, 600)); frame.setVisible(v); }}); } private void dispose(final JFrame jFrame) throws InterruptedException, InvocationTargetException { SwingUtilities.invokeAndWait(new Runnable() { public void run() { jFrame.dispose(); } } ) ; } @Test(timeout=180000) // TO 3 min public void test01_NoGL() throws InterruptedException, InvocationTargetException { testImpl(false); } @Test(timeout=180000) // TO 3 min public void test02_WithGL() throws InterruptedException, InvocationTargetException { testImpl(true); } class OurUncaughtExceptionHandler implements UncaughtExceptionHandler { public volatile Thread thread = null; public volatile Throwable exception = null; @Override public void uncaughtException(final Thread t, final Throwable e) { thread = t; exception = e; System.err.println("*** UncaughtException (this Thread "+Thread.currentThread().getName()+") : Thread <"+t.getName()+">, "+e.getClass().getName()+": "+e.getMessage()); ExceptionUtils.dumpThrowable("", e); } } void testImpl(final boolean useGL) throws InterruptedException, InvocationTargetException { if( !AWTRobotUtil.isAWTEDTAlive() ) { System.err.println("Test aborted: AWT not alive"); return; } // Assume.assumeTrue("AWT not alive", AWTRobotUtil.isAWTEDTAlive()); // Assert.assertTrue("AWT not alive", AWTRobotUtil.isAWTEDTAlive()); final OurUncaughtExceptionHandler uncaughtHandler = new OurUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler( uncaughtHandler ); final Dimension csize = new Dimension(800, 400); final JPanel panel = new JPanel(new GridLayout(2, 1)); final GLCanvas glc; final InterruptableGLEL iglel; if( useGL ) { glc = new GLCanvas(); { final GearsES2 gears = new GearsES2(); gears.setVerbose(false); glc.addGLEventListener(gears); } iglel = new InterruptableGLEL(); glc.addGLEventListener(iglel); glc.setSize(csize); glc.setPreferredSize(csize); panel.add(glc); } else { NativeWindowFactory.initSingleton(); glc = null; iglel = null; final Label l = new Label("No GL Object"); l.setSize(csize); l.setPreferredSize(csize); panel.add(l); } final InterruptingComponent icomp = new InterruptingComponent(); panel.add(icomp); icomp.setSize(csize); icomp.setPreferredSize(csize); final JFrame frame = new JFrame(); frame.setResizable(true); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(panel, BorderLayout.CENTER); setVisible(frame, true); if( useGL ) { Assert.assertTrue(AWTRobotUtil.waitForRealized(glc, true)); } Assert.assertTrue(AWTRobotUtil.waitForRealized(icomp, true)); final InterruptableLoop loop = new InterruptableLoop(icomp, glc); final Thread thread = new Thread(loop); synchronized(loop) { thread.start(); try { loop.notifyAll(); // wake-up startup-block while( !loop.isRunning && !loop.shallStop ) { loop.wait(); // wait until started } loop.ack = true; loop.notifyAll(); // wake-up startup-block } catch (final InterruptedException e) { Assert.assertNull("while starting loop", new InterruptedRuntimeException(e)); } } for(int i=0; thread.isAlive() && null == loop.exception && null == uncaughtHandler.exception && i<100; i++) { icomp.interruptAWTEventQueue(); Thread.sleep(durationPerTest/100); } loop.shallStop = true; synchronized(loop) { try { loop.notifyAll(); // wake-up pause-block (opt) while( loop.isRunning ) { loop.wait(); // wait until stopped } } catch (final InterruptedException e) { Assert.assertNull("while stopping loop", new InterruptedRuntimeException(e)); } } // // Notifications only! // // Note: // On JRE 1.8.0_60: Interrupt is cleared on AWT-EDT // On JRE 1.7.0_45: Interrupt is *NOT* cleared on AWT-EDT // if( null != iglel && null != iglel.exception ) { ExceptionUtils.dumpThrowable("GLEventListener", iglel.exception); } if( null != icomp.exception ) { ExceptionUtils.dumpThrowable("InterruptingComponent", icomp.exception); } if( null != loop.exception ) { ExceptionUtils.dumpThrowable("loop", loop.exception); } if( null != uncaughtHandler.exception ) { ExceptionUtils.dumpThrowable("uncaughtHandler", uncaughtHandler.exception); } if( !AWTRobotUtil.isAWTEDTAlive() ) { System.err.println("AWT is not alive anymore!!! Ooops"); // cannot do anything anymore on AWT-EDT .. frame.dispose(); } else { dispose(frame); } // // Fail if interrupt was propagated to loop or uncaught handler // Assert.assertNull("Caught Exception in loop", loop.exception); Assert.assertNull("Caught Exception via uncaughtHandler", uncaughtHandler.exception); } static class InterruptableLoop implements Runnable { public volatile Exception exception = null; public volatile boolean shallStop = false; public volatile boolean isRunning = false; public volatile boolean ack = false; final InterruptingComponent icomp; final GLCanvas glc; boolean alt = false;; InterruptableLoop(final InterruptingComponent icomp, final GLCanvas glc) { this.icomp = icomp; this.glc = glc; } public void stop() { shallStop = true; } @Override public void run() { synchronized ( this ) { isRunning = true; this.notifyAll(); try { while( !ack ) { this.wait(); // wait until ack } this.notifyAll(); } catch (final InterruptedException e) { throw new InterruptedRuntimeException(e); } ack = false; } synchronized ( this ) { try { while( !shallStop ) { if( alt ) { icomp.repaint(); // issues paint of GLCanvas on AWT-EDT } else if( null != glc ) { // Avoid invokeAndWait(..) in GLCanvas.display() if AWT-EDT dies! glc.repaint(); // issues paint of GLCanvas on AWT-EDT, which then issues display()! } alt = !alt; Thread.sleep(16); if( Thread.interrupted() ) { final InterruptedRuntimeException e = new InterruptedRuntimeException(new InterruptedException("Interrupt detected in loop, thread: "+Thread.currentThread().getName())); throw e; } } } catch (final InterruptedException e) { exception = SourcedInterruptedException.wrap(e); ExceptionUtils.dumpThrowable("", exception); } catch (final Exception e) { exception = e; ExceptionUtils.dumpThrowable("", exception); } finally { isRunning = false; this.notifyAll(); } } } } static class InterruptableGLEL implements GLEventListener { public volatile InterruptedException exception = null; @Override public void init(final GLAutoDrawable drawable) { } @Override public void dispose(final GLAutoDrawable drawable) { } @Override public void display(final GLAutoDrawable drawable) { final Thread c = Thread.currentThread(); if( c.isInterrupted() && null == exception ) { exception = new InterruptedException("Interrupt detected in GLEventListener, thread: "+c.getName()); drawable.removeGLEventListener(this); } } @Override public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { } } static class InterruptingComponent extends Component { private static final long serialVersionUID = 1L; public volatile InterruptedException exception = null; private volatile boolean doInterrupt = false; private final Color[] colors = new Color[] { Color.BLACK, Color.BLUE, Color.DARK_GRAY, Color.GRAY, Color.LIGHT_GRAY }; private int colorIdx = 0; public InterruptingComponent() { } public void interruptAWTEventQueue() { doInterrupt = true; } @Override public void paint(final Graphics g) { final Thread c = Thread.currentThread(); if( c.isInterrupted() && null == exception ) { exception = new InterruptedException("Interrupt detected in AWT Component, thread: "+c.getName()); } g.setColor(colors[colorIdx++]); if( colorIdx >= colors.length ) { colorIdx = 0; } g.fillRect(0, 0, getWidth(), getHeight()); if(doInterrupt) { System.err.println("Thread "+c.getName()+": *Interrupting*"); doInterrupt = false; c.interrupt(); } } } public static void main(final String[] args) { for(int i=0; i<args.length; i++) { if(args[i].equals("-time")) { durationPerTest = MiscUtils.atol(args[++i], durationPerTest); } } org.junit.runner.JUnitCore.main(TestBug1225EventQueueInterruptedAWT.class.getName()); } }