package models.com.mc.workers;
import akka.actor.*;
import akka.contrib.pattern.ClusterClient.SendToAll;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.japi.Function;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
import models.com.mc.workers.Master.Ack;
import models.com.mc.workers.MasterWorkerProtocol.*;
import java.io.Serializable;
import java.util.UUID;
import static akka.actor.SupervisorStrategy.Directive;
import static akka.actor.SupervisorStrategy.escalate;
import static akka.actor.SupervisorStrategy.restart;
import static akka.actor.SupervisorStrategy.stop;
//import models.com.mc.workers.Master.Work;
public class Worker extends UntypedActor {
public static Props props(ActorRef clusterClient, Props workExecutorProps, FiniteDuration registerInterval) {
return Props.create(Worker.class, clusterClient, workExecutorProps, registerInterval);
}
public static Props props(ActorRef clusterClient, Props workExecutorProps) {
return props(clusterClient, workExecutorProps, Duration.create(10, "seconds"));
}
private final ActorRef clusterClient;
private final Props workExecutorProps;
private final FiniteDuration registerInterval;
private LoggingAdapter log = Logging.getLogger(getContext().system(), this);
private final String workerId = UUID.randomUUID().toString();
private final ActorRef workExecutor;
private final Cancellable registerTask;
private String currentWorkId = null;
public Worker(ActorRef clusterClient, Props workExecutorProps, FiniteDuration registerInterval) {
this.clusterClient = clusterClient;
this.workExecutorProps = workExecutorProps;
this.registerInterval = registerInterval;
this.workExecutor = getContext().watch(getContext().actorOf(workExecutorProps, "exec"));
this.registerTask = getContext().system().scheduler().schedule(Duration.Zero(), registerInterval,
clusterClient, new SendToAll("/user/master/active", new RegisterWorker(workerId)),
getContext().dispatcher(), getSelf());
}
private String workId() {
if (currentWorkId != null)
return currentWorkId;
else
throw new IllegalStateException("Not working");
}
/*
Supervisor Strategy to monitor child Actors and handle the failed actors.
*/
@Override
public SupervisorStrategy supervisorStrategy() {
return new OneForOneStrategy(-1, Duration.Inf(),
new Function<Throwable, Directive>() {
@Override
public Directive apply(Throwable t) {
if (t instanceof ActorInitializationException)
return stop();
else if (t instanceof DeathPactException)
return stop();
else if (t instanceof Exception) {
return stop();
}
else {
return escalate();
}
}
}
);
}
@Override
public void postStop() {
registerTask.cancel();
}
public void onReceive(Object message) {
unhandled(message);
}
private final Behavior idle = new Behavior() {
public void apply(Object message) {
if (message instanceof WorkIsReady)
sendToMaster(new WorkerRequestsWork(workerId));
else if (message instanceof Work) {
Work work = (Work) message;
log.debug("Got work: {}", work.getJob());
currentWorkId = work.getWorkId();
workExecutor.tell(work.getJob(), getSelf());
getContext().become(working);
}
else unhandled(message);
}
};
private final Behavior working = new Behavior() {
public void apply(Object message) {
if (message instanceof WorkComplete) {
Object result = ((WorkComplete) message).result;
log.debug("Work is complete. Result {}.", result);
sendToMaster(new WorkIsDone(workerId, workId(), result));
getContext().setReceiveTimeout(Duration.create(5, "seconds"));
getContext().become(waitForWorkIsDoneAck(result));
}
else if (message instanceof Work) {
log.info("Yikes. Master told me to do work, while I'm working.");
}
else {
unhandled(message);
}
}
};
private Behavior waitForWorkIsDoneAck(final Object result) {
return new Behavior() {
public void apply(Object message) {
if (message instanceof Ack && ((Ack) message).workId.equals(workId())) {
sendToMaster(new WorkerRequestsWork(workerId));
getContext().setReceiveTimeout(Duration.Undefined());
getContext().become(idle);
}
else if (message instanceof ReceiveTimeout) {
log.info("No ack from master, retrying (" + workerId + " -> " + workId() + ")");
sendToMaster(new WorkIsDone(workerId, workId(), result));
}
else {
unhandled(message);
}
}
};
}
{
getContext().become(idle);
}
@Override
public void unhandled(Object message) {
if (message instanceof Terminated && ((Terminated) message).getActor().equals(workExecutor)) {
getContext().stop(getSelf());
}
else if (message instanceof WorkIsReady) {
// do nothing
}
else {
super.unhandled(message);
}
}
private void sendToMaster(Object msg) {
clusterClient.tell(new SendToAll("/user/master/active", msg), getSelf());
}
public static final class WorkComplete implements Serializable {
public final Object result;
public WorkComplete(Object result) {
this.result = result;
}
@Override
public String toString() {
return "WorkComplete{" +
"result=" + result +
'}';
}
}
}