make remote supervision and path continuation work
- add supervisor to remote USE message - make remoteDaemon a VirtualPathContainer like LocalActorRefProvider.tempContainer (i.e. synchonous with CHM-based child lookup), scrap remoteDaemonSupervisor and rename remoteDaemon to “/remote” to match the plans in the docs - comment out the remote deployment configuration section, to be done when Henrik is finished with RoutedActorRef work - for now only “remote.nodes = ["sys@host:port"]” is looked at, i.e. if at least one is present, the first one is used to determine where to deploy the currently created child (routers will do the scaling-out component) [rest is commented out] - multi-jvm tests not yet re-enabled (need to be adapted), but all other tests are GREEN (at least on my machine)
This commit is contained in:
parent
fac840adfc
commit
e5bd8b5f88
10 changed files with 391 additions and 270 deletions
|
|
@ -13,6 +13,8 @@ import java.util.concurrent.TimeUnit
|
||||||
import akka.event.EventStream
|
import akka.event.EventStream
|
||||||
import akka.event.DeathWatch
|
import akka.event.DeathWatch
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import akka.event.LoggingAdapter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ActorRef is an immutable and serializable handle to an Actor.
|
* ActorRef is an immutable and serializable handle to an Actor.
|
||||||
|
|
@ -400,6 +402,41 @@ class DeadLetterActorRef(val eventStream: EventStream) extends MinimalActorRef {
|
||||||
private def writeReplace(): AnyRef = DeadLetterActorRef.serialized
|
private def writeReplace(): AnyRef = DeadLetterActorRef.serialized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class VirtualPathContainer(val path: ActorPath, override val getParent: InternalActorRef, val log: LoggingAdapter) extends MinimalActorRef {
|
||||||
|
|
||||||
|
private val children = new ConcurrentHashMap[String, InternalActorRef]
|
||||||
|
|
||||||
|
def addChild(name: String, ref: InternalActorRef): Unit = {
|
||||||
|
children.put(name, ref) match {
|
||||||
|
case null ⇒ // okay
|
||||||
|
case old ⇒ log.warning("{} replacing child {} ({} -> {})", path, name, old, ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def removeChild(name: String): Unit = {
|
||||||
|
children.remove(name) match {
|
||||||
|
case null ⇒ log.warning("{} trying to remove non-child {}", path, name)
|
||||||
|
case _ ⇒ //okay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getChild(name: String): InternalActorRef = children.get(name)
|
||||||
|
|
||||||
|
override def getChild(name: Iterator[String]): InternalActorRef = {
|
||||||
|
if (name.isEmpty) this
|
||||||
|
else {
|
||||||
|
val n = name.next()
|
||||||
|
if (n.isEmpty) this
|
||||||
|
else children.get(n) match {
|
||||||
|
case null ⇒ Nobody
|
||||||
|
case some ⇒
|
||||||
|
if (name.isEmpty) some
|
||||||
|
else some.getChild(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class AskActorRef(
|
class AskActorRef(
|
||||||
val path: ActorPath,
|
val path: ActorPath,
|
||||||
override val getParent: InternalActorRef,
|
override val getParent: InternalActorRef,
|
||||||
|
|
|
||||||
|
|
@ -448,11 +448,18 @@ class LocalActorRefProvider(
|
||||||
|
|
||||||
lazy val terminationFuture: DefaultPromise[Unit] = new DefaultPromise[Unit](Timeout.never)(dispatcher)
|
lazy val terminationFuture: DefaultPromise[Unit] = new DefaultPromise[Unit](Timeout.never)(dispatcher)
|
||||||
|
|
||||||
|
@volatile
|
||||||
|
private var extraNames: Map[String, InternalActorRef] = Map()
|
||||||
|
|
||||||
lazy val rootGuardian: InternalActorRef = new LocalActorRef(system, guardianProps, theOneWhoWalksTheBubblesOfSpaceTime, rootPath, true) {
|
lazy val rootGuardian: InternalActorRef = new LocalActorRef(system, guardianProps, theOneWhoWalksTheBubblesOfSpaceTime, rootPath, true) {
|
||||||
|
object Extra {
|
||||||
|
def unapply(s: String): Option[InternalActorRef] = extraNames.get(s)
|
||||||
|
}
|
||||||
override def getParent: InternalActorRef = this
|
override def getParent: InternalActorRef = this
|
||||||
override def getSingleChild(name: String): InternalActorRef = {
|
override def getSingleChild(name: String): InternalActorRef = {
|
||||||
name match {
|
name match {
|
||||||
case "temp" ⇒ tempContainer
|
case "temp" ⇒ tempContainer
|
||||||
|
case Extra(e) ⇒ e
|
||||||
case _ ⇒ super.getSingleChild(name)
|
case _ ⇒ super.getSingleChild(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -462,24 +469,7 @@ class LocalActorRefProvider(
|
||||||
|
|
||||||
lazy val systemGuardian: InternalActorRef = actorOf(system, guardianProps.withCreator(new SystemGuardian), rootGuardian, "system", true)
|
lazy val systemGuardian: InternalActorRef = actorOf(system, guardianProps.withCreator(new SystemGuardian), rootGuardian, "system", true)
|
||||||
|
|
||||||
lazy val tempContainer = new MinimalActorRef {
|
lazy val tempContainer = new VirtualPathContainer(tempNode, rootGuardian, log)
|
||||||
val children = new ConcurrentHashMap[String, AskActorRef]
|
|
||||||
def path = tempNode
|
|
||||||
override def getParent = rootGuardian
|
|
||||||
override def getChild(name: Iterator[String]): InternalActorRef = {
|
|
||||||
if (name.isEmpty) this
|
|
||||||
else {
|
|
||||||
val n = name.next()
|
|
||||||
if (n.isEmpty) this
|
|
||||||
else children.get(n) match {
|
|
||||||
case null ⇒ Nobody
|
|
||||||
case some ⇒
|
|
||||||
if (name.isEmpty) some
|
|
||||||
else some.getChild(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val deathWatch = createDeathWatch()
|
val deathWatch = createDeathWatch()
|
||||||
|
|
||||||
|
|
@ -491,6 +481,8 @@ class LocalActorRefProvider(
|
||||||
eventStream.startDefaultLoggers(_system)
|
eventStream.startDefaultLoggers(_system)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def registerExtraNames(_extras: Map[String, InternalActorRef]): Unit = extraNames ++= _extras
|
||||||
|
|
||||||
def actorFor(ref: InternalActorRef, path: String): InternalActorRef = path match {
|
def actorFor(ref: InternalActorRef, path: String): InternalActorRef = path match {
|
||||||
case RelativeActorPath(elems) ⇒
|
case RelativeActorPath(elems) ⇒
|
||||||
if (elems.isEmpty) deadLetters
|
if (elems.isEmpty) deadLetters
|
||||||
|
|
@ -578,10 +570,10 @@ class LocalActorRefProvider(
|
||||||
val name = path.name
|
val name = path.name
|
||||||
val a = new AskActorRef(path, tempContainer, deathWatch, t, dispatcher) {
|
val a = new AskActorRef(path, tempContainer, deathWatch, t, dispatcher) {
|
||||||
override def whenDone() {
|
override def whenDone() {
|
||||||
tempContainer.children.remove(name)
|
tempContainer.removeChild(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tempContainer.children.put(name, a)
|
tempContainer.addChild(name, a)
|
||||||
recipient.tell(message, a)
|
recipient.tell(message, a)
|
||||||
a.result
|
a.result
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5265,6 +5265,10 @@ public final class RemoteProtocol {
|
||||||
boolean hasReplicateActorFromUuid();
|
boolean hasReplicateActorFromUuid();
|
||||||
akka.remote.RemoteProtocol.UuidProtocol getReplicateActorFromUuid();
|
akka.remote.RemoteProtocol.UuidProtocol getReplicateActorFromUuid();
|
||||||
akka.remote.RemoteProtocol.UuidProtocolOrBuilder getReplicateActorFromUuidOrBuilder();
|
akka.remote.RemoteProtocol.UuidProtocolOrBuilder getReplicateActorFromUuidOrBuilder();
|
||||||
|
|
||||||
|
// optional string supervisor = 5;
|
||||||
|
boolean hasSupervisor();
|
||||||
|
String getSupervisor();
|
||||||
}
|
}
|
||||||
public static final class RemoteSystemDaemonMessageProtocol extends
|
public static final class RemoteSystemDaemonMessageProtocol extends
|
||||||
com.google.protobuf.GeneratedMessage
|
com.google.protobuf.GeneratedMessage
|
||||||
|
|
@ -5360,11 +5364,44 @@ public final class RemoteProtocol {
|
||||||
return replicateActorFromUuid_;
|
return replicateActorFromUuid_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// optional string supervisor = 5;
|
||||||
|
public static final int SUPERVISOR_FIELD_NUMBER = 5;
|
||||||
|
private java.lang.Object supervisor_;
|
||||||
|
public boolean hasSupervisor() {
|
||||||
|
return ((bitField0_ & 0x00000010) == 0x00000010);
|
||||||
|
}
|
||||||
|
public String getSupervisor() {
|
||||||
|
java.lang.Object ref = supervisor_;
|
||||||
|
if (ref instanceof String) {
|
||||||
|
return (String) ref;
|
||||||
|
} else {
|
||||||
|
com.google.protobuf.ByteString bs =
|
||||||
|
(com.google.protobuf.ByteString) ref;
|
||||||
|
String s = bs.toStringUtf8();
|
||||||
|
if (com.google.protobuf.Internal.isValidUtf8(bs)) {
|
||||||
|
supervisor_ = s;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private com.google.protobuf.ByteString getSupervisorBytes() {
|
||||||
|
java.lang.Object ref = supervisor_;
|
||||||
|
if (ref instanceof String) {
|
||||||
|
com.google.protobuf.ByteString b =
|
||||||
|
com.google.protobuf.ByteString.copyFromUtf8((String) ref);
|
||||||
|
supervisor_ = b;
|
||||||
|
return b;
|
||||||
|
} else {
|
||||||
|
return (com.google.protobuf.ByteString) ref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initFields() {
|
private void initFields() {
|
||||||
messageType_ = akka.remote.RemoteProtocol.RemoteSystemDaemonMessageType.STOP;
|
messageType_ = akka.remote.RemoteProtocol.RemoteSystemDaemonMessageType.STOP;
|
||||||
actorPath_ = "";
|
actorPath_ = "";
|
||||||
payload_ = com.google.protobuf.ByteString.EMPTY;
|
payload_ = com.google.protobuf.ByteString.EMPTY;
|
||||||
replicateActorFromUuid_ = akka.remote.RemoteProtocol.UuidProtocol.getDefaultInstance();
|
replicateActorFromUuid_ = akka.remote.RemoteProtocol.UuidProtocol.getDefaultInstance();
|
||||||
|
supervisor_ = "";
|
||||||
}
|
}
|
||||||
private byte memoizedIsInitialized = -1;
|
private byte memoizedIsInitialized = -1;
|
||||||
public final boolean isInitialized() {
|
public final boolean isInitialized() {
|
||||||
|
|
@ -5400,6 +5437,9 @@ public final class RemoteProtocol {
|
||||||
if (((bitField0_ & 0x00000008) == 0x00000008)) {
|
if (((bitField0_ & 0x00000008) == 0x00000008)) {
|
||||||
output.writeMessage(4, replicateActorFromUuid_);
|
output.writeMessage(4, replicateActorFromUuid_);
|
||||||
}
|
}
|
||||||
|
if (((bitField0_ & 0x00000010) == 0x00000010)) {
|
||||||
|
output.writeBytes(5, getSupervisorBytes());
|
||||||
|
}
|
||||||
getUnknownFields().writeTo(output);
|
getUnknownFields().writeTo(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5425,6 +5465,10 @@ public final class RemoteProtocol {
|
||||||
size += com.google.protobuf.CodedOutputStream
|
size += com.google.protobuf.CodedOutputStream
|
||||||
.computeMessageSize(4, replicateActorFromUuid_);
|
.computeMessageSize(4, replicateActorFromUuid_);
|
||||||
}
|
}
|
||||||
|
if (((bitField0_ & 0x00000010) == 0x00000010)) {
|
||||||
|
size += com.google.protobuf.CodedOutputStream
|
||||||
|
.computeBytesSize(5, getSupervisorBytes());
|
||||||
|
}
|
||||||
size += getUnknownFields().getSerializedSize();
|
size += getUnknownFields().getSerializedSize();
|
||||||
memoizedSerializedSize = size;
|
memoizedSerializedSize = size;
|
||||||
return size;
|
return size;
|
||||||
|
|
@ -5562,6 +5606,8 @@ public final class RemoteProtocol {
|
||||||
replicateActorFromUuidBuilder_.clear();
|
replicateActorFromUuidBuilder_.clear();
|
||||||
}
|
}
|
||||||
bitField0_ = (bitField0_ & ~0x00000008);
|
bitField0_ = (bitField0_ & ~0x00000008);
|
||||||
|
supervisor_ = "";
|
||||||
|
bitField0_ = (bitField0_ & ~0x00000010);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5620,6 +5666,10 @@ public final class RemoteProtocol {
|
||||||
} else {
|
} else {
|
||||||
result.replicateActorFromUuid_ = replicateActorFromUuidBuilder_.build();
|
result.replicateActorFromUuid_ = replicateActorFromUuidBuilder_.build();
|
||||||
}
|
}
|
||||||
|
if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
|
||||||
|
to_bitField0_ |= 0x00000010;
|
||||||
|
}
|
||||||
|
result.supervisor_ = supervisor_;
|
||||||
result.bitField0_ = to_bitField0_;
|
result.bitField0_ = to_bitField0_;
|
||||||
onBuilt();
|
onBuilt();
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -5648,6 +5698,9 @@ public final class RemoteProtocol {
|
||||||
if (other.hasReplicateActorFromUuid()) {
|
if (other.hasReplicateActorFromUuid()) {
|
||||||
mergeReplicateActorFromUuid(other.getReplicateActorFromUuid());
|
mergeReplicateActorFromUuid(other.getReplicateActorFromUuid());
|
||||||
}
|
}
|
||||||
|
if (other.hasSupervisor()) {
|
||||||
|
setSupervisor(other.getSupervisor());
|
||||||
|
}
|
||||||
this.mergeUnknownFields(other.getUnknownFields());
|
this.mergeUnknownFields(other.getUnknownFields());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
@ -5719,6 +5772,11 @@ public final class RemoteProtocol {
|
||||||
setReplicateActorFromUuid(subBuilder.buildPartial());
|
setReplicateActorFromUuid(subBuilder.buildPartial());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 42: {
|
||||||
|
bitField0_ |= 0x00000010;
|
||||||
|
supervisor_ = input.readBytes();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5899,6 +5957,42 @@ public final class RemoteProtocol {
|
||||||
return replicateActorFromUuidBuilder_;
|
return replicateActorFromUuidBuilder_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// optional string supervisor = 5;
|
||||||
|
private java.lang.Object supervisor_ = "";
|
||||||
|
public boolean hasSupervisor() {
|
||||||
|
return ((bitField0_ & 0x00000010) == 0x00000010);
|
||||||
|
}
|
||||||
|
public String getSupervisor() {
|
||||||
|
java.lang.Object ref = supervisor_;
|
||||||
|
if (!(ref instanceof String)) {
|
||||||
|
String s = ((com.google.protobuf.ByteString) ref).toStringUtf8();
|
||||||
|
supervisor_ = s;
|
||||||
|
return s;
|
||||||
|
} else {
|
||||||
|
return (String) ref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Builder setSupervisor(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
bitField0_ |= 0x00000010;
|
||||||
|
supervisor_ = value;
|
||||||
|
onChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Builder clearSupervisor() {
|
||||||
|
bitField0_ = (bitField0_ & ~0x00000010);
|
||||||
|
supervisor_ = getDefaultInstance().getSupervisor();
|
||||||
|
onChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
void setSupervisor(com.google.protobuf.ByteString value) {
|
||||||
|
bitField0_ |= 0x00000010;
|
||||||
|
supervisor_ = value;
|
||||||
|
onChanged();
|
||||||
|
}
|
||||||
|
|
||||||
// @@protoc_insertion_point(builder_scope:RemoteSystemDaemonMessageProtocol)
|
// @@protoc_insertion_point(builder_scope:RemoteSystemDaemonMessageProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -6655,27 +6749,27 @@ public final class RemoteProtocol {
|
||||||
"\t\022\r\n\005value\030\002 \002(\014\"A\n\017AddressProtocol\022\016\n\006s" +
|
"\t\022\r\n\005value\030\002 \002(\014\"A\n\017AddressProtocol\022\016\n\006s" +
|
||||||
"ystem\030\001 \002(\t\022\020\n\010hostname\030\002 \002(\t\022\014\n\004port\030\003 " +
|
"ystem\030\001 \002(\t\022\020\n\010hostname\030\002 \002(\t\022\014\n\004port\030\003 " +
|
||||||
"\002(\r\"7\n\021ExceptionProtocol\022\021\n\tclassname\030\001 " +
|
"\002(\r\"7\n\021ExceptionProtocol\022\021\n\tclassname\030\001 " +
|
||||||
"\002(\t\022\017\n\007message\030\002 \002(\t\"\253\001\n!RemoteSystemDae" +
|
"\002(\t\022\017\n\007message\030\002 \002(\t\"\277\001\n!RemoteSystemDae" +
|
||||||
"monMessageProtocol\0223\n\013messageType\030\001 \002(\0162",
|
"monMessageProtocol\0223\n\013messageType\030\001 \002(\0162",
|
||||||
"\036.RemoteSystemDaemonMessageType\022\021\n\tactor" +
|
"\036.RemoteSystemDaemonMessageType\022\021\n\tactor" +
|
||||||
"Path\030\002 \001(\t\022\017\n\007payload\030\003 \001(\014\022-\n\026replicate" +
|
"Path\030\002 \001(\t\022\017\n\007payload\030\003 \001(\014\022-\n\026replicate" +
|
||||||
"ActorFromUuid\030\004 \001(\0132\r.UuidProtocol\"y\n\035Du" +
|
"ActorFromUuid\030\004 \001(\0132\r.UuidProtocol\022\022\n\nsu" +
|
||||||
"rableMailboxMessageProtocol\022$\n\trecipient" +
|
"pervisor\030\005 \001(\t\"y\n\035DurableMailboxMessageP" +
|
||||||
"\030\001 \002(\0132\021.ActorRefProtocol\022!\n\006sender\030\002 \001(" +
|
"rotocol\022$\n\trecipient\030\001 \002(\0132\021.ActorRefPro" +
|
||||||
"\0132\021.ActorRefProtocol\022\017\n\007message\030\003 \002(\014*(\n" +
|
"tocol\022!\n\006sender\030\002 \001(\0132\021.ActorRefProtocol" +
|
||||||
"\013CommandType\022\013\n\007CONNECT\020\001\022\014\n\010SHUTDOWN\020\002*" +
|
"\022\017\n\007message\030\003 \002(\014*(\n\013CommandType\022\013\n\007CONN" +
|
||||||
"K\n\026ReplicationStorageType\022\r\n\tTRANSIENT\020\001" +
|
"ECT\020\001\022\014\n\010SHUTDOWN\020\002*K\n\026ReplicationStorag" +
|
||||||
"\022\023\n\017TRANSACTION_LOG\020\002\022\r\n\tDATA_GRID\020\003*>\n\027" +
|
"eType\022\r\n\tTRANSIENT\020\001\022\023\n\017TRANSACTION_LOG\020" +
|
||||||
"ReplicationStrategyType\022\021\n\rWRITE_THROUGH",
|
"\002\022\r\n\tDATA_GRID\020\003*>\n\027ReplicationStrategyT",
|
||||||
"\020\001\022\020\n\014WRITE_BEHIND\020\002*\241\002\n\035RemoteSystemDae" +
|
"ype\022\021\n\rWRITE_THROUGH\020\001\022\020\n\014WRITE_BEHIND\020\002" +
|
||||||
"monMessageType\022\010\n\004STOP\020\001\022\007\n\003USE\020\002\022\013\n\007REL" +
|
"*\241\002\n\035RemoteSystemDaemonMessageType\022\010\n\004ST" +
|
||||||
"EASE\020\003\022\022\n\016MAKE_AVAILABLE\020\004\022\024\n\020MAKE_UNAVA" +
|
"OP\020\001\022\007\n\003USE\020\002\022\013\n\007RELEASE\020\003\022\022\n\016MAKE_AVAIL" +
|
||||||
"ILABLE\020\005\022\016\n\nDISCONNECT\020\006\022\r\n\tRECONNECT\020\007\022" +
|
"ABLE\020\004\022\024\n\020MAKE_UNAVAILABLE\020\005\022\016\n\nDISCONNE" +
|
||||||
"\n\n\006RESIGN\020\010\022\n\n\006GOSSIP\020\t\022\031\n\025FAIL_OVER_CON" +
|
"CT\020\006\022\r\n\tRECONNECT\020\007\022\n\n\006RESIGN\020\010\022\n\n\006GOSSI" +
|
||||||
"NECTIONS\020\024\022\026\n\022FUNCTION_FUN0_UNIT\020\025\022\025\n\021FU" +
|
"P\020\t\022\031\n\025FAIL_OVER_CONNECTIONS\020\024\022\026\n\022FUNCTI" +
|
||||||
"NCTION_FUN0_ANY\020\026\022\032\n\026FUNCTION_FUN1_ARG_U" +
|
"ON_FUN0_UNIT\020\025\022\025\n\021FUNCTION_FUN0_ANY\020\026\022\032\n" +
|
||||||
"NIT\020\027\022\031\n\025FUNCTION_FUN1_ARG_ANY\020\030B\017\n\013akka" +
|
"\026FUNCTION_FUN1_ARG_UNIT\020\027\022\031\n\025FUNCTION_FU" +
|
||||||
".remoteH\001"
|
"N1_ARG_ANY\020\030B\017\n\013akka.remoteH\001"
|
||||||
};
|
};
|
||||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||||
|
|
@ -6759,7 +6853,7 @@ public final class RemoteProtocol {
|
||||||
internal_static_RemoteSystemDaemonMessageProtocol_fieldAccessorTable = new
|
internal_static_RemoteSystemDaemonMessageProtocol_fieldAccessorTable = new
|
||||||
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
||||||
internal_static_RemoteSystemDaemonMessageProtocol_descriptor,
|
internal_static_RemoteSystemDaemonMessageProtocol_descriptor,
|
||||||
new java.lang.String[] { "MessageType", "ActorPath", "Payload", "ReplicateActorFromUuid", },
|
new java.lang.String[] { "MessageType", "ActorPath", "Payload", "ReplicateActorFromUuid", "Supervisor", },
|
||||||
akka.remote.RemoteProtocol.RemoteSystemDaemonMessageProtocol.class,
|
akka.remote.RemoteProtocol.RemoteSystemDaemonMessageProtocol.class,
|
||||||
akka.remote.RemoteProtocol.RemoteSystemDaemonMessageProtocol.Builder.class);
|
akka.remote.RemoteProtocol.RemoteSystemDaemonMessageProtocol.Builder.class);
|
||||||
internal_static_DurableMailboxMessageProtocol_descriptor =
|
internal_static_DurableMailboxMessageProtocol_descriptor =
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ message RemoteSystemDaemonMessageProtocol {
|
||||||
optional string actorPath = 2;
|
optional string actorPath = 2;
|
||||||
optional bytes payload = 3;
|
optional bytes payload = 3;
|
||||||
optional UuidProtocol replicateActorFromUuid = 4;
|
optional UuidProtocol replicateActorFromUuid = 4;
|
||||||
|
optional string supervisor = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ class Gossiper(remote: Remote) {
|
||||||
node ← oldAvailableNodes
|
node ← oldAvailableNodes
|
||||||
if connectionManager.connectionFor(node).isEmpty
|
if connectionManager.connectionFor(node).isEmpty
|
||||||
} {
|
} {
|
||||||
val connectionFactory = () ⇒ new RemoteActorRef(remote.system.provider, remote.server, RootActorPath(gossipingNode) / remote.remoteDaemon.path.elements, None)
|
val connectionFactory = () ⇒ new RemoteActorRef(remote.system.provider, remote.server, RootActorPath(gossipingNode) / remote.remoteDaemon.path.elements, Nobody, None)
|
||||||
connectionManager.putIfAbsent(node, connectionFactory) // create a new remote connection to the new node
|
connectionManager.putIfAbsent(node, connectionFactory) // create a new remote connection to the new node
|
||||||
oldState.nodeMembershipChangeListeners foreach (_ nodeConnected node) // notify listeners about the new nodes
|
oldState.nodeMembershipChangeListeners foreach (_ nodeConnected node) // notify listeners about the new nodes
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@
|
||||||
|
|
||||||
package akka.remote
|
package akka.remote
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
|
||||||
import akka.actor._
|
import akka.actor._
|
||||||
import akka.event.Logging
|
import akka.event._
|
||||||
import akka.actor.Status._
|
import akka.actor.Status._
|
||||||
import akka.util._
|
import akka.util._
|
||||||
import akka.util.duration._
|
import akka.util.duration._
|
||||||
|
|
@ -17,13 +16,12 @@ import akka.remote.RemoteProtocol._
|
||||||
import akka.remote.RemoteProtocol.RemoteSystemDaemonMessageType._
|
import akka.remote.RemoteProtocol.RemoteSystemDaemonMessageType._
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import com.eaio.uuid.UUID
|
import com.eaio.uuid.UUID
|
||||||
import akka.serialization.{ JavaSerializer, Serialization, Serializer, Compression }
|
import akka.serialization.{ JavaSerializer, Serialization, Serializer, Compression, SerializationExtension }
|
||||||
import akka.dispatch.{ Terminate, Dispatchers, Future, PinnedDispatcher }
|
import akka.dispatch.{ Terminate, Dispatchers, Future, PinnedDispatcher }
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||||
import akka.serialization.SerializationExtension
|
|
||||||
import akka.dispatch.SystemMessage
|
import akka.dispatch.SystemMessage
|
||||||
import akka.event.LoggingAdapter
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remote module - contains remote client and server config, remote server instance, remote daemon, remote dispatchers etc.
|
* Remote module - contains remote client and server config, remote server instance, remote daemon, remote dispatchers etc.
|
||||||
|
|
@ -37,33 +35,15 @@ class Remote(val system: ActorSystemImpl, val nodename: String, val remoteSettin
|
||||||
import system._
|
import system._
|
||||||
import settings._
|
import settings._
|
||||||
|
|
||||||
private[remote] val serialization = SerializationExtension(system)
|
val serialization = SerializationExtension(system)
|
||||||
private[remote] val remoteAddress = {
|
|
||||||
RemoteAddress(system.name, remoteSettings.serverSettings.Hostname, remoteSettings.serverSettings.Port)
|
val remoteAddress = RemoteAddress(system.name, remoteSettings.serverSettings.Hostname, remoteSettings.serverSettings.Port)
|
||||||
}
|
|
||||||
|
|
||||||
val failureDetector = new AccrualFailureDetector(remoteSettings.FailureDetectorThreshold, remoteSettings.FailureDetectorMaxSampleSize)
|
val failureDetector = new AccrualFailureDetector(remoteSettings.FailureDetectorThreshold, remoteSettings.FailureDetectorMaxSampleSize)
|
||||||
|
|
||||||
// val gossiper = new Gossiper(this)
|
|
||||||
|
|
||||||
val remoteDaemonServiceName = "akka-system-remote-daemon".intern
|
|
||||||
|
|
||||||
val computeGridDispatcher = dispatcherFactory.fromConfig("akka.remote.compute-grid-dispatcher")
|
val computeGridDispatcher = dispatcherFactory.fromConfig("akka.remote.compute-grid-dispatcher")
|
||||||
|
|
||||||
// FIXME it is probably better to create another supervisor for handling the children created by handle_*, ticket #1408
|
val remoteDaemon = new RemoteSystemDaemon(this, provider.rootPath / "remote", provider.rootGuardian, log)
|
||||||
val remoteDaemonSupervisor =
|
|
||||||
system.provider.actorOf(system,
|
|
||||||
Props(OneForOneStrategy(List(classOf[Exception]), None, None)),
|
|
||||||
system.provider.rootGuardian,
|
|
||||||
"akka-system-remote-supervisor",
|
|
||||||
systemService = true) // is infinite restart what we want?
|
|
||||||
|
|
||||||
val remoteDaemon =
|
|
||||||
system.provider.actorOf(system,
|
|
||||||
Props(new RemoteSystemDaemon(this)).withDispatcher(dispatcherFactory.newPinnedDispatcher(remoteDaemonServiceName)),
|
|
||||||
remoteDaemonSupervisor,
|
|
||||||
remoteDaemonServiceName,
|
|
||||||
systemService = true)
|
|
||||||
|
|
||||||
val remoteClientLifeCycleHandler = system.actorOf(Props(new Actor {
|
val remoteClientLifeCycleHandler = system.actorOf(Props(new Actor {
|
||||||
def receive = {
|
def receive = {
|
||||||
|
|
@ -96,7 +76,7 @@ class Remote(val system: ActorSystemImpl, val nodename: String, val remoteSettin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Starting remote server on [{}] and starting remoteDaemon {}", remoteAddress, remoteDaemon)
|
log.info("Starting remote server on [{}]", remoteAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -106,18 +86,37 @@ class Remote(val system: ActorSystemImpl, val nodename: String, val remoteSettin
|
||||||
*
|
*
|
||||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||||
*/
|
*/
|
||||||
class RemoteSystemDaemon(remote: Remote) extends Actor {
|
class RemoteSystemDaemon(remote: Remote, _path: ActorPath, _parent: InternalActorRef, _log: LoggingAdapter)
|
||||||
|
extends VirtualPathContainer(_path, _parent, _log) {
|
||||||
|
|
||||||
import remote._
|
/**
|
||||||
import remote.{ system ⇒ systemImpl }
|
* Find the longest matching path which we know about and return that ref
|
||||||
|
* (or ask that ref to continue searching if elements are left).
|
||||||
|
*/
|
||||||
|
override def getChild(names: Iterator[String]): InternalActorRef = {
|
||||||
|
|
||||||
override def preRestart(reason: Throwable, msg: Option[Any]) {
|
@tailrec
|
||||||
log.debug("RemoteSystemDaemon failed due to [{}] - restarting...", reason)
|
def rec(s: String, n: Int): (InternalActorRef, Int) = {
|
||||||
|
getChild(s) match {
|
||||||
|
case null ⇒
|
||||||
|
val last = s.lastIndexOf('/')
|
||||||
|
if (last == -1) (Nobody, n)
|
||||||
|
else rec(s.substring(0, last), n + 1)
|
||||||
|
case ref ⇒ (ref, n)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def receive: Actor.Receive = {
|
val full = Vector() ++ names
|
||||||
|
rec(full.mkString("/"), 0) match {
|
||||||
|
case (Nobody, _) ⇒ Nobody
|
||||||
|
case (ref, n) if n == 0 ⇒ ref
|
||||||
|
case (ref, n) ⇒ ref.getChild(full.takeRight(n).iterator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def !(msg: Any)(implicit sender: ActorRef = null): Unit = msg match {
|
||||||
case message: RemoteSystemDaemonMessageProtocol ⇒
|
case message: RemoteSystemDaemonMessageProtocol ⇒
|
||||||
log.debug("Received command [\n{}] to RemoteSystemDaemon on [{}]", message.getMessageType, nodename)
|
log.debug("Received command [\n{}] to RemoteSystemDaemon on [{}]", message.getMessageType, remote.nodename)
|
||||||
|
|
||||||
message.getMessageType match {
|
message.getMessageType match {
|
||||||
case USE ⇒ handleUse(message)
|
case USE ⇒ handleUse(message)
|
||||||
|
|
@ -128,53 +127,48 @@ class RemoteSystemDaemon(remote: Remote) extends Actor {
|
||||||
// case RESIGN ⇒ cluster.resign()
|
// case RESIGN ⇒ cluster.resign()
|
||||||
// case FAIL_OVER_CONNECTIONS ⇒ handleFailover(message)
|
// case FAIL_OVER_CONNECTIONS ⇒ handleFailover(message)
|
||||||
case GOSSIP ⇒ handleGossip(message)
|
case GOSSIP ⇒ handleGossip(message)
|
||||||
case FUNCTION_FUN0_UNIT ⇒ handle_fun0_unit(message)
|
// case FUNCTION_FUN0_UNIT ⇒ handle_fun0_unit(message)
|
||||||
case FUNCTION_FUN0_ANY ⇒ handle_fun0_any(message)
|
// case FUNCTION_FUN0_ANY ⇒ handle_fun0_any(message, sender)
|
||||||
case FUNCTION_FUN1_ARG_UNIT ⇒ handle_fun1_arg_unit(message)
|
// case FUNCTION_FUN1_ARG_UNIT ⇒ handle_fun1_arg_unit(message)
|
||||||
case FUNCTION_FUN1_ARG_ANY ⇒ handle_fun1_arg_any(message)
|
// case FUNCTION_FUN1_ARG_ANY ⇒ handle_fun1_arg_any(message, sender)
|
||||||
//TODO: should we not deal with unrecognized message types?
|
case unknown ⇒ log.warning("Unknown message type {} received by {}", unknown, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
case unknown ⇒ log.warning("Unknown message to RemoteSystemDaemon [{}]", unknown)
|
case Terminated(child) ⇒ removeChild(child.path.elements.drop(1).mkString("/"))
|
||||||
|
|
||||||
|
case unknown ⇒ log.warning("Unknown message {} received by {}", unknown, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
def handleUse(message: RemoteSystemDaemonMessageProtocol) {
|
def handleUse(message: RemoteSystemDaemonMessageProtocol) {
|
||||||
try {
|
|
||||||
if (message.hasActorPath) {
|
if (!message.hasActorPath || !message.hasSupervisor) log.error("Ignoring incomplete USE command [{}]", message)
|
||||||
|
else {
|
||||||
|
|
||||||
val actorFactoryBytes =
|
val actorFactoryBytes =
|
||||||
if (remoteSettings.ShouldCompressData) LZF.uncompress(message.getPayload.toByteArray) else message.getPayload.toByteArray
|
if (remote.remoteSettings.ShouldCompressData) LZF.uncompress(message.getPayload.toByteArray)
|
||||||
|
else message.getPayload.toByteArray
|
||||||
|
|
||||||
val actorFactory =
|
val actorFactory =
|
||||||
serialization.deserialize(actorFactoryBytes, classOf[() ⇒ Actor], None) match {
|
remote.serialization.deserialize(actorFactoryBytes, classOf[() ⇒ Actor], None) match {
|
||||||
case Left(error) ⇒ throw error
|
case Left(error) ⇒ throw error
|
||||||
case Right(instance) ⇒ instance.asInstanceOf[() ⇒ Actor]
|
case Right(instance) ⇒ instance.asInstanceOf[() ⇒ Actor]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import remote.remoteAddress
|
||||||
|
|
||||||
message.getActorPath match {
|
message.getActorPath match {
|
||||||
case RemoteActorPath(`remoteAddress`, elems) if elems.size > 0 ⇒
|
case RemoteActorPath(`remoteAddress`, elems) if elems.size > 0 && elems.head == "remote" ⇒
|
||||||
val name = elems.last
|
// TODO RK canonicalize path so as not to duplicate it always
|
||||||
systemImpl.provider.actorFor(systemImpl.lookupRoot, elems.dropRight(1)) match {
|
val subpath = elems.drop(1)
|
||||||
case x if x eq system.deadLetters ⇒
|
val path = remote.remoteDaemon.path / subpath
|
||||||
log.error("Parent actor does not exist, ignoring remote system daemon command [{}]", message)
|
val supervisor = remote.system.actorFor(message.getSupervisor).asInstanceOf[InternalActorRef]
|
||||||
case parent ⇒
|
val actor = new LocalActorRef(remote.system, Props(creator = actorFactory), supervisor, path, true)
|
||||||
systemImpl.provider.actorOf(systemImpl, Props(creator = actorFactory), parent, name)
|
addChild(subpath.mkString("/"), actor)
|
||||||
}
|
remote.system.deathWatch.subscribe(this, actor)
|
||||||
case _ ⇒
|
case _ ⇒
|
||||||
log.error("remote path does not match path from message [{}]", message)
|
log.error("remote path does not match path from message [{}]", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
|
||||||
log.error("Actor 'address' for actor to instantiate is not defined, ignoring remote system daemon command [{}]", message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sender ! Success(remoteAddress)
|
|
||||||
} catch {
|
|
||||||
case exc: Exception ⇒
|
|
||||||
sender ! Failure(exc)
|
|
||||||
throw exc
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME implement handleRelease
|
// FIXME implement handleRelease
|
||||||
|
|
@ -201,45 +195,47 @@ class RemoteSystemDaemon(remote: Remote) extends Actor {
|
||||||
/*
|
/*
|
||||||
* generate name for temporary actor refs
|
* generate name for temporary actor refs
|
||||||
*/
|
*/
|
||||||
private val tempNumber = new AtomicLong
|
// private val tempNumber = new AtomicLong
|
||||||
def tempName = "$_" + Helpers.base64(tempNumber.getAndIncrement())
|
// def tempName = "$_" + Helpers.base64(tempNumber.getAndIncrement())
|
||||||
def tempPath = remoteDaemon.path / tempName
|
// def tempPath = remote.remoteDaemon.path / tempName
|
||||||
|
//
|
||||||
// FIXME: handle real remote supervision, ticket #1408
|
// // FIXME: handle real remote supervision, ticket #1408
|
||||||
def handle_fun0_unit(message: RemoteSystemDaemonMessageProtocol) {
|
// def handle_fun0_unit(message: RemoteSystemDaemonMessageProtocol) {
|
||||||
new LocalActorRef(systemImpl,
|
// new LocalActorRef(remote.system,
|
||||||
Props(
|
// Props(
|
||||||
context ⇒ {
|
// context ⇒ {
|
||||||
case f: Function0[_] ⇒ try { f() } finally { context.self.stop() }
|
// case f: Function0[_] ⇒ try { f() } finally { context.self.stop() }
|
||||||
}).copy(dispatcher = computeGridDispatcher), remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Function0[Unit]])
|
// }).copy(dispatcher = remote.computeGridDispatcher), remote.remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Function0[Unit]])
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// FIXME: handle real remote supervision, ticket #1408
|
// // FIXME: handle real remote supervision, ticket #1408
|
||||||
def handle_fun0_any(message: RemoteSystemDaemonMessageProtocol) {
|
// def handle_fun0_any(message: RemoteSystemDaemonMessageProtocol, sender: ActorRef) {
|
||||||
new LocalActorRef(systemImpl,
|
// implicit val s = sender
|
||||||
Props(
|
// new LocalActorRef(remote.system,
|
||||||
context ⇒ {
|
// Props(
|
||||||
case f: Function0[_] ⇒ try { sender ! f() } finally { context.self.stop() }
|
// context ⇒ {
|
||||||
}).copy(dispatcher = computeGridDispatcher), remoteDaemon, tempPath, systemService = true) forward payloadFor(message, classOf[Function0[Any]])
|
// case f: Function0[_] ⇒ try { context.sender ! f() } finally { context.self.stop() }
|
||||||
}
|
// }).copy(dispatcher = remote.computeGridDispatcher), remote.remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Function0[Any]])
|
||||||
|
// }
|
||||||
// FIXME: handle real remote supervision, ticket #1408
|
//
|
||||||
def handle_fun1_arg_unit(message: RemoteSystemDaemonMessageProtocol) {
|
// // FIXME: handle real remote supervision, ticket #1408
|
||||||
new LocalActorRef(systemImpl,
|
// def handle_fun1_arg_unit(message: RemoteSystemDaemonMessageProtocol) {
|
||||||
Props(
|
// new LocalActorRef(remote.system,
|
||||||
context ⇒ {
|
// Props(
|
||||||
case (fun: Function[_, _], param: Any) ⇒ try { fun.asInstanceOf[Any ⇒ Unit].apply(param) } finally { context.self.stop() }
|
// context ⇒ {
|
||||||
}).copy(dispatcher = computeGridDispatcher), remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Tuple2[Function1[Any, Unit], Any]])
|
// case (fun: Function[_, _], param: Any) ⇒ try { fun.asInstanceOf[Any ⇒ Unit].apply(param) } finally { context.self.stop() }
|
||||||
}
|
// }).copy(dispatcher = remote.computeGridDispatcher), remote.remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Tuple2[Function1[Any, Unit], Any]])
|
||||||
|
// }
|
||||||
// FIXME: handle real remote supervision, ticket #1408
|
//
|
||||||
def handle_fun1_arg_any(message: RemoteSystemDaemonMessageProtocol) {
|
// // FIXME: handle real remote supervision, ticket #1408
|
||||||
new LocalActorRef(systemImpl,
|
// def handle_fun1_arg_any(message: RemoteSystemDaemonMessageProtocol, sender: ActorRef) {
|
||||||
Props(
|
// implicit val s = sender
|
||||||
context ⇒ {
|
// new LocalActorRef(remote.system,
|
||||||
case (fun: Function[_, _], param: Any) ⇒ try { sender ! fun.asInstanceOf[Any ⇒ Any](param) } finally { context.self.stop() }
|
// Props(
|
||||||
}).copy(dispatcher = computeGridDispatcher), remoteDaemon, tempPath, systemService = true) forward payloadFor(message, classOf[Tuple2[Function1[Any, Any], Any]])
|
// context ⇒ {
|
||||||
}
|
// case (fun: Function[_, _], param: Any) ⇒ try { context.sender ! fun.asInstanceOf[Any ⇒ Any](param) } finally { context.self.stop() }
|
||||||
|
// }).copy(dispatcher = remote.computeGridDispatcher), remote.remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Tuple2[Function1[Any, Any], Any]])
|
||||||
|
// }
|
||||||
|
|
||||||
def handleFailover(message: RemoteSystemDaemonMessageProtocol) {
|
def handleFailover(message: RemoteSystemDaemonMessageProtocol) {
|
||||||
// val (from, to) = payloadFor(message, classOf[(InetSocketremoteDaemonServiceName, InetSocketremoteDaemonServiceName)])
|
// val (from, to) = payloadFor(message, classOf[(InetSocketremoteDaemonServiceName, InetSocketremoteDaemonServiceName)])
|
||||||
|
|
@ -247,7 +243,7 @@ class RemoteSystemDaemon(remote: Remote) extends Actor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private def payloadFor[T](message: RemoteSystemDaemonMessageProtocol, clazz: Class[T]): T = {
|
private def payloadFor[T](message: RemoteSystemDaemonMessageProtocol, clazz: Class[T]): T = {
|
||||||
serialization.deserialize(message.getPayload.toByteArray, clazz, None) match {
|
remote.serialization.deserialize(message.getPayload.toByteArray, clazz, None) match {
|
||||||
case Left(error) ⇒ throw error
|
case Left(error) ⇒ throw error
|
||||||
case Right(instance) ⇒ instance.asInstanceOf[T]
|
case Right(instance) ⇒ instance.asInstanceOf[T]
|
||||||
}
|
}
|
||||||
|
|
@ -275,6 +271,8 @@ trait RemoteMarshallingOps {
|
||||||
|
|
||||||
def system: ActorSystem
|
def system: ActorSystem
|
||||||
|
|
||||||
|
def remote: Remote
|
||||||
|
|
||||||
protected def useUntrustedMode: Boolean
|
protected def useUntrustedMode: Boolean
|
||||||
|
|
||||||
def createMessageSendEnvelope(rmp: RemoteMessageProtocol): AkkaRemoteProtocol = {
|
def createMessageSendEnvelope(rmp: RemoteMessageProtocol): AkkaRemoteProtocol = {
|
||||||
|
|
@ -312,7 +310,18 @@ trait RemoteMarshallingOps {
|
||||||
def receiveMessage(remoteMessage: RemoteMessage) {
|
def receiveMessage(remoteMessage: RemoteMessage) {
|
||||||
log.debug("received message {}", remoteMessage)
|
log.debug("received message {}", remoteMessage)
|
||||||
|
|
||||||
|
val remoteDaemon = remote.remoteDaemon
|
||||||
|
|
||||||
remoteMessage.recipient match {
|
remoteMessage.recipient match {
|
||||||
|
case `remoteDaemon` ⇒
|
||||||
|
remoteMessage.payload match {
|
||||||
|
case m: RemoteSystemDaemonMessageProtocol ⇒
|
||||||
|
implicit val timeout = system.settings.ActorTimeout
|
||||||
|
try remoteDaemon ! m catch {
|
||||||
|
case e: Exception ⇒ log.error(e, "exception while processing remote command {} from {}", m.getMessageType(), remoteMessage.sender)
|
||||||
|
}
|
||||||
|
case x ⇒ log.warning("remoteDaemon received illegal message {} from {}", x, remoteMessage.sender)
|
||||||
|
}
|
||||||
case l @ (_: LocalActorRef | _: MinimalActorRef) ⇒
|
case l @ (_: LocalActorRef | _: MinimalActorRef) ⇒
|
||||||
remoteMessage.payload match {
|
remoteMessage.payload match {
|
||||||
case msg: SystemMessage ⇒
|
case msg: SystemMessage ⇒
|
||||||
|
|
|
||||||
|
|
@ -48,12 +48,9 @@ class RemoteActorRefProvider(
|
||||||
def nodename = remoteSettings.NodeName
|
def nodename = remoteSettings.NodeName
|
||||||
def clustername = remoteSettings.ClusterName
|
def clustername = remoteSettings.ClusterName
|
||||||
|
|
||||||
private val actors = new ConcurrentHashMap[String, AnyRef]
|
|
||||||
|
|
||||||
val rootPath: ActorPath = RootActorPath(RemoteAddress(systemName, remoteSettings.serverSettings.Hostname, remoteSettings.serverSettings.Port))
|
val rootPath: ActorPath = RootActorPath(RemoteAddress(systemName, remoteSettings.serverSettings.Hostname, remoteSettings.serverSettings.Port))
|
||||||
private val local = new LocalActorRefProvider(systemName, settings, eventStream, scheduler, _deadLetters, rootPath)
|
private val local = new LocalActorRefProvider(systemName, settings, eventStream, scheduler, _deadLetters, rootPath)
|
||||||
private var serialization: Serialization = _
|
private var serialization: Serialization = _
|
||||||
private var remoteDaemonConnectionManager: RemoteConnectionManager = _
|
|
||||||
|
|
||||||
private var _remote: Remote = _
|
private var _remote: Remote = _
|
||||||
def remote = _remote
|
def remote = _remote
|
||||||
|
|
@ -62,7 +59,7 @@ class RemoteActorRefProvider(
|
||||||
local.init(system)
|
local.init(system)
|
||||||
serialization = SerializationExtension(system)
|
serialization = SerializationExtension(system)
|
||||||
_remote = new Remote(system, nodename, remoteSettings)
|
_remote = new Remote(system, nodename, remoteSettings)
|
||||||
remoteDaemonConnectionManager = new RemoteConnectionManager(system, remote)
|
local.registerExtraNames(Map(("remote", remote.remoteDaemon)))
|
||||||
terminationFuture.onComplete(_ ⇒ remote.server.shutdown())
|
terminationFuture.onComplete(_ ⇒ remote.server.shutdown())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,91 +74,81 @@ class RemoteActorRefProvider(
|
||||||
if (systemService) local.actorOf(system, props, supervisor, name, systemService)
|
if (systemService) local.actorOf(system, props, supervisor, name, systemService)
|
||||||
else {
|
else {
|
||||||
val path = supervisor.path / name
|
val path = supervisor.path / name
|
||||||
val newFuture = Promise[ActorRef](system.settings.ActorTimeout)(dispatcher)
|
|
||||||
|
|
||||||
actors.putIfAbsent(path.toString, newFuture) match { // we won the race -- create the actor and resolve the future
|
deployer.lookupDeploymentFor(path.elements.mkString("/", "/", "")) match {
|
||||||
case null ⇒
|
|
||||||
val actor: InternalActorRef = try {
|
|
||||||
deployer.lookupDeploymentFor(path.toString) match {
|
|
||||||
case Some(DeploymentConfig.Deploy(_, _, routerType, nrOfInstances, RemoteDeploymentConfig.RemoteScope(remoteAddresses))) ⇒
|
case Some(DeploymentConfig.Deploy(_, _, routerType, nrOfInstances, RemoteDeploymentConfig.RemoteScope(remoteAddresses))) ⇒
|
||||||
|
|
||||||
def isReplicaNode: Boolean = remoteAddresses exists { _ == remote.remoteAddress }
|
// FIXME RK deployer shall only concern itself with placement of actors on remote nodes
|
||||||
|
val address = remoteAddresses.head
|
||||||
//system.eventHandler.debug(this, "%s: Deploy Remote Actor with address [%s] connected to [%s]: isReplica(%s)".format(system.defaultAddress, address, remoteAddresses.mkString, isReplicaNode))
|
if (address == rootPath.address) local.actorOf(system, props, supervisor, name, true) // FIXME RK make non-system
|
||||||
|
else {
|
||||||
if (isReplicaNode) {
|
val rpath = RootActorPath(address) / "remote" / rootPath.address.hostPort / path.elements
|
||||||
// we are on one of the replica node for this remote actor
|
useActorOnNode(rpath, props.creator, supervisor)
|
||||||
local.actorOf(system, props, supervisor, name, true) //FIXME systemService = true here to bypass Deploy, should be fixed when create-or-get is replaced by get-or-create (is this fixed now?)
|
new RemoteActorRef(this, remote.server, rpath, supervisor, None)
|
||||||
} else {
|
|
||||||
|
|
||||||
implicit val dispatcher = if (props.dispatcher == Props.defaultDispatcher) system.dispatcher else props.dispatcher
|
|
||||||
implicit val timeout = system.settings.ActorTimeout
|
|
||||||
|
|
||||||
// we are on the single "reference" node uses the remote actors on the replica nodes
|
|
||||||
val routerFactory: () ⇒ Router = DeploymentConfig.routerTypeFor(routerType) match {
|
|
||||||
case RouterType.Direct ⇒
|
|
||||||
if (remoteAddresses.size != 1) throw new ConfigurationException(
|
|
||||||
"Actor [%s] configured with Direct router must have exactly 1 remote node configured. Found [%s]"
|
|
||||||
.format(name, remoteAddresses.mkString(", ")))
|
|
||||||
() ⇒ new DirectRouter
|
|
||||||
|
|
||||||
case RouterType.Broadcast ⇒
|
|
||||||
if (remoteAddresses.size != 1) throw new ConfigurationException(
|
|
||||||
"Actor [%s] configured with Broadcast router must have exactly 1 remote node configured. Found [%s]"
|
|
||||||
.format(name, remoteAddresses.mkString(", ")))
|
|
||||||
() ⇒ new BroadcastRouter
|
|
||||||
|
|
||||||
case RouterType.Random ⇒
|
|
||||||
if (remoteAddresses.size < 1) throw new ConfigurationException(
|
|
||||||
"Actor [%s] configured with Random router must have at least 1 remote node configured. Found [%s]"
|
|
||||||
.format(name, remoteAddresses.mkString(", ")))
|
|
||||||
() ⇒ new RandomRouter
|
|
||||||
|
|
||||||
case RouterType.RoundRobin ⇒
|
|
||||||
if (remoteAddresses.size < 1) throw new ConfigurationException(
|
|
||||||
"Actor [%s] configured with RoundRobin router must have at least 1 remote node configured. Found [%s]"
|
|
||||||
.format(name, remoteAddresses.mkString(", ")))
|
|
||||||
() ⇒ new RoundRobinRouter
|
|
||||||
|
|
||||||
case RouterType.ScatterGather ⇒
|
|
||||||
if (remoteAddresses.size < 1) throw new ConfigurationException(
|
|
||||||
"Actor [%s] configured with ScatterGather router must have at least 1 remote node configured. Found [%s]"
|
|
||||||
.format(name, remoteAddresses.mkString(", ")))
|
|
||||||
() ⇒ new ScatterGatherFirstCompletedRouter()(dispatcher, defaultTimeout)
|
|
||||||
|
|
||||||
case RouterType.LeastCPU ⇒ sys.error("Router LeastCPU not supported yet")
|
|
||||||
case RouterType.LeastRAM ⇒ sys.error("Router LeastRAM not supported yet")
|
|
||||||
case RouterType.LeastMessages ⇒ sys.error("Router LeastMessages not supported yet")
|
|
||||||
case RouterType.Custom(implClass) ⇒ () ⇒ Routing.createCustomRouter(implClass)
|
|
||||||
}
|
|
||||||
|
|
||||||
val connections = (Map.empty[RemoteAddress, ActorRef] /: remoteAddresses) { (conns, a) ⇒
|
|
||||||
conns + (a -> new RemoteActorRef(this, remote.server, path, None)) // FIXME RK correct path must be put in here
|
|
||||||
}
|
|
||||||
|
|
||||||
val connectionManager = new RemoteConnectionManager(system, remote, connections)
|
|
||||||
|
|
||||||
connections.keys foreach { useActorOnNode(system, _, path.toString, props.creator) }
|
|
||||||
|
|
||||||
actorOf(system, RoutedProps(routerFactory = routerFactory, connectionManager = connectionManager), supervisor, name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// def isReplicaNode: Boolean = remoteAddresses exists { _ == remote.remoteAddress }
|
||||||
|
//
|
||||||
|
// //system.eventHandler.debug(this, "%s: Deploy Remote Actor with address [%s] connected to [%s]: isReplica(%s)".format(system.defaultAddress, address, remoteAddresses.mkString, isReplicaNode))
|
||||||
|
//
|
||||||
|
// if (isReplicaNode) {
|
||||||
|
// // we are on one of the replica node for this remote actor
|
||||||
|
// local.actorOf(system, props, supervisor, name, true) //FIXME systemService = true here to bypass Deploy, should be fixed when create-or-get is replaced by get-or-create (is this fixed now?)
|
||||||
|
// } else {
|
||||||
|
//
|
||||||
|
// implicit val dispatcher = if (props.dispatcher == Props.defaultDispatcher) system.dispatcher else props.dispatcher
|
||||||
|
// implicit val timeout = system.settings.ActorTimeout
|
||||||
|
//
|
||||||
|
// // we are on the single "reference" node uses the remote actors on the replica nodes
|
||||||
|
// val routerFactory: () ⇒ Router = DeploymentConfig.routerTypeFor(routerType) match {
|
||||||
|
// case RouterType.Direct ⇒
|
||||||
|
// if (remoteAddresses.size != 1) throw new ConfigurationException(
|
||||||
|
// "Actor [%s] configured with Direct router must have exactly 1 remote node configured. Found [%s]"
|
||||||
|
// .format(name, remoteAddresses.mkString(", ")))
|
||||||
|
// () ⇒ new DirectRouter
|
||||||
|
//
|
||||||
|
// case RouterType.Broadcast ⇒
|
||||||
|
// if (remoteAddresses.size != 1) throw new ConfigurationException(
|
||||||
|
// "Actor [%s] configured with Broadcast router must have exactly 1 remote node configured. Found [%s]"
|
||||||
|
// .format(name, remoteAddresses.mkString(", ")))
|
||||||
|
// () ⇒ new BroadcastRouter
|
||||||
|
//
|
||||||
|
// case RouterType.Random ⇒
|
||||||
|
// if (remoteAddresses.size < 1) throw new ConfigurationException(
|
||||||
|
// "Actor [%s] configured with Random router must have at least 1 remote node configured. Found [%s]"
|
||||||
|
// .format(name, remoteAddresses.mkString(", ")))
|
||||||
|
// () ⇒ new RandomRouter
|
||||||
|
//
|
||||||
|
// case RouterType.RoundRobin ⇒
|
||||||
|
// if (remoteAddresses.size < 1) throw new ConfigurationException(
|
||||||
|
// "Actor [%s] configured with RoundRobin router must have at least 1 remote node configured. Found [%s]"
|
||||||
|
// .format(name, remoteAddresses.mkString(", ")))
|
||||||
|
// () ⇒ new RoundRobinRouter
|
||||||
|
//
|
||||||
|
// case RouterType.ScatterGather ⇒
|
||||||
|
// if (remoteAddresses.size < 1) throw new ConfigurationException(
|
||||||
|
// "Actor [%s] configured with ScatterGather router must have at least 1 remote node configured. Found [%s]"
|
||||||
|
// .format(name, remoteAddresses.mkString(", ")))
|
||||||
|
// () ⇒ new ScatterGatherFirstCompletedRouter()(dispatcher, defaultTimeout)
|
||||||
|
//
|
||||||
|
// case RouterType.LeastCPU ⇒ sys.error("Router LeastCPU not supported yet")
|
||||||
|
// case RouterType.LeastRAM ⇒ sys.error("Router LeastRAM not supported yet")
|
||||||
|
// case RouterType.LeastMessages ⇒ sys.error("Router LeastMessages not supported yet")
|
||||||
|
// case RouterType.Custom(implClass) ⇒ () ⇒ Routing.createCustomRouter(implClass)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val connections = (Map.empty[RemoteAddress, ActorRef] /: remoteAddresses) { (conns, a) ⇒
|
||||||
|
// conns + (a -> new RemoteActorRef(this, remote.server, path, None)) // FIXME RK correct path must be put in here
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val connectionManager = new RemoteConnectionManager(system, remote, connections)
|
||||||
|
//
|
||||||
|
// connections.keys foreach { useActorOnNode(system, _, path.toString, props.creator) }
|
||||||
|
//
|
||||||
|
// actorOf(system, RoutedProps(routerFactory = routerFactory, connectionManager = connectionManager), supervisor, name)
|
||||||
|
// }
|
||||||
case deploy ⇒ local.actorOf(system, props, supervisor, name, systemService)
|
case deploy ⇒ local.actorOf(system, props, supervisor, name, systemService)
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
case e: Exception ⇒
|
|
||||||
newFuture completeWithException e // so the other threads gets notified of error
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
|
|
||||||
// actor foreach system.registry.register // only for ActorRegistry backward compat, will be removed later
|
|
||||||
|
|
||||||
newFuture completeWithResult actor
|
|
||||||
actors.replace(path.toString, newFuture, actor)
|
|
||||||
actor
|
|
||||||
case actor: InternalActorRef ⇒ actor
|
|
||||||
case future: Future[_] ⇒ future.get.asInstanceOf[InternalActorRef]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -175,14 +162,14 @@ class RemoteActorRefProvider(
|
||||||
|
|
||||||
def actorFor(path: ActorPath): InternalActorRef = path.root match {
|
def actorFor(path: ActorPath): InternalActorRef = path.root match {
|
||||||
case `rootPath` ⇒ actorFor(rootGuardian, path.elements)
|
case `rootPath` ⇒ actorFor(rootGuardian, path.elements)
|
||||||
case RootActorPath(_: RemoteAddress, _) ⇒ new RemoteActorRef(this, remote.server, path, None)
|
case RootActorPath(_: RemoteAddress, _) ⇒ new RemoteActorRef(this, remote.server, path, Nobody, None)
|
||||||
case _ ⇒ local.actorFor(path)
|
case _ ⇒ local.actorFor(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
def actorFor(ref: InternalActorRef, path: String): InternalActorRef = path match {
|
def actorFor(ref: InternalActorRef, path: String): InternalActorRef = path match {
|
||||||
case RemoteActorPath(address, elems) ⇒
|
case RemoteActorPath(address, elems) ⇒
|
||||||
if (address == rootPath.address) actorFor(rootGuardian, elems)
|
if (address == rootPath.address) actorFor(rootGuardian, elems)
|
||||||
else new RemoteActorRef(this, remote.server, new RootActorPath(address) / elems, None)
|
else new RemoteActorRef(this, remote.server, new RootActorPath(address) / elems, Nobody, None)
|
||||||
case _ ⇒ local.actorFor(ref, path)
|
case _ ⇒ local.actorFor(ref, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,16 +179,11 @@ class RemoteActorRefProvider(
|
||||||
val optimizeLocal = new AtomicBoolean(true)
|
val optimizeLocal = new AtomicBoolean(true)
|
||||||
def optimizeLocalScoped_?() = optimizeLocal.get
|
def optimizeLocalScoped_?() = optimizeLocal.get
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the actor was in the provider's cache and evicted successfully, else false.
|
|
||||||
*/
|
|
||||||
private[akka] def evict(path: ActorPath): Boolean = actors.remove(path) ne null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using (checking out) actor on a specific node.
|
* Using (checking out) actor on a specific node.
|
||||||
*/
|
*/
|
||||||
def useActorOnNode(system: ActorSystem, remoteAddress: RemoteAddress, actorPath: String, actorFactory: () ⇒ Actor) {
|
def useActorOnNode(path: ActorPath, actorFactory: () ⇒ Actor, supervisor: ActorRef) {
|
||||||
log.debug("[{}] Instantiating Actor [{}] on node [{}]", rootPath, actorPath, remoteAddress)
|
log.debug("[{}] Instantiating Remote Actor [{}]", rootPath, path)
|
||||||
|
|
||||||
val actorFactoryBytes =
|
val actorFactoryBytes =
|
||||||
serialization.serialize(actorFactory) match {
|
serialization.serialize(actorFactory) match {
|
||||||
|
|
@ -211,16 +193,13 @@ class RemoteActorRefProvider(
|
||||||
|
|
||||||
val command = RemoteSystemDaemonMessageProtocol.newBuilder
|
val command = RemoteSystemDaemonMessageProtocol.newBuilder
|
||||||
.setMessageType(USE)
|
.setMessageType(USE)
|
||||||
.setActorPath(actorPath)
|
.setActorPath(path.toString)
|
||||||
.setPayload(ByteString.copyFrom(actorFactoryBytes))
|
.setPayload(ByteString.copyFrom(actorFactoryBytes))
|
||||||
|
.setSupervisor(supervisor.path.toString)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val connectionFactory = () ⇒ actorFor(RootActorPath(remoteAddress) / remote.remoteDaemon.path.elements)
|
// we don’t wait for the ACK, because the remote end will process this command before any other message to the new actor
|
||||||
|
actorFor(RootActorPath(path.address) / "remote") ! command
|
||||||
// try to get the connection for the remote address, if not already there then create it
|
|
||||||
val connection = remoteDaemonConnectionManager.putIfAbsent(remoteAddress, connectionFactory)
|
|
||||||
|
|
||||||
sendCommandToRemoteNode(connection, command, withACK = true) // ensure we get an ACK on the USE command
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def sendCommandToRemoteNode(connection: ActorRef, command: RemoteSystemDaemonMessageProtocol, withACK: Boolean) {
|
private def sendCommandToRemoteNode(connection: ActorRef, command: RemoteSystemDaemonMessageProtocol, withACK: Boolean) {
|
||||||
|
|
@ -265,12 +244,13 @@ private[akka] class RemoteActorRef private[akka] (
|
||||||
provider: ActorRefProvider,
|
provider: ActorRefProvider,
|
||||||
remote: RemoteSupport,
|
remote: RemoteSupport,
|
||||||
val path: ActorPath,
|
val path: ActorPath,
|
||||||
|
val getParent: InternalActorRef,
|
||||||
loader: Option[ClassLoader])
|
loader: Option[ClassLoader])
|
||||||
extends InternalActorRef {
|
extends InternalActorRef {
|
||||||
|
|
||||||
// FIXME RK
|
def getChild(name: Iterator[String]): InternalActorRef = {
|
||||||
def getParent = Nobody
|
new RemoteActorRef(provider, remote, path / name.toStream, Nobody, loader)
|
||||||
def getChild(name: Iterator[String]) = Nobody
|
}
|
||||||
|
|
||||||
@volatile
|
@volatile
|
||||||
private var running: Boolean = true
|
private var running: Boolean = true
|
||||||
|
|
|
||||||
|
|
@ -149,5 +149,5 @@ class RemoteConnectionManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
private[remote] def newConnection(remoteAddress: RemoteAddress, actorPath: ActorPath) =
|
private[remote] def newConnection(remoteAddress: RemoteAddress, actorPath: ActorPath) =
|
||||||
new RemoteActorRef(remote.system.provider, remote.server, actorPath, None)
|
new RemoteActorRef(remote.system.provider, remote.server, actorPath, Nobody, None)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,17 +44,7 @@ class RemoteDeployer(_settings: ActorSystem.Settings, _eventStream: EventStream,
|
||||||
"Config option [" + deploymentKey +
|
"Config option [" + deploymentKey +
|
||||||
".remote.nodes] needs to be a list with elements on format \"<hostname>:<port>\", was [" + remoteNodes.mkString(", ") + "]")
|
".remote.nodes] needs to be a list with elements on format \"<hostname>:<port>\", was [" + remoteNodes.mkString(", ") + "]")
|
||||||
|
|
||||||
val remoteAddresses = remoteNodes map { node ⇒
|
val remoteAddresses = remoteNodes map (RemoteAddress(_, settings.name))
|
||||||
val tokenizer = new java.util.StringTokenizer(node, ":")
|
|
||||||
val hostname = tokenizer.nextElement.toString
|
|
||||||
if ((hostname eq null) || (hostname == "")) raiseRemoteNodeParsingError()
|
|
||||||
val port = try tokenizer.nextElement.toString.toInt catch {
|
|
||||||
case e: Exception ⇒ raiseRemoteNodeParsingError()
|
|
||||||
}
|
|
||||||
if (port == 0) raiseRemoteNodeParsingError()
|
|
||||||
|
|
||||||
RemoteAddress(settings.name, hostname, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoteScope(remoteAddresses)
|
RemoteScope(remoteAddresses)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ import akka.testkit._
|
||||||
import akka.actor._
|
import akka.actor._
|
||||||
import com.typesafe.config._
|
import com.typesafe.config._
|
||||||
|
|
||||||
|
object RemoteCommunicationSpec {
|
||||||
|
val echo = Props(ctx ⇒ { case x ⇒ ctx.sender ! x })
|
||||||
|
}
|
||||||
|
|
||||||
class RemoteCommunicationSpec extends AkkaSpec("""
|
class RemoteCommunicationSpec extends AkkaSpec("""
|
||||||
akka {
|
akka {
|
||||||
actor.provider = "akka.remote.RemoteActorRefProvider"
|
actor.provider = "akka.remote.RemoteActorRefProvider"
|
||||||
|
|
@ -16,9 +20,16 @@ akka {
|
||||||
hostname = localhost
|
hostname = localhost
|
||||||
port = 12345
|
port = 12345
|
||||||
}
|
}
|
||||||
|
actor.deployment {
|
||||||
|
/user/blub {
|
||||||
|
remote.nodes = ["remote_sys@localhost:12346"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
""") with ImplicitSender {
|
""") with ImplicitSender {
|
||||||
|
|
||||||
|
import RemoteCommunicationSpec._
|
||||||
|
|
||||||
val conf = ConfigFactory.parseString("akka.remote.server.port=12346").withFallback(system.settings.config)
|
val conf = ConfigFactory.parseString("akka.remote.server.port=12346").withFallback(system.settings.config)
|
||||||
val other = ActorSystem("remote_sys", conf)
|
val other = ActorSystem("remote_sys", conf)
|
||||||
|
|
||||||
|
|
@ -67,6 +78,13 @@ akka {
|
||||||
}(other)
|
}(other)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"create children on remote node" in {
|
||||||
|
val r = system.actorOf(echo, "blub")
|
||||||
|
r.path.toString must be === "akka://remote_sys@localhost:12346/remote/RemoteCommunicationSpec@localhost:12345/user/blub"
|
||||||
|
r ! 42
|
||||||
|
expectMsg(42)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue