/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.util; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.Lifecycle; /** * Implementation of {@link Executor} that allows jobs to run in a group with a single consumer receiving results for them. * <p> * The maximum number of additional threads is limited, but the thread which submitted jobs may temporarily join the pool to allow its tasks to complete. */ public class PoolExecutor implements Executor, Lifecycle { private static final Logger s_logger = LoggerFactory.getLogger(PoolExecutor.class); /** * Callback interface for receiving results of a pooled execution. */ public interface CompletionListener<T> { void success(T result); void failure(Throwable error); } /** * Implementation of a {@link ExecutorService} that is associated with a group. */ public class Service<T> implements Executor { private final AtomicInteger _pending = new AtomicInteger(); private final CompletionListener<T> _listener; private volatile boolean _shutdown; private boolean _joining; protected Service(final CompletionListener<T> listener) { s_logger.info("Created thread pool service {}", this); _listener = listener; } protected void decrementAndNotify() { if (_pending.decrementAndGet() == 0) { synchronized (this) { if (_joining) { notifyAll(); } } } } protected void postResult(final T result) { if ((_listener != null) && !_shutdown) { s_logger.debug("Result available from {} - {} remaining", this, _pending); _listener.success(result); } else { s_logger.debug("Discarding result from {} - {} remaining", this, _pending); } } protected void postException(final Throwable error) { if ((_listener != null) && !_shutdown) { s_logger.debug("Error available from {} - {} remaining", this, _pending); _listener.failure(error); } else { s_logger.debug("Discarding result from {} - {} remaining", this, _pending); } } /** * Submits a job for execution, posting the result when it completes. * <p> * This must not be used after {@link #shutdown} or {@link #join} have been called. * * @param command the job to execute, not null * @param result the result to post */ public void execute(final Runnable command, final T result) { _pending.incrementAndGet(); PoolExecutor.this.execute(new ExecuteRunnable<T>(this, command, result)); } /** * Submits a job for execution, posting its result when it completes. * <p> * This must not be used after {@link #shutdown} or {@link #join} have been called. * * @param command the job to execute, not null */ public void execute(final Callable<T> command) { _pending.incrementAndGet(); PoolExecutor.this.execute(new ExecuteCallable<T>(this, command)); } /** * Discards any outstanding jobs. This will return immediately; to wait for jobs to be discarded or completed, call {@link #join} afterwards. */ public synchronized void shutdown() { s_logger.info("Shutting down {}", this); if (_shutdown) { return; } _shutdown = true; if (_joining) { notifyAll(); } final Iterator<Runnable> itrQueue = getQueue().iterator(); while (itrQueue.hasNext()) { final Runnable entry = itrQueue.next(); if (entry instanceof Execute) { final Execute<?> execute = (Execute<?>) entry; if ((execute._service == this) && execute.markExecuted()) { s_logger.debug("Discarding {}", execute); _pending.decrementAndGet(); itrQueue.remove(); } } } } /** * Waits for all submitted jobs to complete. This thread may execute one or more of the submitted jobs. */ public void join() throws InterruptedException { s_logger.info("Joining"); Execute<?> inline = null; try { Iterator<Runnable> itrQueue = null; do { synchronized (this) { _joining = true; try { if (_pending.get() == 0) { s_logger.info("No pending tasks"); _shutdown = true; return; } else { if ((itrQueue == null) || !itrQueue.hasNext()) { itrQueue = getQueue().iterator(); } while (itrQueue.hasNext()) { final Runnable entry = itrQueue.next(); if (entry instanceof Execute) { final Execute<?> execute = (Execute<?>) entry; if ((execute._service == this) && execute.markExecuted()) { s_logger.debug("Inline execution of {}", execute); itrQueue.remove(); inline = execute; break; } } } if (inline == null) { s_logger.info("No inline executions available, waiting for {} remaining tasks", _pending); wait(); } } } finally { _joining = false; } } if (inline != null) { inline.runImpl(); inline = null; } } while (true); } finally { if (inline != null) { getQueue().add(inline); } } } // Executor /** * Submit a job for execution to the group. This is the same as calling {@link #execute(Runnable,Object)}. * <p> * This must not be used after {@link #shutdown} or {@link #join} have been called. * * @param command the job to execute, not null */ @Override public void execute(final Runnable command) { execute(command, null); } // Object @Override public String toString() { return Integer.toHexString(hashCode()); } } private abstract static class Execute<T> implements Runnable { private final Service<T> _service; private final AtomicBoolean _executed = new AtomicBoolean(); protected Execute(final Service<T> service) { _service = service; } public boolean markExecuted() { return !_executed.getAndSet(true); } protected abstract T callImpl() throws Throwable; protected void runImpl() { try { s_logger.debug("Executing {}", this); _service.postResult(callImpl()); } catch (Throwable t) { _service.postException(t); } finally { _service.decrementAndNotify(); } } @Override public void run() { if (_service._shutdown) { return; } if (markExecuted()) { runImpl(); } else { s_logger.debug("Already executed or cancelled {}", this); } } @Override public String toString() { return _service.toString(); } } private static final class ExecuteRunnable<T> extends Execute<T> { private final Runnable _runnable; private final T _result; public ExecuteRunnable(final Service<T> service, final Runnable runnable, final T result) { super(service); ArgumentChecker.notNull(runnable, "runnable"); _runnable = runnable; _result = result; } @Override protected T callImpl() { _runnable.run(); return _result; } @Override public String toString() { return super.toString() + "/" + _runnable; } } private static final class ExecuteCallable<T> extends Execute<T> { private final Callable<T> _callable; public ExecuteCallable(final Service<T> service, final Callable<T> callable) { super(service); ArgumentChecker.notNull(callable, "callable"); _callable = callable; } @Override protected T callImpl() throws Throwable { return _callable.call(); } @Override public String toString() { return super.toString() + "/" + _callable; } } private static final ThreadLocal<Reference<PoolExecutor>> s_instance = new ThreadLocal<Reference<PoolExecutor>>(); private final Reference<PoolExecutor> _me = new WeakReference<PoolExecutor>(this); private final BlockingQueue<Runnable> _queue = new LinkedBlockingQueue<Runnable>(); private final ThreadPoolExecutor _underlying; private static final class ExecutorThread extends Thread { private final Reference<PoolExecutor> _owner; private ExecutorThread(final Reference<PoolExecutor> owner, final ThreadGroup group, final Runnable runnable, final String threadName, final int stackSize) { super(group, runnable, threadName, stackSize); _owner = owner; } @Override public void run() { s_instance.set(_owner); super.run(); } } private static final class ExecutorThreadFactory extends NamedThreadPoolFactory { private final Reference<PoolExecutor> _owner; private ExecutorThreadFactory(final Reference<PoolExecutor> owner, final String name) { super(name, true); _owner = owner; } @Override protected Thread createThread(final ThreadGroup group, final Runnable runnable, final String threadName, final int stackSize) { return new ExecutorThread(_owner, group, runnable, threadName, stackSize); } } /** * Creates a new execution pool with the given (maximum) number of threads. * <p> * This can be created with no threads. Tasks submitted will never be executed unless they arrive from a pool and another thread then joins that pool to complete its execution. * * @param maxThreads the maximum number of threads to put in the pool * @param name the diagnostic name to use for the pool */ public PoolExecutor(final int maxThreads, final String name) { if (maxThreads > 0) { ThreadFactory factory = new ExecutorThreadFactory(_me, name); _underlying = new MdcAwareThreadPoolExecutor(maxThreads, maxThreads, 60, TimeUnit.SECONDS, _queue, factory); _underlying.allowCoreThreadTimeOut(true); } else { _underlying = null; } } @Override protected void finalize() { if (_underlying != null) { _underlying.shutdown(); } } protected BlockingQueue<Runnable> getQueue() { return _queue; } /** * Creates a service group with a listener to handle results from that group. * * @param <T> the result type for jobs submitted to the group * @param listener the listener to receive results from jobs in the group, or null if the results are not wanted * @return the service group to submit further jobs to */ public <T> Service<T> createService(CompletionListener<T> listener) { return new Service<T>(listener); } public ExecutorService asService() { return _underlying; } /** * Registers an instance with the current thread, returning the previously registered instance (if any). * * @param instance the instance to register, or null for none * @return the previously registered instance, or null for none */ public static PoolExecutor setInstance(final PoolExecutor instance) { Reference<PoolExecutor> previous = s_instance.get(); if (instance != null) { s_instance.set(instance._me); } else { s_instance.set(null); } if (previous != null) { return previous.get(); } else { return null; } } /** * Returns the instance registered with the current thread, if any. * * @return the registered instance, or null for none */ public static PoolExecutor instance() { Reference<PoolExecutor> executor = s_instance.get(); if (executor != null) { return executor.get(); } else { return null; } } // Executor /** * Submits a job to the underlying execution pool. * * @param command the job to execute, not null */ @Override public void execute(final Runnable command) { s_logger.debug("Submitting {}", command); if (_underlying != null) { _underlying.execute(command); } else { getQueue().add(command); } } // Lifecycle /** * Dummy {@link Lifecycle#start} method; this object is implicitly started at construction and it is not possible to restart it after a {@link #stop} request. */ @Override public void start() { if (!isRunning()) { throw new IllegalStateException("Can't restart service after explicit stop"); } } @Override public void stop() { _me.clear(); if (_underlying != null) { _underlying.shutdown(); } } @Override public boolean isRunning() { return _me.get() != null; } }