/*
* Copyright (C) 2015 Actor LLC. <https://actor.im>
*/
package im.actor.runtime.actors.dispatch;
import java.util.HashMap;
import im.actor.runtime.Runtime;
import im.actor.runtime.actors.Actor;
import im.actor.runtime.actors.ActorContext;
import im.actor.runtime.actors.ActorRef;
import im.actor.runtime.actors.ActorScope;
import im.actor.runtime.actors.ActorSystem;
import im.actor.runtime.actors.ActorTime;
import im.actor.runtime.actors.Props;
import im.actor.runtime.actors.ThreadPriority;
import im.actor.runtime.actors.dispatch.queue.QueueCollection;
import im.actor.runtime.actors.dispatch.queue.QueueDispatcher;
import im.actor.runtime.actors.messages.DeadLetter;
import im.actor.runtime.actors.messages.PoisonPill;
import im.actor.runtime.actors.messages.StartActor;
import im.actor.runtime.function.Consumer;
import im.actor.runtime.threading.ThreadDispatcher;
/**
* Abstract Actor Dispatcher, used for dispatching messages for actors
*/
public class ActorDispatcher {
private final Object LOCK = new Object();
private final HashMap<String, ActorEndpoint> endpoints = new HashMap<>();
private final HashMap<String, ActorScope> scopes = new HashMap<>();
private final ActorSystem actorSystem;
private final QueueCollection<Envelope> queueCollection = new QueueCollection<>();
private final String name;
private final QueueDispatcher<Envelope>[] dispatchers;
public ActorDispatcher(String name, ThreadPriority priority, ActorSystem actorSystem, int dispatchersCount) {
this.name = name;
this.actorSystem = actorSystem;
this.dispatchers = new QueueDispatcher[dispatchersCount];
final Consumer<Envelope> handler = envelope -> processEnvelope(envelope);
for (int i = 0; i < dispatchers.length; i++) {
this.dispatchers[i] = new QueueDispatcher<>(name + "_" + i, priority, queueCollection, handler);
}
}
public String getName() {
return name;
}
public final ActorRef referenceActor(String path, Props props) {
synchronized (LOCK) {
if (scopes.containsKey(path)) {
return scopes.get(path).getActorRef();
}
Mailbox mailbox = new Mailbox(queueCollection);
ActorEndpoint endpoint = endpoints.get(path);
if (endpoint == null) {
endpoint = new ActorEndpoint(path);
endpoints.put(path, endpoint);
}
ActorScope scope = new ActorScope(actorSystem, mailbox, this, path, props, endpoint);
endpoint.connect(mailbox, scope);
scopes.put(scope.getPath(), scope);
// Sending init message
if (!Runtime.isSingleThread() && !Runtime.isMainThread()) {
scope.getActorRef().send(StartActor.INSTANCE);
} else {
Runtime.dispatch(() -> scope.getActorRef().send(StartActor.INSTANCE));
}
return scope.getActorRef();
}
}
/**
* Processing of envelope
*
* @param envelope envelope
*/
private void processEnvelope(Envelope envelope) {
ActorScope scope = envelope.getScope();
if (actorSystem.getTraceInterface() != null) {
actorSystem.getTraceInterface().onEnvelopeDelivered(envelope);
}
long start = ActorTime.currentTime();
if (scope.getActor() == null) {
if (envelope.getMessage() == PoisonPill.INSTANCE) {
// Not creating actor for PoisonPill
return;
}
try {
Actor actor = scope.getProps().create();
actor.initActor(scope.getPath(), new ActorContext(scope), scope.getMailbox());
ThreadDispatcher.pushDispatcher(actor.getDispatcher());
try {
actor.preStart();
} finally {
ThreadDispatcher.popDispatcher();
}
scope.onActorCreated(actor);
} catch (Exception e) {
e.printStackTrace();
if (envelope.getSender() != null) {
envelope.getSender().send(new DeadLetter("Unable to create actor"));
}
return;
}
}
try {
if (envelope.getMessage() == StartActor.INSTANCE) {
// Already created actor
} else if (envelope.getMessage() == PoisonPill.INSTANCE) {
ThreadDispatcher.pushDispatcher(scope.getActor().getDispatcher());
try {
scope.getActor().postStop();
} finally {
ThreadDispatcher.popDispatcher();
}
onActorDie(scope);
} else {
scope.getActor().handleMessage(envelope.getMessage(), envelope.getSender());
}
} catch (Exception e) {
if (actorSystem.getTraceInterface() != null) {
actorSystem.getTraceInterface().onActorDie(scope.getActorRef(), envelope, e);
}
ThreadDispatcher.pushDispatcher(scope.getActor().getDispatcher());
try {
scope.getActor().postStop();
} finally {
ThreadDispatcher.popDispatcher();
}
onActorDie(scope);
} finally {
if (actorSystem.getTraceInterface() != null) {
actorSystem.getTraceInterface().onEnvelopeProcessed(envelope, ActorTime.currentTime() - start);
}
}
}
private void onActorDie(ActorScope scope) {
scope.onActorDie();
if (scope.getProps().getSupervisor() != null) {
scope.getProps().getSupervisor().onActorStopped(scope.getActorRef());
}
Envelope[] deadLetters;
synchronized (LOCK) {
scopes.remove(scope.getPath());
endpoints.remove(scope.getPath());
deadLetters = scope.getMailbox().dispose();
}
for (Envelope e : deadLetters) {
if (e.getSender() != null) {
e.getSender().send(new DeadLetter(e.getMessage()));
}
}
}
}