1 /*
2  * Copyright (c) 2009 Sun Microsystems, Inc. All Rights Reserved.
3  * Copyright (c) 2010 JogAmp Community. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * - Redistribution of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * - Redistribution in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of Sun Microsystems, Inc. or the names of
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * This software is provided "AS IS," without a warranty of any kind. ALL
21  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
22  * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
23  * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
24  * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
25  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
26  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
27  * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
28  * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
29  * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
30  * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
31  * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
32  *
33  * You acknowledge that this software is not designed or intended for use
34  * in the design, construction, operation or maintenance of any nuclear
35  * facility.
36  */
37 
38 package jogamp.newt;
39 
40 import java.util.ArrayList;
41 
42 import com.jogamp.nativewindow.NativeWindowException;
43 
44 import jogamp.common.util.locks.LockDebugUtil;
45 
46 import com.jogamp.common.ExceptionUtils;
47 import com.jogamp.common.util.InterruptSource;
48 import com.jogamp.common.util.InterruptedRuntimeException;
49 import com.jogamp.common.util.RunnableTask;
50 import com.jogamp.common.util.locks.Lock;
51 import com.jogamp.newt.util.EDTUtil;
52 
53 public class DefaultEDTUtil implements EDTUtil {
54     public static final boolean DEBUG = Debug.debug("EDT");
55 
56     /** Used to implement {@link #invokeStop(boolean, Runnable)}. */
57     private static final Object TASK_ATTACHMENT_STOP = new Object();
58     /** Used to provoke an exception on the EDT while waiting / blocking. Merely exists to test code.*/
59     private static final Object TASK_ATTACHMENT_TEST_ERROR = new Object();
60 
61     private final Object edtLock = new Object(); // locking the EDT start/stop state
62     private /* final */ ThreadGroup threadGroup;
63     private final String name;
64     private final Runnable dispatchMessages;
65     private NEDT edt = null;
66     private int start_iter=0;
67     private static long pollPeriod = EDTUtil.defaultEDTPollPeriod;
68 
DefaultEDTUtil(final ThreadGroup tg, final String name, final Runnable dispatchMessages)69     public DefaultEDTUtil(final ThreadGroup tg, final String name, final Runnable dispatchMessages) {
70         this.threadGroup = tg;
71         this.name=Thread.currentThread().getName()+"-"+name+"-EDT-";
72         this.dispatchMessages=dispatchMessages;
73         this.edt = new NEDT(threadGroup, this.name);
74         this.edt.setDaemon(true); // don't stop JVM from shutdown ..
75     }
76 
77     @Override
getPollPeriod()78     final public long getPollPeriod() {
79         return pollPeriod;
80     }
81 
82     @Override
setPollPeriod(final long ms)83     final public void setPollPeriod(final long ms) {
84         pollPeriod = ms; // writing to static field is intended
85     }
86 
87     @Override
start()88     public final void start() throws IllegalStateException {
89         synchronized(edtLock) {
90             if( edt.isRunning() ) {
91                 throw new IllegalStateException("EDT still running and not subject to stop. Curr "+Thread.currentThread().getName()+", EDT "+edt.getName()+", isRunning "+edt.isRunning+", shouldStop "+edt.shouldStop);
92             }
93             if(DEBUG) {
94                 if(edt.tasks.size()>0) {
95                     System.err.println(Thread.currentThread()+": Default-EDT reset, remaining tasks: "+edt.tasks.size()+" - "+edt);
96                 }
97                 System.err.println(Thread.currentThread()+": Default-EDT reset - edt: "+edt);
98             }
99             if( edt.getState() != Thread.State.NEW ) {
100                 if( null != threadGroup && threadGroup.isDestroyed() ) {
101                     // best thing we can do is to use this thread's TG
102                     threadGroup = Thread.currentThread().getThreadGroup();
103                 }
104                 edt = new NEDT(threadGroup, name);
105                 edt.setDaemon(true); // don't stop JVM from shutdown ..
106             }
107             startImpl();
108         }
109         if( !edt.isRunning() ) {
110             throw new RuntimeException("EDT could not be started: "+edt);
111         }
112     }
113 
startImpl()114     private final void startImpl() {
115         if(edt.isAlive()) {
116             throw new RuntimeException("Default-EDT Thread.isAlive(): true, isRunning: "+edt.isRunning+", shouldStop "+edt.shouldStop+", edt: "+edt+", tasks: "+edt.tasks.size());
117         }
118         start_iter++;
119         edt.setName(name+start_iter);
120         if(DEBUG) {
121             System.err.println(Thread.currentThread()+": Default-EDT START - edt: "+edt);
122         }
123         edt.start();
124     }
125 
126     @Override
isCurrentThreadEDT()127     public final boolean isCurrentThreadEDT() {
128         return edt == Thread.currentThread(); // EDT == NEDT
129     }
130 
131     @Override
isCurrentThreadNEDT()132     public final boolean isCurrentThreadNEDT() {
133         return edt == Thread.currentThread(); // EDT == NEDT
134     }
135 
136     @Override
isCurrentThreadEDTorNEDT()137     public final boolean isCurrentThreadEDTorNEDT() {
138         return edt == Thread.currentThread(); // EDT == NEDT
139     }
140 
141     @Override
isRunning()142     public final boolean isRunning() {
143         return edt.isRunning() ;
144     }
145 
146     @Override
invokeStop(final boolean wait, final Runnable task)147     public final boolean invokeStop(final boolean wait, final Runnable task) {
148         if(DEBUG) {
149             System.err.println(Thread.currentThread()+": Default-EDT.invokeStop wait "+wait);
150             ExceptionUtils.dumpStack(System.err);
151         }
152         return invokeImpl(wait, task, true /* stop */, false /* provokeError */);
153     }
154 
invokeAndWaitError(final Runnable task)155     public final boolean invokeAndWaitError(final Runnable task) {
156         if(DEBUG) {
157             System.err.println(Thread.currentThread()+": Default-EDT.invokeAndWaitError");
158             ExceptionUtils.dumpStack(System.err);
159         }
160         return invokeImpl(true /* wait */, task, false /* stop */, true /* provokeError */);
161     }
162 
163     @Override
invoke(final boolean wait, final Runnable task)164     public final boolean invoke(final boolean wait, final Runnable task) {
165         return invokeImpl(wait, task, false /* stop */, false /* provokeError */);
166     }
167 
168     private static Runnable nullTask = new Runnable() {
169         @Override
170         public void run() { }
171     };
172 
invokeImpl(boolean wait, Runnable task, final boolean stop, final boolean provokeError)173     private final boolean invokeImpl(boolean wait, Runnable task, final boolean stop, final boolean provokeError) {
174         final RunnableTask rTask;
175         final Object rTaskLock = new Object();
176         synchronized(rTaskLock) { // lock the optional task execution
177             synchronized(edtLock) { // lock the EDT status
178                 if( edt.shouldStop ) {
179                     // drop task ..
180                     System.err.println(Thread.currentThread()+": Warning: Default-EDT about (1) to stop, won't enqueue new task: "+edt);
181                     if(DEBUG) {
182                         ExceptionUtils.dumpStack(System.err);
183                     }
184                     return false;
185                 }
186                 if( isCurrentThreadEDT() ) {
187                     if(null != task) {
188                         task.run();
189                     }
190                     wait = false; // running in same thread (EDT) -> no wait
191                     rTask = null;
192                     if( stop ) {
193                         edt.shouldStop = true;
194                         if( edt.tasks.size()>0 ) {
195                             System.err.println(Thread.currentThread()+": Warning: Default-EDT about (2) to stop, task executed. Remaining tasks: "+edt.tasks.size()+" - "+edt);
196                             if(DEBUG) {
197                                 ExceptionUtils.dumpStack(System.err);
198                             }
199                         }
200                     }
201                 } else {
202                     if( !edt.isRunning ) {
203                         if( null != task ) {
204                             if( stop ) {
205                                 System.err.println(Thread.currentThread()+": Warning: Default-EDT is about (3) to stop and stopped already, dropping task. Remaining tasks: "+edt.tasks.size()+" - "+edt);
206                             } else {
207                                 System.err.println(Thread.currentThread()+": Warning: Default-EDT is not running, dropping task. NEDT "+edt);
208                             }
209                             if(DEBUG) {
210                                 ExceptionUtils.dumpStack(System.err);
211                             }
212                         }
213                         return false;
214                     } else if( stop && null == task ) {
215                         task = nullTask; // ensures execution triggering stop
216                     }
217 
218                     if(null != task) {
219                         synchronized(edt.tasks) {
220                             rTask = new RunnableTask(task,
221                                                      wait ? rTaskLock : null,
222                                                      true /* always catch and report Exceptions, don't disturb EDT */,
223                                                      wait ? null : System.err);
224                             if(stop) {
225                                 rTask.setAttachment(TASK_ATTACHMENT_STOP); // mark final task, will imply shouldStop:=true
226                             } else if(provokeError) {
227                                 rTask.setAttachment(TASK_ATTACHMENT_TEST_ERROR);
228                             }
229                             // append task ..
230                             edt.tasks.add(rTask);
231                             edt.tasks.notifyAll();
232                         }
233                     } else {
234                         wait = false;
235                         rTask = null;
236                     }
237                 }
238             }
239             if( wait ) {
240                 try {
241                     while( rTask.isInQueue() ) {
242                         rTaskLock.wait(); // free lock, allow execution of rTask
243                     }
244                 } catch (final InterruptedException ie) {
245                     throw new InterruptedRuntimeException(ie);
246                 }
247                 final Throwable throwable = rTask.getThrowable();
248                 if(null!=throwable) {
249                     if(throwable instanceof NativeWindowException) {
250                         throw (NativeWindowException)throwable;
251                     }
252                     throw new RuntimeException(throwable);
253                 }
254             }
255             if(DEBUG) {
256                 if( stop) {
257                     System.err.println(Thread.currentThread()+": Default-EDT signal STOP X edt: "+edt);
258                 }
259             }
260             return true;
261         }
262     }
263 
264     @Override
waitUntilIdle()265     final public boolean waitUntilIdle() {
266         final NEDT _edt;
267         synchronized(edtLock) {
268             _edt = edt;
269         }
270         if(!_edt.isRunning || _edt == Thread.currentThread()) {
271             return false;
272         }
273         synchronized(_edt.tasks) {
274             try {
275                 while(_edt.isRunning && _edt.tasks.size()>0) {
276                     _edt.tasks.notifyAll();
277                     _edt.tasks.wait();
278                 }
279             } catch (final InterruptedException e) {
280                 throw new InterruptedRuntimeException(e);
281             }
282             return true;
283         }
284     }
285 
286     @Override
waitUntilStopped()287     final public boolean waitUntilStopped() {
288         synchronized(edtLock) {
289             if(edt.isRunning && edt != Thread.currentThread() ) {
290                 try {
291                     while( edt.isRunning ) {
292                         edtLock.wait();
293                     }
294                 } catch (final InterruptedException e) {
295                     throw new InterruptedRuntimeException(e);
296                 }
297                 return true;
298             } else {
299                 return false;
300             }
301         }
302     }
303 
304     class NEDT extends InterruptSource.Thread {
305         volatile boolean shouldStop = false;
306         volatile boolean isRunning = false;
307         final ArrayList<RunnableTask> tasks = new ArrayList<RunnableTask>(); // one shot tasks
308 
NEDT(final ThreadGroup tg, final String name)309         public NEDT(final ThreadGroup tg, final String name) {
310             super(tg, null, name);
311         }
312 
isRunning()313         final public boolean isRunning() {
314             return isRunning && !shouldStop;
315         }
316 
317         @Override
start()318         final public void start() throws IllegalThreadStateException {
319             isRunning = true;
320             super.start();
321         }
322 
validateNoRecursiveLocksHold()323         private final void validateNoRecursiveLocksHold() {
324             if(LockDebugUtil.getRecursiveLockTrace().size()>0) {
325                 LockDebugUtil.dumpRecursiveLockTrace(System.err);
326                 throw new InternalError("XXX");
327             }
328         }
329 
330         /**
331          * Utilizing locking only on tasks and its execution,
332          * not for event dispatching.
333          */
334         @Override
run()335         final public void run() {
336             if(DEBUG) {
337                 System.err.println(getName()+": Default-EDT run() START "+ getName());
338             }
339             if(Lock.DEBUG) {
340                 validateNoRecursiveLocksHold();
341             }
342             RuntimeException error = null;
343             try {
344                 do {
345                     // event dispatch
346                     if(!shouldStop) {
347                         dispatchMessages.run();
348                     }
349                     // wait and work on tasks
350                     RunnableTask task = null;
351                     synchronized(tasks) {
352                         // wait for tasks
353                         if( !shouldStop && tasks.size()==0 ) {
354                             try {
355                                 tasks.wait(pollPeriod);
356                             } catch (final InterruptedException e) {
357                                 throw new InterruptedRuntimeException(e);
358                             }
359                         }
360                         // execute one task, if available
361                         if(tasks.size()>0) {
362                             task = tasks.remove(0);
363                             tasks.notifyAll();
364                             final Object attachment = task.getAttachment();
365                             if( TASK_ATTACHMENT_STOP == attachment ) {
366                                 shouldStop = true;
367                             } else if( TASK_ATTACHMENT_TEST_ERROR == attachment ) {
368                                 tasks.add(0, task);
369                                 task = null;
370                                 throw new RuntimeException("TASK_ATTACHMENT_TEST_ERROR");
371                             }
372                         }
373                     }
374                     if(null!=task) {
375                         task.run();
376                         if(Lock.DEBUG) {
377                             validateNoRecursiveLocksHold();
378                         }
379                         if(!task.hasWaiter() && null != task.getThrowable()) {
380                             // at least dump stack-trace in case nobody waits for result
381                             System.err.println("DefaultEDT.run(): Caught exception occured on thread "+java.lang.Thread.currentThread().getName()+": "+task.toString());
382                             task.getThrowable().printStackTrace();
383                         }
384                     }
385                 } while(!shouldStop) ;
386             } catch (final Throwable t) {
387                 // handle errors ..
388                 shouldStop = true;
389                 if(t instanceof RuntimeException) {
390                     error = (RuntimeException) t;
391                 } else {
392                     error = new RuntimeException("Within Default-EDT", t);
393                 }
394             } finally {
395                 final String msg = getName()+": Default-EDT finished w/ "+tasks.size()+" left";
396                 if(DEBUG) {
397                     System.err.println(msg+", "+error);
398                 }
399                 synchronized(edtLock) {
400                     int i = 0;
401                     while( tasks.size() > 0 ) {
402                         // notify all waiter
403                         final String msg2 = msg+", task #"+i;
404                         final Throwable t = null != error ? new Throwable(msg2, error) : new Throwable(msg2);
405                         final RunnableTask rt = tasks.remove(0);
406                         if( null != rt ) {
407                             rt.flush(t);
408                             i++;
409                         }
410                     }
411                     isRunning = false;
412                     edtLock.notifyAll();
413                 }
414                 if(DEBUG) {
415                     System.err.println(msg+" EXIT, exception: "+error);
416                 }
417                 if(null!=error) {
418                     throw error;
419                 }
420             } // finally
421         } // run()
422     } // EventDispatchThread
423 }
424 
425