package edu.harvard.mcb.leschziner.distributed; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import com.hazelcast.core.AtomicNumber; import com.hazelcast.core.Cluster; import com.hazelcast.core.Hazelcast; import edu.harvard.mcb.leschziner.storage.DefaultStorageEngine; import edu.harvard.mcb.leschziner.storage.StorageEngine; /** * A queue that sits in front of an unbounded, distributed executor service, * holding onto tasks until cluster capacity is available. * * @author spartango * */ public class QueuedDistributedExecutor implements Runnable { // Time between cluster capacity checks public static final int POLL_TIME = 250; // Default number of tasks that can be run on each node public static int defaultNodeCapacity = 2; // The underlying executor private final ExecutorService executor; // Number of active tasks private final AtomicNumber activeTasks; // Cluster nodes private final Cluster cluster; // Number of tasks that can be run per node private final int nodeCapacity; // Tasks waiting to be sent to the executor private final BlockingQueue<DistributedProcessingTask> queuedTasks; // Thread that executes queued tasks when there is capacity available private boolean running; private final Thread execThread; /** * Builds a distributed executor that queues tasks until cluster capacity is * available, calculating capacity from the number of nodes and a default * number of nodes * * @param name * of distributed executor which will disperse tasks across a * cluster */ public QueuedDistributedExecutor(String executorName) { this(executorName, defaultNodeCapacity); } /** * Builds a distributed executor that queues tasks until cluster capacity is * available * * @param name * of distributed executor which will disperse tasks across a * cluster * @param nodeCapacity * : number of tasks that can be run on each node */ public QueuedDistributedExecutor(String executorName, int nodeCapacity) { this.executor = DefaultExecutor.getExecutor(executorName); StorageEngine storage = DefaultStorageEngine.getStorageEngine(); this.cluster = Hazelcast.getCluster(); this.activeTasks = storage.getAtomicNumber(executorName + DistributedProcessingTask.ACTIVE_SUFFIX); this.nodeCapacity = nodeCapacity; this.queuedTasks = new LinkedBlockingQueue<DistributedProcessingTask>(); // Spin up the consumer thread this.running = true; execThread = new Thread(this); execThread.start(); } /** * Request execution of a task * * @param task */ public void execute(DistributedProcessingTask task) { queuedTasks.add(task); } /** * Prevent any unexecuted tasks from being executed */ public void shutdown() { running = false; execThread.interrupt(); executor.shutdown(); } /** * Pulls from the queue when there is capacity available */ @Override public void run() { while (running) { try { // Check for capacity available vs number of running tasks if (activeTasks.get() < getCapacity()) { // If capacity is available, grab any queued tasks DistributedProcessingTask task = queuedTasks.take(); task.markActive(); executor.execute(task); } else { // Sleep for a bit, waiting for tasks to finish Thread.sleep(POLL_TIME); } } catch (InterruptedException e) { // Go around and check that we're still supposed to be // running } } } private int getCapacity() { return cluster.getMembers().size() * nodeCapacity; } }