/* Copyright (c) 2011 Danish Maritime Authority * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see <http://www.gnu.org/licenses/>. */ package dk.dma.ais.concurrency.stripedexecutor; import eu.javaspecialists.tjsn.concurrency.StripedObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.AbstractExecutorService; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RunnableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * The StripedExecutorService accepts Runnable/Callable objects * that also implement the StripedObject interface. It executes * all the tasks for a single "stripe" consecutively. * <p/> * In this version, submitted tasks do not necessarily have to * implement the StripedObject interface. If they do not, then * they will simply be passed onto the wrapped ExecutorService * directly. * <p/> * Idea inspired by Glenn McGregor on the Concurrency-interest * mailing list and using the SerialExecutor presented in the * Executor interface's JavaDocs. * <p/> * http://cs.oswego.edu/mailman/listinfo/concurrency-interest * * @author Dr Heinz M. Kabutz * * @see "http://www.javaspecialists.eu/archive/Issue206.html" * * Implementation adapted to use by DMA. */ public class StripedExecutorService extends AbstractExecutorService { static final Logger LOG = LoggerFactory.getLogger(StripedExecutorService.class); /** * The wrapped ExecutorService that will actually execute our * tasks. */ private final ExecutorService executor; /** * The lock prevents shutdown from being called in the middle * of a submit. It also guards the executors IdentityHashMap. */ private final ReentrantLock lock = new ReentrantLock(); /** * This condition allows us to cleanly terminate this executor * service. */ private final Condition terminating = lock.newCondition(); /** * Whenever a new StripedObject is submitted to the pool, it * is added to this IdentityHashMap. As soon as the * SerialExecutor is empty, the entry is removed from the map, * in order to avoid a memory leak. */ private final Map<Object, SerialExecutor> executors = new IdentityHashMap<>(); /** * The default submit() method creates a new FutureTask and * wraps our StripedRunnable with it. We thus need to * remember the stripe object somewhere. In our case, we will * do this inside the ThreadLocal "stripes". Before the * thread returns from submitting the runnable, it will always * remove the thread local entry. */ private static final ThreadLocal<Object> STRIPES = new ThreadLocal<>(); /** * Valid states are RUNNING and SHUTDOWN. We rely on the * underlying executor service for the remaining states. */ private State state = State.RUNNING; private static enum State { RUNNING, SHUTDOWN } /** * The constructor taking executors is private, since we do * not want users to shutdown their executors directly, * otherwise jobs might get stuck in our queues. * * @param executor the executor service that we use to execute * the tasks */ private StripedExecutorService(ExecutorService executor) { this.executor = executor; } /** * This constructs a StripedExecutorService that wraps a * cached thread pool. */ public StripedExecutorService() { this(Executors.newCachedThreadPool()); } /** * This constructs a StripedExecutorService that wraps a fixed * thread pool with the given number of threads. */ public StripedExecutorService(int numberOfThreads) { this(Executors.newFixedThreadPool(numberOfThreads)); } /** * If the runnable also implements StripedObject, we store the * stripe object in a thread local, since the actual runnable * will be wrapped with a FutureTask. */ protected <T> RunnableFuture<T> newTaskFor( Runnable runnable, T value) { saveStripedObject(runnable); return super.newTaskFor(runnable, value); } /** * If the callable also implements StripedObject, we store the * stripe object in a thread local, since the actual callable * will be wrapped with a FutureTask. */ protected <T> RunnableFuture<T> newTaskFor( Callable<T> callable) { saveStripedObject(callable); return super.newTaskFor(callable); } /** * Saves the stripe in a ThreadLocal until we can use it to * schedule the task into our pool. */ private void saveStripedObject(Object task) { if (isStripedObject(task)) { STRIPES.set(((StripedObject) task).getStripe()); } } /** * Returns true if the object implements the StripedObject * interface. */ private static boolean isStripedObject(Object o) { return o instanceof StripedObject; } /** * Delegates the call to submit(task, null). */ public Future<?> submit(Runnable task) { return submit(task, null); } /** * If the task is a StripedObject, we execute it in-order by * its stripe, otherwise we submit it directly to the wrapped * executor. If the pool is not running, we throw a * RejectedExecutionException. */ public <T> Future<T> submit(Runnable task, T result) { lock.lock(); try { checkPoolIsRunning(); if (isStripedObject(task)) { return super.submit(task, result); } else { // bypass the serial executors return executor.submit(task, result); } } finally { lock.unlock(); } } /** * If the task is a StripedObject, we execute it in-order by * its stripe, otherwise we submit it directly to the wrapped * executor. If the pool is not running, we throw a * RejectedExecutionException. */ public <T> Future<T> submit(Callable<T> task) { lock.lock(); try { checkPoolIsRunning(); if (isStripedObject(task)) { return super.submit(task); } else { // bypass the serial executors return executor.submit(task); } } finally { lock.unlock(); } } /** * Throws a RejectedExecutionException if the state is not * RUNNING. */ private void checkPoolIsRunning() { assert lock.isHeldByCurrentThread(); if (state != State.RUNNING) { throw new RejectedExecutionException( "executor not running"); } } /** * Executes the command. If command implements StripedObject, * we execute it with a SerialExecutor. This method can be * called directly by clients or it may be called by the * AbstractExecutorService's submit() methods. In that case, * we check whether the stripes thread local has been set. If * it is, we remove it and use it to determine the * StripedObject and execute it with a SerialExecutor. If no * StripedObject is set, we instead pass the command to the * wrapped ExecutorService directly. */ public void execute(Runnable command) { lock.lock(); try { checkPoolIsRunning(); Object stripe = getStripe(command); if (stripe != null) { SerialExecutor ser_exec = executors.get(stripe); if (ser_exec == null) { executors.put(stripe, ser_exec = new SerialExecutor(stripe)); } ser_exec.execute(command); } else { executor.execute(command); } } finally { lock.unlock(); } } /** * We get the stripe object either from the Runnable if it * also implements StripedObject, or otherwise from the thread * local temporary storage. Result may be null. */ private Object getStripe(Runnable command) { Object stripe; if (command instanceof StripedObject) { stripe = ((StripedObject) command).getStripe(); } else { stripe = STRIPES.get(); } STRIPES.remove(); return stripe; } /** * Shuts down the StripedExecutorService. No more tasks will * be submitted. If the map of SerialExecutors is empty, we * shut down the wrapped executor. */ public void shutdown() { lock.lock(); try { state = State.SHUTDOWN; if (executors.isEmpty()) { executor.shutdown(); } } finally { lock.unlock(); } } /** * All the tasks in each of the SerialExecutors are drained * to a list, as well as the tasks inside the wrapped * ExecutorService. This is then returned to the user. Also, * the shutdownNow method of the wrapped executor is called. */ public List<Runnable> shutdownNow() { lock.lock(); try { shutdown(); List<Runnable> result = new ArrayList<>(); for (SerialExecutor ser_ex : executors.values()) { ser_ex.tasks.drainTo(result); } result.addAll(executor.shutdownNow()); return result; } finally { lock.unlock(); } } /** * Returns true if shutdown() or shutdownNow() have been * called; false otherwise. */ public boolean isShutdown() { lock.lock(); try { return state == State.SHUTDOWN; } finally { lock.unlock(); } } /** * Returns true if this pool has been terminated, that is, all * the SerialExecutors are empty and the wrapped * ExecutorService has been terminated. */ public boolean isTerminated() { lock.lock(); try { if (state == State.RUNNING) { return false; } for (SerialExecutor executor : executors.values()) { if (!executor.isEmpty()) { return false; } } return executor.isTerminated(); } finally { lock.unlock(); } } /** * Returns true if the wrapped ExecutorService terminates * within the allotted amount of time. */ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { lock.lock(); try { long waitUntil = System.nanoTime() + unit.toNanos(timeout); long remainingTime; while ((remainingTime = waitUntil - System.nanoTime()) > 0 && !executors.isEmpty()) { terminating.awaitNanos(remainingTime); } if (remainingTime <= 0) { return false; } if (executors.isEmpty()) { return executor.awaitTermination( remainingTime, TimeUnit.NANOSECONDS); } return false; } finally { lock.unlock(); } } /** * As soon as a SerialExecutor is empty, we remove it from the * executors map. We might thus remove the SerialExecutors * more quickly than necessary, but at least we can avoid a * memory leak. */ private void removeEmptySerialExecutor(Object stripe, SerialExecutor ser_ex) { assert ser_ex == executors.get(stripe); assert lock.isHeldByCurrentThread(); assert ser_ex.isEmpty(); executors.remove(stripe); terminating.signalAll(); if (state == State.SHUTDOWN && executors.isEmpty()) { executor.shutdown(); } } /** * Prints information about current state of this executor, the * wrapped executor and the serial executors. */ public String toString() { lock.lock(); try { return "StripedExecutorService: state=" + state + ", " + "executor=" + executor + ", " + "serialExecutors=" + executors; } finally { lock.unlock(); } } /** * Get number of executors. */ public int numberOfExecutors() { return executors.size(); } /** * Get number of executors. */ public Map<String, Integer> serialExecutorQueueSizes() { TreeMap<String, Integer> map = new TreeMap<>(); for (Map.Entry<Object, SerialExecutor> e : executors.entrySet()) { String key = e.getKey().toString(); int value = e.getValue().tasks.size(); map.put(key, value); } return map; } /** * This field is used for conditional compilation. If it is * false, then the finalize method is an empty method, in * which case the SerialExecutor will not be registered with * the Finalizer. */ private static boolean DEBUG; /** * SerialExecutor is based on the construct with the same name * described in the {@link java.util.concurrent.Executor} JavaDocs. The difference * with our SerialExecutor is that it can be terminated. It * also removes itself automatically once the queue is empty. */ private final class SerialExecutor implements Executor { private final int MAX_NUMBER_OF_WAITING_TASKS = 8192; /** * The queue of unexecuted tasks. */ private final BlockingQueue<Runnable> tasks = new LinkedBlockingQueue<>(MAX_NUMBER_OF_WAITING_TASKS); /** * The runnable that we are currently busy with. */ private Runnable active; /** * The stripe that this SerialExecutor was defined for. It * is needed so that we can remove this executor from the * map once it is empty. */ private final Object stripe; /** * Creates a SerialExecutor for a particular stripe. */ private SerialExecutor(Object stripe) { this.stripe = stripe; LOG.debug("SerialExecutor created " + stripe); } /** * We use finalize() only for debugging purposes. If * DEBUG==false, the body of the method will be compiled * away, thus rendering it a trivial finalize() method, * which means that the object will not incur any overhead * since it won't be registered with the Finalizer. */ protected void finalize() throws Throwable { LOG.debug("SerialExecutor finalized " + stripe); super.finalize(); } /** * For every task that is executed, we add() a wrapper to * the queue of tasks that will run the current task and * then schedule the next task in the queue. */ public void execute(final Runnable r) { boolean accepted = false; while (!accepted) { lock.lock(); try { if (tasks.remainingCapacity() <= 0) { try { LOG.debug("Task queue full. Waiting for capacity."); terminating.await(); LOG.debug("Some task has terminated. Retrying to submit new task."); } catch (InterruptedException e) { LOG.warn(e.getMessage(), e); } } else { try { accepted = tasks.add(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); LOG.debug("Task" + (accepted ? "":" not" ) + " accepted."); if (active == null) { scheduleNext(); } } catch (IllegalStateException e) { LOG.warn("Thread queue was unexpectedly full: " + e.getMessage(), e); } } } finally { lock.unlock(); } } } /** * Schedules the next task for this stripe. Should only be * called if active == null or if we are finished executing * the currently active task. */ private void scheduleNext() { lock.lock(); try { if ((active = tasks.poll()) != null) { executor.execute(active); terminating.signalAll(); } else { removeEmptySerialExecutor(stripe, this); } } finally { lock.unlock(); } } /** * Returns true if the list is empty and there is no task * currently executing. */ public boolean isEmpty() { lock.lock(); try { return active == null && tasks.isEmpty(); } finally { lock.unlock(); } } public String toString() { assert lock.isHeldByCurrentThread(); return "SerialExecutor: active=" + active + ", " + "tasks=" + tasks; } } }