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