package ecologylab.bigsemantics.distributed;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.PriorityBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ecologylab.bigsemantics.distributed.Task.State;
import ecologylab.bigsemantics.distributed.Worker.AvailableEventHandler;
import ecologylab.bigsemantics.distributed.Worker.SubmissionResult;
/**
* The dispatcher. Needs a set of workers. Can queue tasks to this.
*
* @author quyin
*
* @param <T>
* @param <W>
*/
public class Dispatcher<T extends Task, W extends Worker<T>>
{
/**
* Convenient class for handling the task queue.
*
* @author quyin
*
* @param <T>
*/
private static class TaskEntry<T extends Task> extends PQEntry<T>
{
private TaskEventHandler<T> handler;
public TaskEntry(T task, TaskEventHandler<T> handler)
{
super(task, task.getPriority());
this.handler = handler;
}
public T getTask()
{
return getEntry();
}
public TaskEventHandler<T> getHandler()
{
return handler;
}
}
/**
* Convenient class for handling the worker queue.
*
* @author quyin
*
* @param <T>
* @param <W>
*/
private static class WorkerEntry<T extends Task, W extends Worker<T>> extends PQEntry<W>
{
public WorkerEntry(W worker)
{
super(worker, worker.getPriority());
}
public W getWorker()
{
return getEntry();
}
}
static Logger logger;
static
{
logger = LoggerFactory.getLogger(Dispatcher.class);
}
private Map<String, W> workers;
private int maxConsecutiveWorkerFailures;
private PriorityBlockingQueue<TaskEntry<T>> taskQueue;
private PriorityBlockingQueue<WorkerEntry<T, W>> workerQueue;
public Dispatcher()
{
workers = new HashMap<String, W>();
taskQueue = new PriorityBlockingQueue<TaskEntry<T>>();
workerQueue = new PriorityBlockingQueue<WorkerEntry<T, W>>();
}
public Map<String, W> getWorkers()
{
return workers;
}
public void addWorker(W worker)
{
worker.setAvailableEventHandler(new AvailableEventHandler<T>()
{
@SuppressWarnings("unchecked")
@Override
public void onAvailable(Worker<T> worker)
{
if (maxConsecutiveWorkerFailures > 0
&& worker.getConsecutiveFailures() > maxConsecutiveWorkerFailures)
{
logger.warn("Too many worker failures, removed: {}", worker);
workers.remove(worker.getId());
worker.onRemoval();
return;
}
queueWorker((W) worker);
}
});
onAddWorker(worker);
String id = worker.getId();
workers.put(id, worker);
queueWorker(worker);
}
public int getMaxConsecutiveWorkerFailures()
{
return maxConsecutiveWorkerFailures;
}
public void setMaxConsecutiveWorkerFailures(int maxConsecutiveWorkerFailures)
{
this.maxConsecutiveWorkerFailures = maxConsecutiveWorkerFailures;
}
/**
* Subclasses can use this to operate on a worker when it is added to this dispatcher.
*
* @param worker
*/
protected void onAddWorker(W worker)
{
// no op
}
/**
* Queue a task to this dispatcher.
*
* @param task
* @param handler
* The callback.
*/
public void queueTask(T task, TaskEventHandler<T> handler)
{
taskQueue.put(new TaskEntry<T>(task, handler));
onQueued(task);
logger.debug("Task queued: {}", task);
}
/**
* Subclasses can use this to notify the task that it is being dispatched.
*
* @param task
*/
protected void onQueued(T task)
{
// no op
}
protected void queueWorker(W worker)
{
workerQueue.put(new WorkerEntry<T, W>(worker));
}
/**
* Key method for dispatching one task (or moving it to the end if no workers can handle it right
* now).
*
* @throws Exception
*/
public void dispatchTask() throws Exception
{
TaskEntry<T> taskEntry = taskQueue.take(); // this will block if taskQueue is empty
T task = taskEntry.getTask(); // the task to dispatch in this invocation
onDispatch(task);
TaskEventHandler<T> handler = taskEntry.getHandler();
logger.debug("Task taken: {}", task);
// try to dispatch the task
boolean dispatched = false;
List<WorkerEntry<T, W>> triedEntries = new ArrayList<WorkerEntry<T, W>>();
while (true)
{
WorkerEntry<T, W> workerEntry = null;
if (triedEntries.isEmpty())
{
// initially, if no workers available, wait for one
workerEntry = workerQueue.take();
}
else
{
// if we are not in the situation that no workers are available, keep trying without wait
workerEntry = workerQueue.poll();
if (workerEntry == null)
{
// tried all the workers but no one can handle it
break;
}
}
W worker = workerEntry.getWorker();
logger.debug("Worker polled: {}", worker);
if (worker.canHandle(task))
{
DispatcherHandler dispatcherHandler = getDispatcherHandler(handler);
logger.debug("Trying submitting task {} to worker {}", task, worker);
SubmissionResult result = worker.submit(task, dispatcherHandler);
logger.debug("Submitted task {} to worker {}, result: {}", task, worker, result);
switch (result)
{
case ACCEPTED:
dispatched = true;
queueWorker(worker);
break;
case ACCEPTED_AND_FULL:
dispatched = true;
break;
case REJECTED:
dispatched = false;
break;
}
break; // to break the while loop, not the switch
}
else
{
logger.debug("Worker {} not able to handle task {}", worker, task);
triedEntries.add(workerEntry);
}
}
workerQueue.addAll(triedEntries);
// if not dispatched, put the task back
if (!dispatched)
{
logger.debug("Task {} not handled, re-enqueue", task);
queueTask(task, handler);
}
}
/**
* Subclasses can use this to notify the task that it is being dispatched.
*
* @param task
*/
protected void onDispatch(T task)
{
// no op
}
private class DispatcherHandler implements TaskEventHandler<T>
{
private TaskEventHandler<T> handler;
private DispatcherHandler(TaskEventHandler<T> handler)
{
this.handler = handler;
}
@Override
public void onComplete(T task)
{
if (handler != null)
{
handler.onComplete(task);
}
}
@Override
public void onFail(T task)
{
if (isTooManyFail(task))
{
logger.debug("Terminating task {} after too many failures", task);
task.setState(State.TERMINATED);
if (handler != null)
{
handler.onTerminate(task);
}
task.notifyAll();
}
else
{
logger.debug("Retry task {} after failure", task);
queueTask(task, handler);
}
}
@Override
public void onTerminate(T task)
{
if (handler != null)
{
handler.onTerminate(task);
}
}
}
protected DispatcherHandler getDispatcherHandler(TaskEventHandler<T> handler)
{
DispatcherHandler dispatcherHandler = new DispatcherHandler(handler);
return dispatcherHandler;
}
/**
* Subclasses can override this to smartly determine if a task has too many failures, based on
* task information (such as failCount).
*
* @param task
* @return true if there are too many failures for the input task; otherwise false.
*/
protected boolean isTooManyFail(T task)
{
return true;
}
}