package edu.stanford.nlp.util.concurrent; import edu.stanford.nlp.util.RuntimeInterruptedException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RunnableFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class InterruptibleMulticoreWrapper<I,O> extends MulticoreWrapper<I,O> { private final long timeout; public InterruptibleMulticoreWrapper(int numThreads, ThreadsafeProcessor<I,O> processor, boolean orderResults, long timeout) { super(numThreads, processor, orderResults); this.timeout = timeout; } @Override protected ThreadPoolExecutor buildThreadPool(int nThreads) { return new FixedNamedThreadPoolExecutor(nThreads); } @Override protected Integer getProcessor() { try { return (timeout < 0) ? idleProcessors.take() : idleProcessors.poll(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new RuntimeInterruptedException(e); } } /** * Shuts down the thread pool, returns when finished. * <br> * If <code>timeout</code> was set, then <code>join</code> waits at * most <code>timeout</code> milliseconds for threads to finish. If * any fail to finish in that time, the threadpool is shutdownNow. * After that, <code>join</code> continues to wait for the * interrupted threads to finish, so if job do not obey * interruptions, they can continue indefinitely regardless of the * timeout. * * @return a list of jobs which had never been started if * <code>timeout</code> was reached, or an empty list if that did not * happen. */ public List<I> joinWithTimeout() { if (timeout < 0) { join(); return new ArrayList<>(); } // Make blocking calls to the last processes that are running if ( ! threadPool.isShutdown()) { try { List<I> leftover = null; int i; for (i = nThreads; i > 0; --i) { if (idleProcessors.poll(timeout, TimeUnit.MILLISECONDS) == null) { leftover = shutdownNow(); break; } } // if the poll hit a timeout, retake the remaining processors // so join() can guarantee the threads are finished if (i > 0) { for ( ; i > leftover.size(); --i) { idleProcessors.take(); } return leftover; } else { threadPool.shutdown(); // Sanity check. The threadpool should be done after iterating over // the processors. threadPool.awaitTermination(10, TimeUnit.SECONDS); } } catch (InterruptedException e) { throw new RuntimeInterruptedException(e); } } return new ArrayList<>(); } /** * Calls shutdownNow on the underlying ThreadPool. In order for * this to be useful, the jobs need to look for their thread being * interrupted. The job the thread is running needs to occasionally * check Thread.interrupted() and throw an exception or otherwise * clean up. */ private List<I> shutdownNow() { List<I> orphans = new ArrayList<>(); List<Runnable> runnables = threadPool.shutdownNow(); for (Runnable runnable : runnables) { if (!(runnable instanceof NamedTask)) { throw new AssertionError("Should have gotten NamedTask"); } @SuppressWarnings("unchecked") NamedTask<I, O, ?> task = (NamedTask<I, O, ?>) runnable; orphans.add(task.item); } return orphans; } /** * After a shutdown request, await for the final termination of all * threads. Note that if the threads don't actually obey the * interruption, this may take some time. */ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { return threadPool.awaitTermination(timeout, unit); } /** * Internal class for a FutureTask which happens to know the input * it represents. Useful for if the queue is interrupted with * future jobs unallocated. Since it is always created with * CallableJob, we assume that is what it has been created with and * extract the input. */ private static class NamedTask<I, O, V> extends FutureTask<V> { final I item; NamedTask(Callable<V> c) { super(c); if (!(c instanceof CallableJob)) { throw new AssertionError("Should have gotten a CallableJob"); } @SuppressWarnings("unchecked") CallableJob<I, O> callable = (CallableJob<I, O>) c; item = callable.item; } } /** * Internal class which represents a ThreadPoolExecutor whose future * jobs know what their input was. That way, if the ThreadPool is * interrupted, it can return the jobs that were killed. * <br> * We know this will never be asked to provide new tasks for * Runnable, just for Callable, so we throw an exception if asked to * provide a new task for a Runnable. */ private static class FixedNamedThreadPoolExecutor<I, O> extends ThreadPoolExecutor { FixedNamedThreadPoolExecutor(int nThreads) { super(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); } protected <V> RunnableFuture<V> newTaskFor(Callable<V> c) { return new NamedTask<I, O, V>(c); } protected <V> RunnableFuture<V> newTaskFor(Runnable r, V v) { throw new UnsupportedOperationException(); } } }