package com.datascience.executor;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.*;
import org.apache.log4j.Logger;
/**
*
* @author konrad
*/
public class ProjectCommandExecutor{
private static final Logger log =
Logger.getLogger(ProjectCommandExecutor.class);
protected Queue<IExecutorCommand> queue;
protected ListeningExecutorService commandExecutor;
protected volatile boolean isAlive;
public ProjectCommandExecutor(){
this(10); // TODO: work on this number
}
public ProjectCommandExecutor(int numThreads){
queue = new LinkedList<IExecutorCommand>();
ThreadFactory cetf = new ThreadFactoryBuilder()
.setNameFormat("cmdExTh-%d")
.build();
commandExecutor = MoreExecutors.listeningDecorator(
Executors.newFixedThreadPool(numThreads, cetf));
isAlive = true;
}
/**
* Must be synchronized
*/
protected void checkState(){
if (!isAlive) {
throw new IllegalStateException("Adding command after ProjectCommandExecutor was stopped");
}
}
/**
* Must be synchronized
*/
protected boolean canStop(){
return (!isAlive) && queue.isEmpty();
}
public void add(final IExecutorCommand eCommand){
synchronized (ProjectCommandExecutor.this) {
checkState();
if (eCommand.canStart()){
runCommand(eCommand);
} else {
queue.add(eCommand);
}
}
}
private void runCommand(IExecutorCommand eCommand){
ListenableFuture future = commandExecutor.submit(eCommand);
Futures.addCallback(future, new CommandCleaner(eCommand),
MoreExecutors.sameThreadExecutor());
}
private void executePossibleCommands(){
synchronized (ProjectCommandExecutor.this) {
Iterator<IExecutorCommand> iter = queue.iterator();
while (iter.hasNext()) {
IExecutorCommand eCommand = iter.next();
if (eCommand.canStart()) {
iter.remove();
runCommand(eCommand);
}
}
if (canStop()) {
ProjectCommandExecutor.this.notify();
}
}
}
protected void initEmptyAndWaitTillEmpty(){
synchronized (ProjectCommandExecutor.this) {
isAlive = false;
if (!canStop()) {
try {
ProjectCommandExecutor.this.wait();
} catch (InterruptedException e) {
log.error("Error when waiting for signal to continue ProjectCommandExecutor cleanup", e);
}
}
assert canStop();
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
stop();
}
public void stop() throws InterruptedException{
log.info("STARTED Shutting down executors");
initEmptyAndWaitTillEmpty();
commandExecutor.shutdown();
boolean terminated = commandExecutor.awaitTermination(1, TimeUnit.MINUTES);
if (terminated) {
log.info("Command inner executor closed");
} else {
log.error("FAILED to shutdown commandExecutor");
}
log.info("DONE Shutting down executors");
}
class CommandCleaner implements FutureCallback {
private IExecutorCommand eCommand;
public CommandCleaner(IExecutorCommand eCommand){
this.eCommand = eCommand;
}
@Override
public void onSuccess(Object v) {
cleanUp();
}
@Override
public void onFailure(Throwable thrwbl) {
String className = eCommand.getClass().getName();
log.error("Failed executing task: " + className, thrwbl);
cleanUp();
}
private void cleanUp() {
eCommand.cleanup();
executePossibleCommands();
}
}
}