package ecologylab.bigsemantics.distributed; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ecologylab.bigsemantics.distributed.Task.Result; import ecologylab.bigsemantics.distributed.Task.State; import ecologylab.serialization.annotations.simpl_scalar; /** * Represents a worker. This implementation uses a fixed size thread pool, but subclasses can * extend. * * @author quyin */ public class Worker<T extends Task> { public static interface AvailableEventHandler<T extends Task> { void onAvailable(Worker<T> worker); } public static enum SubmissionResult { ACCEPTED, ACCEPTED_AND_FULL, REJECTED } static Logger logger = LoggerFactory.getLogger(Worker.class); @simpl_scalar private String id; @simpl_scalar private int numThreads; private int priority; private ExecutorService executors; @simpl_scalar private int numOngoingTasks; @simpl_scalar private int consecutiveFailures; private AvailableEventHandler<T> availableEventHandler; /** * For deserialization only. */ public Worker() { this("UNINITIALIZED_WORKER", 1); } public Worker(String id, int numThreads) { this(id, numThreads, 0); } public Worker(String id, int numThreads, int priority) { this.id = id; this.numThreads = numThreads; this.priority = priority; executors = Executors.newFixedThreadPool(numThreads); } public String getId() { return this.id; } protected void setId(String id) { this.id = id; } public int getNumThreads() { return numThreads; } protected void setNumThreads(int numThreads) { this.numThreads = numThreads; } public int getPriority() { return priority; } public int getConsecutiveFailures() { return consecutiveFailures; } /** * Subclasses should use this when the failure is caused by the worker itself, not the task. */ protected void incConsecutiveFailures() { consecutiveFailures++; } public void setAvailableEventHandler(AvailableEventHandler<T> availableEventHandler) { this.availableEventHandler = availableEventHandler; } protected synchronized void triggerAvailableEventIfOk() { if (numOngoingTasks < numThreads && availableEventHandler != null) { availableEventHandler.onAvailable(this); } } protected synchronized void incOngoing() { numOngoingTasks++; } protected synchronized void decOngoing() { numOngoingTasks--; triggerAvailableEventIfOk(); } public boolean canHandle(T task) throws Exception { return true; } /** * Submit a task to this worker. * * @param task * @param handler * The callback. * @return */ public synchronized SubmissionResult submit(T task, final TaskEventHandler<T> handler) { if (numOngoingTasks >= numThreads) { return SubmissionResult.REJECTED; } doSubmit(task, handler); incOngoing(); return (numOngoingTasks < numThreads) ? SubmissionResult.ACCEPTED : SubmissionResult.ACCEPTED_AND_FULL; } protected void doSubmit(final T task, final TaskEventHandler<T> handler) { onSubmitAccept(task, handler); executors.submit(new Runnable() { @Override public void run() { performTask(task, handler); logger.debug("Task {} performed in worker {}", task, Worker.this); onTaskPerformed(task, handler); decOngoing(); } }); } /** * Subclasses can override this to take actions when task is accepted by this worker, e.g. to * provide worker-specific information for performing. */ protected void onSubmitAccept(T task, TaskEventHandler<T> handler) { // no op } /** * Subclasses can override this to take actions after task is performed by this worker. */ protected void onTaskPerformed(T task, TaskEventHandler<T> handler) { // no op } /** * Subclasses can override this to take actions when this worker is removed from the dispatcher. */ protected void onRemoval() { // no op } protected void performTask(T task, TaskEventHandler<T> handler) { logger.debug("Performing task {}", task); synchronized (task) { State state = task.getState(); switch (state) { case SUCCEEDED: case TERMINATED: return; default: task.setState(State.ONGOING); Result result = Result.FATAL; try { result = task.perform(); } catch (Exception e) { result = Result.FATAL; logger.error("Exception when performing task " + task, e); } if (result == Result.OK) { task.setState(State.SUCCEEDED); logger.debug("Task {} completed", task); if (handler != null) { handler.onComplete(task); } task.notifyAll(); } else if (result == Result.ERROR) { task.setState(State.WAITING); task.incFailCount(); logger.debug("Task {} failed", task); if (handler != null) { handler.onFail(task); } } else { // result == Result.FATAL task.setState(State.TERMINATED); logger.debug("Task {} terminated with fatal problem", task); if (handler != null) { handler.onTerminate(task); } task.notifyAll(); } consecutiveFailures = 0; } } } @Override public String toString() { return Worker.class.getSimpleName() + "[" + getId() + ", P" + getPriority() + "]"; } }