package lsr.common; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * Adds debugging functionality to the standard * {@link ScheduledThreadPoolExecutor}. The additional debugging support * consists of naming the thread used by the executor and checking if the * current thread executing is the executor thread. It also limits the number of * threads on the pool to one. * * @author Nuno Santos (LSR) */ public class SingleThreadDispatcher extends ScheduledThreadPoolExecutor { private final NamedThreadFactory ntf; private final String threadName; // private final CountDownLatch latch = new CountDownLatch(1); /** * Thread factory that names the thread and keeps a reference to the last * thread created. Intended for debugging. * * @author Nuno Santos (LSR) */ private final static class NamedThreadFactory implements ThreadFactory { final String name; private Thread lastCreatedThread = null; public NamedThreadFactory(String name) { this.name = name; } public Thread newThread(Runnable r) { assert lastCreatedThread == null; // Name the thread and save a reference to it for debugging lastCreatedThread = new Thread(r, name); lastCreatedThread.setUncaughtExceptionHandler(new KillOnExceptionHandler()); return lastCreatedThread; } } public SingleThreadDispatcher(String threadName) { super(1, new NamedThreadFactory(threadName)); this.threadName = threadName; ntf = (NamedThreadFactory) getThreadFactory(); setRejectedExecutionHandler(new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { throw new RuntimeException("" + r + " " + executor); } }); } /** * Checks whether current thread is the same as the thread associated with * this dispatcher. * * @return true if the current and dispatcher threads are equals, false * otherwise */ public boolean amIInDispatcher() { return Thread.currentThread() == ntf.lastCreatedThread; } public void checkInDispatcher() { assert amIInDispatcher() : "Wrong thread: " + Thread.currentThread().getName(); } /** * If the current thread is the dispatcher thread, executes the task * directly, otherwise hands it over to the dispatcher thread and wait for * the task to be finished. * * @param task - the task to execute */ public void executeAndWait(Runnable task) { if (amIInDispatcher()) { task.run(); } else { Future<?> future = submit(task); // Wait until the task is executed try { future.get(); } catch (Exception e) { throw new RuntimeException(e); } } } /** * Handles exceptions thrown by the executed tasks. Kills the process on * exception as tasks shouldn't throw exceptions under normal conditions. */ protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); /* * If the task is wrapped on a Future, any exception will be stored on * the Future and t will be null */ if (t == null && r instanceof FutureTask<?>) { // FutureTasks may be scheduled for repeated execution. In that // case, the task may be scheduled for further re-execution, and // thus there is no result to return. // The get() method would block in this case. FutureTask<?> fTask = (FutureTask<?>) r; if (!fTask.isDone()) { // Since the task is still scheduled for execution, it did not // throw any exception. return; } else { // Either a repeated task that is done, or a single shot task. try { fTask.get(0, TimeUnit.MILLISECONDS); } catch (CancellationException ce) { // TODO: (JK) can it be bad if cancel is called upon a task? // logger_.info("Task was cancelled: " + r); } catch (ExecutionException ee) { t = ee.getCause(); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); // ignore/reset } catch (TimeoutException e) { // The task should already be finished, so get() should // return immediately. // However, if the code executed by this task calls // cancel(true) on the Runnable, the get() may block. // Therefore, if it throws a TimeoutException, check the // implementation of the task to see if it is calling // cancel(). throw new RuntimeException("Timeout retrieving exception object. Task: " + r + " Thread: " + threadName, e); } } } if (t != null) { // It is a severe error, print it to the console as well as to the // log. t.printStackTrace(); throw new RuntimeException(t); } } public void start() { } }