From a1117c693554b104e8e97d5d877508480bbae5e5 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 14 Dec 2010 10:11:17 +0100 Subject: [PATCH 01/38] Fixing a glitch in the API --- akka-actor/src/main/scala/akka/dispatch/Future.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index 2ac412d36d..2b17ed5313 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -43,7 +43,7 @@ object Futures { /** * Returns the First Future that is completed (blocking!) */ - def awaitOne(futures: List[Future[_]], timeout: Long = Long.MaxValue): Future[_] = firstCompletedOf(futures).await + def awaitOne(futures: List[Future[_]], timeout: Long = Long.MaxValue): Future[_] = firstCompletedOf(futures, timeout).await /** * Returns a Future to the result of the first future in the list that is completed From c89ea0a49cf9833459a341c9c50d017ddea24e56 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 14 Dec 2010 18:22:46 +0100 Subject: [PATCH 02/38] First shot at re-doing akka-remote --- .../src/main/scala/akka/actor/Actor.scala | 69 +++++-- .../src/main/scala/akka/actor/ActorRef.scala | 161 +++------------- .../main/scala/akka/actor/ActorRegistry.scala | 15 +- .../main/scala/akka/actor/Supervisor.scala | 8 +- .../main/scala/akka/actor/UntypedActor.scala | 10 - .../scala/akka/config/SupervisionConfig.scala | 14 +- .../remoteinterface/RemoteInterface.scala | 149 +++++++++++++++ .../scala/akka/util/ReflectiveAccess.scala | 30 --- .../remote/BootableRemoteActorService.scala | 10 +- .../main/scala/akka/remote/RemoteClient.scala | 8 +- .../main/scala/akka/remote/RemoteServer.scala | 178 ++++-------------- .../serialization/SerializationProtocol.scala | 35 ++-- .../ClientManagedRemoteActorSample.scala | 9 +- .../main/scala/akka/actor/TypedActor.scala | 6 +- .../config/TypedActorGuiceConfigurator.scala | 5 +- 15 files changed, 321 insertions(+), 386 deletions(-) create mode 100644 akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index c87d1460f9..58ceaf44b3 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -17,18 +17,6 @@ import scala.reflect.BeanProperty import akka.util. {ReflectiveAccess, Logging, Duration} import akka.japi.Procedure -/** - * Extend this abstract class to create a remote actor. - *

- * Equivalent to invoking the makeRemote(..) method in the body of the ActorJonas Bonér - */ -abstract class RemoteActor(address: InetSocketAddress) extends Actor { - def this(hostname: String, port: Int) = this(new InetSocketAddress(hostname, port)) - self.makeRemote(address) -} - /** * Life-cycle messages for the Actors */ @@ -155,17 +143,35 @@ object Actor extends Logging { def actorOf[T <: Actor : Manifest]: ActorRef = actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) /** - * Creates an ActorRef out of the Actor with type T. + * Creates a Client-managed ActorRef out of the Actor of the specified Class. + * If the supplied host and port is identical of the configured local node, it will be a local actor *

    *   import Actor._
-   *   val actor = actorOf[MyActor]
+   *   val actor = actorOf[MyActor]("www.akka.io",2552)
    *   actor.start
    *   actor ! message
    *   actor.stop
    * 
* You can create and start the actor in one statement like this: *
-   *   val actor = actorOf[MyActor].start
+   *   val actor = actorOf[MyActor]("www.akka.io",2552).start
+   * 
+ */ + def actorOf[T <: Actor : Manifest](host: String, port: Int): ActorRef = + actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]], host, port) + + /** + * Creates an ActorRef out of the Actor of the specified Class. + *
+   *   import Actor._
+   *   val actor = actorOf(classOf[MyActor])
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf(classOf[MyActor]).start
    * 
*/ def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(() => { @@ -178,6 +184,39 @@ object Actor extends Logging { "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")) }) + /** + * Creates a Client-managed ActorRef out of the Actor of the specified Class. + * If the supplied host and port is identical of the configured local node, it will be a local actor + *
+   *   import Actor._
+   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552)
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start
+   * 
+ */ + def actorOf(clazz: Class[_ <: Actor], host: String, port: Int, timeout: Long = TIMEOUT): ActorRef = { + import ReflectiveAccess._ + import ReflectiveAccess.RemoteServerModule.{HOSTNAME,PORT} + ensureRemotingEnabled + + (host,port) match { + case null => throw new IllegalArgumentException("No location specified") + case (HOSTNAME, PORT) => actorOf(clazz) //Local + case _ => new RemoteActorRef(clazz.getName, + clazz.getName, + host, + port, + timeout, + true, //Client managed + None) + } + } + /** * Creates an ActorRef out of the Actor. Allows you to pass in a factory function * that creates the Actor. Please note that this function can be invoked multiple diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index dbaf013705..d938366b62 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -77,8 +77,6 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal @volatile protected[this] var _status: ActorRefInternals.StatusType = ActorRefInternals.UNSTARTED @volatile - protected[akka] var _homeAddress = new InetSocketAddress(RemoteServerModule.HOSTNAME, RemoteServerModule.PORT) - @volatile protected[akka] var _futureTimeout: Option[ScheduledFuture[AnyRef]] = None protected[akka] val guard = new ReentrantGuard @@ -390,62 +388,6 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal */ def dispatcher: MessageDispatcher - /** - * Invoking 'makeRemote' means that an actor will be moved to and invoked on a remote host. - */ - def makeRemote(hostname: String, port: Int): Unit - - /** - * Invoking 'makeRemote' means that an actor will be moved to and invoked on a remote host. - */ - def makeRemote(address: InetSocketAddress): Unit - - /** - * Returns the home address and port for this actor. - */ - def homeAddress: InetSocketAddress = _homeAddress - - /** - * Akka Java API - * Returns the home address and port for this actor. - */ - def getHomeAddress(): InetSocketAddress = homeAddress - - /** - * Set the home address and port for this actor. - */ - def homeAddress_=(hostnameAndPort: Tuple2[String, Int]): Unit = - homeAddress_=(new InetSocketAddress(hostnameAndPort._1, hostnameAndPort._2)) - - /** - * Akka Java API - * Set the home address and port for this actor. - */ - def setHomeAddress(hostname: String, port: Int): Unit = homeAddress = (hostname, port) - - /** - * Set the home address and port for this actor. - */ - def homeAddress_=(address: InetSocketAddress): Unit - - /** - * Akka Java API - * Set the home address and port for this actor. - */ - def setHomeAddress(address: InetSocketAddress): Unit = homeAddress = address - - /** - * Returns the remote address for the actor, if any, else None. - */ - def remoteAddress: Option[InetSocketAddress] - protected[akka] def remoteAddress_=(addr: Option[InetSocketAddress]): Unit - - /** - * Akka Java API - * Gets the remote address for the actor, if any, else None. - */ - def getRemoteAddress(): Option[InetSocketAddress] = remoteAddress - /** * Starts up the actor and its message queue. */ @@ -562,7 +504,8 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Unit - protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] + //TODO: REVISIT: REMOVE + //protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] protected[akka] def linkedActors: JMap[Uuid, ActorRef] @@ -601,8 +544,6 @@ class LocalActorRef private[akka] ( private[this] val actorFactory: () => Actor) extends ActorRef with ScalaActorRef { - @volatile - private[akka] var _remoteAddress: Option[InetSocketAddress] = None // only mutable to maintain identity across nodes @volatile private[akka] lazy val _linkedActors = new ConcurrentHashMap[Uuid, ActorRef] @volatile @@ -635,7 +576,6 @@ class LocalActorRef private[akka] ( this(__factory) _uuid = __uuid id = __id - homeAddress = (__hostname, __port) timeout = __timeout receiveTimeout = __receiveTimeout lifeCycle = __lifeCycle @@ -674,41 +614,6 @@ class LocalActorRef private[akka] ( */ def dispatcher: MessageDispatcher = _dispatcher - /** - * Invoking 'makeRemote' means that an actor will be moved to and invoked on a remote host. - */ - def makeRemote(hostname: String, port: Int): Unit = { - ensureRemotingEnabled - if (!isRunning || isBeingRestarted) makeRemote(new InetSocketAddress(hostname, port)) - else throw new ActorInitializationException( - "Can't make a running actor remote. Make sure you call 'makeRemote' before 'start'.") - } - - /** - * Invoking 'makeRemote' means that an actor will be moved to and invoked on a remote host. - */ - def makeRemote(address: InetSocketAddress): Unit = guard.withGuard { - ensureRemotingEnabled - if (!isRunning || isBeingRestarted) { - _remoteAddress = Some(address) - RemoteClientModule.register(address, uuid) - homeAddress = (RemoteServerModule.HOSTNAME, RemoteServerModule.PORT) - } else throw new ActorInitializationException( - "Can't make a running actor remote. Make sure you call 'makeRemote' before 'start'.") - } - - /** - * Set the contact address for this actor. This is used for replying to messages - * sent asynchronously when no reply channel exists. - */ - def homeAddress_=(address: InetSocketAddress): Unit = _homeAddress = address - - /** - * Returns the remote address for the actor, if any, else None. - */ - def remoteAddress: Option[InetSocketAddress] = _remoteAddress - protected[akka] def remoteAddress_=(addr: Option[InetSocketAddress]): Unit = _remoteAddress = addr - /** * Starts up the actor and its message queue. */ @@ -740,11 +645,6 @@ class LocalActorRef private[akka] ( _status = ActorRefInternals.SHUTDOWN actor.postStop ActorRegistry.unregister(this) - if (isRemotingEnabled) { - if (remoteAddress.isDefined) - RemoteClientModule.unregister(remoteAddress.get, uuid) - RemoteServerModule.unregister(this) - } setActorSelfFields(actorInstance.get,null) } //else if (isBeingRestarted) throw new ActorKilledException("Actor [" + toString + "] is being restarted.") } @@ -795,11 +695,8 @@ class LocalActorRef private[akka] ( *

* To be invoked from within the actor itself. */ - def startLinkRemote(actorRef: ActorRef, hostname: String, port: Int): Unit = guard.withGuard { - ensureRemotingEnabled - actorRef.makeRemote(hostname, port) - link(actorRef) - actorRef.start + def startLinkRemote(actorRef: ActorRef, hostname: String, port: Int): Unit = { + //TODO: REVISIT: REMOVED } /** @@ -818,10 +715,7 @@ class LocalActorRef private[akka] ( */ def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef = guard.withGuard { ensureRemotingEnabled - val actor = Actor.actorOf(clazz) - actor.makeRemote(hostname, port) - actor.start - actor + Actor.actorOf(clazz, hostname, port).start } /** @@ -843,8 +737,7 @@ class LocalActorRef private[akka] ( */ def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef = guard.withGuard { ensureRemotingEnabled - val actor = Actor.actorOf(clazz) - actor.makeRemote(hostname, port) + val actor = Actor.actorOf(clazz, hostname, port) link(actor) actor.start actor @@ -878,13 +771,8 @@ class LocalActorRef private[akka] ( protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit = _supervisor = sup protected[akka] def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = { - if (remoteAddress.isDefined && isRemotingEnabled) { - RemoteClientModule.send[Any]( - message, senderOption, None, remoteAddress.get, timeout, true, this, None, ActorType.ScalaActor) - } else { - val invocation = new MessageInvocation(this, message, senderOption, None) - dispatcher dispatchMessage invocation - } + val invocation = new MessageInvocation(this, message, senderOption, None) + dispatcher dispatchMessage invocation } protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout[T]( @@ -892,19 +780,11 @@ class LocalActorRef private[akka] ( timeout: Long, senderOption: Option[ActorRef], senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { - - if (remoteAddress.isDefined && isRemotingEnabled) { - val future = RemoteClientModule.send[T]( - message, senderOption, senderFuture, remoteAddress.get, timeout, false, this, None, ActorType.ScalaActor) - if (future.isDefined) future.get - else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) - } else { val future = if (senderFuture.isDefined) senderFuture else Some(new DefaultCompletableFuture[T](timeout)) val invocation = new MessageInvocation( this, message, senderOption, future.asInstanceOf[Some[CompletableFuture[Any]]]) dispatcher dispatchMessage invocation future.get - } } /** @@ -1061,13 +941,15 @@ class LocalActorRef private[akka] ( } } + //TODO: REVISIT: REMOVE + /* protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = guard.withGuard { ensureRemotingEnabled if (_supervisor.isDefined) { remoteAddress.foreach(address => RemoteClientModule.registerSupervisorForActor(address, this)) Some(_supervisor.get.uuid) } else None - } + }*/ protected[akka] def linkedActors: JMap[Uuid, ActorRef] = _linkedActors @@ -1222,13 +1104,14 @@ private[akka] case class RemoteActorRef private[akka] ( val hostname: String, val port: Int, _timeout: Long, + clientManaged: Boolean, loader: Option[ClassLoader], val actorType: ActorType = ActorType.ScalaActor) extends ActorRef with ScalaActorRef { ensureRemotingEnabled - val remoteAddress: Option[InetSocketAddress] = Some(new InetSocketAddress(hostname, port)) + val homeAddress = new InetSocketAddress(hostname, port) id = classOrServiceName timeout = _timeout @@ -1237,7 +1120,7 @@ private[akka] case class RemoteActorRef private[akka] ( def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = RemoteClientModule.send[Any]( - message, senderOption, None, remoteAddress.get, timeout, true, this, None, actorType) + message, senderOption, None, homeAddress, timeout, true, this, None, actorType) def postMessageToMailboxAndCreateFutureResultWithTimeout[T]( message: Any, @@ -1245,13 +1128,16 @@ private[akka] case class RemoteActorRef private[akka] ( senderOption: Option[ActorRef], senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { val future = RemoteClientModule.send[T]( - message, senderOption, senderFuture, remoteAddress.get, timeout, false, this, None, actorType) + message, senderOption, senderFuture, homeAddress, timeout, false, this, None, actorType) if (future.isDefined) future.get else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) } def start: ActorRef = synchronized { _status = ActorRefInternals.RUNNING + if (clientManaged) { + RemoteClientModule.register(homeAddress, uuid) + } this } @@ -1259,18 +1145,20 @@ private[akka] case class RemoteActorRef private[akka] ( if (_status == ActorRefInternals.RUNNING) { _status = ActorRefInternals.SHUTDOWN postMessageToMailbox(RemoteActorSystemMessage.Stop, None) + if (clientManaged) { + RemoteClientModule.unregister(homeAddress, uuid) + ActorRegistry.remote.unregister(this) //TODO: Why does this need to be deregistered from the server? + } } } - protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = None + //TODO: REVISIT: REMOVE + //protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = None // ==== NOT SUPPORTED ==== def actorClass: Class[_ <: Actor] = unsupported def dispatcher_=(md: MessageDispatcher): Unit = unsupported def dispatcher: MessageDispatcher = unsupported - def makeRemote(hostname: String, port: Int): Unit = unsupported - def makeRemote(address: InetSocketAddress): Unit = unsupported - def homeAddress_=(address: InetSocketAddress): Unit = unsupported def link(actorRef: ActorRef): Unit = unsupported def unlink(actorRef: ActorRef): Unit = unsupported def startLink(actorRef: ActorRef): Unit = unsupported @@ -1288,7 +1176,6 @@ private[akka] case class RemoteActorRef private[akka] ( protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Unit = unsupported protected[akka] def linkedActors: JMap[Uuid, ActorRef] = unsupported protected[akka] def invoke(messageHandle: MessageInvocation): Unit = unsupported - protected[akka] def remoteAddress_=(addr: Option[InetSocketAddress]): Unit = unsupported protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit = unsupported protected[akka] def actorInstance: AtomicReference[Actor] = unsupported private def unsupported = throw new UnsupportedOperationException("Not supported for RemoteActorRef") diff --git a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala index 68fdb50d85..d80e5273da 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala @@ -12,8 +12,9 @@ import java.util.{Set => JSet} import annotation.tailrec import akka.util.ReflectiveAccess._ -import akka.util.{ReadWriteGuard, Address, ListenerManagement} import java.net.InetSocketAddress +import akka.util. {ReadWriteGuard, Address, ListenerManagement} +import akka.remoteinterface.RemoteServerModule /** * Base trait for ActorRegistry events, allows listen to when an actor is added and removed from the ActorRegistry. @@ -223,9 +224,19 @@ object ActorRegistry extends ListenerManagement { TypedActorModule.typedActorObjectInstance.get.proxyFor(actorRef) } + /** + * Handy access to the RemoteServer module + */ + lazy val remote: RemoteServerModule = getObjectFor("akka.remote.RemoteNode$") match { + case Some(module) => module + case None => + log.slf4j.error("Wanted remote module but didn't exist on classpath") + null + } + /** - * Registers an actor in the ActorRegistry. + * Registers an actor in the ActorRegistry. */ private[akka] def register(actor: ActorRef) = { // ID diff --git a/akka-actor/src/main/scala/akka/actor/Supervisor.scala b/akka-actor/src/main/scala/akka/actor/Supervisor.scala index 351cdecf89..daf7a962de 100644 --- a/akka-actor/src/main/scala/akka/actor/Supervisor.scala +++ b/akka-actor/src/main/scala/akka/actor/Supervisor.scala @@ -130,7 +130,7 @@ sealed class Supervisor(handler: FaultHandlingStrategy) { case SupervisorConfig(_, servers) => servers.map(server => server match { - case Supervise(actorRef, lifeCycle, remoteAddress) => + case Supervise(actorRef, lifeCycle, registerAsRemoteService) => actorRef.start val className = actorRef.actor.getClass.getName val currentActors = { @@ -141,10 +141,8 @@ sealed class Supervisor(handler: FaultHandlingStrategy) { _childActors.put(className, actorRef :: currentActors) actorRef.lifeCycle = lifeCycle supervisor.link(actorRef) - if (remoteAddress.isDefined) { - val address = remoteAddress.get - RemoteServerModule.registerActor(new InetSocketAddress(address.hostname, address.port), actorRef) - } + if (registerAsRemoteService) + ActorRegistry.remote.register(actorRef) case supervisorConfig @ SupervisorConfig(_, _) => // recursive supervisor configuration val childSupervisor = Supervisor(supervisorConfig) supervisor.link(childSupervisor.supervisor) diff --git a/akka-actor/src/main/scala/akka/actor/UntypedActor.scala b/akka-actor/src/main/scala/akka/actor/UntypedActor.scala index 9eec8cbb5d..668dad7f86 100644 --- a/akka-actor/src/main/scala/akka/actor/UntypedActor.scala +++ b/akka-actor/src/main/scala/akka/actor/UntypedActor.scala @@ -92,16 +92,6 @@ abstract class UntypedActor extends Actor { */ trait UntypedActorFactory extends Creator[Actor] -/** - * Extend this abstract class to create a remote UntypedActor. - * - * @author Jonas Bonér - */ -abstract class RemoteUntypedActor(address: InetSocketAddress) extends UntypedActor { - def this(hostname: String, port: Int) = this(new InetSocketAddress(hostname, port)) - self.makeRemote(address) -} - /** * Factory object for creating and managing 'UntypedActor's. Meant to be used from Java. *

diff --git a/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala b/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala index f3d6e1ada9..f7771d80b5 100644 --- a/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala +++ b/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala @@ -26,20 +26,16 @@ object Supervision { def this(restartStrategy: FaultHandlingStrategy, worker: Array[Server]) = this(restartStrategy,worker.toList) } - class Supervise(val actorRef: ActorRef, val lifeCycle: LifeCycle, val remoteAddress: Option[RemoteAddress]) extends Server { - //Java API - def this(actorRef: ActorRef, lifeCycle: LifeCycle, remoteAddress: RemoteAddress) = - this(actorRef, lifeCycle, Option(remoteAddress)) - + class Supervise(val actorRef: ActorRef, val lifeCycle: LifeCycle, val registerAsRemoteService: Boolean = false) extends Server { //Java API def this(actorRef: ActorRef, lifeCycle: LifeCycle) = - this(actorRef, lifeCycle, None) + this(actorRef, lifeCycle, false) } object Supervise { - def apply(actorRef: ActorRef, lifeCycle: LifeCycle, remoteAddress: RemoteAddress) = new Supervise(actorRef, lifeCycle, remoteAddress) - def apply(actorRef: ActorRef, lifeCycle: LifeCycle) = new Supervise(actorRef, lifeCycle, None) - def unapply(supervise: Supervise) = Some((supervise.actorRef, supervise.lifeCycle, supervise.remoteAddress)) + def apply(actorRef: ActorRef, lifeCycle: LifeCycle, registerAsRemoteService: Boolean = false) = new Supervise(actorRef, lifeCycle, registerAsRemoteService) + def apply(actorRef: ActorRef, lifeCycle: LifeCycle) = new Supervise(actorRef, lifeCycle, false) + def unapply(supervise: Supervise) = Some((supervise.actorRef, supervise.lifeCycle, supervise.registerAsRemoteService)) } object AllForOneStrategy { diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala new file mode 100644 index 0000000000..4f6cd11a67 --- /dev/null +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -0,0 +1,149 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB + */ + +package akka.remoteinterface + +import akka.japi.Creator +import akka.actor.ActorRef +import akka.util.{ReentrantGuard, Logging, ListenerManagement} + +/** + * This is the interface for the RemoteServer functionality, it's used in ActorRegistry.remote + */ +trait RemoteServerModule extends ListenerManagement with Logging { + protected val guard = new ReentrantGuard + + /** + * Signals whether the server is up and running or not + */ + def isRunning: Boolean + + /** + * Gets the name of the server instance + */ + def name: String + + /** + * Gets the current hostname of the server instance + */ + def hostname: String + + /** + * Gets the current port of the server instance + */ + def port: Int + + /** + * Starts the server up + */ + def start(host: String, port: Int, loader: Option[ClassLoader] = None): RemoteServerModule //TODO possibly hidden + + /** + * Shuts the server down + */ + def shutdown: Unit //TODO possibly hidden + + /** + * Register typed actor by interface name. + */ + def registerTypedActor(intfClass: Class[_], typedActor: AnyRef) : Unit = registerTypedActor(intfClass.getName, typedActor) + + /** + * Register remote typed actor by a specific id. + * @param id custom actor id + * @param typedActor typed actor to register + */ + def registerTypedActor(id: String, typedActor: AnyRef): Unit + + /** + * Register typed actor by interface name. + */ + def registerTypedPerSessionActor(intfClass: Class[_], factory: => AnyRef) : Unit = registerTypedActor(intfClass.getName, factory) + + /** + * Register typed actor by interface name. + * Java API + */ + def registerTypedPerSessionActor(intfClass: Class[_], factory: Creator[AnyRef]) : Unit = registerTypedActor(intfClass.getName, factory) + + /** + * Register remote typed actor by a specific id. + * @param id custom actor id + * @param typedActor typed actor to register + */ + def registerTypedPerSessionActor(id: String, factory: => AnyRef): Unit + + /** + * Register remote typed actor by a specific id. + * @param id custom actor id + * @param typedActor typed actor to register + * Java API + */ + def registerTypedPerSessionActor(id: String, factory: Creator[AnyRef]): Unit = registerTypedPerSessionActor(id, factory.create) + + /** + * Register Remote Actor by the Actor's 'id' field. It starts the Actor if it is not started already. + */ + def register(actorRef: ActorRef): Unit = register(actorRef.id, actorRef) + + /** + * Register Remote Actor by the Actor's uuid field. It starts the Actor if it is not started already. + */ + def registerByUuid(actorRef: ActorRef): Unit + + /** + * Register Remote Actor by a specific 'id' passed as argument. + *

+ * NOTE: If you use this method to register your remote actor then you must unregister the actor by this ID yourself. + */ + def register(id: String, actorRef: ActorRef): Unit + + /** + * Register Remote Session Actor by a specific 'id' passed as argument. + *

+ * NOTE: If you use this method to register your remote actor then you must unregister the actor by this ID yourself. + */ + def registerPerSession(id: String, factory: => ActorRef): Unit + + /** + * Register Remote Session Actor by a specific 'id' passed as argument. + *

+ * NOTE: If you use this method to register your remote actor then you must unregister the actor by this ID yourself. + * Java API + */ + def registerPerSession(id: String, factory: Creator[ActorRef]): Unit = registerPerSession(id, factory.create) + + /** + * Unregister Remote Actor that is registered using its 'id' field (not custom ID). + */ + def unregister(actorRef: ActorRef): Unit + + /** + * Unregister Remote Actor by specific 'id'. + *

+ * NOTE: You need to call this method if you have registered an actor by a custom ID. + */ + def unregister(id: String): Unit + + /** + * Unregister Remote Actor by specific 'id'. + *

+ * NOTE: You need to call this method if you have registered an actor by a custom ID. + */ + def unregisterPerSession(id: String): Unit + + /** + * Unregister Remote Typed Actor by specific 'id'. + *

+ * NOTE: You need to call this method if you have registered an actor by a custom ID. + */ + def unregisterTypedActor(id: String): Unit + + /** + * Unregister Remote Typed Actor by specific 'id'. + *

+ * NOTE: You need to call this method if you have registered an actor by a custom ID. + */ + def unregisterTypedPerSessionActor(id: String): Unit +} \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala index 45e1609b79..14bde14e89 100644 --- a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala +++ b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala @@ -107,36 +107,6 @@ object ReflectiveAccess extends Logging { object RemoteServerModule { val HOSTNAME = Config.config.getString("akka.remote.server.hostname", "localhost") val PORT = Config.config.getInt("akka.remote.server.port", 2552) - - type RemoteServerObject = { - def registerActor(address: InetSocketAddress, actor: ActorRef): Unit - def registerTypedActor(address: InetSocketAddress, name: String, typedActor: AnyRef): Unit - } - - type RemoteNodeObject = { - def unregister(actorRef: ActorRef): Unit - } - - val remoteServerObjectInstance: Option[RemoteServerObject] = - getObjectFor("akka.remote.RemoteServer$") - - val remoteNodeObjectInstance: Option[RemoteNodeObject] = - getObjectFor("akka.remote.RemoteNode$") - - def registerActor(address: InetSocketAddress, actorRef: ActorRef) = { - RemoteClientModule.ensureEnabled - remoteServerObjectInstance.get.registerActor(address, actorRef) - } - - def registerTypedActor(address: InetSocketAddress, implementationClassName: String, proxy: AnyRef) = { - RemoteClientModule.ensureEnabled - remoteServerObjectInstance.get.registerTypedActor(address, implementationClassName, proxy) - } - - def unregister(actorRef: ActorRef) = { - RemoteClientModule.ensureEnabled - remoteNodeObjectInstance.get.unregister(actorRef) - } } /** diff --git a/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala b/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala index 70b6154fda..9c2eeb9c2c 100644 --- a/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala +++ b/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala @@ -4,9 +4,9 @@ package akka.remote -import akka.actor.BootableActorLoaderService -import akka.util.{Bootable, Logging} import akka.config.Config.config +import akka.actor. {ActorRegistry, BootableActorLoaderService} +import akka.util. {ReflectiveAccess, Bootable, Logging} /** * This bundle/service is responsible for booting up and shutting down the remote actors facility @@ -17,10 +17,8 @@ trait BootableRemoteActorService extends Bootable with Logging { self: BootableActorLoaderService => protected lazy val remoteServerThread = new Thread(new Runnable() { - def run = { - if (self.applicationLoader.isDefined) RemoteNode.start(self.applicationLoader.get) - else RemoteNode.start - } + import ReflectiveAccess.RemoteServerModule.{HOSTNAME,PORT} + def run = ActorRegistry.remote.start(HOSTNAME,PORT,loader = self.applicationLoader) }, "Akka Remote Service") def startRemoteService = remoteServerThread.start diff --git a/akka-remote/src/main/scala/akka/remote/RemoteClient.scala b/akka-remote/src/main/scala/akka/remote/RemoteClient.scala index 2b137d8788..0a27f04cb1 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteClient.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteClient.scala @@ -95,7 +95,7 @@ object RemoteClient extends Logging { actorFor(classNameOrServiceId, classNameOrServiceId, timeout, hostname, port, Some(loader)) def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int): ActorRef = - RemoteActorRef(serviceId, className, hostname, port, timeout, None) + RemoteActorRef(serviceId, className, hostname, port, timeout, false, None) def typedActorFor[T](intfClass: Class[T], serviceIdOrClassName: String, hostname: String, port: Int): T = { typedActorFor(intfClass, serviceIdOrClassName, serviceIdOrClassName, Actor.TIMEOUT, hostname, port, None) @@ -114,15 +114,15 @@ object RemoteClient extends Logging { } private[akka] def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): T = { - val actorRef = RemoteActorRef(serviceId, implClassName, hostname, port, timeout, loader, ActorType.TypedActor) + val actorRef = RemoteActorRef(serviceId, implClassName, hostname, port, timeout, false, loader, ActorType.TypedActor) TypedActor.createProxyForRemoteActorRef(intfClass, actorRef) } private[akka] def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int, loader: ClassLoader): ActorRef = - RemoteActorRef(serviceId, className, hostname, port, timeout, Some(loader)) + RemoteActorRef(serviceId, className, hostname, port, timeout, false, Some(loader)) private[akka] def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): ActorRef = - RemoteActorRef(serviceId, className, hostname, port, timeout, loader) + RemoteActorRef(serviceId, className, hostname, port, timeout, false, loader) def clientFor(hostname: String, port: Int): RemoteClient = clientFor(new InetSocketAddress(hostname, port), None) diff --git a/akka-remote/src/main/scala/akka/remote/RemoteServer.scala b/akka-remote/src/main/scala/akka/remote/RemoteServer.scala index 9fa86e25df..1ec427d0ab 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteServer.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteServer.scala @@ -32,6 +32,7 @@ import scala.collection.mutable.Map import scala.reflect.BeanProperty import akka.dispatch. {Future, DefaultCompletableFuture, CompletableFuture} import akka.japi.Creator +import akka.remoteinterface.RemoteServerModule /** * Use this object if you need a single remote server on a specific node. @@ -112,45 +113,6 @@ object RemoteServer { true } else */false } - - private val guard = new ReadWriteGuard - private val remoteServers = Map[Address, RemoteServer]() - - def serverFor(address: InetSocketAddress): Option[RemoteServer] = - serverFor(address.getHostName, address.getPort) - - def serverFor(hostname: String, port: Int): Option[RemoteServer] = guard.withReadGuard { - remoteServers.get(Address(hostname, port)) - } - - private[akka] def getOrCreateServer(address: InetSocketAddress): RemoteServer = guard.withWriteGuard { - serverFor(address) match { - case Some(server) => server - case None => (new RemoteServer).start(address) - } - } - - private[akka] def register(hostname: String, port: Int, server: RemoteServer) = guard.withWriteGuard { - remoteServers.put(Address(hostname, port), server) - } - - private[akka] def unregister(hostname: String, port: Int) = guard.withWriteGuard { - remoteServers.remove(Address(hostname, port)) - } - - /** - * Used in REflectiveAccess - */ - private[akka] def registerActor(address: InetSocketAddress, actorRef: ActorRef) { - serverFor(address) foreach { _.register(actorRef) } - } - - /** - * Used in Reflective - */ - private[akka] def registerTypedActor(address: InetSocketAddress, implementationClassName: String, proxy: AnyRef) { - serverFor(address) foreach { _.registerTypedActor(implementationClassName,proxy)} - } } /** @@ -190,52 +152,31 @@ case class RemoteServerClientClosed( * * @author Jonas Bonér */ -class RemoteServer extends Logging with ListenerManagement { +class RemoteServer extends RemoteServerModule { import RemoteServer._ - def name = "RemoteServer@" + hostname + ":" + port - private[akka] var address = Address(RemoteServer.HOSTNAME,RemoteServer.PORT) + @volatile private[akka] var address = Address(RemoteServer.HOSTNAME,RemoteServer.PORT) def hostname = address.hostname def port = address.port + def name = "RemoteServer@" + hostname + ":" + port - @volatile private var _isRunning = false + private val _isRunning = new Switch(false) - private val factory = new NioServerSocketChannelFactory( - Executors.newCachedThreadPool, - Executors.newCachedThreadPool) + private val factory = new NioServerSocketChannelFactory(Executors.newCachedThreadPool,Executors.newCachedThreadPool) private val bootstrap = new ServerBootstrap(factory) // group of open channels, used for clean-up private val openChannels: ChannelGroup = new DefaultChannelGroup("akka-remote-server") - def isRunning = _isRunning + def isRunning = _isRunning.isOn - def start: RemoteServer = - start(hostname, port, None) - - def start(loader: ClassLoader): RemoteServer = - start(hostname, port, Some(loader)) - - def start(address: InetSocketAddress): RemoteServer = - start(address.getHostName, address.getPort, None) - - def start(address: InetSocketAddress, loader: ClassLoader): RemoteServer = - start(address.getHostName, address.getPort, Some(loader)) - - def start(_hostname: String, _port: Int): RemoteServer = - start(_hostname, _port, None) - - private def start(_hostname: String, _port: Int, loader: ClassLoader): RemoteServer = - start(_hostname, _port, Some(loader)) - - private def start(_hostname: String, _port: Int, loader: Option[ClassLoader]): RemoteServer = synchronized { + def start(_hostname: String, _port: Int, loader: Option[ClassLoader] = None): RemoteServer = guard withGuard { try { - if (!_isRunning) { + _isRunning switchOn { address = Address(_hostname,_port) log.slf4j.info("Starting remote server at [{}:{}]", hostname, port) - RemoteServer.register(hostname, port, this) val pipelineFactory = new RemoteServerPipelineFactory(name, openChannels, loader, this) bootstrap.setPipelineFactory(pipelineFactory) @@ -245,7 +186,6 @@ class RemoteServer extends Logging with ListenerManagement { bootstrap.setOption("child.connectTimeoutMillis", RemoteServer.CONNECTION_TIMEOUT_MILLIS.toMillis) openChannels.add(bootstrap.bind(new InetSocketAddress(hostname, port))) - _isRunning = true notifyListeners(RemoteServerStarted(this)) } } catch { @@ -256,10 +196,9 @@ class RemoteServer extends Logging with ListenerManagement { this } - def shutdown = synchronized { - if (_isRunning) { + def shutdown = guard withGuard { + _isRunning switchOff { try { - RemoteServer.unregister(hostname, port) openChannels.disconnect openChannels.close.awaitUninterruptibly bootstrap.releaseExternalResources @@ -271,70 +210,49 @@ class RemoteServer extends Logging with ListenerManagement { } } - /** - * Register typed actor by interface name. - */ - def registerTypedActor(intfClass: Class[_], typedActor: AnyRef) : Unit = registerTypedActor(intfClass.getName, typedActor) - /** * Register remote typed actor by a specific id. * @param id custom actor id * @param typedActor typed actor to register */ - def registerTypedActor(id: String, typedActor: AnyRef): Unit = synchronized { + def registerTypedActor(id: String, typedActor: AnyRef): Unit = guard withGuard { log.slf4j.debug("Registering server side remote typed actor [{}] with id [{}]", typedActor.getClass.getName, id) if (id.startsWith(UUID_PREFIX)) registerTypedActor(id.substring(UUID_PREFIX.length), typedActor, typedActorsByUuid) else registerTypedActor(id, typedActor, typedActors) } - /** - * Register typed actor by interface name. - */ - def registerTypedPerSessionActor(intfClass: Class[_], factory: => AnyRef) : Unit = registerTypedActor(intfClass.getName, factory) - - /** - * Register typed actor by interface name. - * Java API - */ - def registerTypedPerSessionActor(intfClass: Class[_], factory: Creator[AnyRef]) : Unit = registerTypedActor(intfClass.getName, factory) - /** * Register remote typed actor by a specific id. * @param id custom actor id * @param typedActor typed actor to register */ - def registerTypedPerSessionActor(id: String, factory: => AnyRef): Unit = synchronized { + def registerTypedPerSessionActor(id: String, factory: => AnyRef): Unit = guard withGuard { log.slf4j.debug("Registering server side typed remote session actor with id [{}]", id) registerTypedPerSessionActor(id, () => factory, typedActorsFactories) } - /** - * Register remote typed actor by a specific id. - * @param id custom actor id - * @param typedActor typed actor to register - * Java API - */ - def registerTypedPerSessionActor(id: String, factory: Creator[AnyRef]): Unit = synchronized { - log.slf4j.debug("Registering server side typed remote session actor with id [{}]", id) - registerTypedPerSessionActor(id, factory.create _, typedActorsFactories) - } - - /** - * Register Remote Actor by the Actor's 'id' field. It starts the Actor if it is not started already. - */ - def register(actorRef: ActorRef): Unit = register(actorRef.id, actorRef) - /** * Register Remote Actor by a specific 'id' passed as argument. *

* NOTE: If you use this method to register your remote actor then you must unregister the actor by this ID yourself. */ - def register(id: String, actorRef: ActorRef): Unit = synchronized { + def register(id: String, actorRef: ActorRef): Unit = guard withGuard { log.slf4j.debug("Registering server side remote actor [{}] with id [{}]", actorRef.actorClass.getName, id) if (id.startsWith(UUID_PREFIX)) register(id.substring(UUID_PREFIX.length), actorRef, actorsByUuid) else register(id, actorRef, actors) } + def registerByUuid(actorRef: ActorRef): Unit = guard withGuard { + register(actorRef.uuid.toString, actorRef, actorsByUuid) + } + + private def register[Key](id: Key, actorRef: ActorRef, registry: ConcurrentHashMap[Key, ActorRef]) { + if (_isRunning.isOn) { + registry.put(id, actorRef) //TODO change to putIfAbsent + if (!actorRef.isRunning) actorRef.start + } + } + /** * Register Remote Session Actor by a specific 'id' passed as argument. *

@@ -345,44 +263,26 @@ class RemoteServer extends Logging with ListenerManagement { registerPerSession(id, () => factory, actorsFactories) } - /** - * Register Remote Session Actor by a specific 'id' passed as argument. - *

- * NOTE: If you use this method to register your remote actor then you must unregister the actor by this ID yourself. - * Java API - */ - def registerPerSession(id: String, factory: Creator[ActorRef]): Unit = synchronized { - log.slf4j.debug("Registering server side remote session actor with id [{}]", id) - registerPerSession(id, factory.create _, actorsFactories) - } - - private def register[Key](id: Key, actorRef: ActorRef, registry: ConcurrentHashMap[Key, ActorRef]) { - if (_isRunning) { - registry.put(id, actorRef) //TODO change to putIfAbsent - if (!actorRef.isRunning) actorRef.start - } - } - private def registerPerSession[Key](id: Key, factory: () => ActorRef, registry: ConcurrentHashMap[Key,() => ActorRef]) { - if (_isRunning) + if (_isRunning.isOn) registry.put(id, factory) //TODO change to putIfAbsent } private def registerTypedActor[Key](id: Key, typedActor: AnyRef, registry: ConcurrentHashMap[Key, AnyRef]) { - if (_isRunning) + if (_isRunning.isOn) registry.put(id, typedActor) //TODO change to putIfAbsent } private def registerTypedPerSessionActor[Key](id: Key, factory: () => AnyRef, registry: ConcurrentHashMap[Key,() => AnyRef]) { - if (_isRunning) + if (_isRunning.isOn) registry.put(id, factory) //TODO change to putIfAbsent } /** * Unregister Remote Actor that is registered using its 'id' field (not custom ID). */ - def unregister(actorRef: ActorRef):Unit = synchronized { - if (_isRunning) { + def unregister(actorRef: ActorRef): Unit = guard withGuard { + if (_isRunning.isOn) { log.slf4j.debug("Unregistering server side remote actor [{}] with id [{}:{}]", Array[AnyRef](actorRef.actorClass.getName, actorRef.id, actorRef.uuid)) actors.remove(actorRef.id, actorRef) actorsByUuid.remove(actorRef.uuid, actorRef) @@ -394,8 +294,8 @@ class RemoteServer extends Logging with ListenerManagement { *

* NOTE: You need to call this method if you have registered an actor by a custom ID. */ - def unregister(id: String):Unit = synchronized { - if (_isRunning) { + def unregister(id: String): Unit = guard withGuard { + if (_isRunning.isOn) { log.slf4j.info("Unregistering server side remote actor with id [{}]", id) if (id.startsWith(UUID_PREFIX)) actorsByUuid.remove(id.substring(UUID_PREFIX.length)) else { @@ -411,8 +311,8 @@ class RemoteServer extends Logging with ListenerManagement { *

* NOTE: You need to call this method if you have registered an actor by a custom ID. */ - def unregisterPerSession(id: String):Unit = { - if (_isRunning) { + def unregisterPerSession(id: String): Unit = { + if (_isRunning.isOn) { log.slf4j.info("Unregistering server side remote session actor with id [{}]", id) actorsFactories.remove(id) } @@ -423,8 +323,8 @@ class RemoteServer extends Logging with ListenerManagement { *

* NOTE: You need to call this method if you have registered an actor by a custom ID. */ - def unregisterTypedActor(id: String):Unit = synchronized { - if (_isRunning) { + def unregisterTypedActor(id: String):Unit = guard withGuard { + if (_isRunning.isOn) { log.slf4j.info("Unregistering server side remote typed actor with id [{}]", id) if (id.startsWith(UUID_PREFIX)) typedActorsByUuid.remove(id.substring(UUID_PREFIX.length)) else typedActors.remove(id) @@ -436,8 +336,8 @@ class RemoteServer extends Logging with ListenerManagement { *

* NOTE: You need to call this method if you have registered an actor by a custom ID. */ - def unregisterTypedPerSessionActor(id: String):Unit = { - if (_isRunning) { + def unregisterTypedPerSessionActor(id: String): Unit = { + if (_isRunning.isOn) { typedActorsFactories.remove(id) } } @@ -446,7 +346,6 @@ class RemoteServer extends Logging with ListenerManagement { protected[akka] override def notifyListeners(message: => Any): Unit = super.notifyListeners(message) - private[akka] def actors = ActorRegistry.actors(address) private[akka] def actorsByUuid = ActorRegistry.actorsByUuid(address) private[akka] def actorsFactories = ActorRegistry.actorsFactories(address) @@ -838,7 +737,6 @@ class RemoteServerHandler( actorRef.uuid = uuidFrom(uuid.getHigh,uuid.getLow) actorRef.id = id actorRef.timeout = timeout - actorRef.remoteAddress = None server.actorsByUuid.put(actorRef.uuid.toString, actorRef) // register by uuid actorRef } catch { diff --git a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala index b93b472f51..258d210490 100644 --- a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -16,6 +16,8 @@ import akka.actor._ import scala.collection.immutable.Stack import com.google.protobuf.ByteString +import akka.util.ReflectiveAccess +import akka.util.ReflectiveAccess.RemoteServerModule.{HOSTNAME,PORT} /** * Type class definition for Actor Serialization @@ -88,6 +90,11 @@ object ActorSerialization { def toBinaryJ[T <: Actor](a: ActorRef, format: Format[T], srlMailBox: Boolean = true): Array[Byte] = toBinary(a, srlMailBox)(format) + val localAddress = AddressProtocol.newBuilder + .setHostname(HOSTNAME) + .setPort(PORT) + .build + private[akka] def toSerializedActorRefProtocol[T <: Actor]( actorRef: ActorRef, format: Format[T], serializeMailBox: Boolean = true): SerializedActorRefProtocol = { val lifeCycleProtocol: Option[LifeCycleProtocol] = { @@ -98,16 +105,11 @@ object ActorSerialization { } } - val originalAddress = AddressProtocol.newBuilder - .setHostname(actorRef.homeAddress.getHostName) - .setPort(actorRef.homeAddress.getPort) - .build - val builder = SerializedActorRefProtocol.newBuilder .setUuid(UuidProtocol.newBuilder.setHigh(actorRef.uuid.getTime).setLow(actorRef.uuid.getClockSeqAndNode).build) .setId(actorRef.id) .setActorClassname(actorRef.actorClass.getName) - .setOriginalAddress(originalAddress) + .setOriginalAddress(localAddress) .setTimeout(actorRef.timeout) @@ -233,6 +235,7 @@ object RemoteActorSerialization { protocol.getHomeAddress.getHostname, protocol.getHomeAddress.getPort, protocol.getTimeout, + false, loader) } @@ -241,18 +244,14 @@ object RemoteActorSerialization { */ def toRemoteActorRefProtocol(ar: ActorRef): RemoteActorRefProtocol = { import ar._ - val home = homeAddress - val host = home.getHostName - val port = home.getPort - Actor.log.slf4j.debug("Register serialized Actor [{}] as remote @ [{}]",actorClassName, home) - RemoteServer.getOrCreateServer(homeAddress) - ActorRegistry.registerActorByUuid(homeAddress, uuid.toString, ar) + Actor.log.slf4j.debug("Register serialized Actor [{}] as remote @ [{}:{}]",Array[AnyRef](actorClassName, HOSTNAME, PORT.asInstanceOf[AnyRef])) + ActorRegistry.remote.registerByUuid(ar) RemoteActorRefProtocol.newBuilder .setClassOrServiceName(uuid.toString) .setActorClassname(actorClassName) - .setHomeAddress(AddressProtocol.newBuilder.setHostname(host).setPort(port).build) + .setHomeAddress(ActorSerialization.localAddress) .setTimeout(timeout) .build } @@ -311,6 +310,8 @@ object RemoteActorSerialization { secureCookie.foreach(messageBuilder.setCookie(_)) + //TODO: REVISIT: REMOVE + /** actorRef.foreach { ref => ref.registerSupervisorAsRemoteActor.foreach { id => messageBuilder.setSupervisorUuid( @@ -319,13 +320,11 @@ object RemoteActorSerialization { .setLow(id.getClockSeqAndNode) .build) } - } + }*/ - senderOption.foreach { sender => - RemoteServer.getOrCreateServer(sender.homeAddress).register(sender.uuid.toString, sender) - messageBuilder.setSender(toRemoteActorRefProtocol(sender)) + if( senderOption.isDefined) + messageBuilder.setSender(toRemoteActorRefProtocol(senderOption.get)) - } messageBuilder } } diff --git a/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala b/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala index 1f85c73100..76ab88c7fb 100644 --- a/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala +++ b/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala @@ -5,11 +5,10 @@ package sample.remote import akka.actor.Actor._ -import akka.actor.RemoteActor -import akka.remote.RemoteNode import akka.util.Logging +import akka.actor. {ActorRegistry, Actor} -class RemoteHelloWorldActor extends RemoteActor("localhost", 2552) { +class RemoteHelloWorldActor extends Actor { def receive = { case "Hello" => log.slf4j.info("Received 'Hello'") @@ -19,7 +18,7 @@ class RemoteHelloWorldActor extends RemoteActor("localhost", 2552) { object ClientManagedRemoteActorServer extends Logging { def run = { - RemoteNode.start("localhost", 2552) + ActorRegistry.remote.start("localhost", 2552) log.slf4j.info("Remote node started") } @@ -29,7 +28,7 @@ object ClientManagedRemoteActorServer extends Logging { object ClientManagedRemoteActorClient extends Logging { def run = { - val actor = actorOf[RemoteHelloWorldActor].start + val actor = actorOf[RemoteHelloWorldActor]("localhost",2552).start log.slf4j.info("Remote actor created, moved to the server") log.slf4j.info("Sending 'Hello' to remote actor") val result = actor !! "Hello" diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala index 1a39eab01d..dbdf27b352 100644 --- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala @@ -512,9 +512,9 @@ object TypedActor extends Logging { typedActor.initialize(proxy) if (config._messageDispatcher.isDefined) actorRef.dispatcher = config._messageDispatcher.get if (config._threadBasedDispatcher.isDefined) actorRef.dispatcher = Dispatchers.newThreadBasedDispatcher(actorRef) - if (config._host.isDefined) actorRef.makeRemote(config._host.get) + if (config._host.isDefined) log.slf4j.warn("Client-managed typed actors are not supported!") //TODO: REVISIT: FIXME actorRef.timeout = config.timeout - AspectInitRegistry.register(proxy, AspectInit(intfClass, typedActor, actorRef, actorRef.remoteAddress, actorRef.timeout)) + AspectInitRegistry.register(proxy, AspectInit(intfClass, typedActor, actorRef, None, actorRef.timeout)) //TODO: REVISIT fix Client managed typed actor actorRef.start proxy.asInstanceOf[T] } @@ -751,7 +751,7 @@ private[akka] sealed class ServerManagedTypedActorAspect extends ActorAspect { override def initialize(joinPoint: JoinPoint): Unit = { super.initialize(joinPoint) - remoteAddress = actorRef.remoteAddress + //remoteAddress = actorRef.remoteAddress //TODO: REVISIT: Fix Server managed Typed Actor } } diff --git a/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala b/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala index fc2f83c5d9..d2a9ebca26 100644 --- a/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala +++ b/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala @@ -115,6 +115,7 @@ private[akka] class TypedActorGuiceConfigurator extends TypedActorConfiguratorBa val proxy = Proxy.newInstance(Array(interfaceClass), Array(typedActor), true, false) + /* val remoteAddress = if (component.remoteAddress.isDefined) Some(new InetSocketAddress(component.remoteAddress.get.hostname, component.remoteAddress.get.port)) @@ -122,11 +123,11 @@ private[akka] class TypedActorGuiceConfigurator extends TypedActorConfiguratorBa remoteAddress.foreach { address => actorRef.makeRemote(remoteAddress.get) - } + }*/ AspectInitRegistry.register( proxy, - AspectInit(interfaceClass, typedActor, actorRef, remoteAddress, timeout)) + AspectInit(interfaceClass, typedActor, actorRef, None, timeout)) //TODO: REVISIT: FIX CLIENT MANAGED ACTORS typedActor.initialize(proxy) actorRef.start From 74f544570898e21eb5b911217caa397051036be6 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 14 Dec 2010 18:57:45 +0100 Subject: [PATCH 03/38] Switch to a match instead of a not-so-cute if --- .../src/main/scala/akka/remote/RemoteServer.scala | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/akka-remote/src/main/scala/akka/remote/RemoteServer.scala b/akka-remote/src/main/scala/akka/remote/RemoteServer.scala index 1ec427d0ab..b8e55c8054 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteServer.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteServer.scala @@ -485,14 +485,12 @@ class RemoteServerHandler( super.handleUpstream(ctx, event) } - override def messageReceived(ctx: ChannelHandlerContext, event: MessageEvent) = { - val message = event.getMessage - if (message eq null) throw new IllegalActorStateException("Message in remote MessageEvent is null: " + event) - if (message.isInstanceOf[RemoteMessageProtocol]) { - val requestProtocol = message.asInstanceOf[RemoteMessageProtocol] - if (RemoteServer.REQUIRE_COOKIE) authenticateRemoteClient(requestProtocol, ctx) + override def messageReceived(ctx: ChannelHandlerContext, event: MessageEvent) = event.getMessage match { + case null => throw new IllegalActorStateException("Message in remote MessageEvent is null: " + event) + case requestProtocol: RemoteMessageProtocol => + if (RemoteServer.REQUIRE_COOKIE) authenticateRemoteClient(requestProtocol, ctx) handleRemoteMessageProtocol(requestProtocol, event.getChannel) - } + case _ => //ignore } override def exceptionCaught(ctx: ChannelHandlerContext, event: ExceptionEvent) = { From 5f651c73bab5ec8f052525509d184da6e30aa12d Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 15 Dec 2010 17:52:31 +0100 Subject: [PATCH 04/38] Got API in place now and RemoteServer/Client/Node etc purged. Need to get test-compile to work so I can start testing the new stuff... --- .../src/main/scala/akka/actor/Actor.scala | 51 +- .../src/main/scala/akka/actor/ActorRef.scala | 60 +- .../main/scala/akka/actor/ActorRegistry.scala | 147 ++++- .../main/scala/akka/actor/Supervisor.scala | 2 +- .../scala/akka/dispatch/MessageHandling.scala | 2 +- .../remoteinterface/RemoteInterface.scala | 98 ++- .../scala/akka/util/ReflectiveAccess.scala | 89 +-- .../java/akka/config/SupervisionConfig.java | 2 +- .../remote/BootableRemoteActorService.scala | 4 +- ...eServer.scala => NettyRemoteSupport.scala} | 587 +++++++++++++++--- .../main/scala/akka/remote/RemoteClient.scala | 515 --------------- .../serialization/SerializationProtocol.scala | 10 +- .../ClientInitiatedRemoteActorSpec.scala | 98 ++- .../scala/remote/RemoteSupervisorSpec.scala | 3 - .../ServerManagedRemoteActorSample.scala | 9 +- .../main/scala/akka/actor/TypedActor.scala | 4 +- 16 files changed, 837 insertions(+), 844 deletions(-) rename akka-remote/src/main/scala/akka/remote/{RemoteServer.scala => NettyRemoteSupport.scala} (59%) delete mode 100644 akka-remote/src/main/scala/akka/remote/RemoteClient.scala diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index 58ceaf44b3..d604f3f441 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -126,7 +126,7 @@ object Actor extends Logging { private[actor] val actorRefInCreation = new scala.util.DynamicVariable[Option[ActorRef]](None) - /** + /** * Creates an ActorRef out of the Actor with type T. *

    *   import Actor._
@@ -140,7 +140,8 @@ object Actor extends Logging {
    *   val actor = actorOf[MyActor].start
    * 
*/ - def actorOf[T <: Actor : Manifest]: ActorRef = actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) + def actorOf[T <: Actor : Manifest]: ActorRef = + ActorRegistry.actorOf[T] /** * Creates a Client-managed ActorRef out of the Actor of the specified Class. @@ -158,7 +159,7 @@ object Actor extends Logging { * */ def actorOf[T <: Actor : Manifest](host: String, port: Int): ActorRef = - actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]], host, port) + ActorRegistry.actorOf[T](host,port) /** * Creates an ActorRef out of the Actor of the specified Class. @@ -174,15 +175,8 @@ object Actor extends Logging { * val actor = actorOf(classOf[MyActor]).start * */ - def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(() => { - import ReflectiveAccess.{ createInstance, noParams, noArgs } - createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( - throw new ActorInitializationException( - "Could not instantiate Actor" + - "\nMake sure Actor is NOT defined inside a class/trait," + - "\nif so put it outside the class/trait, f.e. in a companion object," + - "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")) - }) + def actorOf(clazz: Class[_ <: Actor]): ActorRef = + ActorRegistry.actorOf(clazz) /** * Creates a Client-managed ActorRef out of the Actor of the specified Class. @@ -199,23 +193,8 @@ object Actor extends Logging { * val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start * */ - def actorOf(clazz: Class[_ <: Actor], host: String, port: Int, timeout: Long = TIMEOUT): ActorRef = { - import ReflectiveAccess._ - import ReflectiveAccess.RemoteServerModule.{HOSTNAME,PORT} - ensureRemotingEnabled - - (host,port) match { - case null => throw new IllegalArgumentException("No location specified") - case (HOSTNAME, PORT) => actorOf(clazz) //Local - case _ => new RemoteActorRef(clazz.getName, - clazz.getName, - host, - port, - timeout, - true, //Client managed - None) - } - } + def actorOf(clazz: Class[_ <: Actor], host: String, port: Int, timeout: Long = Actor.TIMEOUT): ActorRef = + ActorRegistry.actorOf(clazz, host, port, timeout) /** * Creates an ActorRef out of the Actor. Allows you to pass in a factory function @@ -235,7 +214,7 @@ object Actor extends Logging { * val actor = actorOf(new MyActor).start * */ - def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory) + def actorOf(factory: => Actor): ActorRef = ActorRegistry.actorOf(factory) /** * Use to spawn out a block of code in an event-driven actor. Will shut actor down when @@ -252,15 +231,9 @@ object Actor extends Logging { * } * */ - def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = { - case object Spawn - actorOf(new Actor() { - self.dispatcher = dispatcher - def receive = { - case Spawn => try { body } finally { self.stop } - } - }).start ! Spawn - } + def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = + ActorRegistry.spawn(body)(dispatcher) + /** * Implicitly converts the given Option[Any] to a AnyOptionAsTypedOption which offers the method as[T] diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index d938366b62..c97edba37e 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -80,6 +80,8 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal protected[akka] var _futureTimeout: Option[ScheduledFuture[AnyRef]] = None protected[akka] val guard = new ReentrantGuard + private[akka] def registry: ActorRegistryInstance + /** * User overridable callback/setting. *

@@ -541,6 +543,7 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal * @author Jonas Bonér */ class LocalActorRef private[akka] ( + private[akka] val registry: ActorRegistryInstance, private[this] val actorFactory: () => Actor) extends ActorRef with ScalaActorRef { @@ -563,7 +566,9 @@ class LocalActorRef private[akka] ( if (isRunning) initializeActorInstance // used only for deserialization - private[akka] def this(__uuid: Uuid, + private[akka] def this( + __registry: ActorRegistryInstance, + __uuid: Uuid, __id: String, __hostname: String, __port: Int, @@ -573,7 +578,7 @@ class LocalActorRef private[akka] ( __supervisor: Option[ActorRef], __hotswap: Stack[PartialFunction[Any, Unit]], __factory: () => Actor) = { - this(__factory) + this(__registry, __factory) _uuid = __uuid id = __id timeout = __timeout @@ -583,7 +588,7 @@ class LocalActorRef private[akka] ( hotswap = __hotswap setActorSelfFields(actor,this) start - ActorRegistry.register(this) + __registry.register(this) } // ========= PUBLIC FUNCTIONS ========= @@ -644,7 +649,7 @@ class LocalActorRef private[akka] ( dispatcher.detach(this) _status = ActorRefInternals.SHUTDOWN actor.postStop - ActorRegistry.unregister(this) + registry.unregister(this) setActorSelfFields(actorInstance.get,null) } //else if (isBeingRestarted) throw new ActorKilledException("Actor [" + toString + "] is being restarted.") } @@ -942,8 +947,8 @@ class LocalActorRef private[akka] ( } //TODO: REVISIT: REMOVE - /* - protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = guard.withGuard { + + /*protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = guard.withGuard { ensureRemotingEnabled if (_supervisor.isDefined) { remoteAddress.foreach(address => RemoteClientModule.registerSupervisorForActor(address, this)) @@ -1053,34 +1058,8 @@ class LocalActorRef private[akka] ( private def initializeActorInstance = { actor.preStart // run actor preStart Actor.log.slf4j.trace("[{}] has started", toString) - ActorRegistry.register(this) + registry.register(this) } - - /* - private def serializeMessage(message: AnyRef): AnyRef = if (Actor.SERIALIZE_MESSAGES) { - if (!message.isInstanceOf[String] && - !message.isInstanceOf[Byte] && - !message.isInstanceOf[Int] && - !message.isInstanceOf[Long] && - !message.isInstanceOf[Float] && - !message.isInstanceOf[Double] && - !message.isInstanceOf[Boolean] && - !message.isInstanceOf[Char] && - !message.isInstanceOf[Tuple2[_, _]] && - !message.isInstanceOf[Tuple3[_, _, _]] && - !message.isInstanceOf[Tuple4[_, _, _, _]] && - !message.isInstanceOf[Tuple5[_, _, _, _, _]] && - !message.isInstanceOf[Tuple6[_, _, _, _, _, _]] && - !message.isInstanceOf[Tuple7[_, _, _, _, _, _, _]] && - !message.isInstanceOf[Tuple8[_, _, _, _, _, _, _, _]] && - !message.getClass.isArray && - !message.isInstanceOf[List[_]] && - !message.isInstanceOf[scala.collection.immutable.Map[_, _]] && - !message.isInstanceOf[scala.collection.immutable.Set[_]]) { - Serializer.Java.deepClone(message) - } else message - } else message - */ } /** @@ -1099,12 +1078,13 @@ object RemoteActorSystemMessage { * @author Jonas Bonér */ private[akka] case class RemoteActorRef private[akka] ( + registry: ActorRegistryInstance, classOrServiceName: String, val actorClassName: String, val hostname: String, val port: Int, _timeout: Long, - clientManaged: Boolean, + clientManaged: Boolean, //TODO: REVISIT: ENCODE CLIENT_MANAGED INTO REMOTE PROTOCOL loader: Option[ClassLoader], val actorType: ActorType = ActorType.ScalaActor) extends ActorRef with ScalaActorRef { @@ -1119,16 +1099,14 @@ private[akka] case class RemoteActorRef private[akka] ( start def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = - RemoteClientModule.send[Any]( - message, senderOption, None, homeAddress, timeout, true, this, None, actorType) + registry.remote.send[Any](message, senderOption, None, homeAddress, timeout, true, this, None, actorType) def postMessageToMailboxAndCreateFutureResultWithTimeout[T]( message: Any, timeout: Long, senderOption: Option[ActorRef], senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { - val future = RemoteClientModule.send[T]( - message, senderOption, senderFuture, homeAddress, timeout, false, this, None, actorType) + val future = registry.remote.send[T](message, senderOption, senderFuture, homeAddress, timeout, false, this, None, actorType) if (future.isDefined) future.get else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) } @@ -1136,7 +1114,7 @@ private[akka] case class RemoteActorRef private[akka] ( def start: ActorRef = synchronized { _status = ActorRefInternals.RUNNING if (clientManaged) { - RemoteClientModule.register(homeAddress, uuid) + registry.remote.registerClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) } this } @@ -1146,8 +1124,8 @@ private[akka] case class RemoteActorRef private[akka] ( _status = ActorRefInternals.SHUTDOWN postMessageToMailbox(RemoteActorSystemMessage.Stop, None) if (clientManaged) { - RemoteClientModule.unregister(homeAddress, uuid) - ActorRegistry.remote.unregister(this) //TODO: Why does this need to be deregistered from the server? + registry.remote.unregisterClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) + registry.remote.unregister(this) //TODO: REVISIT: Why does this need to be deregistered from the server? } } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala index d80e5273da..f966928965 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala @@ -13,8 +13,9 @@ import java.util.{Set => JSet} import annotation.tailrec import akka.util.ReflectiveAccess._ import java.net.InetSocketAddress -import akka.util. {ReadWriteGuard, Address, ListenerManagement} -import akka.remoteinterface.RemoteServerModule +import akka.util. {ReflectiveAccess, ReadWriteGuard, Address, ListenerManagement} +import akka.dispatch. {MessageDispatcher, Dispatchers} +import akka.remoteinterface. {RemoteSupport, RemoteServerModule} /** * Base trait for ActorRegistry events, allows listen to when an actor is added and removed from the ActorRegistry. @@ -37,7 +38,9 @@ case class ActorUnregistered(actor: ActorRef) extends ActorRegistryEvent * * @author Jonas Bonér */ -object ActorRegistry extends ListenerManagement { +object ActorRegistry extends ActorRegistryInstance(ReflectiveAccess.Remote.defaultRemoteSupport) + +class ActorRegistryInstance(remoteBootstrap: Option[(ActorRegistryInstance) => RemoteSupport]) extends ListenerManagement { private val actorsByUUID = new ConcurrentHashMap[Uuid, ActorRef] private val actorsById = new Index[String,ActorRef] private val remoteActorSets = Map[Address, RemoteActorSet]() @@ -227,11 +230,127 @@ object ActorRegistry extends ListenerManagement { /** * Handy access to the RemoteServer module */ - lazy val remote: RemoteServerModule = getObjectFor("akka.remote.RemoteNode$") match { - case Some(module) => module - case None => - log.slf4j.error("Wanted remote module but didn't exist on classpath") - null + lazy val remote: RemoteSupport = remoteBootstrap.map(_(this)).getOrElse(throw new UnsupportedOperationException("You need to have akka-remote on classpath")) + + /** + * Creates an ActorRef out of the Actor with type T. + *

+   *   import Actor._
+   *   val actor = actorOf[MyActor]
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf[MyActor].start
+   * 
+ */ + def actorOf[T <: Actor : Manifest]: ActorRef = actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) + + /** + * Creates a Client-managed ActorRef out of the Actor of the specified Class. + * If the supplied host and port is identical of the configured local node, it will be a local actor + *
+   *   import Actor._
+   *   val actor = actorOf[MyActor]("www.akka.io",2552)
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf[MyActor]("www.akka.io",2552).start
+   * 
+ */ + def actorOf[T <: Actor : Manifest](host: String, port: Int): ActorRef = + actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]], host, port) + + /** + * Creates an ActorRef out of the Actor of the specified Class. + *
+   *   import Actor._
+   *   val actor = actorOf(classOf[MyActor])
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf(classOf[MyActor]).start
+   * 
+ */ + def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(this, () => { + import ReflectiveAccess.{ createInstance, noParams, noArgs } + createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( + throw new ActorInitializationException( + "Could not instantiate Actor" + + "\nMake sure Actor is NOT defined inside a class/trait," + + "\nif so put it outside the class/trait, f.e. in a companion object," + + "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")) + }) + + /** + * Creates a Client-managed ActorRef out of the Actor of the specified Class. + * If the supplied host and port is identical of the configured local node, it will be a local actor + *
+   *   import Actor._
+   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552)
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start
+   * 
+ */ + def actorOf(clazz: Class[_ <: Actor], host: String, port: Int, timeout: Long = Actor.TIMEOUT): ActorRef = + remote.clientManagedActorOf(clazz, host, port, timeout) + + /** + * Creates an ActorRef out of the Actor. Allows you to pass in a factory function + * that creates the Actor. Please note that this function can be invoked multiple + * times if for example the Actor is supervised and needs to be restarted. + *

+ * This function should NOT be used for remote actors. + *

+   *   import Actor._
+   *   val actor = actorOf(new MyActor)
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf(new MyActor).start
+   * 
+ */ + def actorOf(factory: => Actor): ActorRef = new LocalActorRef(this,() => factory) + + /** + * Use to spawn out a block of code in an event-driven actor. Will shut actor down when + * the block has been executed. + *

+ * NOTE: If used from within an Actor then has to be qualified with 'ActorRegistry.spawn' since + * there is a method 'spawn[ActorType]' in the Actor trait already. + * Example: + *

+   * import ActorRegistry.{spawn}
+   *
+   * spawn  {
+   *   ... // do stuff
+   * }
+   * 
+ */ + def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = { + case object Spawn + actorOf(new Actor() { + self.dispatcher = dispatcher + def receive = { + case Spawn => try { body } finally { self.stop } + } + }).start ! Spawn } @@ -303,12 +422,12 @@ object ActorRegistry extends ListenerManagement { private[akka] def typedActorsFactories(address: Address) = actorsFor(address).typedActorsFactories private[akka] class RemoteActorSet { - private[ActorRegistry] val actors = new ConcurrentHashMap[String, ActorRef] - private[ActorRegistry] val actorsByUuid = new ConcurrentHashMap[String, ActorRef] - private[ActorRegistry] val actorsFactories = new ConcurrentHashMap[String, () => ActorRef] - private[ActorRegistry] val typedActors = new ConcurrentHashMap[String, AnyRef] - private[ActorRegistry] val typedActorsByUuid = new ConcurrentHashMap[String, AnyRef] - private[ActorRegistry] val typedActorsFactories = new ConcurrentHashMap[String, () => AnyRef] + private[ActorRegistryInstance] val actors = new ConcurrentHashMap[String, ActorRef] + private[ActorRegistryInstance] val actorsByUuid = new ConcurrentHashMap[String, ActorRef] + private[ActorRegistryInstance] val actorsFactories = new ConcurrentHashMap[String, () => ActorRef] + private[ActorRegistryInstance] val typedActors = new ConcurrentHashMap[String, AnyRef] + private[ActorRegistryInstance] val typedActorsByUuid = new ConcurrentHashMap[String, AnyRef] + private[ActorRegistryInstance] val typedActorsFactories = new ConcurrentHashMap[String, () => AnyRef] } } diff --git a/akka-actor/src/main/scala/akka/actor/Supervisor.scala b/akka-actor/src/main/scala/akka/actor/Supervisor.scala index daf7a962de..288bdfda24 100644 --- a/akka-actor/src/main/scala/akka/actor/Supervisor.scala +++ b/akka-actor/src/main/scala/akka/actor/Supervisor.scala @@ -142,7 +142,7 @@ sealed class Supervisor(handler: FaultHandlingStrategy) { actorRef.lifeCycle = lifeCycle supervisor.link(actorRef) if (registerAsRemoteService) - ActorRegistry.remote.register(actorRef) + ActorRegistry.remote.register(actorRef) //TODO: REVISIT: Is this the most sensible approach? other way of obtaining ActorRegistry? case supervisorConfig @ SupervisorConfig(_, _) => // recursive supervisor configuration val childSupervisor = Supervisor(supervisorConfig) supervisor.link(childSupervisor.supervisor) diff --git a/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala b/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala index f0f2f259d6..d12847e149 100644 --- a/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala +++ b/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala @@ -132,7 +132,7 @@ trait MessageDispatcher extends MailboxFactory with Logging { val i = uuids.iterator while(i.hasNext()) { val uuid = i.next() - ActorRegistry.actorFor(uuid) match { + ActorRegistry.actorFor(uuid) match { //TODO: REVISIT: How to keep track of which registry? case Some(actor) => actor.stop case None => log.slf4j.error("stopAllLinkedActors couldn't find linked actor: " + uuid) diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index 4f6cd11a67..cf246e4ae4 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -5,13 +5,33 @@ package akka.remoteinterface import akka.japi.Creator -import akka.actor.ActorRef -import akka.util.{ReentrantGuard, Logging, ListenerManagement} +import java.net.InetSocketAddress +import akka.actor._ +import akka.util._ +import akka.dispatch.CompletableFuture +import akka.actor. {ActorRegistryInstance, ActorType, RemoteActorRef, ActorRef} +import akka.config.Config.{config, TIME_UNIT} + +trait RemoteModule extends Logging { + def registry: ActorRegistryInstance + def optimizeLocalScoped_?(): Boolean //Apply optimizations for remote operations in local scope + protected[akka] def notifyListeners(message: => Any): Unit +} + + +abstract class RemoteSupport extends ListenerManagement with RemoteServerModule with RemoteClientModule { + def shutdown { + this.shutdownServerModule + this.shutdownClientModule + } + protected override def manageLifeCycleOfListeners = false + protected[akka] override def notifyListeners(message: => Any): Unit = super.notifyListeners(message) +} /** * This is the interface for the RemoteServer functionality, it's used in ActorRegistry.remote */ -trait RemoteServerModule extends ListenerManagement with Logging { +trait RemoteServerModule extends RemoteModule { protected val guard = new ReentrantGuard /** @@ -37,12 +57,12 @@ trait RemoteServerModule extends ListenerManagement with Logging { /** * Starts the server up */ - def start(host: String, port: Int, loader: Option[ClassLoader] = None): RemoteServerModule //TODO possibly hidden + def start(host: String, port: Int, loader: Option[ClassLoader] = None): RemoteServerModule /** * Shuts the server down */ - def shutdown: Unit //TODO possibly hidden + def shutdownServerModule: Unit /** * Register typed actor by interface name. @@ -146,4 +166,72 @@ trait RemoteServerModule extends ListenerManagement with Logging { * NOTE: You need to call this method if you have registered an actor by a custom ID. */ def unregisterTypedPerSessionActor(id: String): Unit +} + +trait RemoteClientModule extends RemoteModule { self: RemoteModule => + + def actorFor(classNameOrServiceId: String, hostname: String, port: Int): ActorRef = + actorFor(classNameOrServiceId, classNameOrServiceId, Actor.TIMEOUT, hostname, port, None) + + def actorFor(classNameOrServiceId: String, hostname: String, port: Int, loader: ClassLoader): ActorRef = + actorFor(classNameOrServiceId, classNameOrServiceId, Actor.TIMEOUT, hostname, port, Some(loader)) + + def actorFor(serviceId: String, className: String, hostname: String, port: Int): ActorRef = + actorFor(serviceId, className, Actor.TIMEOUT, hostname, port, None) + + def actorFor(serviceId: String, className: String, hostname: String, port: Int, loader: ClassLoader): ActorRef = + actorFor(serviceId, className, Actor.TIMEOUT, hostname, port, Some(loader)) + + def actorFor(classNameOrServiceId: String, timeout: Long, hostname: String, port: Int): ActorRef = + actorFor(classNameOrServiceId, classNameOrServiceId, timeout, hostname, port, None) + + def actorFor(classNameOrServiceId: String, timeout: Long, hostname: String, port: Int, loader: ClassLoader): ActorRef = + actorFor(classNameOrServiceId, classNameOrServiceId, timeout, hostname, port, Some(loader)) + + def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int): ActorRef = + actorFor(serviceId, className, timeout, hostname, port, None) + + def typedActorFor[T](intfClass: Class[T], serviceIdOrClassName: String, hostname: String, port: Int): T = + typedActorFor(intfClass, serviceIdOrClassName, serviceIdOrClassName, Actor.TIMEOUT, hostname, port, None) + + def typedActorFor[T](intfClass: Class[T], serviceIdOrClassName: String, timeout: Long, hostname: String, port: Int): T = + typedActorFor(intfClass, serviceIdOrClassName, serviceIdOrClassName, timeout, hostname, port, None) + + def typedActorFor[T](intfClass: Class[T], serviceIdOrClassName: String, timeout: Long, hostname: String, port: Int, loader: ClassLoader): T = + typedActorFor(intfClass, serviceIdOrClassName, serviceIdOrClassName, timeout, hostname, port, Some(loader)) + + def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, hostname: String, port: Int, loader: ClassLoader): T = + typedActorFor(intfClass, serviceId, implClassName, timeout, hostname, port, Some(loader)) + + def clientManagedActorOf(clazz: Class[_ <: Actor], host: String, port: Int, timeout: Long): ActorRef + + /** Methods that needs to be implemented by a transport **/ + + protected[akka] def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, host: String, port: Int, loader: Option[ClassLoader]): T + + protected[akka] def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): ActorRef + + protected[akka] def send[T](message: Any, + senderOption: Option[ActorRef], + senderFuture: Option[CompletableFuture[T]], + remoteAddress: InetSocketAddress, + timeout: Long, + isOneWay: Boolean, + actorRef: ActorRef, + typedActorInfo: Option[Tuple2[String, String]], + actorType: ActorType): Option[CompletableFuture[T]] + + //TODO: REVISIT: IMPLEMENT OR REMOVE + //private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef + + //private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef + + /** + * Clean-up all open connections. + */ + def shutdownClientModule: Unit + + private[akka] def registerClientManagedActor(hostname: String, port: Int, uuid: Uuid): Unit + + private[akka] def unregisterClientManagedActor(hostname: String, port: Int, uuid: Uuid): Unit } \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala index 14bde14e89..a923b52dd2 100644 --- a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala +++ b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala @@ -4,12 +4,13 @@ package akka.util -import akka.actor.{ActorRef, IllegalActorStateException, ActorType, Uuid} import akka.dispatch.{Future, CompletableFuture, MessageInvocation} import akka.config.{Config, ModuleNotAvailableException} import akka.AkkaException import java.net.InetSocketAddress +import akka.remoteinterface.RemoteSupport +import akka.actor._ /** * Helper class for reflective access to different modules in order to allow optional loading of modules. @@ -20,10 +21,10 @@ object ReflectiveAccess extends Logging { val loader = getClass.getClassLoader - lazy val isRemotingEnabled = RemoteClientModule.isEnabled + lazy val isRemotingEnabled = Remote.isEnabled lazy val isTypedActorEnabled = TypedActorModule.isEnabled - def ensureRemotingEnabled = RemoteClientModule.ensureEnabled + def ensureRemotingEnabled = Remote.ensureEnabled def ensureTypedActorEnabled = TypedActorModule.ensureEnabled /** @@ -31,82 +32,26 @@ object ReflectiveAccess extends Logging { * * @author Jonas Bonér */ - object RemoteClientModule { + object Remote { + val TRANSPORT = Config.config.getString("akka.remote.transport","akka.remote.NettyRemoteSupport") + val HOSTNAME = Config.config.getString("akka.remote.server.hostname", "localhost") + val PORT = Config.config.getInt("akka.remote.server.port", 2552) - type RemoteClient = { - def send[T]( - message: Any, - senderOption: Option[ActorRef], - senderFuture: Option[CompletableFuture[_]], - remoteAddress: InetSocketAddress, - timeout: Long, - isOneWay: Boolean, - actorRef: ActorRef, - typedActorInfo: Option[Tuple2[String, String]], - actorType: ActorType): Option[CompletableFuture[T]] - def registerSupervisorForActor(actorRef: ActorRef) - } - - type RemoteClientObject = { - def register(hostname: String, port: Int, uuid: Uuid): Unit - def unregister(hostname: String, port: Int, uuid: Uuid): Unit - def clientFor(address: InetSocketAddress): RemoteClient - def clientFor(hostname: String, port: Int, loader: Option[ClassLoader]): RemoteClient - } - - lazy val isEnabled = remoteClientObjectInstance.isDefined + lazy val isEnabled = remoteSupportClass.isDefined def ensureEnabled = if (!isEnabled) throw new ModuleNotAvailableException( "Can't load the remoting module, make sure that akka-remote.jar is on the classpath") - val remoteClientObjectInstance: Option[RemoteClientObject] = - getObjectFor("akka.remote.RemoteClient$") + //TODO: REVISIT: Make class configurable + val remoteSupportClass: Option[Class[_ <: RemoteSupport]] = getClassFor(TRANSPORT) - def register(address: InetSocketAddress, uuid: Uuid) = { - ensureEnabled - remoteClientObjectInstance.get.register(address.getHostName, address.getPort, uuid) + protected[akka] val defaultRemoteSupport: Option[ActorRegistryInstance => RemoteSupport] = remoteSupportClass map { + remoteClass => (registry: ActorRegistryInstance) => + createInstance[RemoteSupport](remoteClass,Array[Class[_]](classOf[ActorRegistryInstance]),Array[AnyRef](registry)). + getOrElse(throw new ModuleNotAvailableException("Can't instantiate "+ + remoteClass.getName+ + ", make sure that akka-remote.jar is on the classpath")) } - - def unregister(address: InetSocketAddress, uuid: Uuid) = { - ensureEnabled - remoteClientObjectInstance.get.unregister(address.getHostName, address.getPort, uuid) - } - - def registerSupervisorForActor(remoteAddress: InetSocketAddress, actorRef: ActorRef) = { - ensureEnabled - val remoteClient = remoteClientObjectInstance.get.clientFor(remoteAddress) - remoteClient.registerSupervisorForActor(actorRef) - } - - def clientFor(hostname: String, port: Int, loader: Option[ClassLoader]): RemoteClient = { - ensureEnabled - remoteClientObjectInstance.get.clientFor(hostname, port, loader) - } - - def send[T]( - message: Any, - senderOption: Option[ActorRef], - senderFuture: Option[CompletableFuture[_]], - remoteAddress: InetSocketAddress, - timeout: Long, - isOneWay: Boolean, - actorRef: ActorRef, - typedActorInfo: Option[Tuple2[String, String]], - actorType: ActorType): Option[CompletableFuture[T]] = { - ensureEnabled - clientFor(remoteAddress.getHostName, remoteAddress.getPort, None).send[T]( - message, senderOption, senderFuture, remoteAddress, timeout, isOneWay, actorRef, typedActorInfo, actorType) - } - } - - /** - * Reflective access to the RemoteServer module. - * - * @author Jonas Bonér - */ - object RemoteServerModule { - val HOSTNAME = Config.config.getString("akka.remote.server.hostname", "localhost") - val PORT = Config.config.getInt("akka.remote.server.port", 2552) } /** diff --git a/akka-actor/src/test/java/akka/config/SupervisionConfig.java b/akka-actor/src/test/java/akka/config/SupervisionConfig.java index 271e6c490e..fd71c86bf1 100644 --- a/akka-actor/src/test/java/akka/config/SupervisionConfig.java +++ b/akka-actor/src/test/java/akka/config/SupervisionConfig.java @@ -13,7 +13,7 @@ public class SupervisionConfig { public SupervisorConfig createSupervisorConfig(List toSupervise) { ArrayList targets = new ArrayList(toSupervise.size()); for(ActorRef ref : toSupervise) { - targets.add(new Supervise(ref, permanent(), new RemoteAddress("localhost",2552))); + targets.add(new Supervise(ref, permanent(), true)); } return new SupervisorConfig(new AllForOneStrategy(new Class[] { Exception.class },50,1000), targets.toArray(new Server[0])); diff --git a/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala b/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala index 9c2eeb9c2c..58892c2ad3 100644 --- a/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala +++ b/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala @@ -17,7 +17,7 @@ trait BootableRemoteActorService extends Bootable with Logging { self: BootableActorLoaderService => protected lazy val remoteServerThread = new Thread(new Runnable() { - import ReflectiveAccess.RemoteServerModule.{HOSTNAME,PORT} + import ReflectiveAccess.Remote.{HOSTNAME,PORT} def run = ActorRegistry.remote.start(HOSTNAME,PORT,loader = self.applicationLoader) }, "Akka Remote Service") @@ -34,7 +34,7 @@ trait BootableRemoteActorService extends Bootable with Logging { abstract override def onUnload = { log.slf4j.info("Shutting down Remote Actors Service") - RemoteNode.shutdown + ActorRegistry.remote.shutdown if (remoteServerThread.isAlive) remoteServerThread.join(1000) log.slf4j.info("Remote Actors Service has been shut down") super.onUnload diff --git a/akka-remote/src/main/scala/akka/remote/RemoteServer.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala similarity index 59% rename from akka-remote/src/main/scala/akka/remote/RemoteServer.scala rename to akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index b8e55c8054..459eebcaf9 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteServer.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -4,63 +4,470 @@ package akka.remote -import java.lang.reflect.InvocationTargetException -import java.net.InetSocketAddress -import java.util.concurrent.{ConcurrentHashMap, Executors} -import java.util.{Map => JMap} - -import akka.actor.Actor._ -import akka.actor.{Actor, TypedActor, ActorRef, IllegalActorStateException, RemoteActorSystemMessage, uuidFrom, Uuid, ActorRegistry, LifeCycleMessage, ActorType => AkkaActorType} -import akka.util._ +import akka.remote.protocol.RemoteProtocol.{ActorType => ActorTypeProtocol, _} +import akka.dispatch.{DefaultCompletableFuture, CompletableFuture, Future} import akka.remote.protocol.RemoteProtocol._ import akka.remote.protocol.RemoteProtocol.ActorType._ -import akka.config.Config._ import akka.config.ConfigurationException import akka.serialization.RemoteActorSerialization +import akka.japi.Creator +import akka.actor.{ActorRegistryInstance, Actor, RemoteActorRef, TypedActor, ActorRef, IllegalActorStateException, + RemoteActorSystemMessage, uuidFrom, Uuid, Exit, ActorRegistry, LifeCycleMessage, ActorType => AkkaActorType} +import akka.remoteinterface. {RemoteSupport, RemoteModule, RemoteServerModule, RemoteClientModule} +import akka.config.Config._ import akka.serialization.RemoteActorSerialization._ +import akka.AkkaException +import akka.actor.Actor._ +import akka.util._ -import org.jboss.netty.bootstrap.ServerBootstrap import org.jboss.netty.channel._ -import org.jboss.netty.channel.group.{DefaultChannelGroup, ChannelGroup} +import org.jboss.netty.channel.group.{DefaultChannelGroup,ChannelGroup} +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory -import org.jboss.netty.handler.codec.frame.{LengthFieldBasedFrameDecoder, LengthFieldPrepender} -import org.jboss.netty.handler.codec.protobuf.{ProtobufDecoder, ProtobufEncoder} -import org.jboss.netty.handler.codec.compression.{ZlibEncoder, ZlibDecoder} +import org.jboss.netty.bootstrap.{ServerBootstrap,ClientBootstrap} +import org.jboss.netty.handler.codec.frame.{ LengthFieldBasedFrameDecoder, LengthFieldPrepender } +import org.jboss.netty.handler.codec.compression.{ ZlibDecoder, ZlibEncoder } +import org.jboss.netty.handler.codec.protobuf.{ ProtobufDecoder, ProtobufEncoder } +import org.jboss.netty.handler.timeout.ReadTimeoutHandler +import org.jboss.netty.util.{ TimerTask, Timeout, HashedWheelTimer } import org.jboss.netty.handler.ssl.SslHandler -import scala.collection.mutable.Map +import java.net.{ SocketAddress, InetSocketAddress } +import java.util.concurrent.{ TimeUnit, Executors, ConcurrentMap, ConcurrentHashMap, ConcurrentSkipListSet } +import java.util.concurrent.atomic.{ AtomicLong, AtomicBoolean } + +import scala.collection.mutable.{ HashSet, HashMap } import scala.reflect.BeanProperty -import akka.dispatch. {Future, DefaultCompletableFuture, CompletableFuture} -import akka.japi.Creator -import akka.remoteinterface.RemoteServerModule +import java.lang.reflect.InvocationTargetException + + /** - * Use this object if you need a single remote server on a specific node. - * - *
- * // takes hostname and port from 'akka.conf'
- * RemoteNode.start
- * 
- * - *
- * RemoteNode.start(hostname, port)
- * 
- * - * You can specify the class loader to use to load the remote actors. - *
- * RemoteNode.start(hostname, port, classLoader)
- * 
- * - * If you need to create more than one, then you can use the RemoteServer: - * - *
- * val server = new RemoteServer
- * server.start(hostname, port)
- * 
+ * Life-cycle events for RemoteClient. + */ +sealed trait RemoteClientLifeCycleEvent +case class RemoteClientError( + @BeanProperty val cause: Throwable, + @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent +case class RemoteClientDisconnected( + @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent +case class RemoteClientConnected( + @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent +case class RemoteClientStarted( + @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent +case class RemoteClientShutdown( + @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent + +/** + * Thrown for example when trying to send a message using a RemoteClient that is either not started or shut down. + */ +class RemoteClientException private[akka] (message: String, @BeanProperty val client: RemoteClient) extends AkkaException(message) + +/** + * The RemoteClient object manages RemoteClient instances and gives you an API to lookup remote actor handles. * * @author Jonas Bonér */ -object RemoteNode extends RemoteServer +trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagement with Logging => + private val remoteClients = new HashMap[String, RemoteClient] + private val remoteActors = new HashMap[Address, HashSet[Uuid]] + + protected[akka] def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): T = + TypedActor.createProxyForRemoteActorRef(intfClass, RemoteActorRef(registry, serviceId, implClassName, hostname, port, timeout, false, loader, AkkaActorType.TypedActor)) + + protected[akka] def send[T](message: Any, + senderOption: Option[ActorRef], + senderFuture: Option[CompletableFuture[T]], + remoteAddress: InetSocketAddress, + timeout: Long, + isOneWay: Boolean, + actorRef: ActorRef, + typedActorInfo: Option[Tuple2[String, String]], + actorType: AkkaActorType): Option[CompletableFuture[T]] = + clientFor(remoteAddress, None).send[T](message, senderOption, senderFuture, remoteAddress, timeout, isOneWay, actorRef, typedActorInfo, actorType) + + private[akka] def clientFor( + address: InetSocketAddress, loader: Option[ClassLoader]): RemoteClient = synchronized { //TODO: REVIST: synchronized here seems bottlenecky + val hostname = address.getHostName + val port = address.getPort + val hash = hostname + ':' + port + loader.foreach(MessageSerializer.setClassLoader(_))//TODO: REVISIT: THIS SMELLS FUNNY + if (remoteClients.contains(hash)) remoteClients(hash) + else { + val client = new RemoteClient(hostname, port, loader, self.notifyListeners _) + client.connect + remoteClients += hash -> client + client + } + } + + def shutdownClientFor(address: InetSocketAddress) = synchronized { + val hostname = address.getHostName + val port = address.getPort + val hash = hostname + ':' + port + if (remoteClients.contains(hash)) { + val client = remoteClients(hash) + client.shutdown + remoteClients -= hash + } + } + //TODO: REVISIT IMPLEMENT OR REMOVE + /*private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef = + clientFor().registerSupervisorForActor(actorRef) + + private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef = + clientFor().deregisterSupervisorForActor(actorRef)*/ + + /** + * Clean-up all open connections. + */ + def shutdownClientModule = synchronized { + remoteClients.foreach({ case (addr, client) => client.shutdown }) + remoteClients.clear + } + + def registerClientManagedActor(hostname: String, port: Int, uuid: Uuid) = synchronized { + actorsFor(Address(hostname, port)) += uuid + } + + private[akka] def unregisterClientManagedActor(hostname: String, port: Int, uuid: Uuid) = synchronized { + val set = actorsFor(Address(hostname, port)) + set -= uuid + if (set.isEmpty) shutdownClientFor(new InetSocketAddress(hostname, port)) + } + + private[akka] def actorsFor(remoteServerAddress: Address): HashSet[Uuid] = { + val set = remoteActors.get(remoteServerAddress) + if (set.isDefined && (set.get ne null)) set.get + else { + val remoteActorSet = new HashSet[Uuid] + remoteActors.put(remoteServerAddress, remoteActorSet) + remoteActorSet + } + } +} + +object RemoteClient { + val SECURE_COOKIE: Option[String] = { + val cookie = config.getString("akka.remote.secure-cookie", "") + if (cookie == "") None else Some(cookie) + } + + val READ_TIMEOUT = Duration(config.getInt("akka.remote.client.read-timeout", 1), TIME_UNIT) + val RECONNECT_DELAY = Duration(config.getInt("akka.remote.client.reconnect-delay", 5), TIME_UNIT) + val MESSAGE_FRAME_SIZE = config.getInt("akka.remote.client.message-frame-size", 1048576) +} +/** + * RemoteClient represents a connection to a RemoteServer. Is used to send messages to remote actors on the RemoteServer. + * + * @author Jonas Bonér + */ +class RemoteClient private[akka] ( + val hostname: String, + val port: Int, + val loader: Option[ClassLoader] = None, + val notifyListeners: (=> Any) => Unit) extends Logging { + val name = "RemoteClient@" + hostname + "::" + port + + //FIXME Should these be clear:ed on postStop? + private val futures = new ConcurrentHashMap[Uuid, CompletableFuture[_]] + private val supervisors = new ConcurrentHashMap[Uuid, ActorRef] + + private val remoteAddress = new InetSocketAddress(hostname, port) + + //FIXME rewrite to a wrapper object (minimize volatile access and maximize encapsulation) + @volatile + private var bootstrap: ClientBootstrap = _ + @volatile + private[remote] var connection: ChannelFuture = _ + @volatile + private[remote] var openChannels: DefaultChannelGroup = _ + @volatile + private var timer: HashedWheelTimer = _ + private[remote] val runSwitch = new Switch() + private[remote] val isAuthenticated = new AtomicBoolean(false) + + private[remote] def isRunning = runSwitch.isOn + + private val reconnectionTimeWindow = Duration(config.getInt( + "akka.remote.client.reconnection-time-window", 600), TIME_UNIT).toMillis + @volatile + private var reconnectionTimeWindowStart = 0L + + def connect = runSwitch switchOn { + openChannels = new DefaultChannelGroup(classOf[RemoteClient].getName) + timer = new HashedWheelTimer + + bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool)) + bootstrap.setPipelineFactory(new RemoteClientPipelineFactory(name, futures, supervisors, bootstrap, remoteAddress, timer, this)) + bootstrap.setOption("tcpNoDelay", true) + bootstrap.setOption("keepAlive", true) + + log.slf4j.info("Starting remote client connection to [{}:{}]", hostname, port) + + // Wait until the connection attempt succeeds or fails. + connection = bootstrap.connect(remoteAddress) + val channel = connection.awaitUninterruptibly.getChannel + openChannels.add(channel) + + if (!connection.isSuccess) { + notifyListeners(RemoteClientError(connection.getCause, this)) + log.slf4j.error("Remote client connection to [{}:{}] has failed", hostname, port) + log.slf4j.debug("Remote client connection failed", connection.getCause) + } + notifyListeners(RemoteClientStarted(this)) + } + + def shutdown = runSwitch switchOff { + log.slf4j.info("Shutting down {}", name) + notifyListeners(RemoteClientShutdown(this)) + timer.stop + timer = null + openChannels.close.awaitUninterruptibly + openChannels = null + bootstrap.releaseExternalResources + bootstrap = null + connection = null + log.slf4j.info("{} has been shut down", name) + } + + def send[T]( + message: Any, + senderOption: Option[ActorRef], + senderFuture: Option[CompletableFuture[T]], + remoteAddress: InetSocketAddress, + timeout: Long, + isOneWay: Boolean, + actorRef: ActorRef, + typedActorInfo: Option[Tuple2[String, String]], + actorType: AkkaActorType): Option[CompletableFuture[T]] = { + val cookie = if (isAuthenticated.compareAndSet(false, true)) RemoteClient.SECURE_COOKIE + else None + send(createRemoteMessageProtocolBuilder( + Some(actorRef), + Left(actorRef.uuid), + actorRef.id, + actorRef.actorClassName, + actorRef.timeout, + Left(message), + isOneWay, + senderOption, + typedActorInfo, + actorType, + cookie + ).build, senderFuture) + } + + def send[T]( + request: RemoteMessageProtocol, + senderFuture: Option[CompletableFuture[T]]): Option[CompletableFuture[T]] = { + if (isRunning) { + if (request.getOneWay) { + connection.getChannel.write(request) + None + } else { + val futureResult = if (senderFuture.isDefined) senderFuture.get + else new DefaultCompletableFuture[T](request.getActorInfo.getTimeout) + futures.put(uuidFrom(request.getUuid.getHigh, request.getUuid.getLow), futureResult) + connection.getChannel.write(request) + Some(futureResult) + } + } else { + val exception = new RemoteClientException( + "Remote client is not running, make sure you have invoked 'RemoteClient.connect' before using it.", this) + notifyListeners(RemoteClientError(exception, this)) + throw exception + } + } + + private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef = + if (!actorRef.supervisor.isDefined) throw new IllegalActorStateException( + "Can't register supervisor for " + actorRef + " since it is not under supervision") + else supervisors.putIfAbsent(actorRef.supervisor.get.uuid, actorRef) + + private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef = + if (!actorRef.supervisor.isDefined) throw new IllegalActorStateException( + "Can't unregister supervisor for " + actorRef + " since it is not under supervision") + else supervisors.remove(actorRef.supervisor.get.uuid) + + private[akka] def isWithinReconnectionTimeWindow: Boolean = { + if (reconnectionTimeWindowStart == 0L) { + reconnectionTimeWindowStart = System.currentTimeMillis + true + } else { + val timeLeft = reconnectionTimeWindow - (System.currentTimeMillis - reconnectionTimeWindowStart) + if (timeLeft > 0) { + log.slf4j.info("Will try to reconnect to remote server for another [{}] milliseconds", timeLeft) + true + } else false + } + } + + private[akka] def resetReconnectionTimeWindow = reconnectionTimeWindowStart = 0L +} + +/** + * @author Jonas Bonér + */ +class RemoteClientPipelineFactory( + name: String, + futures: ConcurrentMap[Uuid, CompletableFuture[_]], + supervisors: ConcurrentMap[Uuid, ActorRef], + bootstrap: ClientBootstrap, + remoteAddress: SocketAddress, + timer: HashedWheelTimer, + client: RemoteClient) extends ChannelPipelineFactory { + + def getPipeline: ChannelPipeline = { + def join(ch: ChannelHandler*) = Array[ChannelHandler](ch: _*) + + lazy val engine = { + val e = RemoteServerSslContext.client.createSSLEngine() + e.setEnabledCipherSuites(e.getSupportedCipherSuites) //TODO is this sensible? + e.setUseClientMode(true) + e + } + + val ssl = if (RemoteServer.SECURE) join(new SslHandler(engine)) else join() + val timeout = new ReadTimeoutHandler(timer, RemoteClient.READ_TIMEOUT.toMillis.toInt) + val lenDec = new LengthFieldBasedFrameDecoder(RemoteClient.MESSAGE_FRAME_SIZE, 0, 4, 0, 4) + val lenPrep = new LengthFieldPrepender(4) + val protobufDec = new ProtobufDecoder(RemoteMessageProtocol.getDefaultInstance) + val protobufEnc = new ProtobufEncoder + val (enc, dec) = RemoteServer.COMPRESSION_SCHEME match { + case "zlib" => (join(new ZlibEncoder(RemoteServer.ZLIB_COMPRESSION_LEVEL)), join(new ZlibDecoder)) + case _ => (join(), join()) + } + + val remoteClient = new RemoteClientHandler(name, futures, supervisors, bootstrap, remoteAddress, timer, client) + val stages = ssl ++ join(timeout) ++ dec ++ join(lenDec, protobufDec) ++ enc ++ join(lenPrep, protobufEnc, remoteClient) + new StaticChannelPipeline(stages: _*) + } +} + +/** + * @author Jonas Bonér + */ +@ChannelHandler.Sharable +class RemoteClientHandler( + val name: String, + val futures: ConcurrentMap[Uuid, CompletableFuture[_]], + val supervisors: ConcurrentMap[Uuid, ActorRef], + val bootstrap: ClientBootstrap, + val remoteAddress: SocketAddress, + val timer: HashedWheelTimer, + val client: RemoteClient) + extends SimpleChannelUpstreamHandler with Logging { + + override def handleUpstream(ctx: ChannelHandlerContext, event: ChannelEvent) = { + if (event.isInstanceOf[ChannelStateEvent] && + event.asInstanceOf[ChannelStateEvent].getState != ChannelState.INTEREST_OPS) { + log.slf4j.debug(event.toString) + } + super.handleUpstream(ctx, event) + } + + override def messageReceived(ctx: ChannelHandlerContext, event: MessageEvent) { + try { + val result = event.getMessage + if (result.isInstanceOf[RemoteMessageProtocol]) { + val reply = result.asInstanceOf[RemoteMessageProtocol] + val replyUuid = uuidFrom(reply.getUuid.getHigh, reply.getUuid.getLow) + log.debug("Remote client received RemoteMessageProtocol[\n{}]",reply) + val future = futures.get(replyUuid).asInstanceOf[CompletableFuture[Any]] + if (reply.hasMessage) { + if (future eq null) throw new IllegalActorStateException("Future mapped to UUID " + replyUuid + " does not exist") + val message = MessageSerializer.deserialize(reply.getMessage) + future.completeWithResult(message) + } else { + if (reply.hasSupervisorUuid()) { + val supervisorUuid = uuidFrom(reply.getSupervisorUuid.getHigh, reply.getSupervisorUuid.getLow) + if (!supervisors.containsKey(supervisorUuid)) throw new IllegalActorStateException( + "Expected a registered supervisor for UUID [" + supervisorUuid + "] but none was found") + val supervisedActor = supervisors.get(supervisorUuid) + if (!supervisedActor.supervisor.isDefined) throw new IllegalActorStateException( + "Can't handle restart for remote actor " + supervisedActor + " since its supervisor has been removed") + else supervisedActor.supervisor.get ! Exit(supervisedActor, parseException(reply, client.loader)) + } + val exception = parseException(reply, client.loader) + future.completeWithException(exception) + } + futures remove replyUuid + } else { + val exception = new RemoteClientException("Unknown message received in remote client handler: " + result, client) + client.notifyListeners(RemoteClientError(exception, client)) + throw exception + } + } catch { + case e: Exception => + client.notifyListeners(RemoteClientError(e, client)) + log.slf4j.error("Unexpected exception in remote client handler: {}", e) + throw e + } + } + + override def channelClosed(ctx: ChannelHandlerContext, event: ChannelStateEvent) = client.runSwitch ifOn { + if (client.isWithinReconnectionTimeWindow) { + timer.newTimeout(new TimerTask() { + def run(timeout: Timeout) = { + client.openChannels.remove(event.getChannel) + client.isAuthenticated.set(false) + log.slf4j.debug("Remote client reconnecting to [{}]", remoteAddress) + client.connection = bootstrap.connect(remoteAddress) + client.connection.awaitUninterruptibly // Wait until the connection attempt succeeds or fails. + if (!client.connection.isSuccess) { + client.notifyListeners(RemoteClientError(client.connection.getCause, client)) + log.slf4j.error("Reconnection to [{}] has failed", remoteAddress) + log.slf4j.debug("Reconnection failed", client.connection.getCause) + } + } + }, RemoteClient.RECONNECT_DELAY.toMillis, TimeUnit.MILLISECONDS) + } else spawn { client.shutdown } + } + + override def channelConnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { + def connect = { + client.notifyListeners(RemoteClientConnected(client)) + log.slf4j.debug("Remote client connected to [{}]", ctx.getChannel.getRemoteAddress) + client.resetReconnectionTimeWindow + } + + if (RemoteServer.SECURE) { + val sslHandler: SslHandler = ctx.getPipeline.get(classOf[SslHandler]) + sslHandler.handshake.addListener(new ChannelFutureListener { + def operationComplete(future: ChannelFuture): Unit = { + if (future.isSuccess) connect + else throw new RemoteClientException("Could not establish SSL handshake", client) + } + }) + } else connect + } + + override def channelDisconnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { + client.notifyListeners(RemoteClientDisconnected(client)) + log.slf4j.debug("Remote client disconnected from [{}]", ctx.getChannel.getRemoteAddress) + } + + override def exceptionCaught(ctx: ChannelHandlerContext, event: ExceptionEvent) = { + client.notifyListeners(RemoteClientError(event.getCause, client)) + if (event.getCause ne null) + log.slf4j.error("Unexpected exception from downstream in remote client", event.getCause) + else + log.slf4j.error("Unexpected exception from downstream in remote client: {}", event) + + event.getChannel.close + } + + private def parseException(reply: RemoteMessageProtocol, loader: Option[ClassLoader]): Throwable = { + val exception = reply.getException + val classname = exception.getClassname + val exceptionClass = if (loader.isDefined) loader.get.loadClass(classname) + else Class.forName(classname) + exceptionClass + .getConstructor(Array[Class[_]](classOf[String]): _*) + .newInstance(exception.getMessage).asInstanceOf[Throwable] + } +} /** * For internal use only. Holds configuration variables, remote actors, remote typed actors and remote servers. @@ -118,41 +525,62 @@ object RemoteServer { /** * Life-cycle events for RemoteServer. */ -sealed trait RemoteServerLifeCycleEvent +sealed trait RemoteServerLifeCycleEvent //TODO: REVISIT: Document change from RemoteServer to RemoteServerModule case class RemoteServerStarted( - @BeanProperty val server: RemoteServer) extends RemoteServerLifeCycleEvent + @BeanProperty val server: RemoteServerModule) extends RemoteServerLifeCycleEvent case class RemoteServerShutdown( - @BeanProperty val server: RemoteServer) extends RemoteServerLifeCycleEvent + @BeanProperty val server: RemoteServerModule) extends RemoteServerLifeCycleEvent case class RemoteServerError( @BeanProperty val cause: Throwable, - @BeanProperty val server: RemoteServer) extends RemoteServerLifeCycleEvent + @BeanProperty val server: RemoteServerModule) extends RemoteServerLifeCycleEvent case class RemoteServerClientConnected( - @BeanProperty val server: RemoteServer, + @BeanProperty val server: RemoteServerModule, @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent case class RemoteServerClientDisconnected( - @BeanProperty val server: RemoteServer, + @BeanProperty val server: RemoteServerModule, @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent case class RemoteServerClientClosed( - @BeanProperty val server: RemoteServer, + @BeanProperty val server: RemoteServerModule, @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent + /** - * Use this class if you need a more than one remote server on a specific node. - * - *
- * val server = new RemoteServer
- * server.start
- * 
- * - * If you need to create more than one, then you can use the RemoteServer: - * - *
- * RemoteNode.start
- * 
- * - * @author Jonas Bonér + * Provides the implementation of the Netty remote support */ -class RemoteServer extends RemoteServerModule { +class NettyRemoteSupport(val registry: ActorRegistryInstance) + extends RemoteSupport with NettyRemoteServerModule with NettyRemoteClientModule { + + protected[akka] def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): ActorRef = { + //TODO: REVISIT: Possible to optimize server-managed actors in local scope? + //val Host = this.hostname + //val Port = this.port + + //(host,port) match { + // case (Host, Port) if optimizeLocalScoped_? => + //if actor with that servicename or uuid is present locally, return a LocalActorRef to that one + //else return RemoteActorRef(registry, serviceId, className, hostname, port, timeout, false, loader) + // case _ => + // RemoteActorRef(registry, serviceId, className, hostname, port, timeout, false, loader) + //} + RemoteActorRef(registry, serviceId, className, hostname, port, timeout, false, loader) + } + + def clientManagedActorOf(clazz: Class[_ <: Actor], host: String, port: Int, timeout: Long): ActorRef = { + val Host = this.hostname + val Port = this.port + + (host,port) match { + case (Host, Port) if optimizeLocalScoped_? => + registry.actorOf(clazz) //Local + case _ => + new RemoteActorRef(registry,clazz.getName,clazz.getName,host,port,timeout,true /*Client managed*/, None) + } + } + + val optimizeLocalScoped_? = true +} + +trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => import RemoteServer._ @volatile private[akka] var address = Address(RemoteServer.HOSTNAME,RemoteServer.PORT) @@ -172,7 +600,7 @@ class RemoteServer extends RemoteServerModule { def isRunning = _isRunning.isOn - def start(_hostname: String, _port: Int, loader: Option[ClassLoader] = None): RemoteServer = guard withGuard { + def start(_hostname: String, _port: Int, loader: Option[ClassLoader] = None): RemoteServerModule = guard withGuard { try { _isRunning switchOn { address = Address(_hostname,_port) @@ -196,7 +624,7 @@ class RemoteServer extends RemoteServerModule { this } - def shutdown = guard withGuard { + def shutdownServerModule = guard withGuard { _isRunning switchOff { try { openChannels.disconnect @@ -336,22 +764,15 @@ class RemoteServer extends RemoteServerModule { *

* NOTE: You need to call this method if you have registered an actor by a custom ID. */ - def unregisterTypedPerSessionActor(id: String): Unit = { - if (_isRunning.isOn) { - typedActorsFactories.remove(id) - } - } + def unregisterTypedPerSessionActor(id: String): Unit = + if (_isRunning.isOn) typedActorsFactories.remove(id) - protected override def manageLifeCycleOfListeners = false - - protected[akka] override def notifyListeners(message: => Any): Unit = super.notifyListeners(message) - - private[akka] def actors = ActorRegistry.actors(address) - private[akka] def actorsByUuid = ActorRegistry.actorsByUuid(address) - private[akka] def actorsFactories = ActorRegistry.actorsFactories(address) - private[akka] def typedActors = ActorRegistry.typedActors(address) - private[akka] def typedActorsByUuid = ActorRegistry.typedActorsByUuid(address) - private[akka] def typedActorsFactories = ActorRegistry.typedActorsFactories(address) + private[akka] def actors = registry.actors(address) + private[akka] def actorsByUuid = registry.actorsByUuid(address) + private[akka] def actorsFactories = registry.actorsFactories(address) + private[akka] def typedActors = registry.typedActors(address) + private[akka] def typedActorsByUuid = registry.typedActorsByUuid(address) + private[akka] def typedActorsFactories = registry.typedActorsFactories(address) } object RemoteServerSslContext { @@ -376,7 +797,7 @@ class RemoteServerPipelineFactory( val name: String, val openChannels: ChannelGroup, val loader: Option[ClassLoader], - val server: RemoteServer) extends ChannelPipelineFactory { + val server: NettyRemoteServerModule) extends ChannelPipelineFactory { import RemoteServer._ def getPipeline: ChannelPipeline = { @@ -413,7 +834,7 @@ class RemoteServerHandler( val name: String, val openChannels: ChannelGroup, val applicationLoader: Option[ClassLoader], - val server: RemoteServer) extends SimpleChannelUpstreamHandler with Logging { + val server: NettyRemoteServerModule) extends SimpleChannelUpstreamHandler with Logging { import RemoteServer._ val AW_PROXY_PREFIX = "$$ProxiedByAW".intern @@ -422,7 +843,7 @@ class RemoteServerHandler( val sessionActors = new ChannelLocal[ConcurrentHashMap[String, ActorRef]]() val typedSessionActors = new ChannelLocal[ConcurrentHashMap[String, AnyRef]]() - applicationLoader.foreach(MessageSerializer.setClassLoader(_)) + applicationLoader.foreach(MessageSerializer.setClassLoader(_)) //TODO: REVISIT: THIS FEELS A BIT DODGY /** * ChannelOpen overridden to store open channels for a clean postStop of a RemoteServer. diff --git a/akka-remote/src/main/scala/akka/remote/RemoteClient.scala b/akka-remote/src/main/scala/akka/remote/RemoteClient.scala deleted file mode 100644 index 0a27f04cb1..0000000000 --- a/akka-remote/src/main/scala/akka/remote/RemoteClient.scala +++ /dev/null @@ -1,515 +0,0 @@ -/** - * Copyright (C) 2009-2010 Scalable Solutions AB - */ - -package akka.remote - -import akka.remote.protocol.RemoteProtocol.{ActorType => ActorTypeProtocol, _} -import akka.actor.{Exit, Actor, ActorRef, ActorType, RemoteActorRef, IllegalActorStateException} -import akka.dispatch.{DefaultCompletableFuture, CompletableFuture} -import akka.actor.{Uuid,newUuid,uuidFrom} -import akka.config.Config._ -import akka.serialization.RemoteActorSerialization._ -import akka.AkkaException -import Actor._ - -import org.jboss.netty.channel._ -import group.DefaultChannelGroup -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory -import org.jboss.netty.bootstrap.ClientBootstrap -import org.jboss.netty.handler.codec.frame.{ LengthFieldBasedFrameDecoder, LengthFieldPrepender } -import org.jboss.netty.handler.codec.compression.{ ZlibDecoder, ZlibEncoder } -import org.jboss.netty.handler.codec.protobuf.{ ProtobufDecoder, ProtobufEncoder } -import org.jboss.netty.handler.timeout.ReadTimeoutHandler -import org.jboss.netty.util.{ TimerTask, Timeout, HashedWheelTimer } -import org.jboss.netty.handler.ssl.SslHandler - -import java.net.{ SocketAddress, InetSocketAddress } -import java.util.concurrent.{ TimeUnit, Executors, ConcurrentMap, ConcurrentHashMap, ConcurrentSkipListSet } -import java.util.concurrent.atomic.{ AtomicLong, AtomicBoolean } - -import scala.collection.mutable.{ HashSet, HashMap } -import scala.reflect.BeanProperty - -import akka.actor._ -import akka.util._ - - -/** - * Life-cycle events for RemoteClient. - */ -sealed trait RemoteClientLifeCycleEvent -case class RemoteClientError( - @BeanProperty val cause: Throwable, - @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent -case class RemoteClientDisconnected( - @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent -case class RemoteClientConnected( - @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent -case class RemoteClientStarted( - @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent -case class RemoteClientShutdown( - @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent - -/** - * Thrown for example when trying to send a message using a RemoteClient that is either not started or shut down. - */ -class RemoteClientException private[akka] (message: String, @BeanProperty val client: RemoteClient) extends AkkaException(message) - -/** - * The RemoteClient object manages RemoteClient instances and gives you an API to lookup remote actor handles. - * - * @author Jonas Bonér - */ -object RemoteClient extends Logging { - - val SECURE_COOKIE: Option[String] = { - val cookie = config.getString("akka.remote.secure-cookie", "") - if (cookie == "") None - else Some(cookie) - } - - val READ_TIMEOUT = Duration(config.getInt("akka.remote.client.read-timeout", 1), TIME_UNIT) - val RECONNECT_DELAY = Duration(config.getInt("akka.remote.client.reconnect-delay", 5), TIME_UNIT) - val MESSAGE_FRAME_SIZE = config.getInt("akka.remote.client.message-frame-size", 1048576) - - private val remoteClients = new HashMap[String, RemoteClient] - private val remoteActors = new HashMap[Address, HashSet[Uuid]] - - def actorFor(classNameOrServiceId: String, hostname: String, port: Int): ActorRef = - actorFor(classNameOrServiceId, classNameOrServiceId, Actor.TIMEOUT, hostname, port, None) - - def actorFor(classNameOrServiceId: String, hostname: String, port: Int, loader: ClassLoader): ActorRef = - actorFor(classNameOrServiceId, classNameOrServiceId, Actor.TIMEOUT, hostname, port, Some(loader)) - - def actorFor(serviceId: String, className: String, hostname: String, port: Int): ActorRef = - actorFor(serviceId, className, Actor.TIMEOUT, hostname, port, None) - - def actorFor(serviceId: String, className: String, hostname: String, port: Int, loader: ClassLoader): ActorRef = - actorFor(serviceId, className, Actor.TIMEOUT, hostname, port, Some(loader)) - - def actorFor(classNameOrServiceId: String, timeout: Long, hostname: String, port: Int): ActorRef = - actorFor(classNameOrServiceId, classNameOrServiceId, timeout, hostname, port, None) - - def actorFor(classNameOrServiceId: String, timeout: Long, hostname: String, port: Int, loader: ClassLoader): ActorRef = - actorFor(classNameOrServiceId, classNameOrServiceId, timeout, hostname, port, Some(loader)) - - def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int): ActorRef = - RemoteActorRef(serviceId, className, hostname, port, timeout, false, None) - - def typedActorFor[T](intfClass: Class[T], serviceIdOrClassName: String, hostname: String, port: Int): T = { - typedActorFor(intfClass, serviceIdOrClassName, serviceIdOrClassName, Actor.TIMEOUT, hostname, port, None) - } - - def typedActorFor[T](intfClass: Class[T], serviceIdOrClassName: String, timeout: Long, hostname: String, port: Int): T = { - typedActorFor(intfClass, serviceIdOrClassName, serviceIdOrClassName, timeout, hostname, port, None) - } - - def typedActorFor[T](intfClass: Class[T], serviceIdOrClassName: String, timeout: Long, hostname: String, port: Int, loader: ClassLoader): T = { - typedActorFor(intfClass, serviceIdOrClassName, serviceIdOrClassName, timeout, hostname, port, Some(loader)) - } - - def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, hostname: String, port: Int, loader: ClassLoader): T = { - typedActorFor(intfClass, serviceId, implClassName, timeout, hostname, port, Some(loader)) - } - - private[akka] def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): T = { - val actorRef = RemoteActorRef(serviceId, implClassName, hostname, port, timeout, false, loader, ActorType.TypedActor) - TypedActor.createProxyForRemoteActorRef(intfClass, actorRef) - } - - private[akka] def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int, loader: ClassLoader): ActorRef = - RemoteActorRef(serviceId, className, hostname, port, timeout, false, Some(loader)) - - private[akka] def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): ActorRef = - RemoteActorRef(serviceId, className, hostname, port, timeout, false, loader) - - def clientFor(hostname: String, port: Int): RemoteClient = - clientFor(new InetSocketAddress(hostname, port), None) - - def clientFor(hostname: String, port: Int, loader: ClassLoader): RemoteClient = - clientFor(new InetSocketAddress(hostname, port), Some(loader)) - - def clientFor(address: InetSocketAddress): RemoteClient = - clientFor(address, None) - - def clientFor(address: InetSocketAddress, loader: ClassLoader): RemoteClient = - clientFor(address, Some(loader)) - - private[akka] def clientFor(hostname: String, port: Int, loader: Option[ClassLoader]): RemoteClient = - clientFor(new InetSocketAddress(hostname, port), loader) - - private[akka] def clientFor( - address: InetSocketAddress, loader: Option[ClassLoader]): RemoteClient = synchronized { - val hostname = address.getHostName - val port = address.getPort - val hash = hostname + ':' + port - loader.foreach(MessageSerializer.setClassLoader(_)) - if (remoteClients.contains(hash)) remoteClients(hash) - else { - val client = new RemoteClient(hostname, port, loader) - client.connect - remoteClients += hash -> client - client - } - } - - def shutdownClientFor(address: InetSocketAddress) = synchronized { - val hostname = address.getHostName - val port = address.getPort - val hash = hostname + ':' + port - if (remoteClients.contains(hash)) { - val client = remoteClients(hash) - client.shutdown - remoteClients -= hash - } - } - - /** - * Clean-up all open connections. - */ - def shutdownAll = synchronized { - remoteClients.foreach({ case (addr, client) => client.shutdown }) - remoteClients.clear - } - - def register(hostname: String, port: Int, uuid: Uuid) = synchronized { - actorsFor(Address(hostname, port)) += uuid - } - - private[akka] def unregister(hostname: String, port: Int, uuid: Uuid) = synchronized { - val set = actorsFor(Address(hostname, port)) - set -= uuid - if (set.isEmpty) shutdownClientFor(new InetSocketAddress(hostname, port)) - } - - private[akka] def actorsFor(remoteServerAddress: Address): HashSet[Uuid] = { - val set = remoteActors.get(remoteServerAddress) - if (set.isDefined && (set.get ne null)) set.get - else { - val remoteActorSet = new HashSet[Uuid] - remoteActors.put(remoteServerAddress, remoteActorSet) - remoteActorSet - } - } -} - -/** - * RemoteClient represents a connection to a RemoteServer. Is used to send messages to remote actors on the RemoteServer. - * - * @author Jonas Bonér - */ -class RemoteClient private[akka] ( - val hostname: String, val port: Int, val loader: Option[ClassLoader] = None) - extends Logging with ListenerManagement { - val name = "RemoteClient@" + hostname + "::" + port - - //FIXME Should these be clear:ed on postStop? - private val futures = new ConcurrentHashMap[Uuid, CompletableFuture[_]] - private val supervisors = new ConcurrentHashMap[Uuid, ActorRef] - - private val remoteAddress = new InetSocketAddress(hostname, port) - - //FIXME rewrite to a wrapper object (minimize volatile access and maximize encapsulation) - @volatile - private var bootstrap: ClientBootstrap = _ - @volatile - private[remote] var connection: ChannelFuture = _ - @volatile - private[remote] var openChannels: DefaultChannelGroup = _ - @volatile - private var timer: HashedWheelTimer = _ - private[remote] val runSwitch = new Switch() - private[remote] val isAuthenticated = new AtomicBoolean(false) - - private[remote] def isRunning = runSwitch.isOn - - private val reconnectionTimeWindow = Duration(config.getInt( - "akka.remote.client.reconnection-time-window", 600), TIME_UNIT).toMillis - @volatile - private var reconnectionTimeWindowStart = 0L - - def connect = runSwitch switchOn { - openChannels = new DefaultChannelGroup(classOf[RemoteClient].getName) - timer = new HashedWheelTimer - - bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool)) - bootstrap.setPipelineFactory(new RemoteClientPipelineFactory(name, futures, supervisors, bootstrap, remoteAddress, timer, this)) - bootstrap.setOption("tcpNoDelay", true) - bootstrap.setOption("keepAlive", true) - - log.slf4j.info("Starting remote client connection to [{}:{}]", hostname, port) - - // Wait until the connection attempt succeeds or fails. - connection = bootstrap.connect(remoteAddress) - val channel = connection.awaitUninterruptibly.getChannel - openChannels.add(channel) - - if (!connection.isSuccess) { - notifyListeners(RemoteClientError(connection.getCause, this)) - log.slf4j.error("Remote client connection to [{}:{}] has failed", hostname, port) - log.slf4j.debug("Remote client connection failed", connection.getCause) - } - notifyListeners(RemoteClientStarted(this)) - } - - def shutdown = runSwitch switchOff { - log.slf4j.info("Shutting down {}", name) - notifyListeners(RemoteClientShutdown(this)) - timer.stop - timer = null - openChannels.close.awaitUninterruptibly - openChannels = null - bootstrap.releaseExternalResources - bootstrap = null - connection = null - log.slf4j.info("{} has been shut down", name) - } - - @deprecated("Use addListener instead") - def registerListener(actorRef: ActorRef) = addListener(actorRef) - - @deprecated("Use removeListener instead") - def deregisterListener(actorRef: ActorRef) = removeListener(actorRef) - - override def notifyListeners(message: => Any): Unit = super.notifyListeners(message) - - protected override def manageLifeCycleOfListeners = false - - def send[T]( - message: Any, - senderOption: Option[ActorRef], - senderFuture: Option[CompletableFuture[T]], - remoteAddress: InetSocketAddress, - timeout: Long, - isOneWay: Boolean, - actorRef: ActorRef, - typedActorInfo: Option[Tuple2[String, String]], - actorType: ActorType): Option[CompletableFuture[T]] = { - val cookie = if (isAuthenticated.compareAndSet(false, true)) RemoteClient.SECURE_COOKIE - else None - send(createRemoteMessageProtocolBuilder( - Some(actorRef), - Left(actorRef.uuid), - actorRef.id, - actorRef.actorClassName, - actorRef.timeout, - Left(message), - isOneWay, - senderOption, - typedActorInfo, - actorType, - cookie - ).build, senderFuture) - } - - def send[T]( - request: RemoteMessageProtocol, - senderFuture: Option[CompletableFuture[T]]): Option[CompletableFuture[T]] = { - if (isRunning) { - if (request.getOneWay) { - connection.getChannel.write(request) - None - } else { - val futureResult = if (senderFuture.isDefined) senderFuture.get - else new DefaultCompletableFuture[T](request.getActorInfo.getTimeout) - futures.put(uuidFrom(request.getUuid.getHigh, request.getUuid.getLow), futureResult) - connection.getChannel.write(request) - Some(futureResult) - } - } else { - val exception = new RemoteClientException( - "Remote client is not running, make sure you have invoked 'RemoteClient.connect' before using it.", this) - notifyListeners(RemoteClientError(exception, this)) - throw exception - } - } - - private[akka] def registerSupervisorForActor(actorRef: ActorRef) = - if (!actorRef.supervisor.isDefined) throw new IllegalActorStateException( - "Can't register supervisor for " + actorRef + " since it is not under supervision") - else supervisors.putIfAbsent(actorRef.supervisor.get.uuid, actorRef) - - private[akka] def deregisterSupervisorForActor(actorRef: ActorRef) = - if (!actorRef.supervisor.isDefined) throw new IllegalActorStateException( - "Can't unregister supervisor for " + actorRef + " since it is not under supervision") - else supervisors.remove(actorRef.supervisor.get.uuid) - - private[akka] def isWithinReconnectionTimeWindow: Boolean = { - if (reconnectionTimeWindowStart == 0L) { - reconnectionTimeWindowStart = System.currentTimeMillis - true - } else { - val timeLeft = reconnectionTimeWindow - (System.currentTimeMillis - reconnectionTimeWindowStart) - if (timeLeft > 0) { - log.slf4j.info("Will try to reconnect to remote server for another [{}] milliseconds", timeLeft) - true - } else false - } - } - - private[akka] def resetReconnectionTimeWindow = reconnectionTimeWindowStart = 0L -} - -/** - * @author Jonas Bonér - */ -class RemoteClientPipelineFactory( - name: String, - futures: ConcurrentMap[Uuid, CompletableFuture[_]], - supervisors: ConcurrentMap[Uuid, ActorRef], - bootstrap: ClientBootstrap, - remoteAddress: SocketAddress, - timer: HashedWheelTimer, - client: RemoteClient) extends ChannelPipelineFactory { - - def getPipeline: ChannelPipeline = { - def join(ch: ChannelHandler*) = Array[ChannelHandler](ch: _*) - - lazy val engine = { - val e = RemoteServerSslContext.client.createSSLEngine() - e.setEnabledCipherSuites(e.getSupportedCipherSuites) //TODO is this sensible? - e.setUseClientMode(true) - e - } - - val ssl = if (RemoteServer.SECURE) join(new SslHandler(engine)) else join() - val timeout = new ReadTimeoutHandler(timer, RemoteClient.READ_TIMEOUT.toMillis.toInt) - val lenDec = new LengthFieldBasedFrameDecoder(RemoteClient.MESSAGE_FRAME_SIZE, 0, 4, 0, 4) - val lenPrep = new LengthFieldPrepender(4) - val protobufDec = new ProtobufDecoder(RemoteMessageProtocol.getDefaultInstance) - val protobufEnc = new ProtobufEncoder - val (enc, dec) = RemoteServer.COMPRESSION_SCHEME match { - case "zlib" => (join(new ZlibEncoder(RemoteServer.ZLIB_COMPRESSION_LEVEL)), join(new ZlibDecoder)) - case _ => (join(), join()) - } - - val remoteClient = new RemoteClientHandler(name, futures, supervisors, bootstrap, remoteAddress, timer, client) - val stages = ssl ++ join(timeout) ++ dec ++ join(lenDec, protobufDec) ++ enc ++ join(lenPrep, protobufEnc, remoteClient) - new StaticChannelPipeline(stages: _*) - } -} - -/** - * @author Jonas Bonér - */ -@ChannelHandler.Sharable -class RemoteClientHandler( - val name: String, - val futures: ConcurrentMap[Uuid, CompletableFuture[_]], - val supervisors: ConcurrentMap[Uuid, ActorRef], - val bootstrap: ClientBootstrap, - val remoteAddress: SocketAddress, - val timer: HashedWheelTimer, - val client: RemoteClient) - extends SimpleChannelUpstreamHandler with Logging { - - override def handleUpstream(ctx: ChannelHandlerContext, event: ChannelEvent) = { - if (event.isInstanceOf[ChannelStateEvent] && - event.asInstanceOf[ChannelStateEvent].getState != ChannelState.INTEREST_OPS) { - log.slf4j.debug(event.toString) - } - super.handleUpstream(ctx, event) - } - - override def messageReceived(ctx: ChannelHandlerContext, event: MessageEvent) { - try { - val result = event.getMessage - if (result.isInstanceOf[RemoteMessageProtocol]) { - val reply = result.asInstanceOf[RemoteMessageProtocol] - val replyUuid = uuidFrom(reply.getUuid.getHigh, reply.getUuid.getLow) - log.debug("Remote client received RemoteMessageProtocol[\n{}]",reply) - val future = futures.get(replyUuid).asInstanceOf[CompletableFuture[Any]] - if (reply.hasMessage) { - if (future eq null) throw new IllegalActorStateException("Future mapped to UUID " + replyUuid + " does not exist") - val message = MessageSerializer.deserialize(reply.getMessage) - future.completeWithResult(message) - } else { - if (reply.hasSupervisorUuid()) { - val supervisorUuid = uuidFrom(reply.getSupervisorUuid.getHigh, reply.getSupervisorUuid.getLow) - if (!supervisors.containsKey(supervisorUuid)) throw new IllegalActorStateException( - "Expected a registered supervisor for UUID [" + supervisorUuid + "] but none was found") - val supervisedActor = supervisors.get(supervisorUuid) - if (!supervisedActor.supervisor.isDefined) throw new IllegalActorStateException( - "Can't handle restart for remote actor " + supervisedActor + " since its supervisor has been removed") - else supervisedActor.supervisor.get ! Exit(supervisedActor, parseException(reply, client.loader)) - } - val exception = parseException(reply, client.loader) - future.completeWithException(exception) - } - futures remove replyUuid - } else { - val exception = new RemoteClientException("Unknown message received in remote client handler: " + result, client) - client.notifyListeners(RemoteClientError(exception, client)) - throw exception - } - } catch { - case e: Exception => - client.notifyListeners(RemoteClientError(e, client)) - log.slf4j.error("Unexpected exception in remote client handler: {}", e) - throw e - } - } - - override def channelClosed(ctx: ChannelHandlerContext, event: ChannelStateEvent) = client.runSwitch ifOn { - if (client.isWithinReconnectionTimeWindow) { - timer.newTimeout(new TimerTask() { - def run(timeout: Timeout) = { - client.openChannels.remove(event.getChannel) - client.isAuthenticated.set(false) - log.slf4j.debug("Remote client reconnecting to [{}]", remoteAddress) - client.connection = bootstrap.connect(remoteAddress) - client.connection.awaitUninterruptibly // Wait until the connection attempt succeeds or fails. - if (!client.connection.isSuccess) { - client.notifyListeners(RemoteClientError(client.connection.getCause, client)) - log.slf4j.error("Reconnection to [{}] has failed", remoteAddress) - log.slf4j.debug("Reconnection failed", client.connection.getCause) - } - } - }, RemoteClient.RECONNECT_DELAY.toMillis, TimeUnit.MILLISECONDS) - } else spawn { client.shutdown } - } - - override def channelConnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { - def connect = { - client.notifyListeners(RemoteClientConnected(client)) - log.slf4j.debug("Remote client connected to [{}]", ctx.getChannel.getRemoteAddress) - client.resetReconnectionTimeWindow - } - - if (RemoteServer.SECURE) { - val sslHandler: SslHandler = ctx.getPipeline.get(classOf[SslHandler]) - sslHandler.handshake.addListener(new ChannelFutureListener { - def operationComplete(future: ChannelFuture): Unit = { - if (future.isSuccess) connect - else throw new RemoteClientException("Could not establish SSL handshake", client) - } - }) - } else connect - } - - override def channelDisconnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { - client.notifyListeners(RemoteClientDisconnected(client)) - log.slf4j.debug("Remote client disconnected from [{}]", ctx.getChannel.getRemoteAddress) - } - - override def exceptionCaught(ctx: ChannelHandlerContext, event: ExceptionEvent) = { - client.notifyListeners(RemoteClientError(event.getCause, client)) - if (event.getCause ne null) - log.slf4j.error("Unexpected exception from downstream in remote client", event.getCause) - else - log.slf4j.error("Unexpected exception from downstream in remote client: {}", event) - - event.getChannel.close - } - - private def parseException(reply: RemoteMessageProtocol, loader: Option[ClassLoader]): Throwable = { - val exception = reply.getException - val classname = exception.getClassname - val exceptionClass = if (loader.isDefined) loader.get.loadClass(classname) - else Class.forName(classname) - exceptionClass - .getConstructor(Array[Class[_]](classOf[String]): _*) - .newInstance(exception.getMessage).asInstanceOf[Throwable] - } -} diff --git a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala index 258d210490..10eed2c362 100644 --- a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -17,7 +17,7 @@ import scala.collection.immutable.Stack import com.google.protobuf.ByteString import akka.util.ReflectiveAccess -import akka.util.ReflectiveAccess.RemoteServerModule.{HOSTNAME,PORT} +import akka.util.ReflectiveAccess.Remote.{HOSTNAME,PORT} /** * Type class definition for Actor Serialization @@ -191,6 +191,7 @@ object ActorSerialization { } val ar = new LocalActorRef( + ActorRegistry,//TODO: REVISIST: Change to an implicit ActorRegistryInstance? uuidFrom(protocol.getUuid.getHigh, protocol.getUuid.getLow), protocol.getId, protocol.getOriginalAddress.getHostname, @@ -230,6 +231,7 @@ object RemoteActorSerialization { private[akka] def fromProtobufToRemoteActorRef(protocol: RemoteActorRefProtocol, loader: Option[ClassLoader]): ActorRef = { Actor.log.slf4j.debug("Deserializing RemoteActorRefProtocol to RemoteActorRef:\n {}", protocol) RemoteActorRef( + ActorRegistry,//TODO: REVISIST: Change to an implicit ActorRegistryInstance? protocol.getClassOrServiceName, protocol.getActorClassname, protocol.getHomeAddress.getHostname, @@ -245,8 +247,10 @@ object RemoteActorSerialization { def toRemoteActorRefProtocol(ar: ActorRef): RemoteActorRefProtocol = { import ar._ - Actor.log.slf4j.debug("Register serialized Actor [{}] as remote @ [{}:{}]",Array[AnyRef](actorClassName, HOSTNAME, PORT.asInstanceOf[AnyRef])) - ActorRegistry.remote.registerByUuid(ar) + Actor.log.slf4j.debug("Register serialized Actor [{}] as remote @ [{}:{}]", + Array[AnyRef](actorClassName, registry.remote.hostname, registry.remote.port.asInstanceOf[AnyRef])) + + registry.remote.registerByUuid(ar) RemoteActorRefProtocol.newBuilder .setClassOrServiceName(uuid.toString) diff --git a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala index a5e4159366..d5ee009142 100644 --- a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala @@ -4,10 +4,11 @@ import java.util.concurrent.{CountDownLatch, TimeUnit} import org.scalatest.junit.JUnitSuite import org.junit.{Test, Before, After} -import akka.remote.{RemoteServer, RemoteClient} import akka.dispatch.Dispatchers -import akka.actor.{ActorRef, Actor} -import Actor._ +import akka.remote. {NettyRemoteSupport, RemoteServer, RemoteClient} +import akka.actor. {RemoteActorRef, ActorRegistryInstance, ActorRef, Actor} + +class ExpectedRemoteProblem extends RuntimeException object ClientInitiatedRemoteActorSpec { case class Send(actor: Actor) @@ -28,8 +29,7 @@ object ClientInitiatedRemoteActorSpec { def receive = { case "Hello" => self.reply("World") - case "Failure" => - throw new RuntimeException("Expected exception; to test fault-tolerance") + case "Failure" => throw new ExpectedRemoteProblem } } @@ -40,6 +40,12 @@ object ClientInitiatedRemoteActorSpec { } } + class CountDownActor(latch: CountDownLatch) extends Actor { + def receive = { + case "World" => latch.countDown + } + } + object SendOneWayAndReplySenderActor { val latch = new CountDownLatch(1) } @@ -74,59 +80,54 @@ class ClientInitiatedRemoteActorSpec extends JUnitSuite { val HOSTNAME = "localhost" val PORT1 = 9990 val PORT2 = 9991 - var s1: RemoteServer = null + var s1,s2: ActorRegistryInstance = null private val unit = TimeUnit.MILLISECONDS @Before def init() { - s1 = new RemoteServer() - s1.start(HOSTNAME, PORT1) - Thread.sleep(1000) + s1 = new ActorRegistryInstance(Some(new NettyRemoteSupport(_))) + s2 = new ActorRegistryInstance(Some(new NettyRemoteSupport(_))) + s1.remote.start(HOSTNAME, PORT1) + s2.remote.start(HOSTNAME, PORT2) + Thread.sleep(2000) } @After def finished() { - s1.shutdown - val s2 = RemoteServer.serverFor(HOSTNAME, PORT2) - if (s2.isDefined) s2.get.shutdown - RemoteClient.shutdownAll + s1.remote.shutdown + s2.remote.shutdown + s1.shutdownAll + s2.shutdownAll Thread.sleep(1000) } @Test def shouldSendOneWay = { - val actor = actorOf[RemoteActorSpecActorUnidirectional] - actor.makeRemote(HOSTNAME, PORT1) - actor.start - actor ! "OneWay" + val clientManaged = s1.actorOf[RemoteActorSpecActorUnidirectional](HOSTNAME,PORT2).start + //implicit val self = Some(s2.actorOf[RemoteActorSpecActorUnidirectional].start) + assert(clientManaged ne null) + assert(clientManaged.getClass.equals(classOf[RemoteActorRef])) + clientManaged ! "OneWay" assert(RemoteActorSpecActorUnidirectional.latch.await(1, TimeUnit.SECONDS)) - actor.stop + clientManaged.stop } @Test def shouldSendOneWayAndReceiveReply = { - val actor = actorOf[SendOneWayAndReplyReceiverActor] - actor.makeRemote(HOSTNAME, PORT1) - actor.start - val sender = actorOf[SendOneWayAndReplySenderActor] - sender.homeAddress = (HOSTNAME, PORT2) - sender.actor.asInstanceOf[SendOneWayAndReplySenderActor].sendTo = actor - sender.start - sender.actor.asInstanceOf[SendOneWayAndReplySenderActor].sendOff - assert(SendOneWayAndReplySenderActor.latch.await(3, TimeUnit.SECONDS)) - assert(sender.actor.asInstanceOf[SendOneWayAndReplySenderActor].state.isDefined === true) - assert("World" === sender.actor.asInstanceOf[SendOneWayAndReplySenderActor].state.get.asInstanceOf[String]) - actor.stop - sender.stop + val latch = new CountDownLatch(1) + val actor = s2.actorOf[SendOneWayAndReplyReceiverActor](HOSTNAME, PORT1).start + implicit val sender = Some(s1.actorOf(new CountDownActor(latch)).start) + + actor ! "OneWay" + + assert(latch.await(3,TimeUnit.SECONDS)) } @Test def shouldSendBangBangMessageAndReceiveReply = { - val actor = actorOf[RemoteActorSpecActorBidirectional] - actor.makeRemote(HOSTNAME, PORT1) - actor.start + val actor = s2.actorOf[RemoteActorSpecActorBidirectional](HOSTNAME, PORT1).start val result = actor !! "Hello" assert("World" === result.get.asInstanceOf[String]) actor.stop @@ -134,29 +135,20 @@ class ClientInitiatedRemoteActorSpec extends JUnitSuite { @Test def shouldSendBangBangMessageAndReceiveReplyConcurrently = { - val actors = (1 to 10). - map(num => { - val a = actorOf[RemoteActorSpecActorBidirectional] - a.makeRemote(HOSTNAME, PORT1) - a.start - }).toList + val actors = (1 to 10).map(num => { s2.actorOf[RemoteActorSpecActorBidirectional](HOSTNAME, PORT1).start }).toList actors.map(_ !!! "Hello").foreach(future => assert("World" === future.await.result.asInstanceOf[Option[String]].get)) actors.foreach(_.stop) } @Test def shouldRegisterActorByUuid { - val actor1 = actorOf[MyActorCustomConstructor] - actor1.makeRemote(HOSTNAME, PORT1) - actor1.start + val actor1 = s2.actorOf[MyActorCustomConstructor](HOSTNAME, PORT1).start actor1 ! "incrPrefix" assert((actor1 !! "test").get === "1-test") actor1 ! "incrPrefix" assert((actor1 !! "test").get === "2-test") - val actor2 = actorOf[MyActorCustomConstructor] - actor2.makeRemote(HOSTNAME, PORT1) - actor2.start + val actor2 = s2.actorOf[MyActorCustomConstructor](HOSTNAME, PORT1).start assert((actor2 !! "test").get === "default-test") @@ -164,19 +156,11 @@ class ClientInitiatedRemoteActorSpec extends JUnitSuite { actor2.stop } - @Test + @Test(expected=classOf[ExpectedRemoteProblem]) def shouldSendAndReceiveRemoteException { implicit val timeout = 500000000L - val actor = actorOf[RemoteActorSpecActorBidirectional] - actor.makeRemote(HOSTNAME, PORT1) - actor.start - try { - actor !! "Failure" - fail("Should have thrown an exception") - } catch { - case e => - assert("Expected exception; to test fault-tolerance" === e.getMessage()) - } + val actor = s2.actorOf[RemoteActorSpecActorBidirectional](HOSTNAME, PORT1).start + actor !! "Failure" actor.stop } } diff --git a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala index b340e89f45..017207cce7 100644 --- a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala +++ b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala @@ -76,9 +76,6 @@ object RemoteSupervisorSpec { var server: RemoteServer = null } -/** - * @author Jonas Bonér - */ class RemoteSupervisorSpec extends JUnitSuite { import RemoteSupervisorSpec._ diff --git a/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala b/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala index b81bc485da..7dad199ca5 100644 --- a/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala +++ b/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala @@ -4,10 +4,9 @@ package sample.remote -import akka.actor.Actor import akka.actor.Actor._ -import akka.remote.{RemoteClient, RemoteNode} import akka.util.Logging +import akka.actor. {ActorRegistry, Actor} class HelloWorldActor extends Actor { def receive = { @@ -20,9 +19,9 @@ class HelloWorldActor extends Actor { object ServerManagedRemoteActorServer extends Logging { def run = { - RemoteNode.start("localhost", 2552) + ActorRegistry.remote.start("localhost", 2552) log.slf4j.info("Remote node started") - RemoteNode.register("hello-service", actorOf[HelloWorldActor]) + ActorRegistry.remote.register("hello-service", actorOf[HelloWorldActor]) log.slf4j.info("Remote actor registered and started") } @@ -32,7 +31,7 @@ object ServerManagedRemoteActorServer extends Logging { object ServerManagedRemoteActorClient extends Logging { def run = { - val actor = RemoteClient.actorFor("hello-service", "localhost", 2552) + val actor = ActorRegistry.remote.actorFor("hello-service", "localhost", 2552) log.slf4j.info("Remote client created") log.slf4j.info("Sending 'Hello' to remote actor") val result = actor !! "Hello" diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala index dbdf27b352..1672cdc3a8 100644 --- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala @@ -841,8 +841,8 @@ private[akka] abstract class ActorAspect { val isOneWay = TypedActor.isOneWay(methodRtti) val (message: Array[AnyRef], isEscaped) = escapeArguments(methodRtti.getParameterValues) - - val future = RemoteClientModule.send[AnyRef]( + //TODO: REVISIT: MAKE REGISTRY COME FROM ACTORREF + val future = ActorRegistry.remote.send[AnyRef]( message, None, None, remoteAddress.get, timeout, isOneWay, actorRef, Some((interfaceClass.getName, methodRtti.getMethod.getName)), From 65a6f3bde2e4810f981085fbd021daf61f924acd Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Thu, 16 Dec 2010 14:47:26 +1300 Subject: [PATCH 05/38] Update group id in sbt plugin --- akka-sbt-plugin/src/main/scala/AkkaProject.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-sbt-plugin/src/main/scala/AkkaProject.scala b/akka-sbt-plugin/src/main/scala/AkkaProject.scala index f9ae176721..e900724d9c 100644 --- a/akka-sbt-plugin/src/main/scala/AkkaProject.scala +++ b/akka-sbt-plugin/src/main/scala/AkkaProject.scala @@ -21,7 +21,7 @@ trait AkkaBaseProject extends BasicScalaProject { // is resolved from a ModuleConfiguration. This will result in a significant acceleration of the update action. // for development version resolve to .ivy2/local - // val akkaModuleConfig = ModuleConfiguration("akka", AkkaRepo) + // val akkaModuleConfig = ModuleConfiguration("se.scalablesolutions.akka", AkkaRepo) val aspectwerkzModuleConfig = ModuleConfiguration("org.codehaus.aspectwerkz", AkkaRepo) val cassandraModuleConfig = ModuleConfiguration("org.apache.cassandra", AkkaRepo) From 8becbad787693e92e53d79335f315ebabc4ea83c Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 17 Dec 2010 16:09:21 +0100 Subject: [PATCH 06/38] Fixing a lot of stuff and starting to port unit tests --- .../src/main/scala/akka/actor/ActorRef.scala | 38 +-- .../main/scala/akka/actor/ActorRegistry.scala | 47 +-- .../remoteinterface/RemoteInterface.scala | 25 +- .../scala/akka/util/ReflectiveAccess.scala | 8 +- .../akka/remote/NettyRemoteSupport.scala | 104 +++--- .../serialization/SerializationProtocol.scala | 34 +- .../ClientInitiatedRemoteActorSpec.scala | 306 +++++++++--------- .../ServerInitiatedRemoteActorSpec.scala | 300 ++++++++--------- 8 files changed, 414 insertions(+), 448 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index c97edba37e..918edfdd00 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -80,8 +80,6 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal protected[akka] var _futureTimeout: Option[ScheduledFuture[AnyRef]] = None protected[akka] val guard = new ReentrantGuard - private[akka] def registry: ActorRegistryInstance - /** * User overridable callback/setting. *

@@ -543,7 +541,6 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal * @author Jonas Bonér */ class LocalActorRef private[akka] ( - private[akka] val registry: ActorRegistryInstance, private[this] val actorFactory: () => Actor) extends ActorRef with ScalaActorRef { @@ -567,7 +564,6 @@ class LocalActorRef private[akka] ( // used only for deserialization private[akka] def this( - __registry: ActorRegistryInstance, __uuid: Uuid, __id: String, __hostname: String, @@ -578,7 +574,7 @@ class LocalActorRef private[akka] ( __supervisor: Option[ActorRef], __hotswap: Stack[PartialFunction[Any, Unit]], __factory: () => Actor) = { - this(__registry, __factory) + this(__factory) _uuid = __uuid id = __id timeout = __timeout @@ -588,7 +584,7 @@ class LocalActorRef private[akka] ( hotswap = __hotswap setActorSelfFields(actor,this) start - __registry.register(this) + ActorRegistry.register(this) //TODO: REVISIT: Is this needed? } // ========= PUBLIC FUNCTIONS ========= @@ -649,7 +645,7 @@ class LocalActorRef private[akka] ( dispatcher.detach(this) _status = ActorRefInternals.SHUTDOWN actor.postStop - registry.unregister(this) + ActorRegistry.unregister(this) setActorSelfFields(actorInstance.get,null) } //else if (isBeingRestarted) throw new ActorKilledException("Actor [" + toString + "] is being restarted.") } @@ -1058,7 +1054,7 @@ class LocalActorRef private[akka] ( private def initializeActorInstance = { actor.preStart // run actor preStart Actor.log.slf4j.trace("[{}] has started", toString) - registry.register(this) + ActorRegistry.register(this) } } @@ -1078,13 +1074,11 @@ object RemoteActorSystemMessage { * @author Jonas Bonér */ private[akka] case class RemoteActorRef private[akka] ( - registry: ActorRegistryInstance, - classOrServiceName: String, + classOrServiceName: Option[String], val actorClassName: String, val hostname: String, val port: Int, _timeout: Long, - clientManaged: Boolean, //TODO: REVISIT: ENCODE CLIENT_MANAGED INTO REMOTE PROTOCOL loader: Option[ClassLoader], val actorType: ActorType = ActorType.ScalaActor) extends ActorRef with ScalaActorRef { @@ -1093,40 +1087,40 @@ private[akka] case class RemoteActorRef private[akka] ( val homeAddress = new InetSocketAddress(hostname, port) - id = classOrServiceName + protected def clientManaged = classOrServiceName.isEmpty //If no class or service name, it's client managed + + id = classOrServiceName.getOrElse("uuid:" + uuid) //If we're a server-managed we want to have classOrServiceName as id, or else, we're a client-managed and we want to have our uuid as id + timeout = _timeout start def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = - registry.remote.send[Any](message, senderOption, None, homeAddress, timeout, true, this, None, actorType) + ActorRegistry.remote.send[Any](message, senderOption, None, homeAddress, timeout, true, this, None, actorType) def postMessageToMailboxAndCreateFutureResultWithTimeout[T]( message: Any, timeout: Long, senderOption: Option[ActorRef], senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { - val future = registry.remote.send[T](message, senderOption, senderFuture, homeAddress, timeout, false, this, None, actorType) + val future = ActorRegistry.remote.send[T](message, senderOption, senderFuture, homeAddress, timeout, false, this, None, actorType) if (future.isDefined) future.get else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) } def start: ActorRef = synchronized { _status = ActorRefInternals.RUNNING - if (clientManaged) { - registry.remote.registerClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) - } + if (clientManaged) + ActorRegistry.remote.registerClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) this } def stop: Unit = synchronized { if (_status == ActorRefInternals.RUNNING) { _status = ActorRefInternals.SHUTDOWN - postMessageToMailbox(RemoteActorSystemMessage.Stop, None) - if (clientManaged) { - registry.remote.unregisterClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) - registry.remote.unregister(this) //TODO: REVISIT: Why does this need to be deregistered from the server? - } + postMessageToMailbox(RemoteActorSystemMessage.Stop, None) //TODO: REVISIT: Should this be called for both server-managed and client-managed? + if (clientManaged) + ActorRegistry.remote.unregisterClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala index f966928965..620aea9ecc 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala @@ -38,12 +38,13 @@ case class ActorUnregistered(actor: ActorRef) extends ActorRegistryEvent * * @author Jonas Bonér */ -object ActorRegistry extends ActorRegistryInstance(ReflectiveAccess.Remote.defaultRemoteSupport) -class ActorRegistryInstance(remoteBootstrap: Option[(ActorRegistryInstance) => RemoteSupport]) extends ListenerManagement { +object ActorRegistry extends ListenerManagement { + + protected def remoteBootstrap = ReflectiveAccess.Remote.defaultRemoteSupport + private val actorsByUUID = new ConcurrentHashMap[Uuid, ActorRef] private val actorsById = new Index[String,ActorRef] - private val remoteActorSets = Map[Address, RemoteActorSet]() private val guard = new ReadWriteGuard /** @@ -230,7 +231,7 @@ class ActorRegistryInstance(remoteBootstrap: Option[(ActorRegistryInstance) => R /** * Handy access to the RemoteServer module */ - lazy val remote: RemoteSupport = remoteBootstrap.map(_(this)).getOrElse(throw new UnsupportedOperationException("You need to have akka-remote on classpath")) + lazy val remote: RemoteSupport = remoteBootstrap.map(_()).getOrElse(throw new UnsupportedOperationException("You need to have akka-remote on classpath")) /** * Creates an ActorRef out of the Actor with type T. @@ -280,7 +281,7 @@ class ActorRegistryInstance(remoteBootstrap: Option[(ActorRegistryInstance) => R * val actor = actorOf(classOf[MyActor]).start * */ - def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(this, () => { + def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(() => { import ReflectiveAccess.{ createInstance, noParams, noArgs } createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( throw new ActorInitializationException( @@ -326,7 +327,7 @@ class ActorRegistryInstance(remoteBootstrap: Option[(ActorRegistryInstance) => R * val actor = actorOf(new MyActor).start * */ - def actorOf(factory: => Actor): ActorRef = new LocalActorRef(this,() => factory) + def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory) /** * Use to spawn out a block of code in an event-driven actor. Will shut actor down when @@ -394,41 +395,13 @@ class ActorRegistryInstance(remoteBootstrap: Option[(ActorRegistryInstance) => R else actorRef.stop } } else foreach(_.stop) + if (Remote.isEnabled) { + remote.clear + } actorsByUUID.clear actorsById.clear log.slf4j.info("All actors have been shut down and unregistered from ActorRegistry") } - - /** - * Get the remote actors for the given server address. For internal use only. - */ - private[akka] def actorsFor(remoteServerAddress: Address): RemoteActorSet = guard.withWriteGuard { - remoteActorSets.getOrElseUpdate(remoteServerAddress, new RemoteActorSet) - } - - private[akka] def registerActorByUuid(address: InetSocketAddress, uuid: String, actor: ActorRef) { - actorsByUuid(Address(address.getHostName, address.getPort)).putIfAbsent(uuid, actor) - } - - private[akka] def registerTypedActorByUuid(address: InetSocketAddress, uuid: String, typedActor: AnyRef) { - typedActorsByUuid(Address(address.getHostName, address.getPort)).putIfAbsent(uuid, typedActor) - } - - private[akka] def actors(address: Address) = actorsFor(address).actors - private[akka] def actorsByUuid(address: Address) = actorsFor(address).actorsByUuid - private[akka] def actorsFactories(address: Address) = actorsFor(address).actorsFactories - private[akka] def typedActors(address: Address) = actorsFor(address).typedActors - private[akka] def typedActorsByUuid(address: Address) = actorsFor(address).typedActorsByUuid - private[akka] def typedActorsFactories(address: Address) = actorsFor(address).typedActorsFactories - - private[akka] class RemoteActorSet { - private[ActorRegistryInstance] val actors = new ConcurrentHashMap[String, ActorRef] - private[ActorRegistryInstance] val actorsByUuid = new ConcurrentHashMap[String, ActorRef] - private[ActorRegistryInstance] val actorsFactories = new ConcurrentHashMap[String, () => ActorRef] - private[ActorRegistryInstance] val typedActors = new ConcurrentHashMap[String, AnyRef] - private[ActorRegistryInstance] val typedActorsByUuid = new ConcurrentHashMap[String, AnyRef] - private[ActorRegistryInstance] val typedActorsFactories = new ConcurrentHashMap[String, () => AnyRef] - } } /** diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index cf246e4ae4..ff2fbcf3cb 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -9,13 +9,20 @@ import java.net.InetSocketAddress import akka.actor._ import akka.util._ import akka.dispatch.CompletableFuture -import akka.actor. {ActorRegistryInstance, ActorType, RemoteActorRef, ActorRef} import akka.config.Config.{config, TIME_UNIT} +import java.util.concurrent.ConcurrentHashMap trait RemoteModule extends Logging { - def registry: ActorRegistryInstance def optimizeLocalScoped_?(): Boolean //Apply optimizations for remote operations in local scope protected[akka] def notifyListeners(message: => Any): Unit + + + private[akka] def actors: ConcurrentHashMap[String, ActorRef] + private[akka] def actorsByUuid: ConcurrentHashMap[String, ActorRef] + private[akka] def actorsFactories: ConcurrentHashMap[String, () => ActorRef] + private[akka] def typedActors: ConcurrentHashMap[String, AnyRef] + private[akka] def typedActorsByUuid: ConcurrentHashMap[String, AnyRef] + private[akka] def typedActorsFactories: ConcurrentHashMap[String, () => AnyRef] } @@ -23,9 +30,21 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule def shutdown { this.shutdownServerModule this.shutdownClientModule + clear } protected override def manageLifeCycleOfListeners = false protected[akka] override def notifyListeners(message: => Any): Unit = super.notifyListeners(message) + + private[akka] val actors = new ConcurrentHashMap[String, ActorRef] + private[akka] val actorsByUuid = new ConcurrentHashMap[String, ActorRef] + private[akka] val actorsFactories = new ConcurrentHashMap[String, () => ActorRef] + private[akka] val typedActors = new ConcurrentHashMap[String, AnyRef] + private[akka] val typedActorsByUuid = new ConcurrentHashMap[String, AnyRef] + private[akka] val typedActorsFactories = new ConcurrentHashMap[String, () => AnyRef] + + def clear { + List(actors,actorsByUuid,actorsFactories,typedActors,typedActorsByUuid,typedActorsFactories) foreach (_.clear) + } } /** @@ -57,7 +76,7 @@ trait RemoteServerModule extends RemoteModule { /** * Starts the server up */ - def start(host: String, port: Int, loader: Option[ClassLoader] = None): RemoteServerModule + def start(host: String = ReflectiveAccess.Remote.HOSTNAME, port: Int = ReflectiveAccess.Remote.PORT, loader: Option[ClassLoader] = None): RemoteServerModule /** * Shuts the server down diff --git a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala index a923b52dd2..1a02136f20 100644 --- a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala +++ b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala @@ -45,10 +45,9 @@ object ReflectiveAccess extends Logging { //TODO: REVISIT: Make class configurable val remoteSupportClass: Option[Class[_ <: RemoteSupport]] = getClassFor(TRANSPORT) - protected[akka] val defaultRemoteSupport: Option[ActorRegistryInstance => RemoteSupport] = remoteSupportClass map { - remoteClass => (registry: ActorRegistryInstance) => - createInstance[RemoteSupport](remoteClass,Array[Class[_]](classOf[ActorRegistryInstance]),Array[AnyRef](registry)). - getOrElse(throw new ModuleNotAvailableException("Can't instantiate "+ + protected[akka] val defaultRemoteSupport: Option[() => RemoteSupport] = remoteSupportClass map { + remoteClass => () => createInstance[RemoteSupport](remoteClass,Array[Class[_]](),Array[AnyRef]()). + getOrElse(throw new ModuleNotAvailableException("Can't instantiate "+ remoteClass.getName+ ", make sure that akka-remote.jar is on the classpath")) } @@ -101,6 +100,7 @@ object ReflectiveAccess extends Logging { } catch { case e => log.slf4j.warn("Could not instantiate class [{}] due to [{}]", clazz.getName, e.getCause) + e.printStackTrace None } diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index 459eebcaf9..ab63d15b64 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -11,8 +11,8 @@ import akka.remote.protocol.RemoteProtocol.ActorType._ import akka.config.ConfigurationException import akka.serialization.RemoteActorSerialization import akka.japi.Creator -import akka.actor.{ActorRegistryInstance, Actor, RemoteActorRef, TypedActor, ActorRef, IllegalActorStateException, - RemoteActorSystemMessage, uuidFrom, Uuid, Exit, ActorRegistry, LifeCycleMessage, ActorType => AkkaActorType} +import akka.actor.{newUuid,ActorRegistry, Actor, RemoteActorRef, TypedActor, ActorRef, IllegalActorStateException, + RemoteActorSystemMessage, uuidFrom, Uuid, Exit, LifeCycleMessage, ActorType => AkkaActorType} import akka.remoteinterface. {RemoteSupport, RemoteModule, RemoteServerModule, RemoteClientModule} import akka.config.Config._ import akka.serialization.RemoteActorSerialization._ @@ -73,7 +73,7 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem private val remoteActors = new HashMap[Address, HashSet[Uuid]] protected[akka] def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): T = - TypedActor.createProxyForRemoteActorRef(intfClass, RemoteActorRef(registry, serviceId, implClassName, hostname, port, timeout, false, loader, AkkaActorType.TypedActor)) + TypedActor.createProxyForRemoteActorRef(intfClass, RemoteActorRef(Some(serviceId), implClassName, hostname, port, timeout, loader, AkkaActorType.TypedActor)) protected[akka] def send[T](message: Any, senderOption: Option[ActorRef], @@ -241,8 +241,6 @@ class RemoteClient private[akka] ( actorRef: ActorRef, typedActorInfo: Option[Tuple2[String, String]], actorType: AkkaActorType): Option[CompletableFuture[T]] = { - val cookie = if (isAuthenticated.compareAndSet(false, true)) RemoteClient.SECURE_COOKIE - else None send(createRemoteMessageProtocolBuilder( Some(actorRef), Left(actorRef.uuid), @@ -254,21 +252,24 @@ class RemoteClient private[akka] ( senderOption, typedActorInfo, actorType, - cookie + if (isAuthenticated.compareAndSet(false, true)) RemoteClient.SECURE_COOKIE else None ).build, senderFuture) } def send[T]( request: RemoteMessageProtocol, senderFuture: Option[CompletableFuture[T]]): Option[CompletableFuture[T]] = { + log.slf4j.debug("sending message: {} is running {} has future {}", Array[AnyRef](request, isRunning.asInstanceOf[AnyRef], senderFuture)) if (isRunning) { if (request.getOneWay) { connection.getChannel.write(request) None } else { val futureResult = if (senderFuture.isDefined) senderFuture.get - else new DefaultCompletableFuture[T](request.getActorInfo.getTimeout) - futures.put(uuidFrom(request.getUuid.getHigh, request.getUuid.getLow), futureResult) + else new DefaultCompletableFuture[T](request.getActorInfo.getTimeout) + val futureUuid = uuidFrom(request.getUuid.getHigh, request.getUuid.getLow) + futures.put(futureUuid, futureResult) + log.slf4j.debug("Stashing away future for {}",futureUuid) connection.getChannel.write(request) Some(futureResult) } @@ -369,34 +370,32 @@ class RemoteClientHandler( override def messageReceived(ctx: ChannelHandlerContext, event: MessageEvent) { try { - val result = event.getMessage - if (result.isInstanceOf[RemoteMessageProtocol]) { - val reply = result.asInstanceOf[RemoteMessageProtocol] - val replyUuid = uuidFrom(reply.getUuid.getHigh, reply.getUuid.getLow) - log.debug("Remote client received RemoteMessageProtocol[\n{}]",reply) - val future = futures.get(replyUuid).asInstanceOf[CompletableFuture[Any]] - if (reply.hasMessage) { - if (future eq null) throw new IllegalActorStateException("Future mapped to UUID " + replyUuid + " does not exist") - val message = MessageSerializer.deserialize(reply.getMessage) - future.completeWithResult(message) - } else { - if (reply.hasSupervisorUuid()) { - val supervisorUuid = uuidFrom(reply.getSupervisorUuid.getHigh, reply.getSupervisorUuid.getLow) - if (!supervisors.containsKey(supervisorUuid)) throw new IllegalActorStateException( - "Expected a registered supervisor for UUID [" + supervisorUuid + "] but none was found") - val supervisedActor = supervisors.get(supervisorUuid) - if (!supervisedActor.supervisor.isDefined) throw new IllegalActorStateException( - "Can't handle restart for remote actor " + supervisedActor + " since its supervisor has been removed") - else supervisedActor.supervisor.get ! Exit(supervisedActor, parseException(reply, client.loader)) + event.getMessage match { + case reply: RemoteMessageProtocol => + val replyUuid = uuidFrom(reply.getActorInfo.getUuid.getHigh, reply.getActorInfo.getUuid.getLow) + log.slf4j.debug("Remote client received RemoteMessageProtocol[\n{}]",reply) + log.slf4j.debug("Trying to map back to future: {}",replyUuid) + val future = futures.remove(replyUuid).asInstanceOf[CompletableFuture[Any]] + if (reply.hasMessage) { + if (future eq null) throw new IllegalActorStateException("Future mapped to UUID " + replyUuid + " does not exist") + val message = MessageSerializer.deserialize(reply.getMessage) + future.completeWithResult(message) + } else { + if (reply.hasSupervisorUuid()) { + val supervisorUuid = uuidFrom(reply.getSupervisorUuid.getHigh, reply.getSupervisorUuid.getLow) + if (!supervisors.containsKey(supervisorUuid)) throw new IllegalActorStateException( + "Expected a registered supervisor for UUID [" + supervisorUuid + "] but none was found") + val supervisedActor = supervisors.get(supervisorUuid) + if (!supervisedActor.supervisor.isDefined) throw new IllegalActorStateException( + "Can't handle restart for remote actor " + supervisedActor + " since its supervisor has been removed") + else supervisedActor.supervisor.get ! Exit(supervisedActor, parseException(reply, client.loader)) + } + + future.completeWithException(parseException(reply, client.loader)) } - val exception = parseException(reply, client.loader) - future.completeWithException(exception) - } - futures remove replyUuid - } else { - val exception = new RemoteClientException("Unknown message received in remote client handler: " + result, client) - client.notifyListeners(RemoteClientError(exception, client)) - throw exception + + case other => + throw new RemoteClientException("Unknown message received in remote client handler: " + other, client) } } catch { case e: Exception => @@ -547,8 +546,11 @@ case class RemoteServerClientClosed( /** * Provides the implementation of the Netty remote support */ -class NettyRemoteSupport(val registry: ActorRegistryInstance) - extends RemoteSupport with NettyRemoteServerModule with NettyRemoteClientModule { +class NettyRemoteSupport extends RemoteSupport with NettyRemoteServerModule with NettyRemoteClientModule { + //Needed for remote testing and switching on/off under run + private[akka] val optimizeLocal = new AtomicBoolean(true) + + def optimizeLocalScoped_?() = optimizeLocal.get protected[akka] def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): ActorRef = { //TODO: REVISIT: Possible to optimize server-managed actors in local scope? @@ -562,7 +564,7 @@ class NettyRemoteSupport(val registry: ActorRegistryInstance) // case _ => // RemoteActorRef(registry, serviceId, className, hostname, port, timeout, false, loader) //} - RemoteActorRef(registry, serviceId, className, hostname, port, timeout, false, loader) + RemoteActorRef(Some(serviceId), className, hostname, port, timeout, loader) } def clientManagedActorOf(clazz: Class[_ <: Actor], host: String, port: Int, timeout: Long): ActorRef = { @@ -571,13 +573,11 @@ class NettyRemoteSupport(val registry: ActorRegistryInstance) (host,port) match { case (Host, Port) if optimizeLocalScoped_? => - registry.actorOf(clazz) //Local + ActorRegistry.actorOf(clazz) //Local case _ => - new RemoteActorRef(registry,clazz.getName,clazz.getName,host,port,timeout,true /*Client managed*/, None) + new RemoteActorRef(None,clazz.getName,host,port,timeout,None) } } - - val optimizeLocalScoped_? = true } trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => @@ -671,6 +671,7 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => } def registerByUuid(actorRef: ActorRef): Unit = guard withGuard { + log.slf4j.debug("Registering remote actor {} to it's uuid {}", actorRef, actorRef.uuid) register(actorRef.uuid.toString, actorRef, actorsByUuid) } @@ -766,13 +767,6 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => */ def unregisterTypedPerSessionActor(id: String): Unit = if (_isRunning.isOn) typedActorsFactories.remove(id) - - private[akka] def actors = registry.actors(address) - private[akka] def actorsByUuid = registry.actorsByUuid(address) - private[akka] def actorsFactories = registry.actorsFactories(address) - private[akka] def typedActors = registry.typedActors(address) - private[akka] def typedActorsByUuid = registry.typedActorsByUuid(address) - private[akka] def typedActorsFactories = registry.typedActorsFactories(address) } object RemoteServerSslContext { @@ -970,15 +964,18 @@ class RemoteServerHandler( None, Some(new DefaultCompletableFuture[AnyRef](request.getActorInfo.getTimeout). onComplete(f => { + log.slf4j.debug("Future was completed, now flushing to remote!") val result = f.result val exception = f.exception if (exception.isDefined) { - log.slf4j.debug("Returning exception from actor invocation [{}]",exception.get) + log.slf4j.debug("Returning exception from actor invocation [{}]",exception.get.getClass) try { channel.write(createErrorReplyMessage(exception.get, request, AkkaActorType.ScalaActor)) } catch { - case e: Throwable => server.notifyListeners(RemoteServerError(e, server)) + case e: Throwable => + log.slf4j.debug("An error occurred in sending the reply",e) + server.notifyListeners(RemoteServerError(e, server)) } } else if (result.isDefined) { @@ -1069,6 +1066,7 @@ class RemoteServerHandler( } private def findActorByUuid(uuid: String) : ActorRef = { + log.slf4j.debug("Trying to find actor for uuid '{}' inside {}",uuid,server.actorsByUuid) server.actorsByUuid.get(uuid) } @@ -1149,10 +1147,10 @@ class RemoteServerHandler( if (RemoteServer.UNTRUSTED_MODE) throw new SecurityException( "Remote server is operating is untrusted mode, can not create remote actors on behalf of the remote client") - log.slf4j.info("Creating a new remote actor [{}:{}]", name, uuid) + log.slf4j.info("Creating a new client-managed remote actor [{}:{}]", name, uuid) val clazz = if (applicationLoader.isDefined) applicationLoader.get.loadClass(name) else Class.forName(name) - val actorRef = Actor.actorOf(clazz.asInstanceOf[Class[_ <: Actor]]) + val actorRef = ActorRegistry.actorOf(clazz.asInstanceOf[Class[_ <: Actor]]) actorRef.uuid = uuidFrom(uuid.getHigh,uuid.getLow) actorRef.id = id actorRef.timeout = timeout diff --git a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala index 10eed2c362..6032818e09 100644 --- a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -127,7 +127,7 @@ object ActorSerialization { messages.map(m => RemoteActorSerialization.createRemoteMessageProtocolBuilder( Some(actorRef), - Left(actorRef.uuid), + Left(actorRef.uuid), //TODO: REVISIT: generate uuid for the request actorRef.id, actorRef.actorClassName, actorRef.timeout, @@ -191,7 +191,6 @@ object ActorSerialization { } val ar = new LocalActorRef( - ActorRegistry,//TODO: REVISIST: Change to an implicit ActorRegistryInstance? uuidFrom(protocol.getUuid.getHigh, protocol.getUuid.getLow), protocol.getId, protocol.getOriginalAddress.getHostname, @@ -230,15 +229,16 @@ object RemoteActorSerialization { */ private[akka] def fromProtobufToRemoteActorRef(protocol: RemoteActorRefProtocol, loader: Option[ClassLoader]): ActorRef = { Actor.log.slf4j.debug("Deserializing RemoteActorRefProtocol to RemoteActorRef:\n {}", protocol) - RemoteActorRef( - ActorRegistry,//TODO: REVISIST: Change to an implicit ActorRegistryInstance? - protocol.getClassOrServiceName, + val ref = RemoteActorRef( + Some(protocol.getClassOrServiceName), protocol.getActorClassname, protocol.getHomeAddress.getHostname, protocol.getHomeAddress.getPort, protocol.getTimeout, - false, loader) + + Actor.log.slf4j.debug("Newly deserialized RemoteActorRef has uuid: {}", ref.uuid) + ref } /** @@ -248,12 +248,12 @@ object RemoteActorSerialization { import ar._ Actor.log.slf4j.debug("Register serialized Actor [{}] as remote @ [{}:{}]", - Array[AnyRef](actorClassName, registry.remote.hostname, registry.remote.port.asInstanceOf[AnyRef])) + Array[AnyRef](actorClassName, ActorSerialization.localAddress.getHostname, ActorSerialization.localAddress.getPort.asInstanceOf[AnyRef])) - registry.remote.registerByUuid(ar) + ActorRegistry.remote.registerByUuid(ar) RemoteActorRefProtocol.newBuilder - .setClassOrServiceName(uuid.toString) + .setClassOrServiceName("uuid:"+uuid.toString) .setActorClassname(actorClassName) .setHomeAddress(ActorSerialization.localAddress) .setTimeout(timeout) @@ -262,7 +262,7 @@ object RemoteActorSerialization { def createRemoteMessageProtocolBuilder( actorRef: Option[ActorRef], - uuid: Either[Uuid, UuidProtocol], + replyUuid: Either[Uuid, UuidProtocol], actorId: String, actorClassName: String, timeout: Long, @@ -273,7 +273,7 @@ object RemoteActorSerialization { actorType: ActorType, secureCookie: Option[String]): RemoteMessageProtocol.Builder = { - val uuidProtocol = uuid match { + val uuidProtocol = replyUuid match { case Left(uid) => UuidProtocol.newBuilder.setHigh(uid.getTime).setLow(uid.getClockSeqAndNode).build case Right(protocol) => protocol } @@ -298,7 +298,10 @@ object RemoteActorSerialization { } val actorInfo = actorInfoBuilder.build val messageBuilder = RemoteMessageProtocol.newBuilder - .setUuid(uuidProtocol) + .setUuid({ + val messageUuid = newUuid + UuidProtocol.newBuilder.setHigh(messageUuid.getTime).setLow(messageUuid.getClockSeqAndNode).build + }) .setActorInfo(actorInfo) .setOneWay(isOneWay) @@ -308,10 +311,15 @@ object RemoteActorSerialization { case Right(exception) => messageBuilder.setException(ExceptionProtocol.newBuilder .setClassname(exception.getClass.getName) - .setMessage(exception.getMessage) + .setMessage(empty(exception.getMessage)) .build) } + def empty(s: String): String = s match { + case null => "" + case s => s + } + secureCookie.foreach(messageBuilder.setCookie(_)) //TODO: REVISIT: REMOVE diff --git a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala index d5ee009142..7213d856b6 100644 --- a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala @@ -1,167 +1,169 @@ package akka.actor.remote import java.util.concurrent.{CountDownLatch, TimeUnit} -import org.scalatest.junit.JUnitSuite -import org.junit.{Test, Before, After} +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} +import org.scalatest.junit.JUnitRunner +import org.junit.runner.RunWith import akka.dispatch.Dispatchers import akka.remote. {NettyRemoteSupport, RemoteServer, RemoteClient} -import akka.actor. {RemoteActorRef, ActorRegistryInstance, ActorRef, Actor} +import akka.actor. {RemoteActorRef, ActorRegistry, ActorRef, Actor} +import akka.actor.Actor._ -class ExpectedRemoteProblem extends RuntimeException +class ExpectedRemoteProblem(msg: String) extends RuntimeException(msg) -object ClientInitiatedRemoteActorSpec { - case class Send(actor: Actor) +object RemoteActorSpecActorUnidirectional { + val latch = new CountDownLatch(1) +} +class RemoteActorSpecActorUnidirectional extends Actor { + self.dispatcher = Dispatchers.newThreadBasedDispatcher(self) - object RemoteActorSpecActorUnidirectional { - val latch = new CountDownLatch(1) - } - class RemoteActorSpecActorUnidirectional extends Actor { - self.dispatcher = Dispatchers.newThreadBasedDispatcher(self) - - def receive = { - case "OneWay" => - RemoteActorSpecActorUnidirectional.latch.countDown - } - } - - class RemoteActorSpecActorBidirectional extends Actor { - def receive = { - case "Hello" => - self.reply("World") - case "Failure" => throw new ExpectedRemoteProblem - } - } - - class SendOneWayAndReplyReceiverActor extends Actor { - def receive = { - case "Hello" => - self.reply("World") - } - } - - class CountDownActor(latch: CountDownLatch) extends Actor { - def receive = { - case "World" => latch.countDown - } - } - - object SendOneWayAndReplySenderActor { - val latch = new CountDownLatch(1) - } - class SendOneWayAndReplySenderActor extends Actor { - var state: Option[AnyRef] = None - var sendTo: ActorRef = _ - var latch: CountDownLatch = _ - - def sendOff = sendTo ! "Hello" - - def receive = { - case msg: AnyRef => - state = Some(msg) - SendOneWayAndReplySenderActor.latch.countDown - } - } - - class MyActorCustomConstructor extends Actor { - var prefix = "default-" - var count = 0 - def receive = { - case "incrPrefix" => count += 1; prefix = "" + count + "-" - case msg: String => self.reply(prefix + msg) - } + def receive = { + case "OneWay" => + RemoteActorSpecActorUnidirectional.latch.countDown } } -class ClientInitiatedRemoteActorSpec extends JUnitSuite { - import ClientInitiatedRemoteActorSpec._ - akka.config.Config.config - - val HOSTNAME = "localhost" - val PORT1 = 9990 - val PORT2 = 9991 - var s1,s2: ActorRegistryInstance = null - - private val unit = TimeUnit.MILLISECONDS - - @Before - def init() { - s1 = new ActorRegistryInstance(Some(new NettyRemoteSupport(_))) - s2 = new ActorRegistryInstance(Some(new NettyRemoteSupport(_))) - s1.remote.start(HOSTNAME, PORT1) - s2.remote.start(HOSTNAME, PORT2) - Thread.sleep(2000) - } - - @After - def finished() { - s1.remote.shutdown - s2.remote.shutdown - s1.shutdownAll - s2.shutdownAll - Thread.sleep(1000) - } - - @Test - def shouldSendOneWay = { - val clientManaged = s1.actorOf[RemoteActorSpecActorUnidirectional](HOSTNAME,PORT2).start - //implicit val self = Some(s2.actorOf[RemoteActorSpecActorUnidirectional].start) - assert(clientManaged ne null) - assert(clientManaged.getClass.equals(classOf[RemoteActorRef])) - clientManaged ! "OneWay" - assert(RemoteActorSpecActorUnidirectional.latch.await(1, TimeUnit.SECONDS)) - clientManaged.stop - } - - - @Test - def shouldSendOneWayAndReceiveReply = { - val latch = new CountDownLatch(1) - val actor = s2.actorOf[SendOneWayAndReplyReceiverActor](HOSTNAME, PORT1).start - implicit val sender = Some(s1.actorOf(new CountDownActor(latch)).start) - - actor ! "OneWay" - - assert(latch.await(3,TimeUnit.SECONDS)) - } - - @Test - def shouldSendBangBangMessageAndReceiveReply = { - val actor = s2.actorOf[RemoteActorSpecActorBidirectional](HOSTNAME, PORT1).start - val result = actor !! "Hello" - assert("World" === result.get.asInstanceOf[String]) - actor.stop - } - - @Test - def shouldSendBangBangMessageAndReceiveReplyConcurrently = { - val actors = (1 to 10).map(num => { s2.actorOf[RemoteActorSpecActorBidirectional](HOSTNAME, PORT1).start }).toList - actors.map(_ !!! "Hello").foreach(future => assert("World" === future.await.result.asInstanceOf[Option[String]].get)) - actors.foreach(_.stop) - } - - @Test - def shouldRegisterActorByUuid { - val actor1 = s2.actorOf[MyActorCustomConstructor](HOSTNAME, PORT1).start - actor1 ! "incrPrefix" - assert((actor1 !! "test").get === "1-test") - actor1 ! "incrPrefix" - assert((actor1 !! "test").get === "2-test") - - val actor2 = s2.actorOf[MyActorCustomConstructor](HOSTNAME, PORT1).start - - assert((actor2 !! "test").get === "default-test") - - actor1.stop - actor2.stop - } - - @Test(expected=classOf[ExpectedRemoteProblem]) - def shouldSendAndReceiveRemoteException { - implicit val timeout = 500000000L - val actor = s2.actorOf[RemoteActorSpecActorBidirectional](HOSTNAME, PORT1).start - actor !! "Failure" - actor.stop +class RemoteActorSpecActorBidirectional extends Actor { + def receive = { + case "Hello" => + self.reply("World") + case "Failure" => throw new ExpectedRemoteProblem("expected") } } +class SendOneWayAndReplyReceiverActor extends Actor { + def receive = { + case "Hello" => + self.reply("World") + } +} + +class CountDownActor(latch: CountDownLatch) extends Actor { + def receive = { + case "World" => latch.countDown + } +} + +object SendOneWayAndReplySenderActor { + val latch = new CountDownLatch(1) +} +class SendOneWayAndReplySenderActor extends Actor { + var state: Option[AnyRef] = None + var sendTo: ActorRef = _ + var latch: CountDownLatch = _ + + def sendOff = sendTo ! "Hello" + + def receive = { + case msg: AnyRef => + state = Some(msg) + SendOneWayAndReplySenderActor.latch.countDown + } +} + +class MyActorCustomConstructor extends Actor { + var prefix = "default-" + var count = 0 + def receive = { + case "incrPrefix" => count += 1; prefix = "" + count + "-" + case msg: String => self.reply(prefix + msg) + } +} + +@RunWith(classOf[JUnitRunner]) +class ClientInitiatedRemoteActorSpec extends + WordSpec with + MustMatchers with + BeforeAndAfterAll with + BeforeAndAfterEach { + + var optimizeLocal_? = ActorRegistry.remote.asInstanceOf[NettyRemoteSupport].optimizeLocalScoped_? + + override def beforeAll() { + ActorRegistry.remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(false) //Can't run the test if we're eliminating all remote calls + ActorRegistry.remote.start() + } + + override def afterAll() { + ActorRegistry.remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(optimizeLocal_?) //Reset optimizelocal after all tests + ActorRegistry.shutdownAll + } + + override def afterEach() { + ActorRegistry.shutdownAll + super.afterEach + } + + "ClientInitiatedRemoteActor" should { + val unit = TimeUnit.MILLISECONDS + val (host, port) = (ActorRegistry.remote.hostname,ActorRegistry.remote.port) + + "shouldSendOneWay" in { + val clientManaged = actorOf[RemoteActorSpecActorUnidirectional](host,port).start + clientManaged must not be null + clientManaged.getClass must be (classOf[RemoteActorRef]) + clientManaged ! "OneWay" + RemoteActorSpecActorUnidirectional.latch.await(1, TimeUnit.SECONDS) must be (true) + clientManaged.stop + } + + "shouldSendOneWayAndReceiveReply" in { + val latch = new CountDownLatch(1) + val actor = actorOf[SendOneWayAndReplyReceiverActor](host,port).start + implicit val sender = Some(actorOf(new CountDownActor(latch)).start) + + actor ! "Hello" + + latch.await(3,TimeUnit.SECONDS) must be (true) + } + + "shouldSendBangBangMessageAndReceiveReply" in { + val actor = actorOf[RemoteActorSpecActorBidirectional](host,port).start + val result = actor !! "Hello" + "World" must equal (result.get.asInstanceOf[String]) + actor.stop + } + + "shouldSendBangBangMessageAndReceiveReplyConcurrently" in { + val actors = (1 to 10).map(num => { actorOf[RemoteActorSpecActorBidirectional](host,port).start }).toList + actors.map(_ !!! "Hello") foreach { future => + "World" must equal (future.await.result.asInstanceOf[Option[String]].get) + } + actors.foreach(_.stop) + } + + "shouldRegisterActorByUuid" in { + val actor1 = actorOf[MyActorCustomConstructor](host, port).start + val actor2 = actorOf[MyActorCustomConstructor](host, port).start + + actor1 ! "incrPrefix" + + (actor1 !! "test").get must equal ("1-test") + + actor1 ! "incrPrefix" + + (actor1 !! "test").get must equal ("2-test") + + (actor2 !! "test").get must equal ("default-test") + + actor1.stop + actor2.stop + } + + "shouldSendAndReceiveRemoteException" in { + + val actor = actorOf[RemoteActorSpecActorBidirectional](host, port).start + try { + implicit val timeout = 500000000L + val f = (actor !!! "Failure").await.resultOrException + fail("Shouldn't get here!!!") + } catch { + case e: ExpectedRemoteProblem => + } + actor.stop + } + } +} diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala index 14387e7909..7b48cde9bb 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala @@ -1,29 +1,31 @@ package akka.actor.remote import java.util.concurrent.{CountDownLatch, TimeUnit} -import org.scalatest.junit.JUnitSuite -import org.junit.{Test, Before, After} +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} +import org.scalatest.junit.JUnitRunner +import org.junit.runner.RunWith import akka.util._ -import akka.remote.{RemoteServer, RemoteClient} import akka.actor.Actor._ import akka.actor.{ActorRegistry, ActorRef, Actor} +import akka.remote. {NettyRemoteSupport, RemoteServer, RemoteClient} object ServerInitiatedRemoteActorSpec { - val HOSTNAME = "localhost" - val PORT = 9990 - var server: RemoteServer = null - case class Send(actor: ActorRef) - object RemoteActorSpecActorUnidirectional { - val latch = new CountDownLatch(1) - } - class RemoteActorSpecActorUnidirectional extends Actor { - + class ReplyHandlerActor(latch: CountDownLatch, expect: String) extends Actor { def receive = { - case "OneWay" => - RemoteActorSpecActorUnidirectional.latch.countDown + case x: String if x == expect => latch.countDown + } + } + + def replyHandler(latch: CountDownLatch, expect: String) = Some(actorOf(new ReplyHandlerActor(latch, expect)).start) + + class RemoteActorSpecActorUnidirectional extends Actor { + def receive = { + case "Ping" => self.reply_?("Pong") } } @@ -51,170 +53,140 @@ object ServerInitiatedRemoteActorSpec { } } -class ServerInitiatedRemoteActorSpec extends JUnitSuite { +@RunWith(classOf[JUnitRunner]) +class ServerInitiatedRemoteActorSpec extends + WordSpec with + MustMatchers with + BeforeAndAfterAll with + BeforeAndAfterEach { import ServerInitiatedRemoteActorSpec._ + import ActorRegistry.remote private val unit = TimeUnit.MILLISECONDS + val (host, port) = (remote.hostname,remote.port) - @Before - def init { - server = new RemoteServer() + var optimizeLocal_? = remote.asInstanceOf[NettyRemoteSupport].optimizeLocalScoped_? - server.start(HOSTNAME, PORT) - - server.register(actorOf[RemoteActorSpecActorUnidirectional]) - server.register(actorOf[RemoteActorSpecActorBidirectional]) - server.register(actorOf[RemoteActorSpecActorAsyncSender]) - - Thread.sleep(1000) + override def beforeAll() { + remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(false) //Can't run the test if we're eliminating all remote calls + remote.start() } - // make sure the servers postStop cleanly after the test has finished - @After - def finished { - try { - server.shutdown - val s2 = RemoteServer.serverFor(HOSTNAME, PORT + 1) - if (s2.isDefined) s2.get.shutdown - RemoteClient.shutdownAll - Thread.sleep(1000) - } catch { - case e => () - } + override def afterAll() { + remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(optimizeLocal_?) //Reset optimizelocal after all tests } - @Test - def shouldSendWithBang { - val actor = RemoteClient.actorFor( - "akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorUnidirectional", - 5000L, - HOSTNAME, PORT) - val result = actor ! "OneWay" - assert(RemoteActorSpecActorUnidirectional.latch.await(1, TimeUnit.SECONDS)) - actor.stop + override def afterEach() { + ActorRegistry.shutdownAll + super.afterEach } - @Test - def shouldSendWithBangBangAndGetReply { - val actor = RemoteClient.actorFor( - "akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorBidirectional", - 5000L, - HOSTNAME, PORT) - val result = actor !! "Hello" - assert("World" === result.get.asInstanceOf[String]) - actor.stop - } + "Server-managed remote actors" should { + "sendWithBang" in { + val latch = new CountDownLatch(1) + implicit val sender = replyHandler(latch, "Pong") + remote.register(actorOf[RemoteActorSpecActorUnidirectional]) + val actor = remote.actorFor("akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorUnidirectional",5000L,host, port) - @Test - def shouldSendWithBangAndGetReplyThroughSenderRef { - implicit val timeout = 500000000L - val actor = RemoteClient.actorFor( - "akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorBidirectional", - timeout, - HOSTNAME, PORT) - val sender = actorOf[RemoteActorSpecActorAsyncSender] - sender.homeAddress = (HOSTNAME, PORT + 1) - sender.start - sender ! Send(actor) - assert(RemoteActorSpecActorAsyncSender.latch.await(1, TimeUnit.SECONDS)) - actor.stop - } - - @Test - def shouldSendWithBangBangAndReplyWithException { - implicit val timeout = 500000000L - val actor = RemoteClient.actorFor( - "akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorBidirectional", - timeout, - HOSTNAME, PORT) - try { - actor !! "Failure" - fail("Should have thrown an exception") - } catch { - case e => - assert("Expected exception; to test fault-tolerance" === e.getMessage()) - } - actor.stop - } - - @Test - def reflectiveAccessShouldNotCreateNewRemoteServerObject { - val server1 = new RemoteServer() - server1.start("localhost", 9990) - - var found = RemoteServer.serverFor("localhost", 9990) - assert(found.isDefined, "sever not found") - - val a = actorOf( new Actor { def receive = { case _ => } } ).start - - found = RemoteServer.serverFor("localhost", 9990) - assert(found.isDefined, "sever not found after creating an actor") + actor ! "Ping" + latch.await(1, TimeUnit.SECONDS) must be (true) } + "sendWithBangBangAndGetReply" in { + remote.register(actorOf[RemoteActorSpecActorBidirectional]) + val actor = remote.actorFor("akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorBidirectional", 5000L,host, port) + (actor !! "Hello").as[String].get must equal ("World") + } - @Test - def shouldNotRecreateRegisteredActor { - server.register(actorOf[RemoteActorSpecActorUnidirectional]) - val actor = RemoteClient.actorFor("akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorUnidirectional", HOSTNAME, PORT) - val numberOfActorsInRegistry = ActorRegistry.actors.length - actor ! "OneWay" - assert(RemoteActorSpecActorUnidirectional.latch.await(1, TimeUnit.SECONDS)) - assert(numberOfActorsInRegistry === ActorRegistry.actors.length) - actor.stop + "sendWithBangAndGetReplyThroughSenderRef" in { + remote.register(actorOf[RemoteActorSpecActorBidirectional]) + implicit val timeout = 500000000L + val actor = remote.actorFor( + "akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorBidirectional", timeout,host, port) + val sender = actorOf[RemoteActorSpecActorAsyncSender].start + sender ! Send(actor) + RemoteActorSpecActorAsyncSender.latch.await(1, TimeUnit.SECONDS) must be (true) + } + + "sendWithBangBangAndReplyWithException" in { + remote.register(actorOf[RemoteActorSpecActorBidirectional]) + implicit val timeout = 500000000L + val actor = remote.actorFor( + "akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorBidirectional", timeout, host, port) + try { + actor !! "Failure" + fail("Should have thrown an exception") + } catch { + case e => e.getMessage must equal ("Expected exception; to test fault-tolerance") + } + } + + "notRecreateRegisteredActor" in { + val latch = new CountDownLatch(1) + implicit val sender = replyHandler(latch, "Pong") + remote.register(actorOf[RemoteActorSpecActorUnidirectional]) + val actor = remote.actorFor("akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorUnidirectional", host, port) + val numberOfActorsInRegistry = ActorRegistry.actors.length + actor ! "Ping" + latch.await(1, TimeUnit.SECONDS) must be (true) + numberOfActorsInRegistry must equal (ActorRegistry.actors.length) + } + + "UseServiceNameAsIdForRemoteActorRef" in { + val latch = new CountDownLatch(3) + implicit val sender = replyHandler(latch, "Pong") + remote.register(actorOf[RemoteActorSpecActorUnidirectional]) + remote.register("my-service", actorOf[RemoteActorSpecActorUnidirectional]) + val actor1 = remote.actorFor("akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorUnidirectional", host, port) + val actor2 = remote.actorFor("my-service", host, port) + val actor3 = remote.actorFor("my-service", host, port) + + actor1 ! "Ping" + actor2 ! "Ping" + actor3 ! "Ping" + + latch.await(1, TimeUnit.SECONDS) must be (true) + actor1.uuid must not equal actor2.uuid + actor1.uuid must not equal actor3.uuid + actor1.id must not equal actor2.id + actor2.id must equal (actor3.id) + } + + "shouldFindActorByUuid" in { + val latch = new CountDownLatch(2) + implicit val sender = replyHandler(latch, "Pong") + val actor1 = actorOf[RemoteActorSpecActorUnidirectional] + val actor2 = actorOf[RemoteActorSpecActorUnidirectional] + remote.register("uuid:" + actor1.uuid, actor1) + remote.register("my-service", actor2) + + val ref1 = remote.actorFor("uuid:" + actor1.uuid, host, port) + val ref2 = remote.actorFor("my-service", host, port) + + ref1 ! "Ping" + ref2 ! "Ping" + latch.await(1, TimeUnit.SECONDS) must be (true) + } + + "shouldRegisterAndUnregister" in { + val actor1 = actorOf[RemoteActorSpecActorUnidirectional] + + remote.register("my-service-1", actor1) + remote.actors.get("my-service-1") must not be null + + remote.unregister("my-service-1") + remote.actors.get("my-service-1") must be (null) + } + + "shouldRegisterAndUnregisterByUuid" in { + val actor1 = actorOf[RemoteActorSpecActorUnidirectional] + val uuid = "uuid:" + actor1.uuid + + remote.register(uuid, actor1) + remote.actorsByUuid.get(actor1.uuid.toString) must not be null + + remote.unregister(uuid) + remote.actorsByUuid.get(actor1.uuid) must be (null) + } } - - @Test - def shouldUseServiceNameAsIdForRemoteActorRef { - server.register(actorOf[RemoteActorSpecActorUnidirectional]) - server.register("my-service", actorOf[RemoteActorSpecActorUnidirectional]) - val actor1 = RemoteClient.actorFor("akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorUnidirectional", HOSTNAME, PORT) - val actor2 = RemoteClient.actorFor("my-service", HOSTNAME, PORT) - val actor3 = RemoteClient.actorFor("my-service", HOSTNAME, PORT) - - actor1 ! "OneWay" - actor2 ! "OneWay" - actor3 ! "OneWay" - - assert(actor1.uuid != actor2.uuid) - assert(actor1.uuid != actor3.uuid) - assert(actor1.id != actor2.id) - assert(actor2.id == actor3.id) - } - - @Test - def shouldFindActorByUuid { - val actor1 = actorOf[RemoteActorSpecActorUnidirectional] - val actor2 = actorOf[RemoteActorSpecActorUnidirectional] - server.register("uuid:" + actor1.uuid, actor1) - server.register("my-service", actor2) - - val ref1 = RemoteClient.actorFor("uuid:" + actor1.uuid, HOSTNAME, PORT) - val ref2 = RemoteClient.actorFor("my-service", HOSTNAME, PORT) - - ref1 ! "OneWay" - assert(RemoteActorSpecActorUnidirectional.latch.await(1, TimeUnit.SECONDS)) - ref1.stop - ref2 ! "OneWay" - ref2.stop - - } - - @Test - def shouldRegisterAndUnregister { - val actor1 = actorOf[RemoteActorSpecActorUnidirectional] - server.register("my-service-1", actor1) - assert(server.actors.get("my-service-1") ne null, "actor registered") - server.unregister("my-service-1") - assert(server.actors.get("my-service-1") eq null, "actor unregistered") - } - - @Test - def shouldRegisterAndUnregisterByUuid { - val actor1 = actorOf[RemoteActorSpecActorUnidirectional] - server.register("uuid:" + actor1.uuid, actor1) - assert(server.actorsByUuid.get(actor1.uuid.toString) ne null, "actor registered") - server.unregister("uuid:" + actor1.uuid) - assert(server.actorsByUuid.get(actor1.uuid) eq null, "actor unregistered") - } - } From 44933e9c1ef5095b600b350fc552e27b3dcc48f2 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 17 Dec 2010 17:30:29 +0100 Subject: [PATCH 07/38] Commented out many of the remote tests while I am porting --- .../test/scala/remote/AkkaRemoteTest.scala | 52 +++++++ .../ClientInitiatedRemoteActorSpec.scala | 32 +--- .../scala/remote/RemoteSupervisorSpec.scala | 11 +- .../scala/remote/RemoteTypedActorSpec.scala | 8 +- .../ServerInitiatedRemoteActorSample.scala | 18 +-- .../ServerInitiatedRemoteActorSpec.scala | 48 +----- ...erverInitiatedRemoteSessionActorSpec.scala | 144 +++++++----------- .../ServerInitiatedRemoteTypedActorSpec.scala | 3 +- ...InitiatedRemoteTypedSessionActorSpec.scala | 3 +- .../src/test/scala/remote/ShutdownSpec.scala | 3 +- ...rotobufActorMessageSerializationSpec.scala | 50 ++---- .../TypedActorSerializationSpec.scala | 4 + .../src/test/scala/ticket/Ticket434Spec.scala | 46 +++--- .../src/test/scala/ticket/Ticket506Spec.scala | 44 ++---- .../src/test/scala/ticket/Ticket519Spec.scala | 23 +-- 15 files changed, 190 insertions(+), 299 deletions(-) create mode 100644 akka-remote/src/test/scala/remote/AkkaRemoteTest.scala diff --git a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala new file mode 100644 index 0000000000..c5ff2b3024 --- /dev/null +++ b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala @@ -0,0 +1,52 @@ +package akka.actor.remote + +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} +import org.scalatest.junit.JUnitRunner +import org.junit.runner.RunWith +import akka.remote.NettyRemoteSupport +import akka.actor. {Actor, ActorRegistry} +import java.util.concurrent. {TimeUnit, CountDownLatch} + +object AkkaRemoteTest { + class ReplyHandlerActor(latch: CountDownLatch, expect: String) extends Actor { + def receive = { + case x: String if x == expect => latch.countDown + } + } +} + +@RunWith(classOf[JUnitRunner]) +class AkkaRemoteTest extends + WordSpec with + MustMatchers with + BeforeAndAfterAll with + BeforeAndAfterEach { + import AkkaRemoteTest._ + + val remote = ActorRegistry.remote + val unit = TimeUnit.SECONDS + val host = remote.hostname + val port = remote.port + + var optimizeLocal_? = remote.asInstanceOf[NettyRemoteSupport].optimizeLocalScoped_? + + override def beforeAll() { + remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(false) //Can't run the test if we're eliminating all remote calls + remote.start() + } + + override def afterAll() { + remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(optimizeLocal_?) //Reset optimizelocal after all tests + } + + override def afterEach() { + ActorRegistry.shutdownAll + super.afterEach + } + + /* Utilities */ + + def replyHandler(latch: CountDownLatch, expect: String) = Some(Actor.actorOf(new ReplyHandlerActor(latch, expect)).start) +} \ No newline at end of file diff --git a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala index 7213d856b6..4b3e301aae 100644 --- a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala @@ -46,7 +46,7 @@ class CountDownActor(latch: CountDownLatch) extends Actor { case "World" => latch.countDown } } - +/* object SendOneWayAndReplySenderActor { val latch = new CountDownLatch(1) } @@ -62,7 +62,7 @@ class SendOneWayAndReplySenderActor extends Actor { state = Some(msg) SendOneWayAndReplySenderActor.latch.countDown } -} +}*/ class MyActorCustomConstructor extends Actor { var prefix = "default-" @@ -73,34 +73,8 @@ class MyActorCustomConstructor extends Actor { } } -@RunWith(classOf[JUnitRunner]) -class ClientInitiatedRemoteActorSpec extends - WordSpec with - MustMatchers with - BeforeAndAfterAll with - BeforeAndAfterEach { - - var optimizeLocal_? = ActorRegistry.remote.asInstanceOf[NettyRemoteSupport].optimizeLocalScoped_? - - override def beforeAll() { - ActorRegistry.remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(false) //Can't run the test if we're eliminating all remote calls - ActorRegistry.remote.start() - } - - override def afterAll() { - ActorRegistry.remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(optimizeLocal_?) //Reset optimizelocal after all tests - ActorRegistry.shutdownAll - } - - override def afterEach() { - ActorRegistry.shutdownAll - super.afterEach - } - +class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { "ClientInitiatedRemoteActor" should { - val unit = TimeUnit.MILLISECONDS - val (host, port) = (ActorRegistry.remote.hostname,ActorRegistry.remote.port) - "shouldSendOneWay" in { val clientManaged = actorOf[RemoteActorSpecActorUnidirectional](host,port).start clientManaged must not be null diff --git a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala index 017207cce7..bb6ed80ca7 100644 --- a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala +++ b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala @@ -1,7 +1,7 @@ /** * Copyright (C) 2009-2010 Scalable Solutions AB */ - + /* THIS SHOULD BE UNCOMMENTED package akka.actor.remote import java.util.concurrent.{LinkedBlockingQueue, TimeUnit, BlockingQueue} @@ -212,7 +212,7 @@ class RemoteSupervisorSpec extends JUnitSuite { expect("Expected exception; to test fault-tolerance") { messageLog.poll(5, TimeUnit.SECONDS) } - } + }*/ /* // Uncomment when the same test passes in SupervisorSpec - pending bug @@ -229,6 +229,7 @@ class RemoteSupervisorSpec extends JUnitSuite { } } */ + /* THIS SHOULD BE UNCOMMENTED @Test def shouldKillCallMultipleActorsOneForOne = { clearMessageLogs val sup = getMultipleActorsOneForOneConf @@ -362,7 +363,7 @@ class RemoteSupervisorSpec extends JUnitSuite { expect("ping") { messageLog.poll(5, TimeUnit.SECONDS) } - } + }*/ /* @@ -462,7 +463,7 @@ class RemoteSupervisorSpec extends JUnitSuite { */ // ============================================= // Creat some supervisors with different configurations - + /* THIS SHOULD BE UNCOMMENTED def getSingleActorAllForOneSupervisor: Supervisor = { // Create an abstract SupervisorContainer that works for all implementations @@ -592,4 +593,4 @@ class RemoteSupervisorSpec extends JUnitSuite { :: Nil)) factory.newInstance } -} +}*/ diff --git a/akka-remote/src/test/scala/remote/RemoteTypedActorSpec.scala b/akka-remote/src/test/scala/remote/RemoteTypedActorSpec.scala index 431c633102..ce66a3d108 100644 --- a/akka-remote/src/test/scala/remote/RemoteTypedActorSpec.scala +++ b/akka-remote/src/test/scala/remote/RemoteTypedActorSpec.scala @@ -15,12 +15,12 @@ import akka.remote.{RemoteServer, RemoteClient} import java.util.concurrent.{LinkedBlockingQueue, TimeUnit, BlockingQueue} import org.scalatest.{BeforeAndAfterEach, Spec, Assertions, BeforeAndAfterAll} import akka.config.{Config, TypedActorConfigurator, RemoteAddress} - +/* THIS SHOULD BE UNCOMMENTED object RemoteTypedActorSpec { val HOSTNAME = "localhost" val PORT = 9988 var server: RemoteServer = null -} +}*/ object RemoteTypedActorLog { val messageLog: BlockingQueue[String] = new LinkedBlockingQueue[String] @@ -31,7 +31,7 @@ object RemoteTypedActorLog { oneWayLog.clear } } - +/* THIS SHOULD BE UNCOMMENTED @RunWith(classOf[JUnitRunner]) class RemoteTypedActorSpec extends Spec with @@ -125,4 +125,4 @@ class RemoteTypedActorSpec extends messageLog.poll(5, TimeUnit.SECONDS) should equal ("Expected exception; to test fault-tolerance") } } -} +} */ diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSample.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSample.scala index 836230d839..186d5a3362 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSample.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSample.scala @@ -1,7 +1,6 @@ package akka.actor.remote -import akka.actor.Actor -import akka.remote.{RemoteClient, RemoteNode} +import akka.actor.{Actor, ActorRegistry} import akka.util.Logging import Actor._ @@ -16,22 +15,17 @@ class HelloWorldActor extends Actor { object ServerInitiatedRemoteActorServer { - def run = { - RemoteNode.start("localhost", 2552) - RemoteNode.register("hello-service", actorOf[HelloWorldActor]) + def main(args: Array[String]) = { + ActorRegistry.remote.start("localhost", 2552) + ActorRegistry.remote.register("hello-service", actorOf[HelloWorldActor]) } - - def main(args: Array[String]) = run } object ServerInitiatedRemoteActorClient extends Logging { - - def run = { - val actor = RemoteClient.actorFor("hello-service", "localhost", 2552) + def main(args: Array[String]) = { + val actor = ActorRegistry.remote.actorFor("hello-service", "localhost", 2552) val result = actor !! "Hello" log.slf4j.info("Result from Remote Actor: {}", result) } - - def main(args: Array[String]) = run } diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala index 7b48cde9bb..c09e266e62 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala @@ -15,14 +15,6 @@ import akka.remote. {NettyRemoteSupport, RemoteServer, RemoteClient} object ServerInitiatedRemoteActorSpec { case class Send(actor: ActorRef) - class ReplyHandlerActor(latch: CountDownLatch, expect: String) extends Actor { - def receive = { - case x: String if x == expect => latch.countDown - } - } - - def replyHandler(latch: CountDownLatch, expect: String) = Some(actorOf(new ReplyHandlerActor(latch, expect)).start) - class RemoteActorSpecActorUnidirectional extends Actor { def receive = { case "Ping" => self.reply_?("Pong") @@ -39,46 +31,17 @@ object ServerInitiatedRemoteActorSpec { } } - object RemoteActorSpecActorAsyncSender { - val latch = new CountDownLatch(1) - } - class RemoteActorSpecActorAsyncSender extends Actor { - + class RemoteActorSpecActorAsyncSender(latch: CountDownLatch) extends Actor { def receive = { case Send(actor: ActorRef) => actor ! "Hello" - case "World" => - RemoteActorSpecActorAsyncSender.latch.countDown + case "World" => latch.countDown } } } -@RunWith(classOf[JUnitRunner]) -class ServerInitiatedRemoteActorSpec extends - WordSpec with - MustMatchers with - BeforeAndAfterAll with - BeforeAndAfterEach { +class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { import ServerInitiatedRemoteActorSpec._ - import ActorRegistry.remote - private val unit = TimeUnit.MILLISECONDS - val (host, port) = (remote.hostname,remote.port) - - var optimizeLocal_? = remote.asInstanceOf[NettyRemoteSupport].optimizeLocalScoped_? - - override def beforeAll() { - remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(false) //Can't run the test if we're eliminating all remote calls - remote.start() - } - - override def afterAll() { - remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(optimizeLocal_?) //Reset optimizelocal after all tests - } - - override def afterEach() { - ActorRegistry.shutdownAll - super.afterEach - } "Server-managed remote actors" should { "sendWithBang" in { @@ -102,9 +65,10 @@ class ServerInitiatedRemoteActorSpec extends implicit val timeout = 500000000L val actor = remote.actorFor( "akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorBidirectional", timeout,host, port) - val sender = actorOf[RemoteActorSpecActorAsyncSender].start + val latch = new CountDownLatch(1) + val sender = actorOf( new RemoteActorSpecActorAsyncSender(latch) ).start sender ! Send(actor) - RemoteActorSpecActorAsyncSender.latch.await(1, TimeUnit.SECONDS) must be (true) + latch.await(1, TimeUnit.SECONDS) must be (true) } "sendWithBangBangAndReplyWithException" in { diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala index 982fc3e642..80ba9e0ef8 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala @@ -5,22 +5,19 @@ package akka.actor.remote import org.scalatest._ -import org.scalatest.matchers.ShouldMatchers -import org.scalatest.BeforeAndAfterAll +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.junit.JUnitRunner import org.junit.runner.RunWith import java.util.concurrent.TimeUnit -import akka.remote.{RemoteServer, RemoteClient} import akka.actor._ import akka.actor.Actor._ -import RemoteTypedActorLog._ +import akka.remote.NettyRemoteSupport object ServerInitiatedRemoteSessionActorSpec { - val HOSTNAME = "localhost" - val PORT = 9990 - var server: RemoteServer = null case class Login(user:String) case class GetUser() @@ -52,111 +49,72 @@ object ServerInitiatedRemoteSessionActorSpec { } -@RunWith(classOf[JUnitRunner]) -class ServerInitiatedRemoteSessionActorSpec extends - FlatSpec with - ShouldMatchers with - BeforeAndAfterEach { +class ServerInitiatedRemoteSessionActorSpec extends AkkaRemoteTest { import ServerInitiatedRemoteSessionActorSpec._ - private val unit = TimeUnit.MILLISECONDS + "A remote session Actor" should { + "create a new session actor per connection" in { + + val session1 = remote.actorFor("untyped-session-actor-service", 5000L, host, port) + + val default1 = session1 !! GetUser() + default1.as[String].get must equal ("anonymous") + + session1 ! Login("session[1]") + val result1 = session1 !! GetUser() + result1.as[String].get must equal ("session[1]") + + session1.stop + + remote.shutdownClientModule + + val session2 = remote.actorFor("untyped-session-actor-service", 5000L, host, port) + + // since this is a new session, the server should reset the state + val default2 = session2 !! GetUser() + default2.as[String].get must equal ("anonymous") + + session2.stop() + } + + /*"stop the actor when the client disconnects" in { + val session1 = RemoteClient.actorFor( + "untyped-session-actor-service", + 5000L, + HOSTNAME, PORT) - override def beforeEach = { - server = new RemoteServer() - server.start(HOSTNAME, PORT) + val default1 = session1 !! GetUser() + default1.get.asInstanceOf[String] should equal ("anonymous") - server.registerPerSession("untyped-session-actor-service", actorOf[RemoteStatefullSessionActorSpec]) + instantiatedSessionActors should have size (1) - Thread.sleep(1000) - } - - // make sure the servers shutdown cleanly after the test has finished - override def afterEach = { - try { - server.shutdown RemoteClient.shutdownAll Thread.sleep(1000) - } catch { - case e => () + instantiatedSessionActors should have size (0) } - } - "A remote session Actor" should "create a new session actor per connection" in { - clearMessageLogs - - val session1 = RemoteClient.actorFor( - "untyped-session-actor-service", - 5000L, - HOSTNAME, PORT) - - val default1 = session1 !! GetUser() - default1.get.asInstanceOf[String] should equal ("anonymous") - session1 ! Login("session[1]") - val result1 = session1 !! GetUser() - result1.get.asInstanceOf[String] should equal ("session[1]") - - session1.stop() - - RemoteClient.shutdownAll - - //RemoteClient.clientFor(HOSTNAME, PORT).connect - - val session2 = RemoteClient.actorFor( - "untyped-session-actor-service", - 5000L, - HOSTNAME, PORT) - - // since this is a new session, the server should reset the state - val default2 = session2 !! GetUser() - default2.get.asInstanceOf[String] should equal ("anonymous") - - session2.stop() - - } - - it should "stop the actor when the client disconnects" in { - - val session1 = RemoteClient.actorFor( - "untyped-session-actor-service", - 5000L, - HOSTNAME, PORT) + "stop the actor when there is an error" in { + val session1 = RemoteClient.actorFor( + "untyped-session-actor-service", + 5000L, + HOSTNAME, PORT) - val default1 = session1 !! GetUser() - default1.get.asInstanceOf[String] should equal ("anonymous") + session1 ! DoSomethingFunny() + session1.stop() - instantiatedSessionActors should have size (1) + Thread.sleep(1000) - RemoteClient.shutdownAll - Thread.sleep(1000) - instantiatedSessionActors should have size (0) + instantiatedSessionActors should have size (0) + } - } - - it should "stop the actor when there is an error" in { - - val session1 = RemoteClient.actorFor( - "untyped-session-actor-service", - 5000L, - HOSTNAME, PORT) - - - session1 ! DoSomethingFunny() - session1.stop() - - Thread.sleep(1000) - - instantiatedSessionActors should have size (0) - } - - - it should "be able to unregister" in { + "be able to unregister" in { server.registerPerSession("my-service-1", actorOf[RemoteStatefullSessionActorSpec]) server.actorsFactories.get("my-service-1") should not be (null) server.unregisterPerSession("my-service-1") server.actorsFactories.get("my-service-1") should be (null) + } */ } - } diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala index c918c7e842..d504d7c4e3 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala @@ -2,6 +2,7 @@ * Copyright (C) 2009-2010 Scalable Solutions AB */ +/* THIS SHOULD BE UNCOMMENTED package akka.actor.remote import org.scalatest.Spec @@ -133,5 +134,5 @@ class ServerInitiatedRemoteTypedActorSpec extends } } -} +} */ diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedSessionActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedSessionActorSpec.scala index 0308ea53b3..0217734745 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedSessionActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedSessionActorSpec.scala @@ -2,6 +2,7 @@ * Copyright (C) 2009-2010 Scalable Solutions AB */ +/* THIS SHOULD BE UNCOMMENTED package akka.actor.remote import org.scalatest._ @@ -104,5 +105,5 @@ class ServerInitiatedRemoteTypedSessionActorSpec extends server.typedActorsFactories.get("my-service-1") should be (null) } -} +}*/ diff --git a/akka-remote/src/test/scala/remote/ShutdownSpec.scala b/akka-remote/src/test/scala/remote/ShutdownSpec.scala index 3e11ac1c5f..81d6a608ad 100644 --- a/akka-remote/src/test/scala/remote/ShutdownSpec.scala +++ b/akka-remote/src/test/scala/remote/ShutdownSpec.scala @@ -1,5 +1,5 @@ package akka.remote - +/* THIS SHOULD BE UNCOMMENTED import akka.actor.Actor import Actor._ @@ -37,3 +37,4 @@ object RemoteServerAndClusterShutdownRunner { s3.shutdown } } +*/ \ No newline at end of file diff --git a/akka-remote/src/test/scala/serialization/ProtobufActorMessageSerializationSpec.scala b/akka-remote/src/test/scala/serialization/ProtobufActorMessageSerializationSpec.scala index c6d8baed55..dfe39a6684 100644 --- a/akka-remote/src/test/scala/serialization/ProtobufActorMessageSerializationSpec.scala +++ b/akka-remote/src/test/scala/serialization/ProtobufActorMessageSerializationSpec.scala @@ -1,13 +1,18 @@ package akka.actor.serialization import java.util.concurrent.TimeUnit -import org.scalatest.junit.JUnitSuite -import org.junit.{Test, Before, After} +import org.scalatest._ +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} +import org.scalatest.junit.JUnitRunner +import org.junit.runner.RunWith -import akka.remote.{RemoteServer, RemoteClient} import akka.actor.{ProtobufProtocol, Actor} import ProtobufProtocol.ProtobufPOJO import Actor._ +import akka.remote.NettyRemoteSupport +import akka.actor.remote.AkkaRemoteTest /* --------------------------- Uses this Protobuf message: @@ -20,11 +25,6 @@ message ProtobufPOJO { --------------------------- */ object ProtobufActorMessageSerializationSpec { - val unit = TimeUnit.MILLISECONDS - val HOSTNAME = "localhost" - val PORT = 9990 - var server: RemoteServer = null - class RemoteActorSpecActorBidirectional extends Actor { def receive = { case pojo: ProtobufPOJO => @@ -36,35 +36,15 @@ object ProtobufActorMessageSerializationSpec { } } -class ProtobufActorMessageSerializationSpec extends JUnitSuite { +class ProtobufActorMessageSerializationSpec extends AkkaRemoteTest { import ProtobufActorMessageSerializationSpec._ - @Before - def init() { - server = new RemoteServer - server.start(HOSTNAME, PORT) - server.register("RemoteActorSpecActorBidirectional", actorOf[RemoteActorSpecActorBidirectional]) - Thread.sleep(1000) - } - - // make sure the servers postStop cleanly after the test has finished - @After - def finished() { - server.shutdown - RemoteClient.shutdownAll - Thread.sleep(1000) - } - - @Test - def shouldSendReplyAsync { - val actor = RemoteClient.actorFor("RemoteActorSpecActorBidirectional", 5000L, HOSTNAME, PORT) - val result = actor !! ProtobufPOJO.newBuilder - .setId(11) - .setStatus(true) - .setName("Coltrane") - .build - assert(12L === result.get.asInstanceOf[Long]) - actor.stop + "A ProtobufMessage" should { + "SendReplyAsync" in { + val actor = remote.actorFor("RemoteActorSpecActorBidirectional", 5000L, host, port) + val result = actor !! ProtobufPOJO.newBuilder.setId(11).setStatus(true).setName("Coltrane").build + result.as[Long].get must be (12) + } } } diff --git a/akka-remote/src/test/scala/serialization/TypedActorSerializationSpec.scala b/akka-remote/src/test/scala/serialization/TypedActorSerializationSpec.scala index 15a7fa3601..9333736821 100644 --- a/akka-remote/src/test/scala/serialization/TypedActorSerializationSpec.scala +++ b/akka-remote/src/test/scala/serialization/TypedActorSerializationSpec.scala @@ -1,6 +1,8 @@ /** * Copyright (C) 2009-2010 Scalable Solutions AB */ + +/* THIS SHOULD BE UNCOMMENTED package akka.actor.serialization import org.scalatest.Spec @@ -164,3 +166,5 @@ class MyStatelessTypedActorImpl extends TypedActor with MyTypedActor { if (message == "hello") "world" else ("hello " + message) } } + +*/ \ No newline at end of file diff --git a/akka-remote/src/test/scala/ticket/Ticket434Spec.scala b/akka-remote/src/test/scala/ticket/Ticket434Spec.scala index 75854b4e04..f69b284d92 100644 --- a/akka-remote/src/test/scala/ticket/Ticket434Spec.scala +++ b/akka-remote/src/test/scala/ticket/Ticket434Spec.scala @@ -3,49 +3,39 @@ */ package akka.actor.ticket -import org.scalatest.Spec -import org.scalatest.matchers.ShouldMatchers + import akka.actor.Actor._ import akka.actor.{Uuid,newUuid,uuidFrom} import akka.actor.remote.ServerInitiatedRemoteActorSpec.RemoteActorSpecActorUnidirectional -import java.util.concurrent.TimeUnit -import akka.remote.{RemoteClient, RemoteServer} import akka.remote.protocol.RemoteProtocol._ +import akka.actor.remote.AkkaRemoteTest +import java.util.concurrent.CountDownLatch +class Ticket434Spec extends AkkaRemoteTest { + "A server managed remote actor" should { + "can use a custom service name containing ':'" in { + val latch = new CountDownLatch(1) + implicit val sender = replyHandler(latch,"Pong") + remote.register("my:service", actorOf[RemoteActorSpecActorUnidirectional]) -class Ticket434Spec extends Spec with ShouldMatchers { + val actor = remote.actorFor("my:service", 5000L, host, port) - val HOSTNAME = "localhost" - val PORT = 9991 + actor ! "Ping" - describe("A server managed remote actor") { - it("can use a custom service name containing ':'") { - val server = new RemoteServer().start(HOSTNAME, PORT) - server.register("my:service", actorOf[RemoteActorSpecActorUnidirectional]) - - val actor = RemoteClient.actorFor("my:service", 5000L, HOSTNAME, PORT) - actor ! "OneWay" - - assert(RemoteActorSpecActorUnidirectional.latch.await(1, TimeUnit.SECONDS)) - actor.stop - - server.shutdown - RemoteClient.shutdownAll + latch.await(1, unit) must be (true) } - } - describe("The ActorInfoProtocol") { - it("should be possible to set the acor id and uuuid") { + "should be possible to set the acor id and uuuid" in { val uuid = newUuid - val actorInfoBuilder = ActorInfoProtocol.newBuilder + val actorInfo = ActorInfoProtocol.newBuilder .setUuid(UuidProtocol.newBuilder.setHigh(uuid.getTime).setLow(uuid.getClockSeqAndNode).build) .setId("some-id") .setTarget("actorClassName") .setTimeout(5000L) - .setActorType(ActorType.SCALA_ACTOR) - val actorInfo = actorInfoBuilder.build - assert(uuidFrom(actorInfo.getUuid.getHigh,actorInfo.getUuid.getLow) === uuid) - assert(actorInfo.getId === "some-id") + .setActorType(ActorType.SCALA_ACTOR).build + + uuidFrom(actorInfo.getUuid.getHigh,actorInfo.getUuid.getLow) must equal (uuid) + actorInfo.getId must equal ("some-id") } } } diff --git a/akka-remote/src/test/scala/ticket/Ticket506Spec.scala b/akka-remote/src/test/scala/ticket/Ticket506Spec.scala index e82d4c5efb..cd58c4a6bd 100644 --- a/akka-remote/src/test/scala/ticket/Ticket506Spec.scala +++ b/akka-remote/src/test/scala/ticket/Ticket506Spec.scala @@ -1,61 +1,43 @@ package ticket -import org.scalatest.Spec -import org.scalatest.matchers.ShouldMatchers - -import akka.remote.{RemoteClient, RemoteNode, RemoteServer} import akka.actor.{Actor, ActorRef} import akka.serialization.RemoteActorSerialization import akka.actor.Actor.actorOf import java.util.concurrent.{CountDownLatch, TimeUnit} - -object State { - val latch = new CountDownLatch(1) -} +import akka.actor.remote.AkkaRemoteTest case class RecvActorRef(bytes:Array[Byte]) -class ActorRefService extends Actor { +class ActorRefService(latch: CountDownLatch) extends Actor { import self._ def receive:Receive = { case RecvActorRef(bytes) => val ref = RemoteActorSerialization.fromBinaryToRemoteActorRef(bytes) ref ! "hello" - case "hello" => - State.latch.countDown + case "hello" => latch.countDown } } -class Ticket506Spec extends Spec with ShouldMatchers { - val hostname:String = "localhost" - val port:Int = 9440 +class Ticket506Spec extends AkkaRemoteTest { + "a RemoteActorRef serialized" should { + "should be remotely usable" in { - describe("a RemoteActorRef serialized") { - it("should be remotely usable") { - val s1,s2 = new RemoteServer - s1.start(hostname, port) - s2.start(hostname, port + 1) + val latch = new CountDownLatch(1) + val a1 = actorOf( new ActorRefService(null)) + val a2 = actorOf( new ActorRefService(latch)) - val a1,a2 = actorOf[ActorRefService] - a1.homeAddress = (hostname, port) - a2.homeAddress = (hostname, port+1) - - s1.register("service", a1) - s2.register("service", a2) + remote.register("service1", a1) + remote.register("service2", a2) // connect to the first server/service - val c1 = RemoteClient.actorFor("service", hostname, port) + val c1 = remote.actorFor("service1", host, port) val bytes = RemoteActorSerialization.toRemoteActorRefProtocol(a2).toByteArray c1 ! RecvActorRef(bytes) - State.latch.await(1000, TimeUnit.MILLISECONDS) should be(true) - - RemoteClient.shutdownAll - s1.shutdown - s2.shutdown + latch.await(1, unit) must be(true) } } } diff --git a/akka-remote/src/test/scala/ticket/Ticket519Spec.scala b/akka-remote/src/test/scala/ticket/Ticket519Spec.scala index 8457f10f45..5af963d2ff 100644 --- a/akka-remote/src/test/scala/ticket/Ticket519Spec.scala +++ b/akka-remote/src/test/scala/ticket/Ticket519Spec.scala @@ -3,28 +3,17 @@ */ package akka.actor.ticket -import org.scalatest.Spec -import org.scalatest.matchers.ShouldMatchers -import akka.remote.{RemoteClient, RemoteServer} import akka.actor._ +import akka.actor.remote.AkkaRemoteTest -class Ticket519Spec extends Spec with ShouldMatchers { - - val HOSTNAME = "localhost" - val PORT = 6666 - - describe("A remote TypedActor") { - it("should handle remote future replies") { - import akka.remote._ - - val server = { val s = new RemoteServer; s.start(HOSTNAME,PORT); s} - val actor = TypedActor.newRemoteInstance(classOf[SamplePojo], classOf[SamplePojoImpl],7000,HOSTNAME,PORT) +class Ticket519Spec extends AkkaRemoteTest { + "A remote TypedActor" should { + "should handle remote future replies" in { + val actor = TypedActor.newRemoteInstance(classOf[SamplePojo], classOf[SamplePojoImpl],7000,host,port) val r = actor.someFutureString - r.await.result.get should equal ("foo") - TypedActor.stop(actor) - server.shutdown + r.await.result.get must equal ("foo") } } } From 6920464e8278f4af14d99ae6be801b9f29f04547 Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Sat, 18 Dec 2010 21:50:51 -0700 Subject: [PATCH 08/38] Backport from master, add new Configgy version with logging removed --- .../src/main/scala/akka/actor/Actor.scala | 12 --- .../configgy-2.0.2-SNAPSHOT.jar | Bin 0 -> 211233 bytes .../configgy-2.0.2-SNAPSHOT.pom | 80 ++++++++++++++++++ project/build/AkkaProject.scala | 4 +- 4 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 embedded-repo/net/lag/configgy/2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.jar create mode 100644 embedded-repo/net/lag/configgy/2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.pom diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index c87d1460f9..0d6b4fee77 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -109,18 +109,6 @@ object Actor extends Logging { tf.setAccessible(true) val subclassAudits = tf.get(null).asInstanceOf[java.util.Map[_,_]] subclassAudits.synchronized {subclassAudits.clear} - - // Clear and reset j.u.l.Level.known (due to Configgy) - log.slf4j.info("Removing Configgy-installed log levels") - import java.util.logging.Level - val lf = classOf[Level].getDeclaredField("known") - lf.setAccessible(true) - val known = lf.get(null).asInstanceOf[java.util.ArrayList[Level]] - known.synchronized { - known.clear - List(Level.OFF,Level.SEVERE,Level.WARNING,Level.INFO,Level.CONFIG, - Level.FINE,Level.FINER,Level.FINEST,Level.ALL) foreach known.add - } } } Runtime.getRuntime.addShutdownHook(new Thread(hook)) diff --git a/embedded-repo/net/lag/configgy/2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.jar b/embedded-repo/net/lag/configgy/2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..5a87c474d2203190b102e96734053395514b1e35 GIT binary patch literal 211233 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�fm^>^}>S9UHRB%AaF|7e-++s=6}Yx4V?ACF|u zOgPHDSnTPPxZaX1m5PZA`sQ@;UpnNpajV;YZ^=D9?*ngdlG9aqNq}shpwxNhz(aw%b)!=Ldgq$$bAe@>;Io* zaLq3(!<+wMS#gBFsYtr*#opEXZSDHm7Y1;8Jhu`PR5C3{V~xGiv25~|#o3cf)W3<{ zx4W}@R>Xpx<*TQwi)Kq^&&ca}^E~iq)<&sianiRx>v?CEBrgJl{mNJz#Ib_3PsKc|5nQ4c8hMzY;5qnV!1k%)c*Jetg{I z=_fP&y7uy~Q{S|wKi#_d*hAqG%~!MXY*KlbESgd);`v^ux;3jj_gS?cFmCRgN()>NV~P zu#@?*@lCg~q~}$?XTKfKOio*GuGLhft&G%~B2*0xB$VqX;X z%6i4z6}QzgvKS?%{*FKJXN?JO&y#h37AO^G>GujuXw*3nAl=@XC?@mBbVkVXc}ujl zPHJpksv__BP#u&URhtheS~D>)d}G3w98GX1NA#(Zvc#OyR23uWxG{8UL|GTKEK~F& zXGqKz{uD<0ZHk5*oJ$;irXKl`xvQ`AMA^2R?1J@EU0Pj)|7JYuH@Y{|%t>YCrjvQ) zY3FRe->H80?fH8B`wVLy2s$s6T{cnQELA~b&ewpw6Ys{#9aW0zs+B8fi@D6dWYS5g z!x2k&9iI@%A1}PaL*it!Eok+M{U@e)XB>UCqC8LTckv+3yF4HHC@f=h69f6>H;il#*y7VP{Nt-3SemYVbWT(`J=*Nt@7h=iYD5$9RGqh)qgPvfG+ z?VWb(dFJ+BoP26wXs5uhQ_t*^9-Y`TZA8t*zSk z|Hm(02C)Z9PDPhrmqmT6eSW}A;?A3>y48;(%F^%U&tz9~+VgbhZa-@$1Wk?X#z7gH#eBJaBqHEe;`Tw9^XcuE3(()UdsucPQJ`lss7L6viFnJ2Nmy> ze_Jq~6e;NNe9C=zt1f5c5e9fwY9rNRT4~2t((~- zDB$YF7?b{h>A9Z`)&9B<0n4~o1?{MBK zKEM9W#a#=fC90+3^+dl)c;DIc<$&`U)eQ>^4+h-|T`4^$mCNb!^Q+R;m4#~*cBjc6 zdp%1{$fmY>g~|V%jjKJc&f3%cbB{*4>EmnXP9-3FG@lb}T0xqx7paA)8ZSCgGV|q> zAFAtS?cDly_1*2fq4PcGs>dB!G;v)3YsAFVli7PtgiMKfc4n&T`n#*|?=23FwAHQ5 z{JOAhW}ThFeVNp4`qBClwF`UKNn5m+UH&LL$*(+C-@v)lv;L{ePLGY3^E8{?WMddw zxC~CT$v5oFHOr~_WY`rvdGg!Ep&d@27JU-D^ggpm;pDN!4{A3t8Kpb0-=E7E+VPlW zm0-Ta)T!quEMEAJuimAMMeoal)hy4Suxp9iJMJ?2#mS}}pYVve^@hI8

$k1xEr| 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 w$eNNZ%>Vt^FAZhJ$z|3v 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

43;G<$r;9j_7W9;@o8{e0-)_kITxPf6OI(MXUo? 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 AU;qFB literal 0 HcmV?d00001 diff --git a/embedded-repo/net/lag/configgy/2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.pom b/embedded-repo/net/lag/configgy/2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.pom new file mode 100644 index 0000000000..17804b0658 --- /dev/null +++ b/embedded-repo/net/lag/configgy/2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.pom @@ -0,0 +1,80 @@ + + + 4.0.0 + net.lag + configgy + jar + 2.0.2-SNAPSHOT + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + org.scala-lang + scala-library + 2.8.1 + compile + + + + + oldtwittercom + old.twitter.com + http://www.lag.net/nest/ + + + twittercom + twitter.com + http://maven.twttr.com/ + + + lagnet + lag.net + http://www.lag.net/repo/ + + + downloadjavanet + download.java.net + http://download.java.net/maven/2/ + + + oauthnet + oauth.net + http://oauth.googlecode.com/svn/code/maven/ + + + powermockapi + powermock-api + http://powermock.googlecode.com/svn/repo/ + + + ibiblio + ibiblio + http://mirrors.ibiblio.org/pub/mirrors/maven2/ + + + scalatoolsorg + scala-tools.org + http://scala-tools.org/repo-releases/ + + + testingscalatoolsorg + testing.scala-tools.org + http://scala-tools.org/repo-releases/testing/ + + + atlassian + atlassian + https://m2proxy.atlassian.com/repository/public/ + + + ScalaToolsMaven2Repository + Scala-Tools Maven2 Repository + http://scala-tools.org/repo-releases/ + + + \ No newline at end of file diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index 9356f5504e..fe148cf27f 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -139,7 +139,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val commons_pool = "commons-pool" % "commons-pool" % "1.5.4" % "compile" //ApacheV2 - lazy val configgy = "net.lag" % "configgy" % "2.8.0-1.5.5" % "compile" //ApacheV2 + lazy val configgy = "net.lag" % "configgy" % "2.0.2-SNAPSHOT" % "compile" //ApacheV2 lazy val dispatch_http = "net.databinder" % "dispatch-http_2.8.0" % DISPATCH_VERSION % "compile" //LGPL v2 lazy val dispatch_json = "net.databinder" % "dispatch-json_2.8.0" % DISPATCH_VERSION % "compile" //LGPL v2 @@ -538,4 +538,4 @@ trait McPom { self: DefaultProject => rewrite(rule)(node.theSeq)(0) } -} \ No newline at end of file +} From 9547d261168b721e9b70ad81e021b88b8e43de49 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Mon, 20 Dec 2010 11:11:52 +1300 Subject: [PATCH 09/38] Refine transactor doNothing (fixes #582) --- .../scala/akka/transactor/Transactor.scala | 5 +++- .../scala/transactor/TransactorSpec.scala | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/akka-stm/src/main/scala/akka/transactor/Transactor.scala b/akka-stm/src/main/scala/akka/transactor/Transactor.scala index 72ba139d85..98871e42a4 100644 --- a/akka-stm/src/main/scala/akka/transactor/Transactor.scala +++ b/akka-stm/src/main/scala/akka/transactor/Transactor.scala @@ -175,5 +175,8 @@ trait Transactor extends Actor { /** * Default catch-all for the different Receive methods. */ - def doNothing: Receive = { case _ => } + def doNothing: Receive = new Receive { + def apply(any: Any) = {} + def isDefinedAt(any: Any) = false + } } diff --git a/akka-stm/src/test/scala/transactor/TransactorSpec.scala b/akka-stm/src/test/scala/transactor/TransactorSpec.scala index c329672486..f10a0e1d2c 100644 --- a/akka-stm/src/test/scala/transactor/TransactorSpec.scala +++ b/akka-stm/src/test/scala/transactor/TransactorSpec.scala @@ -59,8 +59,22 @@ object TransactorIncrement { } } +object SimpleTransactor { + case class Set(ref: Ref[Int], value: Int, latch: CountDownLatch) + + class Setter extends Transactor { + def atomically = { + case Set(ref, value, latch) => { + ref.set(value) + latch.countDown + } + } + } +} + class TransactorSpec extends WordSpec with MustMatchers { import TransactorIncrement._ + import SimpleTransactor._ val numCounters = 5 val timeout = 5 seconds @@ -97,4 +111,17 @@ class TransactorSpec extends WordSpec with MustMatchers { failer.stop } } + + "Transactor" should { + "be usable without overriding normally" in { + val transactor = Actor.actorOf(new Setter).start + val ref = Ref(0) + val latch = new CountDownLatch(1) + transactor ! Set(ref, 5, latch) + latch.await(timeout.length, timeout.unit) + val value = atomic { ref.get } + value must be === 5 + transactor.stop + } + } } From 65a55a71404e2368292f312d48689e1f02b9a843 Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Sun, 19 Dec 2010 16:42:00 -0700 Subject: [PATCH 10/38] Give modified configgy a unique version and add a link to the source repository --- .../configgy-2.0.2-nologgy.jar} | Bin 211233 -> 211225 bytes .../configgy-2.0.2-nologgy.pom} | 65 +++++++++--------- project/build/AkkaProject.scala | 2 +- 3 files changed, 35 insertions(+), 32 deletions(-) rename embedded-repo/net/lag/configgy/{2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.jar => 2.0.2-nologgy/configgy-2.0.2-nologgy.jar} (98%) rename embedded-repo/net/lag/configgy/{2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.pom => 2.0.2-nologgy/configgy-2.0.2-nologgy.pom} (94%) diff --git a/embedded-repo/net/lag/configgy/2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.jar b/embedded-repo/net/lag/configgy/2.0.2-nologgy/configgy-2.0.2-nologgy.jar similarity index 98% rename from embedded-repo/net/lag/configgy/2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.jar rename to embedded-repo/net/lag/configgy/2.0.2-nologgy/configgy-2.0.2-nologgy.jar index 5a87c474d2203190b102e96734053395514b1e35..8a6a0a0e5bd915a144084483e6235b17294f4185 100644 GIT binary patch delta 921 zcmZ4ZiD%{~9^L?NW)=|!4h9Z}Y0Z-xd0TlIxAHO_DP*47JbAjl6rP^}VOL znhgY8zyIuN$WBc=6I^gTH@qx?}0@Z_Un6X|6Xt|*Twj)>%E{UFH~nW-dRvp zIbUuPgQHC5?#K1rYink0>NshYc6J(vaMQJe!TD1e zS&w{sKlS>oW1IV0_PILyeSIXAtUKp?vwQ%LH#^5pCaVke3=9m@CpO4#-(JMj(aJon zdGdCrA53)yyi=Pe+rkx0KdZrRyW6I3)Mgf*epi`U zW;$Oxlf-l$ZDuW|N1YHY4`i6Rr{`!hJ2QGu-=NJL!L)Y*MEwG3W|8UFwVB1Id+0Dr zF>jyDH2uCfqu6wT3ry_On{=2BnTn@Ej5sirNp^a_6tlo|Ze3<6rlJ`T;Q(D`C#Klh z5RN<_Gxzipy3B^mlJmeW7oTo$jfrb|{5&SX>3Vw1T1+z+LR1FmGILMw(_?mK+Pq}C zqcOAcbZ$9j@#(z!%u>t=%bBM8DKbk|o-ao(U3W+6@XO$?5jHnK-8JkYyH_e$0?r ziqUWSLqp~aCci@vv%nGJz{D&*eW4Mv6qDCckS5{j7mS$m8QG>g7&AvPUp(+*zTbpdi^=;I$Uvp(yr#@XjEko`m@)@2Zkj&P zlz9r%-Nzt9%%{hhF*`EyPhVihoWbPr3gVakS4`s5BUzYvrU#fa%Q4x$1t|q5HSXz4 z%$bdtSUx}${x)ZJW_te#WC_^S_R|fRm<6VLi!h5%-)X@t#Z>kcqV=Bzvn-qT4<-f% F1^{pGHy8i_ delta 915 zcmbRFiD%&_9^L?NW)=|!4h9Ye^?4H;d0TlIxAHO_DP&TcH~HgK<>{|Am<8%}drosT z8wj|5|Jl`$otoC8CKw~)^>P`1$M2*^8jWF!OP&7jTPyPJmCU)bE@d8p?<_s82<0x$ zXE9{%k|vhS?fBs$@t)YO&Ly(~6U7_Pq9 z%g%GF#$9>C|MKGh55AT6w^+Ha)|qr8K{Vo-q2J}`8ioLGc8+J+;Zxcf7#L?f) zzKE%#m05k>#O+o;nCc98)#gpKg)5kTRD(HzX>QTeL|X^g_tw`($cFn*b) z#q7y+tQjIRpMzO!y1gzF`}C8X%mULFYB5VO9d4VhsLU)p{el*A9Mh{#s6r8D+3984 z%!W+Cy&!3s$p^}~rk~elHe@_Hol%E5l6n7Rrs;+}%wp4DO=jYoK7)l>VERNIW+|rX zsZbL@Ry9a5i%)0PWtL*9ngJ1hAj&K_JyMt1kSTRGL}Z>mGxziZAQ8oR(;2mxB_`K< z@=TAM$HYJV-XA7`>1KM&Qj80y2kJ3bFzsG4y-=N5dHOp&W<$n|=?ePH!Az2?;pW&d z-ChgfNUUQrn4ZbbEHK^GfLV(1;PeCo=2Rx%tq|D{2F$ihygMLf?UQ5Tm>y-wY{B|h6jhMprgVbtGpJmC!GhM)l*^nvh5J*IGIw%k)->YMvZqLLlFny*GvlLU% zQIJmI=?DHZiA-IS=Pofx zPLDQW)?)f|6&wQ7=bJD)F$LcO843z{w&|><%tlPB?|=kErt|ePa!wY{o3b^z<>vE~roRpD;;G&oyJ#ViJFjBz)eC*^n_{ zI-@yrDwFG5kc8&+2cpc}(|g}CaZKluV-}dc#hh7+iRS~vOJUxOLep6-m<^e}e?qf8 o_cIgwbT1KRf$1A9n5CHNzJg7gEccsj`X38saW>;0ObiSR0H?7tB>(^b diff --git a/embedded-repo/net/lag/configgy/2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.pom b/embedded-repo/net/lag/configgy/2.0.2-nologgy/configgy-2.0.2-nologgy.pom similarity index 94% rename from embedded-repo/net/lag/configgy/2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.pom rename to embedded-repo/net/lag/configgy/2.0.2-nologgy/configgy-2.0.2-nologgy.pom index 17804b0658..9238472de5 100644 --- a/embedded-repo/net/lag/configgy/2.0.2-SNAPSHOT/configgy-2.0.2-SNAPSHOT.pom +++ b/embedded-repo/net/lag/configgy/2.0.2-nologgy/configgy-2.0.2-nologgy.pom @@ -4,7 +4,10 @@ net.lag configgy jar - 2.0.2-SNAPSHOT + 2.0.2-nologgy + Configgy + Configgy logging removed + http://github.com/derekjw/configgy Apache 2 @@ -21,6 +24,36 @@ + + scalatoolsorg + scala-tools.org + http://scala-tools.org/repo-releases/ + + + atlassian + atlassian + https://m2proxy.atlassian.com/repository/public/ + + + lagnet + lag.net + http://www.lag.net/repo/ + + + testingscalatoolsorg + testing.scala-tools.org + http://scala-tools.org/repo-releases/testing/ + + + oauthnet + oauth.net + http://oauth.googlecode.com/svn/code/maven/ + + + downloadjavanet + download.java.net + http://download.java.net/maven/2/ + oldtwittercom old.twitter.com @@ -31,21 +64,6 @@ twitter.com http://maven.twttr.com/ - - lagnet - lag.net - http://www.lag.net/repo/ - - - downloadjavanet - download.java.net - http://download.java.net/maven/2/ - - - oauthnet - oauth.net - http://oauth.googlecode.com/svn/code/maven/ - powermockapi powermock-api @@ -56,21 +74,6 @@ ibiblio http://mirrors.ibiblio.org/pub/mirrors/maven2/ - - scalatoolsorg - scala-tools.org - http://scala-tools.org/repo-releases/ - - - testingscalatoolsorg - testing.scala-tools.org - http://scala-tools.org/repo-releases/testing/ - - - atlassian - atlassian - https://m2proxy.atlassian.com/repository/public/ - ScalaToolsMaven2Repository Scala-Tools Maven2 Repository diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index fe148cf27f..886e9cd407 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -139,7 +139,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val commons_pool = "commons-pool" % "commons-pool" % "1.5.4" % "compile" //ApacheV2 - lazy val configgy = "net.lag" % "configgy" % "2.0.2-SNAPSHOT" % "compile" //ApacheV2 + lazy val configgy = "net.lag" % "configgy" % "2.0.2-nologgy" % "compile" //ApacheV2 lazy val dispatch_http = "net.databinder" % "dispatch-http_2.8.0" % DISPATCH_VERSION % "compile" //LGPL v2 lazy val dispatch_json = "net.databinder" % "dispatch-json_2.8.0" % DISPATCH_VERSION % "compile" //LGPL v2 From 04a7a072684483c0db9d17e5d75e74553de26bf8 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 20 Dec 2010 10:31:08 +0100 Subject: [PATCH 11/38] Adding UnparsableException and make sure that non-recreateable exceptions dont mess up the pipeline --- .../main/scala/akka/remote/RemoteClient.scala | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/akka-remote/src/main/scala/akka/remote/RemoteClient.scala b/akka-remote/src/main/scala/akka/remote/RemoteClient.scala index 2b137d8788..309521fc81 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteClient.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteClient.scala @@ -56,6 +56,8 @@ case class RemoteClientShutdown( */ class RemoteClientException private[akka] (message: String, @BeanProperty val client: RemoteClient) extends AkkaException(message) +case class UnparsableException private[akka] (val originalClassName: String, val originalMessage: String) extends AkkaException(originalMessage) + /** * The RemoteClient object manages RemoteClient instances and gives you an API to lookup remote actor handles. * @@ -506,10 +508,16 @@ class RemoteClientHandler( private def parseException(reply: RemoteMessageProtocol, loader: Option[ClassLoader]): Throwable = { val exception = reply.getException val classname = exception.getClassname - val exceptionClass = if (loader.isDefined) loader.get.loadClass(classname) - else Class.forName(classname) - exceptionClass - .getConstructor(Array[Class[_]](classOf[String]): _*) - .newInstance(exception.getMessage).asInstanceOf[Throwable] + try { + val exceptionClass = if (loader.isDefined) loader.get.loadClass(classname) + else Class.forName(classname) + exceptionClass + .getConstructor(Array[Class[_]](classOf[String]): _*) + .newInstance(exception.getMessage).asInstanceOf[Throwable] + } catch { + case problem => + log.warn("Couldn't create instance of {} with message {}, returning UnparsableException",classname, exception.getMessage) + UnparsableException(classname, exception.getMessage) + } } } From 091bb419306cfcffce984d318a6397e8342aac51 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 20 Dec 2010 10:34:59 +0100 Subject: [PATCH 12/38] Adding debug log of parse exception in parseException --- akka-remote/src/main/scala/akka/remote/RemoteClient.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/akka-remote/src/main/scala/akka/remote/RemoteClient.scala b/akka-remote/src/main/scala/akka/remote/RemoteClient.scala index 309521fc81..3d90d0f417 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteClient.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteClient.scala @@ -516,6 +516,7 @@ class RemoteClientHandler( .newInstance(exception.getMessage).asInstanceOf[Throwable] } catch { case problem => + log.debug("Couldn't parse exception returned from RemoteServer",problem) log.warn("Couldn't create instance of {} with message {}, returning UnparsableException",classname, exception.getMessage) UnparsableException(classname, exception.getMessage) } From cb2054f1030c6b19cd38562dcc095cdcc08dcd6a Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 20 Dec 2010 11:00:55 +0100 Subject: [PATCH 13/38] Giving all remote messages their own uuid, reusing actorInfo.uuid for futures, closing ticket 580 --- .../main/scala/akka/remote/RemoteClient.scala | 53 ++++++++++--------- .../serialization/SerializationProtocol.scala | 5 +- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/akka-remote/src/main/scala/akka/remote/RemoteClient.scala b/akka-remote/src/main/scala/akka/remote/RemoteClient.scala index 3d90d0f417..45500b561f 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteClient.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteClient.scala @@ -56,7 +56,7 @@ case class RemoteClientShutdown( */ class RemoteClientException private[akka] (message: String, @BeanProperty val client: RemoteClient) extends AkkaException(message) -case class UnparsableException private[akka] (val originalClassName: String, val originalMessage: String) extends AkkaException(originalMessage) +case class UnparsableException private[akka] (originalClassName: String, originalMessage: String) extends AkkaException(originalMessage) /** * The RemoteClient object manages RemoteClient instances and gives you an API to lookup remote actor handles. @@ -416,32 +416,33 @@ class RemoteClientHandler( override def messageReceived(ctx: ChannelHandlerContext, event: MessageEvent) { try { - val result = event.getMessage - if (result.isInstanceOf[RemoteMessageProtocol]) { - val reply = result.asInstanceOf[RemoteMessageProtocol] - val replyUuid = uuidFrom(reply.getUuid.getHigh, reply.getUuid.getLow) - log.debug("Remote client received RemoteMessageProtocol[\n{}]",reply) - val future = futures.get(replyUuid).asInstanceOf[CompletableFuture[Any]] - if (reply.hasMessage) { - if (future eq null) throw new IllegalActorStateException("Future mapped to UUID " + replyUuid + " does not exist") - val message = MessageSerializer.deserialize(reply.getMessage) - future.completeWithResult(message) - } else { - if (reply.hasSupervisorUuid()) { - val supervisorUuid = uuidFrom(reply.getSupervisorUuid.getHigh, reply.getSupervisorUuid.getLow) - if (!supervisors.containsKey(supervisorUuid)) throw new IllegalActorStateException( - "Expected a registered supervisor for UUID [" + supervisorUuid + "] but none was found") - val supervisedActor = supervisors.get(supervisorUuid) - if (!supervisedActor.supervisor.isDefined) throw new IllegalActorStateException( - "Can't handle restart for remote actor " + supervisedActor + " since its supervisor has been removed") - else supervisedActor.supervisor.get ! Exit(supervisedActor, parseException(reply, client.loader)) + event.getMessage match { + case reply: RemoteMessageProtocol => + val replyUuid = uuidFrom(reply.getActorInfo.getUuid.getHigh, reply.getActorInfo.getUuid.getLow) + log.debug("Remote client received RemoteMessageProtocol[\n{}]",reply) + val future = futures.remove(replyUuid).asInstanceOf[CompletableFuture[Any]] + + if (reply.hasMessage) { + if (future eq null) throw new IllegalActorStateException("Future mapped to UUID " + replyUuid + " does not exist") + val message = MessageSerializer.deserialize(reply.getMessage) + future.completeWithResult(message) + } else { + val exception = parseException(reply, client.loader) + if (reply.hasSupervisorUuid()) { + val supervisorUuid = uuidFrom(reply.getSupervisorUuid.getHigh, reply.getSupervisorUuid.getLow) + if (!supervisors.containsKey(supervisorUuid)) throw new IllegalActorStateException( + "Expected a registered supervisor for UUID [" + supervisorUuid + "] but none was found") + val supervisedActor = supervisors.get(supervisorUuid) + if (!supervisedActor.supervisor.isDefined) throw new IllegalActorStateException( + "Can't handle restart for remote actor " + supervisedActor + " since its supervisor has been removed") + else supervisedActor.supervisor.get ! Exit(supervisedActor, exception) + } + + future.completeWithException(exception) } - val exception = parseException(reply, client.loader) - future.completeWithException(exception) - } - futures remove replyUuid - } else { - val exception = new RemoteClientException("Unknown message received in remote client handler: " + result, client) + + case message => + val exception = new RemoteClientException("Unknown message received in remote client handler: " + message, client) client.notifyListeners(RemoteClientError(exception, client)) throw exception } diff --git a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala index b93b472f51..d64e0552ab 100644 --- a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -295,7 +295,10 @@ object RemoteActorSerialization { } val actorInfo = actorInfoBuilder.build val messageBuilder = RemoteMessageProtocol.newBuilder - .setUuid(uuidProtocol) + .setUuid({ + val messageUuid = newUuid + UuidProtocol.newBuilder.setHigh(messageUuid.getTime).setLow(messageUuid.getClockSeqAndNode).build + }) .setActorInfo(actorInfo) .setOneWay(isOneWay) From 8f6074d2ea9817318d8897c26477a0e7e1c48d6f Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 20 Dec 2010 11:21:05 +0100 Subject: [PATCH 14/38] Making sure RemoteActorRef.loader is passed into RemoteClient, also adding volatile flag to classloader in Serializer to make sure changes are propagated crossthreads --- .../src/main/scala/akka/actor/ActorRef.scala | 4 ++-- .../scala/akka/util/ReflectiveAccess.scala | 5 +++-- .../scala/akka/remote/MessageSerializer.scala | 19 ++++++++++--------- .../scala/akka/serialization/Serializer.scala | 2 +- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index dbaf013705..a9b74e071f 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -1237,7 +1237,7 @@ private[akka] case class RemoteActorRef private[akka] ( def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = RemoteClientModule.send[Any]( - message, senderOption, None, remoteAddress.get, timeout, true, this, None, actorType) + message, senderOption, None, remoteAddress.get, timeout, true, this, None, actorType, loader) def postMessageToMailboxAndCreateFutureResultWithTimeout[T]( message: Any, @@ -1245,7 +1245,7 @@ private[akka] case class RemoteActorRef private[akka] ( senderOption: Option[ActorRef], senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { val future = RemoteClientModule.send[T]( - message, senderOption, senderFuture, remoteAddress.get, timeout, false, this, None, actorType) + message, senderOption, senderFuture, remoteAddress.get, timeout, false, this, None, actorType, loader) if (future.isDefined) future.get else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) } diff --git a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala index 45e1609b79..67efd575b6 100644 --- a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala +++ b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala @@ -92,9 +92,10 @@ object ReflectiveAccess extends Logging { isOneWay: Boolean, actorRef: ActorRef, typedActorInfo: Option[Tuple2[String, String]], - actorType: ActorType): Option[CompletableFuture[T]] = { + actorType: ActorType, + loader: Option[ClassLoader] = None): Option[CompletableFuture[T]] = { ensureEnabled - clientFor(remoteAddress.getHostName, remoteAddress.getPort, None).send[T]( + clientFor(remoteAddress.getHostName, remoteAddress.getPort, loader).send[T]( message, senderOption, senderFuture, remoteAddress, timeout, isOneWay, actorRef, typedActorInfo, actorType) } } diff --git a/akka-remote/src/main/scala/akka/remote/MessageSerializer.scala b/akka-remote/src/main/scala/akka/remote/MessageSerializer.scala index b68bb0bca6..aca0333582 100644 --- a/akka-remote/src/main/scala/akka/remote/MessageSerializer.scala +++ b/akka-remote/src/main/scala/akka/remote/MessageSerializer.scala @@ -11,17 +11,18 @@ import akka.util._ import com.google.protobuf.{Message, ByteString} object MessageSerializer extends Logging { - private var SERIALIZER_JAVA: Serializer.Java = Serializer.Java - private var SERIALIZER_JAVA_JSON: Serializer.JavaJSON = Serializer.JavaJSON - private var SERIALIZER_SCALA_JSON: Serializer.ScalaJSON = Serializer.ScalaJSON - private var SERIALIZER_SBINARY: Serializer.SBinary = Serializer.SBinary - private var SERIALIZER_PROTOBUF: Serializer.Protobuf = Serializer.Protobuf + private def SERIALIZER_JAVA: Serializer.Java = Serializer.Java + private def SERIALIZER_JAVA_JSON: Serializer.JavaJSON = Serializer.JavaJSON + private def SERIALIZER_SCALA_JSON: Serializer.ScalaJSON = Serializer.ScalaJSON + private def SERIALIZER_SBINARY: Serializer.SBinary = Serializer.SBinary + private def SERIALIZER_PROTOBUF: Serializer.Protobuf = Serializer.Protobuf def setClassLoader(cl: ClassLoader) = { - SERIALIZER_JAVA.classLoader = Some(cl) - SERIALIZER_JAVA_JSON.classLoader = Some(cl) - SERIALIZER_SCALA_JSON.classLoader = Some(cl) - SERIALIZER_SBINARY.classLoader = Some(cl) + val someCl = Some(cl) + SERIALIZER_JAVA.classLoader = someCl + SERIALIZER_JAVA_JSON.classLoader = someCl + SERIALIZER_SCALA_JSON.classLoader = someCl + SERIALIZER_SBINARY.classLoader = someCl } def deserialize(messageProtocol: MessageProtocol): Any = { diff --git a/akka-remote/src/main/scala/akka/serialization/Serializer.scala b/akka-remote/src/main/scala/akka/serialization/Serializer.scala index e30e615322..20c91c8559 100644 --- a/akka-remote/src/main/scala/akka/serialization/Serializer.scala +++ b/akka-remote/src/main/scala/akka/serialization/Serializer.scala @@ -18,7 +18,7 @@ import sjson.json.{Serializer => SJSONSerializer} * @author Jonas Bonér */ @serializable trait Serializer { - var classLoader: Option[ClassLoader] = None + @volatile var classLoader: Option[ClassLoader] = None def deepClone(obj: AnyRef): AnyRef = fromBinary(toBinary(obj), Some(obj.getClass)) def toBinary(obj: AnyRef): Array[Byte] From 6edfb7d5b8123cafc54e6a90d6ec66841e298964 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 20 Dec 2010 16:58:05 +0100 Subject: [PATCH 15/38] Reverted to using LocalActorRefs for client-managed actors to get supervision working, more migrated tests --- .../src/main/scala/akka/actor/ActorRef.scala | 85 ++- .../main/scala/akka/actor/ActorRegistry.scala | 2 +- .../remoteinterface/RemoteInterface.scala | 7 +- .../scala/akka/util/ReflectiveAccess.scala | 2 + .../akka/remote/NettyRemoteSupport.scala | 127 ++-- .../serialization/SerializationProtocol.scala | 26 +- .../test/scala/remote/AkkaRemoteTest.scala | 12 +- .../ClientInitiatedRemoteActorSpec.scala | 4 +- .../scala/remote/RemoteSupervisorSpec.scala | 632 +++++++----------- ...erverInitiatedRemoteSessionActorSpec.scala | 67 +- .../src/test/scala/remote/ShutdownSpec.scala | 40 -- ...rotobufActorMessageSerializationSpec.scala | 4 - .../ScalaJSONSerializerSpec.scala | 1 + 13 files changed, 421 insertions(+), 588 deletions(-) delete mode 100644 akka-remote/src/test/scala/remote/ShutdownSpec.scala diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index d1c55e88d2..28a47646c3 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -172,7 +172,17 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal def getDispatcher(): MessageDispatcher = dispatcher /** - * Holds the hot swapped partial function. + * Returns on which node this actor lives + */ + def homeAddress: InetSocketAddress + + /** + * Java API + */ + def getHomeAddress(): InetSocketAddress = homeAddress + + /** + * Holds the hot swapped partial function. */ @volatile protected[akka] var hotswap = Stack[PartialFunction[Any, Unit]]() @@ -504,8 +514,7 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Option[Int], withinTimeRange: Option[Int]): Unit - //TODO: REVISIT: REMOVE - //protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] + protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] protected[akka] def linkedActors: JMap[Uuid, ActorRef] @@ -541,7 +550,8 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal * @author Jonas Bonér */ class LocalActorRef private[akka] ( - private[this] val actorFactory: () => Actor) + private[this] val actorFactory: () => Actor, + val homeAddress: InetSocketAddress = Remote.localAddress) extends ActorRef with ScalaActorRef { @volatile @@ -566,15 +576,14 @@ class LocalActorRef private[akka] ( private[akka] def this( __uuid: Uuid, __id: String, - __hostname: String, - __port: Int, __timeout: Long, __receiveTimeout: Option[Long], __lifeCycle: LifeCycle, __supervisor: Option[ActorRef], __hotswap: Stack[PartialFunction[Any, Unit]], - __factory: () => Actor) = { - this(__factory) + __factory: () => Actor, + __homeAddress: InetSocketAddress) = { + this(__factory, __homeAddress) _uuid = __uuid id = __id timeout = __timeout @@ -587,6 +596,8 @@ class LocalActorRef private[akka] ( ActorRegistry.register(this) //TODO: REVISIT: Is this needed? } + private final def isClientManaged_? = (homeAddress ne Remote.localAddress) && homeAddress != Remote.localAddress + // ========= PUBLIC FUNCTIONS ========= /** @@ -630,6 +641,9 @@ class LocalActorRef private[akka] ( if ((actorInstance ne null) && (actorInstance.get ne null)) initializeActorInstance + if (isRemotingEnabled && isClientManaged_?) + ActorRegistry.remote.registerClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) + checkReceiveTimeout //Schedule the initial Receive timeout } this @@ -646,6 +660,11 @@ class LocalActorRef private[akka] ( _status = ActorRefInternals.SHUTDOWN actor.postStop ActorRegistry.unregister(this) + if (isRemotingEnabled) { + if (isClientManaged_?) + ActorRegistry.remote.registerClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) + ActorRegistry.remote.unregister(this) + } setActorSelfFields(actorInstance.get,null) } //else if (isBeingRestarted) throw new ActorKilledException("Actor [" + toString + "] is being restarted.") } @@ -771,21 +790,28 @@ class LocalActorRef private[akka] ( protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit = _supervisor = sup - protected[akka] def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = { - val invocation = new MessageInvocation(this, message, senderOption, None) - dispatcher dispatchMessage invocation - } + protected[akka] def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = + if (isClientManaged_? && isRemotingEnabled) { + ActorRegistry.remote.send[Any]( + message, senderOption, None, homeAddress, timeout, true, this, None, ActorType.ScalaActor, None) + } else + dispatcher dispatchMessage new MessageInvocation(this, message, senderOption, None) protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout[T]( message: Any, timeout: Long, senderOption: Option[ActorRef], senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { + if (isClientManaged_? && isRemotingEnabled) { + val future = ActorRegistry.remote.send[T]( + message, senderOption, senderFuture, homeAddress, timeout, false, this, None, ActorType.ScalaActor, None) + if (future.isDefined) future.get + else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) + } else { val future = if (senderFuture.isDefined) senderFuture else Some(new DefaultCompletableFuture[T](timeout)) - val invocation = new MessageInvocation( - this, message, senderOption, future.asInstanceOf[Some[CompletableFuture[Any]]]) - dispatcher dispatchMessage invocation + dispatcher dispatchMessage new MessageInvocation(this, message, senderOption, future.asInstanceOf[Some[CompletableFuture[Any]]]) future.get + } } /** @@ -942,15 +968,13 @@ class LocalActorRef private[akka] ( } } - //TODO: REVISIT: REMOVE - - /*protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = guard.withGuard { + protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = guard.withGuard { ensureRemotingEnabled if (_supervisor.isDefined) { - remoteAddress.foreach(address => RemoteClientModule.registerSupervisorForActor(address, this)) + ActorRegistry.remote.registerSupervisorForActor(this) Some(_supervisor.get.uuid) } else None - }*/ + } protected[akka] def linkedActors: JMap[Uuid, ActorRef] = _linkedActors @@ -1074,7 +1098,7 @@ object RemoteActorSystemMessage { * @author Jonas Bonér */ private[akka] case class RemoteActorRef private[akka] ( - classOrServiceName: Option[String], + classOrServiceName: String, val actorClassName: String, val hostname: String, val port: Int, @@ -1087,9 +1111,9 @@ private[akka] case class RemoteActorRef private[akka] ( val homeAddress = new InetSocketAddress(hostname, port) - protected def clientManaged = classOrServiceName.isEmpty //If no class or service name, it's client managed - - id = classOrServiceName.getOrElse("uuid:" + uuid) //If we're a server-managed we want to have classOrServiceName as id, or else, we're a client-managed and we want to have our uuid as id + //protected def clientManaged = classOrServiceName.isEmpty //If no class or service name, it's client managed + id = classOrServiceName + //id = classOrServiceName.getOrElse("uuid:" + uuid) //If we're a server-managed we want to have classOrServiceName as id, or else, we're a client-managed and we want to have our uuid as id timeout = _timeout @@ -1110,22 +1134,21 @@ private[akka] case class RemoteActorRef private[akka] ( def start: ActorRef = synchronized { _status = ActorRefInternals.RUNNING - if (clientManaged) - ActorRegistry.remote.registerClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) + //if (clientManaged) + // ActorRegistry.remote.registerClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) this } def stop: Unit = synchronized { if (_status == ActorRefInternals.RUNNING) { _status = ActorRefInternals.SHUTDOWN - postMessageToMailbox(RemoteActorSystemMessage.Stop, None) //TODO: REVISIT: Should this be called for both server-managed and client-managed? - if (clientManaged) - ActorRegistry.remote.unregisterClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) + postMessageToMailbox(RemoteActorSystemMessage.Stop, None) + // if (clientManaged) + // ActorRegistry.remote.unregisterClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) } } - //TODO: REVISIT: REMOVE - //protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = None + protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = None // ==== NOT SUPPORTED ==== def actorClass: Class[_ <: Actor] = unsupported diff --git a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala index 620aea9ecc..e3934f1a58 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala @@ -396,7 +396,7 @@ object ActorRegistry extends ListenerManagement { } } else foreach(_.stop) if (Remote.isEnabled) { - remote.clear + remote.clear //TODO: REVISIT: Should this be here? } actorsByUUID.clear actorsById.clear diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index ea816a7c51..11ed70a08a 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -28,8 +28,8 @@ trait RemoteModule extends Logging { abstract class RemoteSupport extends ListenerManagement with RemoteServerModule with RemoteClientModule { def shutdown { - this.shutdownServerModule this.shutdownClientModule + this.shutdownServerModule clear } protected override def manageLifeCycleOfListeners = false @@ -241,10 +241,9 @@ trait RemoteClientModule extends RemoteModule { self: RemoteModule => actorType: ActorType, loader: Option[ClassLoader]): Option[CompletableFuture[T]] - //TODO: REVISIT: IMPLEMENT OR REMOVE - //private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef + private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef - //private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef + private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef /** * Clean-up all open connections. diff --git a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala index 1a02136f20..5ba5f513b2 100644 --- a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala +++ b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala @@ -37,6 +37,8 @@ object ReflectiveAccess extends Logging { val HOSTNAME = Config.config.getString("akka.remote.server.hostname", "localhost") val PORT = Config.config.getInt("akka.remote.server.port", 2552) + lazy val localAddress = new InetSocketAddress(HOSTNAME,PORT) + lazy val isEnabled = remoteSupportClass.isDefined def ensureEnabled = if (!isEnabled) throw new ModuleNotAvailableException( diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index c5453a1830..c4d2036cc4 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -11,8 +11,6 @@ import akka.remote.protocol.RemoteProtocol.ActorType._ import akka.config.ConfigurationException import akka.serialization.RemoteActorSerialization import akka.japi.Creator -import akka.actor.{newUuid,ActorRegistry, Actor, RemoteActorRef, TypedActor, ActorRef, IllegalActorStateException, - RemoteActorSystemMessage, uuidFrom, Uuid, Exit, LifeCycleMessage, ActorType => AkkaActorType} import akka.remoteinterface. {RemoteSupport, RemoteModule, RemoteServerModule, RemoteClientModule} import akka.config.Config._ import akka.serialization.RemoteActorSerialization._ @@ -34,13 +32,11 @@ import org.jboss.netty.handler.ssl.SslHandler import java.net.{ SocketAddress, InetSocketAddress } import java.util.concurrent.{ TimeUnit, Executors, ConcurrentMap, ConcurrentHashMap, ConcurrentSkipListSet } -import java.util.concurrent.atomic.{ AtomicLong, AtomicBoolean } - import scala.collection.mutable.{ HashSet, HashMap } import scala.reflect.BeanProperty import java.lang.reflect.InvocationTargetException - - +import akka.actor. {ActorInitializationException, LocalActorRef, newUuid, ActorRegistry, Actor, RemoteActorRef, TypedActor, ActorRef, IllegalActorStateException, RemoteActorSystemMessage, uuidFrom, Uuid, Exit, LifeCycleMessage, ActorType => AkkaActorType} +import java.util.concurrent.atomic. {AtomicReference, AtomicLong, AtomicBoolean} /** * Life-cycle events for RemoteClient. @@ -78,7 +74,7 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem private val remoteActors = new HashMap[Address, HashSet[Uuid]] protected[akka] def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): T = - TypedActor.createProxyForRemoteActorRef(intfClass, RemoteActorRef(Some(serviceId), implClassName, hostname, port, timeout, loader, AkkaActorType.TypedActor)) + TypedActor.createProxyForRemoteActorRef(intfClass, RemoteActorRef(serviceId, implClassName, hostname, port, timeout, loader, AkkaActorType.TypedActor)) protected[akka] def send[T](message: Any, senderOption: Option[ActorRef], @@ -117,12 +113,12 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem remoteClients -= hash } } - //TODO: REVISIT IMPLEMENT OR REMOVE - /*private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef = - clientFor().registerSupervisorForActor(actorRef) + + private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef = + clientFor(actorRef.homeAddress, None).registerSupervisorForActor(actorRef) private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef = - clientFor().deregisterSupervisorForActor(actorRef)*/ + clientFor(actorRef.homeAddress, None).deregisterSupervisorForActor(actorRef) /** * Clean-up all open connections. @@ -265,7 +261,7 @@ class RemoteClient private[akka] ( def send[T]( request: RemoteMessageProtocol, senderFuture: Option[CompletableFuture[T]]): Option[CompletableFuture[T]] = { - log.slf4j.debug("sending message: {} is running {} has future {}", Array[AnyRef](request, isRunning.asInstanceOf[AnyRef], senderFuture)) + log.slf4j.debug("sending message: {} has future {}", request, senderFuture) if (isRunning) { if (request.getOneWay) { connection.getChannel.write(request) @@ -280,8 +276,7 @@ class RemoteClient private[akka] ( Some(futureResult) } } else { - val exception = new RemoteClientException( - "Remote client is not running, make sure you have invoked 'RemoteClient.connect' before using it.", this) + val exception = new RemoteClientException("Remote client is not running, make sure you have invoked 'RemoteClient.connect' before using it.", this) notifyListeners(RemoteClientError(exception, this)) throw exception } @@ -580,32 +575,26 @@ class NettyRemoteSupport extends RemoteSupport with NettyRemoteServerModule with // case _ => // RemoteActorRef(registry, serviceId, className, hostname, port, timeout, false, loader) //} - RemoteActorRef(Some(serviceId), className, hostname, port, timeout, loader) + RemoteActorRef(serviceId, className, hostname, port, timeout, loader) } def clientManagedActorOf(clazz: Class[_ <: Actor], host: String, port: Int, timeout: Long): ActorRef = { - val Host = this.hostname - val Port = this.port - - (host,port) match { - case (Host, Port) if optimizeLocalScoped_? => - ActorRegistry.actorOf(clazz) //Local - case _ => - new RemoteActorRef(None,clazz.getName,host,port,timeout,None) - } + import ReflectiveAccess.{ createInstance, noParams, noArgs } + val ref = new LocalActorRef(() => createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( + throw new ActorInitializationException( + "Could not instantiate Actor" + + "\nMake sure Actor is NOT defined inside a class/trait," + + "\nif so put it outside the class/trait, f.e. in a companion object," + + "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")), + new InetSocketAddress(host, port)) + ref.timeout = timeout + ref } } -trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => - import RemoteServer._ +class NettyRemoteServer(serverModule: NettyRemoteServerModule, val host: String, val port: Int, val loader: Option[ClassLoader]) { - @volatile private[akka] var address = Address(RemoteServer.HOSTNAME,RemoteServer.PORT) - - def hostname = address.hostname - def port = address.port - def name = "RemoteServer@" + hostname + ":" + port - - private val _isRunning = new Switch(false) + val name = "NettyRemoteServer@" + host + ":" + port private val factory = new NioServerSocketChannelFactory(Executors.newCachedThreadPool,Executors.newCachedThreadPool) @@ -614,23 +603,57 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => // group of open channels, used for clean-up private val openChannels: ChannelGroup = new DefaultChannelGroup("akka-remote-server") + val pipelineFactory = new RemoteServerPipelineFactory(name, openChannels, loader, serverModule) + bootstrap.setPipelineFactory(pipelineFactory) + bootstrap.setOption("child.tcpNoDelay", true) + bootstrap.setOption("child.keepAlive", true) + bootstrap.setOption("child.reuseAddress", true) + bootstrap.setOption("child.connectTimeoutMillis", RemoteServer.CONNECTION_TIMEOUT_MILLIS.toMillis) + + openChannels.add(bootstrap.bind(new InetSocketAddress(host, port))) + serverModule.notifyListeners(RemoteServerStarted(serverModule)) + + def shutdown { + try { + openChannels.disconnect + openChannels.close.awaitUninterruptibly + bootstrap.releaseExternalResources + serverModule.notifyListeners(RemoteServerShutdown(serverModule)) + } catch { + case e: java.nio.channels.ClosedChannelException => {} + case e => serverModule.log.slf4j.warn("Could not close remote server channel in a graceful way") + } + } +} + +trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => + import RemoteServer._ + + private[akka] val currentServer = new AtomicReference[Option[NettyRemoteServer]](None) + def hostname = currentServer.get match { + case Some(s) => s.host + case None => ReflectiveAccess.Remote.HOSTNAME + } + + def port = currentServer.get match { + case Some(s) => s.port + case None => ReflectiveAccess.Remote.PORT + } + + def name = currentServer.get match { + case Some(s) => s.name + case None => "NettyRemoteServer@" + ReflectiveAccess.Remote.HOSTNAME + ":" + ReflectiveAccess.Remote.PORT + } + + private val _isRunning = new Switch(false) + def isRunning = _isRunning.isOn def start(_hostname: String, _port: Int, loader: Option[ClassLoader] = None): RemoteServerModule = guard withGuard { try { _isRunning switchOn { - address = Address(_hostname,_port) - log.slf4j.info("Starting remote server at [{}:{}]", hostname, port) - - val pipelineFactory = new RemoteServerPipelineFactory(name, openChannels, loader, this) - bootstrap.setPipelineFactory(pipelineFactory) - bootstrap.setOption("child.tcpNoDelay", true) - bootstrap.setOption("child.keepAlive", true) - bootstrap.setOption("child.reuseAddress", true) - bootstrap.setOption("child.connectTimeoutMillis", RemoteServer.CONNECTION_TIMEOUT_MILLIS.toMillis) - - openChannels.add(bootstrap.bind(new InetSocketAddress(hostname, port))) - notifyListeners(RemoteServerStarted(this)) + log.slf4j.debug("Starting up remote server on {}:{}",_hostname, _port) + currentServer.set(Some(new NettyRemoteServer(this, _hostname, _port, loader))) } } catch { case e => @@ -642,14 +665,10 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => def shutdownServerModule = guard withGuard { _isRunning switchOff { - try { - openChannels.disconnect - openChannels.close.awaitUninterruptibly - bootstrap.releaseExternalResources - notifyListeners(RemoteServerShutdown(this)) - } catch { - case e: java.nio.channels.ClosedChannelException => {} - case e => log.slf4j.warn("Could not close remote server channel in a graceful way") + currentServer.getAndSet(None) foreach { + instance => + log.slf4j.debug("Shutting down remote server on {}:{}",instance.host, instance.port) + instance.shutdown } } } @@ -1136,7 +1155,7 @@ class RemoteServerHandler( val id = actorInfo.getId val sessionActorRefOrNull = findSessionActor(id, channel) if (sessionActorRefOrNull ne null) { - log.debug("found session actor with id {} for channel {}",id, channel) + log.slf4j.debug("Found session actor with id {} for channel {} = {}",Array[AnyRef](id, channel, sessionActorRefOrNull)) sessionActorRefOrNull } else { // we dont have it in the session either, see if we have a factory for it diff --git a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala index 6032818e09..92edf9d582 100644 --- a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -18,6 +18,7 @@ import scala.collection.immutable.Stack import com.google.protobuf.ByteString import akka.util.ReflectiveAccess import akka.util.ReflectiveAccess.Remote.{HOSTNAME,PORT} +import java.net.InetSocketAddress /** * Type class definition for Actor Serialization @@ -90,9 +91,10 @@ object ActorSerialization { def toBinaryJ[T <: Actor](a: ActorRef, format: Format[T], srlMailBox: Boolean = true): Array[Byte] = toBinary(a, srlMailBox)(format) - val localAddress = AddressProtocol.newBuilder - .setHostname(HOSTNAME) - .setPort(PORT) + private[akka] def toAddressProtocol(actorRef: ActorRef) = + AddressProtocol.newBuilder + .setHostname(actorRef.homeAddress.getHostName) + .setPort(actorRef.homeAddress.getPort) .build private[akka] def toSerializedActorRefProtocol[T <: Actor]( @@ -109,7 +111,7 @@ object ActorSerialization { .setUuid(UuidProtocol.newBuilder.setHigh(actorRef.uuid.getTime).setLow(actorRef.uuid.getClockSeqAndNode).build) .setId(actorRef.id) .setActorClassname(actorRef.actorClass.getName) - .setOriginalAddress(localAddress) + .setOriginalAddress(toAddressProtocol(actorRef)) .setTimeout(actorRef.timeout) @@ -193,14 +195,13 @@ object ActorSerialization { val ar = new LocalActorRef( uuidFrom(protocol.getUuid.getHigh, protocol.getUuid.getLow), protocol.getId, - protocol.getOriginalAddress.getHostname, - protocol.getOriginalAddress.getPort, if (protocol.hasTimeout) protocol.getTimeout else Actor.TIMEOUT, if (protocol.hasReceiveTimeout) Some(protocol.getReceiveTimeout) else None, lifeCycle, supervisor, hotswap, - factory) + factory, + new InetSocketAddress(protocol.getOriginalAddress.getHostname,protocol.getOriginalAddress.getPort)) val messages = protocol.getMessagesList.toArray.toList.asInstanceOf[List[RemoteMessageProtocol]] messages.foreach(message => ar ! MessageSerializer.deserialize(message.getMessage)) @@ -230,7 +231,7 @@ object RemoteActorSerialization { private[akka] def fromProtobufToRemoteActorRef(protocol: RemoteActorRefProtocol, loader: Option[ClassLoader]): ActorRef = { Actor.log.slf4j.debug("Deserializing RemoteActorRefProtocol to RemoteActorRef:\n {}", protocol) val ref = RemoteActorRef( - Some(protocol.getClassOrServiceName), + protocol.getClassOrServiceName, protocol.getActorClassname, protocol.getHomeAddress.getHostname, protocol.getHomeAddress.getPort, @@ -247,15 +248,14 @@ object RemoteActorSerialization { def toRemoteActorRefProtocol(ar: ActorRef): RemoteActorRefProtocol = { import ar._ - Actor.log.slf4j.debug("Register serialized Actor [{}] as remote @ [{}:{}]", - Array[AnyRef](actorClassName, ActorSerialization.localAddress.getHostname, ActorSerialization.localAddress.getPort.asInstanceOf[AnyRef])) + Actor.log.slf4j.debug("Register serialized Actor [{}] as remote @ [{}:{}]",actorClassName, ar.homeAddress) ActorRegistry.remote.registerByUuid(ar) RemoteActorRefProtocol.newBuilder .setClassOrServiceName("uuid:"+uuid.toString) .setActorClassname(actorClassName) - .setHomeAddress(ActorSerialization.localAddress) + .setHomeAddress(ActorSerialization.toAddressProtocol(ar)) .setTimeout(timeout) .build } @@ -322,8 +322,6 @@ object RemoteActorSerialization { secureCookie.foreach(messageBuilder.setCookie(_)) - //TODO: REVISIT: REMOVE - /** actorRef.foreach { ref => ref.registerSupervisorAsRemoteActor.foreach { id => messageBuilder.setSupervisorUuid( @@ -332,7 +330,7 @@ object RemoteActorSerialization { .setLow(id.getClockSeqAndNode) .build) } - }*/ + } if( senderOption.isDefined) messageBuilder.setSender(toRemoteActorRefProtocol(senderOption.get)) diff --git a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala index c5ff2b3024..b22eaa8b27 100644 --- a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala +++ b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala @@ -32,16 +32,22 @@ class AkkaRemoteTest extends var optimizeLocal_? = remote.asInstanceOf[NettyRemoteSupport].optimizeLocalScoped_? - override def beforeAll() { + override def beforeAll { remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(false) //Can't run the test if we're eliminating all remote calls - remote.start() } - override def afterAll() { + override def afterAll { remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(optimizeLocal_?) //Reset optimizelocal after all tests } + override def beforeEach { + remote.start() + Thread.sleep(2000) + super.beforeEach + } + override def afterEach() { + remote.shutdown ActorRegistry.shutdownAll super.afterEach } diff --git a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala index 4b3e301aae..ad0028c8ea 100644 --- a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala @@ -9,8 +9,8 @@ import org.junit.runner.RunWith import akka.dispatch.Dispatchers import akka.remote. {NettyRemoteSupport, RemoteServer, RemoteClient} -import akka.actor. {RemoteActorRef, ActorRegistry, ActorRef, Actor} import akka.actor.Actor._ +import akka.actor._ class ExpectedRemoteProblem(msg: String) extends RuntimeException(msg) @@ -78,7 +78,7 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { "shouldSendOneWay" in { val clientManaged = actorOf[RemoteActorSpecActorUnidirectional](host,port).start clientManaged must not be null - clientManaged.getClass must be (classOf[RemoteActorRef]) + clientManaged.getClass must be (classOf[LocalActorRef]) clientManaged ! "OneWay" RemoteActorSpecActorUnidirectional.latch.await(1, TimeUnit.SECONDS) must be (true) clientManaged.stop diff --git a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala index bb6ed80ca7..33760028fb 100644 --- a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala +++ b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala @@ -1,7 +1,7 @@ /** * Copyright (C) 2009-2010 Scalable Solutions AB */ - /* THIS SHOULD BE UNCOMMENTED + package akka.actor.remote import java.util.concurrent.{LinkedBlockingQueue, TimeUnit, BlockingQueue} @@ -9,8 +9,9 @@ import akka.serialization.BinaryString import akka.config.Supervision._ import akka.remote.{RemoteServer, RemoteClient} import akka.OneWay -import org.scalatest.junit.JUnitSuite -import org.junit.{Test, Before, After} +import org.scalatest._ +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers import akka.actor.{SupervisorFactory, Supervisor, ActorRef, Actor} import Actor._ @@ -70,14 +71,7 @@ object Log { } } -object RemoteSupervisorSpec { - val HOSTNAME = "localhost" - val PORT = 9988 - var server: RemoteServer = null -} - -class RemoteSupervisorSpec extends JUnitSuite { - import RemoteSupervisorSpec._ +class RemoteSupervisorSpec extends AkkaRemoteTest { var pingpong1: ActorRef = _ var pingpong2: ActorRef = _ @@ -85,135 +79,253 @@ class RemoteSupervisorSpec extends JUnitSuite { import Log._ - @Before - def init { - server = new RemoteServer() - server.start(HOSTNAME, PORT) - Thread.sleep(1000) - } + "Remote supervision" should { - @After - def finished { - try { - server.shutdown - RemoteClient.shutdownAll - Thread.sleep(1000) - } catch { - case e => () + "start server" in { + Log.messageLog.clear + val sup = getSingleActorAllForOneSupervisor + + (pingpong1 !! BinaryString("Ping")) must equal (Some("pong")) + } + + "StartServerForNestedSupervisorHierarchy" in { + clearMessageLogs + val sup = getNestedSupervisorsAllForOneConf + sup.start + + (pingpong1 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) + } + + "killSingleActorOneForOne" in { + clearMessageLogs + val sup = getSingleActorOneForOneSupervisor + + (pingpong1 !!! (BinaryString("Die"), 5000)).await.exception.isDefined must be (true) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + } + + "callKillCallSingleActorOneForOne" in { + clearMessageLogs + val sup = getSingleActorOneForOneSupervisor + + (pingpong1 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + + (pingpong1 !!! (BinaryString("Die"), 5000)).await.exception.isDefined must be (true) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + (pingpong1 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + } + + "KillSingleActorAllForOne" in { + clearMessageLogs + val sup = getSingleActorAllForOneSupervisor + + (pingpong1 !!! (BinaryString("Die"), 5000)).await.exception.isDefined must be (true) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + } + + "CallKillCallSingleActorAllForOne" in { + clearMessageLogs + val sup = getSingleActorAllForOneSupervisor + + (pingpong1 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + + (pingpong1 !!! (BinaryString("Die"), 5000)).await.exception.isDefined must be (true) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + + (pingpong1 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + } + + "KillMultipleActorsOneForOne1" in { + clearMessageLogs + val sup = getMultipleActorsOneForOneConf + + (pingpong1 !!! (BinaryString("Die"), 5000)).await.exception.isDefined must be (true) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + } + + "KillCallMultipleActorsOneForOne" in { + clearMessageLogs + val sup = getMultipleActorsOneForOneConf + + (pingpong1 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) + (pingpong2 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) + (pingpong3 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + + (pingpong2 !!! (BinaryString("Die"), 5000)).await.exception.isDefined must be (true) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + + (pingpong1 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) + (pingpong2 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) + (pingpong3 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + } + + "KillMultipleActorsAllForOne" in { + clearMessageLogs + val sup = getMultipleActorsAllForOneConf + + (pingpong2 !!! (BinaryString("Die"), 5000)).await.exception.isDefined must be (true) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + } + + "CallKillCallMultipleActorsAllForOne" in { + clearMessageLogs + val sup = getMultipleActorsAllForOneConf + + pingpong1 !! (BinaryString("Ping"), 5000) must equal (Some("pong")) + pingpong2 !! (BinaryString("Ping"), 5000) must equal (Some("pong")) + pingpong3 !! (BinaryString("Ping"), 5000) must equal (Some("pong")) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + + (pingpong2 !!! (BinaryString("Die"), 5000)).await.exception.isDefined must be (true) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + + pingpong1 !! (BinaryString("Ping"), 5000) must equal (Some("pong")) + pingpong2 !! (BinaryString("Ping"), 5000) must equal (Some("pong")) + pingpong3 !! (BinaryString("Ping"), 5000) must equal (Some("pong")) + + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("ping") } } - @Test def shouldStartServer = { - Log.messageLog.clear - val sup = getSingleActorAllForOneSupervisor + def getSingleActorAllForOneSupervisor: Supervisor = { - expect("pong") { - (pingpong1 !! BinaryString("Ping")).getOrElse("nil") - } - } - @Test def shouldStartServerForNestedSupervisorHierarchy = { - clearMessageLogs - val sup = getNestedSupervisorsAllForOneConf - sup.start + // Create an abstract SupervisorContainer that works for all implementations + // of the different Actors (Services). + // + // Then create a concrete container in which we mix in support for the specific + // implementation of the Actors we want to use. - expect("pong") { - (pingpong1 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } + pingpong1 = actorOf[RemotePingPong1Actor](host,port).start + + val factory = SupervisorFactory( + SupervisorConfig( + AllForOneStrategy(List(classOf[Exception]), 3, 100), + Supervise( + pingpong1, + Permanent) + :: Nil)) + + factory.newInstance } - @Test def shouldKillSingleActorOneForOne = { - clearMessageLogs - val sup = getSingleActorOneForOneSupervisor + def getSingleActorOneForOneSupervisor: Supervisor = { + pingpong1 = actorOf[RemotePingPong1Actor](host,port).start - intercept[RuntimeException] { - pingpong1 !! (BinaryString("Die"), 5000) - } - - expect("Expected exception; to test fault-tolerance") { - messageLog.poll(5, TimeUnit.SECONDS) - } + val factory = SupervisorFactory( + SupervisorConfig( + OneForOneStrategy(List(classOf[Exception]), 3, 100), + Supervise( + pingpong1, + Permanent) + :: Nil)) + factory.newInstance } - @Test def shouldCallKillCallSingleActorOneForOne = { - clearMessageLogs - val sup = getSingleActorOneForOneSupervisor + def getMultipleActorsAllForOneConf: Supervisor = { + pingpong1 = actorOf[RemotePingPong1Actor](host,port).start + pingpong2 = actorOf[RemotePingPong2Actor](host,port).start + pingpong3 = actorOf[RemotePingPong3Actor](host,port).start - expect("pong") { - (pingpong1 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - intercept[RuntimeException] { - pingpong1 !! (BinaryString("Die"), 5000) - } - - expect("Expected exception; to test fault-tolerance") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("pong") { - (pingpong1 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } + val factory = SupervisorFactory( + SupervisorConfig( + AllForOneStrategy(List(classOf[Exception]), 3, 100), + Supervise( + pingpong1, + Permanent) + :: + Supervise( + pingpong2, + Permanent) + :: + Supervise( + pingpong3, + Permanent) + :: Nil)) + factory.newInstance } - @Test def shouldKillSingleActorAllForOne = { - clearMessageLogs - val sup = getSingleActorAllForOneSupervisor + def getMultipleActorsOneForOneConf: Supervisor = { + pingpong1 = actorOf[RemotePingPong1Actor](host,port).start + pingpong2 = actorOf[RemotePingPong2Actor](host,port).start + pingpong3 = actorOf[RemotePingPong3Actor](host,port).start - intercept[RuntimeException] { - pingpong1 !! (BinaryString("Die"), 5000) - } - - expect("Expected exception; to test fault-tolerance") { - messageLog.poll(5, TimeUnit.SECONDS) - } + val factory = SupervisorFactory( + SupervisorConfig( + OneForOneStrategy(List(classOf[Exception]), 3, 100), + Supervise( + pingpong1, + Permanent) + :: + Supervise( + pingpong2, + Permanent) + :: + Supervise( + pingpong3, + Permanent) + :: Nil)) + factory.newInstance } - @Test def shouldCallKillCallSingleActorAllForOne = { - clearMessageLogs - val sup = getSingleActorAllForOneSupervisor + def getNestedSupervisorsAllForOneConf: Supervisor = { + pingpong1 = actorOf[RemotePingPong1Actor](host,port).start + pingpong2 = actorOf[RemotePingPong2Actor](host,port).start + pingpong3 = actorOf[RemotePingPong3Actor](host,port).start - expect("pong") { - (pingpong1 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - intercept[RuntimeException] { - pingpong1 !! (BinaryString("Die"), 5000) - } - - expect("Expected exception; to test fault-tolerance") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("pong") { - (pingpong1 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } + val factory = SupervisorFactory( + SupervisorConfig( + AllForOneStrategy(List(classOf[Exception]), 3, 100), + Supervise( + pingpong1, + Permanent) + :: + SupervisorConfig( + AllForOneStrategy(List(classOf[Exception]), 3, 100), + Supervise( + pingpong2, + Permanent) + :: + Supervise( + pingpong3, + Permanent) + :: Nil) + :: Nil)) + factory.newInstance } - @Test def shouldKillMultipleActorsOneForOne1 = { - clearMessageLogs - val sup = getMultipleActorsOneForOneConf - - intercept[RuntimeException] { - pingpong1 !! (BinaryString("Die"), 5000) - } - - expect("Expected exception; to test fault-tolerance") { - messageLog.poll(5, TimeUnit.SECONDS) - } - }*/ - /* // Uncomment when the same test passes in SupervisorSpec - pending bug @Test def shouldKillMultipleActorsOneForOne2 = { @@ -229,141 +341,6 @@ class RemoteSupervisorSpec extends JUnitSuite { } } */ - /* THIS SHOULD BE UNCOMMENTED - @Test def shouldKillCallMultipleActorsOneForOne = { - clearMessageLogs - val sup = getMultipleActorsOneForOneConf - - expect("pong") { - (pingpong1 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("pong") { - (pingpong2 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("pong") { - (pingpong3 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - intercept[RuntimeException] { - pingpong2 !! (BinaryString("Die"), 5000) - } - - expect("Expected exception; to test fault-tolerance") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("pong") { - (pingpong1 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("pong") { - (pingpong2 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("pong") { - (pingpong3 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - } - - @Test def shouldKillMultipleActorsAllForOne = { - clearMessageLogs - val sup = getMultipleActorsAllForOneConf - - intercept[RuntimeException] { - pingpong2 !! (BinaryString("Die"), 5000) - } - - expect("Expected exception; to test fault-tolerance") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("Expected exception; to test fault-tolerance") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("Expected exception; to test fault-tolerance") { - messageLog.poll(5, TimeUnit.SECONDS) - } - } - - @Test def shouldCallKillCallMultipleActorsAllForOne = { - clearMessageLogs - val sup = getMultipleActorsAllForOneConf - - expect("pong") { - (pingpong1 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("pong") { - (pingpong2 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("pong") { - (pingpong3 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - intercept[RuntimeException] { - pingpong2 !! (BinaryString("Die"), 5000) - } - - expect("Expected exception; to test fault-tolerance") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("Expected exception; to test fault-tolerance") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("Expected exception; to test fault-tolerance") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("pong") { - (pingpong1 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("pong") { - (pingpong2 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("pong") { - (pingpong3 !! (BinaryString("Ping"), 5000)).getOrElse("nil") - } - - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - expect("ping") { - messageLog.poll(5, TimeUnit.SECONDS) - } - }*/ /* @@ -405,15 +382,15 @@ class RemoteSupervisorSpec extends JUnitSuite { expect("pong") { - (pingpong1 !! (BinaryString("Ping"), 5000)).getOrElse("nil") + (pingpong1 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) } expect("pong") { - (pingpong2 !! (BinaryString("Ping"), 5000)).getOrElse("nil") + (pingpong2 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) } expect("pong") { - (pingpong3 !! (BinaryString("Ping"), 5000)).getOrElse("nil") + (pingpong3 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) } expect("ping") { @@ -439,15 +416,15 @@ class RemoteSupervisorSpec extends JUnitSuite { messageLog.poll(5, TimeUnit.SECONDS) } expect("pong") { - (pingpong1 !! (BinaryString("Ping"), 5000)).getOrElse("nil") + (pingpong1 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) } expect("pong") { - (pingpong2 !! (BinaryString("Ping"), 5000)).getOrElse("nil") + (pingpong2 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) } expect("pong") { - (pingpong3 !! (BinaryString("Ping"), 5000)).getOrElse("nil") + (pingpong3 !! (BinaryString("Ping"), 5000)) must equal (Some("pong")) } expect("ping") { @@ -461,136 +438,5 @@ class RemoteSupervisorSpec extends JUnitSuite { } } */ - // ============================================= - // Creat some supervisors with different configurations - /* THIS SHOULD BE UNCOMMENTED - def getSingleActorAllForOneSupervisor: Supervisor = { - // Create an abstract SupervisorContainer that works for all implementations - // of the different Actors (Services). - // - // Then create a concrete container in which we mix in support for the specific - // implementation of the Actors we want to use. - - pingpong1 = actorOf[RemotePingPong1Actor] - pingpong1.makeRemote(HOSTNAME, PORT) - pingpong1.start - - val factory = SupervisorFactory( - SupervisorConfig( - AllForOneStrategy(List(classOf[Exception]), 3, 100), - Supervise( - pingpong1, - Permanent) - :: Nil)) - - factory.newInstance - } - - def getSingleActorOneForOneSupervisor: Supervisor = { - pingpong1 = actorOf[RemotePingPong1Actor] - pingpong1.makeRemote(HOSTNAME, PORT) - pingpong1.start - - val factory = SupervisorFactory( - SupervisorConfig( - OneForOneStrategy(List(classOf[Exception]), 3, 100), - Supervise( - pingpong1, - Permanent) - :: Nil)) - factory.newInstance - } - - def getMultipleActorsAllForOneConf: Supervisor = { - pingpong1 = actorOf[RemotePingPong1Actor] - pingpong1.makeRemote(HOSTNAME, PORT) - pingpong1.start - pingpong2 = actorOf[RemotePingPong2Actor] - pingpong2.makeRemote(HOSTNAME, PORT) - pingpong2.start - pingpong3 = actorOf[RemotePingPong3Actor] - pingpong3.makeRemote(HOSTNAME, PORT) - pingpong3.start - - val factory = SupervisorFactory( - SupervisorConfig( - AllForOneStrategy(List(classOf[Exception]), 3, 100), - Supervise( - pingpong1, - Permanent) - :: - Supervise( - pingpong2, - Permanent) - :: - Supervise( - pingpong3, - Permanent) - :: Nil)) - factory.newInstance - } - - def getMultipleActorsOneForOneConf: Supervisor = { - pingpong1 = actorOf[RemotePingPong1Actor] - pingpong1.makeRemote(HOSTNAME, PORT) - pingpong1 = actorOf[RemotePingPong1Actor] - pingpong1.makeRemote(HOSTNAME, PORT) - pingpong1.start - pingpong2 = actorOf[RemotePingPong2Actor] - pingpong2.makeRemote(HOSTNAME, PORT) - pingpong2.start - pingpong3 = actorOf[RemotePingPong3Actor] - pingpong3.makeRemote(HOSTNAME, PORT) - pingpong3.start - - val factory = SupervisorFactory( - SupervisorConfig( - OneForOneStrategy(List(classOf[Exception]), 3, 100), - Supervise( - pingpong1, - Permanent) - :: - Supervise( - pingpong2, - Permanent) - :: - Supervise( - pingpong3, - Permanent) - :: Nil)) - factory.newInstance - } - - def getNestedSupervisorsAllForOneConf: Supervisor = { - pingpong1 = actorOf[RemotePingPong1Actor] - pingpong1.makeRemote(HOSTNAME, PORT) - pingpong1.start - pingpong2 = actorOf[RemotePingPong2Actor] - pingpong2.makeRemote(HOSTNAME, PORT) - pingpong2.start - pingpong3 = actorOf[RemotePingPong3Actor] - pingpong3.makeRemote(HOSTNAME, PORT) - pingpong3.start - - val factory = SupervisorFactory( - SupervisorConfig( - AllForOneStrategy(List(classOf[Exception]), 3, 100), - Supervise( - pingpong1, - Permanent) - :: - SupervisorConfig( - AllForOneStrategy(List(classOf[Exception]), 3, 100), - Supervise( - pingpong2, - Permanent) - :: - Supervise( - pingpong3, - Permanent) - :: Nil) - :: Nil)) - factory.newInstance - } -}*/ +} diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala index 49c31b1ae7..b0b10e5ada 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala @@ -11,11 +11,10 @@ import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.junit.JUnitRunner import org.junit.runner.RunWith -import java.util.concurrent.TimeUnit - import akka.actor._ import akka.actor.Actor._ import akka.remote.NettyRemoteSupport +import java.util.concurrent. {ConcurrentSkipListSet, TimeUnit} object ServerInitiatedRemoteSessionActorSpec { @@ -23,27 +22,18 @@ object ServerInitiatedRemoteSessionActorSpec { case class GetUser() case class DoSomethingFunny() - var instantiatedSessionActors= Set[ActorRef]() + val instantiatedSessionActors = new ConcurrentSkipListSet[ActorRef]() class RemoteStatefullSessionActorSpec extends Actor { - var user : String= "anonymous" - - override def preStart = { - instantiatedSessionActors += self - } - - override def postStop = { - instantiatedSessionActors -= self - } + override def preStart = instantiatedSessionActors.add(self) + override def postStop = instantiatedSessionActors.remove(self) + var user: String = "anonymous" def receive = { - case Login(user) => - this.user = user - case GetUser() => - self.reply(this.user) - case DoSomethingFunny() => - throw new Exception("Bad boy") + case Login(user) => this.user = user + case GetUser() => self.reply(this.user) + case DoSomethingFunny() => throw new Exception("Bad boy") } } @@ -74,44 +64,37 @@ class ServerInitiatedRemoteSessionActorSpec extends AkkaRemoteTest { default2.as[String] must equal (Some("anonymous")) } - /*"stop the actor when the client disconnects" in { - val session1 = RemoteClient.actorFor( - "untyped-session-actor-service", - 5000L, - HOSTNAME, PORT) - + "stop the actor when the client disconnects" in { + instantiatedSessionActors.clear + remote.registerPerSession("untyped-session-actor-service", actorOf[RemoteStatefullSessionActorSpec]) + val session1 = remote.actorFor("untyped-session-actor-service", 5000L, host, port) val default1 = session1 !! GetUser() - default1.get.asInstanceOf[String] should equal ("anonymous") + default1.as[String] must equal (Some("anonymous")) - instantiatedSessionActors should have size (1) - - RemoteClient.shutdownAll - Thread.sleep(1000) - instantiatedSessionActors should have size (0) + instantiatedSessionActors must have size (1) + remote.shutdownClientModule + instantiatedSessionActors must have size (0) } "stop the actor when there is an error" in { - val session1 = RemoteClient.actorFor( - "untyped-session-actor-service", - 5000L, - HOSTNAME, PORT) - + instantiatedSessionActors.clear + remote.registerPerSession("untyped-session-actor-service", actorOf[RemoteStatefullSessionActorSpec]) + val session1 = remote.actorFor("untyped-session-actor-service", 5000L, host, port) session1 ! DoSomethingFunny() session1.stop() - Thread.sleep(1000) - instantiatedSessionActors should have size (0) + instantiatedSessionActors must have size (0) } "be able to unregister" in { - server.registerPerSession("my-service-1", actorOf[RemoteStatefullSessionActorSpec]) - server.actorsFactories.get("my-service-1") should not be (null) - server.unregisterPerSession("my-service-1") - server.actorsFactories.get("my-service-1") should be (null) - } */ + remote.registerPerSession("my-service-1", actorOf[RemoteStatefullSessionActorSpec]) + remote.asInstanceOf[NettyRemoteSupport].actorsFactories.get("my-service-1") must not be (null) + remote.unregisterPerSession("my-service-1") + remote.asInstanceOf[NettyRemoteSupport].actorsFactories.get("my-service-1") must be (null) + } } } diff --git a/akka-remote/src/test/scala/remote/ShutdownSpec.scala b/akka-remote/src/test/scala/remote/ShutdownSpec.scala deleted file mode 100644 index 81d6a608ad..0000000000 --- a/akka-remote/src/test/scala/remote/ShutdownSpec.scala +++ /dev/null @@ -1,40 +0,0 @@ -package akka.remote -/* THIS SHOULD BE UNCOMMENTED -import akka.actor.Actor - -import Actor._ - -object ActorShutdownRunner { - def main(args: Array[String]) { - class MyActor extends Actor { - def receive = { - case "test" => println("received test") - case m@_ => println("received unknown message " + m) - } - } - - val myActor = actorOf[MyActor] - myActor.start - myActor ! "test" - myActor.stop - } -} - - -// case 2 - -object RemoteServerAndClusterShutdownRunner { - def main(args: Array[String]) { - val s1 = new RemoteServer - val s2 = new RemoteServer - val s3 = new RemoteServer - s1.start("localhost", 2552) - s2.start("localhost", 9998) - s3.start("localhost", 9997) - Thread.sleep(5000) - s1.shutdown - s2.shutdown - s3.shutdown - } -} -*/ \ No newline at end of file diff --git a/akka-remote/src/test/scala/serialization/ProtobufActorMessageSerializationSpec.scala b/akka-remote/src/test/scala/serialization/ProtobufActorMessageSerializationSpec.scala index 3c9aeb97f1..d0dcdac048 100644 --- a/akka-remote/src/test/scala/serialization/ProtobufActorMessageSerializationSpec.scala +++ b/akka-remote/src/test/scala/serialization/ProtobufActorMessageSerializationSpec.scala @@ -4,10 +4,6 @@ import java.util.concurrent.TimeUnit import org.scalatest._ import org.scalatest.WordSpec import org.scalatest.matchers.MustMatchers -import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} -import org.scalatest.junit.JUnitRunner -import org.junit.runner.RunWith - import akka.actor.{ProtobufProtocol, Actor} import ProtobufProtocol.ProtobufPOJO import Actor._ diff --git a/akka-remote/src/test/scala/serialization/ScalaJSONSerializerSpec.scala b/akka-remote/src/test/scala/serialization/ScalaJSONSerializerSpec.scala index 7026d1cfc0..02b29e6de1 100644 --- a/akka-remote/src/test/scala/serialization/ScalaJSONSerializerSpec.scala +++ b/akka-remote/src/test/scala/serialization/ScalaJSONSerializerSpec.scala @@ -7,6 +7,7 @@ import org.scalatest.junit.JUnitRunner import org.junit.runner.RunWith import akka.serialization.Serializer.ScalaJSON +//TODO: FIXME WHY IS THIS COMMENTED OUT? /* object Protocols { import sjson.json.DefaultProtocol._ From 1fa105faa82568c7ee73d5c873a632fa60ed3507 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 20 Dec 2010 17:04:33 +0100 Subject: [PATCH 16/38] Removing redundant call to ActorRegistry-register --- akka-actor/src/main/scala/akka/actor/ActorRef.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 28a47646c3..ff126441e9 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -593,7 +593,6 @@ class LocalActorRef private[akka] ( hotswap = __hotswap setActorSelfFields(actor,this) start - ActorRegistry.register(this) //TODO: REVISIT: Is this needed? } private final def isClientManaged_? = (homeAddress ne Remote.localAddress) && homeAddress != Remote.localAddress From c20aab06ebf5a161c0aa708575d0d3ed83e81812 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 21 Dec 2010 14:36:47 +0100 Subject: [PATCH 17/38] All tests passing, still some work to be done though, but thank God for all tests being green ;) --- .../src/main/scala/akka/actor/Actor.scala | 109 ++++++--- .../src/main/scala/akka/actor/ActorRef.scala | 76 ++++--- .../main/scala/akka/actor/ActorRegistry.scala | 120 +--------- .../main/scala/akka/actor/Supervisor.scala | 2 +- .../scala/akka/dispatch/MessageHandling.scala | 2 +- .../remoteinterface/RemoteInterface.scala | 56 +++-- .../scala/akka/util/ReflectiveAccess.scala | 12 +- .../remote/BootableRemoteActorService.scala | 3 +- .../akka/remote/NettyRemoteSupport.scala | 108 +++------ .../serialization/SerializationProtocol.scala | 14 +- .../test/scala/remote/AkkaRemoteTest.scala | 7 +- .../scala/remote/RemoteTypedActorSpec.scala | 108 +++------ .../ServerInitiatedRemoteActorSpec.scala | 8 +- ...erverInitiatedRemoteSessionActorSpec.scala | 7 - .../ServerInitiatedRemoteTypedActorSpec.scala | 210 +++++++----------- ...InitiatedRemoteTypedSessionActorSpec.scala | 140 +++++------- .../TypedActorSerializationSpec.scala | 71 +++--- .../main/scala/akka/actor/TypedActor.scala | 30 ++- .../config/TypedActorGuiceConfigurator.scala | 22 +- 19 files changed, 445 insertions(+), 660 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index c3e94188b1..618c442b89 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -115,7 +115,7 @@ object Actor extends Logging { private[actor] val actorRefInCreation = new scala.util.DynamicVariable[Option[ActorRef]](None) /** - * Creates an ActorRef out of the Actor with type T. + * Creates an ActorRef out of the Actor with type T. *

    *   import Actor._
    *   val actor = actorOf[MyActor]
@@ -128,26 +128,7 @@ object Actor extends Logging {
    *   val actor = actorOf[MyActor].start
    * 
*/ - def actorOf[T <: Actor : Manifest]: ActorRef = - ActorRegistry.actorOf[T] - - /** - * Creates a Client-managed ActorRef out of the Actor of the specified Class. - * If the supplied host and port is identical of the configured local node, it will be a local actor - *
-   *   import Actor._
-   *   val actor = actorOf[MyActor]("www.akka.io",2552)
-   *   actor.start
-   *   actor ! message
-   *   actor.stop
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf[MyActor]("www.akka.io",2552).start
-   * 
- */ - def actorOf[T <: Actor : Manifest](host: String, port: Int): ActorRef = - ActorRegistry.actorOf[T](host,port) + def actorOf[T <: Actor : Manifest]: ActorRef = actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) /** * Creates an ActorRef out of the Actor of the specified Class. @@ -163,8 +144,15 @@ object Actor extends Logging { * val actor = actorOf(classOf[MyActor]).start * */ - def actorOf(clazz: Class[_ <: Actor]): ActorRef = - ActorRegistry.actorOf(clazz) + def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(() => { + import ReflectiveAccess.{ createInstance, noParams, noArgs } + createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( + throw new ActorInitializationException( + "Could not instantiate Actor" + + "\nMake sure Actor is NOT defined inside a class/trait," + + "\nif so put it outside the class/trait, f.e. in a companion object," + + "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")) + }, None) /** * Creates a Client-managed ActorRef out of the Actor of the specified Class. @@ -181,8 +169,62 @@ object Actor extends Logging { * val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start * */ - def actorOf(clazz: Class[_ <: Actor], host: String, port: Int, timeout: Long = Actor.TIMEOUT): ActorRef = - ActorRegistry.actorOf(clazz, host, port, timeout) + def actorOf(factory: => Actor, host: String, port: Int): ActorRef = + ActorRegistry.remote.clientManagedActorOf(() => factory, host, port) + + /** + * Creates a Client-managed ActorRef out of the Actor of the specified Class. + * If the supplied host and port is identical of the configured local node, it will be a local actor + *
+   *   import Actor._
+   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552)
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start
+   * 
+ */ + def actorOf(clazz: Class[_ <: Actor], host: String, port: Int): ActorRef = { + import ReflectiveAccess.{ createInstance, noParams, noArgs } + ActorRegistry.remote.clientManagedActorOf(() => + createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( + throw new ActorInitializationException( + "Could not instantiate Actor" + + "\nMake sure Actor is NOT defined inside a class/trait," + + "\nif so put it outside the class/trait, f.e. in a companion object," + + "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")), + host, port) + } + + /** + * Creates a Client-managed ActorRef out of the Actor of the specified Class. + * If the supplied host and port is identical of the configured local node, it will be a local actor + *
+   *   import Actor._
+   *   val actor = actorOf[MyActor]("www.akka.io",2552)
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf[MyActor]("www.akka.io",2552).start
+   * 
+ */ + def actorOf[T <: Actor : Manifest](host: String, port: Int): ActorRef = { + import ReflectiveAccess.{ createInstance, noParams, noArgs } + ActorRegistry.remote.clientManagedActorOf(() => + createInstance[Actor](manifest[T].erasure.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( + throw new ActorInitializationException( + "Could not instantiate Actor" + + "\nMake sure Actor is NOT defined inside a class/trait," + + "\nif so put it outside the class/trait, f.e. in a companion object," + + "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")), + host, port) + } /** * Creates an ActorRef out of the Actor. Allows you to pass in a factory function @@ -202,25 +244,32 @@ object Actor extends Logging { * val actor = actorOf(new MyActor).start * */ - def actorOf(factory: => Actor): ActorRef = ActorRegistry.actorOf(factory) + def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory, None) /** * Use to spawn out a block of code in an event-driven actor. Will shut actor down when * the block has been executed. *

- * NOTE: If used from within an Actor then has to be qualified with 'Actor.spawn' since + * NOTE: If used from within an Actor then has to be qualified with 'ActorRegistry.spawn' since * there is a method 'spawn[ActorType]' in the Actor trait already. * Example: *

-   * import Actor._
+   * import ActorRegistry.{spawn}
    *
    * spawn  {
    *   ... // do stuff
    * }
    * 
*/ - def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = - ActorRegistry.spawn(body)(dispatcher) + def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = { + case object Spawn + actorOf(new Actor() { + self.dispatcher = dispatcher + def receive = { + case Spawn => try { body } finally { self.stop } + } + }).start ! Spawn + } /** diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index ff126441e9..cfd1dd9904 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -172,14 +172,14 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal def getDispatcher(): MessageDispatcher = dispatcher /** - * Returns on which node this actor lives + * Returns on which node this actor lives if None it lives in the local ActorRegistry */ - def homeAddress: InetSocketAddress + def homeAddress: Option[InetSocketAddress] /** * Java API */ - def getHomeAddress(): InetSocketAddress = homeAddress + def getHomeAddress(): InetSocketAddress = homeAddress getOrElse null /** * Holds the hot swapped partial function. @@ -451,7 +451,7 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal *

* To be invoked from within the actor itself. */ - def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef + def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long): ActorRef /** * Atomically create (from actor class), link and start an actor. @@ -465,7 +465,7 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal *

* To be invoked from within the actor itself. */ - def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef + def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long): ActorRef /** * Returns the mailbox size. @@ -551,7 +551,7 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal */ class LocalActorRef private[akka] ( private[this] val actorFactory: () => Actor, - val homeAddress: InetSocketAddress = Remote.localAddress) + val homeAddress: Option[InetSocketAddress]) extends ActorRef with ScalaActorRef { @volatile @@ -582,7 +582,7 @@ class LocalActorRef private[akka] ( __supervisor: Option[ActorRef], __hotswap: Stack[PartialFunction[Any, Unit]], __factory: () => Actor, - __homeAddress: InetSocketAddress) = { + __homeAddress: Option[InetSocketAddress]) = { this(__factory, __homeAddress) _uuid = __uuid id = __id @@ -595,7 +595,10 @@ class LocalActorRef private[akka] ( start } - private final def isClientManaged_? = (homeAddress ne Remote.localAddress) && homeAddress != Remote.localAddress + /** + * Returns whether this actor ref is client-managed remote or not + */ + private[akka] final def isClientManaged_? = homeAddress.isDefined && isRemotingEnabled // ========= PUBLIC FUNCTIONS ========= @@ -640,8 +643,8 @@ class LocalActorRef private[akka] ( if ((actorInstance ne null) && (actorInstance.get ne null)) initializeActorInstance - if (isRemotingEnabled && isClientManaged_?) - ActorRegistry.remote.registerClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) + if (isClientManaged_?) + ActorRegistry.remote.registerClientManagedActor(homeAddress.get.getHostName,homeAddress.get.getPort, uuid) checkReceiveTimeout //Schedule the initial Receive timeout } @@ -661,7 +664,7 @@ class LocalActorRef private[akka] ( ActorRegistry.unregister(this) if (isRemotingEnabled) { if (isClientManaged_?) - ActorRegistry.remote.registerClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) + ActorRegistry.remote.registerClientManagedActor(homeAddress.get.getHostName,homeAddress.get.getPort, uuid) ActorRegistry.remote.unregister(this) } setActorSelfFields(actorInstance.get,null) @@ -732,9 +735,11 @@ class LocalActorRef private[akka] ( *

* To be invoked from within the actor itself. */ - def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef = guard.withGuard { + def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long = Actor.TIMEOUT): ActorRef = guard.withGuard { ensureRemotingEnabled - Actor.actorOf(clazz, hostname, port).start + val ref = Actor.actorOf(clazz, hostname, port) + ref.timeout = timeout + ref.start } /** @@ -754,13 +759,15 @@ class LocalActorRef private[akka] ( *

* To be invoked from within the actor itself. */ - def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef = guard.withGuard { - ensureRemotingEnabled - val actor = Actor.actorOf(clazz, hostname, port) - link(actor) - actor.start - actor - } + def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long = Actor.TIMEOUT): ActorRef = + guard.withGuard { + ensureRemotingEnabled + val actor = Actor.actorOf(clazz, hostname, port) + actor.timeout = timeout + link(actor) + actor.start + actor + } /** * Returns the mailbox. @@ -790,9 +797,9 @@ class LocalActorRef private[akka] ( protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit = _supervisor = sup protected[akka] def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = - if (isClientManaged_? && isRemotingEnabled) { + if (isClientManaged_?) { ActorRegistry.remote.send[Any]( - message, senderOption, None, homeAddress, timeout, true, this, None, ActorType.ScalaActor, None) + message, senderOption, None, homeAddress.get, timeout, true, this, None, ActorType.ScalaActor, None) } else dispatcher dispatchMessage new MessageInvocation(this, message, senderOption, None) @@ -801,9 +808,9 @@ class LocalActorRef private[akka] ( timeout: Long, senderOption: Option[ActorRef], senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { - if (isClientManaged_? && isRemotingEnabled) { + if (isClientManaged_?) { val future = ActorRegistry.remote.send[T]( - message, senderOption, senderFuture, homeAddress, timeout, false, this, None, ActorType.ScalaActor, None) + message, senderOption, senderFuture, homeAddress.get, timeout, false, this, None, ActorType.ScalaActor, None) if (future.isDefined) future.get else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) } else { @@ -970,7 +977,8 @@ class LocalActorRef private[akka] ( protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = guard.withGuard { ensureRemotingEnabled if (_supervisor.isDefined) { - ActorRegistry.remote.registerSupervisorForActor(this) + if (homeAddress.isDefined) + ActorRegistry.remote.registerSupervisorForActor(this) Some(_supervisor.get.uuid) } else None } @@ -1108,7 +1116,7 @@ private[akka] case class RemoteActorRef private[akka] ( ensureRemotingEnabled - val homeAddress = new InetSocketAddress(hostname, port) + val homeAddress = Some(new InetSocketAddress(hostname, port)) //protected def clientManaged = classOrServiceName.isEmpty //If no class or service name, it's client managed id = classOrServiceName @@ -1119,14 +1127,14 @@ private[akka] case class RemoteActorRef private[akka] ( start def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = - ActorRegistry.remote.send[Any](message, senderOption, None, homeAddress, timeout, true, this, None, actorType, loader) + ActorRegistry.remote.send[Any](message, senderOption, None, homeAddress.get, timeout, true, this, None, actorType, loader) def postMessageToMailboxAndCreateFutureResultWithTimeout[T]( message: Any, timeout: Long, senderOption: Option[ActorRef], senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { - val future = ActorRegistry.remote.send[T](message, senderOption, senderFuture, homeAddress, timeout, false, this, None, actorType, loader) + val future = ActorRegistry.remote.send[T](message, senderOption, senderFuture, homeAddress.get, timeout, false, this, None, actorType, loader) if (future.isDefined) future.get else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) } @@ -1158,9 +1166,9 @@ private[akka] case class RemoteActorRef private[akka] ( def startLink(actorRef: ActorRef): Unit = unsupported def startLinkRemote(actorRef: ActorRef, hostname: String, port: Int): Unit = unsupported def spawn(clazz: Class[_ <: Actor]): ActorRef = unsupported - def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef = unsupported + def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long): ActorRef = unsupported def spawnLink(clazz: Class[_ <: Actor]): ActorRef = unsupported - def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef = unsupported + def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long): ActorRef = unsupported def supervisor: Option[ActorRef] = unsupported def shutdownLinkedActors: Unit = unsupported protected[akka] def mailbox: AnyRef = unsupported @@ -1397,9 +1405,9 @@ trait ScalaActorRef extends ActorRefShared { ref: ActorRef => /** * Atomically create (from actor class), start and make an actor remote. */ - def spawnRemote[T <: Actor: Manifest](hostname: String, port: Int): ActorRef = { + def spawnRemote[T <: Actor: Manifest](hostname: String, port: Int, timeout: Long): ActorRef = { ensureRemotingEnabled - spawnRemote(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]], hostname, port) + spawnRemote(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]], hostname, port, timeout) } /** @@ -1411,9 +1419,9 @@ trait ScalaActorRef extends ActorRefShared { ref: ActorRef => /** * Atomically create (from actor class), start, link and make an actor remote. */ - def spawnLinkRemote[T <: Actor: Manifest](hostname: String, port: Int): ActorRef = { + def spawnLinkRemote[T <: Actor: Manifest](hostname: String, port: Int, timeout: Long): ActorRef = { ensureRemotingEnabled - spawnLinkRemote(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]], hostname, port) + spawnLinkRemote(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]], hostname, port, timeout) } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala index e3934f1a58..d17f406e89 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala @@ -234,125 +234,9 @@ object ActorRegistry extends ListenerManagement { lazy val remote: RemoteSupport = remoteBootstrap.map(_()).getOrElse(throw new UnsupportedOperationException("You need to have akka-remote on classpath")) /** - * Creates an ActorRef out of the Actor with type T. - *

-   *   import Actor._
-   *   val actor = actorOf[MyActor]
-   *   actor.start
-   *   actor ! message
-   *   actor.stop
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf[MyActor].start
-   * 
+ * Current home address of this ActorRegistry */ - def actorOf[T <: Actor : Manifest]: ActorRef = actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) - - /** - * Creates a Client-managed ActorRef out of the Actor of the specified Class. - * If the supplied host and port is identical of the configured local node, it will be a local actor - *
-   *   import Actor._
-   *   val actor = actorOf[MyActor]("www.akka.io",2552)
-   *   actor.start
-   *   actor ! message
-   *   actor.stop
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf[MyActor]("www.akka.io",2552).start
-   * 
- */ - def actorOf[T <: Actor : Manifest](host: String, port: Int): ActorRef = - actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]], host, port) - - /** - * Creates an ActorRef out of the Actor of the specified Class. - *
-   *   import Actor._
-   *   val actor = actorOf(classOf[MyActor])
-   *   actor.start
-   *   actor ! message
-   *   actor.stop
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf(classOf[MyActor]).start
-   * 
- */ - def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(() => { - import ReflectiveAccess.{ createInstance, noParams, noArgs } - createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( - throw new ActorInitializationException( - "Could not instantiate Actor" + - "\nMake sure Actor is NOT defined inside a class/trait," + - "\nif so put it outside the class/trait, f.e. in a companion object," + - "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")) - }) - - /** - * Creates a Client-managed ActorRef out of the Actor of the specified Class. - * If the supplied host and port is identical of the configured local node, it will be a local actor - *
-   *   import Actor._
-   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552)
-   *   actor.start
-   *   actor ! message
-   *   actor.stop
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start
-   * 
- */ - def actorOf(clazz: Class[_ <: Actor], host: String, port: Int, timeout: Long = Actor.TIMEOUT): ActorRef = - remote.clientManagedActorOf(clazz, host, port, timeout) - - /** - * Creates an ActorRef out of the Actor. Allows you to pass in a factory function - * that creates the Actor. Please note that this function can be invoked multiple - * times if for example the Actor is supervised and needs to be restarted. - *

- * This function should NOT be used for remote actors. - *

-   *   import Actor._
-   *   val actor = actorOf(new MyActor)
-   *   actor.start
-   *   actor ! message
-   *   actor.stop
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf(new MyActor).start
-   * 
- */ - def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory) - - /** - * Use to spawn out a block of code in an event-driven actor. Will shut actor down when - * the block has been executed. - *

- * NOTE: If used from within an Actor then has to be qualified with 'ActorRegistry.spawn' since - * there is a method 'spawn[ActorType]' in the Actor trait already. - * Example: - *

-   * import ActorRegistry.{spawn}
-   *
-   * spawn  {
-   *   ... // do stuff
-   * }
-   * 
- */ - def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = { - case object Spawn - actorOf(new Actor() { - self.dispatcher = dispatcher - def receive = { - case Spawn => try { body } finally { self.stop } - } - }).start ! Spawn - } + def homeAddress(): InetSocketAddress = if (isRemotingEnabled) remote.address else Remote.configDefaultAddress /** diff --git a/akka-actor/src/main/scala/akka/actor/Supervisor.scala b/akka-actor/src/main/scala/akka/actor/Supervisor.scala index 288bdfda24..daf7a962de 100644 --- a/akka-actor/src/main/scala/akka/actor/Supervisor.scala +++ b/akka-actor/src/main/scala/akka/actor/Supervisor.scala @@ -142,7 +142,7 @@ sealed class Supervisor(handler: FaultHandlingStrategy) { actorRef.lifeCycle = lifeCycle supervisor.link(actorRef) if (registerAsRemoteService) - ActorRegistry.remote.register(actorRef) //TODO: REVISIT: Is this the most sensible approach? other way of obtaining ActorRegistry? + ActorRegistry.remote.register(actorRef) case supervisorConfig @ SupervisorConfig(_, _) => // recursive supervisor configuration val childSupervisor = Supervisor(supervisorConfig) supervisor.link(childSupervisor.supervisor) diff --git a/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala b/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala index d12847e149..f0f2f259d6 100644 --- a/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala +++ b/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala @@ -132,7 +132,7 @@ trait MessageDispatcher extends MailboxFactory with Logging { val i = uuids.iterator while(i.hasNext()) { val uuid = i.next() - ActorRegistry.actorFor(uuid) match { //TODO: REVISIT: How to keep track of which registry? + ActorRegistry.actorFor(uuid) match { case Some(actor) => actor.stop case None => log.slf4j.error("stopAllLinkedActors couldn't find linked actor: " + uuid) diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index 11ed70a08a..751bfbc188 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -13,6 +13,8 @@ import akka.config.Config.{config, TIME_UNIT} import java.util.concurrent.ConcurrentHashMap trait RemoteModule extends Logging { + val UUID_PREFIX = "uuid:" + def optimizeLocalScoped_?(): Boolean //Apply optimizations for remote operations in local scope protected[akka] def notifyListeners(message: => Any): Unit @@ -23,6 +25,35 @@ trait RemoteModule extends Logging { private[akka] def typedActors: ConcurrentHashMap[String, AnyRef] private[akka] def typedActorsByUuid: ConcurrentHashMap[String, AnyRef] private[akka] def typedActorsFactories: ConcurrentHashMap[String, () => AnyRef] + + + /** Lookup methods **/ + + private[akka] def findActorById(id: String) : ActorRef = actors.get(id) + + private[akka] def findActorByUuid(uuid: String) : ActorRef = actorsByUuid.get(uuid) + + private[akka] def findActorFactory(id: String) : () => ActorRef = actorsFactories.get(id) + + private[akka] def findTypedActorById(id: String) : AnyRef = typedActors.get(id) + + private[akka] def findTypedActorFactory(id: String) : () => AnyRef = typedActorsFactories.get(id) + + private[akka] def findTypedActorByUuid(uuid: String) : AnyRef = typedActorsByUuid.get(uuid) + + private[akka] def findActorByIdOrUuid(id: String, uuid: String) : ActorRef = { + var actorRefOrNull = if (id.startsWith(UUID_PREFIX)) findActorByUuid(id.substring(UUID_PREFIX.length)) + else findActorById(id) + if (actorRefOrNull eq null) actorRefOrNull = findActorByUuid(uuid) + actorRefOrNull + } + + private[akka] def findTypedActorByIdOrUuid(id: String, uuid: String) : AnyRef = { + var actorRefOrNull = if (id.startsWith(UUID_PREFIX)) findTypedActorByUuid(id.substring(UUID_PREFIX.length)) + else findTypedActorById(id) + if (actorRefOrNull eq null) actorRefOrNull = findTypedActorByUuid(uuid) + actorRefOrNull + } } @@ -35,11 +66,11 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule protected override def manageLifeCycleOfListeners = false protected[akka] override def notifyListeners(message: => Any): Unit = super.notifyListeners(message) - private[akka] val actors = new ConcurrentHashMap[String, ActorRef] - private[akka] val actorsByUuid = new ConcurrentHashMap[String, ActorRef] - private[akka] val actorsFactories = new ConcurrentHashMap[String, () => ActorRef] - private[akka] val typedActors = new ConcurrentHashMap[String, AnyRef] - private[akka] val typedActorsByUuid = new ConcurrentHashMap[String, AnyRef] + private[akka] val actors = new ConcurrentHashMap[String, ActorRef] + private[akka] val actorsByUuid = new ConcurrentHashMap[String, ActorRef] + private[akka] val actorsFactories = new ConcurrentHashMap[String, () => ActorRef] + private[akka] val typedActors = new ConcurrentHashMap[String, AnyRef] + private[akka] val typedActorsByUuid = new ConcurrentHashMap[String, AnyRef] private[akka] val typedActorsFactories = new ConcurrentHashMap[String, () => AnyRef] def clear { @@ -64,19 +95,16 @@ trait RemoteServerModule extends RemoteModule { def name: String /** - * Gets the current hostname of the server instance + * Gets the address of the server instance */ - def hostname: String - - /** - * Gets the current port of the server instance - */ - def port: Int + def address: InetSocketAddress /** * Starts the server up */ - def start(host: String = ReflectiveAccess.Remote.HOSTNAME, port: Int = ReflectiveAccess.Remote.PORT, loader: Option[ClassLoader] = None): RemoteServerModule + def start(host: String = ReflectiveAccess.Remote.configDefaultAddress.getHostName, + port: Int = ReflectiveAccess.Remote.configDefaultAddress.getPort, + loader: Option[ClassLoader] = None): RemoteServerModule /** * Shuts the server down @@ -222,7 +250,7 @@ trait RemoteClientModule extends RemoteModule { self: RemoteModule => def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, hostname: String, port: Int, loader: ClassLoader): T = typedActorFor(intfClass, serviceId, implClassName, timeout, hostname, port, Some(loader)) - def clientManagedActorOf(clazz: Class[_ <: Actor], host: String, port: Int, timeout: Long): ActorRef + def clientManagedActorOf(factory: () => Actor, host: String, port: Int): ActorRef /** Methods that needs to be implemented by a transport **/ diff --git a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala index 5ba5f513b2..b449e45552 100644 --- a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala +++ b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala @@ -21,7 +21,7 @@ object ReflectiveAccess extends Logging { val loader = getClass.getClassLoader - lazy val isRemotingEnabled = Remote.isEnabled + def isRemotingEnabled = Remote.isEnabled lazy val isTypedActorEnabled = TypedActorModule.isEnabled def ensureRemotingEnabled = Remote.ensureEnabled @@ -33,18 +33,18 @@ object ReflectiveAccess extends Logging { * @author Jonas Bonér */ object Remote { - val TRANSPORT = Config.config.getString("akka.remote.transport","akka.remote.NettyRemoteSupport") - val HOSTNAME = Config.config.getString("akka.remote.server.hostname", "localhost") - val PORT = Config.config.getInt("akka.remote.server.port", 2552) + val TRANSPORT = Config.config.getString("akka.remote.layer","akka.remote.NettyRemoteSupport") + + private[akka] val configDefaultAddress = + new InetSocketAddress(Config.config.getString("akka.remote.server.hostname", "localhost"), + Config.config.getInt("akka.remote.server.port", 2552)) - lazy val localAddress = new InetSocketAddress(HOSTNAME,PORT) lazy val isEnabled = remoteSupportClass.isDefined def ensureEnabled = if (!isEnabled) throw new ModuleNotAvailableException( "Can't load the remoting module, make sure that akka-remote.jar is on the classpath") - //TODO: REVISIT: Make class configurable val remoteSupportClass: Option[Class[_ <: RemoteSupport]] = getClassFor(TRANSPORT) protected[akka] val defaultRemoteSupport: Option[() => RemoteSupport] = remoteSupportClass map { diff --git a/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala b/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala index 58892c2ad3..d1d4d954f5 100644 --- a/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala +++ b/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala @@ -17,8 +17,7 @@ trait BootableRemoteActorService extends Bootable with Logging { self: BootableActorLoaderService => protected lazy val remoteServerThread = new Thread(new Runnable() { - import ReflectiveAccess.Remote.{HOSTNAME,PORT} - def run = ActorRegistry.remote.start(HOSTNAME,PORT,loader = self.applicationLoader) + def run = ActorRegistry.remote.start(loader = self.applicationLoader) //Use config host/port }, "Akka Remote Service") def startRemoteService = remoteServerThread.start diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index c4d2036cc4..da0f326705 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -115,10 +115,10 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem } private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef = - clientFor(actorRef.homeAddress, None).registerSupervisorForActor(actorRef) + clientFor(actorRef.homeAddress.get, None).registerSupervisorForActor(actorRef) private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef = - clientFor(actorRef.homeAddress, None).deregisterSupervisorForActor(actorRef) + clientFor(actorRef.homeAddress.get, None).deregisterSupervisorForActor(actorRef) /** * Clean-up all open connections. @@ -486,8 +486,6 @@ class RemoteClientHandler( */ object RemoteServer { val isRemotingEnabled = config.getList("akka.enabled-modules").exists(_ == "remote") - - val UUID_PREFIX = "uuid:" val MESSAGE_FRAME_SIZE = config.getInt("akka.remote.server.message-frame-size", 1048576) val SECURE_COOKIE = config.getString("akka.remote.secure-cookie") val REQUIRE_COOKIE = { @@ -563,31 +561,22 @@ class NettyRemoteSupport extends RemoteSupport with NettyRemoteServerModule with def optimizeLocalScoped_?() = optimizeLocal.get - protected[akka] def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): ActorRef = { - //TODO: REVISIT: Possible to optimize server-managed actors in local scope? - //val Host = this.hostname - //val Port = this.port + protected[akka] def actorFor(serviceId: String, className: String, timeout: Long, host: String, port: Int, loader: Option[ClassLoader]): ActorRef = { + if (optimizeLocalScoped_?) { + val home = this.address + if (host == home.getHostName && port == home.getPort) {//TODO: switch to InetSocketAddres.equals? + val localRef = findActorByIdOrUuid(serviceId,serviceId) - //(host,port) match { - // case (Host, Port) if optimizeLocalScoped_? => - //if actor with that servicename or uuid is present locally, return a LocalActorRef to that one - //else return RemoteActorRef(registry, serviceId, className, hostname, port, timeout, false, loader) - // case _ => - // RemoteActorRef(registry, serviceId, className, hostname, port, timeout, false, loader) - //} - RemoteActorRef(serviceId, className, hostname, port, timeout, loader) + if (localRef ne null) return localRef //Code significantly simpler with the return statement + } + } + + RemoteActorRef(serviceId, className, host, port, timeout, loader) } - def clientManagedActorOf(clazz: Class[_ <: Actor], host: String, port: Int, timeout: Long): ActorRef = { - import ReflectiveAccess.{ createInstance, noParams, noArgs } - val ref = new LocalActorRef(() => createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( - throw new ActorInitializationException( - "Could not instantiate Actor" + - "\nMake sure Actor is NOT defined inside a class/trait," + - "\nif so put it outside the class/trait, f.e. in a companion object," + - "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")), - new InetSocketAddress(host, port)) - ref.timeout = timeout + def clientManagedActorOf(factory: () => Actor, host: String, port: Int): ActorRef = { + val ref = new LocalActorRef(factory, Some(new InetSocketAddress(host, port))) + //ref.timeout = timeout //removed because setting default timeout should be done after construction ref } } @@ -595,6 +584,7 @@ class NettyRemoteSupport extends RemoteSupport with NettyRemoteServerModule with class NettyRemoteServer(serverModule: NettyRemoteServerModule, val host: String, val port: Int, val loader: Option[ClassLoader]) { val name = "NettyRemoteServer@" + host + ":" + port + val address = new InetSocketAddress(host,port) private val factory = new NioServerSocketChannelFactory(Executors.newCachedThreadPool,Executors.newCachedThreadPool) @@ -610,7 +600,7 @@ class NettyRemoteServer(serverModule: NettyRemoteServerModule, val host: String, bootstrap.setOption("child.reuseAddress", true) bootstrap.setOption("child.connectTimeoutMillis", RemoteServer.CONNECTION_TIMEOUT_MILLIS.toMillis) - openChannels.add(bootstrap.bind(new InetSocketAddress(host, port))) + openChannels.add(bootstrap.bind(address)) serverModule.notifyListeners(RemoteServerStarted(serverModule)) def shutdown { @@ -630,19 +620,16 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => import RemoteServer._ private[akka] val currentServer = new AtomicReference[Option[NettyRemoteServer]](None) - def hostname = currentServer.get match { - case Some(s) => s.host - case None => ReflectiveAccess.Remote.HOSTNAME - } - - def port = currentServer.get match { - case Some(s) => s.port - case None => ReflectiveAccess.Remote.PORT + def address = currentServer.get match { + case Some(s) => s.address + case None => ReflectiveAccess.Remote.configDefaultAddress } def name = currentServer.get match { case Some(s) => s.name - case None => "NettyRemoteServer@" + ReflectiveAccess.Remote.HOSTNAME + ":" + ReflectiveAccess.Remote.PORT + case None => + val a = ReflectiveAccess.Remote.configDefaultAddress + "NettyRemoteServer@" + a.getHostName + ":" + a.getPort } private val _isRunning = new Switch(false) @@ -1096,57 +1083,18 @@ class RemoteServerHandler( } } - private def findActorById(id: String) : ActorRef = { - server.actors.get(id) - } - - private def findActorByUuid(uuid: String) : ActorRef = { - log.slf4j.debug("Trying to find actor for uuid '{}' inside {}",uuid,server.actorsByUuid) - server.actorsByUuid.get(uuid) - } - - private def findActorFactory(id: String) : () => ActorRef = { - server.actorsFactories.get(id) - } - private def findSessionActor(id: String, channel: Channel) : ActorRef = { val map = sessionActors.get(channel) if (map ne null) map.get(id) else null } - private def findTypedActorById(id: String) : AnyRef = { - server.typedActors.get(id) - } - - private def findTypedActorFactory(id: String) : () => AnyRef = { - server.typedActorsFactories.get(id) - } - private def findTypedSessionActor(id: String, channel: Channel) : AnyRef = { val map = typedSessionActors.get(channel) if (map ne null) map.get(id) else null } - private def findTypedActorByUuid(uuid: String) : AnyRef = { - server.typedActorsByUuid.get(uuid) - } - - private def findActorByIdOrUuid(id: String, uuid: String) : ActorRef = { - var actorRefOrNull = if (id.startsWith(UUID_PREFIX)) findActorByUuid(id.substring(UUID_PREFIX.length)) - else findActorById(id) - if (actorRefOrNull eq null) actorRefOrNull = findActorByUuid(uuid) - actorRefOrNull - } - - private def findTypedActorByIdOrUuid(id: String, uuid: String) : AnyRef = { - var actorRefOrNull = if (id.startsWith(UUID_PREFIX)) findTypedActorByUuid(id.substring(UUID_PREFIX.length)) - else findTypedActorById(id) - if (actorRefOrNull eq null) actorRefOrNull = findTypedActorByUuid(uuid) - actorRefOrNull - } - /** * gets the actor from the session, or creates one if there is a factory for it */ @@ -1159,7 +1107,7 @@ class RemoteServerHandler( sessionActorRefOrNull } else { // we dont have it in the session either, see if we have a factory for it - val actorFactoryOrNull = findActorFactory(id) + val actorFactoryOrNull = server.findActorFactory(id) if (actorFactoryOrNull ne null) { val actorRef = actorFactoryOrNull() actorRef.uuid = uuidFrom(uuid.getHigh,uuid.getLow) @@ -1185,7 +1133,7 @@ class RemoteServerHandler( log.slf4j.info("Creating a new client-managed remote actor [{}:{}]", name, uuid) val clazz = if (applicationLoader.isDefined) applicationLoader.get.loadClass(name) else Class.forName(name) - val actorRef = ActorRegistry.actorOf(clazz.asInstanceOf[Class[_ <: Actor]]) + val actorRef = Actor.actorOf(clazz.asInstanceOf[Class[_ <: Actor]]) actorRef.uuid = uuidFrom(uuid.getHigh,uuid.getLow) actorRef.id = id actorRef.timeout = timeout @@ -1211,7 +1159,7 @@ class RemoteServerHandler( val uuid = actorInfo.getUuid val id = actorInfo.getId - val actorRefOrNull = findActorByIdOrUuid(id, uuidFrom(uuid.getHigh,uuid.getLow).toString) + val actorRefOrNull = server.findActorByIdOrUuid(id, uuidFrom(uuid.getHigh,uuid.getLow).toString) if (actorRefOrNull ne null) actorRefOrNull @@ -1235,7 +1183,7 @@ class RemoteServerHandler( if (sessionActorRefOrNull ne null) sessionActorRefOrNull else { - val actorFactoryOrNull = findTypedActorFactory(id) + val actorFactoryOrNull = server.findTypedActorFactory(id) if (actorFactoryOrNull ne null) { val newInstance = actorFactoryOrNull() typedSessionActors.get(channel).put(id, newInstance) @@ -1280,7 +1228,7 @@ class RemoteServerHandler( val uuid = actorInfo.getUuid val id = actorInfo.getId - val typedActorOrNull = findTypedActorByIdOrUuid(id, uuidFrom(uuid.getHigh,uuid.getLow).toString) + val typedActorOrNull = server.findTypedActorByIdOrUuid(id, uuidFrom(uuid.getHigh,uuid.getLow).toString) if (typedActorOrNull ne null) typedActorOrNull else diff --git a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala index 92edf9d582..83b75dd5da 100644 --- a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -17,7 +17,6 @@ import scala.collection.immutable.Stack import com.google.protobuf.ByteString import akka.util.ReflectiveAccess -import akka.util.ReflectiveAccess.Remote.{HOSTNAME,PORT} import java.net.InetSocketAddress /** @@ -91,11 +90,14 @@ object ActorSerialization { def toBinaryJ[T <: Actor](a: ActorRef, format: Format[T], srlMailBox: Boolean = true): Array[Byte] = toBinary(a, srlMailBox)(format) - private[akka] def toAddressProtocol(actorRef: ActorRef) = + private[akka] def toAddressProtocol(actorRef: ActorRef) = { + val address = actorRef.homeAddress.getOrElse(ActorRegistry.remote.address) AddressProtocol.newBuilder - .setHostname(actorRef.homeAddress.getHostName) - .setPort(actorRef.homeAddress.getPort) + .setHostname(address.getHostName) + .setPort(address.getPort) .build + } + private[akka] def toSerializedActorRefProtocol[T <: Actor]( actorRef: ActorRef, format: Format[T], serializeMailBox: Boolean = true): SerializedActorRefProtocol = { @@ -129,7 +131,7 @@ object ActorSerialization { messages.map(m => RemoteActorSerialization.createRemoteMessageProtocolBuilder( Some(actorRef), - Left(actorRef.uuid), //TODO: REVISIT: generate uuid for the request + Left(actorRef.uuid), actorRef.id, actorRef.actorClassName, actorRef.timeout, @@ -201,7 +203,7 @@ object ActorSerialization { supervisor, hotswap, factory, - new InetSocketAddress(protocol.getOriginalAddress.getHostname,protocol.getOriginalAddress.getPort)) + None) //TODO: shouldn't originalAddress be optional? val messages = protocol.getMessagesList.toArray.toList.asInstanceOf[List[RemoteMessageProtocol]] messages.foreach(message => ar ! MessageSerializer.deserialize(message.getMessage)) diff --git a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala index b22eaa8b27..2c2ee4ca9a 100644 --- a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala +++ b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala @@ -27,8 +27,9 @@ class AkkaRemoteTest extends val remote = ActorRegistry.remote val unit = TimeUnit.SECONDS - val host = remote.hostname - val port = remote.port + + val host = "localhost" + val port = 25520 var optimizeLocal_? = remote.asInstanceOf[NettyRemoteSupport].optimizeLocalScoped_? @@ -41,7 +42,7 @@ class AkkaRemoteTest extends } override def beforeEach { - remote.start() + remote.start(host,port) Thread.sleep(2000) super.beforeEach } diff --git a/akka-remote/src/test/scala/remote/RemoteTypedActorSpec.scala b/akka-remote/src/test/scala/remote/RemoteTypedActorSpec.scala index ce66a3d108..2301d4b253 100644 --- a/akka-remote/src/test/scala/remote/RemoteTypedActorSpec.scala +++ b/akka-remote/src/test/scala/remote/RemoteTypedActorSpec.scala @@ -4,23 +4,11 @@ package akka.actor.remote -import org.scalatest.matchers.ShouldMatchers -import org.scalatest.junit.JUnitRunner -import org.junit.runner.RunWith - import akka.config.Supervision._ import akka.actor._ -import akka.remote.{RemoteServer, RemoteClient} import java.util.concurrent.{LinkedBlockingQueue, TimeUnit, BlockingQueue} -import org.scalatest.{BeforeAndAfterEach, Spec, Assertions, BeforeAndAfterAll} -import akka.config.{Config, TypedActorConfigurator, RemoteAddress} -/* THIS SHOULD BE UNCOMMENTED -object RemoteTypedActorSpec { - val HOSTNAME = "localhost" - val PORT = 9988 - var server: RemoteServer = null -}*/ +import akka.config. {RemoteAddress, Config, TypedActorConfigurator} object RemoteTypedActorLog { val messageLog: BlockingQueue[String] = new LinkedBlockingQueue[String] @@ -31,22 +19,17 @@ object RemoteTypedActorLog { oneWayLog.clear } } -/* THIS SHOULD BE UNCOMMENTED -@RunWith(classOf[JUnitRunner]) -class RemoteTypedActorSpec extends - Spec with - ShouldMatchers with - BeforeAndAfterEach with BeforeAndAfterAll { + +class RemoteTypedActorSpec extends AkkaRemoteTest { import RemoteTypedActorLog._ - import RemoteTypedActorSpec._ - private val conf = new TypedActorConfigurator + private var conf: TypedActorConfigurator = _ - override def beforeAll = { - server = new RemoteServer() - server.start("localhost", 9995) + override def beforeEach { + super.beforeEach Config.config + conf = new TypedActorConfigurator conf.configure( new AllForOneStrategy(List(classOf[Exception]), 3, 5000), List( @@ -55,74 +38,57 @@ class RemoteTypedActorSpec extends classOf[RemoteTypedActorOneImpl], Permanent, 10000, - new RemoteAddress("localhost", 9995)), + RemoteAddress(host,port)), new SuperviseTypedActor( classOf[RemoteTypedActorTwo], classOf[RemoteTypedActorTwoImpl], Permanent, 10000, - new RemoteAddress("localhost", 9995)) + RemoteAddress(host,port)) ).toArray).supervise + } + + override def afterEach { + clearMessageLogs + conf.stop + super.afterEach Thread.sleep(1000) } - override def afterAll = { - conf.stop - try { - server.shutdown - RemoteClient.shutdownAll - Thread.sleep(1000) - } catch { - case e => () - } - ActorRegistry.shutdownAll - } + "Remote Typed Actor " should { - override def afterEach() { - server.typedActors.clear - } - - describe("Remote Typed Actor ") { - - it("should receive one-way message") { - clearMessageLogs + /*"receives one-way message" in { val ta = conf.getInstance(classOf[RemoteTypedActorOne]) - expect("oneway") { - ta.oneWay - oneWayLog.poll(5, TimeUnit.SECONDS) - } + ta.oneWay + oneWayLog.poll(5, TimeUnit.SECONDS) must equal ("oneway") } - it("should respond to request-reply message") { - clearMessageLogs + "responds to request-reply message" in { + val ta = conf.getInstance(classOf[RemoteTypedActorOne]) + ta.requestReply("ping") must equal ("pong") + } */ + + "be restarted on failure" in { val ta = conf.getInstance(classOf[RemoteTypedActorOne]) - expect("pong") { - ta.requestReply("ping") - } - } - - it("should be restarted on failure") { - clearMessageLogs - val ta = conf.getInstance(classOf[RemoteTypedActorOne]) - - intercept[RuntimeException] { + try { ta.requestReply("die") - } - messageLog.poll(5, TimeUnit.SECONDS) should equal ("Expected exception; to test fault-tolerance") + fail("Shouldn't get here") + } catch { case re: RuntimeException if re.getMessage == "Expected exception; to test fault-tolerance" => } + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") } - it("should restart linked friends on failure") { - clearMessageLogs + /* "restarts linked friends on failure" in { val ta1 = conf.getInstance(classOf[RemoteTypedActorOne]) val ta2 = conf.getInstance(classOf[RemoteTypedActorTwo]) - intercept[RuntimeException] { + try { ta1.requestReply("die") - } - messageLog.poll(5, TimeUnit.SECONDS) should equal ("Expected exception; to test fault-tolerance") - messageLog.poll(5, TimeUnit.SECONDS) should equal ("Expected exception; to test fault-tolerance") - } + fail("Shouldn't get here") + } catch { case re: RuntimeException if re.getMessage == "Expected exception; to test fault-tolerance" => } + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") + }*/ } -} */ +} diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala index c09e266e62..37b303bead 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala @@ -1,16 +1,10 @@ package akka.actor.remote import java.util.concurrent.{CountDownLatch, TimeUnit} -import org.scalatest.WordSpec -import org.scalatest.matchers.MustMatchers -import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} -import org.scalatest.junit.JUnitRunner -import org.junit.runner.RunWith -import akka.util._ import akka.actor.Actor._ import akka.actor.{ActorRegistry, ActorRef, Actor} -import akka.remote. {NettyRemoteSupport, RemoteServer, RemoteClient} +import akka.remote. {NettyRemoteSupport} object ServerInitiatedRemoteActorSpec { case class Send(actor: ActorRef) diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala index b0b10e5ada..6cad41e5e7 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala @@ -4,13 +4,6 @@ package akka.actor.remote -import org.scalatest._ -import org.scalatest.WordSpec -import org.scalatest.matchers.MustMatchers -import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} -import org.scalatest.junit.JUnitRunner -import org.junit.runner.RunWith - import akka.actor._ import akka.actor.Actor._ import akka.remote.NettyRemoteSupport diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala index d504d7c4e3..79de741377 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala @@ -2,137 +2,99 @@ * Copyright (C) 2009-2010 Scalable Solutions AB */ -/* THIS SHOULD BE UNCOMMENTED package akka.actor.remote -import org.scalatest.Spec -import org.scalatest.matchers.ShouldMatchers -import org.scalatest.BeforeAndAfterAll -import org.scalatest.junit.JUnitRunner -import org.junit.runner.RunWith - import java.util.concurrent.TimeUnit import akka.remote.{RemoteServer, RemoteClient} import akka.actor._ import RemoteTypedActorLog._ -object ServerInitiatedRemoteTypedActorSpec { - val HOSTNAME = "localhost" - val PORT = 9990 - var server: RemoteServer = null +class ServerInitiatedRemoteTypedActorSpec extends AkkaRemoteTest { + + override def beforeEach = { + super.beforeEach + val typedActor = TypedActor.newInstance(classOf[RemoteTypedActorOne], classOf[RemoteTypedActorOneImpl], 1000) + remote.registerTypedActor("typed-actor-service", typedActor) + } + + override def afterEach { + super.afterEach + clearMessageLogs + } + + def createRemoteActorRef = remote.typedActorFor(classOf[RemoteTypedActorOne], "typed-actor-service", 5000L, host, port) + + "Server managed remote typed Actor " should { + + "receive one-way message" in { + val actor = createRemoteActorRef + actor.oneWay + oneWayLog.poll(5, TimeUnit.SECONDS) must equal ("oneway") + } + + "should respond to request-reply message" in { + val actor = createRemoteActorRef + actor.requestReply("ping") must equal ("pong") + } + + "should not recreate registered actors" in { + val actor = createRemoteActorRef + val numberOfActorsInRegistry = ActorRegistry.actors.length + actor.oneWay + oneWayLog.poll(5, TimeUnit.SECONDS) must equal ("oneway") + numberOfActorsInRegistry must be (ActorRegistry.actors.length) + } + + "should support multiple variants to get the actor from client side" in { + var actor = createRemoteActorRef + + actor.oneWay + oneWayLog.poll(5, TimeUnit.SECONDS) must equal ("oneway") + + actor = remote.typedActorFor(classOf[RemoteTypedActorOne], "typed-actor-service", host, port) + + actor.oneWay + oneWayLog.poll(5, TimeUnit.SECONDS) must equal ("oneway") + + actor = remote.typedActorFor(classOf[RemoteTypedActorOne], "typed-actor-service", 5000L, host, port, this.getClass().getClassLoader) + + actor.oneWay + oneWayLog.poll(5, TimeUnit.SECONDS) must equal ("oneway") + } + + "should register and unregister typed actors" in { + val typedActor = TypedActor.newInstance(classOf[RemoteTypedActorOne], classOf[RemoteTypedActorOneImpl], 1000) + remote.registerTypedActor("my-test-service", typedActor) + remote.typedActors.get("my-test-service") must not be (null) + remote.unregisterTypedActor("my-test-service") + remote.typedActors.get("my-test-service") must be (null) + } + + "should register and unregister typed actors by uuid" in { + val typedActor = TypedActor.newInstance(classOf[RemoteTypedActorOne], classOf[RemoteTypedActorOneImpl], 1000) + val init = AspectInitRegistry.initFor(typedActor) + val uuid = "uuid:" + init.actorRef.uuid + + remote.registerTypedActor(uuid, typedActor) + remote.typedActorsByUuid.get(init.actorRef.uuid.toString) must not be (null) + + remote.unregisterTypedActor(uuid) + remote.typedActorsByUuid.get(init.actorRef.uuid.toString) must be (null) + } + + "should find typed actors by uuid" in { + val typedActor = TypedActor.newInstance(classOf[RemoteTypedActorOne], classOf[RemoteTypedActorOneImpl], 1000) + val init = AspectInitRegistry.initFor(typedActor) + val uuid = "uuid:" + init.actorRef.uuid + + remote.registerTypedActor(uuid, typedActor) + remote.typedActorsByUuid.get(init.actorRef.uuid.toString) must not be (null) + + val actor = remote.typedActorFor(classOf[RemoteTypedActorOne], uuid, host, port) + actor.oneWay + oneWayLog.poll(5, TimeUnit.SECONDS) must equal ("oneway") + } + } } -@RunWith(classOf[JUnitRunner]) -class ServerInitiatedRemoteTypedActorSpec extends - Spec with - ShouldMatchers with - BeforeAndAfterAll { - import ServerInitiatedRemoteTypedActorSpec._ - - private val unit = TimeUnit.MILLISECONDS - - - override def beforeAll = { - server = new RemoteServer() - server.start(HOSTNAME, PORT) - - val typedActor = TypedActor.newInstance(classOf[RemoteTypedActorOne], classOf[RemoteTypedActorOneImpl], 1000) - server.registerTypedActor("typed-actor-service", typedActor) - - Thread.sleep(1000) - } - - // make sure the servers shutdown cleanly after the test has finished - override def afterAll = { - try { - server.shutdown - RemoteClient.shutdownAll - Thread.sleep(1000) - } catch { - case e => () - } - } - - describe("Server managed remote typed Actor ") { - - it("should receive one-way message") { - clearMessageLogs - val actor = RemoteClient.typedActorFor(classOf[RemoteTypedActorOne], "typed-actor-service", 5000L, HOSTNAME, PORT) - expect("oneway") { - actor.oneWay - oneWayLog.poll(5, TimeUnit.SECONDS) - } - } - - it("should respond to request-reply message") { - clearMessageLogs - val actor = RemoteClient.typedActorFor(classOf[RemoteTypedActorOne], "typed-actor-service", 5000L, HOSTNAME, PORT) - expect("pong") { - actor.requestReply("ping") - } - } - - it("should not recreate registered actors") { - val actor = RemoteClient.typedActorFor(classOf[RemoteTypedActorOne], "typed-actor-service", 5000L, HOSTNAME, PORT) - val numberOfActorsInRegistry = ActorRegistry.actors.length - expect("oneway") { - actor.oneWay - oneWayLog.poll(5, TimeUnit.SECONDS) - } - assert(numberOfActorsInRegistry === ActorRegistry.actors.length) - } - - it("should support multiple variants to get the actor from client side") { - var actor = RemoteClient.typedActorFor(classOf[RemoteTypedActorOne], "typed-actor-service", 5000L, HOSTNAME, PORT) - expect("oneway") { - actor.oneWay - oneWayLog.poll(5, TimeUnit.SECONDS) - } - actor = RemoteClient.typedActorFor(classOf[RemoteTypedActorOne], "typed-actor-service", HOSTNAME, PORT) - expect("oneway") { - actor.oneWay - oneWayLog.poll(5, TimeUnit.SECONDS) - } - actor = RemoteClient.typedActorFor(classOf[RemoteTypedActorOne], "typed-actor-service", 5000L, HOSTNAME, PORT, this.getClass().getClassLoader) - expect("oneway") { - actor.oneWay - oneWayLog.poll(5, TimeUnit.SECONDS) - } - } - - it("should register and unregister typed actors") { - val typedActor = TypedActor.newInstance(classOf[RemoteTypedActorOne], classOf[RemoteTypedActorOneImpl], 1000) - server.registerTypedActor("my-test-service", typedActor) - assert(server.typedActors.get("my-test-service") ne null, "typed actor registered") - server.unregisterTypedActor("my-test-service") - assert(server.typedActors.get("my-test-service") eq null, "typed actor unregistered") - } - - it("should register and unregister typed actors by uuid") { - val typedActor = TypedActor.newInstance(classOf[RemoteTypedActorOne], classOf[RemoteTypedActorOneImpl], 1000) - val init = AspectInitRegistry.initFor(typedActor) - val uuid = "uuid:" + init.actorRef.uuid - server.registerTypedActor(uuid, typedActor) - assert(server.typedActorsByUuid.get(init.actorRef.uuid.toString) ne null, "typed actor registered") - server.unregisterTypedActor(uuid) - assert(server.typedActorsByUuid.get(init.actorRef.uuid.toString) eq null, "typed actor unregistered") - } - - it("should find typed actors by uuid") { - val typedActor = TypedActor.newInstance(classOf[RemoteTypedActorOne], classOf[RemoteTypedActorOneImpl], 1000) - val init = AspectInitRegistry.initFor(typedActor) - val uuid = "uuid:" + init.actorRef.uuid - server.registerTypedActor(uuid, typedActor) - assert(server.typedActorsByUuid.get(init.actorRef.uuid.toString) ne null, "typed actor registered") - - val actor = RemoteClient.typedActorFor(classOf[RemoteTypedActorOne], uuid, HOSTNAME, PORT) - expect("oneway") { - actor.oneWay - oneWayLog.poll(5, TimeUnit.SECONDS) - } - - } - } -} */ - diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedSessionActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedSessionActorSpec.scala index 0217734745..5bbd0f4a29 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedSessionActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedSessionActorSpec.scala @@ -2,108 +2,74 @@ * Copyright (C) 2009-2010 Scalable Solutions AB */ -/* THIS SHOULD BE UNCOMMENTED package akka.actor.remote -import org.scalatest._ -import org.scalatest.matchers.ShouldMatchers -import org.scalatest.BeforeAndAfterAll -import org.scalatest.junit.JUnitRunner -import org.junit.runner.RunWith - -import java.util.concurrent.TimeUnit - -import akka.remote.{RemoteServer, RemoteClient} import akka.actor._ import RemoteTypedActorLog._ -object ServerInitiatedRemoteTypedSessionActorSpec { - val HOSTNAME = "localhost" - val PORT = 9990 - var server: RemoteServer = null -} - -@RunWith(classOf[JUnitRunner]) -class ServerInitiatedRemoteTypedSessionActorSpec extends - FlatSpec with - ShouldMatchers with - BeforeAndAfterEach { - import ServerInitiatedRemoteTypedActorSpec._ - - private val unit = TimeUnit.MILLISECONDS +class ServerInitiatedRemoteTypedSessionActorSpec extends AkkaRemoteTest { override def beforeEach = { - server = new RemoteServer() - server.start(HOSTNAME, PORT) + super.beforeEach - server.registerTypedPerSessionActor("typed-session-actor-service", + remote.registerTypedPerSessionActor("typed-session-actor-service", TypedActor.newInstance(classOf[RemoteTypedSessionActor], classOf[RemoteTypedSessionActorImpl], 1000)) - - Thread.sleep(1000) } // make sure the servers shutdown cleanly after the test has finished override def afterEach = { - try { - server.shutdown - RemoteClient.shutdownAll + super.afterEach + clearMessageLogs + } + + "A remote session Actor" should { + "create a new session actor per connection" in { + + val session1 = remote.typedActorFor(classOf[RemoteTypedSessionActor], "typed-session-actor-service", 5000L, host, port) + + session1.getUser() must equal ("anonymous") + session1.login("session[1]") + session1.getUser() must equal ("session[1]") + + remote.shutdownClientModule + + val session2 = remote.typedActorFor(classOf[RemoteTypedSessionActor], "typed-session-actor-service", 5000L, host, port) + + session2.getUser() must equal ("anonymous") + + } + + "stop the actor when the client disconnects" in { + val session1 = remote.typedActorFor(classOf[RemoteTypedSessionActor], "typed-session-actor-service", 5000L, host, port) + + session1.getUser() must equal ("anonymous") + + RemoteTypedSessionActorImpl.getInstances() must have size (1) + remote.shutdownClientModule Thread.sleep(1000) - } catch { - case e => () + RemoteTypedSessionActorImpl.getInstances() must have size (0) + + } + + "stop the actor when there is an error" in { + val session1 = remote.typedActorFor(classOf[RemoteTypedSessionActor], "typed-session-actor-service", 5000L, host, port) + + session1.doSomethingFunny() + + remote.shutdownClientModule + Thread.sleep(1000) + RemoteTypedSessionActorImpl.getInstances() must have size (0) + } + + + "be able to unregister" in { + remote.registerTypedPerSessionActor("my-service-1",TypedActor.newInstance(classOf[RemoteTypedSessionActor], classOf[RemoteTypedSessionActorImpl], 1000)) + + remote.typedActorsFactories.get("my-service-1") must not be (null) + remote.unregisterTypedPerSessionActor("my-service-1") + remote.typedActorsFactories.get("my-service-1") must be (null) } } - - "A remote session Actor" should "create a new session actor per connection" in { - clearMessageLogs - - val session1 = RemoteClient.typedActorFor(classOf[RemoteTypedSessionActor], "typed-session-actor-service", 5000L, HOSTNAME, PORT) - - session1.getUser() should equal ("anonymous") - session1.login("session[1]") - session1.getUser() should equal ("session[1]") - - RemoteClient.shutdownAll - - val session2 = RemoteClient.typedActorFor(classOf[RemoteTypedSessionActor], "typed-session-actor-service", 5000L, HOSTNAME, PORT) - - session2.getUser() should equal ("anonymous") - - } - - it should "stop the actor when the client disconnects" in { - - val session1 = RemoteClient.typedActorFor(classOf[RemoteTypedSessionActor], "typed-session-actor-service", 5000L, HOSTNAME, PORT) - - session1.getUser() should equal ("anonymous") - - RemoteTypedSessionActorImpl.getInstances() should have size (1) - RemoteClient.shutdownAll - Thread.sleep(1000) - RemoteTypedSessionActorImpl.getInstances() should have size (0) - - } - - it should "stop the actor when there is an error" in { - - val session1 = RemoteClient.typedActorFor(classOf[RemoteTypedSessionActor], "typed-session-actor-service", 5000L, HOSTNAME, PORT) - - session1.doSomethingFunny() - - RemoteClient.shutdownAll - Thread.sleep(1000) - RemoteTypedSessionActorImpl.getInstances() should have size (0) - - } - - - it should "be able to unregister" in { - server.registerTypedPerSessionActor("my-service-1",TypedActor.newInstance(classOf[RemoteTypedSessionActor], classOf[RemoteTypedSessionActorImpl], 1000)) - - server.typedActorsFactories.get("my-service-1") should not be (null) - server.unregisterTypedPerSessionActor("my-service-1") - server.typedActorsFactories.get("my-service-1") should be (null) - } - -}*/ +} diff --git a/akka-remote/src/test/scala/serialization/TypedActorSerializationSpec.scala b/akka-remote/src/test/scala/serialization/TypedActorSerializationSpec.scala index 9333736821..861a77d7e0 100644 --- a/akka-remote/src/test/scala/serialization/TypedActorSerializationSpec.scala +++ b/akka-remote/src/test/scala/serialization/TypedActorSerializationSpec.scala @@ -2,49 +2,30 @@ * Copyright (C) 2009-2010 Scalable Solutions AB */ -/* THIS SHOULD BE UNCOMMENTED -package akka.actor.serialization -import org.scalatest.Spec -import org.scalatest.matchers.ShouldMatchers -import org.scalatest.BeforeAndAfterAll -import org.scalatest.junit.JUnitRunner -import org.junit.runner.RunWith +package akka.actor.serialization import akka.serialization._ import akka.actor._ import TypedActorSerialization._ import Actor._ -import akka.remote.{RemoteClient, RemoteServer} import akka.actor.remote.ServerInitiatedRemoteActorSpec.RemoteActorSpecActorUnidirectional +import akka.actor.remote.AkkaRemoteTest -@RunWith(classOf[JUnitRunner]) -class TypedActorSerializationSpec extends - Spec with - ShouldMatchers with - BeforeAndAfterAll { - - var server1: RemoteServer = null +class TypedActorSerializationSpec extends AkkaRemoteTest { var typedActor: MyTypedActor = null override def beforeAll = { - server1 = new RemoteServer().start("localhost", 9991) + super.beforeAll typedActor = TypedActor.newInstance(classOf[MyTypedActor], classOf[MyTypedActorImpl], 1000) - server1.registerTypedActor("typed-actor-service", typedActor) - Thread.sleep(1000) + remote.registerTypedActor("typed-actor-service", typedActor) } // make sure the servers shutdown cleanly after the test has finished override def afterAll = { - try { - TypedActor.stop(typedActor) - server1.shutdown - RemoteClient.shutdownAll - Thread.sleep(1000) - } catch { - case e => () - } + TypedActor.stop(typedActor) + super.afterAll } object MyTypedStatelessActorFormat extends StatelessActorFormat[MyStatelessTypedActorImpl] @@ -71,48 +52,48 @@ class TypedActorSerializationSpec extends } - describe("Serializable typed actor") { + "Serializable typed actor" should { - it("should be able to serialize and de-serialize a stateless typed actor") { + "should be able to serialize and de-serialize a stateless typed actor" in { val typedActor1 = TypedActor.newInstance(classOf[MyTypedActor], classOf[MyStatelessTypedActorImpl], 1000) - typedActor1.requestReply("hello") should equal("world") - typedActor1.requestReply("hello") should equal("world") + typedActor1.requestReply("hello") must equal("world") + typedActor1.requestReply("hello") must equal("world") val bytes = toBinaryJ(typedActor1, MyTypedStatelessActorFormat) val typedActor2: MyTypedActor = fromBinaryJ(bytes, MyTypedStatelessActorFormat) - typedActor2.requestReply("hello") should equal("world") + typedActor2.requestReply("hello") must equal("world") } - it("should be able to serialize and de-serialize a stateful typed actor") { + "should be able to serialize and de-serialize a stateful typed actor" in { val typedActor1 = TypedActor.newInstance(classOf[MyTypedActor], classOf[MyTypedActorImpl], 1000) - typedActor1.requestReply("hello") should equal("world 1") - typedActor1.requestReply("scala") should equal("hello scala 2") + typedActor1.requestReply("hello") must equal("world 1") + typedActor1.requestReply("scala") must equal("hello scala 2") val f = new MyTypedActorFormat val bytes = toBinaryJ(typedActor1, f) val typedActor2: MyTypedActor = fromBinaryJ(bytes, f) - typedActor2.requestReply("hello") should equal("world 3") + typedActor2.requestReply("hello") must equal("world 3") } - it("should be able to serialize and de-serialize a stateful typed actor with compound state") { + "should be able to serialize and de-serialize a stateful typed actor with compound state" in { val typedActor1 = TypedActor.newInstance(classOf[MyTypedActor], classOf[MyTypedActorWithDualCounter], 1000) - typedActor1.requestReply("hello") should equal("world 1 1") - typedActor1.requestReply("hello") should equal("world 2 2") + typedActor1.requestReply("hello") must equal("world 1 1") + typedActor1.requestReply("hello") must equal("world 2 2") val f = new MyTypedActorWithDualCounterFormat val bytes = toBinaryJ(typedActor1, f) val typedActor2: MyTypedActor = fromBinaryJ(bytes, f) - typedActor2.requestReply("hello") should equal("world 3 3") + typedActor2.requestReply("hello") must equal("world 3 3") } - it("should be able to serialize a local yped actor ref to a remote typed actor ref proxy") { + "should be able to serialize a local yped actor ref to a remote typed actor ref proxy" in { val typedActor1 = TypedActor.newInstance(classOf[MyTypedActor], classOf[MyStatelessTypedActorImpl], 1000) - typedActor1.requestReply("hello") should equal("world") - typedActor1.requestReply("hello") should equal("world") + typedActor1.requestReply("hello") must equal("world") + typedActor1.requestReply("hello") must equal("world") val bytes = RemoteTypedActorSerialization.toBinary(typedActor1) val typedActor2: MyTypedActor = RemoteTypedActorSerialization.fromBinaryToRemoteTypedActorRef(bytes) - typedActor1.requestReply("hello") should equal("world") + typedActor1.requestReply("hello") must equal("world") } } } @@ -165,6 +146,4 @@ class MyStatelessTypedActorImpl extends TypedActor with MyTypedActor { override def requestReply(message: String) : String = { if (message == "hello") "world" else ("hello " + message) } -} - -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala index 5adabb3e54..34288f1637 100644 --- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala @@ -478,21 +478,29 @@ object TypedActor extends Logging { * @param factory factory method that constructs the typed actor * @paramm config configuration object fo the typed actor */ - def newInstance[T](intfClass: Class[T], factory: => AnyRef, config: TypedActorConfiguration): T = { - val actorRef = actorOf(newTypedActor(factory)) - newInstance(intfClass, actorRef, config) + def newInstance[T](intfClass: Class[T], factory: => AnyRef, config: TypedActorConfiguration): T = + newInstance(intfClass, createActorRef(newTypedActor(factory),config), config) + + /** + * Creates an ActorRef, can be local only or client-managed-remote + */ + private[akka] def createActorRef(typedActor: => TypedActor, config: TypedActorConfiguration): ActorRef = { + config match { + case null => actorOf(typedActor) + case c: TypedActorConfiguration if (c._host.isDefined) => + actorOf(typedActor, c._host.get.getHostName, c._host.get.getPort) + case _ => actorOf(typedActor) + } } /** - * Factory method for typed actor. + * Factory method for typed actor. * @param intfClass interface the typed actor implements * @param targetClass implementation class of the typed actor * @paramm config configuration object fo the typed actor */ - def newInstance[T](intfClass: Class[T], targetClass: Class[_], config: TypedActorConfiguration): T = { - val actorRef = actorOf(newTypedActor(targetClass)) - newInstance(intfClass, actorRef, config) - } + def newInstance[T](intfClass: Class[T], targetClass: Class[_], config: TypedActorConfiguration): T = + newInstance(intfClass, createActorRef(newTypedActor(targetClass),config), config) private[akka] def newInstance[T](intfClass: Class[T], actorRef: ActorRef): T = { if (!actorRef.actorInstance.get.isInstanceOf[TypedActor]) throw new IllegalArgumentException("ActorRef is not a ref to a typed actor") @@ -512,9 +520,9 @@ object TypedActor extends Logging { typedActor.initialize(proxy) if (config._messageDispatcher.isDefined) actorRef.dispatcher = config._messageDispatcher.get if (config._threadBasedDispatcher.isDefined) actorRef.dispatcher = Dispatchers.newThreadBasedDispatcher(actorRef) - if (config._host.isDefined) log.slf4j.warn("Client-managed typed actors are not supported!") //TODO: REVISIT: FIXME actorRef.timeout = config.timeout - AspectInitRegistry.register(proxy, AspectInit(intfClass, typedActor, actorRef, None, actorRef.timeout)) //TODO: REVISIT fix Client managed typed actor + log.slf4j.warn("config._host for {} is {} but homeAddress is {}",intfClass, config._host) + AspectInitRegistry.register(proxy, AspectInit(intfClass, typedActor, actorRef, config._host, actorRef.timeout)) //TODO: REVISIT fix Client managed typed actor actorRef.start proxy.asInstanceOf[T] } @@ -582,7 +590,7 @@ object TypedActor extends Logging { val jProxy = JProxy.newProxyInstance(intfClass.getClassLoader(), interfaces, handler) val awProxy = Proxy.newInstance(interfaces, Array(jProxy, jProxy), true, false) - AspectInitRegistry.register(awProxy, AspectInit(intfClass, null, actorRef, None, 5000L)) + AspectInitRegistry.register(awProxy, AspectInit(intfClass, null, actorRef, None, 5000L)) //TODO: does .homeAddress work here or do we need to check if it's local and then provide None? awProxy.asInstanceOf[T] } diff --git a/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala b/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala index d2a9ebca26..93639460b0 100644 --- a/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala +++ b/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala @@ -108,26 +108,24 @@ private[akka] class TypedActorGuiceConfigurator extends TypedActorConfiguratorBa val implementationClass = component.target val timeout = component.timeout - val actorRef = Actor.actorOf(TypedActor.newTypedActor(implementationClass)) + val (remoteAddress,actorRef) = + component.remoteAddress match { + case Some(a) => + (Some(new InetSocketAddress(a.hostname, a.port)), + Actor.actorOf(TypedActor.newTypedActor(implementationClass), a.hostname, a.port)) + case None => + (None, Actor.actorOf(TypedActor.newTypedActor(implementationClass))) + } + actorRef.timeout = timeout if (component.dispatcher.isDefined) actorRef.dispatcher = component.dispatcher.get val typedActor = actorRef.actorInstance.get.asInstanceOf[TypedActor] val proxy = Proxy.newInstance(Array(interfaceClass), Array(typedActor), true, false) - /* - val remoteAddress = - if (component.remoteAddress.isDefined) - Some(new InetSocketAddress(component.remoteAddress.get.hostname, component.remoteAddress.get.port)) - else None - - remoteAddress.foreach { address => - actorRef.makeRemote(remoteAddress.get) - }*/ - AspectInitRegistry.register( proxy, - AspectInit(interfaceClass, typedActor, actorRef, None, timeout)) //TODO: REVISIT: FIX CLIENT MANAGED ACTORS + AspectInit(interfaceClass, typedActor, actorRef, remoteAddress, timeout)) typedActor.initialize(proxy) actorRef.start From a1d02435b1b688668bcc268ecc382839bfc503e0 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 22 Dec 2010 10:10:04 +0100 Subject: [PATCH 18/38] WIP --- .../src/main/scala/akka/actor/Actor.scala | 72 ------------------ .../src/main/scala/akka/actor/ActorRef.scala | 4 +- .../remoteinterface/RemoteInterface.scala | 74 +++++++++++++++++++ .../akka/remote/NettyRemoteSupport.scala | 9 ++- .../ClientInitiatedRemoteActorSpec.scala | 14 ++-- .../scala/remote/RemoteSupervisorSpec.scala | 22 +++--- .../ClientManagedRemoteActorSample.scala | 5 +- .../main/scala/akka/actor/TypedActor.scala | 2 +- .../config/TypedActorGuiceConfigurator.scala | 2 +- 9 files changed, 107 insertions(+), 97 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index 618c442b89..5ce4478391 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -154,78 +154,6 @@ object Actor extends Logging { "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")) }, None) - /** - * Creates a Client-managed ActorRef out of the Actor of the specified Class. - * If the supplied host and port is identical of the configured local node, it will be a local actor - *
-   *   import Actor._
-   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552)
-   *   actor.start
-   *   actor ! message
-   *   actor.stop
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start
-   * 
- */ - def actorOf(factory: => Actor, host: String, port: Int): ActorRef = - ActorRegistry.remote.clientManagedActorOf(() => factory, host, port) - - /** - * Creates a Client-managed ActorRef out of the Actor of the specified Class. - * If the supplied host and port is identical of the configured local node, it will be a local actor - *
-   *   import Actor._
-   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552)
-   *   actor.start
-   *   actor ! message
-   *   actor.stop
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start
-   * 
- */ - def actorOf(clazz: Class[_ <: Actor], host: String, port: Int): ActorRef = { - import ReflectiveAccess.{ createInstance, noParams, noArgs } - ActorRegistry.remote.clientManagedActorOf(() => - createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( - throw new ActorInitializationException( - "Could not instantiate Actor" + - "\nMake sure Actor is NOT defined inside a class/trait," + - "\nif so put it outside the class/trait, f.e. in a companion object," + - "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")), - host, port) - } - - /** - * Creates a Client-managed ActorRef out of the Actor of the specified Class. - * If the supplied host and port is identical of the configured local node, it will be a local actor - *
-   *   import Actor._
-   *   val actor = actorOf[MyActor]("www.akka.io",2552)
-   *   actor.start
-   *   actor ! message
-   *   actor.stop
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf[MyActor]("www.akka.io",2552).start
-   * 
- */ - def actorOf[T <: Actor : Manifest](host: String, port: Int): ActorRef = { - import ReflectiveAccess.{ createInstance, noParams, noArgs } - ActorRegistry.remote.clientManagedActorOf(() => - createInstance[Actor](manifest[T].erasure.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( - throw new ActorInitializationException( - "Could not instantiate Actor" + - "\nMake sure Actor is NOT defined inside a class/trait," + - "\nif so put it outside the class/trait, f.e. in a companion object," + - "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")), - host, port) - } - /** * Creates an ActorRef out of the Actor. Allows you to pass in a factory function * that creates the Actor. Please note that this function can be invoked multiple diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index cfd1dd9904..98b45d7aca 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -737,7 +737,7 @@ class LocalActorRef private[akka] ( */ def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long = Actor.TIMEOUT): ActorRef = guard.withGuard { ensureRemotingEnabled - val ref = Actor.actorOf(clazz, hostname, port) + val ref = ActorRegistry.remote.actorOf(clazz, hostname, port) ref.timeout = timeout ref.start } @@ -762,7 +762,7 @@ class LocalActorRef private[akka] ( def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long = Actor.TIMEOUT): ActorRef = guard.withGuard { ensureRemotingEnabled - val actor = Actor.actorOf(clazz, hostname, port) + val actor = ActorRegistry.remote.actorOf(clazz, hostname, port) actor.timeout = timeout link(actor) actor.start diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index 751bfbc188..94b60cbfc1 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -63,6 +63,80 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule this.shutdownServerModule clear } + + + /** + * Creates a Client-managed ActorRef out of the Actor of the specified Class. + * If the supplied host and port is identical of the configured local node, it will be a local actor + *
+   *   import Actor._
+   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552)
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start
+   * 
+ */ + def actorOf(factory: => Actor, host: String, port: Int): ActorRef = + ActorRegistry.remote.clientManagedActorOf(() => factory, host, port) + + /** + * Creates a Client-managed ActorRef out of the Actor of the specified Class. + * If the supplied host and port is identical of the configured local node, it will be a local actor + *
+   *   import Actor._
+   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552)
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start
+   * 
+ */ + def actorOf(clazz: Class[_ <: Actor], host: String, port: Int): ActorRef = { + import ReflectiveAccess.{ createInstance, noParams, noArgs } + clientManagedActorOf(() => + createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( + throw new ActorInitializationException( + "Could not instantiate Actor" + + "\nMake sure Actor is NOT defined inside a class/trait," + + "\nif so put it outside the class/trait, f.e. in a companion object," + + "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")), + host, port) + } + + /** + * Creates a Client-managed ActorRef out of the Actor of the specified Class. + * If the supplied host and port is identical of the configured local node, it will be a local actor + *
+   *   import Actor._
+   *   val actor = actorOf[MyActor]("www.akka.io",2552)
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf[MyActor]("www.akka.io",2552).start
+   * 
+ */ + def actorOf[T <: Actor : Manifest](host: String, port: Int): ActorRef = { + import ReflectiveAccess.{ createInstance, noParams, noArgs } + clientManagedActorOf(() => + createInstance[Actor](manifest[T].erasure.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( + throw new ActorInitializationException( + "Could not instantiate Actor" + + "\nMake sure Actor is NOT defined inside a class/trait," + + "\nif so put it outside the class/trait, f.e. in a companion object," + + "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")), + host, port) + } + protected override def manageLifeCycleOfListeners = false protected[akka] override def notifyListeners(message: => Any): Unit = super.notifyListeners(message) diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index da0f326705..fff7d49185 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -564,7 +564,7 @@ class NettyRemoteSupport extends RemoteSupport with NettyRemoteServerModule with protected[akka] def actorFor(serviceId: String, className: String, timeout: Long, host: String, port: Int, loader: Option[ClassLoader]): ActorRef = { if (optimizeLocalScoped_?) { val home = this.address - if (host == home.getHostName && port == home.getPort) {//TODO: switch to InetSocketAddres.equals? + if (host == home.getHostName && port == home.getPort) {//TODO: switch to InetSocketAddress.equals? val localRef = findActorByIdOrUuid(serviceId,serviceId) if (localRef ne null) return localRef //Code significantly simpler with the return statement @@ -575,6 +575,13 @@ class NettyRemoteSupport extends RemoteSupport with NettyRemoteServerModule with } def clientManagedActorOf(factory: () => Actor, host: String, port: Int): ActorRef = { + + if (optimizeLocalScoped_?) { + val home = this.address + if (host == home.getHostName && port == home.getPort)//TODO: switch to InetSocketAddress.equals? + return new LocalActorRef(factory, None) // Code is much simpler with return + } + val ref = new LocalActorRef(factory, Some(new InetSocketAddress(host, port))) //ref.timeout = timeout //removed because setting default timeout should be done after construction ref diff --git a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala index ad0028c8ea..3ffff3c3cd 100644 --- a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala @@ -76,7 +76,7 @@ class MyActorCustomConstructor extends Actor { class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { "ClientInitiatedRemoteActor" should { "shouldSendOneWay" in { - val clientManaged = actorOf[RemoteActorSpecActorUnidirectional](host,port).start + val clientManaged = remote.actorOf[RemoteActorSpecActorUnidirectional](host,port).start clientManaged must not be null clientManaged.getClass must be (classOf[LocalActorRef]) clientManaged ! "OneWay" @@ -86,7 +86,7 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { "shouldSendOneWayAndReceiveReply" in { val latch = new CountDownLatch(1) - val actor = actorOf[SendOneWayAndReplyReceiverActor](host,port).start + val actor = remote.actorOf[SendOneWayAndReplyReceiverActor](host,port).start implicit val sender = Some(actorOf(new CountDownActor(latch)).start) actor ! "Hello" @@ -95,14 +95,14 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { } "shouldSendBangBangMessageAndReceiveReply" in { - val actor = actorOf[RemoteActorSpecActorBidirectional](host,port).start + val actor = remote.actorOf[RemoteActorSpecActorBidirectional](host,port).start val result = actor !! "Hello" "World" must equal (result.get.asInstanceOf[String]) actor.stop } "shouldSendBangBangMessageAndReceiveReplyConcurrently" in { - val actors = (1 to 10).map(num => { actorOf[RemoteActorSpecActorBidirectional](host,port).start }).toList + val actors = (1 to 10).map(num => { remote.actorOf[RemoteActorSpecActorBidirectional](host,port).start }).toList actors.map(_ !!! "Hello") foreach { future => "World" must equal (future.await.result.asInstanceOf[Option[String]].get) } @@ -110,8 +110,8 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { } "shouldRegisterActorByUuid" in { - val actor1 = actorOf[MyActorCustomConstructor](host, port).start - val actor2 = actorOf[MyActorCustomConstructor](host, port).start + val actor1 = remote.actorOf[MyActorCustomConstructor](host, port).start + val actor2 = remote.actorOf[MyActorCustomConstructor](host, port).start actor1 ! "incrPrefix" @@ -129,7 +129,7 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { "shouldSendAndReceiveRemoteException" in { - val actor = actorOf[RemoteActorSpecActorBidirectional](host, port).start + val actor = remote.actorOf[RemoteActorSpecActorBidirectional](host, port).start try { implicit val timeout = 500000000L val f = (actor !!! "Failure").await.resultOrException diff --git a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala index 33760028fb..550c883800 100644 --- a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala +++ b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala @@ -228,7 +228,7 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { // Then create a concrete container in which we mix in support for the specific // implementation of the Actors we want to use. - pingpong1 = actorOf[RemotePingPong1Actor](host,port).start + pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start val factory = SupervisorFactory( SupervisorConfig( @@ -242,7 +242,7 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { } def getSingleActorOneForOneSupervisor: Supervisor = { - pingpong1 = actorOf[RemotePingPong1Actor](host,port).start + pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start val factory = SupervisorFactory( SupervisorConfig( @@ -255,9 +255,9 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { } def getMultipleActorsAllForOneConf: Supervisor = { - pingpong1 = actorOf[RemotePingPong1Actor](host,port).start - pingpong2 = actorOf[RemotePingPong2Actor](host,port).start - pingpong3 = actorOf[RemotePingPong3Actor](host,port).start + pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start + pingpong2 = remote.actorOf[RemotePingPong2Actor](host,port).start + pingpong3 = remote.actorOf[RemotePingPong3Actor](host,port).start val factory = SupervisorFactory( SupervisorConfig( @@ -278,9 +278,9 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { } def getMultipleActorsOneForOneConf: Supervisor = { - pingpong1 = actorOf[RemotePingPong1Actor](host,port).start - pingpong2 = actorOf[RemotePingPong2Actor](host,port).start - pingpong3 = actorOf[RemotePingPong3Actor](host,port).start + pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start + pingpong2 = remote.actorOf[RemotePingPong2Actor](host,port).start + pingpong3 = remote.actorOf[RemotePingPong3Actor](host,port).start val factory = SupervisorFactory( SupervisorConfig( @@ -301,9 +301,9 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { } def getNestedSupervisorsAllForOneConf: Supervisor = { - pingpong1 = actorOf[RemotePingPong1Actor](host,port).start - pingpong2 = actorOf[RemotePingPong2Actor](host,port).start - pingpong3 = actorOf[RemotePingPong3Actor](host,port).start + pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start + pingpong2 = remote.actorOf[RemotePingPong2Actor](host,port).start + pingpong3 = remote.actorOf[RemotePingPong3Actor](host,port).start val factory = SupervisorFactory( SupervisorConfig( diff --git a/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala b/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala index 76ab88c7fb..52281f4fbe 100644 --- a/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala +++ b/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala @@ -7,6 +7,7 @@ package sample.remote import akka.actor.Actor._ import akka.util.Logging import akka.actor. {ActorRegistry, Actor} +import ActorRegistry.remote class RemoteHelloWorldActor extends Actor { def receive = { @@ -18,7 +19,7 @@ class RemoteHelloWorldActor extends Actor { object ClientManagedRemoteActorServer extends Logging { def run = { - ActorRegistry.remote.start("localhost", 2552) + remote.start("localhost", 2552) log.slf4j.info("Remote node started") } @@ -28,7 +29,7 @@ object ClientManagedRemoteActorServer extends Logging { object ClientManagedRemoteActorClient extends Logging { def run = { - val actor = actorOf[RemoteHelloWorldActor]("localhost",2552).start + val actor = remote.actorOf[RemoteHelloWorldActor]("localhost",2552).start log.slf4j.info("Remote actor created, moved to the server") log.slf4j.info("Sending 'Hello' to remote actor") val result = actor !! "Hello" diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala index 34288f1637..e39be47549 100644 --- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala @@ -488,7 +488,7 @@ object TypedActor extends Logging { config match { case null => actorOf(typedActor) case c: TypedActorConfiguration if (c._host.isDefined) => - actorOf(typedActor, c._host.get.getHostName, c._host.get.getPort) + ActorRegistry.remote.actorOf(typedActor, c._host.get.getHostName, c._host.get.getPort) case _ => actorOf(typedActor) } } diff --git a/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala b/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala index 93639460b0..1669122812 100644 --- a/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala +++ b/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala @@ -112,7 +112,7 @@ private[akka] class TypedActorGuiceConfigurator extends TypedActorConfiguratorBa component.remoteAddress match { case Some(a) => (Some(new InetSocketAddress(a.hostname, a.port)), - Actor.actorOf(TypedActor.newTypedActor(implementationClass), a.hostname, a.port)) + ActorRegistry.remote.actorOf(TypedActor.newTypedActor(implementationClass), a.hostname, a.port)) case None => (None, Actor.actorOf(TypedActor.newTypedActor(implementationClass))) } From 0f87bd83d3567db7aa1f3c8da849946be440e7de Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 28 Dec 2010 14:34:06 +0100 Subject: [PATCH 19/38] Adding example in test to show how to test remotely using only one registry --- .../ServerInitiatedRemoteActorSpec.scala | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala index 37b303bead..c3d950d48c 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala @@ -15,6 +15,20 @@ object ServerInitiatedRemoteActorSpec { } } + class Decrementer extends Actor { + def receive = { + case "done" => self.reply_?(false) + case i: Int if i > 0 => + self.reply_?(i - 1) + case i: Int => + self.reply_?(0) + this become { + case "done" => self.reply_?(true) + case _ => //Do Nothing + } + } + } + class RemoteActorSpecActorBidirectional extends Actor { def receive = { @@ -145,6 +159,28 @@ class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { remote.unregister(uuid) remote.actorsByUuid.get(actor1.uuid) must be (null) } + + "should be able to remotely comunicate between 2 server-managed actors" in { + remote.register("foo", actorOf[Decrementer]) + remote.register("bar", actorOf[Decrementer]) + + val remoteFoo = remote.actorFor("foo", host, port) + val remoteBar = remote.actorFor("bar", host, port) + + //Seed the start + remoteFoo.!(10)(Some(remoteBar)) + + val latch = new CountDownLatch(100) + while( + (remoteFoo !! "done").as[Boolean].getOrElse(false) && + (remoteBar !! "done").as[Boolean].getOrElse(false) + ) { + if (latch.await(200, TimeUnit.MILLISECONDS)) + error("Test didn't complete within 100 cycles") + else + latch.countDown + } + } } } From d0f94b9e19c6f6c77da73ac8fb33f68b10713cd7 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 28 Dec 2010 14:52:22 +0100 Subject: [PATCH 20/38] Adding additional tests --- .../remote/ServerInitiatedRemoteActorSpec.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala index c3d950d48c..78613820b2 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala @@ -160,9 +160,11 @@ class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { remote.actorsByUuid.get(actor1.uuid) must be (null) } - "should be able to remotely comunicate between 2 server-managed actors" in { - remote.register("foo", actorOf[Decrementer]) - remote.register("bar", actorOf[Decrementer]) + "should be able to remotely communicate between 2 server-managed actors" in { + val localFoo = actorOf[Decrementer] + val localBar = actorOf[Decrementer] + remote.register("foo", localFoo) + remote.register("bar", localBar) val remoteFoo = remote.actorFor("foo", host, port) val remoteBar = remote.actorFor("bar", host, port) @@ -180,6 +182,11 @@ class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { else latch.countDown } + + val decrementers = ActorRegistry.actorsFor[Decrementer] + decrementers must have size(2) //No new are allowed to have been created + decrementers.find( _ eq localFoo) must equal (Some(localFoo)) + decrementers.find( _ eq localBar) must equal (Some(localBar)) } } } From 9309c98bb993927be06dccadbdeee8be66e606da Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 28 Dec 2010 19:26:04 +0100 Subject: [PATCH 21/38] Fixing erronous test --- .../scala/remote/ServerInitiatedRemoteActorSpec.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala index 78613820b2..c648217410 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala @@ -173,10 +173,11 @@ class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { remoteFoo.!(10)(Some(remoteBar)) val latch = new CountDownLatch(100) - while( - (remoteFoo !! "done").as[Boolean].getOrElse(false) && - (remoteBar !! "done").as[Boolean].getOrElse(false) - ) { + + def testDone() = (remoteFoo !! "done").as[Boolean].getOrElse(false) && + (remoteBar !! "done").as[Boolean].getOrElse(false) + + while(!testDone()) { if (latch.await(200, TimeUnit.MILLISECONDS)) error("Test didn't complete within 100 cycles") else From 83c8bb7767dc3d930d64c951c0dea0ab05fe3de7 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 29 Dec 2010 10:59:04 +0100 Subject: [PATCH 22/38] Moved all actorOf-methods from Actor to ActorRegistry and deprecated the forwarders in Actor --- .../src/main/scala/akka/actor/Actor.scala | 26 ++---- .../main/scala/akka/actor/ActorRegistry.scala | 86 +++++++++++++++++++ 2 files changed, 91 insertions(+), 21 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index 6d58f782f9..52d31a2ac4 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -128,7 +128,7 @@ object Actor extends Logging { * val actor = actorOf[MyActor].start * */ - def actorOf[T <: Actor : Manifest]: ActorRef = actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) + @deprecated def actorOf[T <: Actor : Manifest]: ActorRef = ActorRegistry.actorOf[T] /** * Creates an ActorRef out of the Actor of the specified Class. @@ -144,15 +144,7 @@ object Actor extends Logging { * val actor = actorOf(classOf[MyActor]).start * */ - def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(() => { - import ReflectiveAccess.{ createInstance, noParams, noArgs } - createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( - throw new ActorInitializationException( - "Could not instantiate Actor" + - "\nMake sure Actor is NOT defined inside a class/trait," + - "\nif so put it outside the class/trait, f.e. in a companion object," + - "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")) - }, None) + @deprecated def actorOf(clazz: Class[_ <: Actor]): ActorRef = ActorRegistry.actorOf(clazz) /** * Creates an ActorRef out of the Actor. Allows you to pass in a factory function @@ -172,7 +164,7 @@ object Actor extends Logging { * val actor = actorOf(new MyActor).start * */ - def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory, None) + @deprecated def actorOf(factory: => Actor): ActorRef = ActorRegistry.actorOf(factory) /** * Use to spawn out a block of code in an event-driven actor. Will shut actor down when @@ -189,16 +181,8 @@ object Actor extends Logging { * } * */ - def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = { - case object Spawn - actorOf(new Actor() { - self.dispatcher = dispatcher - def receive = { - case Spawn => try { body } finally { self.stop } - } - }).start ! Spawn - } - + @deprecated def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = + ActorRegistry.spawn(body) /** * Implicitly converts the given Option[Any] to a AnyOptionAsTypedOption which offers the method as[T] diff --git a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala index 50c7f72752..02ca9b27bd 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala @@ -239,6 +239,92 @@ object ActorRegistry extends ListenerManagement { def homeAddress(): InetSocketAddress = if (isRemotingEnabled) remote.address else Remote.configDefaultAddress + /** + * Creates an ActorRef out of the Actor with type T. + *
+   *   import Actor._
+   *   val actor = actorOf[MyActor]
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf[MyActor].start
+   * 
+ */ + def actorOf[T <: Actor : Manifest]: ActorRef = actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) + + /** + * Creates an ActorRef out of the Actor of the specified Class. + *
+   *   import Actor._
+   *   val actor = actorOf(classOf[MyActor])
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf(classOf[MyActor]).start
+   * 
+ */ + def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(() => { + import ReflectiveAccess.{ createInstance, noParams, noArgs } + createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( + throw new ActorInitializationException( + "Could not instantiate Actor" + + "\nMake sure Actor is NOT defined inside a class/trait," + + "\nif so put it outside the class/trait, f.e. in a companion object," + + "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")) + }, None) + + /** + * Creates an ActorRef out of the Actor. Allows you to pass in a factory function + * that creates the Actor. Please note that this function can be invoked multiple + * times if for example the Actor is supervised and needs to be restarted. + *

+ * This function should NOT be used for remote actors. + *

+   *   import Actor._
+   *   val actor = actorOf(new MyActor)
+   *   actor.start
+   *   actor ! message
+   *   actor.stop
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf(new MyActor).start
+   * 
+ */ + def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory, None) + + /** + * Use to spawn out a block of code in an event-driven actor. Will shut actor down when + * the block has been executed. + *

+ * NOTE: If used from within an Actor then has to be qualified with 'ActorRegistry.spawn' since + * there is a method 'spawn[ActorType]' in the Actor trait already. + * Example: + *

+   * import ActorRegistry.{spawn}
+   *
+   * spawn  {
+   *   ... // do stuff
+   * }
+   * 
+ */ + def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = { + case object Spawn + actorOf(new Actor() { + self.dispatcher = dispatcher + def receive = { + case Spawn => try { body } finally { self.stop } + } + }).start ! Spawn + } + + /** * Registers an actor in the ActorRegistry. */ From 0a6567ed80309ca2b586f4f41edf94dbffe3fcb6 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 29 Dec 2010 12:38:16 +0100 Subject: [PATCH 23/38] Adding tests for optimize local scoped and non-optimized local scoped --- .../src/main/scala/akka/actor/Actor.scala | 12 +++++--- .../test/scala/remote/AkkaRemoteTest.scala | 8 +++-- .../remote/OptimizedLocalScopedSpec.scala | 29 +++++++++++++++++++ .../remote/UnOptimizedLocalScopedSpec.scala | 28 ++++++++++++++++++ 4 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala create mode 100644 akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index 52d31a2ac4..35a336a873 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -128,7 +128,8 @@ object Actor extends Logging { * val actor = actorOf[MyActor].start * */ - @deprecated def actorOf[T <: Actor : Manifest]: ActorRef = ActorRegistry.actorOf[T] + @deprecated("Use ActorRegistry.actorOf instead") + def actorOf[T <: Actor : Manifest]: ActorRef = ActorRegistry.actorOf[T] /** * Creates an ActorRef out of the Actor of the specified Class. @@ -144,7 +145,8 @@ object Actor extends Logging { * val actor = actorOf(classOf[MyActor]).start * */ - @deprecated def actorOf(clazz: Class[_ <: Actor]): ActorRef = ActorRegistry.actorOf(clazz) + @deprecated("Use ActorRegistry.actorOf instead") + def actorOf(clazz: Class[_ <: Actor]): ActorRef = ActorRegistry.actorOf(clazz) /** * Creates an ActorRef out of the Actor. Allows you to pass in a factory function @@ -164,7 +166,8 @@ object Actor extends Logging { * val actor = actorOf(new MyActor).start * */ - @deprecated def actorOf(factory: => Actor): ActorRef = ActorRegistry.actorOf(factory) + @deprecated("Use ActorRegistry.actorOf instead") + def actorOf(factory: => Actor): ActorRef = ActorRegistry.actorOf(factory) /** * Use to spawn out a block of code in an event-driven actor. Will shut actor down when @@ -181,7 +184,8 @@ object Actor extends Logging { * } * */ - @deprecated def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = + @deprecated("Use ActorRegistry.spawn instead") + def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = ActorRegistry.spawn(body) /** diff --git a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala index 2c2ee4ca9a..00e09a4865 100644 --- a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala +++ b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala @@ -31,14 +31,18 @@ class AkkaRemoteTest extends val host = "localhost" val port = 25520 + def OptimizeLocal = false + var optimizeLocal_? = remote.asInstanceOf[NettyRemoteSupport].optimizeLocalScoped_? override def beforeAll { - remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(false) //Can't run the test if we're eliminating all remote calls + if (!OptimizeLocal) + remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(false) //Can't run the test if we're eliminating all remote calls } override def afterAll { - remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(optimizeLocal_?) //Reset optimizelocal after all tests + if (!OptimizeLocal) + remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(optimizeLocal_?) //Reset optimizelocal after all tests } override def beforeEach { diff --git a/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala b/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala new file mode 100644 index 0000000000..cac34523ba --- /dev/null +++ b/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala @@ -0,0 +1,29 @@ +package akka.actor.remote + +import akka.actor. {ActorRegistry, Actor} + +object OptimizedLocalScopedSpec { + class TestActor extends Actor { + def receive = { case _ => } + } +} + +class OptimizedLocalScopedSpec extends AkkaRemoteTest { + import OptimizedLocalScopedSpec._ + override def OptimizeLocal = true + + "An enabled optimized local scoped remote" should { + "Fetch local actor ref when scope is local" in { + val fooActor = ActorRegistry.actorOf[TestActor].start + remote.register("foo", fooActor) + + remote.actorFor("foo", host, port) must be (fooActor) + } + + "Create local actor when client-managed is hosted locally" in { + val localClientManaged = ActorRegistry.remote.actorOf[TestActor](host, port) + localClientManaged.homeAddress must be (None) + } + + } +} \ No newline at end of file diff --git a/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala b/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala new file mode 100644 index 0000000000..934f56d82a --- /dev/null +++ b/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala @@ -0,0 +1,28 @@ +package akka.actor.remote +import akka.actor. {ActorRegistry, Actor} + +object UnOptimizedLocalScopedSpec { + class TestActor extends Actor { + def receive = { case _ => } + } +} + +class UnOptimizedLocalScopedSpec extends AkkaRemoteTest { + import UnOptimizedLocalScopedSpec._ + override def OptimizeLocal = false + + "An enabled optimized local scoped remote" should { + "Fetch local actor ref when scope is local" in { + val fooActor = ActorRegistry.actorOf[TestActor].start + remote.register("foo", fooActor) + + remote.actorFor("foo", host, port) must not be (fooActor) + } + + "Create local actor when client-managed is hosted locally" in { + val localClientManaged = ActorRegistry.remote.actorOf[TestActor](host, port) + localClientManaged.homeAddress must not be (None) + } + + } +} \ No newline at end of file From c120589684eacb723f0f737a2489607c12563431 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 29 Dec 2010 12:51:58 +0100 Subject: [PATCH 24/38] Changed wording in the unoptimized local scoped spec --- .../src/test/scala/remote/UnOptimizedLocalScopedSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala b/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala index 934f56d82a..46d8ba2730 100644 --- a/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala +++ b/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala @@ -12,14 +12,14 @@ class UnOptimizedLocalScopedSpec extends AkkaRemoteTest { override def OptimizeLocal = false "An enabled optimized local scoped remote" should { - "Fetch local actor ref when scope is local" in { + "Fetch remote actor ref when scope is local" in { val fooActor = ActorRegistry.actorOf[TestActor].start remote.register("foo", fooActor) remote.actorFor("foo", host, port) must not be (fooActor) } - "Create local actor when client-managed is hosted locally" in { + "Create remote actor when client-managed is hosted locally" in { val localClientManaged = ActorRegistry.remote.actorOf[TestActor](host, port) localClientManaged.homeAddress must not be (None) } From 236eecebcff85acce4b3af250db3da100f2645c6 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 29 Dec 2010 16:08:43 +0100 Subject: [PATCH 25/38] Moving shared remote classes into RemoteInterface --- .../remoteinterface/RemoteInterface.scala | 53 +++++++++++ .../akka/remote/NettyRemoteSupport.scala | 92 ++++--------------- 2 files changed, 73 insertions(+), 72 deletions(-) diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index 94b60cbfc1..7764469b62 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -11,6 +11,8 @@ import akka.util._ import akka.dispatch.CompletableFuture import akka.config.Config.{config, TIME_UNIT} import java.util.concurrent.ConcurrentHashMap +import akka.AkkaException +import reflect.BeanProperty trait RemoteModule extends Logging { val UUID_PREFIX = "uuid:" @@ -56,6 +58,57 @@ trait RemoteModule extends Logging { } } +/** + * Life-cycle events for RemoteClient. + */ +sealed trait RemoteClientLifeCycleEvent //TODO: REVISIT: Document change from RemoteClient to RemoteClientModule + remoteAddress +case class RemoteClientError( + @BeanProperty val cause: Throwable, + @BeanProperty val client: RemoteClientModule, val remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent +case class RemoteClientDisconnected( + @BeanProperty val client: RemoteClientModule, val remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent +case class RemoteClientConnected( + @BeanProperty val client: RemoteClientModule, val remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent +case class RemoteClientStarted( + @BeanProperty val client: RemoteClientModule, val remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent +case class RemoteClientShutdown( + @BeanProperty val client: RemoteClientModule, val remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent + + +/** + * Life-cycle events for RemoteServer. + */ +sealed trait RemoteServerLifeCycleEvent //TODO: REVISIT: Document change from RemoteServer to RemoteServerModule +case class RemoteServerStarted( + @BeanProperty val server: RemoteServerModule) extends RemoteServerLifeCycleEvent +case class RemoteServerShutdown( + @BeanProperty val server: RemoteServerModule) extends RemoteServerLifeCycleEvent +case class RemoteServerError( + @BeanProperty val cause: Throwable, + @BeanProperty val server: RemoteServerModule) extends RemoteServerLifeCycleEvent +case class RemoteServerClientConnected( + @BeanProperty val server: RemoteServerModule, + @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent +case class RemoteServerClientDisconnected( + @BeanProperty val server: RemoteServerModule, + @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent +case class RemoteServerClientClosed( + @BeanProperty val server: RemoteServerModule, + @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent + +/** + * Thrown for example when trying to send a message using a RemoteClient that is either not started or shut down. + */ +class RemoteClientException private[akka] (message: String, + @BeanProperty val client: RemoteClientModule, + val remoteAddress: InetSocketAddress) extends AkkaException(message) + +/** + * Returned when a remote exception cannot be instantiated or parsed + */ +case class UnparsableException private[akka] (originalClassName: String, + originalMessage: String) extends AkkaException(originalMessage) + abstract class RemoteSupport extends ListenerManagement with RemoteServerModule with RemoteClientModule { def shutdown { diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index b9e8e511de..cd81c337c0 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -11,7 +11,6 @@ import akka.remote.protocol.RemoteProtocol.ActorType._ import akka.config.ConfigurationException import akka.serialization.RemoteActorSerialization import akka.japi.Creator -import akka.remoteinterface. {RemoteSupport, RemoteModule, RemoteServerModule, RemoteClientModule} import akka.config.Config._ import akka.serialization.RemoteActorSerialization._ import akka.AkkaException @@ -37,32 +36,7 @@ import scala.reflect.BeanProperty import java.lang.reflect.InvocationTargetException import akka.actor. {ActorInitializationException, LocalActorRef, newUuid, ActorRegistry, Actor, RemoteActorRef, TypedActor, ActorRef, IllegalActorStateException, RemoteActorSystemMessage, uuidFrom, Uuid, Exit, LifeCycleMessage, ActorType => AkkaActorType} import java.util.concurrent.atomic. {AtomicReference, AtomicLong, AtomicBoolean} - -/** - * Life-cycle events for RemoteClient. - */ -sealed trait RemoteClientLifeCycleEvent -case class RemoteClientError( - @BeanProperty val cause: Throwable, - @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent -case class RemoteClientDisconnected( - @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent -case class RemoteClientConnected( - @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent -case class RemoteClientStarted( - @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent -case class RemoteClientShutdown( - @BeanProperty val client: RemoteClient) extends RemoteClientLifeCycleEvent - -/** - * Thrown for example when trying to send a message using a RemoteClient that is either not started or shut down. - */ -class RemoteClientException private[akka] (message: String, @BeanProperty val client: RemoteClient) extends AkkaException(message) - -/** - * Returned when a remote exception cannot be instantiated or parsed - */ -case class UnparsableException private[akka] (originalClassName: String, originalMessage: String) extends AkkaException(originalMessage) +import akka.remoteinterface._ /** * The RemoteClient object manages RemoteClient instances and gives you an API to lookup remote actor handles. @@ -96,7 +70,7 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem loader.foreach(MessageSerializer.setClassLoader(_))//TODO: REVISIT: THIS SMELLS FUNNY if (remoteClients.contains(hash)) remoteClients(hash) else { - val client = new RemoteClient(hostname, port, loader, self.notifyListeners _) + val client = new RemoteClient(this, new InetSocketAddress(hostname, port), loader, self.notifyListeners _) client.connect remoteClients += hash -> client client @@ -165,18 +139,16 @@ object RemoteClient { * @author Jonas Bonér */ class RemoteClient private[akka] ( - val hostname: String, - val port: Int, + val module: NettyRemoteClientModule, + val remoteAddress: InetSocketAddress, val loader: Option[ClassLoader] = None, val notifyListeners: (=> Any) => Unit) extends Logging { - val name = "RemoteClient@" + hostname + "::" + port + val name = "RemoteClient@" + remoteAddress.getHostName + "::" + remoteAddress.getPort //FIXME Should these be clear:ed on postStop? private val futures = new ConcurrentHashMap[Uuid, CompletableFuture[_]] private val supervisors = new ConcurrentHashMap[Uuid, ActorRef] - private val remoteAddress = new InetSocketAddress(hostname, port) - //FIXME rewrite to a wrapper object (minimize volatile access and maximize encapsulation) @volatile private var bootstrap: ClientBootstrap = _ @@ -205,7 +177,7 @@ class RemoteClient private[akka] ( bootstrap.setOption("tcpNoDelay", true) bootstrap.setOption("keepAlive", true) - log.slf4j.info("Starting remote client connection to [{}:{}]", hostname, port) + log.slf4j.info("Starting remote client connection to [{}]", remoteAddress) // Wait until the connection attempt succeeds or fails. connection = bootstrap.connect(remoteAddress) @@ -213,16 +185,16 @@ class RemoteClient private[akka] ( openChannels.add(channel) if (!connection.isSuccess) { - notifyListeners(RemoteClientError(connection.getCause, this)) - log.slf4j.error("Remote client connection to [{}:{}] has failed", hostname, port) + notifyListeners(RemoteClientError(connection.getCause, module, remoteAddress)) + log.slf4j.error("Remote client connection to [{}] has failed", remoteAddress) log.slf4j.debug("Remote client connection failed", connection.getCause) } - notifyListeners(RemoteClientStarted(this)) + notifyListeners(RemoteClientStarted(module, remoteAddress)) } def shutdown = runSwitch switchOff { log.slf4j.info("Shutting down {}", name) - notifyListeners(RemoteClientShutdown(this)) + notifyListeners(RemoteClientShutdown(module, remoteAddress)) timer.stop timer = null openChannels.close.awaitUninterruptibly @@ -276,8 +248,8 @@ class RemoteClient private[akka] ( Some(futureResult) } } else { - val exception = new RemoteClientException("Remote client is not running, make sure you have invoked 'RemoteClient.connect' before using it.", this) - notifyListeners(RemoteClientError(exception, this)) + val exception = new RemoteClientException("Remote client is not running, make sure you have invoked 'RemoteClient.connect' before using it.", module, remoteAddress) + notifyListeners(RemoteClientError(exception, module, remoteAddress)) throw exception } } @@ -399,11 +371,11 @@ class RemoteClientHandler( } case other => - throw new RemoteClientException("Unknown message received in remote client handler: " + other, client) + throw new RemoteClientException("Unknown message received in remote client handler: " + other, client.module, client.remoteAddress) } } catch { case e: Exception => - client.notifyListeners(RemoteClientError(e, client)) + client.notifyListeners(RemoteClientError(e, client.module, client.remoteAddress)) log.slf4j.error("Unexpected exception in remote client handler", e) throw e } @@ -419,7 +391,7 @@ class RemoteClientHandler( client.connection = bootstrap.connect(remoteAddress) client.connection.awaitUninterruptibly // Wait until the connection attempt succeeds or fails. if (!client.connection.isSuccess) { - client.notifyListeners(RemoteClientError(client.connection.getCause, client)) + client.notifyListeners(RemoteClientError(client.connection.getCause, client.module, client.remoteAddress)) log.slf4j.error("Reconnection to [{}] has failed", remoteAddress) log.slf4j.debug("Reconnection failed", client.connection.getCause) } @@ -430,7 +402,7 @@ class RemoteClientHandler( override def channelConnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { def connect = { - client.notifyListeners(RemoteClientConnected(client)) + client.notifyListeners(RemoteClientConnected(client.module, client.remoteAddress)) log.slf4j.debug("Remote client connected to [{}]", ctx.getChannel.getRemoteAddress) client.resetReconnectionTimeWindow } @@ -440,19 +412,19 @@ class RemoteClientHandler( sslHandler.handshake.addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture): Unit = { if (future.isSuccess) connect - else throw new RemoteClientException("Could not establish SSL handshake", client) + else throw new RemoteClientException("Could not establish SSL handshake", client.module, client.remoteAddress) } }) } else connect } override def channelDisconnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { - client.notifyListeners(RemoteClientDisconnected(client)) + client.notifyListeners(RemoteClientDisconnected(client.module, client.remoteAddress)) log.slf4j.debug("Remote client disconnected from [{}]", ctx.getChannel.getRemoteAddress) } override def exceptionCaught(ctx: ChannelHandlerContext, event: ExceptionEvent) = { - client.notifyListeners(RemoteClientError(event.getCause, client)) + client.notifyListeners(RemoteClientError(event.getCause, client.module, client.remoteAddress)) if (event.getCause ne null) log.slf4j.error("Unexpected exception from downstream in remote client", event.getCause) else @@ -530,27 +502,6 @@ object RemoteServer { } } -/** - * Life-cycle events for RemoteServer. - */ -sealed trait RemoteServerLifeCycleEvent //TODO: REVISIT: Document change from RemoteServer to RemoteServerModule -case class RemoteServerStarted( - @BeanProperty val server: RemoteServerModule) extends RemoteServerLifeCycleEvent -case class RemoteServerShutdown( - @BeanProperty val server: RemoteServerModule) extends RemoteServerLifeCycleEvent -case class RemoteServerError( - @BeanProperty val cause: Throwable, - @BeanProperty val server: RemoteServerModule) extends RemoteServerLifeCycleEvent -case class RemoteServerClientConnected( - @BeanProperty val server: RemoteServerModule, - @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent -case class RemoteServerClientDisconnected( - @BeanProperty val server: RemoteServerModule, - @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent -case class RemoteServerClientClosed( - @BeanProperty val server: RemoteServerModule, - @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent - /** * Provides the implementation of the Netty remote support @@ -1110,7 +1061,6 @@ class RemoteServerHandler( val id = actorInfo.getId val sessionActorRefOrNull = findSessionActor(id, channel) if (sessionActorRefOrNull ne null) { - log.slf4j.debug("Found session actor with id {} for channel {} = {}",Array[AnyRef](id, channel, sessionActorRefOrNull)) sessionActorRefOrNull } else { // we dont have it in the session either, see if we have a factory for it @@ -1170,9 +1120,7 @@ class RemoteServerHandler( if (actorRefOrNull ne null) actorRefOrNull - else - { - // the actor has not been registered globally. See if we have it in the session + else { // the actor has not been registered globally. See if we have it in the session val sessionActorRefOrNull = createSessionActor(actorInfo, channel) if (sessionActorRefOrNull ne null) sessionActorRefOrNull From b51a4fec647f60e1f6f7ed1bff8430d4e07d38ae Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 29 Dec 2010 16:44:13 +0100 Subject: [PATCH 26/38] Minor refactoring to ActorRegistry --- .../main/scala/akka/actor/ActorRegistry.scala | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala index 02ca9b27bd..9e595e4422 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala @@ -79,7 +79,7 @@ object ActorRegistry extends ListenerManagement { } /** - * Finds all actors that are subtypes of the class passed in as the Manifest argument and supproting passed message. + * Finds all actors that are subtypes of the class passed in as the Manifest argument and supporting passed message. */ def actorsFor[T <: Actor](message: Any)(implicit manifest: Manifest[T] ): Array[ActorRef] = filter(a => manifest.erasure.isAssignableFrom(a.actor.getClass) && a.isDefinedAt(message)) @@ -104,7 +104,7 @@ object ActorRegistry extends ListenerManagement { actorsFor[T](manifest.erasure.asInstanceOf[Class[T]]) /** - * Finds any actor that matches T. + * Finds any actor that matches T. Very expensive, traverses ALL alive actors. */ def actorFor[T <: Actor](implicit manifest: Manifest[T]): Option[ActorRef] = find({ case a: ActorRef if manifest.erasure.isAssignableFrom(a.actor.getClass) => a }) @@ -328,12 +328,12 @@ object ActorRegistry extends ListenerManagement { /** * Registers an actor in the ActorRegistry. */ - private[akka] def register(actor: ActorRef) = { - // ID - actorsById.put(actor.id, actor) + private[akka] def register(actor: ActorRef) { + val id = actor.id + val uuid = actor.uuid - // UUID - actorsByUUID.put(actor.uuid, actor) + actorsById.put(id, actor) + actorsByUUID.put(uuid, actor) // notify listeners notifyListeners(ActorRegistered(actor)) @@ -342,10 +342,12 @@ object ActorRegistry extends ListenerManagement { /** * Unregisters an actor in the ActorRegistry. */ - private[akka] def unregister(actor: ActorRef) = { - actorsByUUID remove actor.uuid + private[akka] def unregister(actor: ActorRef) { + val id = actor.id + val uuid = actor.uuid - actorsById.remove(actor.id,actor) + actorsByUUID remove uuid + actorsById.remove(id,actor) // notify listeners notifyListeners(ActorUnregistered(actor)) From 960e1616595a712e148b00e17a5b5fe34b9ad225 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 29 Dec 2010 17:59:38 +0100 Subject: [PATCH 27/38] Fixing #586 and #588 and adding support for reconnect and shutdown of individual clients --- .../remoteinterface/RemoteInterface.scala | 49 +++++--- .../akka/remote/NettyRemoteSupport.scala | 111 +++++++++++------- 2 files changed, 96 insertions(+), 64 deletions(-) diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index 7764469b62..7a50a3556c 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -379,31 +379,42 @@ trait RemoteClientModule extends RemoteModule { self: RemoteModule => def clientManagedActorOf(factory: () => Actor, host: String, port: Int): ActorRef + + /** + * Clean-up all open connections. + */ + def shutdownClientModule: Unit + + /** + * Shuts down a specific client connected to the supplied remote address returns true if successful + */ + def shutdownClientConnection(address: InetSocketAddress): Boolean + + /** + * Restarts a specific client connected to the supplied remote address, but only if the client is not shut down + */ + def restartClientConnection(address: InetSocketAddress): Boolean + /** Methods that needs to be implemented by a transport **/ - protected[akka] def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, host: String, port: Int, loader: Option[ClassLoader]): T + protected[akka] def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, host: String, port: Int, loader: Option[ClassLoader]): T - protected[akka] def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): ActorRef + protected[akka] def actorFor(serviceId: String, className: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): ActorRef - protected[akka] def send[T](message: Any, - senderOption: Option[ActorRef], - senderFuture: Option[CompletableFuture[T]], - remoteAddress: InetSocketAddress, - timeout: Long, - isOneWay: Boolean, - actorRef: ActorRef, - typedActorInfo: Option[Tuple2[String, String]], - actorType: ActorType, - loader: Option[ClassLoader]): Option[CompletableFuture[T]] + protected[akka] def send[T](message: Any, + senderOption: Option[ActorRef], + senderFuture: Option[CompletableFuture[T]], + remoteAddress: InetSocketAddress, + timeout: Long, + isOneWay: Boolean, + actorRef: ActorRef, + typedActorInfo: Option[Tuple2[String, String]], + actorType: ActorType, + loader: Option[ClassLoader]): Option[CompletableFuture[T]] - private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef + private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef - private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef - - /** - * Clean-up all open connections. - */ - def shutdownClientModule: Unit + private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef private[akka] def registerClientManagedActor(hostname: String, port: Int, uuid: Uuid): Unit diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index cd81c337c0..3b98836f5f 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -63,28 +63,37 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem clientFor(remoteAddress, loader).send[T](message, senderOption, senderFuture, remoteAddress, timeout, isOneWay, actorRef, typedActorInfo, actorType) private[akka] def clientFor( - address: InetSocketAddress, loader: Option[ClassLoader]): RemoteClient = synchronized { //TODO: REVIST: synchronized here seems bottlenecky - val hostname = address.getHostName - val port = address.getPort - val hash = hostname + ':' + port + address: InetSocketAddress, loader: Option[ClassLoader]): RemoteClient = synchronized { //TODO: REVISIT: synchronized here seems bottlenecky + val key = makeKey(address) loader.foreach(MessageSerializer.setClassLoader(_))//TODO: REVISIT: THIS SMELLS FUNNY - if (remoteClients.contains(hash)) remoteClients(hash) - else { - val client = new RemoteClient(this, new InetSocketAddress(hostname, port), loader, self.notifyListeners _) - client.connect - remoteClients += hash -> client - client + remoteClients.get(key) match { + case Some(client) => client + case None => + val client = new RemoteClient(this, address, loader, self.notifyListeners _) + client.connect() + remoteClients += key -> client + client } } - def shutdownClientFor(address: InetSocketAddress) = synchronized { - val hostname = address.getHostName - val port = address.getPort - val hash = hostname + ':' + port - if (remoteClients.contains(hash)) { - val client = remoteClients(hash) - client.shutdown - remoteClients -= hash + private def makeKey(a: InetSocketAddress): String = a match { + case null => null + case address => address.getHostName + ':' + address.getPort + } + + def shutdownClientConnection(address: InetSocketAddress): Boolean = synchronized { + remoteClients.remove(makeKey(address)) match { + case Some(client) => + client.shutdown + true + case None => false + } + } + + def restartClientConnection(address: InetSocketAddress): Boolean = synchronized { + remoteClients.get(makeKey(address)) match { + case Some(client) => client.connect(reconnectIfAlreadyConnected = true) + case None => false } } @@ -109,7 +118,7 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem private[akka] def unregisterClientManagedActor(hostname: String, port: Int, uuid: Uuid) = synchronized { val set = actorsFor(Address(hostname, port)) set -= uuid - if (set.isEmpty) shutdownClientFor(new InetSocketAddress(hostname, port)) + if (set.isEmpty) shutdownClientConnection(new InetSocketAddress(hostname, port)) } private[akka] def actorsFor(remoteServerAddress: Address): HashSet[Uuid] = { @@ -168,28 +177,48 @@ class RemoteClient private[akka] ( @volatile private var reconnectionTimeWindowStart = 0L - def connect = runSwitch switchOn { - openChannels = new DefaultChannelGroup(classOf[RemoteClient].getName) - timer = new HashedWheelTimer + def connect(reconnectIfAlreadyConnected: Boolean = false): Boolean = { + runSwitch switchOn { + openChannels = new DefaultChannelGroup(classOf[RemoteClient].getName) + timer = new HashedWheelTimer - bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool)) - bootstrap.setPipelineFactory(new RemoteClientPipelineFactory(name, futures, supervisors, bootstrap, remoteAddress, timer, this)) - bootstrap.setOption("tcpNoDelay", true) - bootstrap.setOption("keepAlive", true) + bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool)) + bootstrap.setPipelineFactory(new RemoteClientPipelineFactory(name, futures, supervisors, bootstrap, remoteAddress, timer, this)) + bootstrap.setOption("tcpNoDelay", true) + bootstrap.setOption("keepAlive", true) - log.slf4j.info("Starting remote client connection to [{}]", remoteAddress) + log.slf4j.info("Starting remote client connection to [{}]", remoteAddress) - // Wait until the connection attempt succeeds or fails. - connection = bootstrap.connect(remoteAddress) - val channel = connection.awaitUninterruptibly.getChannel - openChannels.add(channel) + // Wait until the connection attempt succeeds or fails. + connection = bootstrap.connect(remoteAddress) + openChannels.add(connection.awaitUninterruptibly.getChannel) - if (!connection.isSuccess) { - notifyListeners(RemoteClientError(connection.getCause, module, remoteAddress)) - log.slf4j.error("Remote client connection to [{}] has failed", remoteAddress) - log.slf4j.debug("Remote client connection failed", connection.getCause) + if (!connection.isSuccess) { + notifyListeners(RemoteClientError(connection.getCause, module, remoteAddress)) + log.slf4j.error("Remote client connection to [{}] has failed", remoteAddress) + log.slf4j.debug("Remote client connection failed", connection.getCause) + false + } else { + notifyListeners(RemoteClientStarted(module, remoteAddress)) + true + } + } match { + case true => true + case false if reconnectIfAlreadyConnected => + isAuthenticated.set(false) + log.slf4j.debug("Remote client reconnecting to [{}]", remoteAddress) + openChannels.remove(connection.getChannel) + connection.getChannel.close + connection = bootstrap.connect(remoteAddress) + openChannels.add(connection.awaitUninterruptibly.getChannel) // Wait until the connection attempt succeeds or fails. + if (!connection.isSuccess) { + notifyListeners(RemoteClientError(connection.getCause, module, remoteAddress)) + log.slf4j.error("Reconnection to [{}] has failed", remoteAddress) + log.slf4j.debug("Reconnection failed", connection.getCause) + false + } else true + case false => false } - notifyListeners(RemoteClientStarted(module, remoteAddress)) } def shutdown = runSwitch switchOff { @@ -386,15 +415,7 @@ class RemoteClientHandler( timer.newTimeout(new TimerTask() { def run(timeout: Timeout) = { client.openChannels.remove(event.getChannel) - client.isAuthenticated.set(false) - log.slf4j.debug("Remote client reconnecting to [{}]", remoteAddress) - client.connection = bootstrap.connect(remoteAddress) - client.connection.awaitUninterruptibly // Wait until the connection attempt succeeds or fails. - if (!client.connection.isSuccess) { - client.notifyListeners(RemoteClientError(client.connection.getCause, client.module, client.remoteAddress)) - log.slf4j.error("Reconnection to [{}] has failed", remoteAddress) - log.slf4j.debug("Reconnection failed", client.connection.getCause) - } + client.connect(reconnectIfAlreadyConnected = true) } }, RemoteClient.RECONNECT_DELAY.toMillis, TimeUnit.MILLISECONDS) } else spawn { client.shutdown } From 4994b13fd5c69c014935d0e394e674bd2b1d0b29 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 29 Dec 2010 18:22:36 +0100 Subject: [PATCH 28/38] Removed if statement because it looked ugly --- .../main/scala/akka/remote/NettyRemoteSupport.scala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index 3b98836f5f..b79bc696a1 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -83,9 +83,7 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem def shutdownClientConnection(address: InetSocketAddress): Boolean = synchronized { remoteClients.remove(makeKey(address)) match { - case Some(client) => - client.shutdown - true + case Some(client) => client.shutdown case None => false } } @@ -133,9 +131,9 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem } object RemoteClient { - val SECURE_COOKIE: Option[String] = { - val cookie = config.getString("akka.remote.secure-cookie", "") - if (cookie == "") None else Some(cookie) + val SECURE_COOKIE: Option[String] = config.getString("akka.remote.secure-cookie", "") match { + case "" => None + case cookie => Some(cookie) } val READ_TIMEOUT = Duration(config.getInt("akka.remote.client.read-timeout", 1), TIME_UNIT) From 718f831650bd44b62943a244b5045675ebac2944 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Thu, 30 Dec 2010 14:59:00 +0100 Subject: [PATCH 29/38] Adding support for failed messages to be notified to listeners, this closes ticket #587 --- .../remoteinterface/RemoteInterface.scala | 16 ++++++----- .../akka/remote/NettyRemoteSupport.scala | 27 +++++++++++++++---- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index 7a50a3556c..891c930ef5 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -63,16 +63,20 @@ trait RemoteModule extends Logging { */ sealed trait RemoteClientLifeCycleEvent //TODO: REVISIT: Document change from RemoteClient to RemoteClientModule + remoteAddress case class RemoteClientError( - @BeanProperty val cause: Throwable, - @BeanProperty val client: RemoteClientModule, val remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent + @BeanProperty cause: Throwable, + @BeanProperty client: RemoteClientModule, remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent case class RemoteClientDisconnected( - @BeanProperty val client: RemoteClientModule, val remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent + @BeanProperty client: RemoteClientModule, remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent case class RemoteClientConnected( - @BeanProperty val client: RemoteClientModule, val remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent + @BeanProperty client: RemoteClientModule, remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent case class RemoteClientStarted( - @BeanProperty val client: RemoteClientModule, val remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent + @BeanProperty client: RemoteClientModule, remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent case class RemoteClientShutdown( - @BeanProperty val client: RemoteClientModule, val remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent + @BeanProperty client: RemoteClientModule, remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent +case class RemoteClientWriteFailed( + @BeanProperty request: AnyRef, + @BeanProperty cause: Throwable, + @BeanProperty client: RemoteClientModule, remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent /** diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index b79bc696a1..ca60b39ab3 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -263,15 +263,32 @@ class RemoteClient private[akka] ( log.slf4j.debug("sending message: {} has future {}", request, senderFuture) if (isRunning) { if (request.getOneWay) { - connection.getChannel.write(request) + connection.getChannel.write(request).addListener(new ChannelFutureListener { + def operationComplete(future: ChannelFuture) { + if (future.isCancelled) { + //We don't care about that right now + } else if (!future.isSuccess) { + notifyListeners(RemoteClientWriteFailed(request, future.getCause, module, remoteAddress)) + } + } + }) None } else { val futureResult = if (senderFuture.isDefined) senderFuture.get else new DefaultCompletableFuture[T](request.getActorInfo.getTimeout) - val futureUuid = uuidFrom(request.getUuid.getHigh, request.getUuid.getLow) - futures.put(futureUuid, futureResult) - log.slf4j.debug("Stashing away future for {}",futureUuid) - connection.getChannel.write(request) + + connection.getChannel.write(request).addListener(new ChannelFutureListener { + def operationComplete(future: ChannelFuture) { + if (future.isCancelled) { + //We don't care about that right now + } else if (!future.isSuccess) { + notifyListeners(RemoteClientWriteFailed(request, future.getCause, module, remoteAddress)) + } else { + val futureUuid = uuidFrom(request.getUuid.getHigh, request.getUuid.getLow) + futures.put(futureUuid, futureResult) + } + } + }) Some(futureResult) } } else { From f679dd06ab324cb4500dc17e1d907cf2aec7b3b0 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Sat, 1 Jan 2011 21:01:44 +0100 Subject: [PATCH 30/38] Added support for passive connections in Netty remoting, closing ticket #507 --- .../akka/remote/NettyRemoteSupport.scala | 418 +++++++++++------- 1 file changed, 269 insertions(+), 149 deletions(-) diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index ca60b39ab3..4831b3fe94 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -31,21 +31,28 @@ import org.jboss.netty.handler.ssl.SslHandler import java.net.{ SocketAddress, InetSocketAddress } import java.util.concurrent.{ TimeUnit, Executors, ConcurrentMap, ConcurrentHashMap, ConcurrentSkipListSet } +import java.util.concurrent.locks.ReentrantReadWriteLock import scala.collection.mutable.{ HashSet, HashMap } import scala.reflect.BeanProperty import java.lang.reflect.InvocationTargetException -import akka.actor. {ActorInitializationException, LocalActorRef, newUuid, ActorRegistry, Actor, RemoteActorRef, TypedActor, ActorRef, IllegalActorStateException, RemoteActorSystemMessage, uuidFrom, Uuid, Exit, LifeCycleMessage, ActorType => AkkaActorType} import java.util.concurrent.atomic. {AtomicReference, AtomicLong, AtomicBoolean} import akka.remoteinterface._ +import akka.actor. {Index, ActorInitializationException, LocalActorRef, newUuid, ActorRegistry, Actor, RemoteActorRef, TypedActor, ActorRef, IllegalActorStateException, RemoteActorSystemMessage, uuidFrom, Uuid, Exit, LifeCycleMessage, ActorType => AkkaActorType} + +trait NettyRemoteShared { + def registerPassiveClient(channel: Channel): Boolean + def deregisterPassiveClient(channel: Channel): Boolean +} /** * The RemoteClient object manages RemoteClient instances and gives you an API to lookup remote actor handles. * * @author Jonas Bonér */ -trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagement with Logging => +trait NettyRemoteClientModule extends RemoteClientModule with NettyRemoteShared { self: ListenerManagement with Logging => private val remoteClients = new HashMap[String, RemoteClient] - private val remoteActors = new HashMap[Address, HashSet[Uuid]] + private val remoteActors = new Index[Address, Uuid] + private val lock = new ReentrantReadWriteLock protected[akka] def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): T = TypedActor.createProxyForRemoteActorRef(intfClass, RemoteActorRef(serviceId, implClassName, hostname, port, timeout, loader, AkkaActorType.TypedActor)) @@ -63,16 +70,76 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem clientFor(remoteAddress, loader).send[T](message, senderOption, senderFuture, remoteAddress, timeout, isOneWay, actorRef, typedActorInfo, actorType) private[akka] def clientFor( - address: InetSocketAddress, loader: Option[ClassLoader]): RemoteClient = synchronized { //TODO: REVISIT: synchronized here seems bottlenecky - val key = makeKey(address) + address: InetSocketAddress, loader: Option[ClassLoader]): RemoteClient = { loader.foreach(MessageSerializer.setClassLoader(_))//TODO: REVISIT: THIS SMELLS FUNNY + + val key = makeKey(address) + lock.readLock.lock remoteClients.get(key) match { - case Some(client) => client + case Some(client) => try { client } finally { lock.readLock.unlock } case None => - val client = new RemoteClient(this, address, loader, self.notifyListeners _) - client.connect() - remoteClients += key -> client - client + lock.readLock.unlock + lock.writeLock.lock //Lock upgrade, not supported natively + try { + remoteClients.get(key) match { //Recheck for addition, race between upgrades + case Some(client) => client //If already populated by other writer + case None => //Populate map + val client = new ActiveRemoteClient(this, address, loader, self.notifyListeners _) + client.connect() + remoteClients += key -> client + client + } + } finally { lock.writeLock.unlock } + } + } + + /** + * This method is called by the server module to register passive clients + */ + def registerPassiveClient(channel: Channel): Boolean = { + val address = channel.getRemoteAddress.asInstanceOf[InetSocketAddress] + val key = makeKey(address) + lock.readLock.lock + remoteClients.get(key) match { + case Some(client) => try { false } finally { lock.readLock.unlock } + case None => + lock.readLock.unlock + lock.writeLock.lock //Lock upgrade, not supported natively + try { + remoteClients.get(key) match { + case Some(client) => false + case None => + val client = new PassiveRemoteClient(this, address, channel, self.notifyListeners _ ) + client.connect() + remoteClients.put(key, client) + true + } + } finally { lock.writeLock.unlock } + } + } + + /** + * This method is called by the server module to deregister passive clients + */ + def deregisterPassiveClient(channel: Channel): Boolean = { + val address = channel.getRemoteAddress.asInstanceOf[InetSocketAddress] + val key = makeKey(address) + lock.readLock.lock + remoteClients.get(key) match { + case Some(client: PassiveRemoteClient) => + lock.readLock.unlock + lock.writeLock.lock //Lock upgrade, not supported natively + try { + remoteClients.get(key) match { + case Some(client: ActiveRemoteClient) => false + case None => false + case Some(client: PassiveRemoteClient) => + remoteClients.remove(key) + true + } + } finally { lock.writeLock.unlock } + //Otherwise, unlock the readlock and return false + case _ => try { false } finally { lock.readLock.unlock } } } @@ -81,52 +148,70 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem case address => address.getHostName + ':' + address.getPort } - def shutdownClientConnection(address: InetSocketAddress): Boolean = synchronized { - remoteClients.remove(makeKey(address)) match { - case Some(client) => client.shutdown - case None => false + def shutdownClientConnection(address: InetSocketAddress): Boolean = { + lock.writeLock.lock + try { + remoteClients.remove(makeKey(address)) match { + case Some(client) => client.shutdown + case None => false + } + } finally { + lock.writeLock.unlock } } - def restartClientConnection(address: InetSocketAddress): Boolean = synchronized { - remoteClients.get(makeKey(address)) match { - case Some(client) => client.connect(reconnectIfAlreadyConnected = true) - case None => false + def restartClientConnection(address: InetSocketAddress): Boolean = { + lock.readLock.lock + try { + remoteClients.get(makeKey(address)) match { + case Some(client) => client.connect(reconnectIfAlreadyConnected = true) + case None => false + } + } finally { + lock.readLock.unlock } } private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef = clientFor(actorRef.homeAddress.get, None).registerSupervisorForActor(actorRef) - private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef = - clientFor(actorRef.homeAddress.get, None).deregisterSupervisorForActor(actorRef) + private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef = { + val key = makeKey(actorRef.homeAddress.get) + lock.readLock.lock //TODO: perhaps use writelock here + try { + remoteClients.get(key) match { + case Some(client) => client.deregisterSupervisorForActor(actorRef) + case None => actorRef + } + } finally { + lock.readLock.unlock + } + } /** * Clean-up all open connections. */ - def shutdownClientModule = synchronized { + def shutdownClientModule = { + shutdownRemoteClients + //TODO: Should we empty our remoteActors too? + //remoteActors.clear + } + + def shutdownRemoteClients = try { + lock.writeLock.lock remoteClients.foreach({ case (addr, client) => client.shutdown }) remoteClients.clear + } finally { + lock.writeLock.unlock } - def registerClientManagedActor(hostname: String, port: Int, uuid: Uuid) = synchronized { - actorsFor(Address(hostname, port)) += uuid + def registerClientManagedActor(hostname: String, port: Int, uuid: Uuid) = { + remoteActors.put(Address(hostname, port), uuid) } - private[akka] def unregisterClientManagedActor(hostname: String, port: Int, uuid: Uuid) = synchronized { - val set = actorsFor(Address(hostname, port)) - set -= uuid - if (set.isEmpty) shutdownClientConnection(new InetSocketAddress(hostname, port)) - } - - private[akka] def actorsFor(remoteServerAddress: Address): HashSet[Uuid] = { - val set = remoteActors.get(remoteServerAddress) - if (set.isDefined && (set.get ne null)) set.get - else { - val remoteActorSet = new HashSet[Uuid] - remoteActors.put(remoteServerAddress, remoteActorSet) - remoteActorSet - } + private[akka] def unregisterClientManagedActor(hostname: String, port: Int, uuid: Uuid) = { + remoteActors.remove(Address(hostname,port), uuid) + //TODO: should the connection be closed when the last actor deregisters? } } @@ -135,45 +220,152 @@ object RemoteClient { case "" => None case cookie => Some(cookie) } - + val RECONNECTION_TIME_WINDOW = Duration(config.getInt("akka.remote.client.reconnection-time-window", 600), TIME_UNIT).toMillis val READ_TIMEOUT = Duration(config.getInt("akka.remote.client.read-timeout", 1), TIME_UNIT) val RECONNECT_DELAY = Duration(config.getInt("akka.remote.client.reconnect-delay", 5), TIME_UNIT) val MESSAGE_FRAME_SIZE = config.getInt("akka.remote.client.message-frame-size", 1048576) } -/** - * RemoteClient represents a connection to a RemoteServer. Is used to send messages to remote actors on the RemoteServer. - * - * @author Jonas Bonér - */ -class RemoteClient private[akka] ( + +abstract class RemoteClient private[akka] ( val module: NettyRemoteClientModule, - val remoteAddress: InetSocketAddress, - val loader: Option[ClassLoader] = None, - val notifyListeners: (=> Any) => Unit) extends Logging { - val name = "RemoteClient@" + remoteAddress.getHostName + "::" + remoteAddress.getPort + val remoteAddress: InetSocketAddress) extends Logging { - //FIXME Should these be clear:ed on postStop? - private val futures = new ConcurrentHashMap[Uuid, CompletableFuture[_]] - private val supervisors = new ConcurrentHashMap[Uuid, ActorRef] + val name = this.getClass.getSimpleName + "@" + remoteAddress.getHostName + "::" + remoteAddress.getPort - //FIXME rewrite to a wrapper object (minimize volatile access and maximize encapsulation) - @volatile - private var bootstrap: ClientBootstrap = _ - @volatile - private[remote] var connection: ChannelFuture = _ - @volatile - private[remote] var openChannels: DefaultChannelGroup = _ - @volatile - private var timer: HashedWheelTimer = _ + protected val futures = new ConcurrentHashMap[Uuid, CompletableFuture[_]] + protected val supervisors = new ConcurrentHashMap[Uuid, ActorRef] private[remote] val runSwitch = new Switch() private[remote] val isAuthenticated = new AtomicBoolean(false) private[remote] def isRunning = runSwitch.isOn - private val reconnectionTimeWindow = Duration(config.getInt( - "akka.remote.client.reconnection-time-window", 600), TIME_UNIT).toMillis - @volatile - private var reconnectionTimeWindowStart = 0L + protected def notifyListeners(msg: => Any); Unit + protected def currentChannel: Channel + + def connect(reconnectIfAlreadyConnected: Boolean = false): Boolean + def shutdown: Boolean + + def send[T]( + message: Any, + senderOption: Option[ActorRef], + senderFuture: Option[CompletableFuture[T]], + remoteAddress: InetSocketAddress, + timeout: Long, + isOneWay: Boolean, + actorRef: ActorRef, + typedActorInfo: Option[Tuple2[String, String]], + actorType: AkkaActorType): Option[CompletableFuture[T]] = { + send(createRemoteMessageProtocolBuilder( + Some(actorRef), + Left(actorRef.uuid), + actorRef.id, + actorRef.actorClassName, + actorRef.timeout, + Left(message), + isOneWay, + senderOption, + typedActorInfo, + actorType, + if (isAuthenticated.compareAndSet(false, true)) RemoteClient.SECURE_COOKIE else None + ).build, senderFuture) + } + + def send[T]( + request: RemoteMessageProtocol, + senderFuture: Option[CompletableFuture[T]]): Option[CompletableFuture[T]] = { + log.slf4j.debug("sending message: {} has future {}", request, senderFuture) + if (isRunning) { + if (request.getOneWay) { + currentChannel.write(request).addListener(new ChannelFutureListener { + def operationComplete(future: ChannelFuture) { + if (future.isCancelled) { + //We don't care about that right now + } else if (!future.isSuccess) { + notifyListeners(RemoteClientWriteFailed(request, future.getCause, module, remoteAddress)) + } + } + }) + None + } else { + val futureResult = if (senderFuture.isDefined) senderFuture.get + else new DefaultCompletableFuture[T](request.getActorInfo.getTimeout) + + currentChannel.write(request).addListener(new ChannelFutureListener { + def operationComplete(future: ChannelFuture) { + if (future.isCancelled) { + //We don't care about that right now + } else if (!future.isSuccess) { + notifyListeners(RemoteClientWriteFailed(request, future.getCause, module, remoteAddress)) + } else { + val futureUuid = uuidFrom(request.getUuid.getHigh, request.getUuid.getLow) + futures.put(futureUuid, futureResult) + } + } + }) + Some(futureResult) + } + } else { + val exception = new RemoteClientException("Remote client is not running, make sure you have invoked 'RemoteClient.connect' before using it.", module, remoteAddress) + notifyListeners(RemoteClientError(exception, module, remoteAddress)) + throw exception + } + } + + private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef = + if (!actorRef.supervisor.isDefined) throw new IllegalActorStateException( + "Can't register supervisor for " + actorRef + " since it is not under supervision") + else supervisors.putIfAbsent(actorRef.supervisor.get.uuid, actorRef) + + private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef = + if (!actorRef.supervisor.isDefined) throw new IllegalActorStateException( + "Can't unregister supervisor for " + actorRef + " since it is not under supervision") + else supervisors.remove(actorRef.supervisor.get.uuid) +} + +/** + * PassiveRemoteClient reuses an incoming connection + */ +class PassiveRemoteClient(module: NettyRemoteClientModule, + remoteAddress: InetSocketAddress, + val currentChannel : Channel, + notifyListenersFun: (=> Any) => Unit) extends RemoteClient(module, remoteAddress) { + def connect(reconnectIfAlreadyConnected: Boolean = false): Boolean = { //Cannot reconnect, it's passive. + runSwitch.switchOn { + notifyListeners(RemoteClientStarted(module, remoteAddress)) + } + false + } + + def shutdown = runSwitch switchOff { + log.slf4j.info("Shutting down {}", name) + notifyListeners(RemoteClientShutdown(module, remoteAddress)) + //try { currentChannel.close } catch { case _ => } //TODO: Add failure notification when currentchannel gets shut down? + log.slf4j.info("{} has been shut down", name) + } + + def notifyListeners(msg: => Any) = notifyListenersFun(msg) +} + +/** + * RemoteClient represents a connection to a RemoteServer. Is used to send messages to remote actors on the RemoteServer. + * + * @author Jonas Bonér + */ +class ActiveRemoteClient private[akka] ( + module: NettyRemoteClientModule, + remoteAddress: InetSocketAddress, + val loader: Option[ClassLoader] = None, + notifyListenersFun: (=> Any) => Unit) extends RemoteClient(module, remoteAddress) { + import RemoteClient._ + //FIXME rewrite to a wrapper object (minimize volatile access and maximize encapsulation) + @volatile private var bootstrap: ClientBootstrap = _ + @volatile private[remote] var connection: ChannelFuture = _ + @volatile private[remote] var openChannels: DefaultChannelGroup = _ + @volatile private var timer: HashedWheelTimer = _ + @volatile private var reconnectionTimeWindowStart = 0L + + def notifyListeners(msg: => Any): Unit = notifyListenersFun(msg) + def currentChannel = connection.getChannel def connect(reconnectIfAlreadyConnected: Boolean = false): Boolean = { runSwitch switchOn { @@ -181,7 +373,7 @@ class RemoteClient private[akka] ( timer = new HashedWheelTimer bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool)) - bootstrap.setPipelineFactory(new RemoteClientPipelineFactory(name, futures, supervisors, bootstrap, remoteAddress, timer, this)) + bootstrap.setPipelineFactory(new ActiveRemoteClientPipelineFactory(name, futures, supervisors, bootstrap, remoteAddress, timer, this)) bootstrap.setOption("tcpNoDelay", true) bootstrap.setOption("keepAlive", true) @@ -232,88 +424,12 @@ class RemoteClient private[akka] ( log.slf4j.info("{} has been shut down", name) } - def send[T]( - message: Any, - senderOption: Option[ActorRef], - senderFuture: Option[CompletableFuture[T]], - remoteAddress: InetSocketAddress, - timeout: Long, - isOneWay: Boolean, - actorRef: ActorRef, - typedActorInfo: Option[Tuple2[String, String]], - actorType: AkkaActorType): Option[CompletableFuture[T]] = { - send(createRemoteMessageProtocolBuilder( - Some(actorRef), - Left(actorRef.uuid), - actorRef.id, - actorRef.actorClassName, - actorRef.timeout, - Left(message), - isOneWay, - senderOption, - typedActorInfo, - actorType, - if (isAuthenticated.compareAndSet(false, true)) RemoteClient.SECURE_COOKIE else None - ).build, senderFuture) - } - - def send[T]( - request: RemoteMessageProtocol, - senderFuture: Option[CompletableFuture[T]]): Option[CompletableFuture[T]] = { - log.slf4j.debug("sending message: {} has future {}", request, senderFuture) - if (isRunning) { - if (request.getOneWay) { - connection.getChannel.write(request).addListener(new ChannelFutureListener { - def operationComplete(future: ChannelFuture) { - if (future.isCancelled) { - //We don't care about that right now - } else if (!future.isSuccess) { - notifyListeners(RemoteClientWriteFailed(request, future.getCause, module, remoteAddress)) - } - } - }) - None - } else { - val futureResult = if (senderFuture.isDefined) senderFuture.get - else new DefaultCompletableFuture[T](request.getActorInfo.getTimeout) - - connection.getChannel.write(request).addListener(new ChannelFutureListener { - def operationComplete(future: ChannelFuture) { - if (future.isCancelled) { - //We don't care about that right now - } else if (!future.isSuccess) { - notifyListeners(RemoteClientWriteFailed(request, future.getCause, module, remoteAddress)) - } else { - val futureUuid = uuidFrom(request.getUuid.getHigh, request.getUuid.getLow) - futures.put(futureUuid, futureResult) - } - } - }) - Some(futureResult) - } - } else { - val exception = new RemoteClientException("Remote client is not running, make sure you have invoked 'RemoteClient.connect' before using it.", module, remoteAddress) - notifyListeners(RemoteClientError(exception, module, remoteAddress)) - throw exception - } - } - - private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef = - if (!actorRef.supervisor.isDefined) throw new IllegalActorStateException( - "Can't register supervisor for " + actorRef + " since it is not under supervision") - else supervisors.putIfAbsent(actorRef.supervisor.get.uuid, actorRef) - - private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef = - if (!actorRef.supervisor.isDefined) throw new IllegalActorStateException( - "Can't unregister supervisor for " + actorRef + " since it is not under supervision") - else supervisors.remove(actorRef.supervisor.get.uuid) - private[akka] def isWithinReconnectionTimeWindow: Boolean = { if (reconnectionTimeWindowStart == 0L) { reconnectionTimeWindowStart = System.currentTimeMillis true } else { - val timeLeft = reconnectionTimeWindow - (System.currentTimeMillis - reconnectionTimeWindowStart) + val timeLeft = RECONNECTION_TIME_WINDOW - (System.currentTimeMillis - reconnectionTimeWindowStart) if (timeLeft > 0) { log.slf4j.info("Will try to reconnect to remote server for another [{}] milliseconds", timeLeft) true @@ -327,14 +443,14 @@ class RemoteClient private[akka] ( /** * @author Jonas Bonér */ -class RemoteClientPipelineFactory( +class ActiveRemoteClientPipelineFactory( name: String, futures: ConcurrentMap[Uuid, CompletableFuture[_]], supervisors: ConcurrentMap[Uuid, ActorRef], bootstrap: ClientBootstrap, remoteAddress: SocketAddress, timer: HashedWheelTimer, - client: RemoteClient) extends ChannelPipelineFactory { + client: ActiveRemoteClient) extends ChannelPipelineFactory { def getPipeline: ChannelPipeline = { def join(ch: ChannelHandler*) = Array[ChannelHandler](ch: _*) @@ -357,7 +473,7 @@ class RemoteClientPipelineFactory( case _ => (join(), join()) } - val remoteClient = new RemoteClientHandler(name, futures, supervisors, bootstrap, remoteAddress, timer, client) + val remoteClient = new ActiveRemoteClientHandler(name, futures, supervisors, bootstrap, remoteAddress, timer, client) val stages = ssl ++ join(timeout) ++ dec ++ join(lenDec, protobufDec) ++ enc ++ join(lenPrep, protobufEnc, remoteClient) new StaticChannelPipeline(stages: _*) } @@ -367,14 +483,14 @@ class RemoteClientPipelineFactory( * @author Jonas Bonér */ @ChannelHandler.Sharable -class RemoteClientHandler( +class ActiveRemoteClientHandler( val name: String, val futures: ConcurrentMap[Uuid, CompletableFuture[_]], val supervisors: ConcurrentMap[Uuid, ActorRef], val bootstrap: ClientBootstrap, val remoteAddress: SocketAddress, val timer: HashedWheelTimer, - val client: RemoteClient) + val client: ActiveRemoteClient) extends SimpleChannelUpstreamHandler with Logging { override def handleUpstream(ctx: ChannelHandlerContext, event: ChannelEvent) = { @@ -610,7 +726,7 @@ class NettyRemoteServer(serverModule: NettyRemoteServerModule, val host: String, } } -trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => +trait NettyRemoteServerModule extends RemoteServerModule with NettyRemoteShared { self: RemoteModule => import RemoteServer._ private[akka] val currentServer = new AtomicReference[Option[NettyRemoteServer]](None) @@ -877,7 +993,10 @@ class RemoteServerHandler( } else future.getChannel.close } }) - } else server.notifyListeners(RemoteServerClientConnected(server, clientAddress)) + } else { + server.registerPassiveClient(ctx.getChannel) + server.notifyListeners(RemoteServerClientConnected(server, clientAddress)) + } if (RemoteServer.REQUIRE_COOKIE) ctx.setAttachment(CHANNEL_INIT) // signal that this is channel initialization, which will need authentication } @@ -900,6 +1019,7 @@ class RemoteServerHandler( TypedActor.stop(channelTypedActorsIterator.nextElement) } } + server.deregisterPassiveClient(ctx.getChannel) server.notifyListeners(RemoteServerClientDisconnected(server, clientAddress)) } From d5095be95d4da1edd37656a72f1d951005f2ff3a Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Sat, 1 Jan 2011 22:20:43 +0100 Subject: [PATCH 31/38] Minor code cleanup --- .../src/main/scala/akka/util/LockUtil.scala | 4 +- .../akka/remote/NettyRemoteSupport.scala | 48 ++++++------------- 2 files changed, 16 insertions(+), 36 deletions(-) diff --git a/akka-actor/src/main/scala/akka/util/LockUtil.scala b/akka-actor/src/main/scala/akka/util/LockUtil.scala index bccf42a121..ab383f5f85 100644 --- a/akka-actor/src/main/scala/akka/util/LockUtil.scala +++ b/akka-actor/src/main/scala/akka/util/LockUtil.scala @@ -37,8 +37,8 @@ class ReentrantGuard { */ class ReadWriteGuard { private val rwl = new ReentrantReadWriteLock - private val readLock = rwl.readLock - private val writeLock = rwl.writeLock + val readLock = rwl.readLock + val writeLock = rwl.writeLock def withWriteGuard[T](body: => T): T = { writeLock.lock diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index 4831b3fe94..165ca1a4c3 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -31,7 +31,6 @@ import org.jboss.netty.handler.ssl.SslHandler import java.net.{ SocketAddress, InetSocketAddress } import java.util.concurrent.{ TimeUnit, Executors, ConcurrentMap, ConcurrentHashMap, ConcurrentSkipListSet } -import java.util.concurrent.locks.ReentrantReadWriteLock import scala.collection.mutable.{ HashSet, HashMap } import scala.reflect.BeanProperty import java.lang.reflect.InvocationTargetException @@ -52,7 +51,7 @@ trait NettyRemoteShared { trait NettyRemoteClientModule extends RemoteClientModule with NettyRemoteShared { self: ListenerManagement with Logging => private val remoteClients = new HashMap[String, RemoteClient] private val remoteActors = new Index[Address, Uuid] - private val lock = new ReentrantReadWriteLock + private val lock = new ReadWriteGuard protected[akka] def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, hostname: String, port: Int, loader: Option[ClassLoader]): T = TypedActor.createProxyForRemoteActorRef(intfClass, RemoteActorRef(serviceId, implClassName, hostname, port, timeout, loader, AkkaActorType.TypedActor)) @@ -148,43 +147,27 @@ trait NettyRemoteClientModule extends RemoteClientModule with NettyRemoteShared case address => address.getHostName + ':' + address.getPort } - def shutdownClientConnection(address: InetSocketAddress): Boolean = { - lock.writeLock.lock - try { - remoteClients.remove(makeKey(address)) match { - case Some(client) => client.shutdown - case None => false - } - } finally { - lock.writeLock.unlock + def shutdownClientConnection(address: InetSocketAddress): Boolean = lock withWriteGuard { + remoteClients.remove(makeKey(address)) match { + case Some(client) => client.shutdown + case None => false } } - def restartClientConnection(address: InetSocketAddress): Boolean = { - lock.readLock.lock - try { - remoteClients.get(makeKey(address)) match { - case Some(client) => client.connect(reconnectIfAlreadyConnected = true) - case None => false - } - } finally { - lock.readLock.unlock + def restartClientConnection(address: InetSocketAddress): Boolean = lock withReadGuard { + remoteClients.get(makeKey(address)) match { + case Some(client) => client.connect(reconnectIfAlreadyConnected = true) + case None => false } } private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef = clientFor(actorRef.homeAddress.get, None).registerSupervisorForActor(actorRef) - private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef = { - val key = makeKey(actorRef.homeAddress.get) - lock.readLock.lock //TODO: perhaps use writelock here - try { - remoteClients.get(key) match { - case Some(client) => client.deregisterSupervisorForActor(actorRef) - case None => actorRef - } - } finally { - lock.readLock.unlock + private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef = lock withReadGuard { + remoteClients.get(makeKey(actorRef.homeAddress.get)) match { + case Some(client) => client.deregisterSupervisorForActor(actorRef) + case None => actorRef } } @@ -197,12 +180,9 @@ trait NettyRemoteClientModule extends RemoteClientModule with NettyRemoteShared //remoteActors.clear } - def shutdownRemoteClients = try { - lock.writeLock.lock + def shutdownRemoteClients = lock withWriteGuard { remoteClients.foreach({ case (addr, client) => client.shutdown }) remoteClients.clear - } finally { - lock.writeLock.unlock } def registerClientManagedActor(hostname: String, port: Int, uuid: Uuid) = { From 63a182afd7fc918ee2fb8e75c50381198352aeaa Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Sat, 1 Jan 2011 23:18:51 +0100 Subject: [PATCH 32/38] Added lock downgrades and fixed unlocking ordering --- .../akka/remote/NettyRemoteSupport.scala | 102 +++++++++--------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index 165ca1a4c3..39a32e9557 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -74,22 +74,24 @@ trait NettyRemoteClientModule extends RemoteClientModule with NettyRemoteShared val key = makeKey(address) lock.readLock.lock - remoteClients.get(key) match { - case Some(client) => try { client } finally { lock.readLock.unlock } - case None => - lock.readLock.unlock - lock.writeLock.lock //Lock upgrade, not supported natively - try { - remoteClients.get(key) match { //Recheck for addition, race between upgrades - case Some(client) => client //If already populated by other writer - case None => //Populate map - val client = new ActiveRemoteClient(this, address, loader, self.notifyListeners _) - client.connect() - remoteClients += key -> client - client - } - } finally { lock.writeLock.unlock } - } + try { + remoteClients.get(key) match { + case Some(client) => client + case None => + lock.readLock.unlock + lock.writeLock.lock //Lock upgrade, not supported natively + try { + remoteClients.get(key) match { //Recheck for addition, race between upgrades + case Some(client) => client //If already populated by other writer + case None => //Populate map + val client = new ActiveRemoteClient(this, address, loader, self.notifyListeners _) + client.connect() + remoteClients += key -> client + client + } + } finally { lock.readLock.lock; lock.writeLock.unlock } //downgrade + } + } finally { lock.readLock.unlock } } /** @@ -99,22 +101,24 @@ trait NettyRemoteClientModule extends RemoteClientModule with NettyRemoteShared val address = channel.getRemoteAddress.asInstanceOf[InetSocketAddress] val key = makeKey(address) lock.readLock.lock - remoteClients.get(key) match { - case Some(client) => try { false } finally { lock.readLock.unlock } - case None => - lock.readLock.unlock - lock.writeLock.lock //Lock upgrade, not supported natively - try { - remoteClients.get(key) match { - case Some(client) => false - case None => - val client = new PassiveRemoteClient(this, address, channel, self.notifyListeners _ ) - client.connect() - remoteClients.put(key, client) - true - } - } finally { lock.writeLock.unlock } - } + try { + remoteClients.get(key) match { + case Some(client) => false + case None => + lock.readLock.unlock + lock.writeLock.lock //Lock upgrade, not supported natively + try { + remoteClients.get(key) match { + case Some(client) => false + case None => + val client = new PassiveRemoteClient(this, address, channel, self.notifyListeners _ ) + client.connect() + remoteClients.put(key, client) + true + } + } finally { lock.readLock.lock; lock.writeLock.unlock } //downgrade + } + } finally { lock.readLock.unlock } } /** @@ -124,22 +128,24 @@ trait NettyRemoteClientModule extends RemoteClientModule with NettyRemoteShared val address = channel.getRemoteAddress.asInstanceOf[InetSocketAddress] val key = makeKey(address) lock.readLock.lock - remoteClients.get(key) match { - case Some(client: PassiveRemoteClient) => - lock.readLock.unlock - lock.writeLock.lock //Lock upgrade, not supported natively - try { - remoteClients.get(key) match { - case Some(client: ActiveRemoteClient) => false - case None => false - case Some(client: PassiveRemoteClient) => - remoteClients.remove(key) - true - } - } finally { lock.writeLock.unlock } - //Otherwise, unlock the readlock and return false - case _ => try { false } finally { lock.readLock.unlock } - } + try { + remoteClients.get(key) match { + case Some(client: PassiveRemoteClient) => + lock.readLock.unlock + lock.writeLock.lock //Lock upgrade, not supported natively + try { + remoteClients.get(key) match { + case Some(client: ActiveRemoteClient) => false + case None => false + case Some(client: PassiveRemoteClient) => + remoteClients.remove(key) + true + } + } finally { lock.readLock.lock; lock.writeLock.unlock } //downgrade + //Otherwise, unlock the readlock and return false + case _ => false + } + } finally { lock.readLock.unlock } } private def makeKey(a: InetSocketAddress): String = a match { From 8e522f4a0354dbe4e1dacc47edcbf13b04019aeb Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 3 Jan 2011 11:25:27 +0100 Subject: [PATCH 33/38] Removing PassiveRemoteClient because of architectural problems --- .../src/main/scala/akka/util/Address.scala | 2 +- .../akka/remote/NettyRemoteSupport.scala | 117 +++--------------- .../ServerInitiatedRemoteActorSpec.scala | 11 ++ 3 files changed, 29 insertions(+), 101 deletions(-) diff --git a/akka-actor/src/main/scala/akka/util/Address.scala b/akka-actor/src/main/scala/akka/util/Address.scala index 07ffbec303..66af8aec6a 100644 --- a/akka-actor/src/main/scala/akka/util/Address.scala +++ b/akka-actor/src/main/scala/akka/util/Address.scala @@ -8,7 +8,7 @@ object Address { } class Address(val hostname: String, val port: Int) { - override def hashCode: Int = { + override val hashCode: Int = { var result = HashCode.SEED result = HashCode.hash(result, hostname) result = HashCode.hash(result, port) diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala index 39a32e9557..dd670b5bd4 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala @@ -37,19 +37,13 @@ import java.lang.reflect.InvocationTargetException import java.util.concurrent.atomic. {AtomicReference, AtomicLong, AtomicBoolean} import akka.remoteinterface._ import akka.actor. {Index, ActorInitializationException, LocalActorRef, newUuid, ActorRegistry, Actor, RemoteActorRef, TypedActor, ActorRef, IllegalActorStateException, RemoteActorSystemMessage, uuidFrom, Uuid, Exit, LifeCycleMessage, ActorType => AkkaActorType} - -trait NettyRemoteShared { - def registerPassiveClient(channel: Channel): Boolean - def deregisterPassiveClient(channel: Channel): Boolean -} - /** * The RemoteClient object manages RemoteClient instances and gives you an API to lookup remote actor handles. * * @author Jonas Bonér */ -trait NettyRemoteClientModule extends RemoteClientModule with NettyRemoteShared { self: ListenerManagement with Logging => - private val remoteClients = new HashMap[String, RemoteClient] +trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagement with Logging => + private val remoteClients = new HashMap[Address, RemoteClient] private val remoteActors = new Index[Address, Uuid] private val lock = new ReadWriteGuard @@ -81,76 +75,24 @@ trait NettyRemoteClientModule extends RemoteClientModule with NettyRemoteShared lock.readLock.unlock lock.writeLock.lock //Lock upgrade, not supported natively try { - remoteClients.get(key) match { //Recheck for addition, race between upgrades - case Some(client) => client //If already populated by other writer - case None => //Populate map - val client = new ActiveRemoteClient(this, address, loader, self.notifyListeners _) - client.connect() - remoteClients += key -> client - client - } - } finally { lock.readLock.lock; lock.writeLock.unlock } //downgrade + try { + remoteClients.get(key) match { //Recheck for addition, race between upgrades + case Some(client) => client //If already populated by other writer + case None => //Populate map + val client = new ActiveRemoteClient(this, address, loader, self.notifyListeners _) + client.connect() + remoteClients += key -> client + client + } + } finally { lock.readLock.lock } //downgrade + } finally { lock.writeLock.unlock } } } finally { lock.readLock.unlock } } - /** - * This method is called by the server module to register passive clients - */ - def registerPassiveClient(channel: Channel): Boolean = { - val address = channel.getRemoteAddress.asInstanceOf[InetSocketAddress] - val key = makeKey(address) - lock.readLock.lock - try { - remoteClients.get(key) match { - case Some(client) => false - case None => - lock.readLock.unlock - lock.writeLock.lock //Lock upgrade, not supported natively - try { - remoteClients.get(key) match { - case Some(client) => false - case None => - val client = new PassiveRemoteClient(this, address, channel, self.notifyListeners _ ) - client.connect() - remoteClients.put(key, client) - true - } - } finally { lock.readLock.lock; lock.writeLock.unlock } //downgrade - } - } finally { lock.readLock.unlock } - } - - /** - * This method is called by the server module to deregister passive clients - */ - def deregisterPassiveClient(channel: Channel): Boolean = { - val address = channel.getRemoteAddress.asInstanceOf[InetSocketAddress] - val key = makeKey(address) - lock.readLock.lock - try { - remoteClients.get(key) match { - case Some(client: PassiveRemoteClient) => - lock.readLock.unlock - lock.writeLock.lock //Lock upgrade, not supported natively - try { - remoteClients.get(key) match { - case Some(client: ActiveRemoteClient) => false - case None => false - case Some(client: PassiveRemoteClient) => - remoteClients.remove(key) - true - } - } finally { lock.readLock.lock; lock.writeLock.unlock } //downgrade - //Otherwise, unlock the readlock and return false - case _ => false - } - } finally { lock.readLock.unlock } - } - - private def makeKey(a: InetSocketAddress): String = a match { + private def makeKey(a: InetSocketAddress): Address = a match { case null => null - case address => address.getHostName + ':' + address.getPort + case address => Address(address.getHostName,address.getPort) } def shutdownClientConnection(address: InetSocketAddress): Boolean = lock withWriteGuard { @@ -308,30 +250,6 @@ abstract class RemoteClient private[akka] ( else supervisors.remove(actorRef.supervisor.get.uuid) } -/** - * PassiveRemoteClient reuses an incoming connection - */ -class PassiveRemoteClient(module: NettyRemoteClientModule, - remoteAddress: InetSocketAddress, - val currentChannel : Channel, - notifyListenersFun: (=> Any) => Unit) extends RemoteClient(module, remoteAddress) { - def connect(reconnectIfAlreadyConnected: Boolean = false): Boolean = { //Cannot reconnect, it's passive. - runSwitch.switchOn { - notifyListeners(RemoteClientStarted(module, remoteAddress)) - } - false - } - - def shutdown = runSwitch switchOff { - log.slf4j.info("Shutting down {}", name) - notifyListeners(RemoteClientShutdown(module, remoteAddress)) - //try { currentChannel.close } catch { case _ => } //TODO: Add failure notification when currentchannel gets shut down? - log.slf4j.info("{} has been shut down", name) - } - - def notifyListeners(msg: => Any) = notifyListenersFun(msg) -} - /** * RemoteClient represents a connection to a RemoteServer. Is used to send messages to remote actors on the RemoteServer. * @@ -712,7 +630,7 @@ class NettyRemoteServer(serverModule: NettyRemoteServerModule, val host: String, } } -trait NettyRemoteServerModule extends RemoteServerModule with NettyRemoteShared { self: RemoteModule => +trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => import RemoteServer._ private[akka] val currentServer = new AtomicReference[Option[NettyRemoteServer]](None) @@ -980,7 +898,6 @@ class RemoteServerHandler( } }) } else { - server.registerPassiveClient(ctx.getChannel) server.notifyListeners(RemoteServerClientConnected(server, clientAddress)) } if (RemoteServer.REQUIRE_COOKIE) ctx.setAttachment(CHANNEL_INIT) // signal that this is channel initialization, which will need authentication @@ -1005,7 +922,7 @@ class RemoteServerHandler( TypedActor.stop(channelTypedActorsIterator.nextElement) } } - server.deregisterPassiveClient(ctx.getChannel) + server.notifyListeners(RemoteServerClientDisconnected(server, clientAddress)) } diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala index c648217410..b119a1e1fc 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala @@ -160,6 +160,17 @@ class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { remote.actorsByUuid.get(actor1.uuid) must be (null) } + "shouldHandleOneWayReplyThroughPassiveRemoteClient" in { + val actor1 = actorOf[RemoteActorSpecActorUnidirectional] + remote.register("foo", actor1) + val latch = new CountDownLatch(1) + val actor2 = actorOf(new Actor { def receive = { case "Pong" => latch.countDown } }).start + + val remoteActor = remote.actorFor("foo", host, port) + remoteActor.!("Ping")(Some(actor2)) + latch.await(3,TimeUnit.SECONDS) must be (true) + } + "should be able to remotely communicate between 2 server-managed actors" in { val localFoo = actorOf[Decrementer] val localBar = actorOf[Decrementer] From a61e591b2a148e0c8bf967560dbc18e636cbe35c Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 3 Jan 2011 12:42:30 +0100 Subject: [PATCH 34/38] Putting the Netty-stuff in akka.remote.netty and disposing of RemoteClient and RemoteServer --- .../src/main/scala/akka/util/Address.scala | 6 + .../scala/akka/util/ReflectiveAccess.scala | 2 +- .../remote/BootableRemoteActorService.scala | 2 +- .../main/scala/akka/remote/RemoteShared.scala | 66 +++++++ .../{ => netty}/NettyRemoteSupport.scala | 181 +++++++----------- .../serialization/SerializationProtocol.scala | 4 +- .../test/scala/remote/AkkaRemoteTest.scala | 2 +- .../ClientInitiatedRemoteActorSpec.scala | 1 - .../scala/remote/RemoteSupervisorSpec.scala | 1 - .../ServerInitiatedRemoteActorSpec.scala | 1 - ...erverInitiatedRemoteSessionActorSpec.scala | 2 +- .../ServerInitiatedRemoteTypedActorSpec.scala | 1 - ...rotobufActorMessageSerializationSpec.scala | 5 - 13 files changed, 144 insertions(+), 130 deletions(-) create mode 100644 akka-remote/src/main/scala/akka/remote/RemoteShared.scala rename akka-remote/src/main/scala/akka/remote/{ => netty}/NettyRemoteSupport.scala (88%) diff --git a/akka-actor/src/main/scala/akka/util/Address.scala b/akka-actor/src/main/scala/akka/util/Address.scala index 66af8aec6a..4e1749c560 100644 --- a/akka-actor/src/main/scala/akka/util/Address.scala +++ b/akka-actor/src/main/scala/akka/util/Address.scala @@ -3,8 +3,14 @@ */ package akka.util +import java.net.InetSocketAddress + object Address { def apply(hostname: String, port: Int) = new Address(hostname, port) + def apply(inetAddress: InetSocketAddress): Address = inetAddress match { + case null => null + case inet => new Address(inet.getHostName, inet.getPort) + } } class Address(val hostname: String, val port: Int) { diff --git a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala index 606044a66e..cc657bca3b 100644 --- a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala +++ b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala @@ -33,7 +33,7 @@ object ReflectiveAccess extends Logging { * @author Jonas Bonér */ object Remote { - val TRANSPORT = Config.config.getString("akka.remote.layer","akka.remote.NettyRemoteSupport") + val TRANSPORT = Config.config.getString("akka.remote.layer","akka.remote.netty.NettyRemoteSupport") private[akka] val configDefaultAddress = new InetSocketAddress(Config.config.getString("akka.remote.server.hostname", "localhost"), diff --git a/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala b/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala index 7951dd25f9..c0aa2218dd 100644 --- a/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala +++ b/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala @@ -23,7 +23,7 @@ trait BootableRemoteActorService extends Bootable with Logging { def startRemoteService = remoteServerThread.start abstract override def onLoad = { - if (RemoteServer.isRemotingEnabled) { + if (ReflectiveAccess.isRemotingEnabled && RemoteServerSettings.isRemotingEnabled) { log.slf4j.info("Initializing Remote Actors Service...") startRemoteService log.slf4j.info("Remote Actors Service initialized") diff --git a/akka-remote/src/main/scala/akka/remote/RemoteShared.scala b/akka-remote/src/main/scala/akka/remote/RemoteShared.scala new file mode 100644 index 0000000000..57a28f5c21 --- /dev/null +++ b/akka-remote/src/main/scala/akka/remote/RemoteShared.scala @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2009-2011 Scalable Solutions AB + */ + +package akka.remote + +import akka.util.Duration +import akka.config.Config._ +import akka.config.ConfigurationException + +object RemoteClientSettings { + val SECURE_COOKIE: Option[String] = config.getString("akka.remote.secure-cookie", "") match { + case "" => None + case cookie => Some(cookie) + } + val RECONNECTION_TIME_WINDOW = Duration(config.getInt("akka.remote.client.reconnection-time-window", 600), TIME_UNIT).toMillis + val READ_TIMEOUT = Duration(config.getInt("akka.remote.client.read-timeout", 1), TIME_UNIT) + val RECONNECT_DELAY = Duration(config.getInt("akka.remote.client.reconnect-delay", 5), TIME_UNIT) + val MESSAGE_FRAME_SIZE = config.getInt("akka.remote.client.message-frame-size", 1048576) +} + +object RemoteServerSettings { + val isRemotingEnabled = config.getList("akka.enabled-modules").exists(_ == "remote") + val MESSAGE_FRAME_SIZE = config.getInt("akka.remote.server.message-frame-size", 1048576) + val SECURE_COOKIE = config.getString("akka.remote.secure-cookie") + val REQUIRE_COOKIE = { + val requireCookie = config.getBool("akka.remote.server.require-cookie", true) + if (isRemotingEnabled && requireCookie && SECURE_COOKIE.isEmpty) throw new ConfigurationException( + "Configuration option 'akka.remote.server.require-cookie' is turned on but no secure cookie is defined in 'akka.remote.secure-cookie'.") + requireCookie + } + + val UNTRUSTED_MODE = config.getBool("akka.remote.server.untrusted-mode", false) + val HOSTNAME = config.getString("akka.remote.server.hostname", "localhost") + val PORT = config.getInt("akka.remote.server.port", 2552) + val CONNECTION_TIMEOUT_MILLIS = Duration(config.getInt("akka.remote.server.connection-timeout", 1), TIME_UNIT) + val COMPRESSION_SCHEME = config.getString("akka.remote.compression-scheme", "zlib") + val ZLIB_COMPRESSION_LEVEL = { + val level = config.getInt("akka.remote.zlib-compression-level", 6) + if (level < 1 && level > 9) throw new IllegalArgumentException( + "zlib compression level has to be within 1-9, with 1 being fastest and 9 being the most compressed") + level + } + + val SECURE = { + /*if (config.getBool("akka.remote.ssl.service",false)) { + val properties = List( + ("key-store-type" , "keyStoreType"), + ("key-store" , "keyStore"), + ("key-store-pass" , "keyStorePassword"), + ("trust-store-type", "trustStoreType"), + ("trust-store" , "trustStore"), + ("trust-store-pass", "trustStorePassword") + ).map(x => ("akka.remote.ssl." + x._1, "javax.net.ssl." + x._2)) + + // If property is not set, and we have a value from our akka.conf, use that value + for { + p <- properties if System.getProperty(p._2) eq null + c <- config.getString(p._1) + } System.setProperty(p._2, c) + + if (config.getBool("akka.remote.ssl.debug", false)) System.setProperty("javax.net.debug","ssl") + true + } else */false + } +} diff --git a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala similarity index 88% rename from akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala rename to akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index dd670b5bd4..00eb9910fa 100644 --- a/akka-remote/src/main/scala/akka/remote/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -2,20 +2,23 @@ * Copyright (C) 2009-2011 Scalable Solutions AB */ -package akka.remote +package akka.remote.netty -import akka.remote.protocol.RemoteProtocol.{ActorType => ActorTypeProtocol, _} import akka.dispatch.{DefaultCompletableFuture, CompletableFuture, Future} import akka.remote.protocol.RemoteProtocol._ import akka.remote.protocol.RemoteProtocol.ActorType._ import akka.config.ConfigurationException import akka.serialization.RemoteActorSerialization +import akka.serialization.RemoteActorSerialization._ import akka.japi.Creator import akka.config.Config._ -import akka.serialization.RemoteActorSerialization._ +import akka.remoteinterface._ +import akka.actor. {Index, ActorInitializationException, LocalActorRef, newUuid, ActorRegistry, Actor, RemoteActorRef, TypedActor, ActorRef, IllegalActorStateException, RemoteActorSystemMessage, uuidFrom, Uuid, Exit, LifeCycleMessage, ActorType => AkkaActorType} import akka.AkkaException import akka.actor.Actor._ import akka.util._ +import akka.remote.MessageSerializer +import akka.remote.{RemoteClientSettings, RemoteServerSettings} import org.jboss.netty.channel._ import org.jboss.netty.channel.group.{DefaultChannelGroup,ChannelGroup} @@ -31,17 +34,11 @@ import org.jboss.netty.handler.ssl.SslHandler import java.net.{ SocketAddress, InetSocketAddress } import java.util.concurrent.{ TimeUnit, Executors, ConcurrentMap, ConcurrentHashMap, ConcurrentSkipListSet } -import scala.collection.mutable.{ HashSet, HashMap } +import scala.collection.mutable.{ HashMap } import scala.reflect.BeanProperty import java.lang.reflect.InvocationTargetException import java.util.concurrent.atomic. {AtomicReference, AtomicLong, AtomicBoolean} -import akka.remoteinterface._ -import akka.actor. {Index, ActorInitializationException, LocalActorRef, newUuid, ActorRegistry, Actor, RemoteActorRef, TypedActor, ActorRef, IllegalActorStateException, RemoteActorSystemMessage, uuidFrom, Uuid, Exit, LifeCycleMessage, ActorType => AkkaActorType} -/** - * The RemoteClient object manages RemoteClient instances and gives you an API to lookup remote actor handles. - * - * @author Jonas Bonér - */ + trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagement with Logging => private val remoteClients = new HashMap[Address, RemoteClient] private val remoteActors = new Index[Address, Uuid] @@ -60,16 +57,16 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem typedActorInfo: Option[Tuple2[String, String]], actorType: AkkaActorType, loader: Option[ClassLoader]): Option[CompletableFuture[T]] = - clientFor(remoteAddress, loader).send[T](message, senderOption, senderFuture, remoteAddress, timeout, isOneWay, actorRef, typedActorInfo, actorType) + withClientFor(remoteAddress, loader)(_.send[T](message, senderOption, senderFuture, remoteAddress, timeout, isOneWay, actorRef, typedActorInfo, actorType)) - private[akka] def clientFor( - address: InetSocketAddress, loader: Option[ClassLoader]): RemoteClient = { + private[akka] def withClientFor[T]( + address: InetSocketAddress, loader: Option[ClassLoader])(fun: RemoteClient => T): T = { loader.foreach(MessageSerializer.setClassLoader(_))//TODO: REVISIT: THIS SMELLS FUNNY - val key = makeKey(address) + val key = Address(address) lock.readLock.lock try { - remoteClients.get(key) match { + val c = remoteClients.get(key) match { case Some(client) => client case None => lock.readLock.unlock @@ -87,33 +84,29 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem } finally { lock.readLock.lock } //downgrade } finally { lock.writeLock.unlock } } + fun(c) } finally { lock.readLock.unlock } } - private def makeKey(a: InetSocketAddress): Address = a match { - case null => null - case address => Address(address.getHostName,address.getPort) - } - def shutdownClientConnection(address: InetSocketAddress): Boolean = lock withWriteGuard { - remoteClients.remove(makeKey(address)) match { + remoteClients.remove(Address(address)) match { case Some(client) => client.shutdown case None => false } } def restartClientConnection(address: InetSocketAddress): Boolean = lock withReadGuard { - remoteClients.get(makeKey(address)) match { + remoteClients.get(Address(address)) match { case Some(client) => client.connect(reconnectIfAlreadyConnected = true) case None => false } } private[akka] def registerSupervisorForActor(actorRef: ActorRef): ActorRef = - clientFor(actorRef.homeAddress.get, None).registerSupervisorForActor(actorRef) + withClientFor(actorRef.homeAddress.get, None)(_.registerSupervisorForActor(actorRef)) private[akka] def deregisterSupervisorForActor(actorRef: ActorRef): ActorRef = lock withReadGuard { - remoteClients.get(makeKey(actorRef.homeAddress.get)) match { + remoteClients.get(Address(actorRef.homeAddress.get)) match { case Some(client) => client.deregisterSupervisorForActor(actorRef) case None => actorRef } @@ -143,17 +136,11 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem } } -object RemoteClient { - val SECURE_COOKIE: Option[String] = config.getString("akka.remote.secure-cookie", "") match { - case "" => None - case cookie => Some(cookie) - } - val RECONNECTION_TIME_WINDOW = Duration(config.getInt("akka.remote.client.reconnection-time-window", 600), TIME_UNIT).toMillis - val READ_TIMEOUT = Duration(config.getInt("akka.remote.client.read-timeout", 1), TIME_UNIT) - val RECONNECT_DELAY = Duration(config.getInt("akka.remote.client.reconnect-delay", 5), TIME_UNIT) - val MESSAGE_FRAME_SIZE = config.getInt("akka.remote.client.message-frame-size", 1048576) -} - +/** + * This is the abstract baseclass for netty remote clients, + * currently there's only an ActiveRemoteClient, but otehrs could be feasible, like a PassiveRemoteClient that + * reuses an already established connection. + */ abstract class RemoteClient private[akka] ( val module: NettyRemoteClientModule, val remoteAddress: InetSocketAddress) extends Logging { @@ -165,14 +152,27 @@ abstract class RemoteClient private[akka] ( private[remote] val runSwitch = new Switch() private[remote] val isAuthenticated = new AtomicBoolean(false) + /** + * Is this client currently running? + */ private[remote] def isRunning = runSwitch.isOn protected def notifyListeners(msg: => Any); Unit protected def currentChannel: Channel + /** + * Pretty self explanatory? + */ def connect(reconnectIfAlreadyConnected: Boolean = false): Boolean + + /** + * Shuts this client down and releases any resources attached + */ def shutdown: Boolean + /** + * Converts the message to the wireprotocol and sends the message across the wire + */ def send[T]( message: Any, senderOption: Option[ActorRef], @@ -194,10 +194,13 @@ abstract class RemoteClient private[akka] ( senderOption, typedActorInfo, actorType, - if (isAuthenticated.compareAndSet(false, true)) RemoteClient.SECURE_COOKIE else None + if (isAuthenticated.compareAndSet(false, true)) RemoteClientSettings.SECURE_COOKIE else None ).build, senderFuture) } + /** + * Sends the message across the wire + */ def send[T]( request: RemoteMessageProtocol, senderFuture: Option[CompletableFuture[T]]): Option[CompletableFuture[T]] = { @@ -251,7 +254,7 @@ abstract class RemoteClient private[akka] ( } /** - * RemoteClient represents a connection to a RemoteServer. Is used to send messages to remote actors on the RemoteServer. + * RemoteClient represents a connection to an Akka node. Is used to send messages to remote actors on the node. * * @author Jonas Bonér */ @@ -260,7 +263,7 @@ class ActiveRemoteClient private[akka] ( remoteAddress: InetSocketAddress, val loader: Option[ClassLoader] = None, notifyListenersFun: (=> Any) => Unit) extends RemoteClient(module, remoteAddress) { - import RemoteClient._ + import RemoteClientSettings._ //FIXME rewrite to a wrapper object (minimize volatile access and maximize encapsulation) @volatile private var bootstrap: ClientBootstrap = _ @volatile private[remote] var connection: ChannelFuture = _ @@ -366,14 +369,14 @@ class ActiveRemoteClientPipelineFactory( e } - val ssl = if (RemoteServer.SECURE) join(new SslHandler(engine)) else join() - val timeout = new ReadTimeoutHandler(timer, RemoteClient.READ_TIMEOUT.toMillis.toInt) - val lenDec = new LengthFieldBasedFrameDecoder(RemoteClient.MESSAGE_FRAME_SIZE, 0, 4, 0, 4) + val ssl = if (RemoteServerSettings.SECURE) join(new SslHandler(engine)) else join() + val timeout = new ReadTimeoutHandler(timer, RemoteClientSettings.READ_TIMEOUT.toMillis.toInt) + val lenDec = new LengthFieldBasedFrameDecoder(RemoteClientSettings.MESSAGE_FRAME_SIZE, 0, 4, 0, 4) val lenPrep = new LengthFieldPrepender(4) val protobufDec = new ProtobufDecoder(RemoteMessageProtocol.getDefaultInstance) val protobufEnc = new ProtobufEncoder - val (enc, dec) = RemoteServer.COMPRESSION_SCHEME match { - case "zlib" => (join(new ZlibEncoder(RemoteServer.ZLIB_COMPRESSION_LEVEL)), join(new ZlibDecoder)) + val (enc, dec) = RemoteServerSettings.COMPRESSION_SCHEME match { + case "zlib" => (join(new ZlibEncoder(RemoteServerSettings.ZLIB_COMPRESSION_LEVEL)), join(new ZlibDecoder)) case _ => (join(), join()) } @@ -452,7 +455,7 @@ class ActiveRemoteClientHandler( client.openChannels.remove(event.getChannel) client.connect(reconnectIfAlreadyConnected = true) } - }, RemoteClient.RECONNECT_DELAY.toMillis, TimeUnit.MILLISECONDS) + }, RemoteClientSettings.RECONNECT_DELAY.toMillis, TimeUnit.MILLISECONDS) } else spawn { client.shutdown } } @@ -463,7 +466,7 @@ class ActiveRemoteClientHandler( client.resetReconnectionTimeWindow } - if (RemoteServer.SECURE) { + if (RemoteServerSettings.SECURE) { val sslHandler: SslHandler = ctx.getPipeline.get(classOf[SslHandler]) sslHandler.handshake.addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture): Unit = { @@ -507,58 +510,6 @@ class ActiveRemoteClientHandler( } } -/** - * For internal use only. Holds configuration variables, remote actors, remote typed actors and remote servers. - * - * @author Jonas Bonér - */ -object RemoteServer { - val isRemotingEnabled = config.getList("akka.enabled-modules").exists(_ == "remote") - val MESSAGE_FRAME_SIZE = config.getInt("akka.remote.server.message-frame-size", 1048576) - val SECURE_COOKIE = config.getString("akka.remote.secure-cookie") - val REQUIRE_COOKIE = { - val requireCookie = config.getBool("akka.remote.server.require-cookie", true) - if (isRemotingEnabled && requireCookie && RemoteServer.SECURE_COOKIE.isEmpty) throw new ConfigurationException( - "Configuration option 'akka.remote.server.require-cookie' is turned on but no secure cookie is defined in 'akka.remote.secure-cookie'.") - requireCookie - } - - val UNTRUSTED_MODE = config.getBool("akka.remote.server.untrusted-mode", false) - val HOSTNAME = config.getString("akka.remote.server.hostname", "localhost") - val PORT = config.getInt("akka.remote.server.port", 2552) - val CONNECTION_TIMEOUT_MILLIS = Duration(config.getInt("akka.remote.server.connection-timeout", 1), TIME_UNIT) - val COMPRESSION_SCHEME = config.getString("akka.remote.compression-scheme", "zlib") - val ZLIB_COMPRESSION_LEVEL = { - val level = config.getInt("akka.remote.zlib-compression-level", 6) - if (level < 1 && level > 9) throw new IllegalArgumentException( - "zlib compression level has to be within 1-9, with 1 being fastest and 9 being the most compressed") - level - } - - val SECURE = { - /*if (config.getBool("akka.remote.ssl.service",false)) { - val properties = List( - ("key-store-type" , "keyStoreType"), - ("key-store" , "keyStore"), - ("key-store-pass" , "keyStorePassword"), - ("trust-store-type", "trustStoreType"), - ("trust-store" , "trustStore"), - ("trust-store-pass", "trustStorePassword") - ).map(x => ("akka.remote.ssl." + x._1, "javax.net.ssl." + x._2)) - - // If property is not set, and we have a value from our akka.conf, use that value - for { - p <- properties if System.getProperty(p._2) eq null - c <- config.getString(p._1) - } System.setProperty(p._2, c) - - if (config.getBool("akka.remote.ssl.debug", false)) System.setProperty("javax.net.debug","ssl") - true - } else */false - } -} - - /** * Provides the implementation of the Netty remote support */ @@ -612,7 +563,7 @@ class NettyRemoteServer(serverModule: NettyRemoteServerModule, val host: String, bootstrap.setOption("child.tcpNoDelay", true) bootstrap.setOption("child.keepAlive", true) bootstrap.setOption("child.reuseAddress", true) - bootstrap.setOption("child.connectTimeoutMillis", RemoteServer.CONNECTION_TIMEOUT_MILLIS.toMillis) + bootstrap.setOption("child.connectTimeoutMillis", RemoteServerSettings.CONNECTION_TIMEOUT_MILLIS.toMillis) openChannels.add(bootstrap.bind(address)) serverModule.notifyListeners(RemoteServerStarted(serverModule)) @@ -631,7 +582,7 @@ class NettyRemoteServer(serverModule: NettyRemoteServerModule, val host: String, } trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => - import RemoteServer._ + import RemoteServerSettings._ private[akka] val currentServer = new AtomicReference[Option[NettyRemoteServer]](None) def address = currentServer.get match { @@ -828,7 +779,7 @@ class RemoteServerPipelineFactory( val openChannels: ChannelGroup, val loader: Option[ClassLoader], val server: NettyRemoteServerModule) extends ChannelPipelineFactory { - import RemoteServer._ + import RemoteServerSettings._ def getPipeline: ChannelPipeline = { def join(ch: ChannelHandler*) = Array[ChannelHandler](ch:_*) @@ -840,13 +791,13 @@ class RemoteServerPipelineFactory( e } - val ssl = if(RemoteServer.SECURE) join(new SslHandler(engine)) else join() - val lenDec = new LengthFieldBasedFrameDecoder(RemoteServer.MESSAGE_FRAME_SIZE, 0, 4, 0, 4) + val ssl = if(SECURE) join(new SslHandler(engine)) else join() + val lenDec = new LengthFieldBasedFrameDecoder(MESSAGE_FRAME_SIZE, 0, 4, 0, 4) val lenPrep = new LengthFieldPrepender(4) val protobufDec = new ProtobufDecoder(RemoteMessageProtocol.getDefaultInstance) val protobufEnc = new ProtobufEncoder - val (enc, dec) = RemoteServer.COMPRESSION_SCHEME match { - case "zlib" => (join(new ZlibEncoder(RemoteServer.ZLIB_COMPRESSION_LEVEL)), join(new ZlibDecoder)) + val (enc, dec) = COMPRESSION_SCHEME match { + case "zlib" => (join(new ZlibEncoder(ZLIB_COMPRESSION_LEVEL)), join(new ZlibDecoder)) case _ => (join(), join()) } @@ -865,7 +816,7 @@ class RemoteServerHandler( val openChannels: ChannelGroup, val applicationLoader: Option[ClassLoader], val server: NettyRemoteServerModule) extends SimpleChannelUpstreamHandler with Logging { - import RemoteServer._ + import RemoteServerSettings._ val AW_PROXY_PREFIX = "$$ProxiedByAW".intern val CHANNEL_INIT = "channel-init".intern @@ -876,7 +827,7 @@ class RemoteServerHandler( applicationLoader.foreach(MessageSerializer.setClassLoader(_)) //TODO: REVISIT: THIS FEELS A BIT DODGY /** - * ChannelOpen overridden to store open channels for a clean postStop of a RemoteServer. + * ChannelOpen overridden to store open channels for a clean postStop of a node. * If a channel is closed before, it is automatically removed from the open channels group. */ override def channelOpen(ctx: ChannelHandlerContext, event: ChannelStateEvent) = openChannels.add(ctx.getChannel) @@ -886,7 +837,7 @@ class RemoteServerHandler( sessionActors.set(event.getChannel(), new ConcurrentHashMap[String, ActorRef]()) typedSessionActors.set(event.getChannel(), new ConcurrentHashMap[String, AnyRef]()) log.slf4j.debug("Remote client [{}] connected to [{}]", clientAddress, server.name) - if (RemoteServer.SECURE) { + if (SECURE) { val sslHandler: SslHandler = ctx.getPipeline.get(classOf[SslHandler]) // Begin handshake. sslHandler.handshake().addListener(new ChannelFutureListener { @@ -900,7 +851,7 @@ class RemoteServerHandler( } else { server.notifyListeners(RemoteServerClientConnected(server, clientAddress)) } - if (RemoteServer.REQUIRE_COOKIE) ctx.setAttachment(CHANNEL_INIT) // signal that this is channel initialization, which will need authentication + if (REQUIRE_COOKIE) ctx.setAttachment(CHANNEL_INIT) // signal that this is channel initialization, which will need authentication } override def channelDisconnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { @@ -942,7 +893,7 @@ class RemoteServerHandler( override def messageReceived(ctx: ChannelHandlerContext, event: MessageEvent) = event.getMessage match { case null => throw new IllegalActorStateException("Message in remote MessageEvent is null: " + event) case requestProtocol: RemoteMessageProtocol => - if (RemoteServer.REQUIRE_COOKIE) authenticateRemoteClient(requestProtocol, ctx) + if (REQUIRE_COOKIE) authenticateRemoteClient(requestProtocol, ctx) handleRemoteMessageProtocol(requestProtocol, event.getChannel) case _ => //ignore } @@ -990,9 +941,9 @@ class RemoteServerHandler( message match { // first match on system messages case RemoteActorSystemMessage.Stop => - if (RemoteServer.UNTRUSTED_MODE) throw new SecurityException("Remote server is operating is untrusted mode, can not stop the actor") + if (UNTRUSTED_MODE) throw new SecurityException("Remote server is operating is untrusted mode, can not stop the actor") else actorRef.stop - case _: LifeCycleMessage if (RemoteServer.UNTRUSTED_MODE) => + case _: LifeCycleMessage if (UNTRUSTED_MODE) => throw new SecurityException("Remote server is operating is untrusted mode, can not pass on a LifeCycleMessage to the remote actor") case _ => // then match on user defined messages @@ -1143,7 +1094,7 @@ class RemoteServerHandler( val name = actorInfo.getTarget try { - if (RemoteServer.UNTRUSTED_MODE) throw new SecurityException( + if (UNTRUSTED_MODE) throw new SecurityException( "Remote server is operating is untrusted mode, can not create remote actors on behalf of the remote client") log.slf4j.info("Creating a new client-managed remote actor [{}:{}]", name, uuid) @@ -1216,7 +1167,7 @@ class RemoteServerHandler( val uuid = actorInfo.getUuid try { - if (RemoteServer.UNTRUSTED_MODE) throw new SecurityException( + if (UNTRUSTED_MODE) throw new SecurityException( "Remote server is operating is untrusted mode, can not create remote actors on behalf of the remote client") log.slf4j.info("Creating a new remote typed actor:\n\t[{} :: {}]", interfaceClassname, targetClassname) @@ -1285,7 +1236,7 @@ class RemoteServerHandler( val clientAddress = ctx.getChannel.getRemoteAddress.toString if (!request.hasCookie) throw new SecurityException( "The remote client [" + clientAddress + "] does not have a secure cookie.") - if (!(request.getCookie == RemoteServer.SECURE_COOKIE.get)) throw new SecurityException( + if (!(request.getCookie == SECURE_COOKIE.get)) throw new SecurityException( "The remote client [" + clientAddress + "] secure cookie is not the same as remote server secure cookie") log.slf4j.info("Remote client [{}] successfully authenticated using secure cookie", clientAddress) } diff --git a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala index f3b110958a..5f351ccbb1 100644 --- a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -5,7 +5,6 @@ package akka.serialization import akka.dispatch.MessageInvocation -import akka.remote.{RemoteServer, RemoteClient, MessageSerializer} import akka.remote.protocol.RemoteProtocol.{ActorType => ActorTypeProtocol, _} import ActorTypeProtocol._ @@ -18,6 +17,7 @@ import scala.collection.immutable.Stack import com.google.protobuf.ByteString import akka.util.ReflectiveAccess import java.net.InetSocketAddress +import akka.remote. {RemoteClientSettings, MessageSerializer} /** * Type class definition for Actor Serialization @@ -140,7 +140,7 @@ object ActorSerialization { actorRef.getSender, None, ActorType.ScalaActor, - RemoteClient.SECURE_COOKIE).build) + RemoteClientSettings.SECURE_COOKIE).build) requestProtocols.foreach(rp => builder.addMessages(rp)) } diff --git a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala index 00e09a4865..63cc942381 100644 --- a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala +++ b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala @@ -5,7 +5,7 @@ import org.scalatest.matchers.MustMatchers import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.junit.JUnitRunner import org.junit.runner.RunWith -import akka.remote.NettyRemoteSupport +import akka.remote.netty.NettyRemoteSupport import akka.actor. {Actor, ActorRegistry} import java.util.concurrent. {TimeUnit, CountDownLatch} diff --git a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala index 3ffff3c3cd..36fd4ae586 100644 --- a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala @@ -8,7 +8,6 @@ import org.scalatest.junit.JUnitRunner import org.junit.runner.RunWith import akka.dispatch.Dispatchers -import akka.remote. {NettyRemoteSupport, RemoteServer, RemoteClient} import akka.actor.Actor._ import akka.actor._ diff --git a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala index 02b65d4a30..f97ea75841 100644 --- a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala +++ b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala @@ -7,7 +7,6 @@ package akka.actor.remote import java.util.concurrent.{LinkedBlockingQueue, TimeUnit, BlockingQueue} import akka.serialization.BinaryString import akka.config.Supervision._ -import akka.remote.{RemoteServer, RemoteClient} import akka.OneWay import org.scalatest._ import org.scalatest.WordSpec diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala index b119a1e1fc..264831b208 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala @@ -4,7 +4,6 @@ import java.util.concurrent.{CountDownLatch, TimeUnit} import akka.actor.Actor._ import akka.actor.{ActorRegistry, ActorRef, Actor} -import akka.remote. {NettyRemoteSupport} object ServerInitiatedRemoteActorSpec { case class Send(actor: ActorRef) diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala index 8a97ec3516..99c6f77ea3 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteSessionActorSpec.scala @@ -6,8 +6,8 @@ package akka.actor.remote import akka.actor._ import akka.actor.Actor._ -import akka.remote.NettyRemoteSupport import java.util.concurrent. {ConcurrentSkipListSet, TimeUnit} +import akka.remote.netty.NettyRemoteSupport object ServerInitiatedRemoteSessionActorSpec { diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala index e26a873b0c..6c7543dbe3 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala @@ -6,7 +6,6 @@ package akka.actor.remote import java.util.concurrent.TimeUnit -import akka.remote.{RemoteServer, RemoteClient} import akka.actor._ import RemoteTypedActorLog._ diff --git a/akka-remote/src/test/scala/serialization/ProtobufActorMessageSerializationSpec.scala b/akka-remote/src/test/scala/serialization/ProtobufActorMessageSerializationSpec.scala index d0dcdac048..2ae30c3b64 100644 --- a/akka-remote/src/test/scala/serialization/ProtobufActorMessageSerializationSpec.scala +++ b/akka-remote/src/test/scala/serialization/ProtobufActorMessageSerializationSpec.scala @@ -1,13 +1,8 @@ package akka.actor.serialization -import java.util.concurrent.TimeUnit -import org.scalatest._ -import org.scalatest.WordSpec -import org.scalatest.matchers.MustMatchers import akka.actor.{ProtobufProtocol, Actor} import ProtobufProtocol.ProtobufPOJO import Actor._ -import akka.remote.NettyRemoteSupport import akka.actor.remote.AkkaRemoteTest /* --------------------------- From 7a0e8a82defdeb18d094a6c7a6782976a3716af8 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 3 Jan 2011 13:49:39 +0100 Subject: [PATCH 35/38] Major code clanup, switched from nested ifs to match statements etc --- .../remote/netty/NettyRemoteSupport.scala | 154 ++++++++---------- 1 file changed, 66 insertions(+), 88 deletions(-) diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index 00eb9910fa..b7a4089fca 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -17,8 +17,7 @@ import akka.actor. {Index, ActorInitializationException, LocalActorRef, newUuid, import akka.AkkaException import akka.actor.Actor._ import akka.util._ -import akka.remote.MessageSerializer -import akka.remote.{RemoteClientSettings, RemoteServerSettings} +import akka.remote.{MessageSerializer, RemoteClientSettings, RemoteServerSettings} import org.jboss.netty.channel._ import org.jboss.netty.channel.group.{DefaultChannelGroup,ChannelGroup} @@ -152,22 +151,12 @@ abstract class RemoteClient private[akka] ( private[remote] val runSwitch = new Switch() private[remote] val isAuthenticated = new AtomicBoolean(false) - /** - * Is this client currently running? - */ private[remote] def isRunning = runSwitch.isOn protected def notifyListeners(msg: => Any); Unit protected def currentChannel: Channel - /** - * Pretty self explanatory? - */ def connect(reconnectIfAlreadyConnected: Boolean = false): Boolean - - /** - * Shuts this client down and releases any resources attached - */ def shutdown: Boolean /** @@ -259,10 +248,8 @@ abstract class RemoteClient private[akka] ( * @author Jonas Bonér */ class ActiveRemoteClient private[akka] ( - module: NettyRemoteClientModule, - remoteAddress: InetSocketAddress, - val loader: Option[ClassLoader] = None, - notifyListenersFun: (=> Any) => Unit) extends RemoteClient(module, remoteAddress) { + module: NettyRemoteClientModule, remoteAddress: InetSocketAddress, + val loader: Option[ClassLoader] = None, notifyListenersFun: (=> Any) => Unit) extends RemoteClient(module, remoteAddress) { import RemoteClientSettings._ //FIXME rewrite to a wrapper object (minimize volatile access and maximize encapsulation) @volatile private var bootstrap: ClientBootstrap = _ @@ -860,17 +847,19 @@ class RemoteServerHandler( // stop all session actors val channelActors = sessionActors.remove(event.getChannel) if (channelActors ne null) { - val channelActorsIterator = channelActors.elements - while (channelActorsIterator.hasMoreElements) { - channelActorsIterator.nextElement.stop + val elems = channelActors.elements + while (elems.hasMoreElements) { + val actor = elems.nextElement + try { actor.stop } catch { case e: Exception => log.slf4j.warn("Couldn't stop {}",actor,e)} } } val channelTypedActors = typedSessionActors.remove(event.getChannel) if (channelTypedActors ne null) { - val channelTypedActorsIterator = channelTypedActors.elements - while (channelTypedActorsIterator.hasMoreElements) { - TypedActor.stop(channelTypedActorsIterator.nextElement) + val elems = channelTypedActors.elements + while (elems.hasMoreElements) { + val actor = elems.nextElement + try { TypedActor.stop(actor) } catch { case e: Exception => log.slf4j.warn("Couldn't stop {}",actor,e)} } } @@ -904,11 +893,11 @@ class RemoteServerHandler( server.notifyListeners(RemoteServerError(event.getCause, server)) } - private def getClientAddress(ctx: ChannelHandlerContext): Option[InetSocketAddress] = { - val remoteAddress = ctx.getChannel.getRemoteAddress - if (remoteAddress.isInstanceOf[InetSocketAddress]) Some(remoteAddress.asInstanceOf[InetSocketAddress]) - else None - } + private def getClientAddress(ctx: ChannelHandlerContext): Option[InetSocketAddress] = + ctx.getChannel.getRemoteAddress match { + case inet: InetSocketAddress => Some(inet) + case _ => None + } private def handleRemoteMessageProtocol(request: RemoteMessageProtocol, channel: Channel) = { log.slf4j.debug("Received RemoteMessageProtocol[\n{}]",request) @@ -1051,17 +1040,17 @@ class RemoteServerHandler( } } - private def findSessionActor(id: String, channel: Channel) : ActorRef = { - val map = sessionActors.get(channel) - if (map ne null) map.get(id) - else null - } + private def findSessionActor(id: String, channel: Channel) : ActorRef = + sessionActors.get(channel) match { + case null => null + case map => map get id + } - private def findTypedSessionActor(id: String, channel: Channel) : AnyRef = { - val map = typedSessionActors.get(channel) - if (map ne null) map.get(id) - else null - } + private def findTypedSessionActor(id: String, channel: Channel) : AnyRef = + typedSessionActors.get(channel) match { + case null => null + case map => map get id + } /** * gets the actor from the session, or creates one if there is a factory for it @@ -1069,20 +1058,18 @@ class RemoteServerHandler( private def createSessionActor(actorInfo: ActorInfoProtocol, channel: Channel): ActorRef = { val uuid = actorInfo.getUuid val id = actorInfo.getId - val sessionActorRefOrNull = findSessionActor(id, channel) - if (sessionActorRefOrNull ne null) { - sessionActorRefOrNull - } else { - // we dont have it in the session either, see if we have a factory for it - val actorFactoryOrNull = server.findActorFactory(id) - if (actorFactoryOrNull ne null) { - val actorRef = actorFactoryOrNull() - actorRef.uuid = uuidFrom(uuid.getHigh,uuid.getLow) - sessionActors.get(channel).put(id, actorRef) - actorRef - } - else - null + + findSessionActor(id, channel) match { + case null => // we dont have it in the session either, see if we have a factory for it + server.findActorFactory(id) match { + case null => null + case factory => + val actorRef = factory() + actorRef.uuid = parseUuid(uuid) //FIXME is this sensible? + sessionActors.get(channel).put(id, actorRef) + actorRef + } + case sessionActor => sessionActor } } @@ -1101,7 +1088,7 @@ class RemoteServerHandler( val clazz = if (applicationLoader.isDefined) applicationLoader.get.loadClass(name) else Class.forName(name) val actorRef = Actor.actorOf(clazz.asInstanceOf[Class[_ <: Actor]]) - actorRef.uuid = uuidFrom(uuid.getHigh,uuid.getLow) + actorRef.uuid = parseUuid(uuid) actorRef.id = id actorRef.timeout = timeout server.actorsByUuid.put(actorRef.uuid.toString, actorRef) // register by uuid @@ -1126,16 +1113,13 @@ class RemoteServerHandler( val uuid = actorInfo.getUuid val id = actorInfo.getId - val actorRefOrNull = server.findActorByIdOrUuid(id, uuidFrom(uuid.getHigh,uuid.getLow).toString) - - if (actorRefOrNull ne null) - actorRefOrNull - else { // the actor has not been registered globally. See if we have it in the session - val sessionActorRefOrNull = createSessionActor(actorInfo, channel) - if (sessionActorRefOrNull ne null) - sessionActorRefOrNull - else // maybe it is a client managed actor - createClientManagedActor(actorInfo) + server.findActorByIdOrUuid(id, parseUuid(uuid).toString) match { + case null => // the actor has not been registered globally. See if we have it in the session + createSessionActor(actorInfo, channel) match { + case null => createClientManagedActor(actorInfo) // maybe it is a client managed actor + case sessionActor => sessionActor + } + case actorRef => actorRef } } @@ -1144,20 +1128,17 @@ class RemoteServerHandler( */ private def createTypedSessionActor(actorInfo: ActorInfoProtocol, channel: Channel):AnyRef ={ val id = actorInfo.getId - val sessionActorRefOrNull = findTypedSessionActor(id, channel) - if (sessionActorRefOrNull ne null) - sessionActorRefOrNull - else { - val actorFactoryOrNull = server.findTypedActorFactory(id) - if (actorFactoryOrNull ne null) { - val newInstance = actorFactoryOrNull() - typedSessionActors.get(channel).put(id, newInstance) - newInstance - } - else - null + findTypedSessionActor(id, channel) match { + case null => + server.findTypedActorFactory(id) match { + case null => null + case factory => + val newInstance = factory() + typedSessionActors.get(channel).put(id, newInstance) + newInstance + } + case sessionActor => sessionActor } - } private def createClientManagedTypedActor(actorInfo: ActorInfoProtocol) = { @@ -1179,7 +1160,7 @@ class RemoteServerHandler( val newInstance = TypedActor.newInstance( interfaceClass, targetClass.asInstanceOf[Class[_ <: TypedActor]], actorInfo.getTimeout).asInstanceOf[AnyRef] - server.typedActors.put(uuidFrom(uuid.getHigh,uuid.getLow).toString, newInstance) // register by uuid + server.typedActors.put(parseUuid(uuid).toString, newInstance) // register by uuid newInstance } catch { case e => @@ -1191,19 +1172,14 @@ class RemoteServerHandler( private def createTypedActor(actorInfo: ActorInfoProtocol, channel: Channel): AnyRef = { val uuid = actorInfo.getUuid - val id = actorInfo.getId - val typedActorOrNull = server.findTypedActorByIdOrUuid(id, uuidFrom(uuid.getHigh,uuid.getLow).toString) - if (typedActorOrNull ne null) - typedActorOrNull - else - { - // the actor has not been registered globally. See if we have it in the session - val sessionActorRefOrNull = createTypedSessionActor(actorInfo, channel) - if (sessionActorRefOrNull ne null) - sessionActorRefOrNull - else // maybe it is a client managed actor - createClientManagedTypedActor(actorInfo) + server.findTypedActorByIdOrUuid(actorInfo.getId, parseUuid(uuid).toString) match { + case null => // the actor has not been registered globally. See if we have it in the session + createTypedSessionActor(actorInfo, channel) match { + case null => createClientManagedTypedActor(actorInfo) //Maybe client managed actor? + case sessionActor => sessionActor + } + case typedActor => typedActor } } @@ -1241,4 +1217,6 @@ class RemoteServerHandler( log.slf4j.info("Remote client [{}] successfully authenticated using secure cookie", clientAddress) } } + + protected def parseUuid(protocol: UuidProtocol): Uuid = uuidFrom(protocol.getHigh,protocol.getLow) } From 00840c8f5aa450e34a1a0c2576732c0290bc8ea2 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 3 Jan 2011 14:44:15 +0100 Subject: [PATCH 36/38] Adding support for non-delivery notifications on server-side as well + more code cleanup --- .../remoteinterface/RemoteInterface.scala | 4 + .../remote/netty/NettyRemoteSupport.scala | 78 +++++++++---------- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index 891c930ef5..c09c5c873b 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -99,6 +99,10 @@ case class RemoteServerClientDisconnected( case class RemoteServerClientClosed( @BeanProperty val server: RemoteServerModule, @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent +case class RemoteServerWriteFailed( + @BeanProperty request: AnyRef, + @BeanProperty cause: Throwable, + @BeanProperty client: RemoteServerModule, remoteAddress: InetSocketAddress) extends RemoteServerLifeCycleEvent /** * Thrown for example when trying to send a message using a RemoteClient that is either not started or shut down. diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index b7a4089fca..98bf270ab5 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -511,7 +511,6 @@ class NettyRemoteSupport extends RemoteSupport with NettyRemoteServerModule with val home = this.address if (host == home.getHostName && port == home.getPort) {//TODO: switch to InetSocketAddress.equals? val localRef = findActorByIdOrUuid(serviceId,serviceId) - if (localRef ne null) return localRef //Code significantly simpler with the return statement } } @@ -804,14 +803,29 @@ class RemoteServerHandler( val applicationLoader: Option[ClassLoader], val server: NettyRemoteServerModule) extends SimpleChannelUpstreamHandler with Logging { import RemoteServerSettings._ - - val AW_PROXY_PREFIX = "$$ProxiedByAW".intern val CHANNEL_INIT = "channel-init".intern + applicationLoader.foreach(MessageSerializer.setClassLoader(_)) //TODO: REVISIT: THIS FEELS A BIT DODGY + val sessionActors = new ChannelLocal[ConcurrentHashMap[String, ActorRef]]() val typedSessionActors = new ChannelLocal[ConcurrentHashMap[String, AnyRef]]() - applicationLoader.foreach(MessageSerializer.setClassLoader(_)) //TODO: REVISIT: THIS FEELS A BIT DODGY + //Writes the specified message to the specified channel and propagates write errors to listeners + private def write(channel: Channel, message: AnyRef): Unit = + channel.write(message).addListener( + new ChannelFutureListener { + def operationComplete(future: ChannelFuture): Unit = { + if (future.isCancelled) { + //Not interesting at the moment + } else if (!future.isSuccess) { + val socketAddress = future.getChannel.getRemoteAddress match { + case i: InetSocketAddress => i + case _ => null + } + server.notifyListeners(RemoteServerWriteFailed(message, future.getCause, server, socketAddress)) + } + } + }) /** * ChannelOpen overridden to store open channels for a clean postStop of a node. @@ -842,25 +856,19 @@ class RemoteServerHandler( } override def channelDisconnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { + import scala.collection.JavaConversions.asScalaIterable val clientAddress = getClientAddress(ctx) log.slf4j.debug("Remote client [{}] disconnected from [{}]", clientAddress, server.name) - // stop all session actors - val channelActors = sessionActors.remove(event.getChannel) - if (channelActors ne null) { - val elems = channelActors.elements - while (elems.hasMoreElements) { - val actor = elems.nextElement - try { actor.stop } catch { case e: Exception => log.slf4j.warn("Couldn't stop {}",actor,e)} - } - } - val channelTypedActors = typedSessionActors.remove(event.getChannel) - if (channelTypedActors ne null) { - val elems = channelTypedActors.elements - while (elems.hasMoreElements) { - val actor = elems.nextElement - try { TypedActor.stop(actor) } catch { case e: Exception => log.slf4j.warn("Couldn't stop {}",actor,e)} - } + // stop all session actors + for (map <- Option(sessionActors.remove(event.getChannel)); + actor <- asScalaIterable(map.values)) { + try { actor.stop } catch { case e: Exception => log.slf4j.warn("Couldn't stop {}",actor,e) } + } + // stop all typed session actors + for (map <- Option(typedSessionActors.remove(event.getChannel)); + actor <- asScalaIterable(map.values)) { + try { TypedActor.stop(actor) } catch { case e: Exception => log.slf4j.warn("Couldn't stop {}",actor,e) } } server.notifyListeners(RemoteServerClientDisconnected(server, clientAddress)) @@ -914,11 +922,9 @@ class RemoteServerHandler( log.slf4j.debug("Dispatching to remote actor [{}:{}]", actorInfo.getTarget, actorInfo.getUuid) val actorRef = - try { - createActor(actorInfo, channel).start - } catch { + try { createActor(actorInfo, channel).start } catch { case e: SecurityException => - channel.write(createErrorReplyMessage(e, request, AkkaActorType.ScalaActor)) + write(channel, createErrorReplyMessage(e, request, AkkaActorType.ScalaActor)) server.notifyListeners(RemoteServerError(e, server)) return } @@ -949,13 +955,7 @@ class RemoteServerHandler( if (exception.isDefined) { log.slf4j.debug("Returning exception from actor invocation [{}]",exception.get.getClass) - try { - channel.write(createErrorReplyMessage(exception.get, request, AkkaActorType.ScalaActor)) - } catch { - case e: Throwable => - log.slf4j.debug("An error occurred in sending the reply",e) - server.notifyListeners(RemoteServerError(e, server)) - } + write(channel, createErrorReplyMessage(exception.get, request, AkkaActorType.ScalaActor)) } else if (result.isDefined) { log.slf4j.debug("Returning result from actor invocation [{}]",result.get) @@ -975,11 +975,7 @@ class RemoteServerHandler( // FIXME lift in the supervisor uuid management into toh createRemoteMessageProtocolBuilder method if (request.hasSupervisorUuid) messageBuilder.setSupervisorUuid(request.getSupervisorUuid) - try { - channel.write(messageBuilder.build) - } catch { - case e: Throwable => server.notifyListeners(RemoteServerError(e, server)) - } + write(channel, messageBuilder.build) } } ) @@ -1015,7 +1011,8 @@ class RemoteServerHandler( AkkaActorType.TypedActor, None) if (request.hasSupervisorUuid) messageBuilder.setSupervisorUuid(request.getSupervisorUuid) - channel.write(messageBuilder.build) + + write(channel, messageBuilder.build) log.slf4j.debug("Returning result from remote typed actor invocation [{}]", result) } catch { case e: Throwable => server.notifyListeners(RemoteServerError(e, server)) @@ -1024,7 +1021,8 @@ class RemoteServerHandler( messageReceiver.invoke(typedActor, args: _*) match { case f: Future[_] => //If it's a future, we can lift on that to defer the send to when the future is completed f.onComplete( future => { - val result: Either[Any,Throwable] = if (future.exception.isDefined) Right(future.exception.get) else Left(future.result.get) + val result: Either[Any,Throwable] = + if (future.exception.isDefined) Right(future.exception.get) else Left(future.result.get) sendResponse(result) }) case other => sendResponse(Left(other)) @@ -1032,10 +1030,10 @@ class RemoteServerHandler( } } catch { case e: InvocationTargetException => - channel.write(createErrorReplyMessage(e.getCause, request, AkkaActorType.TypedActor)) + write(channel, createErrorReplyMessage(e.getCause, request, AkkaActorType.TypedActor)) server.notifyListeners(RemoteServerError(e, server)) case e: Throwable => - channel.write(createErrorReplyMessage(e, request, AkkaActorType.TypedActor)) + write(channel, createErrorReplyMessage(e, request, AkkaActorType.TypedActor)) server.notifyListeners(RemoteServerError(e, server)) } } From dbe6f203b372e5d7415c5124ed97542f4935d4be Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 4 Jan 2011 13:24:28 +0100 Subject: [PATCH 37/38] Removing ActorRegistry object, UntypedActor object, introducing akka.actor.Actors for the Java API --- .../src/main/java/akka/actor/Actors.java | 72 ++++++++++++ .../src/main/scala/akka/actor/Actor.scala | 64 ++++++++--- .../src/main/scala/akka/actor/ActorRef.scala | 28 ++--- .../main/scala/akka/actor/ActorRegistry.scala | 108 +----------------- .../actor/BootableActorLoaderService.scala | 2 +- .../main/scala/akka/actor/Supervisor.scala | 2 +- .../main/scala/akka/actor/UntypedActor.scala | 66 +---------- .../scala/akka/dispatch/MessageHandling.scala | 2 +- .../remoteinterface/RemoteInterface.scala | 4 +- .../src/test/java/akka/actor/JavaAPI.java | 34 ++++++ .../java/akka/actor/JavaAPITestActor.java | 7 ++ .../scala/akka/dataflow/DataFlowSpec.scala | 4 +- .../scala/akka/misc/ActorRegistrySpec.scala | 74 ++++++------ .../test/scala/akka/misc/SchedulerSpec.scala | 6 +- akka-http/src/main/scala/akka/http/Mist.scala | 2 +- .../main/scala/akka/security/Security.scala | 2 +- .../remote/BootableRemoteActorService.scala | 6 +- .../serialization/SerializationProtocol.scala | 4 +- .../test/scala/remote/AkkaRemoteTest.scala | 4 +- .../remote/OptimizedLocalScopedSpec.scala | 6 +- .../ServerInitiatedRemoteActorSample.scala | 6 +- .../ServerInitiatedRemoteActorSpec.scala | 6 +- .../ServerInitiatedRemoteTypedActorSpec.scala | 4 +- .../remote/UnOptimizedLocalScopedSpec.scala | 4 +- .../UntypedActorSerializationSpec.scala | 2 +- .../ClientManagedRemoteActorSample.scala | 2 +- .../ServerManagedRemoteActorSample.scala | 6 +- .../example/UntypedCoordinatedCounter.java | 2 +- .../example/UntypedCoordinatedExample.java | 6 +- .../example/UntypedTransactorExample.java | 6 +- .../test/UntypedCoordinatedCounter.java | 1 + .../test/UntypedCoordinatedIncrementTest.java | 5 +- .../test/UntypedTransactorTest.java | 5 +- .../main/scala/akka/actor/TypedActor.scala | 13 ++- .../config/TypedActorGuiceConfigurator.scala | 2 +- .../typed-actor/TypedActorLifecycleSpec.scala | 4 +- .../typed-actor/TypedActorRegistrySpec.scala | 36 +++--- .../actor/typed-actor/TypedActorSpec.scala | 44 +++---- 38 files changed, 320 insertions(+), 331 deletions(-) create mode 100644 akka-actor/src/main/java/akka/actor/Actors.java create mode 100644 akka-actor/src/test/java/akka/actor/JavaAPI.java create mode 100644 akka-actor/src/test/java/akka/actor/JavaAPITestActor.java diff --git a/akka-actor/src/main/java/akka/actor/Actors.java b/akka-actor/src/main/java/akka/actor/Actors.java new file mode 100644 index 0000000000..d19ee01785 --- /dev/null +++ b/akka-actor/src/main/java/akka/actor/Actors.java @@ -0,0 +1,72 @@ +package akka.actor; + +import akka.japi.Creator; +import akka.remoteinterface.RemoteSupport; + +/** + * JAVA API for + * - creating actors, + * - creating remote actors, + * - locating actors + */ +public class Actors { + /** + * + * @return The actor registry + */ + public static ActorRegistry registry() { + return Actor$.MODULE$.registry(); + } + + /** + * + * @return + * @throws UnsupportedOperationException If remoting isn't configured + * @throws ModuleNotAvailableException If the class for the remote support cannot be loaded + */ + public static RemoteSupport remote() { + return Actor$.MODULE$.remote(); + } + + /** + * NOTE: Use this convenience method with care, do NOT make it possible to get a reference to the + * UntypedActor instance directly, but only through its 'ActorRef' wrapper reference. + *

+ * Creates an ActorRef out of the Actor. Allows you to pass in the instance for the UntypedActor. + * Only use this method when you need to pass in constructor arguments into the 'UntypedActor'. + *

+ * You use it by implementing the UntypedActorFactory interface. + * Example in Java: + *

+   *   ActorRef actor = Actors.actorOf(new UntypedActorFactory() {
+   *     public UntypedActor create() {
+   *       return new MyUntypedActor("service:name", 5);
+   *     }
+   *   });
+   *   actor.start();
+   *   actor.sendOneWay(message, context);
+   *   actor.stop();
+   * 
+ */ + public static ActorRef actorOf(final Creator factory) { + return Actor$.MODULE$.actorOf(factory); + } + + /** + * Creates an ActorRef out of the Actor type represented by the class provided. + * Example in Java: + *
+   *   ActorRef actor = Actors.actorOf(MyUntypedActor.class);
+   *   actor.start();
+   *   actor.sendOneWay(message, context);
+   *   actor.stop();
+   * 
+ * You can create and start the actor in one statement like this: + *
+   *   val actor = Actors.actorOf(MyActor.class).start();
+   * 
+ */ + public static ActorRef actorOf(final Class type) { + return Actor$.MODULE$.actorOf(type); + } +} \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index 35a336a873..e6f9cf6da8 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -15,7 +15,8 @@ import java.net.InetSocketAddress import scala.reflect.BeanProperty import akka.util. {ReflectiveAccess, Logging, Duration} -import akka.japi.Procedure +import akka.remoteinterface.RemoteSupport +import akka.japi. {Creator, Procedure} /** * Life-cycle messages for the Actors @@ -80,7 +81,6 @@ case class UnhandledMessageException(msg: Any, ref: ActorRef) extends Exception * @author Jonas Bonér */ object Actor extends Logging { - /** * Add shutdown cleanups */ @@ -103,8 +103,18 @@ object Actor extends Logging { hook } - val TIMEOUT = Duration(config.getInt("akka.actor.timeout", 5), TIME_UNIT).toMillis - val SERIALIZE_MESSAGES = config.getBool("akka.actor.serialize-messages", false) + val registry = new ActorRegistry + + lazy val remote: RemoteSupport = { + ReflectiveAccess. + Remote. + defaultRemoteSupport. + map(_()). + getOrElse(throw new UnsupportedOperationException("You need to have akka-remote on classpath")) + } + + private[akka] val TIMEOUT = Duration(config.getInt("akka.actor.timeout", 5), TIME_UNIT).toMillis + private[akka] val SERIALIZE_MESSAGES = config.getBool("akka.actor.serialize-messages", false) /** * A Receive is a convenience type that defines actor message behavior currently modeled as @@ -114,6 +124,8 @@ object Actor extends Logging { private[actor] val actorRefInCreation = new scala.util.DynamicVariable[Option[ActorRef]](None) + + /** * Creates an ActorRef out of the Actor with type T. *
@@ -128,8 +140,7 @@ object Actor extends Logging {
    *   val actor = actorOf[MyActor].start
    * 
*/ - @deprecated("Use ActorRegistry.actorOf instead") - def actorOf[T <: Actor : Manifest]: ActorRef = ActorRegistry.actorOf[T] + def actorOf[T <: Actor : Manifest]: ActorRef = actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) /** * Creates an ActorRef out of the Actor of the specified Class. @@ -145,8 +156,15 @@ object Actor extends Logging { * val actor = actorOf(classOf[MyActor]).start * */ - @deprecated("Use ActorRegistry.actorOf instead") - def actorOf(clazz: Class[_ <: Actor]): ActorRef = ActorRegistry.actorOf(clazz) + def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(() => { + import ReflectiveAccess.{ createInstance, noParams, noArgs } + createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( + throw new ActorInitializationException( + "Could not instantiate Actor" + + "\nMake sure Actor is NOT defined inside a class/trait," + + "\nif so put it outside the class/trait, f.e. in a companion object," + + "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")) + }, None) /** * Creates an ActorRef out of the Actor. Allows you to pass in a factory function @@ -166,28 +184,42 @@ object Actor extends Logging { * val actor = actorOf(new MyActor).start * */ - @deprecated("Use ActorRegistry.actorOf instead") - def actorOf(factory: => Actor): ActorRef = ActorRegistry.actorOf(factory) + def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory, None) + + /** + * Creates an ActorRef out of the Actor. Allows you to pass in a factory (Creator) + * that creates the Actor. Please note that this function can be invoked multiple + * times if for example the Actor is supervised and needs to be restarted. + *

+ * This function should NOT be used for remote actors. + * JAVA API + */ + def actorOf(creator: Creator[Actor]): ActorRef = new LocalActorRef(() => creator.create, None) /** * Use to spawn out a block of code in an event-driven actor. Will shut actor down when * the block has been executed. *

- * NOTE: If used from within an Actor then has to be qualified with 'ActorRegistry.spawn' since + * NOTE: If used from within an Actor then has to be qualified with 'Actor.spawn' since * there is a method 'spawn[ActorType]' in the Actor trait already. * Example: *

-   * import ActorRegistry.{spawn}
+   * import Actor.{spawn}
    *
    * spawn  {
    *   ... // do stuff
    * }
    * 
*/ - @deprecated("Use ActorRegistry.spawn instead") - def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = - ActorRegistry.spawn(body) - + def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = { + case object Spawn + actorOf(new Actor() { + self.dispatcher = dispatcher + def receive = { + case Spawn => try { body } finally { self.stop } + } + }).start ! Spawn + } /** * Implicitly converts the given Option[Any] to a AnyOptionAsTypedOption which offers the method as[T] * to convert an Option[Any] to an Option[T]. diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 37e470c835..84769371ab 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -644,7 +644,7 @@ class LocalActorRef private[akka] ( initializeActorInstance if (isClientManaged_?) - ActorRegistry.remote.registerClientManagedActor(homeAddress.get.getHostName,homeAddress.get.getPort, uuid) + Actor.remote.registerClientManagedActor(homeAddress.get.getHostName,homeAddress.get.getPort, uuid) checkReceiveTimeout //Schedule the initial Receive timeout } @@ -661,11 +661,11 @@ class LocalActorRef private[akka] ( dispatcher.detach(this) _status = ActorRefInternals.SHUTDOWN actor.postStop - ActorRegistry.unregister(this) + Actor.registry.unregister(this) if (isRemotingEnabled) { if (isClientManaged_?) - ActorRegistry.remote.registerClientManagedActor(homeAddress.get.getHostName,homeAddress.get.getPort, uuid) - ActorRegistry.remote.unregister(this) + Actor.remote.registerClientManagedActor(homeAddress.get.getHostName,homeAddress.get.getPort, uuid) + Actor.remote.unregister(this) } setActorSelfFields(actorInstance.get,null) } //else if (isBeingRestarted) throw new ActorKilledException("Actor [" + toString + "] is being restarted.") @@ -737,7 +737,7 @@ class LocalActorRef private[akka] ( */ def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long = Actor.TIMEOUT): ActorRef = guard.withGuard { ensureRemotingEnabled - val ref = ActorRegistry.remote.actorOf(clazz, hostname, port) + val ref = Actor.remote.actorOf(clazz, hostname, port) ref.timeout = timeout ref.start } @@ -762,7 +762,7 @@ class LocalActorRef private[akka] ( def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long = Actor.TIMEOUT): ActorRef = guard.withGuard { ensureRemotingEnabled - val actor = ActorRegistry.remote.actorOf(clazz, hostname, port) + val actor = Actor.remote.actorOf(clazz, hostname, port) actor.timeout = timeout link(actor) actor.start @@ -798,7 +798,7 @@ class LocalActorRef private[akka] ( protected[akka] def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = if (isClientManaged_?) { - ActorRegistry.remote.send[Any]( + Actor.remote.send[Any]( message, senderOption, None, homeAddress.get, timeout, true, this, None, ActorType.ScalaActor, None) } else dispatcher dispatchMessage new MessageInvocation(this, message, senderOption, None) @@ -809,7 +809,7 @@ class LocalActorRef private[akka] ( senderOption: Option[ActorRef], senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { if (isClientManaged_?) { - val future = ActorRegistry.remote.send[T]( + val future = Actor.remote.send[T]( message, senderOption, senderFuture, homeAddress.get, timeout, false, this, None, ActorType.ScalaActor, None) if (future.isDefined) future.get else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) @@ -978,7 +978,7 @@ class LocalActorRef private[akka] ( ensureRemotingEnabled if (_supervisor.isDefined) { if (homeAddress.isDefined) - ActorRegistry.remote.registerSupervisorForActor(this) + Actor.remote.registerSupervisorForActor(this) Some(_supervisor.get.uuid) } else None } @@ -1085,7 +1085,7 @@ class LocalActorRef private[akka] ( private def initializeActorInstance = { actor.preStart // run actor preStart Actor.log.slf4j.trace("[{}] has started", toString) - ActorRegistry.register(this) + Actor.registry.register(this) } } @@ -1127,14 +1127,14 @@ private[akka] case class RemoteActorRef private[akka] ( start def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = - ActorRegistry.remote.send[Any](message, senderOption, None, homeAddress.get, timeout, true, this, None, actorType, loader) + Actor.remote.send[Any](message, senderOption, None, homeAddress.get, timeout, true, this, None, actorType, loader) def postMessageToMailboxAndCreateFutureResultWithTimeout[T]( message: Any, timeout: Long, senderOption: Option[ActorRef], senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { - val future = ActorRegistry.remote.send[T](message, senderOption, senderFuture, homeAddress.get, timeout, false, this, None, actorType, loader) + val future = Actor.remote.send[T](message, senderOption, senderFuture, homeAddress.get, timeout, false, this, None, actorType, loader) if (future.isDefined) future.get else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) } @@ -1142,7 +1142,7 @@ private[akka] case class RemoteActorRef private[akka] ( def start: ActorRef = synchronized { _status = ActorRefInternals.RUNNING //if (clientManaged) - // ActorRegistry.remote.registerClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) + // Actor.remote.registerClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) this } @@ -1151,7 +1151,7 @@ private[akka] case class RemoteActorRef private[akka] ( _status = ActorRefInternals.SHUTDOWN postMessageToMailbox(RemoteActorSystemMessage.Stop, None) // if (clientManaged) - // ActorRegistry.remote.unregisterClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) + // Actor.remote.unregisterClientManagedActor(homeAddress.getHostName,homeAddress.getPort, uuid) } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala index 9e595e4422..18fa30d740 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala @@ -12,10 +12,7 @@ import java.util.{Set => JSet} import annotation.tailrec import akka.util.ReflectiveAccess._ -import java.net.InetSocketAddress -import akka.util. {ReflectiveAccess, ReadWriteGuard, Address, ListenerManagement} -import akka.dispatch. {MessageDispatcher, Dispatchers} -import akka.remoteinterface. {RemoteSupport, RemoteServerModule} +import akka.util. {ReflectiveAccess, ReadWriteGuard, ListenerManagement} /** * Base trait for ActorRegistry events, allows listen to when an actor is added and removed from the ActorRegistry. @@ -39,9 +36,7 @@ case class ActorUnregistered(actor: ActorRef) extends ActorRegistryEvent * @author Jonas Bonér */ -object ActorRegistry extends ListenerManagement { - - protected def remoteBootstrap = ReflectiveAccess.Remote.defaultRemoteSupport +private[actor] final class ActorRegistry private[actor] () extends ListenerManagement { private val actorsByUUID = new ConcurrentHashMap[Uuid, ActorRef] private val actorsById = new Index[String,ActorRef] @@ -228,103 +223,6 @@ object ActorRegistry extends ListenerManagement { TypedActorModule.typedActorObjectInstance.get.proxyFor(actorRef) } - /** - * Handy access to the RemoteServer module - */ - lazy val remote: RemoteSupport = remoteBootstrap.map(_()).getOrElse(throw new UnsupportedOperationException("You need to have akka-remote on classpath")) - - /** - * Current home address of this ActorRegistry - */ - def homeAddress(): InetSocketAddress = if (isRemotingEnabled) remote.address else Remote.configDefaultAddress - - - /** - * Creates an ActorRef out of the Actor with type T. - *
-   *   import Actor._
-   *   val actor = actorOf[MyActor]
-   *   actor.start
-   *   actor ! message
-   *   actor.stop
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf[MyActor].start
-   * 
- */ - def actorOf[T <: Actor : Manifest]: ActorRef = actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) - - /** - * Creates an ActorRef out of the Actor of the specified Class. - *
-   *   import Actor._
-   *   val actor = actorOf(classOf[MyActor])
-   *   actor.start
-   *   actor ! message
-   *   actor.stop
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf(classOf[MyActor]).start
-   * 
- */ - def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(() => { - import ReflectiveAccess.{ createInstance, noParams, noArgs } - createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( - throw new ActorInitializationException( - "Could not instantiate Actor" + - "\nMake sure Actor is NOT defined inside a class/trait," + - "\nif so put it outside the class/trait, f.e. in a companion object," + - "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")) - }, None) - - /** - * Creates an ActorRef out of the Actor. Allows you to pass in a factory function - * that creates the Actor. Please note that this function can be invoked multiple - * times if for example the Actor is supervised and needs to be restarted. - *

- * This function should NOT be used for remote actors. - *

-   *   import Actor._
-   *   val actor = actorOf(new MyActor)
-   *   actor.start
-   *   actor ! message
-   *   actor.stop
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf(new MyActor).start
-   * 
- */ - def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory, None) - - /** - * Use to spawn out a block of code in an event-driven actor. Will shut actor down when - * the block has been executed. - *

- * NOTE: If used from within an Actor then has to be qualified with 'ActorRegistry.spawn' since - * there is a method 'spawn[ActorType]' in the Actor trait already. - * Example: - *

-   * import ActorRegistry.{spawn}
-   *
-   * spawn  {
-   *   ... // do stuff
-   * }
-   * 
- */ - def spawn(body: => Unit)(implicit dispatcher: MessageDispatcher = Dispatchers.defaultGlobalDispatcher): Unit = { - case object Spawn - actorOf(new Actor() { - self.dispatcher = dispatcher - def receive = { - case Spawn => try { body } finally { self.stop } - } - }).start ! Spawn - } - - /** * Registers an actor in the ActorRegistry. */ @@ -368,7 +266,7 @@ object ActorRegistry extends ListenerManagement { } } else foreach(_.stop) if (Remote.isEnabled) { - remote.clear //TODO: REVISIT: Should this be here? + Actor.remote.clear //TODO: REVISIT: Should this be here? } actorsByUUID.clear actorsById.clear diff --git a/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala b/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala index 2288a1cba9..6600f486a5 100644 --- a/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala +++ b/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala @@ -62,6 +62,6 @@ trait BootableActorLoaderService extends Bootable with Logging { abstract override def onUnload = { super.onUnload - ActorRegistry.shutdownAll + Actor.registry.shutdownAll } } diff --git a/akka-actor/src/main/scala/akka/actor/Supervisor.scala b/akka-actor/src/main/scala/akka/actor/Supervisor.scala index f9ca4d923f..efba95aa07 100644 --- a/akka-actor/src/main/scala/akka/actor/Supervisor.scala +++ b/akka-actor/src/main/scala/akka/actor/Supervisor.scala @@ -142,7 +142,7 @@ sealed class Supervisor(handler: FaultHandlingStrategy) { actorRef.lifeCycle = lifeCycle supervisor.link(actorRef) if (registerAsRemoteService) - ActorRegistry.remote.register(actorRef) + Actor.remote.register(actorRef) case supervisorConfig @ SupervisorConfig(_, _) => // recursive supervisor configuration val childSupervisor = Supervisor(supervisorConfig) supervisor.link(childSupervisor.supervisor) diff --git a/akka-actor/src/main/scala/akka/actor/UntypedActor.scala b/akka-actor/src/main/scala/akka/actor/UntypedActor.scala index 3d154c600e..ac7daa2dc6 100644 --- a/akka-actor/src/main/scala/akka/actor/UntypedActor.scala +++ b/akka-actor/src/main/scala/akka/actor/UntypedActor.scala @@ -43,14 +43,14 @@ import scala.reflect.BeanProperty * * } else if (msg.equals("ForwardMessage")) { * // Retreive an actor from the ActorRegistry by ID and get an ActorRef back - * ActorRef actorRef = ActorRegistry.actorsFor("some-actor-id").head(); + * ActorRef actorRef = Actor.registry.actorsFor("some-actor-id").head(); * * } else throw new IllegalArgumentException("Unknown message: " + message); * } else throw new IllegalArgumentException("Unknown message: " + message); * } * * public static void main(String[] args) { - * ActorRef actor = UntypedActor.actorOf(SampleUntypedActor.class); + * ActorRef actor = Actors.actorOf(SampleUntypedActor.class); * actor.start(); * actor.sendOneWay("SendToSelf"); * actor.stop(); @@ -86,66 +86,8 @@ abstract class UntypedActor extends Actor { } /** - * Factory closure for an UntypedActor, to be used with 'UntypedActor.actorOf(factory)'. + * Factory closure for an UntypedActor, to be used with 'Actors.actorOf(factory)'. * * @author Jonas Bonér */ -trait UntypedActorFactory extends Creator[Actor] - -/** - * Factory object for creating and managing 'UntypedActor's. Meant to be used from Java. - *

- * Example on how to create an actor: - *

- *   ActorRef actor = UntypedActor.actorOf(MyUntypedActor.class);
- *   actor.start();
- *   actor.sendOneWay(message, context)
- *   actor.stop();
- * 
- * You can create and start the actor in one statement like this: - *
- *   ActorRef actor = UntypedActor.actorOf(MyUntypedActor.class).start();
- * 
- * - * @author Jonas Bonér - */ -object UntypedActor { - - /** - * Creates an ActorRef out of the Actor type represented by the class provided. - * Example in Java: - *
-   *   ActorRef actor = UntypedActor.actorOf(MyUntypedActor.class);
-   *   actor.start();
-   *   actor.sendOneWay(message, context);
-   *   actor.stop();
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf(classOf[MyActor]).start
-   * 
- */ - def actorOf[T <: Actor](clazz: Class[T]): ActorRef = Actor.actorOf(clazz) - - /** - * NOTE: Use this convenience method with care, do NOT make it possible to get a reference to the - * UntypedActor instance directly, but only through its 'ActorRef' wrapper reference. - *

- * Creates an ActorRef out of the Actor. Allows you to pass in the instance for the UntypedActor. - * Only use this method when you need to pass in constructor arguments into the 'UntypedActor'. - *

- * You use it by implementing the UntypedActorFactory interface. - * Example in Java: - *

-   *   ActorRef actor = UntypedActor.actorOf(new UntypedActorFactory() {
-   *     public UntypedActor create() {
-   *       return new MyUntypedActor("service:name", 5);
-   *     }
-   *   });
-   *   actor.start();
-   *   actor.sendOneWay(message, context);
-   *   actor.stop();
-   * 
- */ - def actorOf(factory: UntypedActorFactory): ActorRef = Actor.actorOf(factory.create) -} +trait UntypedActorFactory extends Creator[Actor] \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala b/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala index bc8ed1805a..bca7650a10 100644 --- a/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala +++ b/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala @@ -132,7 +132,7 @@ trait MessageDispatcher extends MailboxFactory with Logging { val i = uuids.iterator while(i.hasNext()) { val uuid = i.next() - ActorRegistry.actorFor(uuid) match { + Actor.registry.actorFor(uuid) match { case Some(actor) => actor.stop case None => log.slf4j.error("stopAllLinkedActors couldn't find linked actor: " + uuid) diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index c09c5c873b..f27ba25dc8 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -142,7 +142,7 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule * */ def actorOf(factory: => Actor, host: String, port: Int): ActorRef = - ActorRegistry.remote.clientManagedActorOf(() => factory, host, port) + Actor.remote.clientManagedActorOf(() => factory, host, port) /** * Creates a Client-managed ActorRef out of the Actor of the specified Class. @@ -214,7 +214,7 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule } /** - * This is the interface for the RemoteServer functionality, it's used in ActorRegistry.remote + * This is the interface for the RemoteServer functionality, it's used in Actor.remote */ trait RemoteServerModule extends RemoteModule { protected val guard = new ReentrantGuard diff --git a/akka-actor/src/test/java/akka/actor/JavaAPI.java b/akka-actor/src/test/java/akka/actor/JavaAPI.java new file mode 100644 index 0000000000..61c829f540 --- /dev/null +++ b/akka-actor/src/test/java/akka/actor/JavaAPI.java @@ -0,0 +1,34 @@ +package akka.actor; + +import akka.japi.Creator; +import org.junit.Test; +import akka.actor.Actors; +import akka.remoteinterface.RemoteSupport; +import static org.junit.Assert.*; + +public class JavaAPI { + + @Test void mustBeAbleToUseUntypedActor() { + final RemoteSupport remote = Actors.remote(); + assertNotNull(remote); + } + + @Test void mustInteractWithActorRegistry() { + final ActorRegistry registry = Actors.registry(); + assertNotNull(registry); + } + + @Test void mustBeAbleToCreateActorRefFromClass() { + ActorRef ref = Actors.actorOf(JavaAPITestActor.class); + assertNotNull(ref); + } + + @Test void mustBeAbleToCreateActorRefFromFactory() { + ActorRef ref = Actors.actorOf(new Creator() { + public Actor create() { + return new JavaAPITestActor(); + } + }); + assertNotNull(ref); + } +} diff --git a/akka-actor/src/test/java/akka/actor/JavaAPITestActor.java b/akka-actor/src/test/java/akka/actor/JavaAPITestActor.java new file mode 100644 index 0000000000..fe729970cc --- /dev/null +++ b/akka-actor/src/test/java/akka/actor/JavaAPITestActor.java @@ -0,0 +1,7 @@ +package akka.actor; + +public class JavaAPITestActor extends UntypedActor { + public void onReceive(Object msg) { + getContext().replySafe("got it!"); + } +} diff --git a/akka-actor/src/test/scala/akka/dataflow/DataFlowSpec.scala b/akka-actor/src/test/scala/akka/dataflow/DataFlowSpec.scala index ae01e84b19..f5a107f511 100644 --- a/akka-actor/src/test/scala/akka/dataflow/DataFlowSpec.scala +++ b/akka-actor/src/test/scala/akka/dataflow/DataFlowSpec.scala @@ -72,7 +72,7 @@ class DataFlowTest extends Spec with ShouldMatchers with BeforeAndAfterAll { /* it("should be able to join streams") { import DataFlow._ - ActorRegistry.shutdownAll + Actor.registry.shutdownAll def ints(n: Int, max: Int, stream: DataFlowStream[Int]): Unit = if (n != max) { stream <<< n @@ -139,7 +139,7 @@ class DataFlowTest extends Spec with ShouldMatchers with BeforeAndAfterAll { /* it("should be able to conditionally set variables") { import DataFlow._ - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val latch = new CountDownLatch(1) val x, y, z, v = new DataFlowVariable[Int] diff --git a/akka-actor/src/test/scala/akka/misc/ActorRegistrySpec.scala b/akka-actor/src/test/scala/akka/misc/ActorRegistrySpec.scala index f951281f2e..09a23dbc5c 100644 --- a/akka-actor/src/test/scala/akka/misc/ActorRegistrySpec.scala +++ b/akka-actor/src/test/scala/akka/misc/ActorRegistrySpec.scala @@ -33,10 +33,10 @@ class ActorRegistrySpec extends JUnitSuite { import ActorRegistrySpec._ @Test def shouldGetActorByIdFromActorRegistry { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor = actorOf[TestActor] actor.start - val actors = ActorRegistry.actorsFor("MyID") + val actors = Actor.registry.actorsFor("MyID") assert(actors.size === 1) assert(actors.head.actor.isInstanceOf[TestActor]) assert(actors.head.id === "MyID") @@ -44,21 +44,21 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetActorByUUIDFromActorRegistry { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor = actorOf[TestActor] val uuid = actor.uuid actor.start - val actorOrNone = ActorRegistry.actorFor(uuid) + val actorOrNone = Actor.registry.actorFor(uuid) assert(actorOrNone.isDefined) assert(actorOrNone.get.uuid === uuid) actor.stop } @Test def shouldGetActorByClassFromActorRegistry { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor = actorOf[TestActor] actor.start - val actors = ActorRegistry.actorsFor(classOf[TestActor]) + val actors = Actor.registry.actorsFor(classOf[TestActor]) assert(actors.size === 1) assert(actors.head.actor.isInstanceOf[TestActor]) assert(actors.head.id === "MyID") @@ -66,10 +66,10 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetActorByManifestFromActorRegistry { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor = actorOf[TestActor] actor.start - val actors = ActorRegistry.actorsFor[TestActor] + val actors = Actor.registry.actorsFor[TestActor] assert(actors.size === 1) assert(actors.head.actor.isInstanceOf[TestActor]) assert(actors.head.id === "MyID") @@ -77,10 +77,10 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldFindThingsFromActorRegistry { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor = actorOf[TestActor] actor.start - val found = ActorRegistry.find({ case a: ActorRef if a.actor.isInstanceOf[TestActor] => a }) + val found = Actor.registry.find({ case a: ActorRef if a.actor.isInstanceOf[TestActor] => a }) assert(found.isDefined) assert(found.get.actor.isInstanceOf[TestActor]) assert(found.get.id === "MyID") @@ -88,12 +88,12 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetActorsByIdFromActorRegistry { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor1 = actorOf[TestActor] actor1.start val actor2 = actorOf[TestActor] actor2.start - val actors = ActorRegistry.actorsFor("MyID") + val actors = Actor.registry.actorsFor("MyID") assert(actors.size === 2) assert(actors.head.actor.isInstanceOf[TestActor]) assert(actors.head.id === "MyID") @@ -104,12 +104,12 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetActorsByClassFromActorRegistry { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor1 = actorOf[TestActor] actor1.start val actor2 = actorOf[TestActor] actor2.start - val actors = ActorRegistry.actorsFor(classOf[TestActor]) + val actors = Actor.registry.actorsFor(classOf[TestActor]) assert(actors.size === 2) assert(actors.head.actor.isInstanceOf[TestActor]) assert(actors.head.id === "MyID") @@ -120,12 +120,12 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetActorsByManifestFromActorRegistry { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor1 = actorOf[TestActor] actor1.start val actor2 = actorOf[TestActor] actor2.start - val actors = ActorRegistry.actorsFor[TestActor] + val actors = Actor.registry.actorsFor[TestActor] assert(actors.size === 2) assert(actors.head.actor.isInstanceOf[TestActor]) assert(actors.head.id === "MyID") @@ -137,26 +137,26 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldGetActorsByMessageFromActorRegistry { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor1 = actorOf[TestActor] actor1.start val actor2 = actorOf[TestActor2] actor2.start - val actorsForAcotrTestActor = ActorRegistry.actorsFor[TestActor] + val actorsForAcotrTestActor = Actor.registry.actorsFor[TestActor] assert(actorsForAcotrTestActor.size === 1) - val actorsForAcotrTestActor2 = ActorRegistry.actorsFor[TestActor2] + val actorsForAcotrTestActor2 = Actor.registry.actorsFor[TestActor2] assert(actorsForAcotrTestActor2.size === 1) - val actorsForAcotr = ActorRegistry.actorsFor[Actor] + val actorsForAcotr = Actor.registry.actorsFor[Actor] assert(actorsForAcotr.size === 2) - val actorsForMessagePing2 = ActorRegistry.actorsFor[Actor]("ping2") + val actorsForMessagePing2 = Actor.registry.actorsFor[Actor]("ping2") assert(actorsForMessagePing2.size === 1) - val actorsForMessagePing = ActorRegistry.actorsFor[Actor]("ping") + val actorsForMessagePing = Actor.registry.actorsFor[Actor]("ping") assert(actorsForMessagePing.size === 2) actor1.stop @@ -164,12 +164,12 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetAllActorsFromActorRegistry { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor1 = actorOf[TestActor] actor1.start val actor2 = actorOf[TestActor] actor2.start - val actors = ActorRegistry.actors + val actors = Actor.registry.actors assert(actors.size === 2) assert(actors.head.actor.isInstanceOf[TestActor]) assert(actors.head.id === "MyID") @@ -180,43 +180,43 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetResponseByAllActorsInActorRegistryWhenInvokingForeach { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor1 = actorOf[TestActor] actor1.start val actor2 = actorOf[TestActor] actor2.start record = "" - ActorRegistry.foreach(actor => actor !! "ping") + Actor.registry.foreach(actor => actor !! "ping") assert(record === "pongpong") actor1.stop actor2.stop } @Test def shouldShutdownAllActorsInActorRegistry { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor1 = actorOf[TestActor] actor1.start val actor2 = actorOf[TestActor] actor2.start - ActorRegistry.shutdownAll - assert(ActorRegistry.actors.size === 0) + Actor.registry.shutdownAll + assert(Actor.registry.actors.size === 0) } @Test def shouldRemoveUnregisterActorInActorRegistry { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val actor1 = actorOf[TestActor] actor1.start val actor2 = actorOf[TestActor] actor2.start - assert(ActorRegistry.actors.size === 2) - ActorRegistry.unregister(actor1) - assert(ActorRegistry.actors.size === 1) - ActorRegistry.unregister(actor2) - assert(ActorRegistry.actors.size === 0) + assert(Actor.registry.actors.size === 2) + Actor.registry.unregister(actor1) + assert(Actor.registry.actors.size === 1) + Actor.registry.unregister(actor2) + assert(Actor.registry.actors.size === 0) } @Test def shouldBeAbleToRegisterActorsConcurrently { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll def mkTestActors = for(i <- (1 to 10).toList;j <- 1 to 3000) yield actorOf( new Actor { self.id = i.toString @@ -244,7 +244,7 @@ class ActorRegistrySpec extends JUnitSuite { for(i <- 1 to 10) { val theId = i.toString - val actors = ActorRegistry.actorsFor(theId).toSet + val actors = Actor.registry.actorsFor(theId).toSet for(a <- actors if a.id == theId) assert(actors contains a) assert(actors.size === 9000) } diff --git a/akka-actor/src/test/scala/akka/misc/SchedulerSpec.scala b/akka-actor/src/test/scala/akka/misc/SchedulerSpec.scala index a4471503da..79b09d49d1 100644 --- a/akka-actor/src/test/scala/akka/misc/SchedulerSpec.scala +++ b/akka-actor/src/test/scala/akka/misc/SchedulerSpec.scala @@ -12,7 +12,7 @@ class SchedulerSpec extends JUnitSuite { def withCleanEndState(action: => Unit) { action Scheduler.restart - ActorRegistry.shutdownAll + Actor.registry.shutdownAll } @@ -62,10 +62,10 @@ class SchedulerSpec extends JUnitSuite { val actor = actorOf(new Actor { def receive = { case Ping => ticks.countDown } }).start - val numActors = ActorRegistry.actors.length + val numActors = Actor.registry.actors.length (1 to 1000).foreach( _ => Scheduler.scheduleOnce(actor,Ping,1,TimeUnit.MILLISECONDS) ) assert(ticks.await(10,TimeUnit.SECONDS)) - assert(ActorRegistry.actors.length === numActors) + assert(Actor.registry.actors.length === numActors) } /** diff --git a/akka-http/src/main/scala/akka/http/Mist.scala b/akka-http/src/main/scala/akka/http/Mist.scala index 992e4695be..3c2ae06ced 100644 --- a/akka-http/src/main/scala/akka/http/Mist.scala +++ b/akka-http/src/main/scala/akka/http/Mist.scala @@ -70,7 +70,7 @@ trait Mist extends Logging { /** * The root endpoint actor */ - protected val _root = ActorRegistry.actorsFor(RootActorID).head + protected val _root = Actor.registry.actorsFor(RootActorID).head /** * Server-specific method factory diff --git a/akka-http/src/main/scala/akka/security/Security.scala b/akka-http/src/main/scala/akka/security/Security.scala index bd50b0a1c7..553984a22e 100644 --- a/akka-http/src/main/scala/akka/security/Security.scala +++ b/akka-http/src/main/scala/akka/security/Security.scala @@ -110,7 +110,7 @@ class AkkaSecurityFilterFactory extends ResourceFilterFactory with Logging { * Currently we always take the first, since there usually should be at most one authentication actor, but a round-robin * strategy could be implemented in the future */ - def authenticator: ActorRef = ActorRegistry.actorsFor(authenticatorFQN).head + def authenticator: ActorRef = Actor.registry.actorsFor(authenticatorFQN).head def mkFilter(roles: Option[List[String]]): java.util.List[ResourceFilter] = java.util.Collections.singletonList(new Filter(authenticator, roles)) diff --git a/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala b/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala index c0aa2218dd..c49e505d5a 100644 --- a/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala +++ b/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala @@ -5,7 +5,7 @@ package akka.remote import akka.config.Config.config -import akka.actor. {ActorRegistry, BootableActorLoaderService} +import akka.actor. {Actor, BootableActorLoaderService} import akka.util. {ReflectiveAccess, Bootable, Logging} /** @@ -17,7 +17,7 @@ trait BootableRemoteActorService extends Bootable with Logging { self: BootableActorLoaderService => protected lazy val remoteServerThread = new Thread(new Runnable() { - def run = ActorRegistry.remote.start(loader = self.applicationLoader) //Use config host/port + def run = Actor.remote.start(loader = self.applicationLoader) //Use config host/port }, "Akka Remote Service") def startRemoteService = remoteServerThread.start @@ -33,7 +33,7 @@ trait BootableRemoteActorService extends Bootable with Logging { abstract override def onUnload = { log.slf4j.info("Shutting down Remote Actors Service") - ActorRegistry.remote.shutdown + Actor.remote.shutdown if (remoteServerThread.isAlive) remoteServerThread.join(1000) log.slf4j.info("Remote Actors Service has been shut down") super.onUnload diff --git a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala index 5f351ccbb1..738e351840 100644 --- a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -91,7 +91,7 @@ object ActorSerialization { toBinary(a, srlMailBox)(format) private[akka] def toAddressProtocol(actorRef: ActorRef) = { - val address = actorRef.homeAddress.getOrElse(ActorRegistry.remote.address) + val address = actorRef.homeAddress.getOrElse(Actor.remote.address) AddressProtocol.newBuilder .setHostname(address.getHostName) .setPort(address.getPort) @@ -252,7 +252,7 @@ object RemoteActorSerialization { Actor.log.slf4j.debug("Register serialized Actor [{}] as remote @ [{}:{}]",actorClassName, ar.homeAddress) - ActorRegistry.remote.registerByUuid(ar) + Actor.remote.registerByUuid(ar) RemoteActorRefProtocol.newBuilder .setClassOrServiceName("uuid:"+uuid.toString) diff --git a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala index 63cc942381..be6234bf00 100644 --- a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala +++ b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala @@ -25,7 +25,7 @@ class AkkaRemoteTest extends BeforeAndAfterEach { import AkkaRemoteTest._ - val remote = ActorRegistry.remote + val remote = Actor.remote val unit = TimeUnit.SECONDS val host = "localhost" @@ -53,7 +53,7 @@ class AkkaRemoteTest extends override def afterEach() { remote.shutdown - ActorRegistry.shutdownAll + Actor.registry.shutdownAll super.afterEach } diff --git a/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala b/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala index cac34523ba..f6e0c1806f 100644 --- a/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala +++ b/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala @@ -1,6 +1,6 @@ package akka.actor.remote -import akka.actor. {ActorRegistry, Actor} +import akka.actor.{Actor} object OptimizedLocalScopedSpec { class TestActor extends Actor { @@ -14,14 +14,14 @@ class OptimizedLocalScopedSpec extends AkkaRemoteTest { "An enabled optimized local scoped remote" should { "Fetch local actor ref when scope is local" in { - val fooActor = ActorRegistry.actorOf[TestActor].start + val fooActor = Actor.actorOf[TestActor].start remote.register("foo", fooActor) remote.actorFor("foo", host, port) must be (fooActor) } "Create local actor when client-managed is hosted locally" in { - val localClientManaged = ActorRegistry.remote.actorOf[TestActor](host, port) + val localClientManaged = Actor.remote.actorOf[TestActor](host, port) localClientManaged.homeAddress must be (None) } diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSample.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSample.scala index a0fbb25d84..a8990e2b88 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSample.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSample.scala @@ -36,14 +36,14 @@ class HelloWorldActor extends Actor { object ServerInitiatedRemoteActorServer { def main(args: Array[String]) = { - ActorRegistry.remote.start("localhost", 2552) - ActorRegistry.remote.register("hello-service", actorOf[HelloWorldActor]) + Actor.remote.start("localhost", 2552) + Actor.remote.register("hello-service", actorOf[HelloWorldActor]) } } object ServerInitiatedRemoteActorClient extends Logging { def main(args: Array[String]) = { - val actor = ActorRegistry.remote.actorFor("hello-service", "localhost", 2552) + val actor = Actor.remote.actorFor("hello-service", "localhost", 2552) val result = actor !! "Hello" log.slf4j.info("Result from Remote Actor: {}", result) } diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala index 264831b208..88a5ec8ec3 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala @@ -96,10 +96,10 @@ class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { implicit val sender = replyHandler(latch, "Pong") remote.register(actorOf[RemoteActorSpecActorUnidirectional]) val actor = remote.actorFor("akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorUnidirectional", host, port) - val numberOfActorsInRegistry = ActorRegistry.actors.length + val numberOfActorsInRegistry = Actor.registry.actors.length actor ! "Ping" latch.await(1, TimeUnit.SECONDS) must be (true) - numberOfActorsInRegistry must equal (ActorRegistry.actors.length) + numberOfActorsInRegistry must equal (Actor.registry.actors.length) } "UseServiceNameAsIdForRemoteActorRef" in { @@ -194,7 +194,7 @@ class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { latch.countDown } - val decrementers = ActorRegistry.actorsFor[Decrementer] + val decrementers = Actor.registry.actorsFor[Decrementer] decrementers must have size(2) //No new are allowed to have been created decrementers.find( _ eq localFoo) must equal (Some(localFoo)) decrementers.find( _ eq localBar) must equal (Some(localBar)) diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala index 6c7543dbe3..48520e2b34 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteTypedActorSpec.scala @@ -39,10 +39,10 @@ class ServerInitiatedRemoteTypedActorSpec extends AkkaRemoteTest { "should not recreate registered actors" in { val actor = createRemoteActorRef - val numberOfActorsInRegistry = ActorRegistry.actors.length + val numberOfActorsInRegistry = Actor.registry.actors.length actor.oneWay oneWayLog.poll(5, TimeUnit.SECONDS) must equal ("oneway") - numberOfActorsInRegistry must be (ActorRegistry.actors.length) + numberOfActorsInRegistry must be (Actor.registry.actors.length) } "should support multiple variants to get the actor from client side" in { diff --git a/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala b/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala index 46d8ba2730..001a66eae0 100644 --- a/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala +++ b/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala @@ -13,14 +13,14 @@ class UnOptimizedLocalScopedSpec extends AkkaRemoteTest { "An enabled optimized local scoped remote" should { "Fetch remote actor ref when scope is local" in { - val fooActor = ActorRegistry.actorOf[TestActor].start + val fooActor = Actor.actorOf[TestActor].start remote.register("foo", fooActor) remote.actorFor("foo", host, port) must not be (fooActor) } "Create remote actor when client-managed is hosted locally" in { - val localClientManaged = ActorRegistry.remote.actorOf[TestActor](host, port) + val localClientManaged = Actor.remote.actorOf[TestActor](host, port) localClientManaged.homeAddress must not be (None) } diff --git a/akka-remote/src/test/scala/serialization/UntypedActorSerializationSpec.scala b/akka-remote/src/test/scala/serialization/UntypedActorSerializationSpec.scala index e844ff0104..b9752d2d55 100644 --- a/akka-remote/src/test/scala/serialization/UntypedActorSerializationSpec.scala +++ b/akka-remote/src/test/scala/serialization/UntypedActorSerializationSpec.scala @@ -43,7 +43,7 @@ class UntypedActorSerializationSpec extends describe("Serializable untyped actor") { it("should be able to serialize and de-serialize a stateful untyped actor") { - val actor1 = UntypedActor.actorOf[MyUntypedActor](classOf[MyUntypedActor]).start + val actor1 = Actors.actorOf(classOf[MyUntypedActor]).start actor1.sendRequestReply("hello") should equal("world 1") actor1.sendRequestReply("debasish") should equal("hello debasish 2") diff --git a/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala b/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala index 5df1a725ce..f0adb42899 100644 --- a/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala +++ b/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala @@ -7,7 +7,7 @@ package sample.remote import akka.actor.Actor._ import akka.util.Logging import akka.actor. {ActorRegistry, Actor} -import ActorRegistry.remote +import Actor.remote class RemoteHelloWorldActor extends Actor { def receive = { diff --git a/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala b/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala index 0c937ccdae..96e8d1debf 100644 --- a/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala +++ b/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala @@ -19,9 +19,9 @@ class HelloWorldActor extends Actor { object ServerManagedRemoteActorServer extends Logging { def run = { - ActorRegistry.remote.start("localhost", 2552) + Actor.remote.start("localhost", 2552) log.slf4j.info("Remote node started") - ActorRegistry.remote.register("hello-service", actorOf[HelloWorldActor]) + Actor.remote.register("hello-service", actorOf[HelloWorldActor]) log.slf4j.info("Remote actor registered and started") } @@ -31,7 +31,7 @@ object ServerManagedRemoteActorServer extends Logging { object ServerManagedRemoteActorClient extends Logging { def run = { - val actor = ActorRegistry.remote.actorFor("hello-service", "localhost", 2552) + val actor = Actor.remote.actorFor("hello-service", "localhost", 2552) log.slf4j.info("Remote client created") log.slf4j.info("Sending 'Hello' to remote actor") val result = actor !! "Hello" diff --git a/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedCounter.java b/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedCounter.java index 649ef42d3b..658ec71ff4 100644 --- a/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedCounter.java +++ b/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedCounter.java @@ -2,7 +2,7 @@ package akka.transactor.example; import akka.transactor.Coordinated; import akka.transactor.Atomically; -import akka.actor.ActorRef; +import akka.actor.Actors; import akka.actor.UntypedActor; import akka.stm.Ref; diff --git a/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedExample.java b/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedExample.java index d3a2a14107..127a911677 100644 --- a/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedExample.java +++ b/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedExample.java @@ -2,7 +2,7 @@ package akka.transactor.example; import akka.transactor.Coordinated; import akka.actor.ActorRef; -import akka.actor.UntypedActor; +import akka.actor.Actors; import akka.dispatch.Future; import akka.dispatch.Futures; @@ -12,8 +12,8 @@ public class UntypedCoordinatedExample { System.out.println("Untyped transactor example"); System.out.println(); - ActorRef counter1 = UntypedActor.actorOf(UntypedCoordinatedCounter.class).start(); - ActorRef counter2 = UntypedActor.actorOf(UntypedCoordinatedCounter.class).start(); + ActorRef counter1 = Actors.actorOf(UntypedCoordinatedCounter.class).start(); + ActorRef counter2 = Actors.actorOf(UntypedCoordinatedCounter.class).start(); counter1.sendOneWay(new Coordinated(new Increment(counter2))); diff --git a/akka-stm/src/test/java/akka/transactor/example/UntypedTransactorExample.java b/akka-stm/src/test/java/akka/transactor/example/UntypedTransactorExample.java index fdef74ca60..d2f83ff25e 100644 --- a/akka-stm/src/test/java/akka/transactor/example/UntypedTransactorExample.java +++ b/akka-stm/src/test/java/akka/transactor/example/UntypedTransactorExample.java @@ -1,7 +1,7 @@ package akka.transactor.example; import akka.actor.ActorRef; -import akka.actor.UntypedActor; +import akka.actor.Actors; import akka.dispatch.Future; import akka.dispatch.Futures; @@ -11,8 +11,8 @@ public class UntypedTransactorExample { System.out.println("Untyped transactor example"); System.out.println(); - ActorRef counter1 = UntypedActor.actorOf(UntypedCounter.class).start(); - ActorRef counter2 = UntypedActor.actorOf(UntypedCounter.class).start(); + ActorRef counter1 = Actors.actorOf(UntypedCounter.class).start(); + ActorRef counter2 = Actors.actorOf(UntypedCounter.class).start(); counter1.sendOneWay(new Increment(counter2)); diff --git a/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedCounter.java b/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedCounter.java index b1030106de..6ddcf0e4a4 100644 --- a/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedCounter.java +++ b/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedCounter.java @@ -3,6 +3,7 @@ package akka.transactor.test; import akka.transactor.Coordinated; import akka.transactor.Atomically; import akka.actor.ActorRef; +import akka.actor.Actors; import akka.actor.UntypedActor; import akka.stm.*; import akka.util.Duration; diff --git a/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedIncrementTest.java b/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedIncrementTest.java index 35635ceb4f..a9eabe7be1 100644 --- a/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedIncrementTest.java +++ b/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedIncrementTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import org.junit.Before; import akka.transactor.Coordinated; +import akka.actor.Actors; import akka.actor.ActorRef; import akka.actor.UntypedActor; import akka.actor.UntypedActorFactory; @@ -28,7 +29,7 @@ public class UntypedCoordinatedIncrementTest { counters = new ArrayList(); for (int i = 1; i <= numCounters; i++) { final String name = "counter" + i; - ActorRef counter = UntypedActor.actorOf(new UntypedActorFactory() { + ActorRef counter = Actors.actorOf(new UntypedActorFactory() { public UntypedActor create() { return new UntypedCoordinatedCounter(name); } @@ -36,7 +37,7 @@ public class UntypedCoordinatedIncrementTest { counter.start(); counters.add(counter); } - failer = UntypedActor.actorOf(UntypedFailer.class); + failer = Actors.actorOf(UntypedFailer.class); failer.start(); } diff --git a/akka-stm/src/test/java/akka/transactor/test/UntypedTransactorTest.java b/akka-stm/src/test/java/akka/transactor/test/UntypedTransactorTest.java index e378b1c598..9610aa2ebe 100644 --- a/akka-stm/src/test/java/akka/transactor/test/UntypedTransactorTest.java +++ b/akka-stm/src/test/java/akka/transactor/test/UntypedTransactorTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import org.junit.Before; import akka.actor.ActorRef; +import akka.actor.Actors; import akka.actor.UntypedActor; import akka.actor.UntypedActorFactory; import akka.dispatch.Future; @@ -27,7 +28,7 @@ public class UntypedTransactorTest { counters = new ArrayList(); for (int i = 1; i <= numCounters; i++) { final String name = "counter" + i; - ActorRef counter = UntypedActor.actorOf(new UntypedActorFactory() { + ActorRef counter = Actors.actorOf(new UntypedActorFactory() { public UntypedActor create() { return new UntypedCounter(name); } @@ -35,7 +36,7 @@ public class UntypedTransactorTest { counter.start(); counters.add(counter); } - failer = UntypedActor.actorOf(UntypedFailer.class); + failer = Actors.actorOf(UntypedFailer.class); failer.start(); } diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala index 8f0f97d744..7486d8fd9a 100644 --- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala @@ -348,7 +348,7 @@ final class TypedActorContext(private[akka] val actorRef: ActorRef) { /** * Returns the home address and port for this actor. */ - def homeAddress: InetSocketAddress = actorRef.homeAddress.getOrElse(ActorRegistry.homeAddress) + def homeAddress: InetSocketAddress = actorRef.homeAddress.getOrElse(null)//TODO: REVISIT: Sensible to return null? } object TypedActorConfiguration { @@ -539,7 +539,7 @@ object TypedActor extends Logging { config match { case null => actorOf(typedActor) case c: TypedActorConfiguration if (c._host.isDefined) => - ActorRegistry.remote.actorOf(typedActor, c._host.get.getHostName, c._host.get.getPort) + Actor.remote.actorOf(typedActor, c._host.get.getHostName, c._host.get.getPort) case _ => actorOf(typedActor) } } @@ -676,9 +676,10 @@ object TypedActor extends Logging { * Get the underlying typed actor for the given Typed Actor. */ def actorFor(proxy: AnyRef): Option[ActorRef] = - ActorRegistry - .actorsFor(classOf[TypedActor]) - .find(a => a.actor.asInstanceOf[TypedActor].proxy == proxy) + Actor.registry find { + case a if classOf[TypedActor].isAssignableFrom(a.actor.getClass) && a.actor.asInstanceOf[TypedActor].proxy == proxy => + a + } /** * Get the typed actor proxy for the given Typed Actor. @@ -906,7 +907,7 @@ private[akka] abstract class ActorAspect { val (message: Array[AnyRef], isEscaped) = escapeArguments(methodRtti.getParameterValues) - val future = ActorRegistry.remote.send[AnyRef]( + val future = Actor.remote.send[AnyRef]( message, None, None, remoteAddress.get, timeout, isOneWay, actorRef, Some((interfaceClass.getName, methodRtti.getMethod.getName)), diff --git a/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala b/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala index 321c6b0c3a..81bde84281 100644 --- a/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala +++ b/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala @@ -112,7 +112,7 @@ private[akka] class TypedActorGuiceConfigurator extends TypedActorConfiguratorBa component.remoteAddress match { case Some(a) => (Some(new InetSocketAddress(a.hostname, a.port)), - ActorRegistry.remote.actorOf(TypedActor.newTypedActor(implementationClass), a.hostname, a.port)) + Actor.remote.actorOf(TypedActor.newTypedActor(implementationClass), a.hostname, a.port)) case None => (None, Actor.actorOf(TypedActor.newTypedActor(implementationClass))) } diff --git a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorLifecycleSpec.scala b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorLifecycleSpec.scala index a8dd7a75ad..9790c3657e 100644 --- a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorLifecycleSpec.scala +++ b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorLifecycleSpec.scala @@ -112,11 +112,11 @@ class TypedActorLifecycleSpec extends Spec with ShouldMatchers with BeforeAndAft } } - it("should postStop non-supervised, annotated typed actor on ActorRegistry.shutdownAll") { + it("should postStop non-supervised, annotated typed actor on Actor.registry.shutdownAll") { val obj = TypedActor.newInstance(classOf[SamplePojoAnnotated]) assert(AspectInitRegistry.initFor(obj) ne null) assert("hello akka" === obj.greet("akka")) - ActorRegistry.shutdownAll + Actor.registry.shutdownAll assert(AspectInitRegistry.initFor(obj) eq null) assert(!obj.pre) assert(!obj.post) diff --git a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorRegistrySpec.scala b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorRegistrySpec.scala index cbbb6c169d..0a031026ef 100644 --- a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorRegistrySpec.scala +++ b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorRegistrySpec.scala @@ -17,59 +17,59 @@ class TypedActorRegistrySpec extends WordSpec with MustMatchers { "Typed Actor" should { "be able to be retreived from the registry by class" in { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val my = TypedActor.newInstance[My](classOf[My], classOf[MyImpl], 3000) - val actors = ActorRegistry.typedActorsFor(classOf[My]) + val actors = Actor.registry.typedActorsFor(classOf[My]) actors.length must be (1) - ActorRegistry.shutdownAll + Actor.registry.shutdownAll } "be able to be retreived from the registry by manifest" in { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val my = TypedActor.newInstance[My](classOf[My], classOf[MyImpl], 3000) - val option = ActorRegistry.typedActorFor[My] + val option = Actor.registry.typedActorFor[My] option must not be (null) option.isDefined must be (true) - ActorRegistry.shutdownAll + Actor.registry.shutdownAll } "be able to be retreived from the registry by class two times" in { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val my = TypedActor.newInstance[My](classOf[My], classOf[MyImpl], 3000) - val actors1 = ActorRegistry.typedActorsFor(classOf[My]) + val actors1 = Actor.registry.typedActorsFor(classOf[My]) actors1.length must be (1) - val actors2 = ActorRegistry.typedActorsFor(classOf[My]) + val actors2 = Actor.registry.typedActorsFor(classOf[My]) actors2.length must be (1) - ActorRegistry.shutdownAll + Actor.registry.shutdownAll } "be able to be retreived from the registry by manifest two times" in { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val my = TypedActor.newInstance[My](classOf[My], classOf[MyImpl], 3000) - val option1 = ActorRegistry.typedActorFor[My] + val option1 = Actor.registry.typedActorFor[My] option1 must not be (null) option1.isDefined must be (true) - val option2 = ActorRegistry.typedActorFor[My] + val option2 = Actor.registry.typedActorFor[My] option2 must not be (null) option2.isDefined must be (true) - ActorRegistry.shutdownAll + Actor.registry.shutdownAll } "be able to be retreived from the registry by manifest two times (even when created in supervisor)" in { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll val manager = new TypedActorConfigurator manager.configure( OneForOneStrategy(classOf[Exception] :: Nil, 3, 1000), Array(new SuperviseTypedActor(classOf[My], classOf[MyImpl], Permanent, 6000)) ).supervise - val option1 = ActorRegistry.typedActorFor[My] + val option1 = Actor.registry.typedActorFor[My] option1 must not be (null) option1.isDefined must be (true) - val option2 = ActorRegistry.typedActorFor[My] + val option2 = Actor.registry.typedActorFor[My] option2 must not be (null) option2.isDefined must be (true) - ActorRegistry.shutdownAll + Actor.registry.shutdownAll } } } diff --git a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala index 12e9691de7..863a1bb064 100644 --- a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala +++ b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala @@ -68,7 +68,7 @@ class TypedActorSpec extends } override def afterEach() { - ActorRegistry.shutdownAll + Actor.registry.shutdownAll } describe("TypedActor") { @@ -115,67 +115,67 @@ class TypedActorSpec extends it("should support finding a typed actor by uuid ") { val typedActorRef = TypedActor.actorFor(simplePojo).get val uuid = typedActorRef.uuid - assert(ActorRegistry.typedActorFor(newUuid()) === None) - assert(ActorRegistry.typedActorFor(uuid).isDefined) - assert(ActorRegistry.typedActorFor(uuid).get === simplePojo) + assert(Actor.registry.typedActorFor(newUuid()) === None) + assert(Actor.registry.typedActorFor(uuid).isDefined) + assert(Actor.registry.typedActorFor(uuid).get === simplePojo) } it("should support finding typed actors by id ") { - val typedActors = ActorRegistry.typedActorsFor("my-custom-id") + val typedActors = Actor.registry.typedActorsFor("my-custom-id") assert(typedActors.length === 1) assert(typedActors.contains(pojo)) // creating untyped actor with same custom id val actorRef = Actor.actorOf[MyActor].start - val typedActors2 = ActorRegistry.typedActorsFor("my-custom-id") + val typedActors2 = Actor.registry.typedActorsFor("my-custom-id") assert(typedActors2.length === 1) assert(typedActors2.contains(pojo)) actorRef.stop } it("should support to filter typed actors") { - val actors = ActorRegistry.filterTypedActors(ta => ta.isInstanceOf[MyTypedActor]) + val actors = Actor.registry.filterTypedActors(ta => ta.isInstanceOf[MyTypedActor]) assert(actors.length === 1) assert(actors.contains(pojo)) } it("should support to find typed actors by class") { - val actors = ActorRegistry.typedActorsFor(classOf[MyTypedActorImpl]) + val actors = Actor.registry.typedActorsFor(classOf[MyTypedActorImpl]) assert(actors.length === 1) assert(actors.contains(pojo)) - assert(ActorRegistry.typedActorsFor(classOf[MyActor]).isEmpty) + assert(Actor.registry.typedActorsFor(classOf[MyActor]).isEmpty) } it("should support to get all typed actors") { - val actors = ActorRegistry.typedActors + val actors = Actor.registry.typedActors assert(actors.length === 2) assert(actors.contains(pojo)) assert(actors.contains(simplePojo)) } it("should support to find typed actors by manifest") { - val actors = ActorRegistry.typedActorsFor[MyTypedActorImpl] + val actors = Actor.registry.typedActorsFor[MyTypedActorImpl] assert(actors.length === 1) assert(actors.contains(pojo)) - assert(ActorRegistry.typedActorsFor[MyActor].isEmpty) + assert(Actor.registry.typedActorsFor[MyActor].isEmpty) } it("should support foreach for typed actors") { val actorRef = Actor.actorOf[MyActor].start - assert(ActorRegistry.actors.size === 3) - assert(ActorRegistry.typedActors.size === 2) - ActorRegistry.foreachTypedActor(TypedActor.stop(_)) - assert(ActorRegistry.actors.size === 1) - assert(ActorRegistry.typedActors.size === 0) + assert(Actor.registry.actors.size === 3) + assert(Actor.registry.typedActors.size === 2) + Actor.registry.foreachTypedActor(TypedActor.stop(_)) + assert(Actor.registry.actors.size === 1) + assert(Actor.registry.typedActors.size === 0) } it("should shutdown all typed and untyped actors") { val actorRef = Actor.actorOf[MyActor].start - assert(ActorRegistry.actors.size === 3) - assert(ActorRegistry.typedActors.size === 2) - ActorRegistry.shutdownAll() - assert(ActorRegistry.actors.size === 0) - assert(ActorRegistry.typedActors.size === 0) + assert(Actor.registry.actors.size === 3) + assert(Actor.registry.typedActors.size === 2) + Actor.registry.shutdownAll() + assert(Actor.registry.actors.size === 0) + assert(Actor.registry.typedActors.size === 0) } } } From 18c8098fd9e89056f1c7070cce53277d1d0863d3 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 4 Jan 2011 15:23:33 +0100 Subject: [PATCH 38/38] Minor typed actor lookup cleanup --- akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala index 7486d8fd9a..45962ed7fa 100644 --- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala @@ -677,7 +677,7 @@ object TypedActor extends Logging { */ def actorFor(proxy: AnyRef): Option[ActorRef] = Actor.registry find { - case a if classOf[TypedActor].isAssignableFrom(a.actor.getClass) && a.actor.asInstanceOf[TypedActor].proxy == proxy => + case a if a.actor.isInstanceOf[TypedActor] && a.actor.asInstanceOf[TypedActor].proxy == proxy => a }