/* * Copyright (c) 2009 Sun Microsystems, Inc. All Rights Reserved. * Copyright (c) 2010 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: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution 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. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use * in the design, construction, operation or maintenance of any nuclear * facility. */ package jogamp.newt; import java.util.ArrayList; import com.jogamp.nativewindow.NativeWindowException; import jogamp.common.util.locks.LockDebugUtil; import com.jogamp.common.ExceptionUtils; import com.jogamp.common.util.InterruptSource; import com.jogamp.common.util.InterruptedRuntimeException; import com.jogamp.common.util.RunnableTask; import com.jogamp.common.util.locks.Lock; 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; private final Runnable dispatchMessages; private NEDT edt = null; private int start_iter=0; private static long pollPeriod = EDTUtil.defaultEDTPollPeriod; public DefaultEDTUtil(final ThreadGroup tg, final String name, final Runnable dispatchMessages) { this.threadGroup = tg; this.name=Thread.currentThread().getName()+"-"+name+"-EDT-"; this.dispatchMessages=dispatchMessages; this.edt = new NEDT(threadGroup, this.name); this.edt.setDaemon(true); // don't stop JVM from shutdown .. } @Override final public long getPollPeriod() { return pollPeriod; } @Override final public void setPollPeriod(final long ms) { pollPeriod = ms; // writing to static field is intended } @Override public final void start() throws IllegalStateException { synchronized(edtLock) { if( edt.isRunning() ) { throw new IllegalStateException("EDT still running and not subject to stop. Curr "+Thread.currentThread().getName()+", EDT "+edt.getName()+", isRunning "+edt.isRunning+", shouldStop "+edt.shouldStop); } if(DEBUG) { if(edt.tasks.size()>0) { System.err.println(Thread.currentThread()+": Default-EDT reset, remaining tasks: "+edt.tasks.size()+" - "+edt); } System.err.println(Thread.currentThread()+": Default-EDT reset - edt: "+edt); } if( edt.getState() != Thread.State.NEW ) { if( null != threadGroup && threadGroup.isDestroyed() ) { // best thing we can do is to use this thread's TG threadGroup = Thread.currentThread().getThreadGroup(); } edt = new NEDT(threadGroup, name); edt.setDaemon(true); // don't stop JVM from shutdown .. } startImpl(); } if( !edt.isRunning() ) { throw new RuntimeException("EDT could not be started: "+edt); } } private final void startImpl() { if(edt.isAlive()) { throw new RuntimeException("Default-EDT Thread.isAlive(): true, isRunning: "+edt.isRunning+", shouldStop "+edt.shouldStop+", edt: "+edt+", tasks: "+edt.tasks.size()); } start_iter++; edt.setName(name+start_iter); if(DEBUG) { System.err.println(Thread.currentThread()+": Default-EDT START - edt: "+edt); } edt.start(); } @Override public final boolean isCurrentThreadEDT() { return edt == Thread.currentThread(); // EDT == NEDT } @Override public final boolean isCurrentThreadNEDT() { return edt == Thread.currentThread(); // EDT == NEDT } @Override public final boolean isCurrentThreadEDTorNEDT() { return edt == Thread.currentThread(); // EDT == NEDT } @Override public final boolean isRunning() { return edt.isRunning() ; } @Override public final boolean invokeStop(final boolean wait, final Runnable task) { if(DEBUG) { System.err.println(Thread.currentThread()+": Default-EDT.invokeStop wait "+wait); ExceptionUtils.dumpStack(System.err); } return invokeImpl(wait, task, true /* stop */, false /* provokeError */); } public final boolean invokeAndWaitError(final Runnable task) { if(DEBUG) { System.err.println(Thread.currentThread()+": Default-EDT.invokeAndWaitError"); ExceptionUtils.dumpStack(System.err); } return invokeImpl(true /* wait */, task, false /* stop */, true /* provokeError */); } @Override public final boolean invoke(final boolean wait, final Runnable task) { return invokeImpl(wait, task, false /* stop */, false /* provokeError */); } private static Runnable nullTask = new Runnable() { @Override public void run() { } }; private final boolean invokeImpl(boolean wait, Runnable task, final boolean stop, final boolean provokeError) { final RunnableTask rTask; final Object rTaskLock = new Object(); synchronized(rTaskLock) { // lock the optional task execution synchronized(edtLock) { // lock the EDT status if( edt.shouldStop ) { // drop task .. System.err.println(Thread.currentThread()+": Warning: Default-EDT about (1) to stop, won't enqueue new task: "+edt); if(DEBUG) { ExceptionUtils.dumpStack(System.err); } return false; } if( isCurrentThreadEDT() ) { if(null != task) { task.run(); } wait = false; // running in same thread (EDT) -> no wait rTask = null; if( stop ) { edt.shouldStop = true; if( edt.tasks.size()>0 ) { System.err.println(Thread.currentThread()+": Warning: Default-EDT about (2) to stop, task executed. Remaining tasks: "+edt.tasks.size()+" - "+edt); if(DEBUG) { ExceptionUtils.dumpStack(System.err); } } } } else { if( !edt.isRunning ) { if( null != task ) { if( stop ) { System.err.println(Thread.currentThread()+": Warning: Default-EDT is about (3) to stop and stopped already, dropping task. Remaining tasks: "+edt.tasks.size()+" - "+edt); } else { System.err.println(Thread.currentThread()+": Warning: Default-EDT is not running, dropping task. NEDT "+edt); } if(DEBUG) { ExceptionUtils.dumpStack(System.err); } } return false; } else if( stop && null == task ) { task = nullTask; // ensures execution triggering stop } if(null != task) { synchronized(edt.tasks) { rTask = new RunnableTask(task, wait ? rTaskLock : null, true /* always catch and report Exceptions, don't disturb EDT */, wait ? null : System.err); if(stop) { 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); edt.tasks.notifyAll(); } } else { wait = false; rTask = null; } } } if( wait ) { try { while( rTask.isInQueue() ) { rTaskLock.wait(); // free lock, allow execution of rTask } } catch (final InterruptedException ie) { throw new InterruptedRuntimeException(ie); } final Throwable throwable = rTask.getThrowable(); if(null!=throwable) { if(throwable instanceof NativeWindowException) { throw (NativeWindowException)throwable; } throw new RuntimeException(throwable); } } if(DEBUG) { if( stop) { System.err.println(Thread.currentThread()+": Default-EDT signal STOP X edt: "+edt); } } return true; } } @Override final public boolean waitUntilIdle() { final NEDT _edt; synchronized(edtLock) { _edt = edt; } if(!_edt.isRunning || _edt == Thread.currentThread()) { return false; } synchronized(_edt.tasks) { try { while(_edt.isRunning && _edt.tasks.size()>0) { _edt.tasks.notifyAll(); _edt.tasks.wait(); } } catch (final InterruptedException e) { throw new InterruptedRuntimeException(e); } return true; } } @Override final public boolean waitUntilStopped() { synchronized(edtLock) { if(edt.isRunning && edt != Thread.currentThread() ) { try { while( edt.isRunning ) { edtLock.wait(); } } catch (final InterruptedException e) { throw new InterruptedRuntimeException(e); } return true; } else { return false; } } } class NEDT extends InterruptSource.Thread { volatile boolean shouldStop = false; volatile boolean isRunning = false; final ArrayList<RunnableTask> tasks = new ArrayList<RunnableTask>(); // one shot tasks public NEDT(final ThreadGroup tg, final String name) { super(tg, null, name); } final public boolean isRunning() { return isRunning && !shouldStop; } @Override final public void start() throws IllegalThreadStateException { isRunning = true; super.start(); } private final void validateNoRecursiveLocksHold() { if(LockDebugUtil.getRecursiveLockTrace().size()>0) { LockDebugUtil.dumpRecursiveLockTrace(System.err); throw new InternalError("XXX"); } } /** * Utilizing locking only on tasks and its execution, * not for event dispatching. */ @Override final public void run() { if(DEBUG) { System.err.println(getName()+": Default-EDT run() START "+ getName()); } if(Lock.DEBUG) { validateNoRecursiveLocksHold(); } RuntimeException error = null; try { do { // event dispatch if(!shouldStop) { dispatchMessages.run(); } // wait and work on tasks RunnableTask task = null; synchronized(tasks) { // wait for tasks if( !shouldStop && tasks.size()==0 ) { try { tasks.wait(pollPeriod); } catch (final InterruptedException e) { throw new InterruptedRuntimeException(e); } } // execute one task, if available if(tasks.size()>0) { task = tasks.remove(0); tasks.notifyAll(); 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"); } } } if(null!=task) { task.run(); if(Lock.DEBUG) { validateNoRecursiveLocksHold(); } if(!task.hasWaiter() && null != task.getThrowable()) { // at least dump stack-trace in case nobody waits for result System.err.println("DefaultEDT.run(): Caught exception occured on thread "+java.lang.Thread.currentThread().getName()+": "+task.toString()); task.getThrowable().printStackTrace(); } } } while(!shouldStop) ; } catch (final Throwable t) { // handle errors .. shouldStop = true; if(t instanceof RuntimeException) { error = (RuntimeException) t; } else { error = new RuntimeException("Within Default-EDT", t); } } finally { final String msg = getName()+": Default-EDT finished w/ "+tasks.size()+" left"; if(DEBUG) { 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(msg+" EXIT, exception: "+error); } if(null!=error) { throw error; } } // finally } // run() } // EventDispatchThread }