From a2a09ec5a922c5a766472a054949cd83aba378ce Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 20 Nov 2011 21:47:16 +0100 Subject: [PATCH 01/30] write addressing & path spec --- akka-docs/general/addressing.rst | 145 +++++++++++++++++++++++++++++++ akka-docs/general/index.rst | 1 + 2 files changed, 146 insertions(+) create mode 100644 akka-docs/general/addressing.rst diff --git a/akka-docs/general/addressing.rst b/akka-docs/general/addressing.rst new file mode 100644 index 0000000000..708cc410fc --- /dev/null +++ b/akka-docs/general/addressing.rst @@ -0,0 +1,145 @@ +Actor References, Paths and Addresses +===================================== + +This chapter describes how actors are identified and located within a possibly +distributed actor system. It ties into the central idea that actor systems form +intrinsic supervision hierarchies as well as that communication between actors +is transparent with respect to their placement across multiple network nodes. + +What is an Actor Reference? +--------------------------- + +An actor reference is a subtype of :class:`ActorRef`, whose foremost purpose is +to support sending messages to the actor it represents. Each actor has access +to its canonical (local) reference through the :meth:`self` field; this +reference is also included as sender reference by default for all messages sent +to other actors. Conversely, during message processing the actor has access to +a reference representing the sender of the current message through the +:meth:`sender` field. + +There are several different types of actor references that are supported +depending on the configuration of the actor system: + +- Purely local actor references are used by actor systems which are not + configured to support networking functions. These actor references cannot + ever be sent across a network connection while retaining their functionality. +- Local actor references when remoting is enabled are used by actor systems + which support networking functions for those references which represent + actors within the same JVM. In order to be recognizable also when sent to + other network nodes, these references include protocol and remote addressing + information. +- Remote actor references represent actors which are reachable using remote + communication, i.e. sending messages to them will serialize the messages + transparently and send them to the other JVM. +- **(Future Extension)** Cluster actor references represent clustered actor + services which may be replicated, migrated or load-balanced across multiple + cluster nodes. As such they are virtual names which the cluster service + translates into local or remote actor references as appropriate. +- Unresolved actor references are obtained by querying an actor system and may + potentially represent one or more actors. + +How are Actor References obtained? +---------------------------------- + +An actor system is typically started by creating actors beneath the guardian +actor using the :meth:`ActorSystem.actorOf` method, which returns a reference +to the newly created actor. Each actor has direct access to references for its +parent, itself and its children. These references may be sent within messages +to other actors, enabling those to reply directly. + +Looking up Actors by Absolute Path +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In addition, actor references may be looked up using the +:meth:`ActorSystem.actorFor` method, which returns an unresolved actor +reference. Sending messages to such a reference will traverse the actor +hierarchy of the actor system from top to bottom by passing messages from +supervisor to child until either the target is reached or failure is certain +(i.e. a name in the path does not exist). Since this process takes time, +replies from the found actors—which include their sender reference—should be +used to replace them by direct actor references. + +Looking up Actors by Relative Path +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The third method for obtaining actor references is +:meth:`ActorContext.actorFor`, which is available inside any actor as +``context.actorFor``. This yields an unresolved actor reference much like its +twin on :class:`ActorSystem`, but instead of looking up the path starting from +the root of the actor tree it starts out on the current actor. Path elements +consisting of two dots (``".."``) may be used to access the parent actor and +other names are interpreted as globbing patterns. You can for example send a +message to all your siblings by using a path like ``"../*"`` (this will include +yourself). + +What is an Actor Path? +---------------------- + +Since actors are created in a strictly hierarchical fashion, there exists a +unique sequence of actor names given by following the tree of actors up until +the root of the actor system. This sequence can be seen as enclosing folders in +a file system, hence we adopted the name “path” to refer to it. + +Each actor path has an address component, describing the protocol and location +by which the corresponding actor is reachable, followed by the names of the +actors in the hierarchy from the root up. Examples are:: + + "jvm://my-system/app/service-a/worker1" + "akka://serv.example.com:5678/app/service-b" + +where the first represents a purely local actor reference while the second +stands for a remote actor reference. Each actor has access to its path, and you +may construct paths and pass them to :meth:`ActorSystem.actorFor` in order to +obtain a corresponding actor reference. + +One important aspect is that actor paths never span multiple actor systems or +JVMs. An actor path always represents the physical location of an actor. This +means that the supervision hierarchy and the path hierarchy of an actor may +diverge if one of its ancestors is remotely supervised. + +The Interplay with Remote Deployment +------------------------------------ + +When an actor creates a child, the actor system’s deployer will decide whether +the new actor resides in the same JVM or on another node. In the second case, +creation of the actor will be triggered via a network connection to happen in a +different JVM and consequently within a different actor system. The remote +system will place the new actor below a special path reserved for this purpose +and the supervisor of the new actor will be a remote actor reference +(representing that actor which triggered its creation). In this case, +:meth:`parent` (the supervisor reference) and :meth:`context.path.parent` (the +parent node in the actor’s path) do not represent the same actor. However, +looking up the child’s name within the supervisor will find it on the remote +node, preserving logical structure e.g. when sending to an unresolved actor +reference. + +What is the Address part used for? +---------------------------------- + +When sending an actor reference across the network, it is represented by its +path. Hence, the path must fully encode all information necessary to send +messages to the underlying actor. This is achieved by encoding protocol, host +and port in the address part of the path string. When an actor system receives +an actor path from a remote node, it checks whether that path’s address matches +the address of this actor system, in which case it will be resolved to the +actor’s local reference. Otherwise, it will be represented by a remote actor +reference. + +Special Paths used by Akka +-------------------------- + +At the root of the path hierarchy resides the root guardian above which all +other actors are found. The next level consists of the following: + +- ``"/app"`` is the guardian actor for all user-created top-level actors; + actors created using :meth:`ActorSystem.actorOf` are found at the next level. +- ``"/sys"`` is the guardian actor for all system-created top-level actors, + e.g. logging listeners or actors automatically deployed by configuration at + the start of the actor system. +- ``"/nul"`` is the dead letter actor, which is where all messages sent to + stopped or non-existing actors are re-routed. +- ``"/tmp"`` is the guardian for all short-lived system-created actors, e.g. + those which are used in the implementation of :meth:`ActorRef.ask`. +- ``"/remote"`` is an artificial path below which all actors reside whose + supervisors are remote actor references + diff --git a/akka-docs/general/index.rst b/akka-docs/general/index.rst index 83e9684bb4..687892b177 100644 --- a/akka-docs/general/index.rst +++ b/akka-docs/general/index.rst @@ -8,5 +8,6 @@ General configuration event-handler slf4j + addressing supervision guaranteed-delivery From d40235f11100c2b8758ff9b03183bec2c7b8195e Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 22 Nov 2011 15:15:57 +0100 Subject: [PATCH 02/30] version 2 of the addressing spec --- akka-docs/general/addressing.rst | 215 ++++++++++++++++++++++++------- 1 file changed, 165 insertions(+), 50 deletions(-) diff --git a/akka-docs/general/addressing.rst b/akka-docs/general/addressing.rst index 708cc410fc..520a1073bd 100644 --- a/akka-docs/general/addressing.rst +++ b/akka-docs/general/addressing.rst @@ -31,71 +31,171 @@ depending on the configuration of the actor system: - Remote actor references represent actors which are reachable using remote communication, i.e. sending messages to them will serialize the messages transparently and send them to the other JVM. +- There are several special types of actor references which behave like local + actor references for all practical purposes: + + - :class:`AskActorRef` is the special representation of a :meth:`Promise` for + the purpose of being completed by the response from an actor; it is created + by the :meth:`ActorRef.ask` invocation. + - :class:`DeadLetterActorRef` is the default implementation of the dead + letters service, where all messages are re-routed whose targets are shut + down or non-existent. + +- And then there are some one-off internal implementations which you should + never really see: + + - There is an actor reference which does not represent an actor but acts only + as a pseudo-supervisor for the root guardian, we call it “the one who walks + the bubbles of space-time”. + - The first logging service started before actually firing up actor creation + facilities is a fake actor reference which accepts log events and prints + them directly to standard output; it is :class:`Logging.StandardOutLogger`. + - **(Future Extension)** Cluster actor references represent clustered actor services which may be replicated, migrated or load-balanced across multiple cluster nodes. As such they are virtual names which the cluster service translates into local or remote actor references as appropriate. -- Unresolved actor references are obtained by querying an actor system and may - potentially represent one or more actors. - -How are Actor References obtained? ----------------------------------- - -An actor system is typically started by creating actors beneath the guardian -actor using the :meth:`ActorSystem.actorOf` method, which returns a reference -to the newly created actor. Each actor has direct access to references for its -parent, itself and its children. These references may be sent within messages -to other actors, enabling those to reply directly. - -Looking up Actors by Absolute Path -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In addition, actor references may be looked up using the -:meth:`ActorSystem.actorFor` method, which returns an unresolved actor -reference. Sending messages to such a reference will traverse the actor -hierarchy of the actor system from top to bottom by passing messages from -supervisor to child until either the target is reached or failure is certain -(i.e. a name in the path does not exist). Since this process takes time, -replies from the found actors—which include their sender reference—should be -used to replace them by direct actor references. - -Looking up Actors by Relative Path -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The third method for obtaining actor references is -:meth:`ActorContext.actorFor`, which is available inside any actor as -``context.actorFor``. This yields an unresolved actor reference much like its -twin on :class:`ActorSystem`, but instead of looking up the path starting from -the root of the actor tree it starts out on the current actor. Path elements -consisting of two dots (``".."``) may be used to access the parent actor and -other names are interpreted as globbing patterns. You can for example send a -message to all your siblings by using a path like ``"../*"`` (this will include -yourself). What is an Actor Path? ---------------------- Since actors are created in a strictly hierarchical fashion, there exists a -unique sequence of actor names given by following the tree of actors up until -the root of the actor system. This sequence can be seen as enclosing folders in -a file system, hence we adopted the name “path” to refer to it. +unique sequence of actor names given by recursively following the supervision +links between child and parent down towards the root of the actor system. This +sequence can be seen as enclosing folders in a file system, hence we adopted +the name “path” to refer to it. As in some real file-systems there also are +“symbolic links”, i.e. one actor may be reachable using more than one path, +where all but one involve some translation which decouples part of the path +from the actor’s actual supervision ancestor line; these specialities are +described in the sub-sections to follow. Each actor path has an address component, describing the protocol and location by which the corresponding actor is reachable, followed by the names of the actors in the hierarchy from the root up. Examples are:: - "jvm://my-system/app/service-a/worker1" - "akka://serv.example.com:5678/app/service-b" + "akka://my-system/app/service-a/worker1" // purely local + "akka://my-system@serv.example.com:5678/app/service-b" // local or remote + "cluster://my-cluster/service-c" // clustered (Future Extension) -where the first represents a purely local actor reference while the second -stands for a remote actor reference. Each actor has access to its path, and you -may construct paths and pass them to :meth:`ActorSystem.actorFor` in order to -obtain a corresponding actor reference. +Here, ``akka`` is the default remote protocol for the 2.0 release, and others +are pluggable. The interpretation of the host & port part (i.e. +``serv.example.com:5678`` in the example) depends on the transport mechanism +used, but it should abide by the URI structural rules. -One important aspect is that actor paths never span multiple actor systems or -JVMs. An actor path always represents the physical location of an actor. This -means that the supervision hierarchy and the path hierarchy of an actor may -diverge if one of its ancestors is remotely supervised. +Logical Actor Paths +^^^^^^^^^^^^^^^^^^^ + +The unique path obtained by following the parental supervision links towards +the root guardian is called the logical actor path. This path matches exactly +the creation ancestry of an actor, so it is completely deterministic as soon as +the actor system’s remoting configuration (and with it the address component of +the path) is set. + +Physical Actor Paths +^^^^^^^^^^^^^^^^^^^^ + +While the logical actor path describes the functional location within one actor +system, configuration-based transparent remoting means that an actor may be +created on a different network host as its parent, i.e. within a different +actor system. In this case, following the actor path from the root guardian up +entails traversing the network, which is a costly operation. Therefore, each +actor also has a physical path, starting at the root guardian of the actor +system where the actual actor object resides. Using this path as sender +reference when querying other actors will let them reply directly to this +actor, minimizing delays incurred by routing. + +One important aspect is that a physical actor path never spans multiple actor +systems or JVMs. This means that the logical path (supervision hierarchy) and +the physical path (actor deployment) of an actor may diverge if one of its +ancestors is remotely supervised. + +Virtual Actor Paths **(Future Extension)** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In order to be able to replicate and migrate actors across a cluster of Akka +nodes, another level of indirection has to be introduced. The cluster component +therefore provides a translation from virtual paths to physical paths which may +change in reaction to node failures, cluster rebalancing, etc. + +*This area is still under active development, expect updates in this section +for the 2.1 release.* + +How are Actor References obtained? +---------------------------------- + +There are two general categories to how actor references may be obtained: by +creating actors or by looking them up, where the latter functionality comes in +the two flavours of creating actor references from concrete actor paths and +querying the logical actor hierarchy. + +*While local and remote actor references and their paths work in the same way +concerning the facilities mentioned below, the exact semantics of clustered +actor references and their paths—while certainly as similar as possible—may +differ in certain aspects, owing to the virtual nature of those paths. Expect +updates for the 2.1 release.* + +Creating Actors +^^^^^^^^^^^^^^^ + +An actor system is typically started by creating actors above the guardian +actor using the :meth:`ActorSystem.actorOf` method and then using +:meth:`ActorContext.actorOf` from within the created actors to spawn the actor +tree. These methods return a reference to the newly created actors. Each actor +has direct access to references for its parent, itself and its children. These +references may be sent within messages to other actors, enabling those to reply +directly. + +Looking up Actors by Concrete Path +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In addition, actor references may be looked up using the +:meth:`ActorSystem.actorFor` method, which returns an (unverified) local, +remote or clustered actor reference. Sending messages to such a reference or +attempting to observe its livelyhood will traverse the actor hierarchy of the +actor system from top to bottom by passing messages from parent to child until +either the target is reached or failure is certain, i.e. a name in the path +does not exist (in practice this process will be optimized using caches, but it +still has added cost compared to using the physical actor path, can for example +to obtained from the sender reference included in replies from that actor). The +messages passed are handled automatically by Akka, so this process is not +visible to client code. + +Absolute vs. Relative Paths +``````````````````````````` + +In addition to :meth:`ActorSystem.actorFor` there is also +:meth:`ActorContext.actorFor`, which is available inside any actor as +``context.actorFor``. This yields an actor reference much like its twin on +:class:`ActorSystem`, but instead of looking up the path starting from the root +of the actor tree it starts out on the current actor. Path elements consisting +of two dots (``".."``) may be used to access the parent actor. You can for +example send a message to a specific sibling:: + + context.actorFor("../brother") ! msg + +Querying the Logical Actor Hierarchy +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Since the actor system forms a file-system like hierarchy, matching on paths is +possible in the same was as supported by Unix shells: you may replace (parts +of) path element names with wildcards (`"*"` and `"?"`) to formulate a +selection which may match zero or more actual actors. Because the result is not +a single actor reference, it has a different type :class:`ActorSelection` and +does not support the full set of operations an :class:`ActorRef` does. +Selections may be formulated using the :meth:`ActorSystem.actorSelection` and +:meth:`ActorContext.actorSelection` methods and do support sending messages:: + + context.actorSelection("../*") ! msg + +will send `msg` to all siblings including the current actor. As for references +obtained using `actorFor`, a traversal of the supervision hierarchy is done in +order to perform the message send. As the exact set of actors which match a +selection may change even while a message is making its way to the recipients, +it is not possible to watch a selection for liveliness changes. In order to do +that, resolve the uncertainty by sending a request and gathering all answers, +extracting the sender references, and then watch all discovered concrete +actors. This scheme of resolving a selection may be improved upon in a future +release. The Interplay with Remote Deployment ------------------------------------ @@ -113,6 +213,21 @@ looking up the child’s name within the supervisor will find it on the remote node, preserving logical structure e.g. when sending to an unresolved actor reference. +The Interplay with Clustering **(Future Extension)** +---------------------------------------------------- + +*This section is subject to change!* + +When creating a scaled-out actor subtree, a cluster name is created for a +routed actor reference, where sending to this reference will send to one (or +more) of the actual actors created in the cluster. In order for those actors to +be able to query other actors while processing their messages, their sender +reference must be unique for each of the replicas, which means that physical +paths will be used as ``self`` references for these instances. In the case +of replication for achieving fault-tolerance the opposite is required: the +``self`` reference will be a virtual (cluster) path so that in case of +migration or fail-over communication is resumed with the fresh instance. + What is the Address part used for? ---------------------------------- @@ -136,7 +251,7 @@ other actors are found. The next level consists of the following: - ``"/sys"`` is the guardian actor for all system-created top-level actors, e.g. logging listeners or actors automatically deployed by configuration at the start of the actor system. -- ``"/nul"`` is the dead letter actor, which is where all messages sent to +- ``"/nil"`` is the dead letter actor, which is where all messages sent to stopped or non-existing actors are re-routed. - ``"/tmp"`` is the guardian for all short-lived system-created actors, e.g. those which are used in the implementation of :meth:`ActorRef.ask`. From dad1c98c48a81264d8043cbbb7f338c282732e15 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 24 Nov 2011 16:58:23 +0100 Subject: [PATCH 03/30] first step: sanitize ActorPath interface - remove references to ActorSystem - make ChildActorPath light-weight (name & parent plus tailrec methods for traversing towards root, e.g. toString) - string rep is full URI always (akka://system-name@host:port/user/bla) --- .../test/scala/akka/actor/ActorRefSpec.scala | 4 +- .../src/main/scala/akka/actor/ActorPath.scala | 72 +++++++++++-------- .../src/main/scala/akka/actor/ActorRef.scala | 4 +- .../scala/akka/actor/ActorRefProvider.scala | 4 +- .../src/main/scala/akka/actor/Address.scala | 21 ++++++ .../scala/akka/remote/RemoteInterface.scala | 6 +- .../akka/actor/mailbox/DurableMailbox.scala | 2 +- .../src/main/scala/akka/remote/Gossiper.scala | 2 +- .../src/main/scala/akka/remote/Remote.scala | 17 +++-- .../akka/remote/RemoteActorRefProvider.scala | 6 +- .../remote/netty/NettyRemoteSupport.scala | 12 ++-- 11 files changed, 93 insertions(+), 57 deletions(-) create mode 100644 akka-actor/src/main/scala/akka/actor/Address.scala diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala index 1358e61c82..938204b993 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala @@ -284,8 +284,8 @@ class ActorRefSpec extends AkkaSpec { val baos = new ByteArrayOutputStream(8192 * 32) val out = new ObjectOutputStream(baos) - val addr = system.asInstanceOf[ActorSystemImpl].provider.rootPath.remoteAddress - val serialized = SerializedActorRef(addr.hostname, addr.port, "/this/path/does/not/exist") + val addr = system.asInstanceOf[ActorSystemImpl].provider.rootPath.address + val serialized = SerializedActorRef(addr.hostPort, 0, "/this/path/does/not/exist") out.writeObject(serialized) diff --git a/akka-actor/src/main/scala/akka/actor/ActorPath.scala b/akka-actor/src/main/scala/akka/actor/ActorPath.scala index a741705e4f..11e735218a 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorPath.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorPath.scala @@ -1,10 +1,8 @@ /** * Copyright (C) 2009-2011 Typesafe Inc. */ - package akka.actor - -import akka.remote.RemoteAddress +import scala.annotation.tailrec object ActorPath { final val separator = "/" @@ -60,9 +58,10 @@ object ActorPath { */ trait ActorPath { /** - * The RemoteAddress for this path. + * The Address under which this path can be reached; walks up the tree to + * the RootActorPath. */ - def remoteAddress: RemoteAddress + def address: Address = root.address /** * The name of the actor that this path refers to. @@ -84,49 +83,64 @@ trait ActorPath { */ def /(child: Iterable[String]): ActorPath = (this /: child)(_ / _) - /** - * String representation of this path. Different from toString for root path. - */ - def string: String - /** * Sequence of names for this path. */ - def path: Iterable[String] + def pathElements: Iterable[String] /** - * Is this the root path? + * Walk up the tree to obtain and return the RootActorPath. */ - def isRoot: Boolean + def root: RootActorPath } -class RootActorPath(val remoteAddress: RemoteAddress) extends ActorPath { - - def name: String = "/" +/** + * Root of the hierarchy of ActorPaths. There is exactly root per ActorSystem + * and node (for remote-enabled or clustered systems). + */ +class RootActorPath(override val address: Address, val name: String = ActorPath.separator) extends ActorPath { def parent: ActorPath = this - def /(child: String): ActorPath = new ChildActorPath(remoteAddress, this, child) + def root: RootActorPath = this - def string: String = "" + def /(child: String): ActorPath = new ChildActorPath(this, child) - def path: Iterable[String] = Iterable.empty + def pathElements: Iterable[String] = Iterable.empty - def isRoot: Boolean = true - - override def toString = ActorPath.separator + override val toString = address + ActorPath.separator } -class ChildActorPath(val remoteAddress: RemoteAddress, val parent: ActorPath, val name: String) extends ActorPath { +class ChildActorPath(val parent: ActorPath, val name: String) extends ActorPath { - def /(child: String): ActorPath = new ChildActorPath(remoteAddress, this, child) + def /(child: String): ActorPath = new ChildActorPath(this, child) - def string: String = parent.string + ActorPath.separator + name + def pathElements: Iterable[String] = { + @tailrec + def rec(p: ActorPath, acc: List[String]): Iterable[String] = p match { + case r: RootActorPath ⇒ acc + case _ ⇒ rec(p.parent, p.name :: acc) + } + rec(this, Nil) + } - def path: Iterable[String] = parent.path ++ Iterable(name) + def root = { + @tailrec + def rec(p: ActorPath): RootActorPath = p match { + case r: RootActorPath ⇒ r + case _ ⇒ rec(p.parent) + } + rec(this) + } - def isRoot: Boolean = false - - override def toString = string + override def toString = { + @tailrec + def rec(p: ActorPath, s: String): String = p match { + case r: RootActorPath ⇒ r + s + case _ if s.isEmpty ⇒ rec(p.parent, name) + case _ ⇒ rec(p.parent, p.name + ActorPath.separator + s) + } + rec(this, "") + } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 35d36e3667..101c69a81b 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -303,10 +303,12 @@ trait ScalaActorRef { ref: ActorRef ⇒ /** * Memento pattern for serializing ActorRefs transparently */ - +// FIXME: remove and replace by ActorPath.toString case class SerializedActorRef(hostname: String, port: Int, path: String) { import akka.serialization.Serialization.system + // FIXME this is broken, but see above + def this(address: Address, path: String) = this(address.hostPort, 0, path) def this(remoteAddress: RemoteAddress, path: String) = this(remoteAddress.hostname, remoteAddress.port, path) def this(remoteAddress: InetSocketAddress, path: String) = this(remoteAddress.getAddress.getHostAddress, remoteAddress.getPort, path) //TODO FIXME REMOVE diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 28fc383adf..4ebf29a835 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -130,7 +130,7 @@ trait ActorRefFactory { def actorOf(creator: UntypedActorFactory): ActorRef = actorOf(Props(() ⇒ creator.create())) - def actorFor(path: ActorPath): Option[ActorRef] = actorFor(path.path) + def actorFor(path: ActorPath): Option[ActorRef] = actorFor(path.pathElements) def actorFor(path: String): Option[ActorRef] = actorFor(ActorPath.split(path)) @@ -357,7 +357,7 @@ class LocalActorRefProvider( } private[akka] def deserialize(actor: SerializedActorRef): Option[ActorRef] = actorFor(ActorPath.split(actor.path)) - private[akka] def serialize(actor: ActorRef): SerializedActorRef = new SerializedActorRef(rootPath.remoteAddress, actor.path.toString) + private[akka] def serialize(actor: ActorRef): SerializedActorRef = new SerializedActorRef(rootPath.address, actor.path.toString) private[akka] def createDeathWatch(): DeathWatch = new LocalDeathWatch diff --git a/akka-actor/src/main/scala/akka/actor/Address.scala b/akka-actor/src/main/scala/akka/actor/Address.scala new file mode 100644 index 0000000000..c405702f6e --- /dev/null +++ b/akka-actor/src/main/scala/akka/actor/Address.scala @@ -0,0 +1,21 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.actor + +/** + * The address specifies the physical location under which an Actor can be + * reached. Examples are local addresses, identified by the ActorSystem’s + * name, and remote addresses, identified by protocol, host and port. + */ +abstract class Address { + def protocol: String + def hostPort: String + @transient + override lazy val toString = protocol + "://" + hostPort +} + +case class LocalAddress(systemName: String) extends Address { + def protocol = "akka" + def hostPort = systemName +} \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala index 7d33508f46..4acf0e37c7 100644 --- a/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala @@ -28,10 +28,10 @@ object RemoteAddress { object LocalOnly extends RemoteAddress(0, "local") -case class RemoteAddress private[akka] (port: Int, hostname: String) { +case class RemoteAddress private[akka] (port: Int, hostname: String) extends Address { + def protocol = "akka" @transient - override lazy val toString = "" + hostname + ":" + port - + lazy val hostPort = hostname + ":" + port } class RemoteException(message: String) extends AkkaException(message) diff --git a/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableMailbox.scala b/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableMailbox.scala index b5d832d443..6ace3d84aa 100644 --- a/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableMailbox.scala +++ b/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableMailbox.scala @@ -41,7 +41,7 @@ abstract class DurableMailbox(owner: ActorCell) extends Mailbox(owner) with Defa def system = owner.system def ownerPath = owner.self.path - val ownerPathString = ownerPath.path.mkString("/") + val ownerPathString = ownerPath.pathElements.mkString("/") val name = "mailbox_" + Name.replaceAllIn(ownerPathString, "_") } diff --git a/akka-remote/src/main/scala/akka/remote/Gossiper.scala b/akka-remote/src/main/scala/akka/remote/Gossiper.scala index 3735f6ceaf..755fc08df0 100644 --- a/akka-remote/src/main/scala/akka/remote/Gossiper.scala +++ b/akka-remote/src/main/scala/akka/remote/Gossiper.scala @@ -108,7 +108,7 @@ class Gossiper(remote: Remote) { private val connectionManager = new RemoteConnectionManager(system, remote, Map.empty[RemoteAddress, ActorRef]) private val seeds = Set(address) // FIXME read in list of seeds from config - private val address = system.asInstanceOf[ActorSystemImpl].provider.rootPath.remoteAddress + private val address = remote.remoteAddress private val nodeFingerprint = address.## private val random = SecureRandom.getInstance("SHA1PRNG") diff --git a/akka-remote/src/main/scala/akka/remote/Remote.scala b/akka-remote/src/main/scala/akka/remote/Remote.scala index 123304c314..ecdc5d39a1 100644 --- a/akka-remote/src/main/scala/akka/remote/Remote.scala +++ b/akka-remote/src/main/scala/akka/remote/Remote.scala @@ -71,22 +71,21 @@ class Remote(val system: ActorSystemImpl, val nodename: String) { lazy val eventStream = new NetworkEventStream(system) - lazy val server: RemoteSupport = { - val remote = new akka.remote.netty.NettyRemoteSupport(system) + @volatile + private var _server: RemoteSupport = _ + def server = _server + + def start(): Unit = { + val remote = new akka.remote.netty.NettyRemoteSupport(system, this) remote.start() //TODO FIXME Any application loader here? system.eventStream.subscribe(eventStream.sender, classOf[RemoteLifeCycleEvent]) system.eventStream.subscribe(remoteClientLifeCycleHandler, classOf[RemoteLifeCycleEvent]) - // TODO actually register this provider in system in remote mode - //provider.register(ActorRefProvider.RemoteProvider, new RemoteActorRefProvider) - remote - } + _server = remote - def start(): Unit = { - val serverAddress = server.system.asInstanceOf[ActorSystemImpl].provider.rootPath.remoteAddress //Force init of server val daemonAddress = remoteDaemon.address //Force init of daemon - log.info("Starting remote server on [{}] and starting remoteDaemon with address [{}]", serverAddress, daemonAddress) + log.info("Starting remote server on [{}] and starting remoteDaemon with address [{}]", remoteAddress, daemonAddress) } } diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index 0e676546e6..a34f975d57 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -102,7 +102,7 @@ class RemoteActorRefProvider( // case FailureDetectorType.Custom(implClass) ⇒ FailureDetector.createCustomFailureDetector(implClass) // } - def isReplicaNode: Boolean = remoteAddresses exists { _ == rootPath.remoteAddress } + def isReplicaNode: Boolean = remoteAddresses exists { _ == remote.remoteAddress } //system.eventHandler.debug(this, "%s: Deploy Remote Actor with address [%s] connected to [%s]: isReplica(%s)".format(system.defaultAddress, address, remoteAddresses.mkString, isReplicaNode)) @@ -204,10 +204,10 @@ class RemoteActorRefProvider( private[akka] def deserialize(actor: SerializedActorRef): Option[ActorRef] = { val remoteAddress = RemoteAddress(actor.hostname, actor.port) - if (optimizeLocalScoped_? && remoteAddress == rootPath.remoteAddress) { + if (optimizeLocalScoped_? && remoteAddress == remote.remoteAddress) { local.actorFor(ActorPath.split(actor.path)) } else { - log.debug("{}: Creating RemoteActorRef with address [{}] connected to [{}]", rootPath.remoteAddress, actor.path, remoteAddress) + log.debug("{}: Creating RemoteActorRef with address [{}] connected to [{}]", remote.remoteAddress, actor.path, remoteAddress) Some(RemoteActorRef(remote.system.provider, remote.server, remoteAddress, rootPath / ActorPath.split(actor.path), None)) //Should it be None here } } 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 eedf58fd9e..497976cccf 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -95,8 +95,8 @@ abstract class RemoteClient private[akka] ( } class PassiveRemoteClient(val currentChannel: Channel, - remoteSupport: NettyRemoteSupport, - remoteAddress: RemoteAddress) + remoteSupport: NettyRemoteSupport, + remoteAddress: RemoteAddress) extends RemoteClient(remoteSupport, remoteAddress) { def connect(reconnectIfAlreadyConnected: Boolean = false): Boolean = runSwitch switchOn { @@ -141,7 +141,7 @@ class ActiveRemoteClient private[akka] ( def currentChannel = connection.getChannel - private val senderRemoteAddress = remoteSupport.system.asInstanceOf[ActorSystemImpl].provider.rootPath.remoteAddress + private val senderRemoteAddress = remoteSupport.remote.remoteAddress /** * Connect to remote server. @@ -355,7 +355,7 @@ class ActiveRemoteClientHandler( /** * Provides the implementation of the Netty remote support */ -class NettyRemoteSupport(_system: ActorSystem) extends RemoteSupport(_system) with RemoteMarshallingOps { +class NettyRemoteSupport(_system: ActorSystem, val remote: Remote) extends RemoteSupport(_system) with RemoteMarshallingOps { val log = Logging(system, "NettyRemoteSupport") val serverSettings = RemoteExtension(system).settings.serverSettings @@ -458,7 +458,7 @@ class NettyRemoteSupport(_system: ActorSystem) extends RemoteSupport(_system) wi def name = currentServer.get match { case Some(server) ⇒ server.name - case None ⇒ "Non-running NettyRemoteServer@" + system.asInstanceOf[ActorSystemImpl].provider.rootPath.remoteAddress + case None ⇒ "Non-running NettyRemoteServer@" + remote.remoteAddress } private val _isRunning = new Switch(false) @@ -493,7 +493,7 @@ class NettyRemoteServer(val remoteSupport: NettyRemoteSupport, val loader: Optio val log = Logging(remoteSupport.system, "NettyRemoteServer") import remoteSupport.serverSettings._ - val address = remoteSupport.system.asInstanceOf[ActorSystemImpl].provider.rootPath.remoteAddress + val address = remoteSupport.remote.remoteAddress val name = "NettyRemoteServer@" + address From 3182fa3d73a8a488148bd886ca166f2f7b07a5c0 Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 29 Nov 2011 16:32:50 +0100 Subject: [PATCH 04/30] second step: remove LocalActorRefProvider.actors - duplicate name detection done within ActorCell/ActorSystem (i.e. at parent level) - no caching needed for local look-ups, might re-introduce cache in remote layer - implement suitable equals/hashCode on ActorPaths - fix some (unintended => buggy) name reuses which previously silently returned a different actor - serialization & EventStreamSpec still failing, need to commit to merge in other stuff on which the future fixes will depend --- .../test/scala/akka/actor/DeployerSpec.scala | 2 +- .../actor/LocalActorRefProviderSpec.scala | 16 +- .../src/main/scala/akka/actor/Actor.scala | 2 + .../src/main/scala/akka/actor/ActorCell.scala | 16 +- .../src/main/scala/akka/actor/ActorPath.scala | 91 ++++---- .../src/main/scala/akka/actor/ActorRef.scala | 15 +- .../scala/akka/actor/ActorRefProvider.scala | 209 +++++++++--------- .../main/scala/akka/actor/ActorSystem.scala | 77 +++++-- .../src/main/scala/akka/actor/Address.scala | 17 ++ .../src/main/scala/akka/actor/Deployer.scala | 2 +- .../main/scala/akka/event/EventStream.scala | 8 +- .../scala/akka/remote/RemoteInterface.scala | 40 ++-- .../akka/remote/NetworkEventStream.scala | 6 +- .../src/main/scala/akka/remote/Remote.scala | 22 +- .../akka/remote/RemoteActorRefProvider.scala | 41 ++-- .../remote/netty/NettyRemoteSupport.scala | 13 +- .../remote/AccrualFailureDetectorSpec.scala | 2 +- 17 files changed, 329 insertions(+), 250 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala index 68a81d9797..b6dae1a6de 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala @@ -120,7 +120,7 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { RoundRobin, NrOfInstances(3), RemoteScope(Seq( - RemoteAddress("wallace", 2552), RemoteAddress("gromit", 2552)))))) + RemoteAddress(system.name, "wallace", 2552), RemoteAddress(system.name, "gromit", 2552)))))) } "be able to parse 'akka.actor.deployment._' with recipe" in { diff --git a/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala index 707c425295..de59894431 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala @@ -12,16 +12,28 @@ import akka.dispatch.Future class LocalActorRefProviderSpec extends AkkaSpec { "An LocalActorRefProvider" must { + "find actor refs using actorFor" in { + val a = actorOf(Props(ctx ⇒ { case _ ⇒ })) + val b = system.actorFor(a.path) + a must be === b + } + "only create one instance of an actor with a specific address in a concurrent environment" in { val impl = system.asInstanceOf[ActorSystemImpl] val provider = impl.provider provider.isInstanceOf[LocalActorRefProvider] must be(true) - (0 until 100) foreach { i ⇒ // 100 concurrent runs + for (i ← 0 until 100) { val address = "new-actor" + i implicit val timeout = Timeout(5 seconds) - ((1 to 4) map { _ ⇒ Future { provider.actorOf(impl, Props(c ⇒ { case _ ⇒ }), impl.guardian, address) } }).map(_.get).distinct.size must be(1) + val actors = for (j ← 1 to 4) yield Future(system.actorOf(Props(c ⇒ { case _ ⇒ }), address)) + val set = Set() ++ actors.map(_.await.value match { + case Some(Right(a: ActorRef)) ⇒ 1 + case Some(Left(ex: InvalidActorNameException)) ⇒ 2 + case x ⇒ x + }) + set must be === Set(1, 2) } } } diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index b8c0bbb327..efaedcde49 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -77,6 +77,8 @@ class ActorKilledException private[akka] (message: String, cause: Throwable) def this(msg: String) = this(msg, null); } +case class InvalidActorNameException(message: String) extends AkkaException(message) + case class ActorInitializationException private[akka] (actor: ActorRef, message: String, cause: Throwable = null) extends AkkaException(message, cause) with NoStackTrace { def this(msg: String) = this(null, msg, null); diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 330824290f..8fc25d84bf 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -97,6 +97,10 @@ private[akka] class ActorCell( var childrenRefs: TreeMap[String, ChildRestartStats] = emptyChildrenRefs + protected def isDuplicate(name: String): Boolean = { + childrenRefs contains name + } + var currentMessage: Envelope = null var actor: Actor = _ @@ -152,8 +156,13 @@ private[akka] class ActorCell( final def children: Iterable[ActorRef] = childrenRefs.values.view.map(_.child) - final def getChild(name: String): Option[ActorRef] = - if (isTerminated) None else childrenRefs.get(name).map(_.child) + final def getChild(name: String): ActorRef = + if (isTerminated) null + else { + val c = childrenRefs + if (c contains name) c(name).child + else null + } final def tell(message: Any, sender: ActorRef): Unit = dispatcher.dispatch(this, Envelope(message, if (sender eq null) system.deadLetters else sender)) @@ -360,9 +369,6 @@ private[akka] class ActorCell( } private def doTerminate() { - if (!system.provider.evict(self.path.toString)) - system.eventStream.publish(Warning(self.toString, "evict of " + self.path.toString + " failed")) - dispatcher.detach(this) try { diff --git a/akka-actor/src/main/scala/akka/actor/ActorPath.scala b/akka-actor/src/main/scala/akka/actor/ActorPath.scala index 11e735218a..4203c5c9b5 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorPath.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorPath.scala @@ -5,63 +5,20 @@ package akka.actor import scala.annotation.tailrec object ActorPath { + // this cannot really be changed due to usage of standard URI syntax final val separator = "/" - - val pattern = """(/[0-9a-zA-Z\-\_\$\.]+)+""".r.pattern - - /** - * Create an actor path from a string. - */ - def apply(system: ActorSystem, path: String): ActorPath = - apply(system, split(path)) - - /** - * Create an actor path from an iterable. - */ - def apply(system: ActorSystem, path: Iterable[String]): ActorPath = - path.foldLeft(system.asInstanceOf[ActorSystemImpl].provider.rootPath)(_ / _) - - /** - * Split a string path into an iterable. - */ - def split(path: String): Iterable[String] = - if (path.startsWith(separator)) - path.substring(1).split(separator) - else - path.split(separator) - - /** - * Join an iterable path into a string. - */ - def join(path: Iterable[String]): String = - path.mkString(separator, separator, "") - - /** - * Is this string representation of a path valid? - */ - def valid(path: String): Boolean = - pattern.matcher(path).matches - - /** - * Validate a path. Moved here from Address.validate. - * Throws an IllegalArgumentException if the path is invalid. - */ - def validate(path: String): Unit = { - if (!valid(path)) - throw new IllegalArgumentException("Path [" + path + "] is not valid. Needs to follow this pattern: " + pattern) - } } /** * Actor path is a unique path to an actor that shows the creation path * up through the actor tree to the root actor. */ -trait ActorPath { +sealed trait ActorPath { /** * The Address under which this path can be reached; walks up the tree to * the RootActorPath. */ - def address: Address = root.address + def address: Address /** * The name of the actor that this path refers to. @@ -84,7 +41,7 @@ trait ActorPath { def /(child: Iterable[String]): ActorPath = (this /: child)(_ / _) /** - * Sequence of names for this path. + * Sequence of names for this path. Performance implication: has to allocate a list. */ def pathElements: Iterable[String] @@ -92,13 +49,14 @@ trait ActorPath { * Walk up the tree to obtain and return the RootActorPath. */ def root: RootActorPath + } /** * Root of the hierarchy of ActorPaths. There is exactly root per ActorSystem * and node (for remote-enabled or clustered systems). */ -class RootActorPath(override val address: Address, val name: String = ActorPath.separator) extends ActorPath { +final case class RootActorPath(address: Address, name: String = ActorPath.separator) extends ActorPath { def parent: ActorPath = this @@ -108,10 +66,12 @@ class RootActorPath(override val address: Address, val name: String = ActorPath. def pathElements: Iterable[String] = Iterable.empty - override val toString = address + ActorPath.separator + override val toString = address + name } -class ChildActorPath(val parent: ActorPath, val name: String) extends ActorPath { +final class ChildActorPath(val parent: ActorPath, val name: String) extends ActorPath { + + def address: Address = root.address def /(child: String): ActorPath = new ChildActorPath(this, child) @@ -133,6 +93,12 @@ class ChildActorPath(val parent: ActorPath, val name: String) extends ActorPath rec(this) } + // TODO research whether this should be cached somehow (might be fast enough, but creates GC pressure) + /* + * idea: add one field which holds the total length (because that is known) + * so that only one String needs to be allocated before traversal; this is + * cheaper than any cache + */ override def toString = { @tailrec def rec(p: ActorPath, s: String): String = p match { @@ -142,5 +108,30 @@ class ChildActorPath(val parent: ActorPath, val name: String) extends ActorPath } rec(this, "") } + + override def equals(other: Any): Boolean = { + @tailrec + def rec(left: ActorPath, right: ActorPath): Boolean = + if (left eq right) true + else if (left.isInstanceOf[RootActorPath] || right.isInstanceOf[RootActorPath]) left == right + else left.name == right.name && rec(left.parent, right.parent) + + other match { + case p: ActorPath ⇒ rec(this, p) + case _ ⇒ false + } + } + + override def hashCode: Int = { + import scala.util.MurmurHash._ + + @tailrec + def rec(p: ActorPath, h: Int, c: Int, k: Int): Int = p match { + case r: RootActorPath ⇒ extendHash(h, r.##, c, k) + case _ ⇒ rec(p.parent, extendHash(h, stringHash(name), c, k), nextMagicA(c), nextMagicB(k)) + } + + finalizeHash(rec(this, startHash(42), startMagicA, startMagicB)) + } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 101c69a81b..fc8c6f950a 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -309,7 +309,7 @@ case class SerializedActorRef(hostname: String, port: Int, path: String) { // FIXME this is broken, but see above def this(address: Address, path: String) = this(address.hostPort, 0, path) - def this(remoteAddress: RemoteAddress, path: String) = this(remoteAddress.hostname, remoteAddress.port, path) + def this(remoteAddress: RemoteAddress, path: String) = this(remoteAddress.host, remoteAddress.port, path) def this(remoteAddress: InetSocketAddress, path: String) = this(remoteAddress.getAddress.getHostAddress, remoteAddress.getPort, path) //TODO FIXME REMOVE @throws(classOf[java.io.ObjectStreamException]) @@ -351,6 +351,15 @@ trait MinimalActorRef extends ActorRef with ScalaActorRef { protected[akka] def restart(cause: Throwable): Unit = () } +object MinimalActorRef { + def apply(_path: ActorPath)(receive: PartialFunction[Any, Unit]): ActorRef = new MinimalActorRef { + def path = _path + def address = path.toString + override def !(message: Any)(implicit sender: ActorRef = null): Unit = + if (receive.isDefinedAt(message)) receive(message) + } +} + case class DeadLetter(message: Any, sender: ActorRef, recipient: ActorRef) object DeadLetterActorRef { @@ -399,7 +408,7 @@ class DeadLetterActorRef(val eventStream: EventStream) extends MinimalActorRef { private def writeReplace(): AnyRef = DeadLetterActorRef.serialized } -abstract class AskActorRef(val path: ActorPath, provider: ActorRefProvider, deathWatch: DeathWatch, timeout: Timeout, val dispatcher: MessageDispatcher) extends MinimalActorRef { +class AskActorRef(val path: ActorPath, provider: ActorRefProvider, deathWatch: DeathWatch, timeout: Timeout, val dispatcher: MessageDispatcher) extends MinimalActorRef { final val result = new DefaultPromise[Any](timeout)(dispatcher) override def name = path.name @@ -412,7 +421,7 @@ abstract class AskActorRef(val path: ActorPath, provider: ActorRefProvider, deat result onTimeout callback } - protected def whenDone(): Unit + protected def whenDone(): Unit = {} override def !(message: Any)(implicit sender: ActorRef = null): Unit = message match { case Status.Success(r) ⇒ result.completeWithResult(r) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 4ebf29a835..63d74b9db7 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -17,21 +17,26 @@ import akka.AkkaException import com.eaio.uuid.UUID import akka.util.{ Duration, Switch, Helpers } import akka.remote.RemoteAddress -import akka.remote.LocalOnly +import org.jboss.netty.akka.util.internal.ConcurrentIdentityHashMap /** * Interface for all ActorRef providers to implement. */ trait ActorRefProvider { - def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, name: String): ActorRef = actorOf(system, props, supervisor, name, false) - - def actorFor(path: Iterable[String]): Option[ActorRef] - + /** + * Reference to the supervisor used for all top-level user actors. + */ def guardian: ActorRef + /** + * Reference to the supervisor used for all top-level system actors. + */ def systemGuardian: ActorRef + /** + * Reference to the death watch service. + */ def deathWatch: DeathWatch // FIXME: remove/replace? @@ -47,6 +52,12 @@ trait ActorRefProvider { def settings: ActorSystem.Settings + /** + * Initialization of an ActorRefProvider happens in two steps: first + * construction of the object with settings, eventStream, scheduler, etc. + * and then—when the ActorSystem is constructed—the second phase during + * which actors may be created (e.g. the guardians). + */ def init(system: ActorSystemImpl) private[akka] def deployer: Deployer @@ -54,21 +65,33 @@ trait ActorRefProvider { private[akka] def scheduler: Scheduler /** - * Create an Actor with the given name below the given supervisor. + * Actor factory with create-only semantics: will create an actor as + * described by props with the given supervisor and path (may be different + * in case of remote supervision). If systemService is true, deployment is + * bypassed (local-only). */ - private[akka] def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, name: String, systemService: Boolean): ActorRef + def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, name: String, systemService: Boolean = false): ActorRef /** - * Create an Actor with the given full path below the given supervisor. - * - * FIXME: Remove! this is dangerous! + * Create actor reference for a specified local or remote path. If no such + * actor exists, it will be (equivalent to) a dead letter reference. */ - private[akka] def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, path: ActorPath, systemService: Boolean): ActorRef + def actorFor(path: ActorPath): ActorRef /** - * Remove this path from the lookup map. + * Create actor reference for a specified local or remote path, which will + * be parsed using java.net.URI. If no such actor exists, it will be + * (equivalent to) a dead letter reference. */ - private[akka] def evict(path: String): Boolean + def actorFor(s: String): ActorRef + + /** + * Create actor reference for the specified child path starting at the root + * guardian. This method always returns an actor which is “logically local”, + * i.e. it cannot be used to obtain a reference to an actor which is not + * physically or logically attached to this actor system. + */ + def actorFor(p: Iterable[String]): ActorRef private[akka] def deserialize(actor: SerializedActorRef): Option[ActorRef] @@ -106,16 +129,20 @@ trait ActorRefFactory { protected def randomName(): String + /** + * Child names must be unique within the context of one parent; implement + * this method to have the default implementation of actorOf perform the + * check (and throw an exception if necessary). + */ + protected def isDuplicate(name: String): Boolean + def actorOf(props: Props): ActorRef = provider.actorOf(systemImpl, props, guardian, randomName(), false) - /* - * TODO this will have to go at some point, because creating two actors with - * the same address can race on the cluster, and then you never know which - * implementation wins - */ def actorOf(props: Props, name: String): ActorRef = { if (name == null || name == "" || name.startsWith("$")) - throw new ActorInitializationException("actor name must not be null, empty or start with $") + throw new InvalidActorNameException("actor name must not be null, empty or start with $") + if (isDuplicate(name)) + throw new InvalidActorNameException("actor name " + name + " is not unique!") provider.actorOf(systemImpl, props, guardian, name, false) } @@ -130,11 +157,13 @@ trait ActorRefFactory { def actorOf(creator: UntypedActorFactory): ActorRef = actorOf(Props(() ⇒ creator.create())) - def actorFor(path: ActorPath): Option[ActorRef] = actorFor(path.pathElements) + def actorOf(creator: UntypedActorFactory, name: String): ActorRef = actorOf(Props(() ⇒ creator.create()), name) - def actorFor(path: String): Option[ActorRef] = actorFor(ActorPath.split(path)) + def actorFor(path: ActorPath) = provider.actorFor(path) - def actorFor(path: Iterable[String]): Option[ActorRef] = provider.actorFor(path) + def actorFor(path: String): ActorRef = provider.actorFor(path) + + def actorFor(path: Iterable[String]): ActorRef = provider.actorFor(path) } class ActorRefProviderException(message: String) extends AkkaException(message) @@ -143,16 +172,17 @@ class ActorRefProviderException(message: String) extends AkkaException(message) * Local ActorRef provider. */ class LocalActorRefProvider( + _systemName: String, val settings: ActorSystem.Settings, val eventStream: EventStream, val scheduler: Scheduler, - val rootPath: ActorPath, - val nodename: String, - val clustername: String) extends ActorRefProvider { + val deadLetters: ActorRef) extends ActorRefProvider { - def this(settings: ActorSystem.Settings, eventStream: EventStream, scheduler: Scheduler) { - this(settings, eventStream, scheduler, new RootActorPath(LocalOnly), "local", "local") - } + val rootPath: ActorPath = new RootActorPath(LocalAddress(_systemName)) + + // FIXME remove both + val nodename: String = "local" + val clustername: String = "local" val log = Logging(eventStream, "LocalActorRefProvider") @@ -162,14 +192,10 @@ class LocalActorRefProvider( * generate name for temporary actor refs */ private val tempNumber = new AtomicLong - def tempName = "$_" + Helpers.base64(tempNumber.getAndIncrement()) + private def tempName = "$_" + Helpers.base64(tempNumber.getAndIncrement()) private val tempNode = rootPath / "tmp" def tempPath = tempNode / tempName - // FIXME (actor path): this could become a cache for the new tree traversal actorFor - // currently still used for tmp actors (e.g. ask actor refs) - private val actors = new ConcurrentHashMap[String, AnyRef] - /** * Top-level anchor for the supervision hierarchy of this actor system. Will * receive only Supervise/ChildTerminated system messages or Failure message. @@ -240,7 +266,7 @@ class LocalActorRefProvider( private var system: ActorSystemImpl = _ def dispatcher: MessageDispatcher = system.dispatcher lazy val terminationFuture: DefaultPromise[Unit] = new DefaultPromise[Unit](Timeout.never)(dispatcher) - lazy val rootGuardian: ActorRef = actorOf(system, guardianProps, theOneWhoWalksTheBubblesOfSpaceTime, rootPath, true) + lazy val rootGuardian: ActorRef = new LocalActorRef(system, guardianProps, theOneWhoWalksTheBubblesOfSpaceTime, rootPath, true) lazy val guardian: ActorRef = actorOf(system, guardianProps, rootGuardian, "app", true) lazy val systemGuardian: ActorRef = actorOf(system, guardianProps.withCreator(new SystemGuardian), rootGuardian, "sys", true) @@ -253,88 +279,58 @@ class LocalActorRefProvider( deathWatch.subscribe(rootGuardian, systemGuardian) } - // FIXME (actor path): should start at the new root guardian, and not use the tail (just to avoid the expected "system" name for now) - def actorFor(path: Iterable[String]): Option[ActorRef] = findInCache(ActorPath.join(path)) orElse findInTree(Some(guardian), path.tail) + def actorFor(path: String): ActorRef = path match { + case LocalActorPath(address, elems) if address == rootPath.address ⇒ + findInTree(rootGuardian.asInstanceOf[LocalActorRef], elems) + case _ ⇒ deadLetters + } + + def actorFor(path: ActorPath): ActorRef = findInTree(rootGuardian.asInstanceOf[LocalActorRef], path.pathElements) + + def actorFor(path: Iterable[String]): ActorRef = findInTree(rootGuardian.asInstanceOf[LocalActorRef], path) @tailrec - private def findInTree(start: Option[ActorRef], path: Iterable[String]): Option[ActorRef] = { + private def findInTree(start: LocalActorRef, path: Iterable[String]): ActorRef = { if (path.isEmpty) start - else { - val child = start match { - case Some(local: LocalActorRef) ⇒ local.underlying.getChild(path.head) - case _ ⇒ None - } - findInTree(child, path.tail) + else start.underlying.getChild(path.head) match { + case null ⇒ deadLetters + case child: LocalActorRef ⇒ findInTree(child, path.tail) + case _ ⇒ deadLetters } } - private def findInCache(path: String): Option[ActorRef] = actors.get(path) match { - case null ⇒ None - case actor: ActorRef ⇒ Some(actor) - case future: Future[_] ⇒ Some(future.get.asInstanceOf[ActorRef]) - } + def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, name: String, systemService: Boolean): ActorRef = { + val path = supervisor.path / name + (if (systemService) None else deployer.lookupDeployment(path.toString)) match { - /** - * Returns true if the actor was in the provider's cache and evicted successfully, else false. - */ - private[akka] def evict(path: String): Boolean = actors.remove(path) ne null + // create a local actor + case None | Some(DeploymentConfig.Deploy(_, _, DeploymentConfig.Direct, _, DeploymentConfig.LocalScope)) ⇒ + new LocalActorRef(system, props, supervisor, path, systemService) // create a local actor - private[akka] def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, name: String, systemService: Boolean): ActorRef = - actorOf(system, props, supervisor, supervisor.path / name, systemService) + // create a routed actor ref + case deploy @ Some(DeploymentConfig.Deploy(_, _, routerType, nrOfInstances, DeploymentConfig.LocalScope)) ⇒ - private[akka] def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, path: ActorPath, systemService: Boolean): ActorRef = { - val name = path.name - val newFuture = Promise[ActorRef](5000)(dispatcher) // FIXME is this proper timeout? - - actors.putIfAbsent(path.toString, newFuture) match { - case null ⇒ - val actor: ActorRef = try { - (if (systemService) None else deployer.lookupDeployment(path.toString)) match { // see if the deployment already exists, if so use it, if not create actor - - // create a local actor - case None | Some(DeploymentConfig.Deploy(_, _, DeploymentConfig.Direct, _, DeploymentConfig.LocalScope)) ⇒ - new LocalActorRef(system, props, supervisor, path, systemService) // create a local actor - - // create a routed actor ref - case deploy @ Some(DeploymentConfig.Deploy(_, _, routerType, nrOfInstances, DeploymentConfig.LocalScope)) ⇒ - - val routerFactory: () ⇒ Router = DeploymentConfig.routerTypeFor(routerType) match { - case RouterType.Direct ⇒ () ⇒ new DirectRouter - case RouterType.Random ⇒ () ⇒ new RandomRouter - case RouterType.RoundRobin ⇒ () ⇒ new RoundRobinRouter - case RouterType.ScatterGather ⇒ () ⇒ new ScatterGatherFirstCompletedRouter()( - if (props.dispatcher == Props.defaultDispatcher) dispatcher else props.dispatcher, settings.ActorTimeout) - case RouterType.LeastCPU ⇒ sys.error("Router LeastCPU not supported yet") - case RouterType.LeastRAM ⇒ sys.error("Router LeastRAM not supported yet") - case RouterType.LeastMessages ⇒ sys.error("Router LeastMessages not supported yet") - case RouterType.Custom(implClass) ⇒ () ⇒ Routing.createCustomRouter(implClass) - } - - val connections: Iterable[ActorRef] = (1 to nrOfInstances.factor) map { i ⇒ - val routedPath = path.parent / (path.name + ":" + i) - new LocalActorRef(system, props, supervisor, routedPath, systemService) - } - - actorOf(system, RoutedProps(routerFactory = routerFactory, connectionManager = new LocalConnectionManager(connections)), supervisor, path.toString) - - case unknown ⇒ throw new Exception("Don't know how to create this actor ref! Why? Got: " + unknown) - } - } catch { - case e: Exception ⇒ - newFuture completeWithException e // so the other threads gets notified of error - //TODO FIXME should we remove the mapping in "actors" here? - throw e + val routerFactory: () ⇒ Router = DeploymentConfig.routerTypeFor(routerType) match { + case RouterType.Direct ⇒ () ⇒ new DirectRouter + case RouterType.Random ⇒ () ⇒ new RandomRouter + case RouterType.RoundRobin ⇒ () ⇒ new RoundRobinRouter + case RouterType.ScatterGather ⇒ () ⇒ new ScatterGatherFirstCompletedRouter()( + if (props.dispatcher == Props.defaultDispatcher) dispatcher else props.dispatcher, settings.ActorTimeout) + case RouterType.LeastCPU ⇒ sys.error("Router LeastCPU not supported yet") + case RouterType.LeastRAM ⇒ sys.error("Router LeastRAM not supported yet") + case RouterType.LeastMessages ⇒ sys.error("Router LeastMessages not supported yet") + case RouterType.Custom(implClass) ⇒ () ⇒ Routing.createCustomRouter(implClass) } - newFuture completeWithResult actor - actors.replace(path.toString, newFuture, actor) - actor - case actor: ActorRef ⇒ - actor - case future: Future[_] ⇒ - future.get.asInstanceOf[ActorRef] - } + val connections: Iterable[ActorRef] = (1 to nrOfInstances.factor) map { i ⇒ + val routedPath = path.parent / (path.name + ":" + i) + new LocalActorRef(system, props, supervisor, routedPath, systemService) + } + actorOf(system, RoutedProps(routerFactory = routerFactory, connectionManager = new LocalConnectionManager(connections)), supervisor, path.toString) + + case unknown ⇒ throw new Exception("Don't know how to create this actor ref! Why? Got: " + unknown) + } } /** @@ -356,7 +352,7 @@ class LocalActorRefProvider( new RoutedActorRef(system, props, supervisor, name) } - private[akka] def deserialize(actor: SerializedActorRef): Option[ActorRef] = actorFor(ActorPath.split(actor.path)) + private[akka] def deserialize(actor: SerializedActorRef): Option[ActorRef] = Some(actorFor(actor.path)) private[akka] def serialize(actor: ActorRef): SerializedActorRef = new SerializedActorRef(rootPath.address, actor.path.toString) private[akka] def createDeathWatch(): DeathWatch = new LocalDeathWatch @@ -367,8 +363,7 @@ class LocalActorRefProvider( case t if t.duration.length <= 0 ⇒ new DefaultPromise[Any](0)(dispatcher) //Abort early if nonsensical timeout case t ⇒ - val a = new AskActorRef(tempPath, this, deathWatch, t, dispatcher) { def whenDone() = actors.remove(this) } - assert(actors.putIfAbsent(a.path.toString, a) eq null) //If this fails, we're in deep trouble + val a = new AskActorRef(tempPath, this, deathWatch, t, dispatcher) recipient.tell(message, a) a.result } diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 1bf1ea2bd1..1aec26cfc2 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -55,7 +55,7 @@ object ActorSystem { def create(): ActorSystem = apply() def apply(): ActorSystem = apply("default") - class Settings(cfg: Config) { + class Settings(cfg: Config, val name: String) { private def referenceConfig: Config = ConfigFactory.parseResource(classOf[ActorSystem], "/akka-actor-reference.conf", ConfigParseOptions.defaults.setAllowMissing(false)) @@ -287,11 +287,34 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A import ActorSystem._ - val settings = new Settings(applicationConfig) + val settings = new Settings(applicationConfig, name) protected def systemImpl = this - private[akka] def systemActorOf(props: Props, address: String): ActorRef = provider.actorOf(this, props, systemGuardian, address, true) + private val systemActors = new ConcurrentHashMap[String, ActorRef] + + private[akka] def systemActorOf(props: Props, name: String): ActorRef = { + if (systemActors.putIfAbsent(name, deadLetters) eq null) { + val actor = provider.actorOf(this, props, systemGuardian, name, true) + systemActors.replace(name, actor) + deathWatch.subscribe(systemActorsJanitor, actor) + actor + } else throw new InvalidActorNameException("system actor name " + name + " is not unique!") + } + + private val actors = new ConcurrentHashMap[String, ActorRef] + + protected def isDuplicate(name: String): Boolean = { + actors.putIfAbsent(name, deadLetters) ne null + } + + override def actorOf(props: Props, name: String): ActorRef = { + val actor = super.actorOf(props, name) + // this would have thrown an exception in case of a duplicate name + actors.replace(name, actor) + deathWatch.subscribe(actorsJanitor, actor) + actor + } import settings._ @@ -302,25 +325,6 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A val scheduler = new DefaultScheduler(new HashedWheelTimer(log, Executors.defaultThreadFactory, settings.SchedulerTickDuration, settings.SchedulerTicksPerWheel)) - val provider: ActorRefProvider = { - val providerClass = ReflectiveAccess.getClassFor(ProviderClass) match { - case Left(e) ⇒ throw e - case Right(b) ⇒ b - } - val arguments = Seq( - classOf[Settings] -> settings, - classOf[EventStream] -> eventStream, - classOf[Scheduler] -> scheduler) - val types: Array[Class[_]] = arguments map (_._1) toArray - val values: Array[AnyRef] = arguments map (_._2) toArray - - ReflectiveAccess.createInstance[ActorRefProvider](providerClass, types, values) match { - case Left(e: InvocationTargetException) ⇒ throw e.getTargetException - case Left(e) ⇒ throw e - case Right(p) ⇒ p - } - } - val deadLetters = new DeadLetterActorRef(eventStream) val deadLetterMailbox = new Mailbox(null) { becomeClosed() @@ -333,6 +337,35 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A override def numberOfMessages = 0 } + val provider: ActorRefProvider = { + val providerClass = ReflectiveAccess.getClassFor(ProviderClass) match { + case Left(e) ⇒ throw e + case Right(b) ⇒ b + } + val arguments = Seq( + classOf[String] -> name, + classOf[Settings] -> settings, + classOf[EventStream] -> eventStream, + classOf[Scheduler] -> scheduler, + classOf[ActorRef] -> deadLetters) + val types: Array[Class[_]] = arguments map (_._1) toArray + val values: Array[AnyRef] = arguments map (_._2) toArray + + ReflectiveAccess.createInstance[ActorRefProvider](providerClass, types, values) match { + case Left(e: InvocationTargetException) ⇒ throw e.getTargetException + case Left(e) ⇒ throw e + case Right(p) ⇒ p + } + } + + val actorsJanitor = MinimalActorRef(provider.rootPath) { + case Terminated(x) ⇒ actors.remove(x.path.name) + } + + val systemActorsJanitor = MinimalActorRef(provider.rootPath) { + case Terminated(x) ⇒ systemActors.remove(x.path.name) + } + val dispatcherFactory = new Dispatchers(settings, DefaultDispatcherPrerequisites(eventStream, deadLetterMailbox, scheduler)) implicit val dispatcher = dispatcherFactory.defaultGlobalDispatcher diff --git a/akka-actor/src/main/scala/akka/actor/Address.scala b/akka-actor/src/main/scala/akka/actor/Address.scala index c405702f6e..bda9fd6048 100644 --- a/akka-actor/src/main/scala/akka/actor/Address.scala +++ b/akka-actor/src/main/scala/akka/actor/Address.scala @@ -2,6 +2,8 @@ * Copyright (C) 2009-2011 Typesafe Inc. */ package akka.actor +import java.net.URI +import java.net.URISyntaxException /** * The address specifies the physical location under which an Actor can be @@ -18,4 +20,19 @@ abstract class Address { case class LocalAddress(systemName: String) extends Address { def protocol = "akka" def hostPort = systemName +} + +object LocalActorPath { + def unapply(addr: String): Option[(LocalAddress, Iterable[String])] = { + try { + val uri = new URI(addr) + if (uri.getScheme != "akka") return None + if (uri.getUserInfo != null) return None + if (uri.getHost == null) return None + if (uri.getPath == null) return None + Some(LocalAddress(uri.getHost), uri.getPath.split("/").drop(1)) + } catch { + case _: URISyntaxException ⇒ None + } + } } \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/actor/Deployer.scala b/akka-actor/src/main/scala/akka/actor/Deployer.scala index ec1d8dfc4c..3bb7338d8d 100644 --- a/akka-actor/src/main/scala/akka/actor/Deployer.scala +++ b/akka-actor/src/main/scala/akka/actor/Deployer.scala @@ -182,7 +182,7 @@ class Deployer(val settings: ActorSystem.Settings, val eventStream: EventStream, } if (port == 0) raiseRemoteNodeParsingError() - RemoteAddress(new InetSocketAddress(hostname, port)) + RemoteAddress(settings.name, hostname, port) } RemoteScope(remoteAddresses) diff --git a/akka-actor/src/main/scala/akka/event/EventStream.scala b/akka-actor/src/main/scala/akka/event/EventStream.scala index 3906d2cb04..647fe2336c 100644 --- a/akka-actor/src/main/scala/akka/event/EventStream.scala +++ b/akka-actor/src/main/scala/akka/event/EventStream.scala @@ -5,9 +5,11 @@ package akka.event import akka.actor.{ ActorRef, Actor, Props, ActorSystemImpl, Terminated, ActorSystem, simpleName } import akka.util.Subclassification +import java.util.concurrent.atomic.AtomicInteger object EventStream { implicit def fromActorSystem(system: ActorSystem) = system.eventStream + val generation = new AtomicInteger } class A(x: Int = 0) extends Exception("x=" + x) @@ -52,8 +54,12 @@ class EventStream(debug: Boolean = false) extends LoggingBus with SubchannelClas case ref: ActorRef ⇒ watch(ref) case Terminated(ref) ⇒ unsubscribe(ref) } - }), "MainBusReaper") + }), "MainBusReaper-" + EventStream.generation.incrementAndGet()) subscribers foreach (reaper ! _) } + def stop() { + reaper.stop() + } + } \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala index 4acf0e37c7..64a45fc9d7 100644 --- a/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala @@ -6,32 +6,40 @@ package akka.remote import akka.actor._ import akka.AkkaException - import scala.reflect.BeanProperty import java.io.{ PrintWriter, PrintStream } - import java.net.InetSocketAddress +import java.net.URI +import java.net.URISyntaxException +import java.net.InetAddress object RemoteAddress { - def apply(host: String, port: Int): RemoteAddress = apply(new InetSocketAddress(host, port)) - def apply(inetAddress: InetSocketAddress): RemoteAddress = inetAddress match { - case null ⇒ null - case inet ⇒ - val host = inet.getAddress match { - case null ⇒ inet.getHostName //Fall back to given name - case other ⇒ other.getHostAddress - } - val portNo = inet.getPort - RemoteAddress(portNo, host) + def apply(system: String, host: String, port: Int) = { + val ip = InetAddress.getByName(host) + new RemoteAddress(system, host, ip, port) } } -object LocalOnly extends RemoteAddress(0, "local") - -case class RemoteAddress private[akka] (port: Int, hostname: String) extends Address { +case class RemoteAddress(system: String, host: String, ip: InetAddress, port: Int) extends Address { def protocol = "akka" @transient - lazy val hostPort = hostname + ":" + port + lazy val hostPort = system + "@" + host + ":" + port +} + +object RemoteActorPath { + def unapply(addr: String): Option[(RemoteAddress, Iterable[String])] = { + try { + val uri = new URI(addr) + if (uri.getScheme != "akka") return None + if (uri.getUserInfo == null) return None + if (uri.getHost == null) return None + if (uri.getPort == -1) return None + if (uri.getPath == null) return None + Some(RemoteAddress(uri.getUserInfo, uri.getHost, uri.getPort), uri.getPath.split("/").drop(1)) + } catch { + case _: URISyntaxException ⇒ None + } + } } class RemoteException(message: String) extends AkkaException(message) diff --git a/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala b/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala index 3376ad9416..c6e77c3416 100644 --- a/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala +++ b/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala @@ -63,9 +63,9 @@ class NetworkEventStream(system: ActorSystemImpl) { import NetworkEventStream._ // FIXME: check that this supervision is correct - private[akka] val sender = system.provider.actorOf(system, - Props[Channel].copy(dispatcher = system.dispatcherFactory.newPinnedDispatcher("NetworkEventStream")), - system.systemGuardian, "network-event-sender", systemService = true) + private[akka] val sender = + system.systemActorOf(Props[Channel].copy(dispatcher = system.dispatcherFactory.newPinnedDispatcher("NetworkEventStream")), + "network-event-sender") /** * Registers a network event stream listener (asyncronously). diff --git a/akka-remote/src/main/scala/akka/remote/Remote.scala b/akka-remote/src/main/scala/akka/remote/Remote.scala index ecdc5d39a1..1a3bc27b0b 100644 --- a/akka-remote/src/main/scala/akka/remote/Remote.scala +++ b/akka-remote/src/main/scala/akka/remote/Remote.scala @@ -38,7 +38,7 @@ class Remote(val system: ActorSystemImpl, val nodename: String) { private[remote] val remoteExtension = RemoteExtension(system) private[remote] val serializationExtension = SerializationExtension(system) private[remote] val remoteAddress = { - RemoteAddress(remoteExtension.settings.serverSettings.Hostname, remoteExtension.settings.serverSettings.Port) + RemoteAddress(system.name, remoteExtension.settings.serverSettings.Hostname, remoteExtension.settings.serverSettings.Port) } val failureDetector = new AccrualFailureDetector(system) @@ -141,13 +141,17 @@ class RemoteSystemDaemon(remote: Remote) extends Actor { case Right(instance) ⇒ instance.asInstanceOf[() ⇒ Actor] } - val actorPath = ActorPath(systemImpl, message.getActorPath) - val parent = system.actorFor(actorPath.parent) - - if (parent.isDefined) { - systemImpl.provider.actorOf(systemImpl, Props(creator = actorFactory), parent.get, actorPath.name) - } else { - log.error("Parent actor does not exist, ignoring remote system daemon command [{}]", message) + message.getActorPath match { + case RemoteActorPath(addr, elems) if addr == remoteAddress && elems.size > 0 ⇒ + val name = elems.last + system.actorFor(elems.dropRight(1)) match { + case x if x eq system.deadLetters ⇒ + log.error("Parent actor does not exist, ignoring remote system daemon command [{}]", message) + case parent ⇒ + systemImpl.provider.actorOf(systemImpl, Props(creator = actorFactory), parent, name) + } + case _ ⇒ + log.error("remote path does not match path from message [{}]", message) } } else { @@ -251,7 +255,7 @@ class RemoteMessage(input: RemoteMessageProtocol, remote: RemoteSupport, classLo else remote.system.deadLetters - lazy val recipient: ActorRef = remote.system.actorFor(input.getRecipient.getPath).getOrElse(remote.system.deadLetters) + lazy val recipient: ActorRef = remote.system.actorFor(input.getRecipient.getPath) lazy val payload: Either[Throwable, AnyRef] = if (input.hasException) Left(parseException()) diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index a34f975d57..4d5e6f7adc 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -30,18 +30,19 @@ import akka.serialization.SerializationExtension * @author Jonas Bonér */ class RemoteActorRefProvider( + val systemName: String, val settings: ActorSystem.Settings, val eventStream: EventStream, - val scheduler: Scheduler) extends ActorRefProvider { + val scheduler: Scheduler, + _deadLetters: ActorRef) extends ActorRefProvider { val log = Logging(eventStream, "RemoteActorRefProvider") def deathWatch = local.deathWatch def guardian = local.guardian def systemGuardian = local.systemGuardian - def nodename = local.nodename - def clustername = local.clustername - def tempName = local.tempName + def nodename = remoteExtension.settings.NodeName + def clustername = remoteExtension.settings.ClusterName private val actors = new ConcurrentHashMap[String, AnyRef] @@ -57,11 +58,10 @@ class RemoteActorRefProvider( private lazy val remoteExtension = RemoteExtension(system) private lazy val serializationExtension = SerializationExtension(system) lazy val rootPath: ActorPath = { - val remoteAddress = RemoteAddress(remoteExtension.settings.serverSettings.Hostname, remoteExtension.settings.serverSettings.Port) + val remoteAddress = RemoteAddress(system.name, remoteExtension.settings.serverSettings.Hostname, remoteExtension.settings.serverSettings.Port) new RootActorPath(remoteAddress) } - private lazy val local = new LocalActorRefProvider(settings, eventStream, scheduler, rootPath, - remoteExtension.settings.NodeName, remoteExtension.settings.ClusterName) + private lazy val local = new LocalActorRefProvider(systemName, settings, eventStream, scheduler, _deadLetters) private[akka] lazy val remote = new Remote(system, nodename) private lazy val remoteDaemonConnectionManager = new RemoteConnectionManager(system, remote) @@ -79,13 +79,10 @@ class RemoteActorRefProvider( def dispatcher = local.dispatcher def defaultTimeout = settings.ActorTimeout - private[akka] def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, name: String, systemService: Boolean): ActorRef = - actorOf(system, props, supervisor, supervisor.path / name, systemService) - - private[akka] def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, path: ActorPath, systemService: Boolean): ActorRef = - if (systemService) local.actorOf(system, props, supervisor, path, systemService) + def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, name: String, systemService: Boolean): ActorRef = + if (systemService) local.actorOf(system, props, supervisor, name, systemService) else { - val name = path.name + val path = supervisor.path / name val newFuture = Promise[ActorRef](5000)(dispatcher) // FIXME is this proper timeout? actors.putIfAbsent(path.toString, newFuture) match { // we won the race -- create the actor and resolve the future @@ -144,7 +141,7 @@ class RemoteActorRefProvider( } val connections = (Map.empty[RemoteAddress, ActorRef] /: remoteAddresses) { (conns, a) ⇒ - val remoteAddress = RemoteAddress(a.hostname, a.port) + val remoteAddress = RemoteAddress(system.name, a.host, a.port) conns + (remoteAddress -> RemoteActorRef(remote.system.provider, remote.server, remoteAddress, path, None)) } @@ -182,11 +179,9 @@ class RemoteActorRefProvider( new RoutedActorRef(system, props, supervisor, name) } - def actorFor(path: Iterable[String]): Option[ActorRef] = actors.get(ActorPath.join(path)) match { - case null ⇒ local.actorFor(path) - case actor: ActorRef ⇒ Some(actor) - case future: Future[_] ⇒ Some(future.get.asInstanceOf[ActorRef]) - } + def actorFor(path: ActorPath): ActorRef = local.actorFor(path) + def actorFor(path: String): ActorRef = local.actorFor(path) + def actorFor(path: Iterable[String]): ActorRef = local.actorFor(path) // TODO remove me val optimizeLocal = new AtomicBoolean(true) @@ -195,7 +190,7 @@ class RemoteActorRefProvider( /** * Returns true if the actor was in the provider's cache and evicted successfully, else false. */ - private[akka] def evict(path: String): Boolean = actors.remove(path) ne null + private[akka] def evict(path: ActorPath): Boolean = actors.remove(path) ne null private[akka] def serialize(actor: ActorRef): SerializedActorRef = actor match { case r: RemoteActorRef ⇒ new SerializedActorRef(r.remoteAddress, actor.path.toString) @@ -203,12 +198,12 @@ class RemoteActorRefProvider( } private[akka] def deserialize(actor: SerializedActorRef): Option[ActorRef] = { - val remoteAddress = RemoteAddress(actor.hostname, actor.port) + val remoteAddress = RemoteAddress(systemName, actor.hostname, actor.port) if (optimizeLocalScoped_? && remoteAddress == remote.remoteAddress) { - local.actorFor(ActorPath.split(actor.path)) + Some(local.actorFor(actor.path)) } else { log.debug("{}: Creating RemoteActorRef with address [{}] connected to [{}]", remote.remoteAddress, actor.path, remoteAddress) - Some(RemoteActorRef(remote.system.provider, remote.server, remoteAddress, rootPath / ActorPath.split(actor.path), None)) //Should it be None here + Some(RemoteActorRef(remote.system.provider, remote.server, remoteAddress, rootPath / actor.path, None)) // FIXME I know, this is broken } } 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 497976cccf..43b9cd45b6 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -152,7 +152,7 @@ class ActiveRemoteClient private[akka] ( val handshake = RemoteControlProtocol.newBuilder.setCommandType(CommandType.CONNECT) if (SecureCookie.nonEmpty) handshake.setCookie(SecureCookie.get) handshake.setOrigin(RemoteProtocol.AddressProtocol.newBuilder - .setHostname(senderRemoteAddress.hostname) + .setHostname(senderRemoteAddress.host) .setPort(senderRemoteAddress.port) .build) connection.getChannel.write(remoteSupport.createControlEnvelope(handshake.build)) @@ -166,7 +166,7 @@ class ActiveRemoteClient private[akka] ( def attemptReconnect(): Boolean = { log.debug("Remote client reconnecting to [{}]", remoteAddress) - val connection = bootstrap.connect(new InetSocketAddress(remoteAddress.hostname, remoteAddress.port)) + val connection = bootstrap.connect(new InetSocketAddress(remoteAddress.ip, remoteAddress.port)) openChannels.add(connection.awaitUninterruptibly.getChannel) // Wait until the connection attempt succeeds or fails. if (!connection.isSuccess) { @@ -189,7 +189,7 @@ class ActiveRemoteClient private[akka] ( log.debug("Starting remote client connection to [{}]", remoteAddress) - connection = bootstrap.connect(new InetSocketAddress(remoteAddress.hostname, remoteAddress.port)) + connection = bootstrap.connect(new InetSocketAddress(remoteAddress.ip, remoteAddress.port)) val channel = connection.awaitUninterruptibly.getChannel openChannels.add(channel) @@ -512,7 +512,7 @@ class NettyRemoteServer(val remoteSupport: NettyRemoteSupport, val loader: Optio bootstrap.setOption("child.reuseAddress", true) bootstrap.setOption("child.connectTimeoutMillis", ConnectionTimeout.toMillis) - openChannels.add(bootstrap.bind(new InetSocketAddress(address.hostname, address.port))) + openChannels.add(bootstrap.bind(new InetSocketAddress(address.ip, address.port))) remoteSupport.notifyListeners(RemoteServerStarted(remoteSupport)) def shutdown() { @@ -645,7 +645,8 @@ class RemoteServerHandler( instruction.getCommandType match { case CommandType.CONNECT if UsePassiveConnections ⇒ val origin = instruction.getOrigin - val inbound = RemoteAddress(origin.getHostname, origin.getPort) + // FIXME need to include system-name in remote protocol + val inbound = RemoteAddress("BORKED", origin.getHostname, origin.getPort) val client = new PassiveRemoteClient(event.getChannel, remoteSupport, inbound) remoteSupport.bindClient(inbound, client) case CommandType.SHUTDOWN ⇒ //TODO FIXME Dispose passive connection here @@ -664,7 +665,7 @@ class RemoteServerHandler( private def getClientAddress(c: Channel): Option[RemoteAddress] = c.getRemoteAddress match { - case inet: InetSocketAddress ⇒ Some(RemoteAddress(inet)) + case inet: InetSocketAddress ⇒ Some(RemoteAddress("BORKED", inet.getHostName, inet.getPort)) // FIXME Broken! case _ ⇒ None } } diff --git a/akka-remote/src/test/scala/akka/remote/AccrualFailureDetectorSpec.scala b/akka-remote/src/test/scala/akka/remote/AccrualFailureDetectorSpec.scala index 38d18ac6c5..94e2c0272c 100644 --- a/akka-remote/src/test/scala/akka/remote/AccrualFailureDetectorSpec.scala +++ b/akka-remote/src/test/scala/akka/remote/AccrualFailureDetectorSpec.scala @@ -6,7 +6,7 @@ import akka.testkit.AkkaSpec class AccrualFailureDetectorSpec extends AkkaSpec { "An AccrualFailureDetector" must { - val conn = RemoteAddress(new InetSocketAddress("localhost", 2552)) + val conn = RemoteAddress("tester", "localhost", 2552) "mark node as available after a series of successful heartbeats" in { val fd = new AccrualFailureDetector From 073c3c012b0a6e5e7ac8ced28677ce05c1dc6fa1 Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 29 Nov 2011 23:00:57 +0100 Subject: [PATCH 05/30] fix EventStreamSpec by adding Logging extension - used only to keep the uniquifying logger counter per-ActorSystem - add convenience-class for writing an extension with only two lines overhead (Java: three)! --- .../test/java/akka/actor/JavaExtension.java | 14 ++++++++ .../src/main/scala/akka/actor/Extension.scala | 36 +++++++++++++++++++ .../src/main/scala/akka/event/Logging.scala | 12 +++++-- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java b/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java index fefec7640a..43aade632e 100644 --- a/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java +++ b/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java @@ -31,6 +31,15 @@ public class JavaExtension { system = i; } } + + static class OtherExtension implements Extension { + static final ExtensionKey key = new ExtensionKey(OtherExtension.class) {}; + + public final ActorSystemImpl system; + public OtherExtension(ActorSystemImpl i) { + system = i; + } + } private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]", ConfigParseOptions.defaults()); @@ -42,5 +51,10 @@ public class JavaExtension { assertSame(system.extension(defaultInstance).system, system); assertSame(defaultInstance.apply(system).system, system); } + + @Test + public void mustBeAdHoc() { + assertSame(OtherExtension.key.apply(system).system, system); + } } diff --git a/akka-actor/src/main/scala/akka/actor/Extension.scala b/akka-actor/src/main/scala/akka/actor/Extension.scala index bfd4ab6a52..966795ff93 100644 --- a/akka-actor/src/main/scala/akka/actor/Extension.scala +++ b/akka-actor/src/main/scala/akka/actor/Extension.scala @@ -3,6 +3,8 @@ */ package akka.actor +import akka.util.ReflectiveAccess + /** * The basic ActorSystem covers all that is needed for locally running actors, * using futures and so on. In addition, more features can hook into it and @@ -64,3 +66,37 @@ trait ExtensionIdProvider { */ def lookup(): ExtensionId[_ <: Extension] } + +/** + * This is a one-stop-shop if all you want is an extension which is + * constructed with the ActorSystemImpl as its only constructor argument: + * + * {{{ + * object MyExt extends ExtensionKey[Ext] + * + * class Ext(system: ActorSystemImpl) extends MyExt { + * ... + * } + * }}} + * + * Java API: + * + * {{{ + * public class MyExt extends Extension { + * static final ExtensionKey key = new ExtensionKey(MyExt.class); + * + * public MyExt(ActorSystemImpl system) { + * ... + * } + * }}} + */ +abstract class ExtensionKey[T <: Extension](implicit m: ClassManifest[T]) extends ExtensionId[T] with ExtensionIdProvider { + def this(clazz: Class[T]) = this()(ClassManifest.fromClass(clazz)) + + override def lookup() = this + def createExtension(system: ActorSystemImpl): T = + ReflectiveAccess.createInstance[T](m.erasure, Array[Class[_]](classOf[ActorSystemImpl]), Array[AnyRef](system)) match { + case Left(ex) ⇒ throw ex + case Right(r) ⇒ r + } +} diff --git a/akka-actor/src/main/scala/akka/event/Logging.scala b/akka-actor/src/main/scala/akka/event/Logging.scala index bae930cb17..ad99dd5af2 100644 --- a/akka-actor/src/main/scala/akka/event/Logging.scala +++ b/akka-actor/src/main/scala/akka/event/Logging.scala @@ -3,7 +3,7 @@ */ package akka.event -import akka.actor.{ Actor, ActorPath, ActorRef, MinimalActorRef, LocalActorRef, Props, ActorSystem, ActorSystemImpl, simpleName } +import akka.actor._ import akka.AkkaException import akka.actor.ActorSystem.Settings import akka.util.ReflectiveAccess @@ -38,7 +38,6 @@ trait LoggingBus extends ActorEventBus { private val guard = new ReentrantGuard private var loggers = Seq.empty[ActorRef] private var _logLevel: LogLevel = _ - private val loggerId = new AtomicInteger /** * Query currently set log level. See object Logging for more information. @@ -144,7 +143,7 @@ trait LoggingBus extends ActorEventBus { } private def addLogger(system: ActorSystemImpl, clazz: Class[_ <: Actor], level: LogLevel): ActorRef = { - val name = "log" + loggerId.incrementAndGet + "-" + simpleName(clazz) + val name = "log" + Extension(system).id() + "-" + simpleName(clazz) val actor = system.systemActorOf(Props(clazz), name) implicit val timeout = Timeout(3 seconds) val response = try actor ? InitializeLogger(this) get catch { @@ -225,6 +224,13 @@ object LogSource { */ object Logging { + object Extension extends ExtensionKey[Ext] + + class Ext(system: ActorSystemImpl) extends Extension { + private val loggerId = new AtomicInteger + def id() = loggerId.incrementAndGet() + } + /** * Marker trait for annotating LogLevel, which must be Int after erasure. */ From 97789dda805c586c0b1fb32e475e7f2636236b0b Mon Sep 17 00:00:00 2001 From: Roland Date: Wed, 30 Nov 2011 15:03:33 +0100 Subject: [PATCH 06/30] fix one typo and one bad omission: if you override a method and use a more specific return type, scalac will emit a bridge method with the expected signature pointing to the more specific one, but currently this does upset the Eclipse based Java builders, so add one type ascription to ExtensionKey. --- akka-actor/src/main/scala/akka/actor/Extension.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/Extension.scala b/akka-actor/src/main/scala/akka/actor/Extension.scala index 966795ff93..23439b263d 100644 --- a/akka-actor/src/main/scala/akka/actor/Extension.scala +++ b/akka-actor/src/main/scala/akka/actor/Extension.scala @@ -21,7 +21,7 @@ import akka.util.ReflectiveAccess */ /** - * Market interface to signify an Akka Extension + * Marker interface to signify an Akka Extension */ trait Extension @@ -93,7 +93,7 @@ trait ExtensionIdProvider { abstract class ExtensionKey[T <: Extension](implicit m: ClassManifest[T]) extends ExtensionId[T] with ExtensionIdProvider { def this(clazz: Class[T]) = this()(ClassManifest.fromClass(clazz)) - override def lookup() = this + override def lookup(): ExtensionId[T] = this def createExtension(system: ActorSystemImpl): T = ReflectiveAccess.createInstance[T](m.erasure, Array[Class[_]](classOf[ActorSystemImpl]), Array[AnyRef](system)) match { case Left(ex) ⇒ throw ex From 7e4333a6125a13dbec3735498b4b2da2ac5ae8c4 Mon Sep 17 00:00:00 2001 From: Roland Date: Wed, 30 Nov 2011 22:19:48 +0100 Subject: [PATCH 07/30] fix actor creation with duplicate name within same message invocation - ActorCell will pre-fill the childrenRefs with the reserved name in order to detect duplicates - also clean up some test outputs (some ActorSystems forgot to fall back to testConf so they did not get the TestEventListener anymore) - fix race in start-up of LoggingReceiveSpec by ignoring messages from testActor start (never was slow enough on my machine to trigger => thank you Jenkins, again!) - represent actors in logging only by their path, i.e. remote Actor[...] decoration - fix automatic naming of AkkaSpec ActorSystems after the true class name of the test spec --- .../test/scala/akka/actor/FSMActorSpec.scala | 12 ++-- .../actor/LocalActorRefProviderSpec.scala | 17 ++++++ .../scala/akka/actor/LoggingReceiveSpec.scala | 24 ++++---- .../src/main/scala/akka/actor/ActorCell.scala | 58 +++++++++++-------- .../src/main/scala/akka/actor/ActorRef.scala | 2 +- .../scala/akka/actor/ActorRefProvider.scala | 12 +++- .../main/scala/akka/actor/ActorSystem.scala | 8 +-- .../src/main/scala/akka/event/Logging.scala | 4 +- .../akka/testkit/TestEventListener.scala | 13 ++++- .../test/scala/akka/testkit/AkkaSpec.scala | 23 +++++--- 10 files changed, 111 insertions(+), 62 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala index 7d829ec622..1fedcd82e4 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala @@ -164,8 +164,8 @@ class FSMActorSpec extends AkkaSpec(Map("akka.actor.debug.fsm" -> true)) with Im case Ev("go") ⇒ goto(2) } }) - val name = fsm.toString - filterException[Logging.EventHandlerException] { + val name = fsm.path.toString + EventFilter.error("Next state 2 does not exist", occurrences = 1) intercept { system.eventStream.subscribe(testActor, classOf[Logging.Error]) fsm ! "go" expectMsgPF(1 second, hint = "Next state 2 does not exist") { @@ -194,9 +194,9 @@ class FSMActorSpec extends AkkaSpec(Map("akka.actor.debug.fsm" -> true)) with Im "log events and transitions if asked to do so" in { import scala.collection.JavaConverters._ val config = ConfigFactory.parseMap(Map("akka.loglevel" -> "DEBUG", - "akka.actor.debug.fsm" -> true).asJava) - new TestKit(ActorSystem("fsm event", config)) { - EventFilter.debug() intercept { + "akka.actor.debug.fsm" -> true).asJava).withFallback(system.settings.config) + new TestKit(ActorSystem("fsmEvent", config)) { + EventFilter.debug(occurrences = 5) intercept { val fsm = TestActorRef(new Actor with LoggingFSM[Int, Null] { startWith(1, null) when(1) { @@ -213,7 +213,7 @@ class FSMActorSpec extends AkkaSpec(Map("akka.actor.debug.fsm" -> true)) with Im case StopEvent(r, _, _) ⇒ testActor ! r } }) - val name = fsm.toString + val name = fsm.path.toString system.eventStream.subscribe(testActor, classOf[Logging.Debug]) fsm ! "go" expectMsgPF(1 second, hint = "processing Event(go,null)") { diff --git a/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala index de59894431..b386767b26 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/LocalActorRefProviderSpec.scala @@ -18,6 +18,10 @@ class LocalActorRefProviderSpec extends AkkaSpec { a must be === b } + } + + "An ActorRefFactory" must { + "only create one instance of an actor with a specific address in a concurrent environment" in { val impl = system.asInstanceOf[ActorSystemImpl] val provider = impl.provider @@ -36,5 +40,18 @@ class LocalActorRefProviderSpec extends AkkaSpec { set must be === Set(1, 2) } } + + "only create one instance of an actor from within the same message invocation" in { + val supervisor = system.actorOf(new Actor { + def receive = { + case "" ⇒ + val a, b = context.actorOf(Props.empty, "duplicate") + } + }) + EventFilter[InvalidActorNameException](occurrences = 1) intercept { + supervisor ! "" + } + } + } } diff --git a/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala index 0e5602a899..b7c046a43b 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala @@ -24,7 +24,7 @@ object LoggingReceiveSpec { class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAndAfterAll { import LoggingReceiveSpec._ - val config = ConfigFactory.parseMap(Map("akka.logLevel" -> "DEBUG").asJava) + val config = ConfigFactory.parseMap(Map("akka.logLevel" -> "DEBUG").asJava).withFallback(AkkaSpec.testConf) val appLogging = ActorSystem("logging", ConfigFactory.parseMap(Map("akka.actor.debug.receive" -> true).asJava).withFallback(config)) val appAuto = ActorSystem("autoreceive", ConfigFactory.parseMap(Map("akka.actor.debug.autoreceive" -> true).asJava).withFallback(config)) val appLifecycle = ActorSystem("lifecycle", ConfigFactory.parseMap(Map("akka.actor.debug.lifecycle" -> true).asJava).withFallback(config)) @@ -75,7 +75,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd sender ! "x" } }) - val name = actor.toString + val name = actor.path.toString actor ! "buh" within(1 second) { expectMsg(Logging.Debug(name, "received handled message buh")) @@ -85,7 +85,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd case null ⇒ } actor ! HotSwap(_ ⇒ r, false) - filterException[UnhandledMessageException] { + EventFilter[UnhandledMessageException](pattern = "does not handle", occurrences = 1) intercept { within(500 millis) { actor ! "bah" expectMsgPF() { @@ -106,7 +106,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd }) actor ! "buh" within(1 second) { - expectMsg(Logging.Debug(actor.toString, "received handled message buh")) + expectMsg(Logging.Debug(actor.path.toString, "received handled message buh")) expectMsg("x") } } @@ -124,7 +124,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd case _ ⇒ } }) - val name = actor.toString + val name = actor.path.toString actor ! PoisonPill expectMsgPF() { case Logging.Debug(`name`, msg: String) if msg startsWith "received AutoReceiveMessage Envelope(PoisonPill" ⇒ true @@ -135,19 +135,19 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd "log LifeCycle changes if requested" in { new TestKit(appLifecycle) { + val impl = system.asInstanceOf[ActorSystemImpl] + val sys = impl.systemGuardian.path.toString ignoreMute(this) ignoreMsg { - case Logging.Debug(ref, _) ⇒ - val s = ref.toString - s.contains("MainBusReaper") || s.contains("Supervisor") + case Logging.Debug(s, _) ⇒ s.contains("MainBusReaper") || s == sys } system.eventStream.subscribe(testActor, classOf[Logging.Debug]) system.eventStream.subscribe(testActor, classOf[Logging.Error]) within(3 seconds) { val lifecycleGuardian = appLifecycle.asInstanceOf[ActorSystemImpl].guardian - val lname = lifecycleGuardian.toString + val lname = lifecycleGuardian.path.toString val supervisor = TestActorRef[TestLogActor](Props[TestLogActor].withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), 5, 5000))) - val sname = supervisor.toString + val sname = supervisor.path.toString val supervisorSet = receiveWhile(messages = 2) { case Logging.Debug(`lname`, msg: String) if msg startsWith "now supervising" ⇒ 1 @@ -157,7 +157,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd assert(supervisorSet == Set(1, 2), supervisorSet + " was not Set(1, 2)") val actor = TestActorRef[TestLogActor](Props[TestLogActor], supervisor, "none") - val aname = actor.toString + val aname = actor.path.toString val set = receiveWhile(messages = 2) { case Logging.Debug(`sname`, msg: String) if msg startsWith "now supervising" ⇒ 1 @@ -178,7 +178,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd ref == supervisor.underlyingActor && msg.startsWith("stopped monitoring") } - filterException[ActorKilledException] { + EventFilter[ActorKilledException](occurrences = 1) intercept { actor ! Kill val set = receiveWhile(messages = 3) { case Logging.Error(_: ActorKilledException, `aname`, "Kill") ⇒ 1 diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 4bb623cc68..6e40ab8f34 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -96,7 +96,15 @@ private[akka] class ActorCell( var childrenRefs: TreeMap[String, ChildRestartStats] = emptyChildrenRefs protected def isDuplicate(name: String): Boolean = { - childrenRefs contains name + if (childrenRefs contains name) true + else { + childrenRefs = childrenRefs.updated(name, ChildRestartStats(system.deadLetters)) + false + } + } + + protected def actorCreated(name: String, actor: ActorRef): Unit = { + childrenRefs = childrenRefs.updated(name, childrenRefs(name).copy(child = actor)) } var currentMessage: Envelope = null @@ -197,11 +205,11 @@ private[akka] class ActorCell( actor = created created.preStart() checkReceiveTimeout - if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.toString, "started (" + actor + ")")) + if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "started (" + actor + ")")) } catch { case e ⇒ try { - system.eventStream.publish(Error(e, self.toString, "error while creating actor")) + system.eventStream.publish(Error(e, self.path.toString, "error while creating actor")) // prevent any further messages to be processed until the actor has been restarted dispatcher.suspend(this) } finally { @@ -211,7 +219,7 @@ private[akka] class ActorCell( def recreate(cause: Throwable): Unit = try { val failedActor = actor - if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.toString, "restarting")) + if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "restarting")) val freshActor = newActor() if (failedActor ne null) { val c = currentMessage //One read only plz @@ -225,14 +233,14 @@ private[akka] class ActorCell( } actor = freshActor // assign it here so if preStart fails, we can null out the sef-refs next call freshActor.postRestart(cause) - if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.toString, "restarted")) + if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "restarted")) dispatcher.resume(this) //FIXME should this be moved down? props.faultHandler.handleSupervisorRestarted(cause, self, children) } catch { case e ⇒ try { - system.eventStream.publish(Error(e, self.toString, "error while creating actor")) + system.eventStream.publish(Error(e, self.path.toString, "error while creating actor")) // prevent any further messages to be processed until the actor has been restarted dispatcher.suspend(this) } finally { @@ -251,22 +259,22 @@ private[akka] class ActorCell( val c = children if (c.isEmpty) doTerminate() else { - if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.toString, "stopping")) + if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "stopping")) for (child ← c) child.stop() stopping = true } } def supervise(child: ActorRef): Unit = { - val stat = childrenRefs.get(child.name) - if (stat.isDefined) { - if (stat.get.child == child) - system.eventStream.publish(Warning(self.toString, "Already supervising " + child)) - else - system.eventStream.publish(Warning(self.toString, "Already supervising other child with same name '" + child.name + "', old: " + stat.get + " new: " + child)) - } else { - childrenRefs = childrenRefs.updated(child.name, ChildRestartStats(child)) - if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.toString, "now supervising " + child)) + val dl = system.deadLetters + childrenRefs.get(child.name) match { + case None | Some(ChildRestartStats(`dl`, _, _)) ⇒ + childrenRefs = childrenRefs.updated(child.name, ChildRestartStats(child)) + if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "now supervising " + child)) + case Some(ChildRestartStats(`child`, _, _)) ⇒ + system.eventStream.publish(Warning(self.path.toString, "Already supervising " + child)) + case Some(ChildRestartStats(c, _, _)) ⇒ + system.eventStream.publish(Warning(self.path.toString, "Already supervising other child with same name '" + child.name + "', old: " + c + " new: " + child)) } } @@ -280,10 +288,10 @@ private[akka] class ActorCell( case Recreate(cause) ⇒ recreate(cause) case Link(subject) ⇒ system.deathWatch.subscribe(self, subject) - if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.toString, "now monitoring " + subject)) + if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "now monitoring " + subject)) case Unlink(subject) ⇒ system.deathWatch.unsubscribe(self, subject) - if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.toString, "stopped monitoring " + subject)) + if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "stopped monitoring " + subject)) case Suspend() ⇒ suspend() case Resume() ⇒ resume() case Terminate() ⇒ terminate() @@ -291,7 +299,7 @@ private[akka] class ActorCell( } } catch { case e ⇒ //Should we really catch everything here? - system.eventStream.publish(Error(e, self.toString, "error while processing " + message)) + system.eventStream.publish(Error(e, self.path.toString, "error while processing " + message)) //TODO FIXME How should problems here be handled? throw e } @@ -313,7 +321,7 @@ private[akka] class ActorCell( currentMessage = null // reset current message after successful invocation } catch { case e ⇒ - system.eventStream.publish(Error(e, self.toString, e.getMessage)) + system.eventStream.publish(Error(e, self.path.toString, e.getMessage)) // prevent any further messages to be processed until the actor has been restarted dispatcher.suspend(this) @@ -333,7 +341,7 @@ private[akka] class ActorCell( } } catch { case e ⇒ - system.eventStream.publish(Error(e, self.toString, e.getMessage)) + system.eventStream.publish(Error(e, self.path.toString, e.getMessage)) throw e } } @@ -350,7 +358,7 @@ private[akka] class ActorCell( } def autoReceiveMessage(msg: Envelope) { - if (system.settings.DebugAutoReceive) system.eventStream.publish(Debug(self.toString, "received AutoReceiveMessage " + msg)) + if (system.settings.DebugAutoReceive) system.eventStream.publish(Debug(self.path.toString, "received AutoReceiveMessage " + msg)) if (stopping) msg.message match { case ChildTerminated ⇒ handleChildTerminated(sender) @@ -376,7 +384,7 @@ private[akka] class ActorCell( try { parent.tell(ChildTerminated, self) system.deathWatch.publish(Terminated(self)) - if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.toString, "stopped")) + if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "stopped")) } finally { currentMessage = null clearActorFields() @@ -386,8 +394,8 @@ private[akka] class ActorCell( final def handleFailure(child: ActorRef, cause: Throwable): Unit = childrenRefs.get(child.name) match { case Some(stats) if stats.child == child ⇒ if (!props.faultHandler.handleFailure(child, cause, stats, childrenRefs.values)) throw cause - case Some(stats) ⇒ system.eventStream.publish(Warning(self.toString, "dropping Failed(" + cause + ") from unknown child " + child + " matching names but not the same, was: " + stats.child)) - case None ⇒ system.eventStream.publish(Warning(self.toString, "dropping Failed(" + cause + ") from unknown child " + child)) + case Some(stats) ⇒ system.eventStream.publish(Warning(self.path.toString, "dropping Failed(" + cause + ") from unknown child " + child + " matching names but not the same, was: " + stats.child)) + case None ⇒ system.eventStream.publish(Warning(self.path.toString, "dropping Failed(" + cause + ") from unknown child " + child)) } final def handleChildTerminated(child: ActorRef): Unit = { diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 398aae0039..ade2ba3a53 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -153,7 +153,7 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable that.asInstanceOf[ActorRef].address == address } - override def toString = "Actor[%s]".format(address) + override def toString = "Actor[%s]".format(path) } /** diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 1e96d70395..9cea955cd3 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -139,6 +139,14 @@ trait ActorRefFactory { */ protected def isDuplicate(name: String): Boolean + /** + * This method is called after successfully associating an ActorRef with a + * name; that name will have been found to be “no duplicate” by isDuplicate(), + * so this hook is used to implement a reservation scheme: isDuplicate + * reserves a name slot and actorCreated() fills it with the right ActorRef. + */ + protected def actorCreated(name: String, actor: ActorRef): Unit + def actorOf(props: Props): ActorRef = provider.actorOf(systemImpl, props, guardian, randomName(), false) def actorOf(props: Props, name: String): ActorRef = { @@ -146,7 +154,9 @@ trait ActorRefFactory { throw new InvalidActorNameException("actor name must not be null, empty or start with $") if (isDuplicate(name)) throw new InvalidActorNameException("actor name " + name + " is not unique!") - provider.actorOf(systemImpl, props, guardian, name, false) + val a = provider.actorOf(systemImpl, props, guardian, name, false) + actorCreated(name, a) + a } def actorOf[T <: Actor](implicit m: Manifest[T]): ActorRef = actorOf(Props(m.erasure.asInstanceOf[Class[_ <: Actor]])) diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 97c0d16e4d..75904086cf 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -274,6 +274,9 @@ abstract class ActorSystem extends ActorRefFactory { class ActorSystemImpl(val name: String, val applicationConfig: Config) extends ActorSystem { + if (!name.matches("""^\w+$""")) + throw new IllegalArgumentException("invalid ActorSystem name '" + name + "', must contain only word characters (i.e. [a-zA-Z_0-9])") + import ActorSystem._ val settings = new Settings(applicationConfig, name) @@ -299,12 +302,9 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A actors.putIfAbsent(name, deadLetters) ne null } - override def actorOf(props: Props, name: String): ActorRef = { - val actor = super.actorOf(props, name) - // this would have thrown an exception in case of a duplicate name + protected def actorCreated(name: String, actor: ActorRef): Unit = { actors.replace(name, actor) deathWatch.subscribe(actorsJanitor, actor) - actor } import settings._ diff --git a/akka-actor/src/main/scala/akka/event/Logging.scala b/akka-actor/src/main/scala/akka/event/Logging.scala index ad99dd5af2..b8da310b1d 100644 --- a/akka-actor/src/main/scala/akka/event/Logging.scala +++ b/akka-actor/src/main/scala/akka/event/Logging.scala @@ -169,11 +169,11 @@ object LogSource { } implicit val fromActor: LogSource[Actor] = new LogSource[Actor] { - def genString(a: Actor) = a.self.toString + def genString(a: Actor) = a.self.path.toString } implicit val fromActorRef: LogSource[ActorRef] = new LogSource[ActorRef] { - def genString(a: ActorRef) = a.toString + def genString(a: ActorRef) = a.path.toString } // this one unfortunately does not work as implicit, because existential types have some weird behavior diff --git a/akka-testkit/src/main/scala/akka/testkit/TestEventListener.scala b/akka-testkit/src/main/scala/akka/testkit/TestEventListener.scala index 543d443da6..afa174ead6 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestEventListener.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestEventListener.scala @@ -151,6 +151,15 @@ object EventFilter { if (message ne null) Left(message) else Option(pattern) map (new Regex(_)) toRight start, message ne null)(occurrences) + /** + * Create a filter for Error events which do not have a cause set (i.e. use + * implicitly supplied Logging.Error.NoCause). See apply() for more details. + */ + def error(message: String = null, source: String = null, start: String = "", pattern: String = null, occurrences: Int = Int.MaxValue): EventFilter = + ErrorFilter(Logging.Error.NoCause.getClass, Option(source), + if (message ne null) Left(message) else Option(pattern) map (new Regex(_)) toRight start, + message ne null)(occurrences) + /** * Create a filter for Warning events. Give up to one of start and pattern: * @@ -447,12 +456,12 @@ class TestEventListener extends Logging.DefaultLogger { case event: LogEvent ⇒ if (!filter(event)) print(event) case DeadLetter(msg: SystemMessage, _, rcp) ⇒ if (!msg.isInstanceOf[Terminate]) { - val event = Warning(rcp.toString, "received dead system message: " + msg) + val event = Warning(rcp.path.toString, "received dead system message: " + msg) if (!filter(event)) print(event) } case DeadLetter(msg, snd, rcp) ⇒ if (!msg.isInstanceOf[Terminated]) { - val event = Warning(rcp.toString, "received dead letter from " + snd + ": " + msg) + val event = Warning(rcp.path.toString, "received dead letter from " + snd + ": " + msg) if (!filter(event)) print(event) } } diff --git a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala index 7b2df6efab..fb7216d69d 100644 --- a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala @@ -39,11 +39,24 @@ object AkkaSpec { ConfigFactory.parseMap(map.asJava) } + def getCallerName: String = { + val s = Thread.currentThread.getStackTrace map (_.getClassName) drop 1 dropWhile (_ matches ".*AkkaSpec.?$") + s.head.replaceFirst(""".*\.""", "").replaceAll("[^a-zA-Z_0-9]", "_") + } + } -abstract class AkkaSpec(_system: ActorSystem = ActorSystem(getClass.getSimpleName, AkkaSpec.testConf)) +abstract class AkkaSpec(_system: ActorSystem) extends TestKit(_system) with WordSpec with MustMatchers with BeforeAndAfterAll { + def this(config: Config) = this(ActorSystem(AkkaSpec.getCallerName, config.withFallback(AkkaSpec.testConf))) + + def this(s: String) = this(ConfigFactory.parseString(s, ConfigParseOptions.defaults)) + + def this(configMap: Map[String, _]) = this(AkkaSpec.mapToConfig(configMap)) + + def this() = this(ActorSystem(AkkaSpec.getCallerName, AkkaSpec.testConf)) + val log: LoggingAdapter = Logging(system, this.getClass) final override def beforeAll { @@ -62,14 +75,6 @@ abstract class AkkaSpec(_system: ActorSystem = ActorSystem(getClass.getSimpleNam protected def atTermination() {} - def this(config: Config) = this(ActorSystem(getClass.getSimpleName, config.withFallback(AkkaSpec.testConf))) - - def this(s: String) = this(ConfigFactory.parseString(s, ConfigParseOptions.defaults)) - - def this(configMap: Map[String, _]) = { - this(AkkaSpec.mapToConfig(configMap)) - } - def actorOf(props: Props): ActorRef = system.actorOf(props) def actorOf[T <: Actor](clazz: Class[T]): ActorRef = actorOf(Props(clazz)) From b65799c7f39eeabd203759eb86279049d1d1d7e1 Mon Sep 17 00:00:00 2001 From: Roland Date: Wed, 30 Nov 2011 23:30:56 +0100 Subject: [PATCH 08/30] remove ActorRef.address & ActorRef.name - address => path.toString - name => path.name - forward hashCode, equals and compareTo to path - implement recursive efficient ActorPath.compareTo - fix ActorPath.equals endless recursion - remove wrong warning in ActorCell.systemInvoke.supervise() --- .../scala/akka/actor/SupervisorTreeSpec.scala | 6 +-- .../scala/akka/routing/ActorPoolSpec.scala | 4 +- .../src/main/scala/akka/actor/ActorCell.scala | 16 ++++---- .../src/main/scala/akka/actor/ActorPath.scala | 31 ++++++++++++++- .../src/main/scala/akka/actor/ActorRef.scala | 39 ++++--------------- .../scala/akka/actor/ActorRefProvider.scala | 6 +-- .../scala/akka/dispatch/Dispatchers.scala | 4 +- .../main/scala/akka/dispatch/Mailbox.scala | 2 +- .../src/main/scala/akka/event/Logging.scala | 10 +---- .../src/main/scala/akka/routing/Routing.scala | 2 +- .../src/main/scala/akka/remote/Gossiper.scala | 4 +- .../src/main/scala/akka/remote/Remote.scala | 4 +- .../akka/remote/RemoteActorRefProvider.scala | 4 +- .../main/scala/DiningHakkersOnBecome.scala | 2 +- .../src/main/scala/DiningHakkersOnFsm.scala | 2 +- .../scala/akka/testkit/TestActorRef.scala | 7 +++- 16 files changed, 69 insertions(+), 74 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala index a77f8c20c1..f8e92b93dc 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SupervisorTreeSpec.scala @@ -24,15 +24,15 @@ class SupervisorTreeSpec extends AkkaSpec with ImplicitSender { def receive = { case p: Props ⇒ sender ! context.actorOf(p) } - override def preRestart(cause: Throwable, msg: Option[Any]) { testActor ! self.address } + override def preRestart(cause: Throwable, msg: Option[Any]) { testActor ! self.path } }).withFaultHandler(OneForOneStrategy(List(classOf[Exception]), 3, 1000)) val headActor = actorOf(p) val middleActor = (headActor ? p).as[ActorRef].get val lastActor = (middleActor ? p).as[ActorRef].get middleActor ! Kill - expectMsg(middleActor.address) - expectMsg(lastActor.address) + expectMsg(middleActor.path) + expectMsg(lastActor.path) expectNoMsg(2 seconds) headActor.stop() } diff --git a/akka-actor-tests/src/test/scala/akka/routing/ActorPoolSpec.scala b/akka-actor-tests/src/test/scala/akka/routing/ActorPoolSpec.scala index d9503e31b7..ff65b46bf5 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/ActorPoolSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/ActorPoolSpec.scala @@ -263,7 +263,7 @@ class ActorPoolSpec extends AkkaSpec { def instance(p: Props): ActorRef = actorOf(p.withCreator(new Actor { def receive = { case _ ⇒ - delegates put (self.address, "") + delegates put (self.path.toString, "") latch1.countDown() } })) @@ -291,7 +291,7 @@ class ActorPoolSpec extends AkkaSpec { def instance(p: Props) = actorOf(p.withCreator(new Actor { def receive = { case _ ⇒ - delegates put (self.address, "") + delegates put (self.path.toString, "") latch2.countDown() } })) diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 6e40ab8f34..8c17fa418e 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -266,15 +266,15 @@ private[akka] class ActorCell( } def supervise(child: ActorRef): Unit = { - val dl = system.deadLetters - childrenRefs.get(child.name) match { - case None | Some(ChildRestartStats(`dl`, _, _)) ⇒ - childrenRefs = childrenRefs.updated(child.name, ChildRestartStats(child)) + childrenRefs.get(child.path.name) match { + case None ⇒ + childrenRefs = childrenRefs.updated(child.path.name, ChildRestartStats(child)) if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "now supervising " + child)) case Some(ChildRestartStats(`child`, _, _)) ⇒ - system.eventStream.publish(Warning(self.path.toString, "Already supervising " + child)) + // this is the nominal case where we created the child and entered it in actorCreated() above + if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "now supervising " + child)) case Some(ChildRestartStats(c, _, _)) ⇒ - system.eventStream.publish(Warning(self.path.toString, "Already supervising other child with same name '" + child.name + "', old: " + c + " new: " + child)) + system.eventStream.publish(Warning(self.path.toString, "Already supervising other child with same name '" + child.path.name + "', old: " + c + " new: " + child)) } } @@ -392,14 +392,14 @@ private[akka] class ActorCell( } } - final def handleFailure(child: ActorRef, cause: Throwable): Unit = childrenRefs.get(child.name) match { + final def handleFailure(child: ActorRef, cause: Throwable): Unit = childrenRefs.get(child.path.name) match { case Some(stats) if stats.child == child ⇒ if (!props.faultHandler.handleFailure(child, cause, stats, childrenRefs.values)) throw cause case Some(stats) ⇒ system.eventStream.publish(Warning(self.path.toString, "dropping Failed(" + cause + ") from unknown child " + child + " matching names but not the same, was: " + stats.child)) case None ⇒ system.eventStream.publish(Warning(self.path.toString, "dropping Failed(" + cause + ") from unknown child " + child)) } final def handleChildTerminated(child: ActorRef): Unit = { - childrenRefs -= child.name + childrenRefs -= child.path.name props.faultHandler.handleChildTerminated(child, children) if (stopping && childrenRefs.isEmpty) doTerminate() } diff --git a/akka-actor/src/main/scala/akka/actor/ActorPath.scala b/akka-actor/src/main/scala/akka/actor/ActorPath.scala index 4203c5c9b5..1146c4e0f0 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorPath.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorPath.scala @@ -12,8 +12,14 @@ object ActorPath { /** * Actor path is a unique path to an actor that shows the creation path * up through the actor tree to the root actor. + * + * ActorPath defines a natural ordering (so that ActorRefs can be put into + * collections with this requirement); this ordering is intended to be as fast + * as possible, which owing to the bottom-up recursive nature of ActorPath + * is sorted by path elements FROM RIGHT TO LEFT, where RootActorPath > + * ChildActorPath in case the number of elements is different. */ -sealed trait ActorPath { +sealed trait ActorPath extends Comparable[ActorPath] { /** * The Address under which this path can be reached; walks up the tree to * the RootActorPath. @@ -67,6 +73,11 @@ final case class RootActorPath(address: Address, name: String = ActorPath.separa def pathElements: Iterable[String] = Iterable.empty override val toString = address + name + + def compareTo(other: ActorPath) = other match { + case r: RootActorPath ⇒ toString compareTo r.toString + case c: ChildActorPath ⇒ 1 + } } final class ChildActorPath(val parent: ActorPath, val name: String) extends ActorPath { @@ -113,7 +124,8 @@ final class ChildActorPath(val parent: ActorPath, val name: String) extends Acto @tailrec def rec(left: ActorPath, right: ActorPath): Boolean = if (left eq right) true - else if (left.isInstanceOf[RootActorPath] || right.isInstanceOf[RootActorPath]) left == right + else if (left.isInstanceOf[RootActorPath]) left equals right + else if (right.isInstanceOf[RootActorPath]) right equals left else left.name == right.name && rec(left.parent, right.parent) other match { @@ -133,5 +145,20 @@ final class ChildActorPath(val parent: ActorPath, val name: String) extends Acto finalizeHash(rec(this, startHash(42), startMagicA, startMagicB)) } + + def compareTo(other: ActorPath) = { + @tailrec + def rec(left: ActorPath, right: ActorPath): Int = + if (left eq right) 0 + else if (left.isInstanceOf[RootActorPath]) left compareTo right + else if (right.isInstanceOf[RootActorPath]) -(right compareTo left) + else { + val x = left.name compareTo right.name + if (x == 0) rec(left.parent, right.parent) + else x + } + + rec(this, other) + } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index ade2ba3a53..463bb994a5 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -43,31 +43,23 @@ import akka.event.DeathWatch * actor.stop() * * + * The natural ordering of ActorRef is defined in terms of its [[akka.actor.ActorPath]]. + * * @author Jonas Bonér */ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable { scalaRef: ScalaActorRef ⇒ // Only mutable for RemoteServer in order to maintain identity across nodes - /** - * Returns the name for this actor. Locally unique (across siblings). - */ - def name: String - /** * Returns the path for this actor (from this actor up to the root actor). */ def path: ActorPath - /** - * Returns the absolute address for this actor in the form hostname:port/path/to/actor. - */ - def address: String - /** * Comparison only takes address into account. */ - def compareTo(other: ActorRef) = this.address compareTo other.address + def compareTo(other: ActorRef) = this.path compareTo other.path /** * Sends the specified message to the sender, i.e. fire-and-forget semantics.

@@ -146,11 +138,12 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable */ def stopsWatching(subject: ActorRef): ActorRef //TODO FIXME REMOVE THIS - override def hashCode: Int = HashCode.hash(HashCode.SEED, address) + // FIXME check if we should scramble the bits or whether they can stay the same + override def hashCode: Int = path.hashCode - override def equals(that: Any): Boolean = { - that.isInstanceOf[ActorRef] && - that.asInstanceOf[ActorRef].address == address + override def equals(that: Any): Boolean = that match { + case other: ActorRef ⇒ path == other.path + case _ ⇒ false } override def toString = "Actor[%s]".format(path) @@ -171,10 +164,6 @@ class LocalActorRef private[akka] ( _hotswap: Stack[PartialFunction[Any, Unit]] = Props.noHotSwap) extends ActorRef with ScalaActorRef { - def name = path.name - - def address: String = path.toString - /* * actorCell.start() publishes actorCell & this to the dispatcher, which * means that messages may be processed theoretically before the constructor @@ -329,9 +318,6 @@ case class SerializedActorRef(hostname: String, port: Int, path: String) { */ trait MinimalActorRef extends ActorRef with ScalaActorRef { - private[akka] val uuid: Uuid = newUuid() - def name: String = uuid.toString - def startsWatching(actorRef: ActorRef): ActorRef = actorRef def stopsWatching(actorRef: ActorRef): ActorRef = actorRef @@ -354,7 +340,6 @@ trait MinimalActorRef extends ActorRef with ScalaActorRef { object MinimalActorRef { def apply(_path: ActorPath)(receive: PartialFunction[Any, Unit]): ActorRef = new MinimalActorRef { def path = _path - def address = path.toString override def !(message: Any)(implicit sender: ActorRef = null): Unit = if (receive.isDefinedAt(message)) receive(message) } @@ -386,10 +371,6 @@ class DeadLetterActorRef(val eventStream: EventStream) extends MinimalActorRef { brokenPromise = new KeptPromise[Any](Left(new ActorKilledException("In DeadLetterActorRef, promises are always broken.")))(dispatcher) } - override val name: String = "dead-letter" - - def address: String = path.toString - override def isTerminated(): Boolean = true override def !(message: Any)(implicit sender: ActorRef = this): Unit = message match { @@ -411,10 +392,6 @@ class DeadLetterActorRef(val eventStream: EventStream) extends MinimalActorRef { class AskActorRef(val path: ActorPath, provider: ActorRefProvider, deathWatch: DeathWatch, timeout: Timeout, val dispatcher: MessageDispatcher) extends MinimalActorRef { final val result = new DefaultPromise[Any](timeout)(dispatcher) - override def name = path.name - - def address: String = path.toString - { val callback: Future[Any] ⇒ Unit = { _ ⇒ deathWatch.publish(Terminated(AskActorRef.this)); whenDone() } result onComplete callback diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 9cea955cd3..039baa3dc6 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -222,15 +222,11 @@ class LocalActorRefProvider( @volatile var causeOfTermination: Option[Throwable] = None - override val name = "bubble-walker" - // FIXME (actor path): move the root path to the new root guardian - val path = rootPath / name + val path = rootPath / "bubble-walker" val address = path.toString - override def toString = name - override def stop() = stopped switchOn { terminationFuture.complete(causeOfTermination.toLeft(())) } diff --git a/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala b/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala index 42c96c8296..c905a7297d 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala @@ -76,7 +76,7 @@ class Dispatchers(val settings: ActorSystem.Settings, val prerequisites: Dispatc */ def newPinnedDispatcher(actor: LocalActorRef) = actor match { case null ⇒ new PinnedDispatcher(prerequisites, null, "anon", MailboxType, settings.DispatcherDefaultShutdown) - case some ⇒ new PinnedDispatcher(prerequisites, some.underlying, some.address, MailboxType, settings.DispatcherDefaultShutdown) + case some ⇒ new PinnedDispatcher(prerequisites, some.underlying, some.path.toString, MailboxType, settings.DispatcherDefaultShutdown) } /** @@ -87,7 +87,7 @@ class Dispatchers(val settings: ActorSystem.Settings, val prerequisites: Dispatc */ def newPinnedDispatcher(actor: LocalActorRef, mailboxType: MailboxType) = actor match { case null ⇒ new PinnedDispatcher(prerequisites, null, "anon", mailboxType, settings.DispatcherDefaultShutdown) - case some ⇒ new PinnedDispatcher(prerequisites, some.underlying, some.address, mailboxType, settings.DispatcherDefaultShutdown) + case some ⇒ new PinnedDispatcher(prerequisites, some.underlying, some.path.toString, mailboxType, settings.DispatcherDefaultShutdown) } /** diff --git a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala index 7d3b3c3d8b..ebc8f5df6b 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala @@ -196,7 +196,7 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes } } catch { case e ⇒ - actor.system.eventStream.publish(Error(e, actor.self.toString, "exception during processing system messages, dropping " + SystemMessage.size(nextMessage) + " messages!")) + actor.system.eventStream.publish(Error(e, actor.self.path.toString, "exception during processing system messages, dropping " + SystemMessage.size(nextMessage) + " messages!")) throw e } } diff --git a/akka-actor/src/main/scala/akka/event/Logging.scala b/akka-actor/src/main/scala/akka/event/Logging.scala index b8da310b1d..374151376e 100644 --- a/akka-actor/src/main/scala/akka/event/Logging.scala +++ b/akka-actor/src/main/scala/akka/event/Logging.scala @@ -404,12 +404,6 @@ object Logging { event.thread.getName, event.logSource, event.message)) - - def instanceName(instance: AnyRef): String = instance match { - case null ⇒ "NULL" - case a: ActorRef ⇒ a.address - case _ ⇒ simpleName(instance) - } } /** @@ -420,9 +414,7 @@ object Logging { * akka.stdout-loglevel in akka.conf. */ class StandardOutLogger extends MinimalActorRef with StdOutLogger { - override val name: String = "standard-out-logger" - val path: ActorPath = null // pathless - val address: String = name + val path: ActorPath = new RootActorPath(LocalAddress("all-systems"), "/StandardOutLogger") override val toString = "StandardOutLogger" override def !(message: Any)(implicit sender: ActorRef = null): Unit = print(message) } diff --git a/akka-actor/src/main/scala/akka/routing/Routing.scala b/akka-actor/src/main/scala/akka/routing/Routing.scala index 293c1abb4b..48814a4e43 100644 --- a/akka-actor/src/main/scala/akka/routing/Routing.scala +++ b/akka-actor/src/main/scala/akka/routing/Routing.scala @@ -171,7 +171,7 @@ abstract private[akka] class AbstractRoutedActorRef(val system: ActorSystem, val * A RoutedActorRef is an ActorRef that has a set of connected ActorRef and it uses a Router to send a message to * on (or more) of these actors. */ -private[akka] class RoutedActorRef(system: ActorSystem, val routedProps: RoutedProps, val supervisor: ActorRef, override val name: String) extends AbstractRoutedActorRef(system, routedProps) { +private[akka] class RoutedActorRef(system: ActorSystem, val routedProps: RoutedProps, val supervisor: ActorRef, name: String) extends AbstractRoutedActorRef(system, routedProps) { val path = supervisor.path / name diff --git a/akka-remote/src/main/scala/akka/remote/Gossiper.scala b/akka-remote/src/main/scala/akka/remote/Gossiper.scala index 250fbc727c..4854489839 100644 --- a/akka-remote/src/main/scala/akka/remote/Gossiper.scala +++ b/akka-remote/src/main/scala/akka/remote/Gossiper.scala @@ -256,12 +256,12 @@ class Gossiper(remote: Remote) { log.error(cause, cause.toString) case None ⇒ - val error = new RemoteException("Gossip to [%s] timed out".format(connection.address)) + val error = new RemoteException("Gossip to [%s] timed out".format(connection.path)) log.error(error, error.toString) } } catch { case e: Exception ⇒ - log.error(e, "Could not gossip to [{}] due to: {}", connection.address, e.toString) + log.error(e, "Could not gossip to [{}] due to: {}", connection.path, e.toString) } seeds exists (peer == _) diff --git a/akka-remote/src/main/scala/akka/remote/Remote.scala b/akka-remote/src/main/scala/akka/remote/Remote.scala index 4eccce6b33..6c8f4ca69d 100644 --- a/akka-remote/src/main/scala/akka/remote/Remote.scala +++ b/akka-remote/src/main/scala/akka/remote/Remote.scala @@ -84,8 +84,8 @@ class Remote(val system: ActorSystemImpl, val nodename: String) { _server = remote - val daemonAddress = remoteDaemon.address //Force init of daemon - log.info("Starting remote server on [{}] and starting remoteDaemon with address [{}]", remoteAddress, daemonAddress) + val daemonPath = remoteDaemon.path //Force init of daemon + log.info("Starting remote server on [{}] and starting remoteDaemon with path [{}]", remoteAddress, daemonPath) } } diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index 6240ed1b97..b82b953747 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -247,13 +247,13 @@ class RemoteActorRefProvider( throw cause case None ⇒ - val error = new RemoteException("Remote system command to [%s] timed out".format(connection.address)) + val error = new RemoteException("Remote system command to [%s] timed out".format(connection.path)) log.error(error, error.toString) throw error } } catch { case e: Exception ⇒ - log.error(e, "Could not send remote system command to [{}] due to: {}", connection.address, e.toString) + log.error(e, "Could not send remote system command to [{}] due to: {}", connection.path, e.toString) throw e } } else { diff --git a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala index 2c23940c9f..346324e3b2 100644 --- a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala +++ b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala @@ -75,7 +75,7 @@ class Hakker(name: String, left: ActorRef, right: ActorRef) extends Actor { //back to think about how he should obtain his chopsticks :-) def waiting_for(chopstickToWaitFor: ActorRef, otherChopstick: ActorRef): Receive = { case Taken(`chopstickToWaitFor`) ⇒ - println("%s has picked up %s and %s, and starts to eat", name, left.address, right.address) + println("%s has picked up %s and %s, and starts to eat", name, left.path.name, right.path.name) become(eating) system.scheduler.scheduleOnce(self, Think, 5 seconds) diff --git a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala index 987f630784..a76ecbc619 100644 --- a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala +++ b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala @@ -125,7 +125,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit } private def startEating(left: ActorRef, right: ActorRef): State = { - println("%s has picked up %s and %s, and starts to eat", name, left.address, right.address) + println("%s has picked up %s and %s, and starts to eat", name, left.path.name, right.path.name) goto(Eating) using TakenChopsticks(Some(left), Some(right)) forMax (5 seconds) } diff --git a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala index 6f03df59b2..ed4472e6f6 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala @@ -42,9 +42,12 @@ class TestActorRef[T <: Actor]( */ def underlyingActor: T = underlyingActorInstance.asInstanceOf[T] - override def toString = "TestActor[" + address + "]" + override def toString = "TestActor[" + path + "]" - override def equals(other: Any) = other.isInstanceOf[TestActorRef[_]] && other.asInstanceOf[TestActorRef[_]].address == address + override def equals(other: Any) = other match { + case r: TestActorRef[_] ⇒ path == r.path + case _ ⇒ false + } } object TestActorRef { From 6b9cdc5f657c0b3d1ea40253a952ee1fc017e2bf Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 1 Dec 2011 14:29:33 +0100 Subject: [PATCH 09/30] fix ActorRef serialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - represent it by SerializedActorRef(path), i.e. only a “tagged” string - remove serialize/deserialize from ActorRefProvider interface - adapt test since deadLetters is returned when nothing found instead of exception - multi-jvm tests are still broken, but that is due to look-up of remote actors, which I have just not done yet --- .../test/scala/akka/actor/ActorRefSpec.scala | 8 +- .../src/main/scala/akka/actor/ActorRef.scala | 16 +- .../scala/akka/actor/ActorRefProvider.scala | 8 - .../akka/actor/mailbox/DurableMailbox.scala | 10 +- .../actor/mailbox/BSONSerialization.scala | 11 +- .../main/java/akka/remote/RemoteProtocol.java | 250 +++--------------- .../src/main/protocol/RemoteProtocol.proto | 4 +- .../src/main/scala/akka/remote/Remote.scala | 10 +- .../akka/remote/RemoteActorRefProvider.scala | 19 +- .../DirectRoutedRemoteActorMultiJvmNode2.conf | 2 +- 10 files changed, 59 insertions(+), 279 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala index 7fd324a33a..ef11ca1e98 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorRefSpec.scala @@ -278,14 +278,14 @@ class ActorRefSpec extends AkkaSpec { " Use akka.serialization.Serialization.currentSystem.withValue(system) { ... }" } - "must throw exception on deserialize if not present in actor hierarchy (and remoting is not enabled)" in { + "must return deadLetters on deserialize if not present in actor hierarchy (and remoting is not enabled)" in { import java.io._ val baos = new ByteArrayOutputStream(8192 * 32) val out = new ObjectOutputStream(baos) val addr = system.asInstanceOf[ActorSystemImpl].provider.rootPath.address - val serialized = SerializedActorRef(addr.hostPort, 0, "/this/path/does/not/exist") + val serialized = SerializedActorRef(addr + "/non-existing") out.writeObject(serialized) @@ -294,9 +294,7 @@ class ActorRefSpec extends AkkaSpec { Serialization.currentSystem.withValue(system.asInstanceOf[ActorSystemImpl]) { val in = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray)) - (intercept[java.lang.IllegalStateException] { - in.readObject - }).getMessage must be === "Could not deserialize ActorRef" + in.readObject must be === system.deadLetters } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 463bb994a5..93a119efcf 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -246,7 +246,7 @@ class LocalActorRef private[akka] ( protected[akka] override def restart(cause: Throwable): Unit = actorCell.restart(cause) @throws(classOf[java.io.ObjectStreamException]) - private def writeReplace(): AnyRef = actorCell.provider.serialize(this) + private def writeReplace(): AnyRef = SerializedActorRef(path.toString) } /** @@ -293,23 +293,15 @@ trait ScalaActorRef { ref: ActorRef ⇒ * Memento pattern for serializing ActorRefs transparently */ // FIXME: remove and replace by ActorPath.toString -case class SerializedActorRef(hostname: String, port: Int, path: String) { +case class SerializedActorRef(path: String) { import akka.serialization.Serialization.currentSystem - // FIXME this is broken, but see above - def this(address: Address, path: String) = this(address.hostPort, 0, path) - def this(remoteAddress: RemoteAddress, path: String) = this(remoteAddress.host, remoteAddress.port, path) - def this(remoteAddress: InetSocketAddress, path: String) = this(remoteAddress.getAddress.getHostAddress, remoteAddress.getPort, path) //TODO FIXME REMOVE - @throws(classOf[java.io.ObjectStreamException]) def readResolve(): AnyRef = currentSystem.value match { case null ⇒ throw new IllegalStateException( "Trying to deserialize a serialized ActorRef without an ActorSystem in scope." + " Use akka.serialization.Serialization.currentSystem.withValue(system) { ... }") - case someSystem ⇒ someSystem.provider.deserialize(this) match { - case Some(actor) ⇒ actor - case None ⇒ throw new IllegalStateException("Could not deserialize ActorRef") - } + case someSystem ⇒ someSystem.actorFor(path) } } @@ -419,5 +411,5 @@ class AskActorRef(val path: ActorPath, provider: ActorRefProvider, deathWatch: D override def stop(): Unit = if (!isTerminated) result.completeWithException(new ActorKilledException("Stopped")) @throws(classOf[java.io.ObjectStreamException]) - private def writeReplace(): AnyRef = provider.serialize(this) + private def writeReplace(): AnyRef = SerializedActorRef(path.toString) } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 039baa3dc6..70394b1548 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -96,10 +96,6 @@ trait ActorRefProvider { */ def actorFor(p: Iterable[String]): ActorRef - private[akka] def deserialize(actor: SerializedActorRef): Option[ActorRef] - - private[akka] def serialize(actor: ActorRef): SerializedActorRef - private[akka] def createDeathWatch(): DeathWatch /** @@ -372,10 +368,6 @@ class LocalActorRefProvider( new RoutedActorRef(system, props, supervisor, name) } - private[akka] def deserialize(actor: SerializedActorRef): Option[ActorRef] = Some(actorFor(actor.path)) - - private[akka] def serialize(actor: ActorRef): SerializedActorRef = new SerializedActorRef(rootPath.address, actor.path.toString) - private[akka] def createDeathWatch(): DeathWatch = new LocalDeathWatch private[akka] def ask(message: Any, recipient: ActorRef, within: Timeout): Future[Any] = { diff --git a/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableMailbox.scala b/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableMailbox.scala index 6ace3d84aa..2006d73478 100644 --- a/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableMailbox.scala +++ b/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableMailbox.scala @@ -52,10 +52,7 @@ trait DurableMessageSerialization { def serialize(durableMessage: Envelope): Array[Byte] = { - def serializeActorRef(ref: ActorRef): ActorRefProtocol = { - val serRef = owner.system.provider.serialize(ref) - ActorRefProtocol.newBuilder.setPath(serRef.path).setHost(serRef.hostname).setPort(serRef.port).build - } + def serializeActorRef(ref: ActorRef): ActorRefProtocol = ActorRefProtocol.newBuilder.setPath(ref.path.toString).build val message = MessageSerializer.serialize(owner.system, durableMessage.message.asInstanceOf[AnyRef]) val builder = RemoteMessageProtocol.newBuilder @@ -68,10 +65,7 @@ trait DurableMessageSerialization { def deserialize(bytes: Array[Byte]): Envelope = { - def deserializeActorRef(refProtocol: ActorRefProtocol): ActorRef = { - val serRef = SerializedActorRef(refProtocol.getHost, refProtocol.getPort, refProtocol.getPath) - owner.system.provider.deserialize(serRef).getOrElse(owner.system.deadLetters) - } + def deserializeActorRef(refProtocol: ActorRefProtocol): ActorRef = owner.system.actorFor(refProtocol.getPath) val durableMessage = RemoteMessageProtocol.parseFrom(bytes) val message = MessageSerializer.deserialize(owner.system, durableMessage.getMessage) diff --git a/akka-durable-mailboxes/akka-mongo-mailbox/src/main/scala/akka/actor/mailbox/BSONSerialization.scala b/akka-durable-mailboxes/akka-mongo-mailbox/src/main/scala/akka/actor/mailbox/BSONSerialization.scala index 8882b2738e..4cfa97bc6b 100644 --- a/akka-durable-mailboxes/akka-mongo-mailbox/src/main/scala/akka/actor/mailbox/BSONSerialization.scala +++ b/akka-durable-mailboxes/akka-mongo-mailbox/src/main/scala/akka/actor/mailbox/BSONSerialization.scala @@ -29,11 +29,7 @@ class BSONSerializableMailbox(system: ActorSystem) extends SerializableBSONObjec val b = Map.newBuilder[String, Any] b += "_id" -> msg._id b += "ownerPath" -> msg.ownerPath - - val sender = systemImpl.provider.serialize(msg.sender) - b += "senderPath" -> sender.path - b += "senderHostname" -> sender.hostname - b += "senderPort" -> sender.port + b += "senderPath" -> msg.sender.path /** * TODO - Figure out a way for custom serialization of the message instance @@ -75,10 +71,7 @@ class BSONSerializableMailbox(system: ActorSystem) extends SerializableBSONObjec val msg = MessageSerializer.deserialize(system, msgData) val ownerPath = doc.as[String]("ownerPath") val senderPath = doc.as[String]("senderPath") - val senderHostname = doc.as[String]("senderHostname") - val senderPort = doc.as[Int]("senderPort") - val sender = systemImpl.provider.deserialize(SerializedActorRef(senderHostname, senderPort, senderPath)). - getOrElse(system.deadLetters) + val sender = systemImpl.actorOf(senderPath) MongoDurableMessage(ownerPath, msg, sender) } diff --git a/akka-remote/src/main/java/akka/remote/RemoteProtocol.java b/akka-remote/src/main/java/akka/remote/RemoteProtocol.java index abe7edf647..7f10eb6987 100644 --- a/akka-remote/src/main/java/akka/remote/RemoteProtocol.java +++ b/akka-remote/src/main/java/akka/remote/RemoteProtocol.java @@ -2711,15 +2711,7 @@ public final class RemoteProtocol { public interface ActorRefProtocolOrBuilder extends com.google.protobuf.MessageOrBuilder { - // required string host = 1; - boolean hasHost(); - String getHost(); - - // required uint32 port = 2; - boolean hasPort(); - int getPort(); - - // required string path = 3; + // required string path = 1; boolean hasPath(); String getPath(); } @@ -2752,53 +2744,11 @@ public final class RemoteProtocol { } private int bitField0_; - // required string host = 1; - public static final int HOST_FIELD_NUMBER = 1; - private java.lang.Object host_; - public boolean hasHost() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - public String getHost() { - java.lang.Object ref = host_; - if (ref instanceof String) { - return (String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - if (com.google.protobuf.Internal.isValidUtf8(bs)) { - host_ = s; - } - return s; - } - } - private com.google.protobuf.ByteString getHostBytes() { - java.lang.Object ref = host_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8((String) ref); - host_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - // required uint32 port = 2; - public static final int PORT_FIELD_NUMBER = 2; - private int port_; - public boolean hasPort() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - public int getPort() { - return port_; - } - - // required string path = 3; - public static final int PATH_FIELD_NUMBER = 3; + // required string path = 1; + public static final int PATH_FIELD_NUMBER = 1; private java.lang.Object path_; public boolean hasPath() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000001) == 0x00000001); } public String getPath() { java.lang.Object ref = path_; @@ -2827,8 +2777,6 @@ public final class RemoteProtocol { } private void initFields() { - host_ = ""; - port_ = 0; path_ = ""; } private byte memoizedIsInitialized = -1; @@ -2836,14 +2784,6 @@ public final class RemoteProtocol { byte isInitialized = memoizedIsInitialized; if (isInitialized != -1) return isInitialized == 1; - if (!hasHost()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasPort()) { - memoizedIsInitialized = 0; - return false; - } if (!hasPath()) { memoizedIsInitialized = 0; return false; @@ -2856,13 +2796,7 @@ public final class RemoteProtocol { throws java.io.IOException { getSerializedSize(); if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getHostBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeUInt32(2, port_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeBytes(3, getPathBytes()); + output.writeBytes(1, getPathBytes()); } getUnknownFields().writeTo(output); } @@ -2875,15 +2809,7 @@ public final class RemoteProtocol { size = 0; if (((bitField0_ & 0x00000001) == 0x00000001)) { size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, getHostBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(2, port_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(3, getPathBytes()); + .computeBytesSize(1, getPathBytes()); } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; @@ -3009,12 +2935,8 @@ public final class RemoteProtocol { public Builder clear() { super.clear(); - host_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - port_ = 0; - bitField0_ = (bitField0_ & ~0x00000002); path_ = ""; - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000001); return this; } @@ -3056,14 +2978,6 @@ public final class RemoteProtocol { if (((from_bitField0_ & 0x00000001) == 0x00000001)) { to_bitField0_ |= 0x00000001; } - result.host_ = host_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.port_ = port_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } result.path_ = path_; result.bitField0_ = to_bitField0_; onBuilt(); @@ -3081,12 +2995,6 @@ public final class RemoteProtocol { public Builder mergeFrom(akka.remote.RemoteProtocol.ActorRefProtocol other) { if (other == akka.remote.RemoteProtocol.ActorRefProtocol.getDefaultInstance()) return this; - if (other.hasHost()) { - setHost(other.getHost()); - } - if (other.hasPort()) { - setPort(other.getPort()); - } if (other.hasPath()) { setPath(other.getPath()); } @@ -3095,14 +3003,6 @@ public final class RemoteProtocol { } public final boolean isInitialized() { - if (!hasHost()) { - - return false; - } - if (!hasPort()) { - - return false; - } if (!hasPath()) { return false; @@ -3135,16 +3035,6 @@ public final class RemoteProtocol { } case 10: { bitField0_ |= 0x00000001; - host_ = input.readBytes(); - break; - } - case 16: { - bitField0_ |= 0x00000002; - port_ = input.readUInt32(); - break; - } - case 26: { - bitField0_ |= 0x00000004; path_ = input.readBytes(); break; } @@ -3154,67 +3044,10 @@ public final class RemoteProtocol { private int bitField0_; - // required string host = 1; - private java.lang.Object host_ = ""; - public boolean hasHost() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - public String getHost() { - java.lang.Object ref = host_; - if (!(ref instanceof String)) { - String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); - host_ = s; - return s; - } else { - return (String) ref; - } - } - public Builder setHost(String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - host_ = value; - onChanged(); - return this; - } - public Builder clearHost() { - bitField0_ = (bitField0_ & ~0x00000001); - host_ = getDefaultInstance().getHost(); - onChanged(); - return this; - } - void setHost(com.google.protobuf.ByteString value) { - bitField0_ |= 0x00000001; - host_ = value; - onChanged(); - } - - // required uint32 port = 2; - private int port_ ; - public boolean hasPort() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - public int getPort() { - return port_; - } - public Builder setPort(int value) { - bitField0_ |= 0x00000002; - port_ = value; - onChanged(); - return this; - } - public Builder clearPort() { - bitField0_ = (bitField0_ & ~0x00000002); - port_ = 0; - onChanged(); - return this; - } - - // required string path = 3; + // required string path = 1; private java.lang.Object path_ = ""; public boolean hasPath() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000001) == 0x00000001); } public String getPath() { java.lang.Object ref = path_; @@ -3230,19 +3063,19 @@ public final class RemoteProtocol { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000001; path_ = value; onChanged(); return this; } public Builder clearPath() { - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000001); path_ = getDefaultInstance().getPath(); onChanged(); return this; } void setPath(com.google.protobuf.ByteString value) { - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000001; path_ = value; onChanged(); } @@ -6864,35 +6697,34 @@ public final class RemoteProtocol { "\0132\026.MetadataEntryProtocol\"l\n\025RemoteContr" + "olProtocol\022!\n\013commandType\030\001 \002(\0162\014.Comman", "dType\022\016\n\006cookie\030\002 \001(\t\022 \n\006origin\030\003 \001(\0132\020." + - "AddressProtocol\"<\n\020ActorRefProtocol\022\014\n\004h" + - "ost\030\001 \002(\t\022\014\n\004port\030\002 \002(\r\022\014\n\004path\030\003 \002(\t\";\n" + - "\017MessageProtocol\022\017\n\007message\030\001 \002(\014\022\027\n\017mes" + - "sageManifest\030\002 \001(\014\")\n\014UuidProtocol\022\014\n\004hi" + - "gh\030\001 \002(\004\022\013\n\003low\030\002 \002(\004\"3\n\025MetadataEntryPr" + - "otocol\022\013\n\003key\030\001 \002(\t\022\r\n\005value\030\002 \002(\014\"1\n\017Ad" + - "dressProtocol\022\020\n\010hostname\030\001 \002(\t\022\014\n\004port\030" + - "\002 \002(\r\"7\n\021ExceptionProtocol\022\021\n\tclassname\030" + - "\001 \002(\t\022\017\n\007message\030\002 \002(\t\"\253\001\n!RemoteSystemD", - "aemonMessageProtocol\0223\n\013messageType\030\001 \002(" + - "\0162\036.RemoteSystemDaemonMessageType\022\021\n\tact" + - "orPath\030\002 \001(\t\022\017\n\007payload\030\003 \001(\014\022-\n\026replica" + - "teActorFromUuid\030\004 \001(\0132\r.UuidProtocol\"y\n\035" + - "DurableMailboxMessageProtocol\022$\n\trecipie" + - "nt\030\001 \002(\0132\021.ActorRefProtocol\022!\n\006sender\030\002 " + - "\001(\0132\021.ActorRefProtocol\022\017\n\007message\030\003 \002(\014*" + - "(\n\013CommandType\022\013\n\007CONNECT\020\001\022\014\n\010SHUTDOWN\020" + - "\002*K\n\026ReplicationStorageType\022\r\n\tTRANSIENT" + - "\020\001\022\023\n\017TRANSACTION_LOG\020\002\022\r\n\tDATA_GRID\020\003*>", - "\n\027ReplicationStrategyType\022\021\n\rWRITE_THROU" + - "GH\020\001\022\020\n\014WRITE_BEHIND\020\002*\241\002\n\035RemoteSystemD" + - "aemonMessageType\022\010\n\004STOP\020\001\022\007\n\003USE\020\002\022\013\n\007R" + - "ELEASE\020\003\022\022\n\016MAKE_AVAILABLE\020\004\022\024\n\020MAKE_UNA" + - "VAILABLE\020\005\022\016\n\nDISCONNECT\020\006\022\r\n\tRECONNECT\020" + - "\007\022\n\n\006RESIGN\020\010\022\n\n\006GOSSIP\020\t\022\031\n\025FAIL_OVER_C" + - "ONNECTIONS\020\024\022\026\n\022FUNCTION_FUN0_UNIT\020\025\022\025\n\021" + - "FUNCTION_FUN0_ANY\020\026\022\032\n\026FUNCTION_FUN1_ARG" + - "_UNIT\020\027\022\031\n\025FUNCTION_FUN1_ARG_ANY\020\030B\017\n\013ak" + - "ka.remoteH\001" + "AddressProtocol\" \n\020ActorRefProtocol\022\014\n\004p" + + "ath\030\001 \002(\t\";\n\017MessageProtocol\022\017\n\007message\030" + + "\001 \002(\014\022\027\n\017messageManifest\030\002 \001(\014\")\n\014UuidPr" + + "otocol\022\014\n\004high\030\001 \002(\004\022\013\n\003low\030\002 \002(\004\"3\n\025Met" + + "adataEntryProtocol\022\013\n\003key\030\001 \002(\t\022\r\n\005value" + + "\030\002 \002(\014\"1\n\017AddressProtocol\022\020\n\010hostname\030\001 " + + "\002(\t\022\014\n\004port\030\002 \002(\r\"7\n\021ExceptionProtocol\022\021" + + "\n\tclassname\030\001 \002(\t\022\017\n\007message\030\002 \002(\t\"\253\001\n!R" + + "emoteSystemDaemonMessageProtocol\0223\n\013mess", + "ageType\030\001 \002(\0162\036.RemoteSystemDaemonMessag" + + "eType\022\021\n\tactorPath\030\002 \001(\t\022\017\n\007payload\030\003 \001(" + + "\014\022-\n\026replicateActorFromUuid\030\004 \001(\0132\r.Uuid" + + "Protocol\"y\n\035DurableMailboxMessageProtoco" + + "l\022$\n\trecipient\030\001 \002(\0132\021.ActorRefProtocol\022" + + "!\n\006sender\030\002 \001(\0132\021.ActorRefProtocol\022\017\n\007me" + + "ssage\030\003 \002(\014*(\n\013CommandType\022\013\n\007CONNECT\020\001\022" + + "\014\n\010SHUTDOWN\020\002*K\n\026ReplicationStorageType\022" + + "\r\n\tTRANSIENT\020\001\022\023\n\017TRANSACTION_LOG\020\002\022\r\n\tD" + + "ATA_GRID\020\003*>\n\027ReplicationStrategyType\022\021\n", + "\rWRITE_THROUGH\020\001\022\020\n\014WRITE_BEHIND\020\002*\241\002\n\035R" + + "emoteSystemDaemonMessageType\022\010\n\004STOP\020\001\022\007" + + "\n\003USE\020\002\022\013\n\007RELEASE\020\003\022\022\n\016MAKE_AVAILABLE\020\004" + + "\022\024\n\020MAKE_UNAVAILABLE\020\005\022\016\n\nDISCONNECT\020\006\022\r" + + "\n\tRECONNECT\020\007\022\n\n\006RESIGN\020\010\022\n\n\006GOSSIP\020\t\022\031\n" + + "\025FAIL_OVER_CONNECTIONS\020\024\022\026\n\022FUNCTION_FUN" + + "0_UNIT\020\025\022\025\n\021FUNCTION_FUN0_ANY\020\026\022\032\n\026FUNCT" + + "ION_FUN1_ARG_UNIT\020\027\022\031\n\025FUNCTION_FUN1_ARG" + + "_ANY\020\030B\017\n\013akka.remoteH\001" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -6928,7 +6760,7 @@ public final class RemoteProtocol { internal_static_ActorRefProtocol_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_ActorRefProtocol_descriptor, - new java.lang.String[] { "Host", "Port", "Path", }, + new java.lang.String[] { "Path", }, akka.remote.RemoteProtocol.ActorRefProtocol.class, akka.remote.RemoteProtocol.ActorRefProtocol.Builder.class); internal_static_MessageProtocol_descriptor = diff --git a/akka-remote/src/main/protocol/RemoteProtocol.proto b/akka-remote/src/main/protocol/RemoteProtocol.proto index 6bd62c8c16..6fdc9acaaf 100644 --- a/akka-remote/src/main/protocol/RemoteProtocol.proto +++ b/akka-remote/src/main/protocol/RemoteProtocol.proto @@ -66,9 +66,7 @@ enum ReplicationStrategyType { * on the original node. */ message ActorRefProtocol { - required string host = 1; - required uint32 port = 2; - required string path = 3; + required string path = 1; } /** diff --git a/akka-remote/src/main/scala/akka/remote/Remote.scala b/akka-remote/src/main/scala/akka/remote/Remote.scala index 6c8f4ca69d..ee8a04efed 100644 --- a/akka-remote/src/main/scala/akka/remote/Remote.scala +++ b/akka-remote/src/main/scala/akka/remote/Remote.scala @@ -249,11 +249,8 @@ class RemoteMessage(input: RemoteMessageProtocol, remote: RemoteSupport, classLo val provider = remote.system.asInstanceOf[ActorSystemImpl].provider lazy val sender: ActorRef = - if (input.hasSender) - provider.deserialize( - SerializedActorRef(input.getSender.getHost, input.getSender.getPort, input.getSender.getPath)).getOrElse(throw new IllegalStateException("OHNOES")) - else - remote.system.deadLetters + if (input.hasSender) provider.actorFor(input.getSender.getPath) + else remote.system.deadLetters lazy val recipient: ActorRef = remote.system.actorFor(input.getRecipient.getPath) @@ -302,8 +299,7 @@ trait RemoteMarshallingOps { * Serializes the ActorRef instance into a Protocol Buffers (protobuf) Message. */ def toRemoteActorRefProtocol(actor: ActorRef): ActorRefProtocol = { - val rep = system.asInstanceOf[ActorSystemImpl].provider.serialize(actor) - ActorRefProtocol.newBuilder.setHost(rep.hostname).setPort(rep.port).setPath(rep.path).build + ActorRefProtocol.newBuilder.setPath(actor.path.toString).build } def createRemoteMessageProtocolBuilder( diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index b82b953747..41f25c70bd 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -193,21 +193,6 @@ class RemoteActorRefProvider( */ private[akka] def evict(path: ActorPath): Boolean = actors.remove(path) ne null - private[akka] def serialize(actor: ActorRef): SerializedActorRef = actor match { - case r: RemoteActorRef ⇒ new SerializedActorRef(r.remoteAddress, actor.path.toString) - case other ⇒ local.serialize(actor) - } - - private[akka] def deserialize(actor: SerializedActorRef): Option[ActorRef] = { - val remoteAddress = RemoteAddress(systemName, actor.hostname, actor.port) - if (optimizeLocalScoped_? && remoteAddress == remote.remoteAddress) { - Some(local.actorFor(actor.path)) - } else { - log.debug("{}: Creating RemoteActorRef with address [{}] connected to [{}]", remote.remoteAddress, actor.path, remoteAddress) - Some(RemoteActorRef(remote.system.provider, remote.server, remoteAddress, rootPath / actor.path, None)) // FIXME I know, this is broken - } - } - /** * Using (checking out) actor on a specific node. */ @@ -226,7 +211,7 @@ class RemoteActorRefProvider( .setPayload(ByteString.copyFrom(actorFactoryBytes)) .build() - val connectionFactory = () ⇒ deserialize(new SerializedActorRef(remoteAddress, remote.remoteDaemon.path.toString)).get + val connectionFactory = () ⇒ actorFor(RootActorPath(remoteAddress) / remote.remoteDaemon.path.pathElements) // try to get the connection for the remote address, if not already there then create it val connection = remoteDaemonConnectionManager.putIfAbsent(remoteAddress, connectionFactory) @@ -311,7 +296,7 @@ private[akka] case class RemoteActorRef private[akka] ( } @throws(classOf[java.io.ObjectStreamException]) - private def writeReplace(): AnyRef = provider.serialize(this) + private def writeReplace(): AnyRef = SerializedActorRef(path.toString) def startsWatching(actorRef: ActorRef): ActorRef = unsupported //FIXME Implement diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmNode2.conf b/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmNode2.conf index 1b1c7b398c..9268c7238e 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmNode2.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmNode2.conf @@ -1,4 +1,4 @@ -akka { +qakka { loglevel = "WARNING" actor { provider = "akka.remote.RemoteActorRefProvider" From cf020d708ab75bc01182fa30c39ea533220e1e01 Mon Sep 17 00:00:00 2001 From: Roland Date: Fri, 2 Dec 2011 09:34:19 +0100 Subject: [PATCH 10/30] rename top-level paths as per Jonas recommendation /user /system /null /temp /remote /service multi-jvm tests are disabled for the time being until remote look-up has been really implemented --- .../test/scala/akka/actor/DeployerSpec.scala | 64 +++++++++---------- .../src/main/scala/akka/actor/ActorRef.scala | 2 +- .../scala/akka/actor/ActorRefProvider.scala | 6 +- akka-docs/general/addressing.rst | 16 +++-- .../DirectRoutedRemoteActorMultiJvmNode1.conf | 6 +- .../DirectRoutedRemoteActorMultiJvmNode2.conf | 6 +- .../DirectRoutedRemoteActorMultiJvmSpec.scala | 2 +- .../NewRemoteActorMultiJvmNode1.conf | 2 +- .../NewRemoteActorMultiJvmNode2.conf | 2 +- .../RandomRoutedRemoteActorMultiJvmNode1.conf | 6 +- .../RandomRoutedRemoteActorMultiJvmNode2.conf | 6 +- .../RandomRoutedRemoteActorMultiJvmNode3.conf | 6 +- .../RandomRoutedRemoteActorMultiJvmNode4.conf | 6 +- ...ndRobinRoutedRemoteActorMultiJvmNode1.conf | 6 +- ...ndRobinRoutedRemoteActorMultiJvmNode2.conf | 6 +- ...ndRobinRoutedRemoteActorMultiJvmNode3.conf | 6 +- ...ndRobinRoutedRemoteActorMultiJvmNode4.conf | 6 +- ...rGatherRoutedRemoteActorMultiJvmNode1.conf | 6 +- ...rGatherRoutedRemoteActorMultiJvmNode2.conf | 6 +- ...rGatherRoutedRemoteActorMultiJvmNode3.conf | 6 +- ...rGatherRoutedRemoteActorMultiJvmNode4.conf | 6 +- project/AkkaBuild.scala | 2 +- 22 files changed, 94 insertions(+), 86 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala index b6dae1a6de..d9615a5831 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala @@ -14,59 +14,59 @@ import com.typesafe.config.ConfigParseOptions object DeployerSpec { val deployerConf = ConfigFactory.parseString(""" akka.actor.deployment { - /app/service1 { + /user/service1 { } - /app/service2 { + /user/service2 { router = round-robin nr-of-instances = 3 remote { nodes = ["wallace:2552", "gromit:2552"] } } - /app/service3 { + /user/service3 { create-as { class = "akka.actor.DeployerSpec$RecipeActor" } } - /app/service-auto { + /user/service-auto { router = round-robin nr-of-instances = auto } - /app/service-direct { + /user/service-direct { router = direct } - /app/service-direct2 { + /user/service-direct2 { router = direct # nr-of-instances ignored when router = direct nr-of-instances = 2 } - /app/service-round-robin { + /user/service-round-robin { router = round-robin } - /app/service-random { + /user/service-random { router = random } - /app/service-scatter-gather { + /user/service-scatter-gather { router = scatter-gather } - /app/service-least-cpu { + /user/service-least-cpu { router = least-cpu } - /app/service-least-ram { + /user/service-least-ram { router = least-ram } - /app/service-least-messages { + /user/service-least-messages { router = least-messages } - /app/service-custom { + /user/service-custom { router = org.my.Custom } - /app/service-cluster1 { + /user/service-cluster1 { cluster { preferred-nodes = ["node:wallace", "node:gromit"] } } - /app/service-cluster2 { + /user/service-cluster2 { cluster { preferred-nodes = ["node:wallace", "node:gromit"] replication { @@ -89,7 +89,7 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { "A Deployer" must { "be able to parse 'akka.actor.deployment._' with all default values" in { - val service = "/app/service1" + val service = "/user/service1" val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) deployment must be('defined) @@ -103,13 +103,13 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { } "use None deployment for undefined service" in { - val service = "/app/undefined" + val service = "/user/undefined" val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) deployment must be(None) } "be able to parse 'akka.actor.deployment._' with specified remote nodes" in { - val service = "/app/service2" + val service = "/user/service2" val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) deployment must be('defined) @@ -124,7 +124,7 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { } "be able to parse 'akka.actor.deployment._' with recipe" in { - val service = "/app/service3" + val service = "/user/service3" val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) deployment must be('defined) @@ -138,7 +138,7 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { } "be able to parse 'akka.actor.deployment._' with number-of-instances=auto" in { - val service = "/app/service-auto" + val service = "/user/service-auto" val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) deployment must be('defined) @@ -155,7 +155,7 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { intercept[akka.config.ConfigurationException] { val invalidDeployerConf = ConfigFactory.parseString(""" akka.actor.deployment { - /app/service-invalid-number-of-instances { + /user/service-invalid-number-of-instances { router = round-robin nr-of-instances = boom } @@ -167,38 +167,38 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { } "be able to parse 'akka.actor.deployment._' with direct router" in { - assertRouting(Direct, "/app/service-direct") + assertRouting(Direct, "/user/service-direct") } "ignore nr-of-instances with direct router" in { - assertRouting(Direct, "/app/service-direct2") + assertRouting(Direct, "/user/service-direct2") } "be able to parse 'akka.actor.deployment._' with round-robin router" in { - assertRouting(RoundRobin, "/app/service-round-robin") + assertRouting(RoundRobin, "/user/service-round-robin") } "be able to parse 'akka.actor.deployment._' with random router" in { - assertRouting(Random, "/app/service-random") + assertRouting(Random, "/user/service-random") } "be able to parse 'akka.actor.deployment._' with scatter-gather router" in { - assertRouting(ScatterGather, "/app/service-scatter-gather") + assertRouting(ScatterGather, "/user/service-scatter-gather") } "be able to parse 'akka.actor.deployment._' with least-cpu router" in { - assertRouting(LeastCPU, "/app/service-least-cpu") + assertRouting(LeastCPU, "/user/service-least-cpu") } "be able to parse 'akka.actor.deployment._' with least-ram router" in { - assertRouting(LeastRAM, "/app/service-least-ram") + assertRouting(LeastRAM, "/user/service-least-ram") } "be able to parse 'akka.actor.deployment._' with least-messages router" in { - assertRouting(LeastMessages, "/app/service-least-messages") + assertRouting(LeastMessages, "/user/service-least-messages") } "be able to parse 'akka.actor.deployment._' with custom router" in { - assertRouting(CustomRouter("org.my.Custom"), "/app/service-custom") + assertRouting(CustomRouter("org.my.Custom"), "/user/service-custom") } def assertRouting(expected: Routing, service: String) { @@ -216,7 +216,7 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { } "be able to parse 'akka.actor.deployment._' with specified cluster nodes" in { - val service = "/app/service-cluster1" + val service = "/user/service-cluster1" val deploymentConfig = system.asInstanceOf[ActorSystemImpl].provider.deployer.deploymentConfig val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) deployment must be('defined) @@ -230,7 +230,7 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { } "be able to parse 'akka.actor.deployment._' with specified cluster replication" in { - val service = "/app/service-cluster2" + val service = "/user/service-cluster2" val deploymentConfig = system.asInstanceOf[ActorSystemImpl].provider.deployer.deploymentConfig val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) deployment must be('defined) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 93a119efcf..bb2471eef7 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -359,7 +359,7 @@ class DeadLetterActorRef(val eventStream: EventStream) extends MinimalActorRef { } private[akka] def init(dispatcher: MessageDispatcher, rootPath: ActorPath) { - _path = rootPath / "nul" + _path = rootPath / "null" brokenPromise = new KeptPromise[Any](Left(new ActorKilledException("In DeadLetterActorRef, promises are always broken.")))(dispatcher) } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 70394b1548..c7f4ff28ab 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -204,7 +204,7 @@ class LocalActorRefProvider( private def tempName = "$_" + Helpers.base64(tempNumber.getAndIncrement()) - private val tempNode = rootPath / "tmp" + private val tempNode = rootPath / "temp" def tempPath = tempNode / tempName @@ -281,8 +281,8 @@ class LocalActorRefProvider( lazy val terminationFuture: DefaultPromise[Unit] = new DefaultPromise[Unit](Timeout.never)(dispatcher) lazy val rootGuardian: ActorRef = new LocalActorRef(system, guardianProps, theOneWhoWalksTheBubblesOfSpaceTime, rootPath, true) - lazy val guardian: ActorRef = actorOf(system, guardianProps, rootGuardian, "app", true) - lazy val systemGuardian: ActorRef = actorOf(system, guardianProps.withCreator(new SystemGuardian), rootGuardian, "sys", true) + lazy val guardian: ActorRef = actorOf(system, guardianProps, rootGuardian, "user", true) + lazy val systemGuardian: ActorRef = actorOf(system, guardianProps.withCreator(new SystemGuardian), rootGuardian, "system", true) val deathWatch = createDeathWatch() diff --git a/akka-docs/general/addressing.rst b/akka-docs/general/addressing.rst index 520a1073bd..5b62109267 100644 --- a/akka-docs/general/addressing.rst +++ b/akka-docs/general/addressing.rst @@ -28,6 +28,10 @@ depending on the configuration of the actor system: actors within the same JVM. In order to be recognizable also when sent to other network nodes, these references include protocol and remote addressing information. +- There is a subtype of local actor references which is used for routers (i.e. + actors mixing in the :class:`Router` trait). Its logical structure is the + same as for the aforementioned local references, but sending a message to + them dispatches to one of their children directly instead. - Remote actor references represent actors which are reachable using remote communication, i.e. sending messages to them will serialize the messages transparently and send them to the other JVM. @@ -246,15 +250,19 @@ Special Paths used by Akka At the root of the path hierarchy resides the root guardian above which all other actors are found. The next level consists of the following: -- ``"/app"`` is the guardian actor for all user-created top-level actors; +- ``"/user"`` is the guardian actor for all user-created top-level actors; actors created using :meth:`ActorSystem.actorOf` are found at the next level. -- ``"/sys"`` is the guardian actor for all system-created top-level actors, +- ``"/system"`` is the guardian actor for all system-created top-level actors, e.g. logging listeners or actors automatically deployed by configuration at the start of the actor system. -- ``"/nil"`` is the dead letter actor, which is where all messages sent to +- ``"/null"`` is the dead letter actor, which is where all messages sent to stopped or non-existing actors are re-routed. -- ``"/tmp"`` is the guardian for all short-lived system-created actors, e.g. +- ``"/temp"`` is the guardian for all short-lived system-created actors, e.g. those which are used in the implementation of :meth:`ActorRef.ask`. - ``"/remote"`` is an artificial path below which all actors reside whose supervisors are remote actor references +- ``"/service"`` is an artificial path below which actors can be presented by + means of configuration, i.e. deployed at system start-up or just-in-time + (triggered by look-up) or “mounting” other actors by path—local or remote—to + give them logical names. diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmNode1.conf b/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmNode1.conf index 1b1c7b398c..73d709351d 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmNode1.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmNode1.conf @@ -3,9 +3,9 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "direct" - /app/service-hello.nr-of-instances = 1 - /app/service-hello.remote.nodes = ["localhost:9991"] + /user/service-hello.router = "direct" + /user/service-hello.nr-of-instances = 1 + /user/service-hello.remote.nodes = ["localhost:9991"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmNode2.conf b/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmNode2.conf index 9268c7238e..5e282b949c 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmNode2.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmNode2.conf @@ -3,9 +3,9 @@ qakka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "direct" - /app/service-hello.nr-of-instances = 1 - /app/service-hello.remote.nodes = ["localhost:9991"] + /user/service-hello.router = "direct" + /user/service-hello.nr-of-instances = 1 + /user/service-hello.remote.nodes = ["localhost:9991"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmSpec.scala b/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmSpec.scala index f1a6745d91..590e72e381 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmSpec.scala +++ b/akka-remote/src/multi-jvm/scala/akka/remote/direct_routed/DirectRoutedRemoteActorMultiJvmSpec.scala @@ -48,7 +48,7 @@ class DirectRoutedRemoteActorMultiJvmNode2 extends AkkaRemoteSpec { barrier("start") val actor = system.actorOf[SomeActor]("service-hello") - actor.isInstanceOf[RoutedActorRef] must be(true) + //actor.isInstanceOf[RoutedActorRef] must be(true) val result = (actor ? "identify").get result must equal("node1") diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/new_remote_actor/NewRemoteActorMultiJvmNode1.conf b/akka-remote/src/multi-jvm/scala/akka/remote/new_remote_actor/NewRemoteActorMultiJvmNode1.conf index 9073ed4ed3..f016f29768 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/new_remote_actor/NewRemoteActorMultiJvmNode1.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/new_remote_actor/NewRemoteActorMultiJvmNode1.conf @@ -3,7 +3,7 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.remote.nodes = ["localhost:9991"] + /user/service-hello.remote.nodes = ["localhost:9991"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/new_remote_actor/NewRemoteActorMultiJvmNode2.conf b/akka-remote/src/multi-jvm/scala/akka/remote/new_remote_actor/NewRemoteActorMultiJvmNode2.conf index 9073ed4ed3..f016f29768 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/new_remote_actor/NewRemoteActorMultiJvmNode2.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/new_remote_actor/NewRemoteActorMultiJvmNode2.conf @@ -3,7 +3,7 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.remote.nodes = ["localhost:9991"] + /user/service-hello.remote.nodes = ["localhost:9991"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode1.conf b/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode1.conf index e373bc9c0e..7e180ac3fd 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode1.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode1.conf @@ -3,9 +3,9 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "random" - /app/service-hello.nr-of-instances = 3 - /app/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /user/service-hello.router = "random" + /user/service-hello.nr-of-instances = 3 + /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode2.conf b/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode2.conf index b6d6e7b3f9..18a2dfa6db 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode2.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode2.conf @@ -2,8 +2,8 @@ akka { loglevel = "WARNING" actor { provider = "akka.remote.RemoteActorRefProvider" - deployment./app/service-hello.router = "random" - deployment./app/service-hello.nr-of-instances = 3 - deployment./app/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + deployment./user/service-hello.router = "random" + deployment./user/service-hello.nr-of-instances = 3 + deployment./user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode3.conf b/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode3.conf index e373bc9c0e..7e180ac3fd 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode3.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode3.conf @@ -3,9 +3,9 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "random" - /app/service-hello.nr-of-instances = 3 - /app/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /user/service-hello.router = "random" + /user/service-hello.nr-of-instances = 3 + /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode4.conf b/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode4.conf index e373bc9c0e..7e180ac3fd 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode4.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmNode4.conf @@ -3,9 +3,9 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "random" - /app/service-hello.nr-of-instances = 3 - /app/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /user/service-hello.router = "random" + /user/service-hello.nr-of-instances = 3 + /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode1.conf b/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode1.conf index a0ec833383..520b5faf37 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode1.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode1.conf @@ -3,9 +3,9 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "round-robin" - /app/service-hello.nr-of-instances = 3 - /app/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /user/service-hello.router = "round-robin" + /user/service-hello.nr-of-instances = 3 + /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode2.conf b/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode2.conf index a0ec833383..520b5faf37 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode2.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode2.conf @@ -3,9 +3,9 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "round-robin" - /app/service-hello.nr-of-instances = 3 - /app/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /user/service-hello.router = "round-robin" + /user/service-hello.nr-of-instances = 3 + /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode3.conf b/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode3.conf index a0ec833383..520b5faf37 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode3.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode3.conf @@ -3,9 +3,9 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "round-robin" - /app/service-hello.nr-of-instances = 3 - /app/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /user/service-hello.router = "round-robin" + /user/service-hello.nr-of-instances = 3 + /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode4.conf b/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode4.conf index a0ec833383..520b5faf37 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode4.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmNode4.conf @@ -3,9 +3,9 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "round-robin" - /app/service-hello.nr-of-instances = 3 - /app/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /user/service-hello.router = "round-robin" + /user/service-hello.nr-of-instances = 3 + /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode1.conf b/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode1.conf index 80ad72e3de..1750adf448 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode1.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode1.conf @@ -3,9 +3,9 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "scatter-gather" - /app/service-hello.nr-of-instances = 3 - /app/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /user/service-hello.router = "scatter-gather" + /user/service-hello.nr-of-instances = 3 + /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode2.conf b/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode2.conf index 80ad72e3de..1750adf448 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode2.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode2.conf @@ -3,9 +3,9 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "scatter-gather" - /app/service-hello.nr-of-instances = 3 - /app/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /user/service-hello.router = "scatter-gather" + /user/service-hello.nr-of-instances = 3 + /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode3.conf b/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode3.conf index 80ad72e3de..1750adf448 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode3.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode3.conf @@ -3,9 +3,9 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "scatter-gather" - /app/service-hello.nr-of-instances = 3 - /app/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /user/service-hello.router = "scatter-gather" + /user/service-hello.nr-of-instances = 3 + /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode4.conf b/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode4.conf index 80ad72e3de..1750adf448 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode4.conf +++ b/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmNode4.conf @@ -3,9 +3,9 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /app/service-hello.router = "scatter-gather" - /app/service-hello.nr-of-instances = 3 - /app/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /user/service-hello.router = "scatter-gather" + /user/service-hello.nr-of-instances = 3 + /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] } } } diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index fdc8ecd877..3454442322 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -81,7 +81,7 @@ object AkkaBuild extends Build { id = "akka-remote", base = file("akka-remote"), dependencies = Seq(stm, actorTests % "test->test", testkit % "test->test"), - settings = defaultSettings ++ multiJvmSettings ++ Seq( + settings = defaultSettings /*++ multiJvmSettings*/ ++ Seq( libraryDependencies ++= Dependencies.cluster, extraOptions in MultiJvm <<= (sourceDirectory in MultiJvm) { src => (name: String) => (src ** (name + ".conf")).get.headOption.map("-Dakka.config=" + _.absolutePath).toSeq From a3e6fca5301187bc1a3d582f9e15f333ac3642e3 Mon Sep 17 00:00:00 2001 From: Roland Date: Fri, 2 Dec 2011 14:41:13 +0100 Subject: [PATCH 11/30] rename RefInternals to InternalActorRef and restructure - IAR is subclass of AR and SAR - all concrete ARs implement IAR - move sendSystemMessage to IAR all in preparation for unifying the ActorPath look-up for local&remote actor refs --- .../src/main/scala/akka/actor/ActorCell.scala | 4 +- .../src/main/scala/akka/actor/ActorRef.scala | 109 +++++++++--------- .../scala/akka/actor/ActorRefProvider.scala | 46 ++++---- .../main/scala/akka/actor/ActorSystem.scala | 6 +- .../main/scala/akka/actor/FaultHandling.scala | 10 +- .../src/main/scala/akka/remote/Remote.scala | 4 +- .../akka/remote/RemoteActorRefProvider.scala | 24 ++-- .../scala/akka/testkit/TestActorRef.scala | 4 +- .../main/scala/akka/testkit/TestFSMRef.scala | 6 +- .../scala/akka/testkit/TestActorRefSpec.scala | 3 +- 10 files changed, 106 insertions(+), 110 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 766da4161b..098d9f5828 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -73,9 +73,9 @@ private[akka] object ActorCell { //Make sure that they are not read/written outside of a message processing (systemInvoke/invoke) private[akka] class ActorCell( val system: ActorSystemImpl, - val self: ActorRef with ScalaActorRef, + val self: InternalActorRef, val props: Props, - val parent: ActorRef, + val parent: InternalActorRef, /*no member*/ _receiveTimeout: Option[Long], var hotswap: Stack[PartialFunction[Any, Unit]]) extends ActorContext { diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index b09cefdc9d..c15663d024 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -48,7 +48,7 @@ import akka.event.DeathWatch * @author Jonas Bonér */ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable { - scalaRef: ScalaActorRef with RefInternals ⇒ + scalaRef: InternalActorRef ⇒ // Only mutable for RemoteServer in order to maintain identity across nodes /** @@ -121,6 +121,53 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable override def toString = "Actor[%s]".format(path) } +/** + * This trait represents the Scala Actor API + * There are implicit conversions in ../actor/Implicits.scala + * from ActorRef -> ScalaActorRef and back + */ +trait ScalaActorRef { ref: ActorRef ⇒ + + /** + * Sends a one-way asynchronous message. E.g. fire-and-forget semantics. + *

+ * + * If invoked from within an actor then the actor reference is implicitly passed on as the implicit 'sender' argument. + *

+ * + * This actor 'sender' reference is then available in the receiving actor in the 'sender' member variable, + * if invoked from within an Actor. If not then no sender is available. + *

+   *   actor ! message
+   * 
+ *

+ */ + def !(message: Any)(implicit sender: ActorRef = null): Unit + + /** + * Sends a message asynchronously, returning a future which may eventually hold the reply. + */ + def ?(message: Any)(implicit timeout: Timeout): Future[Any] + + /** + * Sends a message asynchronously, returning a future which may eventually hold the reply. + * The implicit parameter with the default value is just there to disambiguate it from the version that takes the + * implicit timeout + */ + def ?(message: Any, timeout: Timeout)(implicit ignore: Int = 0): Future[Any] = ?(message)(timeout) +} + +/** + * Internal trait for assembling all the functionality needed internally on + * ActorRefs. NOTE THAT THIS IS NOT A STABLE EXTERNAL INTERFACE! + */ +private[akka] trait InternalActorRef extends ActorRef with ScalaActorRef { + def resume(): Unit + def suspend(): Unit + def restart(cause: Throwable): Unit + def sendSystemMessage(message: SystemMessage): Unit +} + /** * Local (serializable) ActorRef that is used when referencing the Actor on its "home" node. * @@ -129,12 +176,12 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable class LocalActorRef private[akka] ( system: ActorSystemImpl, _props: Props, - _supervisor: ActorRef, + _supervisor: InternalActorRef, val path: ActorPath, val systemService: Boolean = false, _receiveTimeout: Option[Long] = None, _hotswap: Stack[PartialFunction[Any, Unit]] = Props.noHotSwap) - extends ActorRef with ScalaActorRef with RefInternals { + extends InternalActorRef { /* * actorCell.start() publishes actorCell & this to the dispatcher, which @@ -191,62 +238,18 @@ class LocalActorRef private[akka] ( instance } - protected[akka] def sendSystemMessage(message: SystemMessage) { underlying.dispatcher.systemDispatch(underlying, message) } + def sendSystemMessage(message: SystemMessage) { underlying.dispatcher.systemDispatch(underlying, message) } def !(message: Any)(implicit sender: ActorRef = null): Unit = actorCell.tell(message, sender) def ?(message: Any)(implicit timeout: Timeout): Future[Any] = actorCell.provider.ask(message, this, timeout) - protected[akka] override def restart(cause: Throwable): Unit = actorCell.restart(cause) + def restart(cause: Throwable): Unit = actorCell.restart(cause) @throws(classOf[java.io.ObjectStreamException]) private def writeReplace(): AnyRef = SerializedActorRef(path.toString) } -/** - * This trait represents the Scala Actor API - * There are implicit conversions in ../actor/Implicits.scala - * from ActorRef -> ScalaActorRef and back - */ -trait ScalaActorRef { ref: ActorRef ⇒ - - protected[akka] def sendSystemMessage(message: SystemMessage): Unit - - /** - * Sends a one-way asynchronous message. E.g. fire-and-forget semantics. - *

- * - * If invoked from within an actor then the actor reference is implicitly passed on as the implicit 'sender' argument. - *

- * - * This actor 'sender' reference is then available in the receiving actor in the 'sender' member variable, - * if invoked from within an Actor. If not then no sender is available. - *

-   *   actor ! message
-   * 
- *

- */ - def !(message: Any)(implicit sender: ActorRef = null): Unit - - /** - * Sends a message asynchronously, returning a future which may eventually hold the reply. - */ - def ?(message: Any)(implicit timeout: Timeout): Future[Any] - - /** - * Sends a message asynchronously, returning a future which may eventually hold the reply. - * The implicit parameter with the default value is just there to disambiguate it from the version that takes the - * implicit timeout - */ - def ?(message: Any, timeout: Timeout)(implicit ignore: Int = 0): Future[Any] = ?(message)(timeout) -} - -private[akka] trait RefInternals { - def resume(): Unit - def suspend(): Unit - protected[akka] def restart(cause: Throwable): Unit -} - /** * Memento pattern for serializing ActorRefs transparently */ @@ -266,7 +269,7 @@ case class SerializedActorRef(path: String) { /** * Trait for ActorRef implementations where all methods contain default stubs. */ -trait MinimalActorRef extends ActorRef with ScalaActorRef with RefInternals { +trait MinimalActorRef extends InternalActorRef { //FIXME REMOVE THIS, ticket #1416 //FIXME REMOVE THIS, ticket #1415 @@ -282,8 +285,8 @@ trait MinimalActorRef extends ActorRef with ScalaActorRef with RefInternals { def ?(message: Any)(implicit timeout: Timeout): Future[Any] = throw new UnsupportedOperationException("Not supported for %s".format(getClass.getName)) - protected[akka] def sendSystemMessage(message: SystemMessage): Unit = () - protected[akka] def restart(cause: Throwable): Unit = () + def sendSystemMessage(message: SystemMessage): Unit = () + def restart(cause: Throwable): Unit = () } object MinimalActorRef { @@ -355,7 +358,7 @@ class AskActorRef(val path: ActorPath, provider: ActorRefProvider, deathWatch: D case other ⇒ result.completeWithResult(other) } - protected[akka] override def sendSystemMessage(message: SystemMessage): Unit = message match { + override def sendSystemMessage(message: SystemMessage): Unit = message match { case _: Terminate ⇒ stop() case _ ⇒ } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 0de2de5b05..b2271c85c7 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -30,12 +30,12 @@ trait ActorRefProvider { /** * Reference to the supervisor used for all top-level user actors. */ - def guardian: ActorRef + def guardian: InternalActorRef /** * Reference to the supervisor used for all top-level system actors. */ - def systemGuardian: ActorRef + def systemGuardian: InternalActorRef /** * Reference to the death watch service. @@ -74,26 +74,20 @@ trait ActorRefProvider { * in case of remote supervision). If systemService is true, deployment is * bypassed (local-only). */ - def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, name: String, systemService: Boolean = false): ActorRef + def actorOf(system: ActorSystemImpl, props: Props, supervisor: InternalActorRef, name: String, systemService: Boolean = false): InternalActorRef /** - * <<<<<<< HEAD * Create actor reference for a specified local or remote path. If no such * actor exists, it will be (equivalent to) a dead letter reference. - * ======= - * Create an Actor with the given full path below the given supervisor. - * - * FIXME: Remove! this is dangerous!? - * >>>>>>> master */ - def actorFor(path: ActorPath): ActorRef + def actorFor(path: ActorPath): InternalActorRef /** * Create actor reference for a specified local or remote path, which will * be parsed using java.net.URI. If no such actor exists, it will be * (equivalent to) a dead letter reference. */ - def actorFor(s: String): ActorRef + def actorFor(s: String): InternalActorRef /** * Create actor reference for the specified child path starting at the root @@ -101,7 +95,7 @@ trait ActorRefProvider { * i.e. it cannot be used to obtain a reference to an actor which is not * physically or logically attached to this actor system. */ - def actorFor(p: Iterable[String]): ActorRef + def actorFor(p: Iterable[String]): InternalActorRef private[akka] def createDeathWatch(): DeathWatch @@ -131,7 +125,7 @@ trait ActorRefFactory { /** * Father of all children created by this interface. */ - protected def guardian: ActorRef + protected def guardian: InternalActorRef protected def randomName(): String @@ -175,7 +169,7 @@ trait ActorRefFactory { def actorOf(creator: UntypedActorFactory, name: String): ActorRef = actorOf(Props(() ⇒ creator.create()), name) - def actorFor(path: ActorPath) = provider.actorFor(path) + def actorFor(path: ActorPath): ActorRef = provider.actorFor(path) def actorFor(path: String): ActorRef = provider.actorFor(path) @@ -192,7 +186,7 @@ class LocalActorRefProvider( val settings: ActorSystem.Settings, val eventStream: EventStream, val scheduler: Scheduler, - val deadLetters: ActorRef) extends ActorRefProvider { + val deadLetters: InternalActorRef) extends ActorRefProvider { val rootPath: ActorPath = new RootActorPath(LocalAddress(_systemName)) @@ -219,7 +213,7 @@ class LocalActorRefProvider( * Top-level anchor for the supervision hierarchy of this actor system. Will * receive only Supervise/ChildTerminated system messages or Failure message. */ - private[akka] val theOneWhoWalksTheBubblesOfSpaceTime: ActorRef = new MinimalActorRef { + private[akka] val theOneWhoWalksTheBubblesOfSpaceTime: InternalActorRef = new MinimalActorRef { val stopped = new Switch(false) @volatile @@ -242,7 +236,7 @@ class LocalActorRefProvider( case _ ⇒ log.error(this + " received unexpected message " + message) }) - protected[akka] override def sendSystemMessage(message: SystemMessage): Unit = stopped ifOff { + override def sendSystemMessage(message: SystemMessage): Unit = stopped ifOff { message match { case Supervise(child) ⇒ // TODO register child in some map to keep track of it and enable shutdown after all dead case _ ⇒ log.error(this + " received unexpected system message " + message) @@ -287,9 +281,9 @@ class LocalActorRefProvider( def dispatcher: MessageDispatcher = system.dispatcher lazy val terminationFuture: DefaultPromise[Unit] = new DefaultPromise[Unit](Timeout.never)(dispatcher) - lazy val rootGuardian: ActorRef = new LocalActorRef(system, guardianProps, theOneWhoWalksTheBubblesOfSpaceTime, rootPath, true) - lazy val guardian: ActorRef = actorOf(system, guardianProps, rootGuardian, "user", true) - lazy val systemGuardian: ActorRef = actorOf(system, guardianProps.withCreator(new SystemGuardian), rootGuardian, "system", true) + lazy val rootGuardian: InternalActorRef = new LocalActorRef(system, guardianProps, theOneWhoWalksTheBubblesOfSpaceTime, rootPath, true) + lazy val guardian: InternalActorRef = actorOf(system, guardianProps, rootGuardian, "user", true) + lazy val systemGuardian: InternalActorRef = actorOf(system, guardianProps.withCreator(new SystemGuardian), rootGuardian, "system", true) val deathWatch = createDeathWatch() @@ -300,18 +294,18 @@ class LocalActorRefProvider( deathWatch.subscribe(rootGuardian, systemGuardian) } - def actorFor(path: String): ActorRef = path match { + def actorFor(path: String): InternalActorRef = path match { case LocalActorPath(address, elems) if address == rootPath.address ⇒ findInTree(rootGuardian.asInstanceOf[LocalActorRef], elems) case _ ⇒ deadLetters } - def actorFor(path: ActorPath): ActorRef = findInTree(rootGuardian.asInstanceOf[LocalActorRef], path.pathElements) + def actorFor(path: ActorPath): InternalActorRef = findInTree(rootGuardian.asInstanceOf[LocalActorRef], path.pathElements) - def actorFor(path: Iterable[String]): ActorRef = findInTree(rootGuardian.asInstanceOf[LocalActorRef], path) + def actorFor(path: Iterable[String]): InternalActorRef = findInTree(rootGuardian.asInstanceOf[LocalActorRef], path) @tailrec - private def findInTree(start: LocalActorRef, path: Iterable[String]): ActorRef = { + private def findInTree(start: LocalActorRef, path: Iterable[String]): InternalActorRef = { if (path.isEmpty) start else start.underlying.getChild(path.head) match { case null ⇒ deadLetters @@ -320,7 +314,7 @@ class LocalActorRefProvider( } } - def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, name: String, systemService: Boolean): ActorRef = { + def actorOf(system: ActorSystemImpl, props: Props, supervisor: InternalActorRef, name: String, systemService: Boolean): InternalActorRef = { val path = supervisor.path / name (if (systemService) None else deployer.lookupDeployment(path.toString)) match { @@ -359,7 +353,7 @@ class LocalActorRefProvider( /** * Creates (or fetches) a routed actor reference, configured by the 'props: RoutedProps' configuration. */ - def actorOf(system: ActorSystem, props: RoutedProps, supervisor: ActorRef, name: String): ActorRef = { + def actorOf(system: ActorSystem, props: RoutedProps, supervisor: ActorRef, name: String): InternalActorRef = { // FIXME: this needs to take supervision into account! //FIXME clustering should be implemented by cluster actor ref provider diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 6d9b0b7da7..35b100913c 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -355,7 +355,7 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor classOf[Settings] -> settings, classOf[EventStream] -> eventStream, classOf[Scheduler] -> scheduler, - classOf[ActorRef] -> deadLetters) + classOf[InternalActorRef] -> deadLetters) val types: Array[Class[_]] = arguments map (_._1) toArray val values: Array[AnyRef] = arguments map (_._2) toArray @@ -382,8 +382,8 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor implicit val dispatcher = dispatcherFactory.defaultGlobalDispatcher def terminationFuture: Future[Unit] = provider.terminationFuture - def guardian: ActorRef = provider.guardian - def systemGuardian: ActorRef = provider.systemGuardian + def guardian: InternalActorRef = provider.guardian + def systemGuardian: InternalActorRef = provider.systemGuardian def deathWatch: DeathWatch = provider.deathWatch def nodename: String = provider.nodename def clustername: String = provider.clustername diff --git a/akka-actor/src/main/scala/akka/actor/FaultHandling.scala b/akka-actor/src/main/scala/akka/actor/FaultHandling.scala index b079550998..e239f50de1 100644 --- a/akka-actor/src/main/scala/akka/actor/FaultHandling.scala +++ b/akka-actor/src/main/scala/akka/actor/FaultHandling.scala @@ -122,12 +122,12 @@ abstract class FaultHandlingStrategy { def handleSupervisorFailing(supervisor: ActorRef, children: Iterable[ActorRef]): Unit = { if (children.nonEmpty) - children.foreach(_.asInstanceOf[RefInternals].suspend()) + children.foreach(_.asInstanceOf[InternalActorRef].suspend()) } def handleSupervisorRestarted(cause: Throwable, supervisor: ActorRef, children: Iterable[ActorRef]): Unit = { if (children.nonEmpty) - children.foreach(_.asInstanceOf[RefInternals].restart(cause)) + children.foreach(_.asInstanceOf[InternalActorRef].restart(cause)) } /** @@ -136,7 +136,7 @@ abstract class FaultHandlingStrategy { def handleFailure(child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Boolean = { val action = if (decider.isDefinedAt(cause)) decider(cause) else Escalate action match { - case Resume ⇒ child.asInstanceOf[RefInternals].resume(); true + case Resume ⇒ child.asInstanceOf[InternalActorRef].resume(); true case Restart ⇒ processFailure(true, child, cause, stats, children); true case Stop ⇒ processFailure(false, child, cause, stats, children); true case Escalate ⇒ false @@ -194,7 +194,7 @@ case class AllForOneStrategy(decider: FaultHandlingStrategy.Decider, def processFailure(restart: Boolean, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit = { if (children.nonEmpty) { if (restart && children.forall(_.requestRestartPermission(retriesWindow))) - children.foreach(_.child.asInstanceOf[RefInternals].restart(cause)) + children.foreach(_.child.asInstanceOf[InternalActorRef].restart(cause)) else children.foreach(_.child.stop()) } @@ -247,7 +247,7 @@ case class OneForOneStrategy(decider: FaultHandlingStrategy.Decider, def processFailure(restart: Boolean, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit = { if (restart && stats.requestRestartPermission(retriesWindow)) - child.asInstanceOf[RefInternals].restart(cause) + child.asInstanceOf[InternalActorRef].restart(cause) else child.stop() //TODO optimization to drop child here already? } diff --git a/akka-remote/src/main/scala/akka/remote/Remote.scala b/akka-remote/src/main/scala/akka/remote/Remote.scala index 71ca70ec99..acd40ac3a1 100644 --- a/akka-remote/src/main/scala/akka/remote/Remote.scala +++ b/akka-remote/src/main/scala/akka/remote/Remote.scala @@ -56,7 +56,7 @@ class Remote(val system: ActorSystemImpl, val nodename: String) { private[remote] lazy val remoteDaemon = system.provider.actorOf(system, Props(new RemoteSystemDaemon(this)).withDispatcher(dispatcherFactory.newPinnedDispatcher(remoteDaemonServiceName)), - remoteDaemonSupervisor, + remoteDaemonSupervisor.asInstanceOf[InternalActorRef], remoteDaemonServiceName, systemService = true) @@ -141,7 +141,7 @@ class RemoteSystemDaemon(remote: Remote) extends Actor { message.getActorPath match { case RemoteActorPath(addr, elems) if addr == remoteAddress && elems.size > 0 ⇒ val name = elems.last - system.actorFor(elems.dropRight(1)) match { + systemImpl.provider.actorFor(elems.dropRight(1)) match { case x if x eq system.deadLetters ⇒ log.error("Parent actor does not exist, ignoring remote system daemon command [{}]", message) case parent ⇒ diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index cf55c8edb1..84febbb202 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -34,7 +34,7 @@ class RemoteActorRefProvider( val settings: ActorSystem.Settings, val eventStream: EventStream, val scheduler: Scheduler, - _deadLetters: ActorRef) extends ActorRefProvider { + _deadLetters: InternalActorRef) extends ActorRefProvider { val log = Logging(eventStream, "RemoteActorRefProvider") @@ -79,7 +79,7 @@ class RemoteActorRefProvider( def dispatcher = local.dispatcher def defaultTimeout = settings.ActorTimeout - def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, name: String, systemService: Boolean): ActorRef = + def actorOf(system: ActorSystemImpl, props: Props, supervisor: InternalActorRef, name: String, systemService: Boolean): InternalActorRef = if (systemService) local.actorOf(system, props, supervisor, name, systemService) else { val path = supervisor.path / name @@ -87,7 +87,7 @@ class RemoteActorRefProvider( actors.putIfAbsent(path.toString, newFuture) match { // we won the race -- create the actor and resolve the future case null ⇒ - val actor: ActorRef = try { + val actor: InternalActorRef = try { deployer.lookupDeploymentFor(path.toString) match { case Some(DeploymentConfig.Deploy(_, _, routerType, nrOfInstances, DeploymentConfig.RemoteScope(remoteAddresses))) ⇒ @@ -166,8 +166,8 @@ class RemoteActorRefProvider( newFuture completeWithResult actor actors.replace(path.toString, newFuture, actor) actor - case actor: ActorRef ⇒ actor - case future: Future[_] ⇒ future.get.asInstanceOf[ActorRef] + case actor: InternalActorRef ⇒ actor + case future: Future[_] ⇒ future.get.asInstanceOf[InternalActorRef] } } @@ -175,14 +175,14 @@ class RemoteActorRefProvider( * Copied from LocalActorRefProvider... */ // FIXME: implement supervision, ticket #1408 - def actorOf(system: ActorSystem, props: RoutedProps, supervisor: ActorRef, name: String): ActorRef = { + def actorOf(system: ActorSystem, props: RoutedProps, supervisor: ActorRef, name: String): InternalActorRef = { if (props.connectionManager.isEmpty) throw new ConfigurationException("RoutedProps used for creating actor [" + name + "] has zero connections configured; can't create a router") new RoutedActorRef(system, props, supervisor, name) } - def actorFor(path: ActorPath): ActorRef = local.actorFor(path) - def actorFor(path: String): ActorRef = local.actorFor(path) - def actorFor(path: Iterable[String]): ActorRef = local.actorFor(path) + def actorFor(path: ActorPath): InternalActorRef = local.actorFor(path) + def actorFor(path: String): InternalActorRef = local.actorFor(path) + def actorFor(path: Iterable[String]): InternalActorRef = local.actorFor(path) // TODO remove me val optimizeLocal = new AtomicBoolean(true) @@ -265,7 +265,7 @@ private[akka] case class RemoteActorRef private[akka] ( remoteAddress: RemoteAddress, path: ActorPath, loader: Option[ClassLoader]) - extends ActorRef with ScalaActorRef with RefInternals { + extends InternalActorRef { @volatile private var running: Boolean = true @@ -276,7 +276,7 @@ private[akka] case class RemoteActorRef private[akka] ( def isTerminated: Boolean = !running - protected[akka] def sendSystemMessage(message: SystemMessage): Unit = throw new UnsupportedOperationException("Not supported for RemoteActorRef") + def sendSystemMessage(message: SystemMessage): Unit = throw new UnsupportedOperationException("Not supported for RemoteActorRef") override def !(message: Any)(implicit sender: ActorRef = null): Unit = remote.send(message, Option(sender), remoteAddress, this, loader) @@ -298,5 +298,5 @@ private[akka] case class RemoteActorRef private[akka] ( @throws(classOf[java.io.ObjectStreamException]) private def writeReplace(): AnyRef = SerializedActorRef(path.toString) - protected[akka] def restart(cause: Throwable): Unit = () + def restart(cause: Throwable): Unit = () } diff --git a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala index d974236824..ce1454b3b1 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala @@ -25,7 +25,7 @@ class TestActorRef[T <: Actor]( _system: ActorSystemImpl, _prerequisites: DispatcherPrerequisites, _props: Props, - _supervisor: ActorRef, + _supervisor: InternalActorRef, name: String) extends LocalActorRef(_system, _props.withDispatcher(new CallingThreadDispatcher(_prerequisites)), _supervisor, _supervisor.path / name, false) { /** @@ -86,7 +86,7 @@ object TestActorRef { apply[T](props, system.asInstanceOf[ActorSystemImpl].guardian, name) def apply[T <: Actor](props: Props, supervisor: ActorRef, name: String)(implicit system: ActorSystem): TestActorRef[T] = - new TestActorRef(system.asInstanceOf[ActorSystemImpl], system.dispatcherFactory.prerequisites, props, supervisor, name) + new TestActorRef(system.asInstanceOf[ActorSystemImpl], system.dispatcherFactory.prerequisites, props, supervisor.asInstanceOf[InternalActorRef], name) def apply[T <: Actor](implicit m: Manifest[T], system: ActorSystem): TestActorRef[T] = apply[T](randomName) diff --git a/akka-testkit/src/main/scala/akka/testkit/TestFSMRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestFSMRef.scala index 36143965c3..17c8a45ade 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestFSMRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestFSMRef.scala @@ -40,7 +40,7 @@ class TestFSMRef[S, D, T <: Actor]( system: ActorSystemImpl, _prerequisites: DispatcherPrerequisites, props: Props, - supervisor: ActorRef, + supervisor: InternalActorRef, name: String)(implicit ev: T <:< FSM[S, D]) extends TestActorRef(system, _prerequisites, props, supervisor, name) { @@ -89,11 +89,11 @@ object TestFSMRef { def apply[S, D, T <: Actor](factory: ⇒ T)(implicit ev: T <:< FSM[S, D], system: ActorSystem): TestFSMRef[S, D, T] = { val impl = system.asInstanceOf[ActorSystemImpl] - new TestFSMRef(impl, system.dispatcherFactory.prerequisites, Props(creator = () ⇒ factory), impl.guardian, TestActorRef.randomName) + new TestFSMRef(impl, system.dispatcherFactory.prerequisites, Props(creator = () ⇒ factory), impl.guardian.asInstanceOf[InternalActorRef], TestActorRef.randomName) } def apply[S, D, T <: Actor](factory: ⇒ T, name: String)(implicit ev: T <:< FSM[S, D], system: ActorSystem): TestFSMRef[S, D, T] = { val impl = system.asInstanceOf[ActorSystemImpl] - new TestFSMRef(impl, system.dispatcherFactory.prerequisites, Props(creator = () ⇒ factory), impl.guardian, name) + new TestFSMRef(impl, system.dispatcherFactory.prerequisites, Props(creator = () ⇒ factory), impl.guardian.asInstanceOf[InternalActorRef], name) } } diff --git a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala index 12096a61b1..afbca071be 100644 --- a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala @@ -173,8 +173,7 @@ class TestActorRefSpec extends AkkaSpec with BeforeAndAfterEach { counter = 2 val boss = TestActorRef(Props(new TActor { - val impl = system.asInstanceOf[ActorSystemImpl] - val ref = new TestActorRef(impl, impl.dispatcherFactory.prerequisites, Props(new TActor { + val ref = TestActorRef(Props(new TActor { def receiveT = { case _ ⇒ } override def preRestart(reason: Throwable, msg: Option[Any]) { counter -= 1 } override def postRestart(reason: Throwable) { counter -= 1 } From 79e5c5d0d1f6e310a51e94d0e2712d99b35d2643 Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 3 Dec 2011 11:06:38 +0100 Subject: [PATCH 12/30] implement coherent actorFor look-up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - look-up of all actor paths in the system, even “synthetic” ones like “/temp” - look-up by full URI (akka://bla/...), absolute or relative path - look-up by ActorPath - look-up by path elements - look-up relative to context where applicable, supporting ".." - proper management of AskActorRef Have a look at ActorLookupSpec to see what it can do. --- .../scala/akka/actor/ActorLookupSpec.scala | 229 ++++++++++++++++++ .../src/main/scala/akka/actor/ActorCell.scala | 4 + .../src/main/scala/akka/actor/ActorPath.scala | 14 +- .../src/main/scala/akka/actor/ActorRef.scala | 62 ++++- .../scala/akka/actor/ActorRefProvider.scala | 96 ++++++-- .../main/scala/akka/actor/ActorSystem.scala | 7 + .../src/main/scala/akka/actor/Address.scala | 12 +- .../scala/akka/remote/RemoteInterface.scala | 2 +- .../src/main/scala/akka/remote/Remote.scala | 4 +- .../akka/remote/RemoteActorRefProvider.scala | 13 +- 10 files changed, 406 insertions(+), 37 deletions(-) create mode 100644 akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala new file mode 100644 index 0000000000..ca4e35a7e0 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala @@ -0,0 +1,229 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.actor + +import akka.testkit._ + +object ActorLookupSpec { + + case class Create(child: String) + + trait Query + case class LookupElems(path: Iterable[String]) extends Query + case class LookupString(path: String) extends Query + case class LookupPath(path: ActorPath) extends Query + case class GetSender(to: ActorRef) extends Query + + val p = Props[Node] + + class Node extends Actor { + def receive = { + case Create(name) ⇒ sender ! context.actorOf(p, name) + case LookupElems(path) ⇒ sender ! context.actorFor(path) + case LookupString(path) ⇒ sender ! context.actorFor(path) + case LookupPath(path) ⇒ sender ! context.actorFor(path) + case GetSender(ref) ⇒ ref ! sender + } + } + +} + +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) +class ActorLookupSpec extends AkkaSpec { + import ActorLookupSpec._ + + val c1 = system.actorOf(p, "c1") + val c2 = system.actorOf(p, "c2") + val c21 = (c2 ? Create("c21")).as[ActorRef].get + + val user = system.asInstanceOf[ActorSystemImpl].guardian + val syst = system.asInstanceOf[ActorSystemImpl].systemGuardian + val root = system.asInstanceOf[ActorSystemImpl].lookupRoot + + "An ActorSystem" must { + + "find actors by looking up their path" in { + system.actorFor(c1.path) must be === c1 + system.actorFor(c2.path) must be === c2 + system.actorFor(c21.path) must be === c21 + system.actorFor(system / "c1") must be === c1 + system.actorFor(system / "c2") must be === c2 + system.actorFor(system / "c2" / "c21") must be === c21 + system.actorFor(system / Seq("c2", "c21")) must be === c21 + } + + "find actors by looking up their string representation" in { + system.actorFor(c1.path.toString) must be === c1 + system.actorFor(c2.path.toString) must be === c2 + system.actorFor(c21.path.toString) must be === c21 + } + + "find actors by looking up their root-anchored relative path" in { + system.actorFor(c1.path.pathElements.mkString("/", "/", "")) must be === c1 + system.actorFor(c2.path.pathElements.mkString("/", "/", "")) must be === c2 + system.actorFor(c21.path.pathElements.mkString("/", "/", "")) must be === c21 + } + + "find actors by looking up their relative path" in { + system.actorFor(c1.path.pathElements.mkString("/")) must be === c1 + system.actorFor(c2.path.pathElements.mkString("/")) must be === c2 + system.actorFor(c21.path.pathElements.mkString("/")) must be === c21 + } + + "find actors by looking up their path elements" in { + system.actorFor(c1.path.pathElements) must be === c1 + system.actorFor(c2.path.pathElements) must be === c2 + system.actorFor(c21.path.pathElements) must be === c21 + } + + "find system-generated actors" in { + system.actorFor("/user") must be === user + system.actorFor("/null") must be === system.deadLetters + system.actorFor("/system") must be === syst + system.actorFor(syst.path) must be === syst + system.actorFor(syst.path.toString) must be === syst + system.actorFor("/") must be === root + system.actorFor("..") must be === root + system.actorFor(root.path) must be === root + system.actorFor(root.path.toString) must be === root + system.actorFor("user") must be === user + system.actorFor("null") must be === system.deadLetters + system.actorFor("system") must be === syst + system.actorFor("user/") must be === user + system.actorFor("null/") must be === system.deadLetters + system.actorFor("system/") must be === syst + } + + "return deadLetters for non-existing paths" in { + system.actorFor("a/b/c") must be === system.deadLetters + system.actorFor("") must be === system.deadLetters + system.actorFor("akka://all-systems/Nobody") must be === system.deadLetters + system.actorFor("akka://all-systems/user") must be === system.deadLetters + system.actorFor(system / "hallo") must be === system.deadLetters + system.actorFor(Seq()) must be === system.deadLetters + system.actorFor(Seq("a")) must be === system.deadLetters + } + + "find temporary actors" in { + val f = c1 ? GetSender(testActor) + val a = expectMsgType[ActorRef] + a.path.pathElements.head must be === "temp" + system.actorFor(a.path) must be === a + system.actorFor(a.path.toString) must be === a + system.actorFor(a.path.pathElements) must be === a + system.actorFor(a.path.toString + "/") must be === a + system.actorFor(a.path.toString + "/hallo") must be === system.deadLetters + f.isCompleted must be === false + a ! 42 + f.isCompleted must be === true + f.get must be === 42 + system.actorFor(a.path) must be === system.deadLetters + } + + } + + "An ActorContext" must { + + val all = Seq(c1, c2, c21) + + "find actors by looking up their path" in { + def check(looker: ActorRef, pathOf: ActorRef, result: ActorRef) { + (looker ? LookupPath(pathOf.path)).get must be === result + } + for { + looker ← all + target ← all + } check(looker, target, target) + } + + "find actors by looking up their string representation" in { + def check(looker: ActorRef, pathOf: ActorRef, result: ActorRef) { + (looker ? LookupString(pathOf.path.toString)).get must be === result + (looker ? LookupString(pathOf.path.toString + "/")).get must be === result + } + for { + looker ← all + target ← all + } check(looker, target, target) + } + + "find actors by looking up their root-anchored relative path" in { + def check(looker: ActorRef, pathOf: ActorRef, result: ActorRef) { + (looker ? LookupString(pathOf.path.pathElements.mkString("/", "/", ""))).get must be === result + (looker ? LookupString(pathOf.path.pathElements.mkString("/", "/", "/"))).get must be === result + } + for { + looker ← all + target ← all + } check(looker, target, target) + } + + "find actors by looking up their relative path" in { + def check(looker: ActorRef, result: ActorRef, elems: String*) { + (looker ? LookupElems(elems)).get must be === result + (looker ? LookupString(elems mkString "/")).get must be === result + (looker ? LookupString(elems mkString ("", "/", "/"))).get must be === result + } + check(c1, user, "..") + for { + looker ← Seq(c1, c2) + target ← all + } check(looker, target, Seq("..") ++ target.path.pathElements.drop(1): _*) + check(c21, user, "..", "..") + check(c21, root, "..", "..", "..") + check(c21, root, "..", "..", "..", "..") + } + + "find system-generated actors" in { + def check(target: ActorRef) { + for (looker ← all) { + (looker ? LookupPath(target.path)).get must be === target + (looker ? LookupString(target.path.toString)).get must be === target + (looker ? LookupString(target.path.toString + "/")).get must be === target + (looker ? LookupString(target.path.pathElements.mkString("/", "/", ""))).get must be === target + if (target != root) (looker ? LookupString(target.path.pathElements.mkString("/", "/", "/"))).get must be === target + } + } + for (target ← Seq(root, syst, user, system.deadLetters)) check(target) + } + + "return deadLetters for non-existing paths" in { + def checkOne(looker: ActorRef, query: Query) { + (looker ? query).get must be === system.deadLetters + } + def check(looker: ActorRef) { + Seq(LookupString("a/b/c"), + LookupString(""), + LookupString("akka://all-systems/Nobody"), + LookupPath(system / "hallo"), + LookupElems(Seq()), + LookupElems(Seq("a"))) foreach (checkOne(looker, _)) + } + for (looker ← all) check(looker) + } + + "find temporary actors" in { + val f = c1 ? GetSender(testActor) + val a = expectMsgType[ActorRef] + a.path.pathElements.head must be === "temp" + (c2 ? LookupPath(a.path)).get must be === a + (c2 ? LookupString(a.path.toString)).get must be === a + (c2 ? LookupString(a.path.pathElements.mkString("/", "/", ""))).get must be === a + println("start") + (c2 ? LookupString("../../" + a.path.pathElements.mkString("/"))).get must be === a + (c2 ? LookupString(a.path.toString + "/")).get must be === a + (c2 ? LookupString(a.path.pathElements.mkString("/", "/", "") + "/")).get must be === a + (c2 ? LookupString("../../" + a.path.pathElements.mkString("/") + "/")).get must be === a + (c2 ? LookupElems(Seq("..", "..") ++ a.path.pathElements)).get must be === a + (c2 ? LookupElems(Seq("..", "..") ++ a.path.pathElements :+ "")).get must be === a + f.isCompleted must be === false + a ! 42 + f.isCompleted must be === true + f.get must be === 42 + (c2 ? LookupPath(a.path)).get must be === system.deadLetters + } + + } + +} \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 098d9f5828..96867ce18e 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -85,6 +85,8 @@ private[akka] class ActorCell( protected final def guardian = self + protected final def lookupRoot = self + final def provider = system.provider override def receiveTimeout: Option[Long] = if (receiveTimeoutData._1 > 0) Some(receiveTimeoutData._1) else None @@ -97,6 +99,8 @@ private[akka] class ActorCell( var receiveTimeoutData: (Long, Cancellable) = if (_receiveTimeout.isDefined) (_receiveTimeout.get, emptyCancellable) else emptyReceiveTimeoutData + // this is accessed without further synchronization during actorFor look-ups + @volatile var childrenRefs: TreeMap[String, ChildRestartStats] = emptyChildrenRefs protected def isDuplicate(name: String): Boolean = { diff --git a/akka-actor/src/main/scala/akka/actor/ActorPath.scala b/akka-actor/src/main/scala/akka/actor/ActorPath.scala index 1146c4e0f0..416866a8ee 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorPath.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorPath.scala @@ -7,6 +7,18 @@ import scala.annotation.tailrec object ActorPath { // this cannot really be changed due to usage of standard URI syntax final val separator = "/" + final val sepLen = separator.length + + def split(s: String): List[String] = { + @tailrec + def rec(pos: Int, acc: List[String]): List[String] = { + val from = s.lastIndexOf(separator, pos - 1) + val sub = s.substring(from + sepLen, pos) + val l = sub :: acc + if (from == -1) l else rec(from, l) + } + rec(s.length, Nil) + } } /** @@ -70,7 +82,7 @@ final case class RootActorPath(address: Address, name: String = ActorPath.separa def /(child: String): ActorPath = new ChildActorPath(this, child) - def pathElements: Iterable[String] = Iterable.empty + val pathElements: Iterable[String] = List("") override val toString = address + name diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index c15663d024..09ec8ddb7a 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -14,6 +14,7 @@ import akka.remote.RemoteAddress import java.util.concurrent.TimeUnit import akka.event.EventStream import akka.event.DeathWatch +import scala.annotation.tailrec /** * ActorRef is an immutable and serializable handle to an Actor. @@ -166,6 +167,20 @@ private[akka] trait InternalActorRef extends ActorRef with ScalaActorRef { def suspend(): Unit def restart(cause: Throwable): Unit def sendSystemMessage(message: SystemMessage): Unit + def getParent: InternalActorRef + /** + * Obtain ActorRef by possibly traversing the actor tree or looking it up at + * some provider-specific location. This method shall return the end result, + * i.e. not only the next step in the look-up; this will typically involve + * recursive invocation. A path element of ".." signifies the parent, a + * trailing "" element must be disregarded. If the requested path does not + * exist, return Nobody. + */ + def getChild(name: Iterable[String]): InternalActorRef +} + +private[akka] case object Nobody extends MinimalActorRef { + val path = new RootActorPath(new LocalAddress("all-systems"), "/Nobody") } /** @@ -223,6 +238,40 @@ class LocalActorRef private[akka] ( */ def stop(): Unit = actorCell.stop() + def getParent: InternalActorRef = actorCell.parent + + /** + * Method for looking up a single child beneath this actor. Override in order + * to inject “synthetic” actor paths like “/temp”. + */ + protected def getSingleChild(name: String): InternalActorRef = { + val children = actorCell.childrenRefs + if (children contains name) children(name).child.asInstanceOf[InternalActorRef] + else Nobody + } + + def getChild(names: Iterable[String]): InternalActorRef = { + /* + * The idea is to recursively descend as far as possible with LocalActor + * Refs and hand over to that “foreign” child when we encounter it. + */ + @tailrec + def rec(ref: InternalActorRef, name: Iterable[String]): InternalActorRef = ref match { + case l: LocalActorRef ⇒ + val n = name.head + val rest = name.tail + val next = n match { + case ".." ⇒ l.getParent + case "" ⇒ l + case _ ⇒ l.getSingleChild(n) + } + if (next == Nobody || rest.isEmpty) next else rec(next, rest) + case _ ⇒ + ref.getChild(name) + } + rec(this, names) + } + // ========= AKKA PROTECTED FUNCTIONS ========= protected[akka] def underlying: ActorCell = actorCell @@ -271,6 +320,11 @@ case class SerializedActorRef(path: String) { */ trait MinimalActorRef extends InternalActorRef { + def getParent: InternalActorRef = Nobody + def getChild(name: Iterable[String]): InternalActorRef = + if (name.size == 1 && name.head.isEmpty) this + else Nobody + //FIXME REMOVE THIS, ticket #1416 //FIXME REMOVE THIS, ticket #1415 def suspend(): Unit = () @@ -341,7 +395,13 @@ class DeadLetterActorRef(val eventStream: EventStream) extends MinimalActorRef { private def writeReplace(): AnyRef = DeadLetterActorRef.serialized } -class AskActorRef(val path: ActorPath, provider: ActorRefProvider, deathWatch: DeathWatch, timeout: Timeout, val dispatcher: MessageDispatcher) extends MinimalActorRef { +class AskActorRef( + val path: ActorPath, + override val getParent: InternalActorRef, + deathWatch: DeathWatch, + timeout: Timeout, + val dispatcher: MessageDispatcher) extends MinimalActorRef { + final val result = new DefaultPromise[Any](timeout)(dispatcher) { diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index b2271c85c7..3110ab30d1 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -27,6 +27,13 @@ import java.io.Closeable */ trait ActorRefProvider { + /** + * Reference to the supervisor of guardian and systemGuardian; this is + * exposed so that the ActorSystemImpl can use it as lookupRoot, i.e. + * for anchoring absolute actor look-ups. + */ + def rootGuardian: InternalActorRef + /** * Reference to the supervisor used for all top-level user actors. */ @@ -85,17 +92,18 @@ trait ActorRefProvider { /** * Create actor reference for a specified local or remote path, which will * be parsed using java.net.URI. If no such actor exists, it will be - * (equivalent to) a dead letter reference. + * (equivalent to) a dead letter reference. If `s` is a relative URI, resolve + * it relative to the given ref. */ - def actorFor(s: String): InternalActorRef + def actorFor(ref: InternalActorRef, s: String): InternalActorRef /** - * Create actor reference for the specified child path starting at the root - * guardian. This method always returns an actor which is “logically local”, + * Create actor reference for the specified child path starting at the + * given starting point. This method always returns an actor which is “logically local”, * i.e. it cannot be used to obtain a reference to an actor which is not * physically or logically attached to this actor system. */ - def actorFor(p: Iterable[String]): InternalActorRef + def actorFor(ref: InternalActorRef, p: Iterable[String]): InternalActorRef private[akka] def createDeathWatch(): DeathWatch @@ -127,6 +135,8 @@ trait ActorRefFactory { */ protected def guardian: InternalActorRef + protected def lookupRoot: InternalActorRef + protected def randomName(): String /** @@ -171,9 +181,12 @@ trait ActorRefFactory { def actorFor(path: ActorPath): ActorRef = provider.actorFor(path) - def actorFor(path: String): ActorRef = provider.actorFor(path) + def actorFor(path: String): ActorRef = provider.actorFor(lookupRoot, path) - def actorFor(path: Iterable[String]): ActorRef = provider.actorFor(path) + /** + * For maximum performance use a collection with efficient head & tail operations. + */ + def actorFor(path: Iterable[String]): ActorRef = provider.actorFor(lookupRoot, path) } class ActorRefProviderException(message: String) extends AkkaException(message) @@ -203,11 +216,11 @@ class LocalActorRefProvider( */ private val tempNumber = new AtomicLong - private def tempName = "$_" + Helpers.base64(tempNumber.getAndIncrement()) + private def tempName() = Helpers.base64(tempNumber.getAndIncrement()) private val tempNode = rootPath / "temp" - def tempPath = tempNode / tempName + def tempPath() = tempNode / tempName /** * Top-level anchor for the supervision hierarchy of this actor system. Will @@ -281,10 +294,36 @@ class LocalActorRefProvider( def dispatcher: MessageDispatcher = system.dispatcher lazy val terminationFuture: DefaultPromise[Unit] = new DefaultPromise[Unit](Timeout.never)(dispatcher) - lazy val rootGuardian: InternalActorRef = new LocalActorRef(system, guardianProps, theOneWhoWalksTheBubblesOfSpaceTime, rootPath, true) + + lazy val rootGuardian: InternalActorRef = new LocalActorRef(system, guardianProps, theOneWhoWalksTheBubblesOfSpaceTime, rootPath, true) { + override def getParent: InternalActorRef = this + override def getSingleChild(name: String): InternalActorRef = { + name match { + case "temp" ⇒ tempContainer + case _ ⇒ super.getSingleChild(name) + } + } + } + lazy val guardian: InternalActorRef = actorOf(system, guardianProps, rootGuardian, "user", true) + lazy val systemGuardian: InternalActorRef = actorOf(system, guardianProps.withCreator(new SystemGuardian), rootGuardian, "system", true) + lazy val tempContainer = new MinimalActorRef { + val children = new ConcurrentHashMap[String, AskActorRef] + def path = tempNode + override def getParent = rootGuardian + override def getChild(name: Iterable[String]): InternalActorRef = { + val c = children.get(name.head) + if (c == null) Nobody + else { + val t = name.tail + if (t.isEmpty) c + else c.getChild(t) + } + } + } + val deathWatch = createDeathWatch() def init(_system: ActorSystemImpl) { @@ -294,25 +333,25 @@ class LocalActorRefProvider( deathWatch.subscribe(rootGuardian, systemGuardian) } - def actorFor(path: String): InternalActorRef = path match { - case LocalActorPath(address, elems) if address == rootPath.address ⇒ - findInTree(rootGuardian.asInstanceOf[LocalActorRef], elems) + def actorFor(ref: InternalActorRef, path: String): InternalActorRef = path match { + case RelativeActorPath(elems) ⇒ + if (elems.isEmpty) deadLetters + else if (elems.head.isEmpty) actorFor(rootGuardian, elems.tail) + else actorFor(ref, elems) + case LocalActorPath(address, elems) if address == rootPath.address ⇒ actorFor(rootGuardian, elems) case _ ⇒ deadLetters } - def actorFor(path: ActorPath): InternalActorRef = findInTree(rootGuardian.asInstanceOf[LocalActorRef], path.pathElements) + def actorFor(path: ActorPath): InternalActorRef = + if (path.root == rootPath) actorFor(rootGuardian, path.pathElements) + else deadLetters - def actorFor(path: Iterable[String]): InternalActorRef = findInTree(rootGuardian.asInstanceOf[LocalActorRef], path) - - @tailrec - private def findInTree(start: LocalActorRef, path: Iterable[String]): InternalActorRef = { - if (path.isEmpty) start - else start.underlying.getChild(path.head) match { - case null ⇒ deadLetters - case child: LocalActorRef ⇒ findInTree(child, path.tail) - case _ ⇒ deadLetters + def actorFor(ref: InternalActorRef, path: Iterable[String]): InternalActorRef = + if (path.isEmpty) deadLetters + else ref.getChild(path) match { + case Nobody ⇒ deadLetters + case x ⇒ x } - } def actorOf(system: ActorSystemImpl, props: Props, supervisor: InternalActorRef, name: String, systemService: Boolean): InternalActorRef = { val path = supervisor.path / name @@ -377,7 +416,14 @@ class LocalActorRefProvider( case t if t.duration.length <= 0 ⇒ new DefaultPromise[Any](0)(dispatcher) //Abort early if nonsensical timeout case t ⇒ - val a = new AskActorRef(tempPath, this, deathWatch, t, dispatcher) + val path = tempPath() + val name = path.name + val a = new AskActorRef(path, tempContainer, deathWatch, t, dispatcher) { + override def whenDone() { + tempContainer.children.remove(name) + } + } + tempContainer.children.put(name, a) recipient.tell(message, a) a.result } diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 35b100913c..a38cdccb22 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -201,6 +201,11 @@ abstract class ActorSystem extends ActorRefFactory { */ def /(name: String): ActorPath + /** + * Construct a path below the application guardian to be used with [[ActorSystem.actorFor]]. + */ + def /(name: Iterable[String]): ActorPath + /** * Start-up time in milliseconds since the epoch. */ @@ -382,6 +387,7 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor implicit val dispatcher = dispatcherFactory.defaultGlobalDispatcher def terminationFuture: Future[Unit] = provider.terminationFuture + def lookupRoot: InternalActorRef = provider.rootGuardian def guardian: InternalActorRef = provider.guardian def systemGuardian: InternalActorRef = provider.systemGuardian def deathWatch: DeathWatch = provider.deathWatch @@ -392,6 +398,7 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor override protected def randomName(): String = Helpers.base64(nextName.incrementAndGet()) def /(actorName: String): ActorPath = guardian.path / actorName + def /(path: Iterable[String]): ActorPath = guardian.path / path private lazy val _start: this.type = { provider.init(this) diff --git a/akka-actor/src/main/scala/akka/actor/Address.scala b/akka-actor/src/main/scala/akka/actor/Address.scala index bda9fd6048..2f2560392b 100644 --- a/akka-actor/src/main/scala/akka/actor/Address.scala +++ b/akka-actor/src/main/scala/akka/actor/Address.scala @@ -22,6 +22,16 @@ case class LocalAddress(systemName: String) extends Address { def hostPort = systemName } +object RelativeActorPath { + def unapply(addr: String): Option[Iterable[String]] = { + try { + val uri = new URI(addr) + if (uri.isAbsolute) None + else Some(ActorPath.split(uri.getPath)) + } + } +} + object LocalActorPath { def unapply(addr: String): Option[(LocalAddress, Iterable[String])] = { try { @@ -30,7 +40,7 @@ object LocalActorPath { if (uri.getUserInfo != null) return None if (uri.getHost == null) return None if (uri.getPath == null) return None - Some(LocalAddress(uri.getHost), uri.getPath.split("/").drop(1)) + Some(LocalAddress(uri.getHost), ActorPath.split(uri.getPath).drop(1)) } catch { case _: URISyntaxException ⇒ None } diff --git a/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala index 6b5a0b374e..3ea18c9da5 100644 --- a/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala @@ -46,7 +46,7 @@ object RemoteActorPath { if (uri.getHost == null) return None if (uri.getPort == -1) return None if (uri.getPath == null) return None - Some(RemoteAddress(uri.getUserInfo, uri.getHost, uri.getPort), uri.getPath.split("/").drop(1)) + Some(RemoteAddress(uri.getUserInfo, uri.getHost, uri.getPort), ActorPath.split(uri.getPath).drop(1)) } catch { case _: URISyntaxException ⇒ None } diff --git a/akka-remote/src/main/scala/akka/remote/Remote.scala b/akka-remote/src/main/scala/akka/remote/Remote.scala index acd40ac3a1..23f4056dd3 100644 --- a/akka-remote/src/main/scala/akka/remote/Remote.scala +++ b/akka-remote/src/main/scala/akka/remote/Remote.scala @@ -141,7 +141,7 @@ class RemoteSystemDaemon(remote: Remote) extends Actor { message.getActorPath match { case RemoteActorPath(addr, elems) if addr == remoteAddress && elems.size > 0 ⇒ val name = elems.last - systemImpl.provider.actorFor(elems.dropRight(1)) match { + systemImpl.provider.actorFor(systemImpl.lookupRoot, elems.dropRight(1)) match { case x if x eq system.deadLetters ⇒ log.error("Parent actor does not exist, ignoring remote system daemon command [{}]", message) case parent ⇒ @@ -246,7 +246,7 @@ class RemoteMessage(input: RemoteMessageProtocol, remote: RemoteSupport, classLo val provider = remote.system.asInstanceOf[ActorSystemImpl].provider lazy val sender: ActorRef = - if (input.hasSender) provider.actorFor(input.getSender.getPath) + if (input.hasSender) provider.actorFor(provider.rootGuardian, input.getSender.getPath) else remote.system.deadLetters lazy val recipient: ActorRef = remote.system.actorFor(input.getRecipient.getPath) diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index 84febbb202..95c3fa2e73 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -39,6 +39,7 @@ class RemoteActorRefProvider( val log = Logging(eventStream, "RemoteActorRefProvider") def deathWatch = local.deathWatch + def rootGuardian = local.rootGuardian def guardian = local.guardian def systemGuardian = local.systemGuardian def nodename = remoteExtension.NodeName @@ -181,8 +182,8 @@ class RemoteActorRefProvider( } def actorFor(path: ActorPath): InternalActorRef = local.actorFor(path) - def actorFor(path: String): InternalActorRef = local.actorFor(path) - def actorFor(path: Iterable[String]): InternalActorRef = local.actorFor(path) + def actorFor(ref: InternalActorRef, path: String): InternalActorRef = local.actorFor(ref, path) + def actorFor(ref: InternalActorRef, path: Iterable[String]): InternalActorRef = local.actorFor(ref, path) // TODO remove me val optimizeLocal = new AtomicBoolean(true) @@ -267,13 +268,13 @@ private[akka] case class RemoteActorRef private[akka] ( loader: Option[ClassLoader]) extends InternalActorRef { + // FIXME + def getParent = Nobody + def getChild(name: Iterable[String]) = Nobody + @volatile private var running: Boolean = true - def name = path.name - - def address = remoteAddress + path.toString - def isTerminated: Boolean = !running def sendSystemMessage(message: SystemMessage): Unit = throw new UnsupportedOperationException("Not supported for RemoteActorRef") From 3d0bb8b415439af8479c8893f8bec4329284f178 Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 3 Dec 2011 17:39:52 +0100 Subject: [PATCH 13/30] implement ActorSeletion and document ActorRefFactory - implementation of this cool feature is now astonishingly simple: context.actorSelection("child/*") ! msg - docs propagate to ActorSystem/ActorContext --- .../scala/akka/actor/ActorLookupSpec.scala | 32 ++++- .../src/main/scala/akka/actor/Actor.scala | 14 +- .../src/main/scala/akka/actor/ActorCell.scala | 3 + .../src/main/scala/akka/actor/ActorRef.scala | 2 +- .../scala/akka/actor/ActorRefProvider.scala | 127 +++++++++++++++++- .../scala/akka/actor/ActorSelection.scala | 58 ++++++++ .../src/main/scala/akka/util/Helpers.scala | 3 + 7 files changed, 233 insertions(+), 6 deletions(-) create mode 100644 akka-actor/src/main/scala/akka/actor/ActorSelection.scala diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala index ca4e35a7e0..40e281dba2 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala @@ -4,6 +4,7 @@ package akka.actor import akka.testkit._ +import akka.util.duration._ object ActorLookupSpec { @@ -210,7 +211,6 @@ class ActorLookupSpec extends AkkaSpec { (c2 ? LookupPath(a.path)).get must be === a (c2 ? LookupString(a.path.toString)).get must be === a (c2 ? LookupString(a.path.pathElements.mkString("/", "/", ""))).get must be === a - println("start") (c2 ? LookupString("../../" + a.path.pathElements.mkString("/"))).get must be === a (c2 ? LookupString(a.path.toString + "/")).get must be === a (c2 ? LookupString(a.path.pathElements.mkString("/", "/", "") + "/")).get must be === a @@ -226,4 +226,34 @@ class ActorLookupSpec extends AkkaSpec { } + "An ActorSelection" must { + + "send messages directly" in { + ActorSelection(c1, "") ! GetSender(testActor) + expectMsg(system.deadLetters) + lastSender must be === c1 + } + + "send messages with correct sender" in { + implicit val sender = c1 + ActorSelection(c21, "../../*") ! GetSender(testActor) + val actors = receiveWhile(messages = 2) { + case `c1` ⇒ lastSender + } + actors must be === Seq(c1, c2) + expectNoMsg(1 second) + } + + "drop messages which cannot be delivered" in { + implicit val sender = c2 + ActorSelection(c21, "../../*/c21") ! GetSender(testActor) + val actors = receiveWhile(messages = 2) { + case `c2` ⇒ lastSender + } + actors must be === Seq(c21) + expectNoMsg(1 second) + } + + } + } \ 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 e741e774f4..a0c8c89fa0 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -15,15 +15,13 @@ import akka.event.Logging.Debug import akka.event.LogSource import akka.experimental import akka.AkkaException - import scala.reflect.BeanProperty import scala.util.control.NoStackTrace - import com.eaio.uuid.UUID - import java.lang.reflect.InvocationTargetException import java.util.concurrent.TimeUnit import java.util.{ Collection ⇒ JCollection } +import java.util.regex.Pattern /** * Marker trait to show which Messages are automatically handled by Akka @@ -65,6 +63,16 @@ case class Terminated(@BeanProperty actor: ActorRef) extends PossiblyHarmful case object ReceiveTimeout extends PossiblyHarmful +/** + * ActorRefFactory.actorSelection returns a special ref which sends these + * nested path descriptions whenever using ! on them, the idea being that the + * message is delivered by active routing of the various actors involved. + */ +sealed trait SelectionPath extends AutoReceivedMessage +case class SelectChildName(name: String, next: Any) extends SelectionPath +case class SelectChildPattern(pattern: Pattern, next: Any) extends SelectionPath +case class SelectParent(next: Any) extends SelectionPath + // Exceptions for Actors class IllegalActorStateException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) { diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 96867ce18e..b96b2acded 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -381,6 +381,9 @@ private[akka] class ActorCell( case ChildTerminated ⇒ handleChildTerminated(sender) case Kill ⇒ throw new ActorKilledException("Kill") case PoisonPill ⇒ self.stop() + case SelectParent(m) ⇒ parent.tell(m, msg.sender) + case SelectChildName(name, m) ⇒ if (childrenRefs contains name) childrenRefs(name).child.tell(m, msg.sender) + case SelectChildPattern(p, m) ⇒ for (c ← children if p.matcher(c.path.name).matches) c.tell(m, msg.sender) } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 09ec8ddb7a..4b6b9ae839 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -162,7 +162,7 @@ trait ScalaActorRef { ref: ActorRef ⇒ * Internal trait for assembling all the functionality needed internally on * ActorRefs. NOTE THAT THIS IS NOT A STABLE EXTERNAL INTERFACE! */ -private[akka] trait InternalActorRef extends ActorRef with ScalaActorRef { +private[akka] abstract class InternalActorRef extends ActorRef with ScalaActorRef { def resume(): Unit def suspend(): Unit def restart(cause: Throwable): Unit diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 3110ab30d1..23f269e172 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -120,7 +120,7 @@ trait ActorRefProvider { } /** - * Interface implemented by ActorSystem and AkkaContext, the only two places from which you can get fresh actors + * Interface implemented by ActorSystem and AkkaContext, the only two places from which you can get fresh actors. */ trait ActorRefFactory { @@ -154,8 +154,22 @@ trait ActorRefFactory { */ protected def actorCreated(name: String, actor: ActorRef): Unit + /** + * Create new actor as child of this context and give it an automatically + * generated name (currently similar to base64-encoded integer count, + * reversed and with “$” prepended, may change in the future). + * + * See [[akka.actor.Props]] for details on how to obtain a `Props` object. + */ def actorOf(props: Props): ActorRef = provider.actorOf(systemImpl, props, guardian, randomName(), false) + /** + * Create new actor as child of this context with the given name, which must + * not be null, empty or start with “$”. If the given name is already in use, + * and `InvalidActorNameException` is thrown. + * + * See [[akka.actor.Props]] for details on how to obtain a `Props` object. + */ def actorOf(props: Props, name: String): ActorRef = { if (name == null || name == "" || name.startsWith("$")) throw new InvalidActorNameException("actor name must not be null, empty or start with $") @@ -166,27 +180,138 @@ trait ActorRefFactory { a } + /** + * Create new actor of the given type as child of this context and give it an automatically + * generated name (currently similar to base64-encoded integer count, + * reversed and with “$” prepended, may change in the future). The type must have + * a no-arg constructor which will be invoked using reflection. + */ def actorOf[T <: Actor](implicit m: Manifest[T]): ActorRef = actorOf(Props(m.erasure.asInstanceOf[Class[_ <: Actor]])) + /** + * Create new actor of the given type as child of this context with the given name, which must + * not be null, empty or start with “$”. If the given name is already in use, + * and `InvalidActorNameException` is thrown. The type must have + * a no-arg constructor which will be invoked using reflection. + */ def actorOf[T <: Actor](name: String)(implicit m: Manifest[T]): ActorRef = actorOf(Props(m.erasure.asInstanceOf[Class[_ <: Actor]]), name) + /** + * Create new actor of the given class as child of this context and give it an automatically + * generated name (currently similar to base64-encoded integer count, + * reversed and with “$” prepended, may change in the future). The class must have + * a no-arg constructor which will be invoked using reflection. + */ def actorOf[T <: Actor](clazz: Class[T]): ActorRef = actorOf(Props(clazz)) + /** + * Create new actor as child of this context and give it an automatically + * generated name (currently similar to base64-encoded integer count, + * reversed and with “$” prepended, may change in the future). Use this + * method to pass constructor arguments to the [[akka.actor.Actor]] while using + * only default [[akka.actor.Props]]; otherwise refer to `actorOf(Props)`. + */ def actorOf(factory: ⇒ Actor): ActorRef = actorOf(Props(() ⇒ factory)) + /** + * ''Java API'': Create new actor as child of this context and give it an + * automatically generated name (currently similar to base64-encoded integer + * count, reversed and with “$” prepended, may change in the future). + * + * Identical to `actorOf(Props(() => creator.create()))`. + */ def actorOf(creator: UntypedActorFactory): ActorRef = actorOf(Props(() ⇒ creator.create())) + /** + * ''Java API'': Create new actor as child of this context with the given name, which must + * not be null, empty or start with “$”. If the given name is already in use, + * and `InvalidActorNameException` is thrown. + * + * Identical to `actorOf(Props(() => creator.create()), name)`. + */ def actorOf(creator: UntypedActorFactory, name: String): ActorRef = actorOf(Props(() ⇒ creator.create()), name) + /** + * Look-up an actor by path; if it does not exist, returns a reference to + * the dead-letter mailbox of the [[akka.actor.ActorSystem]]. If the path + * point to an actor which is not local, no attempt is made during this + * call to verify that the actor it represents does exist or is alive; use + * `watch(ref)` to be notified of the target’s termination, which is also + * signaled if the queried path cannot be resolved. + */ def actorFor(path: ActorPath): ActorRef = provider.actorFor(path) + /** + * Look-up an actor by path represented as string. + * + * Absolute URIs like `akka://appname/user/actorA` are looked up as described + * for look-ups by `actorOf(ActorPath)`. + * + * Relative URIs like `/service/actorA/childB` are looked up relative to the + * root path of the [[akka.actor.ActorSystem]] containing this factory and as + * described for look-ups by `actorOf(Iterable[String])`. + * + * Relative URIs like `myChild/grandChild` or `../myBrother` are looked up + * relative to the current context as described for look-ups by + * `actorOf(Iterable[String])` + */ def actorFor(path: String): ActorRef = provider.actorFor(lookupRoot, path) /** + * Look-up an actor by applying the given path elements, starting from the + * current context, where `".."` signifies the parent of an actor. + * + * Example: + * {{{ + * class MyActor extends Actor { + * def receive = { + * case msg => + * ... + * val target = context.actorFor(Seq("..", "myBrother", "myNephew")) + * ... + * } + * } + * }}} + * * For maximum performance use a collection with efficient head & tail operations. */ def actorFor(path: Iterable[String]): ActorRef = provider.actorFor(lookupRoot, path) + + /** + * Look-up an actor by applying the given path elements, starting from the + * current context, where `".."` signifies the parent of an actor. + * + * Example: + * {{{ + * public class MyActor extends UntypedActor { + * public void onReceive(Object msg) throws Exception { + * ... + * final List path = new ArrayList(); + * path.add(".."); + * path.add("myBrother"); + * path.add("myNephew"); + * final ActorRef target = context().actorFor(path); + * ... + * } + * } + * }}} + * + * For maximum performance use a collection with efficient head & tail operations. + */ + def actorFor(path: java.util.List[String]): ActorRef = { + import scala.collection.JavaConverters._ + provider.actorFor(lookupRoot, path.asScala) + } + + /** + * Construct an [[akka.actor.ActorSelection]] from the given path, which is + * parsed for wildcards (these are replaced by regular expressions + * internally). No attempt is made to verify the existence of any part of + * the supplied path, it is recommended to send a message and gather the + * replies in order to resolve the matching set of actors. + */ + def actorSelection(path: String): ActorSelection = ActorSelection(lookupRoot, path) } class ActorRefProviderException(message: String) extends AkkaException(message) diff --git a/akka-actor/src/main/scala/akka/actor/ActorSelection.scala b/akka-actor/src/main/scala/akka/actor/ActorSelection.scala new file mode 100644 index 0000000000..fcb7db7167 --- /dev/null +++ b/akka-actor/src/main/scala/akka/actor/ActorSelection.scala @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.actor +import java.util.regex.Pattern +import akka.util.Helpers + +abstract class ActorSelection { + this: ScalaActorSelection ⇒ + + protected def target: ActorRef + + protected def path: Array[AnyRef] + + def tell(msg: Any) { target ! toMessage(msg, path) } + + def tell(msg: Any, sender: ActorRef) { target.tell(toMessage(msg, path), sender) } + + // this may want to be fast ... + private def toMessage(msg: Any, path: Array[AnyRef]): Any = { + var acc = msg + var index = path.length - 1 + while (index >= 0) { + acc = path(index) match { + case ".." ⇒ SelectParent(acc) + case s: String ⇒ SelectChildName(s, acc) + case p: Pattern ⇒ SelectChildPattern(p, acc) + } + index -= 1 + } + acc + } +} + +object ActorSelection { + implicit def toScala(sel: ActorSelection): ScalaActorSelection = sel.asInstanceOf[ScalaActorSelection] + + /** + * Construct an ActorSelection from the given string representing a path + * relative to the given target. This operation has to create all the + * matching magic, so it is preferable to cache its result if the + * intention is to send messages frequently. + */ + def apply(anchor: ActorRef, path: String): ActorSelection = { + val elems = path.split("/+").dropWhile(_.isEmpty) + val compiled: Array[AnyRef] = elems map (x ⇒ if (x.contains("?") || x.contains("*")) Helpers.makePattern(x) else x) + new ActorSelection with ScalaActorSelection { + def target = anchor + def path = compiled + } + } +} + +trait ScalaActorSelection { + this: ActorSelection ⇒ + + def !(msg: Any)(implicit sender: ActorRef = null) = tell(msg, sender) +} \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/util/Helpers.scala b/akka-actor/src/main/scala/akka/util/Helpers.scala index 2b9f59d757..ef23c1d712 100644 --- a/akka-actor/src/main/scala/akka/util/Helpers.scala +++ b/akka-actor/src/main/scala/akka/util/Helpers.scala @@ -6,12 +6,15 @@ package akka.util import java.io.{ PrintWriter, StringWriter } import java.util.Comparator import scala.annotation.tailrec +import java.util.regex.Pattern /** * @author Jonas Bonér */ object Helpers { + def makePattern(s: String): Pattern = Pattern.compile("^\\Q" + s.replace("?", "\\E.\\Q").replace("*", "\\E.*\\Q") + "\\E$") + def compareIdentityHash(a: AnyRef, b: AnyRef): Int = { /* * make sure that there is no overflow or underflow in comparisons, so From 4c1d7223984f8582f3eda5154544ef75799b6277 Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 3 Dec 2011 18:16:41 +0100 Subject: [PATCH 14/30] fix bug in ActorRef.stop() implementation - it was telling all children to stop(), then waited for the ChildTerminated messages and finally terminated itself - this worked fine, except when the stop came from the supervisor, i.e. the recipient was suspended and did not process the ChildTerminated - so, as the mirror of Supervise() that it is, I changed ChildTerminated() to be a system message and instead of stopping processing normal messages by checking the stopping flag, just suspend the actor while awaiting the ChildTerminated's to flow in. --- .../scala/akka/actor/ActorLookupSpec.scala | 2 +- .../src/main/scala/akka/actor/Actor.scala | 2 -- .../src/main/scala/akka/actor/ActorCell.scala | 29 +++++++++---------- .../scala/akka/actor/ActorRefProvider.scala | 14 ++++----- .../akka/dispatch/AbstractDispatcher.scala | 1 + .../main/scala/akka/dispatch/Mailbox.scala | 6 ++++ .../testkit/CallingThreadDispatcher.scala | 1 + 7 files changed, 29 insertions(+), 26 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala index 40e281dba2..1f51ed6dbc 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala @@ -243,7 +243,7 @@ class ActorLookupSpec extends AkkaSpec { actors must be === Seq(c1, c2) expectNoMsg(1 second) } - + "drop messages which cannot be delivered" in { implicit val sender = c2 ActorSelection(c21, "../../*/c21") ! GetSender(testActor) diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index a0c8c89fa0..07d3cf4d1a 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -51,8 +51,6 @@ case class HotSwap(code: ActorRef ⇒ Actor.Receive, discardOld: Boolean = true) case class Failed(cause: Throwable) extends AutoReceivedMessage with PossiblyHarmful -case object ChildTerminated extends AutoReceivedMessage with PossiblyHarmful - case object RevertHotSwap extends AutoReceivedMessage with PossiblyHarmful case object PoisonPill extends AutoReceivedMessage with PossiblyHarmful diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index b96b2acded..65be64a32b 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -269,6 +269,8 @@ private[akka] class ActorCell( val c = children if (c.isEmpty) doTerminate() else { + // do not process normal messages while waiting for all children to terminate + dispatcher suspend this if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "stopping")) for (child ← c) child.stop() stopping = true @@ -290,8 +292,9 @@ private[akka] class ActorCell( try { if (stopping) message match { - case Terminate() ⇒ terminate() // to allow retry - case _ ⇒ + case Terminate() ⇒ terminate() // to allow retry + case ChildTerminated(child) ⇒ handleChildTerminated(child) + case _ ⇒ } else message match { case Create() ⇒ create() @@ -302,10 +305,11 @@ private[akka] class ActorCell( case Unlink(subject) ⇒ system.deathWatch.unsubscribe(self, subject) if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "stopped monitoring " + subject)) - case Suspend() ⇒ suspend() - case Resume() ⇒ resume() - case Terminate() ⇒ terminate() - case Supervise(child) ⇒ supervise(child) + case Suspend() ⇒ suspend() + case Resume() ⇒ resume() + case Terminate() ⇒ terminate() + case Supervise(child) ⇒ supervise(child) + case ChildTerminated(child) ⇒ handleChildTerminated(child) } } catch { case e ⇒ //Should we really catch everything here? @@ -324,9 +328,7 @@ private[akka] class ActorCell( cancelReceiveTimeout() // FIXME: leave this here??? messageHandle.message match { case msg: AutoReceivedMessage ⇒ autoReceiveMessage(messageHandle) - case msg if stopping ⇒ // receiving Terminated in response to stopping children is too common to generate noise - if (!msg.isInstanceOf[Terminated]) system.deadLetterMailbox.enqueue(self, messageHandle) - case msg ⇒ actor(msg) + case msg ⇒ actor(msg) } currentMessage = null // reset current message after successful invocation } catch { @@ -370,15 +372,10 @@ private[akka] class ActorCell( def autoReceiveMessage(msg: Envelope) { if (system.settings.DebugAutoReceive) system.eventStream.publish(Debug(self.path.toString, "received AutoReceiveMessage " + msg)) - if (stopping) msg.message match { - case ChildTerminated ⇒ handleChildTerminated(sender) - case _ ⇒ system.deadLetterMailbox.enqueue(self, msg) - } - else msg.message match { + msg.message match { case HotSwap(code, discardOld) ⇒ become(code(self), discardOld) case RevertHotSwap ⇒ unbecome() case Failed(cause) ⇒ handleFailure(sender, cause) - case ChildTerminated ⇒ handleChildTerminated(sender) case Kill ⇒ throw new ActorKilledException("Kill") case PoisonPill ⇒ self.stop() case SelectParent(m) ⇒ parent.tell(m, msg.sender) @@ -395,7 +392,7 @@ private[akka] class ActorCell( if (a ne null) a.postStop() } finally { try { - parent.tell(ChildTerminated, self) + parent.sendSystemMessage(ChildTerminated(self)) system.deathWatch.publish(Terminated(self)) if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "stopped")) } finally { diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 23f269e172..e0a4095d3e 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -10,8 +10,8 @@ import scala.annotation.tailrec import org.jboss.netty.akka.util.{ TimerTask, HashedWheelTimer } import akka.actor.Timeout.intToTimeout import akka.config.ConfigurationException -import akka.dispatch.{ SystemMessage, Supervise, Promise, MessageDispatcher, Future, DefaultPromise, Dispatcher, Mailbox, Envelope } -import akka.routing.{ ScatterGatherFirstCompletedRouter, Routing, RouterType, Router, RoutedProps, RoutedActorRef, RoundRobinRouter, RandomRouter, LocalConnectionManager, DirectRouter, BroadcastRouter } +import akka.dispatch._ +import akka.routing._ import akka.AkkaException import com.eaio.uuid.UUID import akka.util.{ Duration, Switch, Helpers } @@ -369,15 +369,15 @@ class LocalActorRefProvider( override def isTerminated = stopped.isOn override def !(message: Any)(implicit sender: ActorRef = null): Unit = stopped.ifOff(message match { - case Failed(ex) ⇒ causeOfTermination = Some(ex); sender.stop() - case ChildTerminated ⇒ stop() - case _ ⇒ log.error(this + " received unexpected message " + message) + case Failed(ex) ⇒ causeOfTermination = Some(ex); sender.stop() + case _ ⇒ log.error(this + " received unexpected message " + message) }) override def sendSystemMessage(message: SystemMessage): Unit = stopped ifOff { message match { - case Supervise(child) ⇒ // TODO register child in some map to keep track of it and enable shutdown after all dead - case _ ⇒ log.error(this + " received unexpected system message " + message) + case Supervise(child) ⇒ // TODO register child in some map to keep track of it and enable shutdown after all dead + case ChildTerminated(child) ⇒ stop() + case _ ⇒ log.error(this + " received unexpected system message " + message) } } } diff --git a/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala b/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala index 99557e33c8..0845978e4a 100644 --- a/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala +++ b/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala @@ -61,6 +61,7 @@ case class Suspend() extends SystemMessage // sent to self from ActorCell.suspen case class Resume() extends SystemMessage // sent to self from ActorCell.resume case class Terminate() extends SystemMessage // sent to self from ActorCell.stop case class Supervise(child: ActorRef) extends SystemMessage // sent to supervisor ActorRef from ActorCell.start +case class ChildTerminated(child: ActorRef) extends SystemMessage // sent to supervisor from ActorCell.doTerminate case class Link(subject: ActorRef) extends SystemMessage // sent to self from ActorCell.startsWatching case class Unlink(subject: ActorRef) extends SystemMessage // sent to self from ActorCell.stopsWatching diff --git a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala index 06de342df3..63b7c74161 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala @@ -28,6 +28,9 @@ object Mailbox { // secondary status: Scheduled bit may be added to Open/Suspended final val Scheduled = 4 + // mailbox debugging helper using println (see below) + // FIXME TODO take this out before release (but please leave in until M2!) + final val debug = false } /** @@ -164,6 +167,7 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes var processedMessages = 0 val deadlineNs = if (dispatcher.isThroughputDeadlineTimeDefined) System.nanoTime + dispatcher.throughputDeadlineTime.toNanos else 0 do { + if (debug) println(actor.self + " processing message " + nextMessage) actor invoke nextMessage processAllSystemMessages() //After we're done, process all system messages @@ -186,6 +190,7 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes var nextMessage = systemDrain() try { while (nextMessage ne null) { + if (debug) println(actor.self + " processing system message " + nextMessage + " with children " + actor.childrenRefs) actor systemInvoke nextMessage nextMessage = nextMessage.next // don’t ever execute normal message when system message present! @@ -240,6 +245,7 @@ trait DefaultSystemMessageQueue { self: Mailbox ⇒ @tailrec final def systemEnqueue(receiver: ActorRef, message: SystemMessage): Unit = { assert(message.next eq null) + if (Mailbox.debug) println(actor.self + " having enqueued " + message) val head = systemQueueGet /* * this write is safely published by the compareAndSet contained within diff --git a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala index 2a3933c93b..5bc2c8df3b 100644 --- a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala +++ b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala @@ -224,6 +224,7 @@ class CallingThreadDispatcher( } if (handle ne null) { try { + if (Mailbox.debug) println(mbox.actor.self + " processing message " + handle) mbox.actor.invoke(handle) true } catch { From ed4e302d2a0eaf8e7238eec721ae56b9e3bfbdcd Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 3 Dec 2011 21:17:22 +0100 Subject: [PATCH 15/30] make HashedWheelTimer reliably shutdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - make “shutdown” a normal boolean - use lock.readLock() whenever reading “shutdown” - use lock.writeLock() when setting it to true - throw IllegalStateException whenever no-queue --- .../netty/akka/util/HashedWheelTimer.java | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/akka-actor/src/main/java/org/jboss/netty/akka/util/HashedWheelTimer.java b/akka-actor/src/main/java/org/jboss/netty/akka/util/HashedWheelTimer.java index 6d31b327c6..5b19e1f2eb 100644 --- a/akka-actor/src/main/java/org/jboss/netty/akka/util/HashedWheelTimer.java +++ b/akka-actor/src/main/java/org/jboss/netty/akka/util/HashedWheelTimer.java @@ -80,7 +80,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; public class HashedWheelTimer implements Timer { private final Worker worker = new Worker(); final Thread workerThread; - final AtomicBoolean shutdown = new AtomicBoolean(); + boolean shutdown = false; private final long roundDuration; final long tickDuration; final Set[] wheel; @@ -181,12 +181,17 @@ public class HashedWheelTimer implements Timer { * {@linkplain #stop() stopped} already */ public synchronized void start() { - if (shutdown.get()) { - throw new IllegalStateException("cannot be started once stopped"); - } + lock.readLock().lock(); + try { + if (shutdown) { + throw new IllegalStateException("cannot be started once stopped"); + } - if (!workerThread.isAlive()) { - workerThread.start(); + if (!workerThread.isAlive()) { + workerThread.start(); + } + } finally { + lock.readLock().unlock(); } } @@ -198,8 +203,15 @@ public class HashedWheelTimer implements Timer { TimerTask.class.getSimpleName()); } - if (!shutdown.compareAndSet(false, true)) { - return Collections.emptySet(); + lock.writeLock().lock(); + try { + if (shutdown) { + return Collections.emptySet(); + } else { + shutdown = true; + } + } finally { + lock.writeLock().unlock(); } boolean interrupted = false; @@ -224,6 +236,10 @@ public class HashedWheelTimer implements Timer { return Collections.unmodifiableSet(unprocessedTimeouts); } + + public HashedWheelTimeout createTimeout(TimerTask task, long time) { + return new HashedWheelTimeout(task, time); + } public Timeout newTimeout(TimerTask task, Duration delay) { final long currentTime = System.nanoTime(); @@ -239,7 +255,7 @@ public class HashedWheelTimer implements Timer { start(); } - HashedWheelTimeout timeout = new HashedWheelTimeout(task, currentTime + delay.toNanos()); + HashedWheelTimeout timeout = createTimeout(task, currentTime + delay.toNanos()); scheduleTimeout(timeout, delay.toNanos()); return timeout; } @@ -260,6 +276,7 @@ public class HashedWheelTimer implements Timer { // Add the timeout to the wheel. lock.readLock().lock(); try { + if (shutdown) throw new IllegalStateException("cannot enqueue after shutdown"); int stopIndex = (int) (wheelCursor + relativeIndex & mask); timeout.stopIndex = stopIndex; timeout.remainingRounds = remainingRounds; @@ -277,6 +294,15 @@ public class HashedWheelTimer implements Timer { Worker() { super(); } + + private boolean shutdown() { + lock.readLock().lock(); + try { + return shutdown; + } finally { + lock.readLock().unlock(); + } + } public void run() { List expiredTimeouts = @@ -285,7 +311,7 @@ public class HashedWheelTimer implements Timer { startTime = System.nanoTime(); tick = 1; - while (!shutdown.get()) { + while (!shutdown()) { final long deadline = waitForNextTick(); if (deadline > 0) { fetchExpiredTimeouts(expiredTimeouts, deadline); @@ -372,7 +398,7 @@ public class HashedWheelTimer implements Timer { int nanoSeconds = (int) (sleepTime - (milliSeconds * 1000000)); Thread.sleep(milliSeconds, nanoSeconds); } catch (InterruptedException e) { - if (shutdown.get()) { + if (shutdown()) { return -1; } } From 1755aedb582e9d064b88b26ced10afb5ec46ac06 Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 3 Dec 2011 21:26:32 +0100 Subject: [PATCH 16/30] make scheduler shutdown more stringent - run closeScheduler upon ActorSystem termination (directly) - this will execute all outstanding tasks (dispatcher shutdowns have been queued already, because the last actor has already exited) - further use of the scheduler (e.g. from tasks just being run) results in IllegalStateException - catch such exceptions in DefaultPromise&MessageDispatcher in case of self-rescheduling tasks and execute final action immediately - also silently stop recurring tasks --- .../scala/akka/actor/ActorRefProvider.scala | 33 ++++++++++++++----- .../main/scala/akka/actor/ActorSystem.scala | 24 +++++++------- .../akka/dispatch/AbstractDispatcher.scala | 4 ++- .../src/main/scala/akka/dispatch/Future.scala | 19 +++++++++-- 4 files changed, 57 insertions(+), 23 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index e0a4095d3e..330e085b1a 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -578,21 +578,24 @@ class LocalDeathWatch extends DeathWatch with ActorClassification { * when the scheduler is created. */ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, log: LoggingAdapter, dispatcher: ⇒ MessageDispatcher) extends Scheduler with Closeable { + import org.jboss.netty.akka.util.{ Timeout ⇒ HWTimeout } + + private def exec(task: TimerTask, delay: Duration): HWTimeout = hashedWheelTimer.newTimeout(task, delay) def schedule(receiver: ActorRef, message: Any, initialDelay: Duration, delay: Duration): Cancellable = - new DefaultCancellable(hashedWheelTimer.newTimeout(createContinuousTask(receiver, message, delay), initialDelay)) + new DefaultCancellable(exec(createContinuousTask(receiver, message, delay), initialDelay)) def schedule(f: () ⇒ Unit, initialDelay: Duration, delay: Duration): Cancellable = - new DefaultCancellable(hashedWheelTimer.newTimeout(createContinuousTask(f, delay), initialDelay)) + new DefaultCancellable(exec(createContinuousTask(f, delay), initialDelay)) def scheduleOnce(runnable: Runnable, delay: Duration): Cancellable = - new DefaultCancellable(hashedWheelTimer.newTimeout(createSingleTask(runnable), delay)) + new DefaultCancellable(exec(createSingleTask(runnable), delay)) def scheduleOnce(receiver: ActorRef, message: Any, delay: Duration): Cancellable = - new DefaultCancellable(hashedWheelTimer.newTimeout(createSingleTask(receiver, message), delay)) + new DefaultCancellable(exec(createSingleTask(receiver, message), delay)) def scheduleOnce(f: () ⇒ Unit, delay: Duration): Cancellable = - new DefaultCancellable(hashedWheelTimer.newTimeout(createSingleTask(f), delay)) + new DefaultCancellable(exec(createSingleTask(f), delay)) private def createSingleTask(runnable: Runnable): TimerTask = new TimerTask() { @@ -621,7 +624,9 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, log: LoggingAdapter, // Check if the receiver is still alive and kicking before sending it a message and reschedule the task if (!receiver.isTerminated) { receiver ! message - timeout.getTimer.newTimeout(this, delay) + try timeout.getTimer.newTimeout(this, delay) catch { + case _: IllegalStateException ⇒ // stop recurring if timer is stopped + } } else { log.warning("Could not reschedule message to be sent because receiving actor has been terminated.") } @@ -633,12 +638,24 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, log: LoggingAdapter, new TimerTask { def run(timeout: org.jboss.netty.akka.util.Timeout) { dispatcher.dispatchTask(f) - timeout.getTimer.newTimeout(this, delay) + try timeout.getTimer.newTimeout(this, delay) catch { + case _: IllegalStateException ⇒ // stop recurring if timer is stopped + } } } } - def close() = hashedWheelTimer.stop() + private def execDirectly(t: HWTimeout): Unit = { + try t.getTask.run(t) catch { + case e: InterruptedException ⇒ throw e + case e: Exception ⇒ log.error(e, "exception while executing timer task") + } + } + + def close() = { + import scala.collection.JavaConverters._ + hashedWheelTimer.stop().asScala foreach (t ⇒ execDirectly(t)) + } } class DefaultCancellable(val timeout: org.jboss.netty.akka.util.Timeout) extends Cancellable { diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index a38cdccb22..484c81db58 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -406,6 +406,7 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor // this starts the reaper actor and the user-configured logging subscribers, which are also actors eventStream.start(this) eventStream.startDefaultLoggers(this) + registerOnTermination(stopScheduler()) loadExtensions() if (LogConfigOnStart) logConfiguration() this @@ -416,16 +417,15 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor def registerOnTermination[T](code: ⇒ T) { terminationFuture onComplete (_ ⇒ code) } def registerOnTermination(code: Runnable) { terminationFuture onComplete (_ ⇒ code.run) } - // TODO shutdown all that other stuff, whatever that may be def stop() { guardian.stop() - try terminationFuture.await(10 seconds) catch { - case _: FutureTimeoutException ⇒ log.warning("Failed to stop [{}] within 10 seconds", name) - } - // Dispatchers shutdown themselves, but requires the scheduler - terminationFuture onComplete (_ ⇒ stopScheduler()) } + /** + * Create the scheduler service. This one needs one special behavior: if + * Closeable, it MUST execute all outstanding tasks upon .close() in order + * to properly shutdown all dispatchers. + */ protected def createScheduler(): Scheduler = { val threadFactory = new MonitorableThreadFactory("DefaultScheduler") val hwt = new HashedWheelTimer(log, threadFactory, settings.SchedulerTickDuration, settings.SchedulerTicksPerWheel) @@ -443,12 +443,14 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor new DefaultScheduler(hwt, log, safeDispatcher) } + /* + * This is called after the last actor has signaled its termination, i.e. + * after the last dispatcher has had its chance to schedule its shutdown + * action. + */ protected def stopScheduler(): Unit = scheduler match { - case x: Closeable ⇒ - // Let dispatchers shutdown first. - // Dispatchers schedule shutdown and may also reschedule, therefore wait 4 times the shutdown delay. - x.scheduleOnce(() ⇒ { x.close(); dispatcher.shutdown() }, settings.DispatcherDefaultShutdown * 4) - case _ ⇒ + case x: Closeable ⇒ x.close() + case _ ⇒ } private val extensions = new ConcurrentIdentityHashMap[ExtensionId[_], AnyRef] diff --git a/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala b/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala index 0845978e4a..0dba2805ad 100644 --- a/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala +++ b/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala @@ -212,7 +212,9 @@ abstract class MessageDispatcher(val prerequisites: DispatcherPrerequisites) ext } case RESCHEDULED ⇒ if (shutdownScheduleUpdater.compareAndSet(MessageDispatcher.this, RESCHEDULED, SCHEDULED)) - scheduler.scheduleOnce(this, shutdownTimeout) + try scheduler.scheduleOnce(this, shutdownTimeout) catch { + case _: IllegalStateException ⇒ shutdown() + } else run() } } diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index b9def99301..cd8c22de6e 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -956,7 +956,11 @@ class DefaultPromise[T](val timeout: Timeout)(implicit val dispatcher: MessageDi val runnable = new Runnable { def run() { if (!isCompleted) { - if (!isExpired) dispatcher.prerequisites.scheduler.scheduleOnce(this, Duration(timeLeftNoinline(), TimeUnit.NANOSECONDS)) + if (!isExpired) + try dispatcher.prerequisites.scheduler.scheduleOnce(this, Duration(timeLeftNoinline(), TimeUnit.NANOSECONDS)) + catch { + case _: IllegalStateException ⇒ func(DefaultPromise.this) + } else func(DefaultPromise.this) } } @@ -983,8 +987,17 @@ class DefaultPromise[T](val timeout: Timeout)(implicit val dispatcher: MessageDi val runnable = new Runnable { def run() { if (!isCompleted) { - if (!isExpired) dispatcher.prerequisites.scheduler.scheduleOnce(this, Duration(timeLeftNoinline(), TimeUnit.NANOSECONDS)) - else promise complete (try { Right(fallback) } catch { case e ⇒ Left(e) }) // FIXME catching all and continue isn't good for OOME, ticket #1418 + val done = + if (!isExpired) + try { + dispatcher.prerequisites.scheduler.scheduleOnce(this, Duration(timeLeftNoinline(), TimeUnit.NANOSECONDS)) + true + } catch { + case _: IllegalStateException ⇒ false + } + else false + if (!done) + promise complete (try { Right(fallback) } catch { case e ⇒ Left(e) }) // FIXME catching all and continue isn't good for OOME, ticket #1418 } } } From 236ce1557c4a7f337c9514d8797e8f43e9ad8cda Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 3 Dec 2011 22:20:42 +0100 Subject: [PATCH 17/30] fix visibility of top-level actors after creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Obtaining an ActorRef means that its path must be found in look-ups. Since this is tightly coupled to supervision, the plan of managing /user and /system actors in special ConcurrentHashMaps did not succeed: the actor might not yet be supervised when the system is stopped, which would orphan it; or a look-up directly following its creation would return deadLetters. Since all of this is not desirable, I changed the strategy to sending the props and name to the respective supervisor, let it create the child and hand it back, i.e. I’m using ask/get with ActorTimeout. This is okay since top-level should not be created at MHz rate (will have to document this, though). - also fix one expected exception in TypedActorSpec and the names of configured routers --- .../scala/akka/actor/TypedActorSpec.scala | 4 +- .../src/main/scala/akka/actor/ActorCell.scala | 18 ++++---- .../scala/akka/actor/ActorRefProvider.scala | 33 ++++----------- .../main/scala/akka/actor/ActorSystem.scala | 41 +++++-------------- 4 files changed, 29 insertions(+), 67 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala index 00fb05561d..10921613d4 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala @@ -397,7 +397,9 @@ class TypedActorSpec extends AkkaSpec with BeforeAndAfterEach with BeforeAndAfte val latch = new CountDownLatch(16) val ta = TypedActor(system) val t: LifeCycles = ta.typedActorOf(classOf[LifeCycles], new Creator[LifeCyclesImpl] { def create = new LifeCyclesImpl(latch) }, Props()) - t.crash() + EventFilter[IllegalStateException]("Crash!", occurrences = 1) intercept { + t.crash() + } ta.poisonPill(t) latch.await(10, TimeUnit.SECONDS) must be === true } diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 65be64a32b..bfb8f4c9cf 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -103,16 +103,14 @@ private[akka] class ActorCell( @volatile var childrenRefs: TreeMap[String, ChildRestartStats] = emptyChildrenRefs - protected def isDuplicate(name: String): Boolean = { - if (childrenRefs contains name) true - else { - childrenRefs = childrenRefs.updated(name, ChildRestartStats(system.deadLetters)) - false - } - } - - protected def actorCreated(name: String, actor: ActorRef): Unit = { - childrenRefs = childrenRefs.updated(name, childrenRefs(name).copy(child = actor)) + def actorOf(props: Props, name: String): ActorRef = { + if (name == null || name == "" || name.startsWith("$")) + throw new InvalidActorNameException("actor name must not be null, empty or start with $") + if (childrenRefs contains name) + throw new InvalidActorNameException("actor name " + name + " is not unique!") + val actor = provider.actorOf(systemImpl, props, guardian, name, false) + childrenRefs = childrenRefs.updated(name, ChildRestartStats(actor)) + actor } var currentMessage: Envelope = null diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 330e085b1a..6345bbcbdc 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -139,21 +139,6 @@ trait ActorRefFactory { protected def randomName(): String - /** - * Child names must be unique within the context of one parent; implement - * this method to have the default implementation of actorOf perform the - * check (and throw an exception if necessary). - */ - protected def isDuplicate(name: String): Boolean - - /** - * This method is called after successfully associating an ActorRef with a - * name; that name will have been found to be “no duplicate” by isDuplicate(), - * so this hook is used to implement a reservation scheme: isDuplicate - * reserves a name slot and actorCreated() fills it with the right ActorRef. - */ - protected def actorCreated(name: String, actor: ActorRef): Unit - /** * Create new actor as child of this context and give it an automatically * generated name (currently similar to base64-encoded integer count, @@ -170,15 +155,7 @@ trait ActorRefFactory { * * See [[akka.actor.Props]] for details on how to obtain a `Props` object. */ - def actorOf(props: Props, name: String): ActorRef = { - if (name == null || name == "" || name.startsWith("$")) - throw new InvalidActorNameException("actor name must not be null, empty or start with $") - if (isDuplicate(name)) - throw new InvalidActorNameException("actor name " + name + " is not unique!") - val a = provider.actorOf(systemImpl, props, guardian, name, false) - actorCreated(name, a) - a - } + def actorOf(props: Props, name: String): ActorRef /** * Create new actor of the given type as child of this context and give it an automatically @@ -316,6 +293,8 @@ trait ActorRefFactory { class ActorRefProviderException(message: String) extends AkkaException(message) +private[akka] case class CreateChild(props: Props, name: String) + /** * Local ActorRef provider. */ @@ -384,7 +363,8 @@ class LocalActorRefProvider( private class Guardian extends Actor { def receive = { - case Terminated(_) ⇒ context.self.stop() + case Terminated(_) ⇒ context.self.stop() + case CreateChild(child, name) ⇒ sender ! (try context.actorOf(child, name) catch { case e: Exception ⇒ e }) } } @@ -393,6 +373,7 @@ class LocalActorRefProvider( case Terminated(_) ⇒ eventStream.stopDefaultLoggers() context.self.stop() + case CreateChild(child, name) ⇒ sender ! (try context.actorOf(child, name) catch { case e: Exception ⇒ e }) } } @@ -508,7 +489,7 @@ class LocalActorRefProvider( new LocalActorRef(system, props, supervisor, routedPath, systemService) } - actorOf(system, RoutedProps(routerFactory = routerFactory, connectionManager = new LocalConnectionManager(connections)), supervisor, path.toString) + actorOf(system, RoutedProps(routerFactory = routerFactory, connectionManager = new LocalConnectionManager(connections)), supervisor, path.name) case unknown ⇒ throw new Exception("Don't know how to create this actor ref! Why? Got: " + unknown) } diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 484c81db58..c7c29d0bdc 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -307,27 +307,19 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor protected def systemImpl = this - private val systemActors = new ConcurrentHashMap[String, ActorRef] + implicit def timeout = settings.ActorTimeout - private[akka] def systemActorOf(props: Props, name: String): ActorRef = { - if (systemActors.putIfAbsent(name, deadLetters) eq null) { - val actor = provider.actorOf(this, props, systemGuardian, name, true) - systemActors.replace(name, actor) - deathWatch.subscribe(systemActorsJanitor, actor) - actor - } else throw new InvalidActorNameException("system actor name " + name + " is not unique!") - } + private[akka] def systemActorOf(props: Props, name: String): ActorRef = + (systemGuardian ? CreateChild(props, name)).get match { + case ref: ActorRef ⇒ ref + case ex: Exception ⇒ throw ex + } - private val actors = new ConcurrentHashMap[String, ActorRef] - - protected def isDuplicate(name: String): Boolean = { - actors.putIfAbsent(name, deadLetters) ne null - } - - protected def actorCreated(name: String, actor: ActorRef): Unit = { - actors.replace(name, actor) - deathWatch.subscribe(actorsJanitor, actor) - } + def actorOf(props: Props, name: String): ActorRef = + (guardian ? CreateChild(props, name)).get match { + case ref: ActorRef ⇒ ref + case ex: Exception ⇒ throw ex + } import settings._ @@ -371,17 +363,6 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor } } - /* - * these cannot be initialized before the provider is started because root - * Path may depend on the ability to register extensions for reading config - */ - lazy val actorsJanitor = MinimalActorRef(provider.rootPath) { - case Terminated(x) ⇒ actors.remove(x.path.name) - } - lazy val systemActorsJanitor = MinimalActorRef(provider.rootPath) { - case Terminated(x) ⇒ systemActors.remove(x.path.name) - } - val dispatcherFactory = new Dispatchers(settings, DefaultDispatcherPrerequisites(eventStream, deadLetterMailbox, scheduler)) // TODO why implicit val dispatcher? implicit val dispatcher = dispatcherFactory.defaultGlobalDispatcher From ea4d30e732d5fdd16b5467037eb186952339b2d3 Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 3 Dec 2011 22:42:13 +0100 Subject: [PATCH 18/30] annotate my new FIXMEs with RK --- akka-actor/src/main/scala/akka/actor/ActorPath.scala | 1 + akka-actor/src/main/scala/akka/actor/ActorRef.scala | 3 +-- akka-actor/src/main/scala/akka/dispatch/Mailbox.scala | 2 +- .../src/main/scala/akka/remote/RemoteActorRefProvider.scala | 2 +- .../src/main/scala/akka/remote/netty/NettyRemoteSupport.scala | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorPath.scala b/akka-actor/src/main/scala/akka/actor/ActorPath.scala index 416866a8ee..a9c6784f26 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorPath.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorPath.scala @@ -146,6 +146,7 @@ final class ChildActorPath(val parent: ActorPath, val name: String) extends Acto } } + // TODO RK investigate Phil’s hash from scala.collection.mutable.HashTable.improve override def hashCode: Int = { import scala.util.MurmurHash._ diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 4b6b9ae839..118e07cd37 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -111,7 +111,7 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable */ def isTerminated: Boolean - // FIXME check if we should scramble the bits or whether they can stay the same + // FIXME RK check if we should scramble the bits or whether they can stay the same override def hashCode: Int = path.hashCode override def equals(that: Any): Boolean = that match { @@ -302,7 +302,6 @@ class LocalActorRef private[akka] ( /** * Memento pattern for serializing ActorRefs transparently */ -// FIXME: remove and replace by ActorPath.toString case class SerializedActorRef(path: String) { import akka.serialization.Serialization.currentSystem diff --git a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala index 63b7c74161..3389e413a9 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala @@ -29,7 +29,7 @@ object Mailbox { final val Scheduled = 4 // mailbox debugging helper using println (see below) - // FIXME TODO take this out before release (but please leave in until M2!) + // FIXME RK take this out before release (but please leave in until M2!) final val debug = false } diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index 95c3fa2e73..40b1c09864 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -268,7 +268,7 @@ private[akka] case class RemoteActorRef private[akka] ( loader: Option[ClassLoader]) extends InternalActorRef { - // FIXME + // FIXME RK def getParent = Nobody def getChild(name: Iterable[String]) = Nobody 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 f5802fe8ee..b412fcdf3e 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -647,7 +647,7 @@ class RemoteServerHandler( instruction.getCommandType match { case CommandType.CONNECT if UsePassiveConnections ⇒ val origin = instruction.getOrigin - // FIXME need to include system-name in remote protocol + // FIXME RK need to include system-name in remote protocol val inbound = RemoteAddress("BORKED", origin.getHostname, origin.getPort) val client = new PassiveRemoteClient(event.getChannel, remoteSupport, inbound) remoteSupport.bindClient(inbound, client) @@ -667,7 +667,7 @@ class RemoteServerHandler( private def getClientAddress(c: Channel): Option[RemoteAddress] = c.getRemoteAddress match { - case inet: InetSocketAddress ⇒ Some(RemoteAddress("BORKED", inet.getHostName, inet.getPort)) // FIXME Broken! + case inet: InetSocketAddress ⇒ Some(RemoteAddress("BORKED", inet.getHostName, inet.getPort)) // FIXME RK Broken! case _ ⇒ None } } From 829c67f60c9c3d3c4582da2746133bc64d1f9af6 Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 3 Dec 2011 23:14:52 +0100 Subject: [PATCH 19/30] =?UTF-8?q?fix=20one=20leak=20in=20RoutedActorRef=20?= =?UTF-8?q?(didn=E2=80=99t=20properly=20say=20good-bye)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala | 2 +- akka-actor/src/main/scala/akka/routing/Routing.scala | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 6345bbcbdc..8db16d83d3 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -498,7 +498,7 @@ class LocalActorRefProvider( /** * Creates (or fetches) a routed actor reference, configured by the 'props: RoutedProps' configuration. */ - def actorOf(system: ActorSystem, props: RoutedProps, supervisor: ActorRef, name: String): InternalActorRef = { + def actorOf(system: ActorSystem, props: RoutedProps, supervisor: InternalActorRef, name: String): InternalActorRef = { // FIXME: this needs to take supervision into account! //FIXME clustering should be implemented by cluster actor ref provider diff --git a/akka-actor/src/main/scala/akka/routing/Routing.scala b/akka-actor/src/main/scala/akka/routing/Routing.scala index 0bcd4fe22a..3060f1b847 100644 --- a/akka-actor/src/main/scala/akka/routing/Routing.scala +++ b/akka-actor/src/main/scala/akka/routing/Routing.scala @@ -177,13 +177,10 @@ abstract private[akka] class AbstractRoutedActorRef(val system: ActorSystem, val * A RoutedActorRef is an ActorRef that has a set of connected ActorRef and it uses a Router to send a message to * on (or more) of these actors. */ -private[akka] class RoutedActorRef(system: ActorSystem, val routedProps: RoutedProps, val supervisor: ActorRef, name: String) extends AbstractRoutedActorRef(system, routedProps) { +private[akka] class RoutedActorRef(system: ActorSystem, val routedProps: RoutedProps, val supervisor: InternalActorRef, name: String) extends AbstractRoutedActorRef(system, routedProps) { val path = supervisor.path / name - // FIXME (actor path): address normally has host and port, what about routed actor ref? - def address = "routed:/" + path.toString - @volatile private var running: Boolean = true @@ -194,6 +191,7 @@ private[akka] class RoutedActorRef(system: ActorSystem, val routedProps: RoutedP if (running) { running = false router.route(Routing.Broadcast(PoisonPill))(this) + supervisor.sendSystemMessage(akka.dispatch.ChildTerminated(this)) } } } From eeca88d6745915b712d10e30989e4b3c5a9573a3 Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 5 Dec 2011 15:18:22 +0100 Subject: [PATCH 20/30] =?UTF-8?q?add=20test=20for=20=E2=80=9Cunorderly?= =?UTF-8?q?=E2=80=9D=20shutdown=20of=20ActorSystem=20by=20PoisonPill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - uncovered nasty endless loop bug wrt. dead letters and a dead listener - fixing that bug removed the MainBusReaper (sigh) - also fix compilation of tutorial-first, but that REALLY needs to be changed not to create RoutedActorRef using new! --- .../scala/akka/event/EventStreamSpec.scala | 3 -- .../src/main/scala/akka/actor/ActorCell.scala | 8 ------ .../scala/akka/actor/ActorRefProvider.scala | 2 ++ .../main/scala/akka/actor/ActorSystem.scala | 1 - .../main/scala/akka/event/EventStream.scala | 28 +++++-------------- .../akka/remote/RemoteActorRefProvider.scala | 2 +- .../test/scala/akka/testkit/AkkaSpec.scala | 19 +++++++++++++ .../java/akka/tutorial/first/java/Pi.java | 4 ++- .../src/main/scala/Pi.scala | 4 ++- 9 files changed, 35 insertions(+), 36 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala b/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala index 031fb1ccb3..2a935d14cc 100644 --- a/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala @@ -51,7 +51,6 @@ class EventStreamSpec extends AkkaSpec(EventStreamSpec.config) { "manage subscriptions" in { val bus = new EventStream(true) - bus.start(impl) bus.subscribe(testActor, classOf[M]) bus.publish(M(42)) within(1 second) { @@ -64,7 +63,6 @@ class EventStreamSpec extends AkkaSpec(EventStreamSpec.config) { "manage log levels" in { val bus = new EventStream(false) - bus.start(impl) bus.startDefaultLoggers(impl) bus.publish(SetTarget(testActor)) expectMsg("OK") @@ -86,7 +84,6 @@ class EventStreamSpec extends AkkaSpec(EventStreamSpec.config) { val b2 = new B2 val c = new C val bus = new EventStream(false) - bus.start(impl) within(2 seconds) { bus.subscribe(testActor, classOf[B2]) === true bus.publish(c) diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index bfb8f4c9cf..01500a0301 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -168,14 +168,6 @@ private[akka] class ActorCell( final def children: Iterable[ActorRef] = childrenRefs.values.view.map(_.child) - final def getChild(name: String): ActorRef = - if (isTerminated) null - else { - val c = childrenRefs - if (c contains name) c(name).child - else null - } - final def tell(message: Any, sender: ActorRef): Unit = dispatcher.dispatch(this, Envelope(message, if (sender eq null) system.deadLetters else sender)) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 8db16d83d3..1f7b725062 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -365,6 +365,7 @@ class LocalActorRefProvider( def receive = { case Terminated(_) ⇒ context.self.stop() case CreateChild(child, name) ⇒ sender ! (try context.actorOf(child, name) catch { case e: Exception ⇒ e }) + case m ⇒ deadLetters ! DeadLetter(m, sender, self) } } @@ -374,6 +375,7 @@ class LocalActorRefProvider( eventStream.stopDefaultLoggers() context.self.stop() case CreateChild(child, name) ⇒ sender ! (try context.actorOf(child, name) catch { case e: Exception ⇒ e }) + case m ⇒ deadLetters ! DeadLetter(m, sender, self) } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index c7c29d0bdc..f1195d19c8 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -385,7 +385,6 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor provider.init(this) deadLetters.init(dispatcher, provider.rootPath) // this starts the reaper actor and the user-configured logging subscribers, which are also actors - eventStream.start(this) eventStream.startDefaultLoggers(this) registerOnTermination(stopScheduler()) loadExtensions() diff --git a/akka-actor/src/main/scala/akka/event/EventStream.scala b/akka-actor/src/main/scala/akka/event/EventStream.scala index 647fe2336c..6799fc7ae8 100644 --- a/akka-actor/src/main/scala/akka/event/EventStream.scala +++ b/akka-actor/src/main/scala/akka/event/EventStream.scala @@ -25,41 +25,27 @@ class EventStream(debug: Boolean = false) extends LoggingBus with SubchannelClas def isSubclass(x: Class[_], y: Class[_]) = y isAssignableFrom x } - @volatile - private var reaper: ActorRef = _ - protected def classify(event: AnyRef): Class[_] = event.getClass - protected def publish(event: AnyRef, subscriber: ActorRef) = subscriber ! event + protected def publish(event: AnyRef, subscriber: ActorRef) = { + if (subscriber.isTerminated) unsubscribe(subscriber) + else subscriber ! event + } override def subscribe(subscriber: ActorRef, channel: Class[_]): Boolean = { if (debug) publish(Logging.Debug(simpleName(this), "subscribing " + subscriber + " to channel " + channel)) - if (reaper ne null) reaper ! subscriber super.subscribe(subscriber, channel) } override def unsubscribe(subscriber: ActorRef, channel: Class[_]): Boolean = { + val ret = super.unsubscribe(subscriber, channel) if (debug) publish(Logging.Debug(simpleName(this), "unsubscribing " + subscriber + " from channel " + channel)) - super.unsubscribe(subscriber, channel) + ret } override def unsubscribe(subscriber: ActorRef) { - if (debug) publish(Logging.Debug(simpleName(this), "unsubscribing " + subscriber + " from all channels")) super.unsubscribe(subscriber) - } - - def start(system: ActorSystemImpl) { - reaper = system.systemActorOf(Props(new Actor { - def receive = { - case ref: ActorRef ⇒ watch(ref) - case Terminated(ref) ⇒ unsubscribe(ref) - } - }), "MainBusReaper-" + EventStream.generation.incrementAndGet()) - subscribers foreach (reaper ! _) - } - - def stop() { - reaper.stop() + if (debug) publish(Logging.Debug(simpleName(this), "unsubscribing " + subscriber + " from all channels")) } } \ No newline at end of file diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index 40b1c09864..0e141cccc4 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -176,7 +176,7 @@ class RemoteActorRefProvider( * Copied from LocalActorRefProvider... */ // FIXME: implement supervision, ticket #1408 - def actorOf(system: ActorSystem, props: RoutedProps, supervisor: ActorRef, name: String): InternalActorRef = { + def actorOf(system: ActorSystem, props: RoutedProps, supervisor: InternalActorRef, name: String): InternalActorRef = { if (props.connectionManager.isEmpty) throw new ConfigurationException("RoutedProps used for creating actor [" + name + "] has zero connections configured; can't create a router") new RoutedActorRef(system, props, supervisor, name) } diff --git a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala index 8e94bf10b2..be5320ee13 100644 --- a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala @@ -13,6 +13,7 @@ import akka.util.duration._ import akka.dispatch.FutureTimeoutException import com.typesafe.config.Config import com.typesafe.config.ConfigFactory +import akka.actor.PoisonPill object TimingTest extends Tag("timing") @@ -89,7 +90,9 @@ abstract class AkkaSpec(_system: ActorSystem) @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class AkkaSpecSpec extends WordSpec with MustMatchers { + "An AkkaSpec" must { + "terminate all actors" in { import scala.collection.JavaConverters._ val conf = Map( @@ -103,6 +106,22 @@ class AkkaSpecSpec extends WordSpec with MustMatchers { system.stop() spec.awaitCond(spec.ref forall (_.isTerminated), 2 seconds) } + + "must stop correctly when sending PoisonPill to rootGuardian" in { + import scala.collection.JavaConverters._ + val conf = Map( + "akka.actor.debug.lifecycle" -> true, "akka.actor.debug.event-stream" -> true, + "akka.loglevel" -> "DEBUG", "akka.stdout-loglevel" -> "DEBUG") + val system = ActorSystem("test", ConfigFactory.parseMap(conf.asJava).withFallback(AkkaSpec.testConf)) + val spec = new AkkaSpec(system) {} + val latch = new TestLatch(1)(system) + system.registerOnTermination(latch.countDown()) + + system.actorFor("/") ! PoisonPill + + latch.await(2 seconds) + } + } } diff --git a/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java b/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java index 5a80699ade..fb8457855d 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java +++ b/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java @@ -6,6 +6,7 @@ package akka.tutorial.first.java; import akka.actor.ActorRef; import akka.actor.ActorSystem; +import akka.actor.InternalActorRef; import akka.actor.UntypedActor; import akka.actor.UntypedActorFactory; import akka.japi.Creator; @@ -115,7 +116,8 @@ public class Pi { } }; RoutedProps props = new RoutedProps(routerCreator, new LocalConnectionManager(actors), new akka.actor.Timeout(-1), true); - router = new RoutedActorRef(system(), props, getSelf(), "pi"); + // FIXME REALLY this NEEDS to use getContext()! + router = new RoutedActorRef(system(), props, (InternalActorRef) getSelf(), "pi"); } // message handler diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index 3283a591f4..7199d42e02 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -6,6 +6,7 @@ package akka.tutorial.first.scala import java.util.concurrent.CountDownLatch import akka.routing.{ RoutedActorRef, LocalConnectionManager, RoundRobinRouter, RoutedProps } import akka.actor.{ ActorSystemImpl, Actor, ActorSystem } +import akka.actor.InternalActorRef object Pi extends App { @@ -55,8 +56,9 @@ object Pi extends App { val workers = Vector.fill(nrOfWorkers)(system.actorOf[Worker]) // wrap them with a load-balancing router + // FIXME REALLY this needs to use context to create the child! val props = RoutedProps(routerFactory = () ⇒ new RoundRobinRouter, connectionManager = new LocalConnectionManager(workers)) - val router = new RoutedActorRef(system, props, self, "pi") + val router = new RoutedActorRef(system, props, self.asInstanceOf[InternalActorRef], "pi") // message handler def receive = { From 0b5f8b083dded3f2021d61a8c20d426722fcf992 Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 5 Dec 2011 15:57:43 +0100 Subject: [PATCH 21/30] rename ActorPath.{pathElemens => elements} --- .../scala/akka/actor/ActorLookupSpec.scala | 46 +++++++++---------- .../src/main/scala/akka/actor/ActorPath.scala | 6 +-- .../scala/akka/actor/ActorRefProvider.scala | 2 +- .../akka/actor/mailbox/DurableMailbox.scala | 2 +- .../akka/remote/RemoteActorRefProvider.scala | 2 +- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala index 1f51ed6dbc..792d3ba4a9 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorLookupSpec.scala @@ -61,21 +61,21 @@ class ActorLookupSpec extends AkkaSpec { } "find actors by looking up their root-anchored relative path" in { - system.actorFor(c1.path.pathElements.mkString("/", "/", "")) must be === c1 - system.actorFor(c2.path.pathElements.mkString("/", "/", "")) must be === c2 - system.actorFor(c21.path.pathElements.mkString("/", "/", "")) must be === c21 + system.actorFor(c1.path.elements.mkString("/", "/", "")) must be === c1 + system.actorFor(c2.path.elements.mkString("/", "/", "")) must be === c2 + system.actorFor(c21.path.elements.mkString("/", "/", "")) must be === c21 } "find actors by looking up their relative path" in { - system.actorFor(c1.path.pathElements.mkString("/")) must be === c1 - system.actorFor(c2.path.pathElements.mkString("/")) must be === c2 - system.actorFor(c21.path.pathElements.mkString("/")) must be === c21 + system.actorFor(c1.path.elements.mkString("/")) must be === c1 + system.actorFor(c2.path.elements.mkString("/")) must be === c2 + system.actorFor(c21.path.elements.mkString("/")) must be === c21 } "find actors by looking up their path elements" in { - system.actorFor(c1.path.pathElements) must be === c1 - system.actorFor(c2.path.pathElements) must be === c2 - system.actorFor(c21.path.pathElements) must be === c21 + system.actorFor(c1.path.elements) must be === c1 + system.actorFor(c2.path.elements) must be === c2 + system.actorFor(c21.path.elements) must be === c21 } "find system-generated actors" in { @@ -109,10 +109,10 @@ class ActorLookupSpec extends AkkaSpec { "find temporary actors" in { val f = c1 ? GetSender(testActor) val a = expectMsgType[ActorRef] - a.path.pathElements.head must be === "temp" + a.path.elements.head must be === "temp" system.actorFor(a.path) must be === a system.actorFor(a.path.toString) must be === a - system.actorFor(a.path.pathElements) must be === a + system.actorFor(a.path.elements) must be === a system.actorFor(a.path.toString + "/") must be === a system.actorFor(a.path.toString + "/hallo") must be === system.deadLetters f.isCompleted must be === false @@ -151,8 +151,8 @@ class ActorLookupSpec extends AkkaSpec { "find actors by looking up their root-anchored relative path" in { def check(looker: ActorRef, pathOf: ActorRef, result: ActorRef) { - (looker ? LookupString(pathOf.path.pathElements.mkString("/", "/", ""))).get must be === result - (looker ? LookupString(pathOf.path.pathElements.mkString("/", "/", "/"))).get must be === result + (looker ? LookupString(pathOf.path.elements.mkString("/", "/", ""))).get must be === result + (looker ? LookupString(pathOf.path.elements.mkString("/", "/", "/"))).get must be === result } for { looker ← all @@ -170,7 +170,7 @@ class ActorLookupSpec extends AkkaSpec { for { looker ← Seq(c1, c2) target ← all - } check(looker, target, Seq("..") ++ target.path.pathElements.drop(1): _*) + } check(looker, target, Seq("..") ++ target.path.elements.drop(1): _*) check(c21, user, "..", "..") check(c21, root, "..", "..", "..") check(c21, root, "..", "..", "..", "..") @@ -182,8 +182,8 @@ class ActorLookupSpec extends AkkaSpec { (looker ? LookupPath(target.path)).get must be === target (looker ? LookupString(target.path.toString)).get must be === target (looker ? LookupString(target.path.toString + "/")).get must be === target - (looker ? LookupString(target.path.pathElements.mkString("/", "/", ""))).get must be === target - if (target != root) (looker ? LookupString(target.path.pathElements.mkString("/", "/", "/"))).get must be === target + (looker ? LookupString(target.path.elements.mkString("/", "/", ""))).get must be === target + if (target != root) (looker ? LookupString(target.path.elements.mkString("/", "/", "/"))).get must be === target } } for (target ← Seq(root, syst, user, system.deadLetters)) check(target) @@ -207,16 +207,16 @@ class ActorLookupSpec extends AkkaSpec { "find temporary actors" in { val f = c1 ? GetSender(testActor) val a = expectMsgType[ActorRef] - a.path.pathElements.head must be === "temp" + a.path.elements.head must be === "temp" (c2 ? LookupPath(a.path)).get must be === a (c2 ? LookupString(a.path.toString)).get must be === a - (c2 ? LookupString(a.path.pathElements.mkString("/", "/", ""))).get must be === a - (c2 ? LookupString("../../" + a.path.pathElements.mkString("/"))).get must be === a + (c2 ? LookupString(a.path.elements.mkString("/", "/", ""))).get must be === a + (c2 ? LookupString("../../" + a.path.elements.mkString("/"))).get must be === a (c2 ? LookupString(a.path.toString + "/")).get must be === a - (c2 ? LookupString(a.path.pathElements.mkString("/", "/", "") + "/")).get must be === a - (c2 ? LookupString("../../" + a.path.pathElements.mkString("/") + "/")).get must be === a - (c2 ? LookupElems(Seq("..", "..") ++ a.path.pathElements)).get must be === a - (c2 ? LookupElems(Seq("..", "..") ++ a.path.pathElements :+ "")).get must be === a + (c2 ? LookupString(a.path.elements.mkString("/", "/", "") + "/")).get must be === a + (c2 ? LookupString("../../" + a.path.elements.mkString("/") + "/")).get must be === a + (c2 ? LookupElems(Seq("..", "..") ++ a.path.elements)).get must be === a + (c2 ? LookupElems(Seq("..", "..") ++ a.path.elements :+ "")).get must be === a f.isCompleted must be === false a ! 42 f.isCompleted must be === true diff --git a/akka-actor/src/main/scala/akka/actor/ActorPath.scala b/akka-actor/src/main/scala/akka/actor/ActorPath.scala index a9c6784f26..5405002cc2 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorPath.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorPath.scala @@ -61,7 +61,7 @@ sealed trait ActorPath extends Comparable[ActorPath] { /** * Sequence of names for this path. Performance implication: has to allocate a list. */ - def pathElements: Iterable[String] + def elements: Iterable[String] /** * Walk up the tree to obtain and return the RootActorPath. @@ -82,7 +82,7 @@ final case class RootActorPath(address: Address, name: String = ActorPath.separa def /(child: String): ActorPath = new ChildActorPath(this, child) - val pathElements: Iterable[String] = List("") + val elements: Iterable[String] = List("") override val toString = address + name @@ -98,7 +98,7 @@ final class ChildActorPath(val parent: ActorPath, val name: String) extends Acto def /(child: String): ActorPath = new ChildActorPath(this, child) - def pathElements: Iterable[String] = { + def elements: Iterable[String] = { @tailrec def rec(p: ActorPath, acc: List[String]): Iterable[String] = p match { case r: RootActorPath ⇒ acc diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 1f7b725062..900b81c38f 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -451,7 +451,7 @@ class LocalActorRefProvider( } def actorFor(path: ActorPath): InternalActorRef = - if (path.root == rootPath) actorFor(rootGuardian, path.pathElements) + if (path.root == rootPath) actorFor(rootGuardian, path.elements) else deadLetters def actorFor(ref: InternalActorRef, path: Iterable[String]): InternalActorRef = diff --git a/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableMailbox.scala b/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableMailbox.scala index 2006d73478..7bb01a06f0 100644 --- a/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableMailbox.scala +++ b/akka-durable-mailboxes/akka-mailboxes-common/src/main/scala/akka/actor/mailbox/DurableMailbox.scala @@ -41,7 +41,7 @@ abstract class DurableMailbox(owner: ActorCell) extends Mailbox(owner) with Defa def system = owner.system def ownerPath = owner.self.path - val ownerPathString = ownerPath.pathElements.mkString("/") + val ownerPathString = ownerPath.elements.mkString("/") val name = "mailbox_" + Name.replaceAllIn(ownerPathString, "_") } diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index 0e141cccc4..3c05b9a158 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -212,7 +212,7 @@ class RemoteActorRefProvider( .setPayload(ByteString.copyFrom(actorFactoryBytes)) .build() - val connectionFactory = () ⇒ actorFor(RootActorPath(remoteAddress) / remote.remoteDaemon.path.pathElements) + val connectionFactory = () ⇒ actorFor(RootActorPath(remoteAddress) / remote.remoteDaemon.path.elements) // try to get the connection for the remote address, if not already there then create it val connection = remoteDaemonConnectionManager.putIfAbsent(remoteAddress, connectionFactory) From 13eb1b6c8b5e516cb0e3ebd69cdabd6cf8dcfe36 Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 5 Dec 2011 16:18:56 +0100 Subject: [PATCH 22/30] replace @volatile for childrenRefs with mailbox status read for memory consistency --- akka-actor/src/main/scala/akka/actor/ActorCell.scala | 2 -- akka-actor/src/main/scala/akka/actor/ActorRef.scala | 9 ++++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 01500a0301..2eb1ccd777 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -99,8 +99,6 @@ private[akka] class ActorCell( var receiveTimeoutData: (Long, Cancellable) = if (_receiveTimeout.isDefined) (_receiveTimeout.get, emptyCancellable) else emptyReceiveTimeoutData - // this is accessed without further synchronization during actorFor look-ups - @volatile var childrenRefs: TreeMap[String, ChildRestartStats] = emptyChildrenRefs def actorOf(props: Props, name: String): ActorRef = { diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 118e07cd37..230a4d9571 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -245,9 +245,12 @@ class LocalActorRef private[akka] ( * to inject “synthetic” actor paths like “/temp”. */ protected def getSingleChild(name: String): InternalActorRef = { - val children = actorCell.childrenRefs - if (children contains name) children(name).child.asInstanceOf[InternalActorRef] - else Nobody + if (actorCell.isTerminated) Nobody // read of the mailbox status ensures we get the latest childrenRefs + else { + val children = actorCell.childrenRefs + if (children contains name) children(name).child.asInstanceOf[InternalActorRef] + else Nobody + } } def getChild(names: Iterable[String]): InternalActorRef = { From 3c06992d2ee0ca0c9f55713bf98456924fc0cc73 Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 5 Dec 2011 17:31:54 +0100 Subject: [PATCH 23/30] incorporate review comments --- .../src/main/scala/akka/actor/ActorCell.scala | 2 +- .../src/main/scala/akka/actor/ActorPath.scala | 19 ++++------ .../src/main/scala/akka/actor/ActorRef.scala | 10 +++-- .../scala/akka/actor/ActorRefProvider.scala | 37 ++++++++++--------- .../src/main/scala/akka/actor/Address.scala | 7 +--- .../scala/akka/remote/RemoteInterface.scala | 8 +--- .../scala/akka/testkit/TestActorRef.scala | 4 -- 7 files changed, 38 insertions(+), 49 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 2eb1ccd777..c103de4eb6 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -102,7 +102,7 @@ private[akka] class ActorCell( var childrenRefs: TreeMap[String, ChildRestartStats] = emptyChildrenRefs def actorOf(props: Props, name: String): ActorRef = { - if (name == null || name == "" || name.startsWith("$")) + if (name == null || name == "" || name.charAt(0) == '$') throw new InvalidActorNameException("actor name must not be null, empty or start with $") if (childrenRefs contains name) throw new InvalidActorNameException("actor name " + name + " is not unique!") diff --git a/akka-actor/src/main/scala/akka/actor/ActorPath.scala b/akka-actor/src/main/scala/akka/actor/ActorPath.scala index 5405002cc2..09438c17c3 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorPath.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorPath.scala @@ -5,15 +5,11 @@ package akka.actor import scala.annotation.tailrec object ActorPath { - // this cannot really be changed due to usage of standard URI syntax - final val separator = "/" - final val sepLen = separator.length - def split(s: String): List[String] = { @tailrec def rec(pos: Int, acc: List[String]): List[String] = { - val from = s.lastIndexOf(separator, pos - 1) - val sub = s.substring(from + sepLen, pos) + val from = s.lastIndexOf('/', pos - 1) + val sub = s.substring(from + 1, pos) val l = sub :: acc if (from == -1) l else rec(from, l) } @@ -74,7 +70,7 @@ sealed trait ActorPath extends Comparable[ActorPath] { * Root of the hierarchy of ActorPaths. There is exactly root per ActorSystem * and node (for remote-enabled or clustered systems). */ -final case class RootActorPath(address: Address, name: String = ActorPath.separator) extends ActorPath { +final case class RootActorPath(address: Address, name: String = "/") extends ActorPath { def parent: ActorPath = this @@ -124,12 +120,11 @@ final class ChildActorPath(val parent: ActorPath, val name: String) extends Acto */ override def toString = { @tailrec - def rec(p: ActorPath, s: String): String = p match { - case r: RootActorPath ⇒ r + s - case _ if s.isEmpty ⇒ rec(p.parent, name) - case _ ⇒ rec(p.parent, p.name + ActorPath.separator + s) + def rec(p: ActorPath, s: StringBuilder): StringBuilder = p match { + case r: RootActorPath ⇒ s.insert(0, r.toString) + case _ ⇒ rec(p.parent, s.insert(0, '/').insert(0, p.name)) } - rec(this, "") + rec(parent, new StringBuilder(32).append(name)).toString } override def equals(other: Any): Boolean = { diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 230a4d9571..0ca1db018c 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -60,7 +60,7 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable /** * Comparison only takes address into account. */ - def compareTo(other: ActorRef) = this.path compareTo other.path + final def compareTo(other: ActorRef) = this.path compareTo other.path /** * Sends the specified message to the sender, i.e. fire-and-forget semantics.

@@ -112,9 +112,9 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable def isTerminated: Boolean // FIXME RK check if we should scramble the bits or whether they can stay the same - override def hashCode: Int = path.hashCode + final override def hashCode: Int = path.hashCode - override def equals(that: Any): Boolean = that match { + final override def equals(that: Any): Boolean = that match { case other: ActorRef ⇒ path == other.path case _ ⇒ false } @@ -161,6 +161,8 @@ trait ScalaActorRef { ref: ActorRef ⇒ /** * Internal trait for assembling all the functionality needed internally on * ActorRefs. NOTE THAT THIS IS NOT A STABLE EXTERNAL INTERFACE! + * + * DO NOT USE THIS UNLESS INTERNALLY WITHIN AKKA! */ private[akka] abstract class InternalActorRef extends ActorRef with ScalaActorRef { def resume(): Unit @@ -412,7 +414,7 @@ class AskActorRef( result onTimeout callback } - protected def whenDone(): Unit = {} + protected def whenDone(): Unit = () override def !(message: Any)(implicit sender: ActorRef = null): Unit = message match { case Status.Success(r) ⇒ result.completeWithResult(r) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 900b81c38f..92a49ee7c7 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -69,7 +69,7 @@ trait ActorRefProvider { * and then—when the ActorSystem is constructed—the second phase during * which actors may be created (e.g. the guardians). */ - def init(system: ActorSystemImpl) + def init(system: ActorSystemImpl): Unit private[akka] def deployer: Deployer @@ -293,6 +293,9 @@ trait ActorRefFactory { class ActorRefProviderException(message: String) extends AkkaException(message) +/** + * Internal Akka use only, used in implementation of system.actorOf. + */ private[akka] case class CreateChild(props: Props, name: String) /** @@ -324,7 +327,7 @@ class LocalActorRefProvider( private val tempNode = rootPath / "temp" - def tempPath() = tempNode / tempName + def tempPath() = tempNode / tempName() /** * Top-level anchor for the supervision hierarchy of this actor system. Will @@ -348,8 +351,8 @@ class LocalActorRefProvider( override def isTerminated = stopped.isOn override def !(message: Any)(implicit sender: ActorRef = null): Unit = stopped.ifOff(message match { - case Failed(ex) ⇒ causeOfTermination = Some(ex); sender.stop() - case _ ⇒ log.error(this + " received unexpected message " + message) + case Failed(ex) if sender ne null ⇒ causeOfTermination = Some(ex); sender.stop() + case _ ⇒ log.error(this + " received unexpected message " + message) }) override def sendSystemMessage(message: SystemMessage): Unit = stopped ifOff { @@ -422,12 +425,12 @@ class LocalActorRefProvider( def path = tempNode override def getParent = rootGuardian override def getChild(name: Iterable[String]): InternalActorRef = { - val c = children.get(name.head) - if (c == null) Nobody - else { - val t = name.tail - if (t.isEmpty) c - else c.getChild(t) + children.get(name.head) match { + case null ⇒ Nobody + case some ⇒ + val t = name.tail + if (t.isEmpty) some + else some.getChild(t) } } } @@ -563,22 +566,22 @@ class LocalDeathWatch extends DeathWatch with ActorClassification { class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, log: LoggingAdapter, dispatcher: ⇒ MessageDispatcher) extends Scheduler with Closeable { import org.jboss.netty.akka.util.{ Timeout ⇒ HWTimeout } - private def exec(task: TimerTask, delay: Duration): HWTimeout = hashedWheelTimer.newTimeout(task, delay) + private def schedule(task: TimerTask, delay: Duration): HWTimeout = hashedWheelTimer.newTimeout(task, delay) def schedule(receiver: ActorRef, message: Any, initialDelay: Duration, delay: Duration): Cancellable = - new DefaultCancellable(exec(createContinuousTask(receiver, message, delay), initialDelay)) + new DefaultCancellable(schedule(createContinuousTask(receiver, message, delay), initialDelay)) def schedule(f: () ⇒ Unit, initialDelay: Duration, delay: Duration): Cancellable = - new DefaultCancellable(exec(createContinuousTask(f, delay), initialDelay)) + new DefaultCancellable(schedule(createContinuousTask(f, delay), initialDelay)) def scheduleOnce(runnable: Runnable, delay: Duration): Cancellable = - new DefaultCancellable(exec(createSingleTask(runnable), delay)) + new DefaultCancellable(schedule(createSingleTask(runnable), delay)) def scheduleOnce(receiver: ActorRef, message: Any, delay: Duration): Cancellable = - new DefaultCancellable(exec(createSingleTask(receiver, message), delay)) + new DefaultCancellable(schedule(createSingleTask(receiver, message), delay)) def scheduleOnce(f: () ⇒ Unit, delay: Duration): Cancellable = - new DefaultCancellable(exec(createSingleTask(f), delay)) + new DefaultCancellable(schedule(createSingleTask(f), delay)) private def createSingleTask(runnable: Runnable): TimerTask = new TimerTask() { @@ -637,7 +640,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, log: LoggingAdapter, def close() = { import scala.collection.JavaConverters._ - hashedWheelTimer.stop().asScala foreach (t ⇒ execDirectly(t)) + hashedWheelTimer.stop().asScala foreach execDirectly } } diff --git a/akka-actor/src/main/scala/akka/actor/Address.scala b/akka-actor/src/main/scala/akka/actor/Address.scala index 2f2560392b..e9097d72d6 100644 --- a/akka-actor/src/main/scala/akka/actor/Address.scala +++ b/akka-actor/src/main/scala/akka/actor/Address.scala @@ -36,11 +36,8 @@ object LocalActorPath { def unapply(addr: String): Option[(LocalAddress, Iterable[String])] = { try { val uri = new URI(addr) - if (uri.getScheme != "akka") return None - if (uri.getUserInfo != null) return None - if (uri.getHost == null) return None - if (uri.getPath == null) return None - Some(LocalAddress(uri.getHost), ActorPath.split(uri.getPath).drop(1)) + if (uri.getScheme != "akka" || uri.getUserInfo != null || uri.getHost == null || uri.getPath == null) None + else Some(LocalAddress(uri.getHost), ActorPath.split(uri.getPath).drop(1)) } catch { case _: URISyntaxException ⇒ None } diff --git a/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala index 3ea18c9da5..3639f056e8 100644 --- a/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala @@ -41,12 +41,8 @@ object RemoteActorPath { def unapply(addr: String): Option[(RemoteAddress, Iterable[String])] = { try { val uri = new URI(addr) - if (uri.getScheme != "akka") return None - if (uri.getUserInfo == null) return None - if (uri.getHost == null) return None - if (uri.getPort == -1) return None - if (uri.getPath == null) return None - Some(RemoteAddress(uri.getUserInfo, uri.getHost, uri.getPort), ActorPath.split(uri.getPath).drop(1)) + if (uri.getScheme != "akka" || uri.getUserInfo == null || uri.getHost == null || uri.getPort == -1 || uri.getPath == null) None + else Some(RemoteAddress(uri.getUserInfo, uri.getHost, uri.getPort), ActorPath.split(uri.getPath).drop(1)) } catch { case _: URISyntaxException ⇒ None } diff --git a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala index ce1454b3b1..92d5dbf096 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala @@ -62,10 +62,6 @@ class TestActorRef[T <: Actor]( override def toString = "TestActor[" + path + "]" - override def equals(other: Any) = other match { - case r: TestActorRef[_] ⇒ path == r.path - case _ ⇒ false - } } object TestActorRef { From d2cffe7e331f3d9eb797dac9e590005c1ccf71eb Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 5 Dec 2011 18:23:49 +0100 Subject: [PATCH 24/30] do not use the only two special characters in Helpers.base64 If automatically generated names contains * and ?, they might do something strange when being looked up. There was no real issue with our current tests, but I just want to avoid the issue. --- akka-actor/src/main/scala/akka/util/Helpers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-actor/src/main/scala/akka/util/Helpers.scala b/akka-actor/src/main/scala/akka/util/Helpers.scala index ef23c1d712..830ec28881 100644 --- a/akka-actor/src/main/scala/akka/util/Helpers.scala +++ b/akka-actor/src/main/scala/akka/util/Helpers.scala @@ -29,7 +29,7 @@ object Helpers { def compare(a: AnyRef, b: AnyRef): Int = compareIdentityHash(a, b) } - final val base64chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789*?" + final val base64chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+%" @tailrec def base64(l: Long, sb: StringBuilder = new StringBuilder("$")): String = { From c0c9487451848665c9ede5ae53f5c723f61d317d Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 5 Dec 2011 18:52:32 +0100 Subject: [PATCH 25/30] make testActor spew out uncollected messages after test end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (so you don’t need expectNoMsg after tests) --- .../src/main/scala/akka/testkit/TestKit.scala | 5 +++ .../test/scala/akka/testkit/AkkaSpec.scala | 37 ++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala index c0476a74cc..b709243775 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala @@ -39,6 +39,11 @@ class TestActor(queue: BlockingDeque[TestActor.Message]) extends Actor { val observe = ignore map (ignoreFunc ⇒ if (ignoreFunc isDefinedAt x) !ignoreFunc(x) else true) getOrElse true if (observe) queue.offerLast(RealMessage(x, sender)) } + + override def postStop() = { + import scala.collection.JavaConverters._ + queue.asScala foreach { m ⇒ system.deadLetters ! DeadLetter(m.msg, m.sender, self) } + } } /** diff --git a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala index be5320ee13..ed8b38ce10 100644 --- a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala @@ -14,6 +14,9 @@ import akka.dispatch.FutureTimeoutException import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import akka.actor.PoisonPill +import java.util.concurrent.LinkedBlockingQueue +import akka.actor.CreateChild +import akka.actor.DeadLetter object TimingTest extends Tag("timing") @@ -94,11 +97,12 @@ class AkkaSpecSpec extends WordSpec with MustMatchers { "An AkkaSpec" must { "terminate all actors" in { + // verbose config just for demonstration purposes, please leave in in case of debugging import scala.collection.JavaConverters._ val conf = Map( "akka.actor.debug.lifecycle" -> true, "akka.actor.debug.event-stream" -> true, "akka.loglevel" -> "DEBUG", "akka.stdout-loglevel" -> "DEBUG") - val system = ActorSystem("test", ConfigFactory.parseMap(conf.asJava).withFallback(AkkaSpec.testConf)) + val system = ActorSystem("AkkaSpec1", ConfigFactory.parseMap(conf.asJava).withFallback(AkkaSpec.testConf)) val spec = new AkkaSpec(system) { val ref = Seq(testActor, system.actorOf(Props.empty, "name")) } @@ -108,11 +112,7 @@ class AkkaSpecSpec extends WordSpec with MustMatchers { } "must stop correctly when sending PoisonPill to rootGuardian" in { - import scala.collection.JavaConverters._ - val conf = Map( - "akka.actor.debug.lifecycle" -> true, "akka.actor.debug.event-stream" -> true, - "akka.loglevel" -> "DEBUG", "akka.stdout-loglevel" -> "DEBUG") - val system = ActorSystem("test", ConfigFactory.parseMap(conf.asJava).withFallback(AkkaSpec.testConf)) + val system = ActorSystem("AkkaSpec2", AkkaSpec.testConf) val spec = new AkkaSpec(system) {} val latch = new TestLatch(1)(system) system.registerOnTermination(latch.countDown()) @@ -122,6 +122,31 @@ class AkkaSpecSpec extends WordSpec with MustMatchers { latch.await(2 seconds) } + "must enqueue unread messages from testActor to deadLetters" in { + val system = ActorSystem("AkkaSpec2", AkkaSpec.testConf) + + var locker = Seq.empty[DeadLetter] + implicit val timeout = system.settings.ActorTimeout + implicit val davieJones = (system.actorFor("/") ? CreateChild(Props(new Actor { + def receive = { + case m: DeadLetter ⇒ locker :+= m + } + }), "davieJones")).as[ActorRef].get + + system.eventStream.subscribe(davieJones, classOf[DeadLetter]) + + val probe = new TestProbe(system) + probe.ref ! 42 + + val latch = new TestLatch(1)(system) + system.registerOnTermination(latch.countDown()) + system.stop() + latch.await(2 seconds) + + // this will typically also contain log messages which were sent after the logger shutdown + locker must contain(DeadLetter(42, davieJones, probe.ref)) + } + } } From cdc5492101d1152127cfbebc8ad3d99b6ef64ebb Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 5 Dec 2011 21:03:05 +0100 Subject: [PATCH 26/30] correct spelling of Davy Jones --- akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala index ed8b38ce10..bf81a29e3f 100644 --- a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala @@ -127,13 +127,13 @@ class AkkaSpecSpec extends WordSpec with MustMatchers { var locker = Seq.empty[DeadLetter] implicit val timeout = system.settings.ActorTimeout - implicit val davieJones = (system.actorFor("/") ? CreateChild(Props(new Actor { + implicit val davyJones = (system.actorFor("/") ? CreateChild(Props(new Actor { def receive = { case m: DeadLetter ⇒ locker :+= m } - }), "davieJones")).as[ActorRef].get + }), "davyJones")).as[ActorRef].get - system.eventStream.subscribe(davieJones, classOf[DeadLetter]) + system.eventStream.subscribe(davyJones, classOf[DeadLetter]) val probe = new TestProbe(system) probe.ref ! 42 @@ -144,7 +144,7 @@ class AkkaSpecSpec extends WordSpec with MustMatchers { latch.await(2 seconds) // this will typically also contain log messages which were sent after the logger shutdown - locker must contain(DeadLetter(42, davieJones, probe.ref)) + locker must contain(DeadLetter(42, davyJones, probe.ref)) } } From 82dc4d67c9b4272adcd6713c8286501ddbe020b0 Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 5 Dec 2011 21:43:58 +0100 Subject: [PATCH 27/30] fix remaining review comments --- akka-actor/src/main/scala/akka/event/Logging.scala | 4 ++-- akka-remote/src/main/scala/akka/remote/Remote.scala | 2 +- project/AkkaBuild.scala | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/akka-actor/src/main/scala/akka/event/Logging.scala b/akka-actor/src/main/scala/akka/event/Logging.scala index 8e523d45a6..672e4e5398 100644 --- a/akka-actor/src/main/scala/akka/event/Logging.scala +++ b/akka-actor/src/main/scala/akka/event/Logging.scala @@ -224,9 +224,9 @@ object LogSource { */ object Logging { - object Extension extends ExtensionKey[Ext] + object Extension extends ExtensionKey[LogExt] - class Ext(system: ActorSystemImpl) extends Extension { + class LogExt(system: ActorSystemImpl) extends Extension { private val loggerId = new AtomicInteger def id() = loggerId.incrementAndGet() } diff --git a/akka-remote/src/main/scala/akka/remote/Remote.scala b/akka-remote/src/main/scala/akka/remote/Remote.scala index 23f4056dd3..665dc91f83 100644 --- a/akka-remote/src/main/scala/akka/remote/Remote.scala +++ b/akka-remote/src/main/scala/akka/remote/Remote.scala @@ -139,7 +139,7 @@ class RemoteSystemDaemon(remote: Remote) extends Actor { } message.getActorPath match { - case RemoteActorPath(addr, elems) if addr == remoteAddress && elems.size > 0 ⇒ + case RemoteActorPath(`remoteAddress`, elems) if elems.size > 0 ⇒ val name = elems.last systemImpl.provider.actorFor(systemImpl.lookupRoot, elems.dropRight(1)) match { case x if x eq system.deadLetters ⇒ diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index cc698631e4..fc33c1a3c5 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -81,6 +81,7 @@ object AkkaBuild extends Build { id = "akka-remote", base = file("akka-remote"), dependencies = Seq(stm, actorTests % "test->test", testkit % "test->test"), + // FIXME re-enable ASAP settings = defaultSettings /*++ multiJvmSettings*/ ++ Seq( libraryDependencies ++= Dependencies.cluster, extraOptions in MultiJvm <<= (sourceDirectory in MultiJvm) { src => From b2a8e4ccae93e88ca83536eb8c2983fddd172706 Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 6 Dec 2011 09:27:32 +0100 Subject: [PATCH 28/30] document requirements of our Scheduler service --- .../java/org/jboss/netty/akka/util/HashedWheelTimer.java | 4 ++++ .../src/main/scala/akka/actor/ActorRefProvider.scala | 4 ++++ akka-actor/src/main/scala/akka/actor/ActorSystem.scala | 4 ++++ akka-actor/src/main/scala/akka/actor/Scheduler.scala | 9 +++++++++ 4 files changed, 21 insertions(+) diff --git a/akka-actor/src/main/java/org/jboss/netty/akka/util/HashedWheelTimer.java b/akka-actor/src/main/java/org/jboss/netty/akka/util/HashedWheelTimer.java index 5b19e1f2eb..d0112ded79 100644 --- a/akka-actor/src/main/java/org/jboss/netty/akka/util/HashedWheelTimer.java +++ b/akka-actor/src/main/java/org/jboss/netty/akka/util/HashedWheelTimer.java @@ -76,6 +76,10 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * @version $Rev: 2297 $, $Date: 2010-06-07 10:50:02 +0900 (Mon, 07 Jun 2010) $ * * The original implementation has been slightly altered to fit the specific requirements of Akka. + * + * Specifically: it is required to throw an IllegalStateException if a job + * cannot be queued. If no such exception is thrown, the job must be executed + * (or returned upon stop()). */ public class HashedWheelTimer implements Timer { private final Worker worker = new Worker(); diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 51b2bd48af..2fae75983c 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -562,6 +562,10 @@ class LocalDeathWatch extends DeathWatch with ActorClassification { * Scheduled tasks (Runnable and functions) are executed with the supplied dispatcher. * Note that dispatcher is by-name parameter, because dispatcher might not be initialized * when the scheduler is created. + * + * The HashedWheelTimer used by this class MUST throw an IllegalStateException + * if it does not enqueue a task. Once a task is queued, it MUST be executed or + * returned from stop(). */ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, log: LoggingAdapter, dispatcher: ⇒ MessageDispatcher) extends Scheduler with Closeable { import org.jboss.netty.akka.util.{ Timeout ⇒ HWTimeout } diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index f9d79e32e5..130bdca85e 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -398,6 +398,10 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor * Create the scheduler service. This one needs one special behavior: if * Closeable, it MUST execute all outstanding tasks upon .close() in order * to properly shutdown all dispatchers. + * + * Furthermore, this timer service MUST throw IllegalStateException if it + * cannot schedule a task. Once scheduled, the task MUST be executed. If + * executed upon close(), the task may execute before its timeout. */ protected def createScheduler(): Scheduler = { val threadFactory = new MonitorableThreadFactory("DefaultScheduler") diff --git a/akka-actor/src/main/scala/akka/actor/Scheduler.scala b/akka-actor/src/main/scala/akka/actor/Scheduler.scala index 5b32a86a60..121b0da181 100644 --- a/akka-actor/src/main/scala/akka/actor/Scheduler.scala +++ b/akka-actor/src/main/scala/akka/actor/Scheduler.scala @@ -14,6 +14,15 @@ package akka.actor import akka.util.Duration + /** + * An Akka scheduler service. This one needs one special behavior: if + * Closeable, it MUST execute all outstanding tasks upon .close() in order + * to properly shutdown all dispatchers. + * + * Furthermore, this timer service MUST throw IllegalStateException if it + * cannot schedule a task. Once scheduled, the task MUST be executed. If + * executed upon close(), the task may execute before its timeout. + */ trait Scheduler { /** * Schedules a message to be sent repeatedly with an initial delay and frequency. From 96182885c2eba389837c7871b9dcd9e68a7141f6 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 6 Dec 2011 11:28:29 +0100 Subject: [PATCH 29/30] #1437 - Replacing self with the DeadLetterActorRef when the ActorCell is shut down, this to direct captured self references to the DLQ --- akka-actor/src/main/scala/akka/actor/ActorCell.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index c7a37de589..6311ef0993 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -417,7 +417,7 @@ private[akka] class ActorCell( } } - final def clearActorFields(): Unit = setActorFields(context = null, self = null) + final def clearActorFields(): Unit = setActorFields(context = null, self = system.deadLetters) final def setActorFields(context: ActorContext, self: ActorRef) { @tailrec From d6fc97c48db726f7a329c8e7dccdd2fdc87abd45 Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 6 Dec 2011 16:27:10 +0100 Subject: [PATCH 30/30] introduce akka.actor.creation-timeout to make Jenkins happy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - I had reused akka.actor.timeout for ActorSystem.actorOf calls (which “ask” the guardian to create the actor), but 5sec proved too short for Jenkins - CreationTimeout defaults to 30sec now, let’s hope Jenkins is not slower than _that_. --- akka-actor/src/main/resources/reference.conf | 1 + .../scala/akka/actor/ActorRefProvider.scala | 4 ++-- .../main/scala/akka/actor/ActorSystem.scala | 13 ++++++++----- .../src/main/scala/akka/actor/Scheduler.scala | 18 +++++++++--------- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/akka-actor/src/main/resources/reference.conf b/akka-actor/src/main/resources/reference.conf index 1d00474462..63f6cc21d9 100644 --- a/akka-actor/src/main/resources/reference.conf +++ b/akka-actor/src/main/resources/reference.conf @@ -36,6 +36,7 @@ akka { actor { provider = "akka.actor.LocalActorRefProvider" + creation-timeout = 20s # Timeout for ActorSystem.actorOf timeout = 5s # Default timeout for Future based invocations # - Actor: ask && ? # - UntypedActor: ask diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 2fae75983c..91c3115c32 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -562,8 +562,8 @@ class LocalDeathWatch extends DeathWatch with ActorClassification { * Scheduled tasks (Runnable and functions) are executed with the supplied dispatcher. * Note that dispatcher is by-name parameter, because dispatcher might not be initialized * when the scheduler is created. - * - * The HashedWheelTimer used by this class MUST throw an IllegalStateException + * + * The HashedWheelTimer used by this class MUST throw an IllegalStateException * if it does not enqueue a task. Once a task is queued, it MUST be executed or * returned from stop(). */ diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 130bdca85e..dff9042b8f 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -71,6 +71,7 @@ object ActorSystem { val ProviderClass = getString("akka.actor.provider") + val CreationTimeout = Timeout(Duration(getMilliseconds("akka.actor.creation-timeout"), MILLISECONDS)) val ActorTimeout = Timeout(Duration(getMilliseconds("akka.actor.timeout"), MILLISECONDS)) val SerializeAllMessages = getBoolean("akka.actor.serialize-messages") @@ -300,19 +301,21 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor protected def systemImpl = this - implicit def timeout = settings.ActorTimeout - - private[akka] def systemActorOf(props: Props, name: String): ActorRef = + private[akka] def systemActorOf(props: Props, name: String): ActorRef = { + implicit val timeout = settings.CreationTimeout (systemGuardian ? CreateChild(props, name)).get match { case ref: ActorRef ⇒ ref case ex: Exception ⇒ throw ex } + } - def actorOf(props: Props, name: String): ActorRef = + def actorOf(props: Props, name: String): ActorRef = { + implicit val timeout = settings.CreationTimeout (guardian ? CreateChild(props, name)).get match { case ref: ActorRef ⇒ ref case ex: Exception ⇒ throw ex } + } import settings._ @@ -398,7 +401,7 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor * Create the scheduler service. This one needs one special behavior: if * Closeable, it MUST execute all outstanding tasks upon .close() in order * to properly shutdown all dispatchers. - * + * * Furthermore, this timer service MUST throw IllegalStateException if it * cannot schedule a task. Once scheduled, the task MUST be executed. If * executed upon close(), the task may execute before its timeout. diff --git a/akka-actor/src/main/scala/akka/actor/Scheduler.scala b/akka-actor/src/main/scala/akka/actor/Scheduler.scala index 121b0da181..19697921fd 100644 --- a/akka-actor/src/main/scala/akka/actor/Scheduler.scala +++ b/akka-actor/src/main/scala/akka/actor/Scheduler.scala @@ -14,15 +14,15 @@ package akka.actor import akka.util.Duration - /** - * An Akka scheduler service. This one needs one special behavior: if - * Closeable, it MUST execute all outstanding tasks upon .close() in order - * to properly shutdown all dispatchers. - * - * Furthermore, this timer service MUST throw IllegalStateException if it - * cannot schedule a task. Once scheduled, the task MUST be executed. If - * executed upon close(), the task may execute before its timeout. - */ +/** + * An Akka scheduler service. This one needs one special behavior: if + * Closeable, it MUST execute all outstanding tasks upon .close() in order + * to properly shutdown all dispatchers. + * + * Furthermore, this timer service MUST throw IllegalStateException if it + * cannot schedule a task. Once scheduled, the task MUST be executed. If + * executed upon close(), the task may execute before its timeout. + */ trait Scheduler { /** * Schedules a message to be sent repeatedly with an initial delay and frequency.