package edu.washington.escience.myria.util.concurrent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.jboss.netty.util.internal.ConcurrentIdentityWeakKeyHashMap;
import com.google.common.base.Preconditions;
/**
* A simple implementation of the {@link ThreadAffinityExecutorService}. It is fixed-sized. It uses a simple round-robin
* policy to assign new tasks to an executor.
* */
public class ThreadAffinityFixedRoundRobinExecutionPool extends AbstractExecutorService
implements ThreadAffinityExecutorService {
/**
* If the pool is shutdown.
* */
private volatile boolean shutdown = false;
/**
* The executors.
* */
private final DefaultThreadAffinityExecutor[] executors;
/**
* The next executor to assign for new tasks.
* */
private final AtomicInteger executorIndex = new AtomicInteger(0);
/**
* @param poolSize thread pool size
* */
public ThreadAffinityFixedRoundRobinExecutionPool(final int poolSize) {
this(poolSize, null);
}
/**
* @param poolSize thread pool size
* @param threadFactory thread factory
* */
public ThreadAffinityFixedRoundRobinExecutionPool(
final int poolSize, final ThreadFactory threadFactory) {
Preconditions.checkArgument(poolSize > 0);
ThreadFactory tf = threadFactory;
if (tf == null) {
tf = new DefaultThreadFactory();
}
executors = new DefaultThreadAffinityExecutor[poolSize];
for (int i = 0; i < executors.length; i++) {
executors[i] = new DefaultThreadAffinityExecutor(tf);
}
}
/**
* {@link Runnable} or {@link Callable} -> executor.
* */
private final ConcurrentMap<Object, ThreadAffinityExecutor> childExecutors =
new ConcurrentIdentityWeakKeyHashMap<Object, ThreadAffinityExecutor>();
@Override
public void shutdown() {
for (DefaultThreadAffinityExecutor executor : executors) {
executor.shutdown();
}
shutdown = true;
}
@Override
public List<Runnable> shutdownNow() {
ArrayList<Runnable> r = new ArrayList<Runnable>();
for (DefaultThreadAffinityExecutor executor : executors) {
r.addAll(executor.shutdownNow());
}
return r;
}
@Override
public boolean isShutdown() {
return shutdown;
}
@Override
public boolean isTerminated() {
for (DefaultThreadAffinityExecutor executor : executors) {
if (!executor.isTerminated()) {
return false;
}
}
return true;
}
@Override
public boolean awaitTermination(final long timeout, final TimeUnit unit)
throws InterruptedException {
long timeoutNanoLeft = unit.toNanos(timeout);
long startNano = System.nanoTime();
for (DefaultThreadAffinityExecutor executor : executors) {
if (timeoutNanoLeft <= 0) {
return false;
}
if (executor.isTerminated()) {
continue;
}
executor.awaitTermination(timeoutNanoLeft, TimeUnit.NANOSECONDS);
timeoutNanoLeft -= (System.nanoTime() - startNano);
}
return true;
}
/**
* @param task the Runnable or Callable task
* @return the executor for the task.
* */
private ThreadAffinityExecutor getExecutor0(final Object task) {
Preconditions.checkNotNull(task);
ThreadAffinityExecutor executor = childExecutors.get(task);
if (executor == null) {
executor = nextExecutor();
ThreadAffinityExecutor old = childExecutors.putIfAbsent(task, executor);
if (old != null) {
executor = old;
}
}
return executor;
}
@Override
public void execute(final Runnable command) {
this.submit(command);
}
@Override
public ExecutionFuture<?> submit(final Runnable task) {
return getExecutor(task).submit(Executors.callable(task));
}
@Override
public <T> ExecutionFuture<T> submit(final Runnable task, final T result) {
return getExecutor(task)
.submit(
new Callable<T>() {
@Override
public T call() throws Exception {
task.run();
return result;
}
});
}
@Override
public <T> ExecutionFuture<T> submit(final Callable<T> task) {
return getExecutor(task).submit(task);
}
/**
* @return a {@link ThreadAffinityExecutor}. The jobs submitted through
* */
@Override
public final ThreadAffinityExecutor nextExecutor() {
int i = 0;
int newI = 1;
while (true) {
i = executorIndex.get();
newI = (i + 1) % executors.length;
if (executorIndex.compareAndSet(i, newI)) {
break;
}
}
return executors[i];
}
@Override
public ThreadAffinityExecutor getExecutor(final Runnable task) {
return getExecutor0(task);
}
@Override
public ThreadAffinityExecutor getExecutor(final Callable<?> task) {
return getExecutor0(task);
}
}