From 45160297725fa3688b6c9dccb1d7c3095c2ca773 Mon Sep 17 00:00:00 2001
From: Sven Gothel <sgothel@jausoft.com>
Date: Sun, 12 Jan 2014 08:08:42 +0100
Subject: DefaultEDTUtil: At EDT finish, notify all task-waiter to avoid
 deadlock at error / Add test method 'invokeAndWaitError(..)'

---
 src/newt/classes/jogamp/newt/DefaultEDTUtil.java   | 49 ++++++++++++++++++----
 .../jogl/demos/es2/newt/TestGearsES2NEWT.java      | 49 +++++++++++++++++-----
 .../jogamp/opengl/test/junit/util/QuitAdapter.java | 17 ++++----
 3 files changed, 88 insertions(+), 27 deletions(-)

(limited to 'src')

diff --git a/src/newt/classes/jogamp/newt/DefaultEDTUtil.java b/src/newt/classes/jogamp/newt/DefaultEDTUtil.java
index f33b4744e..d481ce8f9 100644
--- a/src/newt/classes/jogamp/newt/DefaultEDTUtil.java
+++ b/src/newt/classes/jogamp/newt/DefaultEDTUtil.java
@@ -50,6 +50,11 @@ import com.jogamp.newt.util.EDTUtil;
 public class DefaultEDTUtil implements EDTUtil {
     public static final boolean DEBUG = Debug.debug("EDT");
 
+    /** Used to implement {@link #invokeStop(boolean, Runnable)}. */
+    private static final Object TASK_ATTACHMENT_STOP = new Object();
+    /** Used to provoke an exception on the EDT while waiting / blocking. Merely exists to test code.*/
+    private static final Object TASK_ATTACHMENT_TEST_ERROR = new Object();
+
     private final Object edtLock = new Object(); // locking the EDT start/stop state
     private /* final */ ThreadGroup threadGroup;
     private final String name;
@@ -139,12 +144,20 @@ public class DefaultEDTUtil implements EDTUtil {
             System.err.println(Thread.currentThread()+": Default-EDT.invokeStop wait "+wait);
             Thread.dumpStack();
         }
-        return invokeImpl(wait, task, true);
+        return invokeImpl(wait, task, true /* stop */, false /* provokeError */);
+    }
+
+    public final boolean invokeAndWaitError(Runnable task) {
+        if(DEBUG) {
+            System.err.println(Thread.currentThread()+": Default-EDT.invokeAndWaitError");
+            Thread.dumpStack();
+        }
+        return invokeImpl(true /* wait */, task, false /* stop */, true /* provokeError */);
     }
 
     @Override
     public final boolean invoke(boolean wait, Runnable task) {
-        return invokeImpl(wait, task, false);
+        return invokeImpl(wait, task, false /* stop */, false /* provokeError */);
     }
 
     private static Runnable nullTask = new Runnable() {
@@ -152,7 +165,7 @@ public class DefaultEDTUtil implements EDTUtil {
         public void run() { }
     };
 
-    private final boolean invokeImpl(boolean wait, Runnable task, boolean stop) {
+    private final boolean invokeImpl(boolean wait, Runnable task, boolean stop, boolean provokeError) {
         Throwable throwable = null;
         RunnableTask rTask = null;
         final Object rTaskLock = new Object();
@@ -204,7 +217,9 @@ public class DefaultEDTUtil implements EDTUtil {
                                                      true /* always catch and report Exceptions, don't disturb EDT */,
                                                      wait ? null : System.err);
                             if(stop) {
-                                rTask.setAttachment(new Boolean(true)); // mark final task, will imply shouldStop:=true
+                                rTask.setAttachment(TASK_ATTACHMENT_STOP); // mark final task, will imply shouldStop:=true
+                            } else if(provokeError) {
+                                rTask.setAttachment(TASK_ATTACHMENT_TEST_ERROR);
                             }
                             // append task ..
                             edt.tasks.add(rTask);
@@ -283,7 +298,7 @@ public class DefaultEDTUtil implements EDTUtil {
     class NEDT extends Thread {
         volatile boolean shouldStop = false;
         volatile boolean isRunning = false;
-        ArrayList<RunnableTask> tasks = new ArrayList<RunnableTask>(); // one shot tasks
+        final ArrayList<RunnableTask> tasks = new ArrayList<RunnableTask>(); // one shot tasks
 
         public NEDT(ThreadGroup tg, String name) {
             super(tg, name);
@@ -340,8 +355,13 @@ public class DefaultEDTUtil implements EDTUtil {
                         if(tasks.size()>0) {
                             task = tasks.remove(0);
                             tasks.notifyAll();
-                            if( null != task.getAttachment() ) {
+                            final Object attachment = task.getAttachment();
+                            if( TASK_ATTACHMENT_STOP == attachment ) {
                                 shouldStop = true;
+                            } else if( TASK_ATTACHMENT_TEST_ERROR == attachment ) {
+                                tasks.add(0, task);
+                                task = null;
+                                throw new RuntimeException("TASK_ATTACHMENT_TEST_ERROR");
                             }
                         }
                     }
@@ -366,16 +386,27 @@ public class DefaultEDTUtil implements EDTUtil {
                     error = new RuntimeException("Within Default-EDT", t);
                 }
             } finally {
+                final String msg = getName()+": Default-EDT finished w/ "+tasks.size()+" left";
                 if(DEBUG) {
-                    RunnableTask rt = ( tasks.size() > 0 ) ? tasks.get(0) : null ;
-                    System.err.println(getName()+": Default-EDT run() END "+ getName()+", tasks: "+tasks.size()+", "+rt+", "+error);
+                    System.err.println(msg+", "+error);
                 }
                 synchronized(edtLock) {
+                    int i = 0;
+                    while( tasks.size() > 0 ) {
+                        // notify all waiter
+                        final String msg2 = msg+", task #"+i;
+                        final Throwable t = null != error ? new Throwable(msg2, error) : new Throwable(msg2);
+                        final RunnableTask rt = tasks.remove(0);
+                        if( null != rt ) {
+                            rt.flush(t);
+                            i++;
+                        }
+                    }
                     isRunning = false;
                     edtLock.notifyAll();
                 }
                 if(DEBUG) {
-                    System.err.println(getName()+": Default-EDT run() EXIT "+ getName()+", exception: "+error);
+                    System.err.println(msg+" EXIT, exception: "+error);
                 }
                 if(null!=error) {
                     throw error;
diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NEWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NEWT.java
index 059a930ed..bfed497e8 100644
--- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NEWT.java
+++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NEWT.java
@@ -36,6 +36,7 @@ import com.jogamp.newt.Display;
 import com.jogamp.newt.Display.PointerIcon;
 import com.jogamp.newt.NewtFactory;
 import com.jogamp.newt.Screen;
+import com.jogamp.newt.Window;
 import com.jogamp.newt.event.KeyAdapter;
 import com.jogamp.newt.event.KeyEvent;
 import com.jogamp.newt.event.MouseAdapter;
@@ -43,6 +44,7 @@ import com.jogamp.newt.event.MouseEvent;
 import com.jogamp.newt.event.WindowEvent;
 import com.jogamp.newt.event.WindowAdapter;
 import com.jogamp.newt.opengl.GLWindow;
+import com.jogamp.newt.util.EDTUtil;
 import com.jogamp.opengl.test.junit.util.AWTRobotUtil;
 import com.jogamp.opengl.test.junit.util.MiscUtils;
 import com.jogamp.opengl.test.junit.util.UITestCase;
@@ -63,6 +65,8 @@ import javax.media.opengl.GLCapabilitiesImmutable;
 import javax.media.opengl.GLEventListener;
 import javax.media.opengl.GLProfile;
 
+import jogamp.newt.DefaultEDTUtil;
+
 import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.AfterClass;
@@ -98,7 +102,7 @@ public class TestGearsES2NEWT extends UITestCase {
     static boolean mainRun = false;
     static boolean exclusiveContext = false;
     static boolean useAnimator = true;
-    static enum SysExit { none, testExit, testError, displayExit, displayError };
+    static enum SysExit { none, testExit, testError, testEDTError, displayExit, displayError, displayEDTError };
     static SysExit sysExit = SysExit.none;
 
     @BeforeClass
@@ -158,7 +162,7 @@ public class TestGearsES2NEWT extends UITestCase {
             animator.setExclusiveContext(exclusiveContext);
         }
 
-        QuitAdapter quitAdapter = new QuitAdapter();
+        final QuitAdapter quitAdapter = new QuitAdapter();
         //glWindow.addKeyListener(new TraceKeyAdapter(quitAdapter));
         //glWindow.addWindowListener(new TraceWindowAdapter(quitAdapter));
         glWindow.addKeyListener(quitAdapter);
@@ -349,25 +353,38 @@ public class TestGearsES2NEWT extends UITestCase {
             Assert.assertEquals(exclusiveContext ? animator.getThread() : null, glWindow.getExclusiveContextThread());
         }
 
-        if( SysExit.displayError == sysExit || SysExit.displayExit == sysExit ) {
+        if( SysExit.displayError == sysExit || SysExit.displayExit == sysExit || SysExit.displayEDTError == sysExit ) {
             glWindow.addGLEventListener(new GLEventListener() {
-
                 @Override
                 public void init(GLAutoDrawable drawable) {}
-
                 @Override
                 public void dispose(GLAutoDrawable drawable) { }
-
                 @Override
                 public void display(GLAutoDrawable drawable) {
                     final GLAnimatorControl anim = drawable.getAnimator();
                     if( null != anim && anim.isAnimating() ) {
-                        if( anim.getTotalFPSDuration() >= duration/2 ) {
+                        final long ms = anim.getTotalFPSDuration();
+                        if( ms >= duration/2 || ms >= 3000 ) { // max 3s wait until provoking error
                             if( SysExit.displayError == sysExit ) {
-                                throw new Error("test error send from GLEventListener");
+                                throw new Error("test error send from GLEventListener.display - "+Thread.currentThread());
                             } else if ( SysExit.displayExit == sysExit ) {
                                 System.err.println("exit(0) send from GLEventListener");
                                 System.exit(0);
+                            } else if ( SysExit.displayEDTError == sysExit ) {
+                                final Object upstream = drawable.getUpstreamWidget();
+                                System.err.println("EDT invokeAndWaitError: upstream type "+upstream.getClass().getName());
+                                if( upstream instanceof Window ) {
+                                    final EDTUtil edt = ((Window)upstream).getScreen().getDisplay().getEDTUtil();
+                                    System.err.println("EDT invokeAndWaitError: edt type "+edt.getClass().getName());
+                                    if( edt instanceof DefaultEDTUtil ) {
+                                        quitAdapter.doQuit();
+                                        ((DefaultEDTUtil)edt).invokeAndWaitError(new Runnable() {
+                                            public void run() {
+                                                throw new RuntimeException("XXX Should never ever be seen! - "+Thread.currentThread());
+                                            }
+                                        });
+                                    }
+                                }
                             }
                         }
                     } else {
@@ -403,13 +420,25 @@ public class TestGearsES2NEWT extends UITestCase {
         while(!quitAdapter.shouldQuit() && t1-t0<duration) {
             Thread.sleep(100);
             t1 = System.currentTimeMillis();
-            if( t1-t0 >= duration/2 ) {
-                if( SysExit.testError == sysExit || SysExit.testExit == sysExit ) {
+            if( SysExit.testError == sysExit || SysExit.testExit == sysExit || SysExit.testEDTError == sysExit) {
+                final long ms = t1-t0;
+                if( ms >= duration/2 || ms >= 3000 ) { // max 3s wait until provoking error
                     if( SysExit.testError == sysExit ) {
                         throw new Error("test error send from test thread");
                     } else if ( SysExit.testExit == sysExit ) {
                         System.err.println("exit(0) send from test thread");
                         System.exit(0);
+                    } else if ( SysExit.testEDTError == sysExit ) {
+                        final EDTUtil edt = glWindow.getScreen().getDisplay().getEDTUtil();
+                        System.err.println("EDT invokeAndWaitError: edt type "+edt.getClass().getName());
+                        if( edt instanceof DefaultEDTUtil ) {
+                            quitAdapter.doQuit();
+                            ((DefaultEDTUtil)edt).invokeAndWaitError(new Runnable() {
+                                public void run() {
+                                    throw new RuntimeException("XXX Should never ever be seen!");
+                                }
+                            });
+                        }
                     }
                 }
             }
diff --git a/src/test/com/jogamp/opengl/test/junit/util/QuitAdapter.java b/src/test/com/jogamp/opengl/test/junit/util/QuitAdapter.java
index b5864e39c..467dd7fae 100644
--- a/src/test/com/jogamp/opengl/test/junit/util/QuitAdapter.java
+++ b/src/test/com/jogamp/opengl/test/junit/util/QuitAdapter.java
@@ -3,14 +3,14 @@
  *
  * 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
@@ -20,12 +20,12 @@
  * 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.util;
 
 import com.jogamp.newt.event.*;
@@ -35,10 +35,11 @@ public class QuitAdapter extends WindowAdapter implements WindowListener, KeyLis
     boolean enabled = true;
 
     public void enable(boolean v) { enabled = v; }
-    
+
     public void clear() { shouldQuit = false; }
-    
+
     public boolean shouldQuit() { return shouldQuit; }
+    public void doQuit() { shouldQuit=true; }
 
     public void windowDestroyNotify(WindowEvent e) {
         if( enabled ) {
@@ -50,7 +51,7 @@ public class QuitAdapter extends WindowAdapter implements WindowListener, KeyLis
     public void keyReleased(KeyEvent e) {
         if( !e.isPrintableKey() || e.isAutoRepeat() ) {
             return;
-        }            
+        }
         if( enabled ) {
             if(e.getKeyChar()=='q') {
                 System.err.println("QUIT Key "+Thread.currentThread());
-- 
cgit v1.2.3