From 3b98ba249bbaf51e2f39963ec5e4411ac20a7b85 Mon Sep 17 00:00:00 2001 From: Rich Dougherty Date: Thu, 31 Jan 2013 21:48:03 +1300 Subject: [PATCH] Improved router documentation. Fixes #2874 * Edit of basic usage and design. * Rearranged order of sections. * Added more information on special message types. * Added receiveN to JavaTestKit. --- .../docs/jrouting/RouterViaConfigExample.java | 4 +- .../jrouting/RouterViaProgramDocTest.scala | 5 + .../jrouting/RouterViaProgramDocTestBase.java | 130 +++++++ akka-docs/rst/java/remoting.rst | 2 + akka-docs/rst/java/routing.rst | 321 ++++++++++++------ akka-docs/rst/java/untyped-actors.rst | 14 +- akka-docs/rst/scala/actors.rst | 16 +- .../routing/RouterViaProgramDocSpec.scala | 103 ++++++ akka-docs/rst/scala/remoting.rst | 2 + akka-docs/rst/scala/routing.rst | 320 +++++++++++------ .../main/java/akka/testkit/JavaTestKit.java | 4 + 11 files changed, 718 insertions(+), 203 deletions(-) create mode 100644 akka-docs/rst/java/code/docs/jrouting/RouterViaProgramDocTest.scala create mode 100644 akka-docs/rst/java/code/docs/jrouting/RouterViaProgramDocTestBase.java create mode 100644 akka-docs/rst/scala/code/docs/routing/RouterViaProgramDocSpec.scala diff --git a/akka-docs/rst/java/code/docs/jrouting/RouterViaConfigExample.java b/akka-docs/rst/java/code/docs/jrouting/RouterViaConfigExample.java index c21881c80b..ee4ba7ff7e 100644 --- a/akka-docs/rst/java/code/docs/jrouting/RouterViaConfigExample.java +++ b/akka-docs/rst/java/code/docs/jrouting/RouterViaConfigExample.java @@ -44,7 +44,7 @@ public class RouterViaConfigExample { ActorSystem system = ActorSystem.create("Example", config); //#configurableRouting ActorRef router = system.actorOf( - new Props(ExampleActor.class).withRouter(new FromConfig()), "router"); + new Props(ExampleActor.class).withRouter(new FromConfig()), "myrouter1"); //#configurableRouting for (int i = 1; i <= 10; i++) { router.tell(new ExampleActor.Message(i), null); @@ -52,7 +52,7 @@ public class RouterViaConfigExample { //#configurableRoutingWithResizer ActorRef router2 = system.actorOf( - new Props(ExampleActor.class).withRouter(new FromConfig()), "router2"); + new Props(ExampleActor.class).withRouter(new FromConfig()), "myrouter2"); //#configurableRoutingWithResizer for (int i = 1; i <= 10; i++) { router2.tell(new ExampleActor.Message(i), null); diff --git a/akka-docs/rst/java/code/docs/jrouting/RouterViaProgramDocTest.scala b/akka-docs/rst/java/code/docs/jrouting/RouterViaProgramDocTest.scala new file mode 100644 index 0000000000..141c214d02 --- /dev/null +++ b/akka-docs/rst/java/code/docs/jrouting/RouterViaProgramDocTest.scala @@ -0,0 +1,5 @@ +package docs.jrouting; + +import org.scalatest.junit.JUnitSuite + +class RouterViaProgramDocTest extends RouterViaProgramDocTestBase with JUnitSuite diff --git a/akka-docs/rst/java/code/docs/jrouting/RouterViaProgramDocTestBase.java b/akka-docs/rst/java/code/docs/jrouting/RouterViaProgramDocTestBase.java new file mode 100644 index 0000000000..ece75496a4 --- /dev/null +++ b/akka-docs/rst/java/code/docs/jrouting/RouterViaProgramDocTestBase.java @@ -0,0 +1,130 @@ +/** + * Copyright (C) 2009-2013 Typesafe Inc. + */ +package docs.jrouting; + +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.actor.Kill; +import akka.actor.PoisonPill; +import akka.actor.Props; +import akka.actor.UntypedActor; +import akka.remote.routing.RemoteRouterConfig; +import akka.routing.Broadcast; +import akka.routing.RoundRobinRouter; +import akka.testkit.JavaTestKit; +import docs.jrouting.RouterViaProgramExample.ExampleActor; +import docs.routing.RouterViaProgramDocSpec.Echo; +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class RouterViaProgramDocTestBase { + + static ActorSystem system; + + @BeforeClass + public static void setup() { + system = ActorSystem.create(); + } + + @AfterClass + public static void teardown() { + system.shutdown(); + } + + private static class JavaTestKitWithSelf extends JavaTestKit { + public JavaTestKitWithSelf(ActorSystem system) { + super(system); + } + /** + * Wrap `getRef()` so our examples look like they're within a normal actor. + */ + public ActorRef getSelf() { + return getRef(); + } + } + + @Test + public void demonstrateRouteesFromPaths() { + new JavaTestKit(system) {{ + //#programmaticRoutingRouteePaths + ActorRef actor1 = system.actorOf(new Props(ExampleActor.class), "actor1"); + ActorRef actor2 = system.actorOf(new Props(ExampleActor.class), "actor2"); + ActorRef actor3 = system.actorOf(new Props(ExampleActor.class), "actor3"); + Iterable routees = Arrays.asList( + new String[] { "/user/actor1", "/user/actor2", "/user/actor3" }); + ActorRef router = system.actorOf( + new Props().withRouter(new RoundRobinRouter(routees))); + //#programmaticRoutingRouteePaths + for (int i = 1; i <= 6; i++) { + router.tell(new ExampleActor.Message(i), null); + } + }}; + } + + @Test + public void demonstrateBroadcast() { + new JavaTestKitWithSelf(system) {{ + ActorRef router = system.actorOf(new Props(Echo.class).withRouter(new RoundRobinRouter(5))); + //#broadcastDavyJonesWarning + router.tell(new Broadcast("Watch out for Davy Jones' locker"), getSelf()); + //#broadcastDavyJonesWarning + receiveN(5, duration("5 seconds")); + }}; + } + + @Test + public void demonstratePoisonPill() { + new JavaTestKitWithSelf(system) {{ + ActorRef router = system.actorOf(new Props(Echo.class).withRouter(new RoundRobinRouter(5))); + //#poisonPill + router.tell(PoisonPill.getInstance(), getSelf()); + //#poisonPill + expectNoMsg(duration("1 seconds")); + Assert.assertTrue(router.isTerminated()); + }}; + } + + @Test + public void demonstrateBroadcastOfPoisonPill() { + new JavaTestKitWithSelf(system) {{ + ActorRef router = system.actorOf(new Props(Echo.class).withRouter(new RoundRobinRouter(5))); + //#broadcastPoisonPill + router.tell(new Broadcast(PoisonPill.getInstance()), getSelf()); + //#broadcastPoisonPill + expectNoMsg(duration("1 seconds")); + Assert.assertTrue(router.isTerminated()); + }}; + } + + @Test + public void demonstrateKill() { + new JavaTestKitWithSelf(system) {{ + ActorRef router = system.actorOf(new Props(Echo.class).withRouter(new RoundRobinRouter(5))); + //#kill + router.tell(Kill.getInstance(), getSelf()); + //#kill + expectNoMsg(duration("1 seconds")); + Assert.assertTrue(router.isTerminated()); + }}; + } + + @Test + public void demonstrateBroadcastOfKill() { + new JavaTestKitWithSelf(system) {{ + ActorRef router = system.actorOf(new Props(Echo.class).withRouter(new RoundRobinRouter(5))); + //#broadcastKill + router.tell(new Broadcast(Kill.getInstance()), getSelf()); + //#broadcastKill + expectNoMsg(duration("1 seconds")); + Assert.assertTrue(router.isTerminated()); + }}; + } + +} diff --git a/akka-docs/rst/java/remoting.rst b/akka-docs/rst/java/remoting.rst index fae73cfde7..a3c17e6bec 100644 --- a/akka-docs/rst/java/remoting.rst +++ b/akka-docs/rst/java/remoting.rst @@ -131,6 +131,8 @@ actor systems has to have a JAR containing the class. ``/foo/bar`` is considered **more specific** than ``/foo/*`` and only the highest priority match is used. Please note that it **cannot** be used to partially match section, like this: ``/foo*/bar``, ``/f*o/bar`` etc. +.. _remote-deployment-warnings-java: + .. warning:: *Caveat:* Remote deployment ties both systems together in a tight fashion, diff --git a/akka-docs/rst/java/routing.rst b/akka-docs/rst/java/routing.rst index 9f74e6d902..16c72bd876 100644 --- a/akka-docs/rst/java/routing.rst +++ b/akka-docs/rst/java/routing.rst @@ -4,11 +4,14 @@ Routing (Java) ============== -A Router is an actor that routes incoming messages to outbound actors. -The router routes the messages sent to it to its underlying actors called 'routees'. +A Router is an actor that receives messages and efficiently routes them to other actors, known as +its *routees*. -Akka comes with some defined routers out of the box, but as you will see in this chapter it -is really easy to create your own. The routers shipped with Akka are: +Different routing strategies can be used, according to your application's needs. Akka comes with +several useful routing strategies right out of the box. But, as you will see in this chapter, it is +also possible to :ref:`create your own `. + +The routers shipped with Akka are: * ``akka.routing.RoundRobinRouter`` * ``akka.routing.RandomRouter`` @@ -17,112 +20,119 @@ is really easy to create your own. The routers shipped with Akka are: * ``akka.routing.ScatterGatherFirstCompletedRouter`` * ``akka.routing.ConsistentHashingRouter`` -Routers In Action +Routers in Action ^^^^^^^^^^^^^^^^^ -This is an example of how to create a router that is defined in configuration: - -.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-round-robin - -.. includecode:: code/docs/jrouting/RouterViaConfigExample.java#configurableRouting - -This is an example of how to programmatically create a router and set the number of routees it should create: - -.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#programmaticRoutingNrOfInstances - -You can also give the router already created routees as in: - -.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#programmaticRoutingRoutees - -.. note:: - - No actor factory or class needs to be provided in this - case, as the ``Router`` will not create any children on its own (which is not - true anymore when using a resizer). The routees can also be specified by giving - their path strings. - - -When you create a router programmatically you define the number of routees *or* you pass already created routees to it. -If you send both parameters to the router *only* the latter will be used, i.e. ``nrOfInstances`` is disregarded. - -*It is also worth pointing out that if you define the ``router`` in the -configuration file then this value will be used instead of any programmatically -sent parameters. The decision whether to create a router at all, on the other -hand, must be taken within the code, i.e. you cannot make something a router by -external configuration alone (see below for details).* - -Once you have the router actor it is just to send messages to it as you would to any actor: +Sending a message to a router is easy. .. code-block:: java router.tell(new MyMsg()); -The router will forward the message to its routees according to its routing policy. +A router actor forwards messages to its routees according to its routing policy. -Remotely Deploying Routees -************************** +.. note:: -In addition to being able to supply looked-up remote actors as routees, you can -make the router deploy its created children on a set of remote hosts; this will -be done in round-robin fashion. In order to do that, wrap the router -configuration in a :class:`RemoteRouterConfig`, attaching the remote addresses of -the nodes to deploy to. Naturally, this requires you to include the -``akka-remote`` module on your classpath: + In general, any message sent to a router will be sent onwards to its routees. But there are a + few exceptions. These are documented in the :ref:`router-special-messages-java` section below. + +Creating a Router +***************** + +Routers and routees are closely intertwined. Router actors are created by specifying the desired +*routee* :class:`Props` then attaching the router's :class:`RouterConfig`. When you create a router +actor it will create routees, as needed, as its children. + +For example, the following code and configuration snippets show how to create a :ref:`round-robin +` router that forwards messages to five ``ExampleActor`` routees. The +routees will be created as the router's children. + +.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-round-robin + +.. includecode:: code/docs/jrouting/RouterViaConfigExample.java#configurableRouting + +Here is the same example, but with the router configuration provided programmatically instead of +from configuration. + +.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#programmaticRoutingNrOfInstances + +Sometimes, rather than having the router create its routees, it is desirable to create routees +separately and provide them to the router for its use. You can do this by passing an +:class:`Iterable` of routees to the router's configuration. + +The example below shows how to create a router by providing it with the :class:`ActorRef`\s of three +routee actors. + +.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#programmaticRoutingRoutees + +Routees can also be specified by providing their path strings instead of their :class:`ActorRef`\s. + +.. includecode:: code/docs/jrouting/RouterViaProgramDocTestBase.java#programmaticRoutingRouteePaths + +In addition to being able to supply looked-up remote actors as routees, you can ask the router to +deploy its created children on a set of remote hosts. Routees will be deployed in round-robin +fashion. In order to deploy routees remotely, wrap the router configuration in a +:class:`RemoteRouterConfig`, attaching the remote addresses of the nodes to deploy to. Remote +deployment requires the ``akka-remote`` module to be included in the classpath. .. includecode:: code/docs/jrouting/RouterViaProgramExample.java#remoteRoutees -How Routing is Designed within Akka -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +There are a few gotchas to be aware of when creating routers: -Routers behave like single actors, but they should also not hinder scalability. -This apparent contradiction is solved by making routers be represented by a -special :class:`RoutedActorRef` (implementation detail, what the user gets is -an :class:`ActorRef` as usual) which dispatches incoming messages destined -for the routees without actually invoking the router actor’s behavior (and thus -avoiding its mailbox; the single router actor’s task is to manage all aspects -related to the lifecycle of the routees). This means that the code which decides -which route to take is invoked concurrently from all possible senders and hence -must be thread-safe, it cannot live the simple and happy life of code within an -actor. +* If you define the ``router`` in the configuration file then this value will be used instead of any + programmatically provided parameters. +* Although routers can be configured in the configuration file, they must still be created + programmatically, i.e. you cannot make a router through external configuration alone. +* If you provide the ``routees`` in the router configuration then + the value of ``nrOfInstances``, if provided, will be disregarded. +* When you provide routees programmatically the router will generally ignore the routee + :class:`Props`, as it does not need to create routees. However, if you use a :ref:`resizable + router ` then the routee :class:`Props` will be used whenever the + resizer creates new routees. +* The same issues that apply to remotely-deployed actors also apply to remotely-deployed routees. + Read about :ref:`the limitations of remote deployment ` for + more information. -There is one part in the above paragraph which warrants some more background -explanation: Why does a router need a “head” which is actual parent to all the -routees? The initial design tried to side-step this issue, but location -transparency as well as mandatory parental supervision required a redesign. -Each of the actors which the router spawns must have its unique identity, which -translates into a unique actor path. Since the router has only one given name -in its parent’s context, another level in the name space is needed, which -according to the addressing semantics implies the existence of an actor with -the router’s name. This is not only necessary for the internal messaging -involved in creating, restarting and terminating actors, it is also needed when -the pooled actors need to converse with other actors and receive replies in a -deterministic fashion. Since each actor knows its own external representation -as well as that of its parent, the routees decide where replies should be sent -when reacting to a message: +Routers, Routees and Senders +**************************** -.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#reply-with-parent +The router forwards messages onto its routees without changing the original sender. When a routee +replies to a routed message, the reply will be sent to the original sender, not to the router. + +When a router creates routees, they are created as the routers children. This gives each routee its +own identity in the actor system. + +When a routee replies it can :ref:`set itself ` as the sender of the reply. .. includecode:: code/docs/jrouting/RouterViaProgramExample.java#reply-with-self -It is apparent now why routing needs to be enabled in code rather than being -possible to “bolt on” later: whether or not an actor is routed means a change -to the actor hierarchy, changing the actor paths of all children of the router. -The routees especially do need to know that they are routed to in order to -choose the sender reference for any messages they dispatch as shown above. +However, it is often useful for routees to set the *router* as the sender. For example, you might +want to set the router as the sender if you want to hide the details of the routees behind the +router. The following code snippet shows how to set the parent router as sender. -Routers vs. Supervision +.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#reply-with-parent + +Note that different code would be needed if the routees were not children of the router, i.e. if +they were provided when the router was created. + +Routers and Supervision ^^^^^^^^^^^^^^^^^^^^^^^ -As explained in the previous section, routers create new actor instances as -children of the “head” router, who therefore also is their supervisor. The -supervisor strategy of this actor can be configured by means of the -:meth:`RouterConfig.supervisorStrategy` property, which is supported for all -built-in router types. It defaults to “always escalate”, which leads to the -application of the router’s parent’s supervision directive to all children of -the router uniformly (i.e. not only the one which failed). It should be -mentioned that the router overrides the default behavior of terminating all -children upon restart, which means that a restart—while re-creating them—does -not have an effect on the number of actors in the pool. +Routees can be created by a router or provided to the router when it is created. Any routees that +are created by a router will be created as the router's children. The router is therefore also the +children's supervisor. + +The supervision strategy of the router actor can be configured with the +:meth:`RouterConfig.supervisorStrategy` property. If no configuration is provided, routers default +to a strategy of “always escalate”. This means that errors are passed up to the router's supervisor +for handling. The router's supervisor will decide what to do about any errors. + +Note the router's supervisor will treat the error as an error with the router itself. Therefore a +directive to stop or restart will cause the router *itself* to stop or restart. The router, in +turn, will cause its children to stop and restart. + +It should be mentioned that the router's restart behavior has been overridden so that a restart, +while still re-creating the children, will still preserve the same number of actors in the pool. Setting the strategy is easily done: @@ -133,6 +143,8 @@ Another potentially useful approach is to give the router the same strategy as its parent, which effectively treats all actors in the pool as if they were direct children of their grand-parent instead. +.. _note-router-terminated-children-java: + .. note:: If the child of a router terminates, the router will not automatically spawn @@ -151,6 +163,8 @@ and .. includecode:: code/docs/jrouting/FibonacciActor.java#fibonacciActor +.. _round-robin-router-java: + RoundRobinRouter **************** Routes in a `round-robin `_ fashion to its routees. @@ -255,6 +269,12 @@ This is an example of how to define a broadcast router in configuration: .. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-broadcast +.. note:: + + Broadcast routers always broadcast *every* message to their routees. If you do not want to + broadcast every message, then you can use a non-broadcasting router and use + :ref:`broadcast-messages-java` as needed. + ScatterGatherFirstCompletedRouter ********************************* The ScatterGatherFirstCompletedRouter will send the message on to all its routees as a future. @@ -323,18 +343,94 @@ This is an example of how to define a consistent-hashing router in configuration .. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-consistent-hashing +.. _router-special-messages-java: + +Handling for Special Messages +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Most messages sent to routers will be forwarded according to the routers' usual routing rules. +However there are a few types of messages that have special behavior. + +.. _broadcast-messages-java: + Broadcast Messages -^^^^^^^^^^^^^^^^^^ +****************** -There is a special type of message that will be sent to all routees regardless of the router. -This message is called ``Broadcast`` and is used in the following manner: +A ``Broadcast`` message can be used to send a message to *all* of a router's routees. When a router +receives a ``Broadcast`` message, it will broadcast that message's *payload* to all routees, no +matter how that router would normally route its messages. -.. code-block:: java +The example below shows how you would use a ``Broadcast`` message to send a very important message +to every routee of a router. - router.tell(new Broadcast("Watch out for Davy Jones' locker")); +.. includecode:: code/docs/jrouting/RouterViaProgramDocTestBase.java#broadcastDavyJonesWarning -Only the actual message is forwarded to the routees, i.e. "Watch out for Davy Jones' locker" in the example above. -It is up to the routee implementation whether to handle the broadcast message or not. +In this example the router receives the ``Broadcast`` message, extracts its payload +(``"Watch out for Davy Jones' locker"``), and then sends the payload on to all of the router's +routees. It is up to each each routee actor to handle the received payload message. + +PoisonPill Messages +******************* + +A ``PoisonPill`` message has special handling for all actors, including for routers. When any actor +receives a ``PoisonPill`` message, that actor will be stopped. See the :ref:`poison-pill-java` +documentation for details. + +.. includecode:: code/docs/jrouting/RouterViaProgramDocTestBase.java#poisonPill + +For a router, which normally passes on messages to routees, it is important to realised that +``PoisonPill`` messages are processed by the router only. ``PoisonPill`` messages sent to a router +will *not* be sent on to routees. + +However, a ``PoisonPill`` message sent to a router may still affect its routees, because it will +stop the router and when the router stops it also stops its children. Stopping children is normal +actor behavior. The router will stop routees that it has created as children. Each child will +process its current message and then tstop. This may lead to some messages being unprocessed. See +the documentation on :ref:`stopping-actors-java` for more information. + +If you wish to stop a router and its routees, but you would like the routees to first process all +the messages currently in their mailboxes, then you should not send a ``PoisonPill`` message to the +router. Instead you should wrap a ``PoisonPill`` message inside a broadcast message so that each +routee will the ``PoisonPill`` message directly. Note that this will stop all routees, even if the +routees aren't children of the router, i.e. even routees programmatically provided to the router. + +.. includecode:: code/docs/jrouting/RouterViaProgramDocTestBase.java#broadcastPoisonPill + +With the code shown above, each routee will receive a ``PoisonPill`` message. Each routee will +continue to process its messages as normal, eventually processing the ``PoisonPill``. This will +cause the routee to stop. After all routees have stopped the router will itself be :ref:`stopped +automatically `. + +.. note:: + + Brendan W McAdams' excellent blog post `Distributing Akka Workloads - And Shutting Down Afterwards + `_ + discusses in more detail how ``PoisonPill`` messages can be used to shut down routers and routees. + +Kill Messages +************* + +``Kill`` messages are another type of message that has special handling. See +:ref:`killing-actors-java` for general information about how actors handle ``Kill`` messages. + +When a ``Kill`` message is sent to a router the router processes the message internally, and does +*not* send it on to its routees. The router will throw an :class:`ActorKilledException` and fail. It +will then be either resumed, restarted or terminated, depending how it is supervised. + +Routees that are children of the router will also be suspended, and will be affected by the +supervision directive that is applied to the router. Routees that are not the routers children, i.e. +those that were created externally to the router, will not be affected. + +.. includecode:: code/docs/jrouting/RouterViaProgramDocTestBase.java#kill + +As with the ``PoisonPill`` message, there is a distinction between killing a router, which +indirectly kills its children (who happen to be routees), and killing routees directly (some of whom +may not be children.) To kill routees directly the router should be sent a ``Kill`` message wrapped +in a ``Broadcast`` message. + +.. includecode:: code/docs/jrouting/RouterViaProgramDocTestBase.java#broadcastKill + +.. _resizable-routers-java: Dynamically Resizable Routers ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -368,12 +464,43 @@ will be used instead of any programmatically sent parameters.* this, configure the pool to use a balancing dispatcher, see `Configuring Dispatchers`_ for more information. +.. _router-design-java: + +How Routing is Designed within Akka +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On the surface routers look like normal actors, but they are actually implemented differently. +Routers are designed to be extremely efficient at receiving messages and passing them quickly on to +routees. + +A normal actor can be used for routing messages, but an actor's single-threaded processing can +become a bottleneck. Routers can achieve much higher throughput with an optimization to the usual +message-processing pipeline that allows concurrent routing. This is achieved by embedding routers' +routing logic directly in their :class:`ActorRef` rather than in the router actor. Messages sent to +a router's :class:`ActorRef` can be immediately routed to the routee, bypassing the single-threaded +router actor entirely. + +The cost to this is, of course, that the internals of routing code are more complicated than if +routers were implemented with normal actors. Fortunately all of this complexity is invisible to +consumers of the routing API. However, it is something to be aware of when implementing your own +routers. + +.. _custom-router-java: + Custom Router ^^^^^^^^^^^^^ -You can also create your own router should you not find any of the ones provided by Akka sufficient for your needs. +You can create your own router should you not find any of the ones provided by Akka sufficient for your needs. In order to roll your own router you have to fulfill certain criteria which are explained in this section. +Before creating your own router you should consider whether a normal actor with router-like +behavior might do the job just as well as a full-blown router. As explained +:ref:`above `, the primary benefit of routers over normal actors is their +higher performance. But they are somewhat more complicated to write than normal actors. Therefore if +lower maximum throughput is acceptable in your application you may wish to stick with traditional +actors. This section, however, assumes that you wish to get maximum performance and so demonstrates +how you can create your own router. + The router created in this example is a simple vote counter. It will route the votes to specific vote counter actors. In this case we only have two parties the Republicans and the Democrats. We would like a router that forwards all democrat related messages to the Democrat actor and all republican related messages to the Republican actor. diff --git a/akka-docs/rst/java/untyped-actors.rst b/akka-docs/rst/java/untyped-actors.rst index 2acc8bbee1..4d77194d86 100644 --- a/akka-docs/rst/java/untyped-actors.rst +++ b/akka-docs/rst/java/untyped-actors.rst @@ -322,6 +322,8 @@ message. This gives the best concurrency and scalability characteristics. actor.tell("Hello"); +.. _actors-tell-sender-java: + Or with the sender reference passed along with the message and available to the receiving Actor in its ``getSender: ActorRef`` member field. The target actor can use this to reply to the original sender, by using ``getSender().tell(replyMsg)``. @@ -499,6 +501,8 @@ enables cleaning up of resources: actor and create its replacement in response to the :class:`Terminated` message which will eventually arrive. +.. _poison-pill-java: + PoisonPill ---------- @@ -628,14 +632,18 @@ actor's state which have the same property. The :class:`Stash` trait’s implementation of :meth:`preRestart` will call ``unstashAll()``, which is usually the desired behavior. +.. _killing-actors-java: Killing an Actor ================ -You can kill an actor by sending a ``Kill`` message. This will restart the actor -through regular supervisor semantics. +You can kill an actor by sending a ``Kill`` message. This will cause the actor +to throw a :class:`ActorKilledException`, triggering a failure. The actor will +suspend operation and its supervisor will be asked how to handle the failure, +which may mean resuming the actor, restarting it or terminating it completely. +See :ref:`supervision-directives` for more information. -Use it like this: +Use ``Kill`` like this: .. includecode:: code/docs/actor/UntypedActorDocTestBase.java :include: import-actors diff --git a/akka-docs/rst/scala/actors.rst b/akka-docs/rst/scala/actors.rst index 4f44497485..64bdf31e53 100644 --- a/akka-docs/rst/scala/actors.rst +++ b/akka-docs/rst/scala/actors.rst @@ -441,6 +441,8 @@ message. This gives the best concurrency and scalability characteristics. actor ! "hello" +.. _actors-tell-sender-scala: + If invoked from within an Actor, then the sending actor reference will be implicitly passed along with the message and available to the receiving Actor in its ``sender: ActorRef`` member field. The target actor can use this @@ -626,6 +628,8 @@ enables cleaning up of resources: actor and create its replacement in response to the :class:`Terminated` message which will eventually arrive. +.. _poison-pill-scala: + PoisonPill ---------- @@ -756,18 +760,22 @@ actor's state which have the same property. The :class:`Stash` trait’s implementation of :meth:`preRestart` will call ``unstashAll()``, which is usually the desired behavior. +.. _killing-actors-scala: Killing an Actor ================ -You can kill an actor by sending a ``Kill`` message. This will restart the actor -through regular supervisor semantics. +You can kill an actor by sending a ``Kill`` message. This will cause the actor +to throw a :class:`ActorKilledException`, triggering a failure. The actor will +suspend operation and its supervisor will be asked how to handle the failure, +which may mean resuming the actor, restarting it or terminating it completely. +See :ref:`supervision-directives` for more information. -Use it like this: +Use ``Kill`` like this: .. code-block:: scala - // kill the actor called 'victim' + // kill the 'victim' actor victim ! Kill diff --git a/akka-docs/rst/scala/code/docs/routing/RouterViaProgramDocSpec.scala b/akka-docs/rst/scala/code/docs/routing/RouterViaProgramDocSpec.scala new file mode 100644 index 0000000000..109bed0e36 --- /dev/null +++ b/akka-docs/rst/scala/code/docs/routing/RouterViaProgramDocSpec.scala @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2009-2013 Typesafe Inc. + */ +package docs.routing + +import akka.actor.{ Actor, ActorRef, ActorSystem, Props, ActorLogging } +import akka.routing.ConsistentHashingRouter.ConsistentHashable +import akka.routing.FromConfig +import akka.routing.RoundRobinRouter +import akka.testkit._ +import com.typesafe.config.ConfigFactory +import scala.concurrent.duration._ + +object RouterViaProgramDocSpec { + case class Message1(nbr: Int) + + class ExampleActor1 extends Actor { + def receive = { + case m @ Message1(nbr) ⇒ sender ! (self, m) + } + } + + class Echo extends Actor { + def receive = { + case m ⇒ sender ! m + } + } +} + +class RouterViaProgramDocSpec extends AkkaSpec with ImplicitSender { + import RouterViaProgramDocSpec._ + + "demonstrate routees from paths" in { + + //#programmaticRoutingRouteePaths + val actor1 = system.actorOf(Props[ExampleActor1], "actor1") + val actor2 = system.actorOf(Props[ExampleActor1], "actor2") + val actor3 = system.actorOf(Props[ExampleActor1], "actor3") + val routees = Vector[String]("/user/actor1", "/user/actor2", "/user/actor3") + val router = system.actorOf(Props().withRouter( + RoundRobinRouter(routees = routees))) + //#programmaticRoutingRouteePaths + 1 to 6 foreach { i ⇒ router ! Message1(i) } + val received = receiveN(6, 5.seconds.dilated) + 1 to 6 foreach { i ⇒ + val expectedActor = system.actorFor(routees((i - 1) % routees.length)) + val expectedMsg = Message1(i) + received must contain[AnyRef]((expectedActor, expectedMsg)) + } + } + + "demonstrate broadcast" in { + val router = system.actorOf(Props[Echo].withRouter(RoundRobinRouter(nrOfInstances = 5))) + //#broadcastDavyJonesWarning + import akka.routing.Broadcast + router ! Broadcast("Watch out for Davy Jones' locker") + //#broadcastDavyJonesWarning + receiveN(5, 5.seconds.dilated) must have length (5) + } + + "demonstrate PoisonPill" in { + val router = system.actorOf(Props[Echo].withRouter(RoundRobinRouter(nrOfInstances = 5))) + //#poisonPill + import akka.actor.PoisonPill + router ! PoisonPill + //#poisonPill + expectNoMsg(1.seconds.dilated) + router.isTerminated must be(true) + } + + "demonstrate broadcast of PoisonPill" in { + val router = system.actorOf(Props[Echo].withRouter(RoundRobinRouter(nrOfInstances = 5))) + //#broadcastPoisonPill + import akka.actor.PoisonPill + import akka.routing.Broadcast + router ! Broadcast(PoisonPill) + //#broadcastPoisonPill + expectNoMsg(1.seconds.dilated) + router.isTerminated must be(true) + } + + "demonstrate Kill" in { + val router = system.actorOf(Props[Echo].withRouter(RoundRobinRouter(nrOfInstances = 5))) + //#kill + import akka.actor.Kill + router ! Kill + //#kill + expectNoMsg(1.seconds.dilated) + router.isTerminated must be(true) + } + + "demonstrate broadcast of Kill" in { + val router = system.actorOf(Props[Echo].withRouter(RoundRobinRouter(nrOfInstances = 5))) + //#broadcastKill + import akka.actor.Kill + import akka.routing.Broadcast + router ! Broadcast(Kill) + //#broadcastKill + expectNoMsg(1.seconds.dilated) + router.isTerminated must be(true) + } + +} diff --git a/akka-docs/rst/scala/remoting.rst b/akka-docs/rst/scala/remoting.rst index cf12c93c60..c5ee06a8d0 100644 --- a/akka-docs/rst/scala/remoting.rst +++ b/akka-docs/rst/scala/remoting.rst @@ -138,6 +138,8 @@ actor systems has to have a JAR containing the class. ``/foo/bar`` is considered **more specific** than ``/foo/*`` and only the highest priority match is used. Please note that it **cannot** be used to partially match section, like this: ``/foo*/bar``, ``/f*o/bar`` etc. +.. _remote-deployment-warnings-scala: + .. warning:: *Caveat:* Remote deployment ties both systems together in a tight fashion, diff --git a/akka-docs/rst/scala/routing.rst b/akka-docs/rst/scala/routing.rst index f04223b5d3..4b6e8c5011 100644 --- a/akka-docs/rst/scala/routing.rst +++ b/akka-docs/rst/scala/routing.rst @@ -4,11 +4,14 @@ Routing (Scala) =============== -A Router is an actor that routes incoming messages to outbound actors. -The router routes the messages sent to it to its underlying actors called 'routees'. +A Router is an actor that receives messages and efficiently routes them to other actors, known as +its *routees*. -Akka comes with some defined routers out of the box, but as you will see in this chapter it -is really easy to create your own. The routers shipped with Akka are: +Different routing strategies can be used, according to your application's needs. Akka comes with +several useful routing strategies right out of the box. But, as you will see in this chapter, it is +also possible to :ref:`create your own `. + +The routers shipped with Akka are: * ``akka.routing.RoundRobinRouter`` * ``akka.routing.RandomRouter`` @@ -17,112 +20,120 @@ is really easy to create your own. The routers shipped with Akka are: * ``akka.routing.ScatterGatherFirstCompletedRouter`` * ``akka.routing.ConsistentHashingRouter`` -Routers In Action +Routers in Action ^^^^^^^^^^^^^^^^^ -This is an example of how to create a router that is defined in configuration: - -.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-round-robin - -.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#configurableRouting - -This is an example of how to programmatically create a router and set the number of routees it should create: - -.. includecode:: code/docs/routing/RouterViaProgramExample.scala#programmaticRoutingNrOfInstances - -You can also give the router already created routees as in: - -.. includecode:: code/docs/routing/RouterViaProgramExample.scala#programmaticRoutingRoutees - -.. note:: - - No actor factory or class needs to be provided in this - case, as the ``Router`` will not create any children on its own (which is not - true anymore when using a resizer). The routees can also be specified by giving - their path strings. - - -When you create a router programmatically you define the number of routees *or* you pass already created routees to it. -If you send both parameters to the router *only* the latter will be used, i.e. ``nrOfInstances`` is disregarded. - -*It is also worth pointing out that if you define the ``router`` in the -configuration file then this value will be used instead of any programmatically -sent parameters. The decision whether to create a router at all, on the other -hand, must be taken within the code, i.e. you cannot make something a router by -external configuration alone (see below for details).* - -Once you have the router actor it is just to send messages to it as you would to any actor: +Sending a message to a router is easy. .. code-block:: scala router ! MyMsg -The router will forward the message to its routees according to its routing policy. +A router actor forwards messages to its routees according to its routing policy. -Remotely Deploying Routees -************************** +.. note:: -In addition to being able to supply looked-up remote actors as routees, you can -make the router deploy its created children on a set of remote hosts; this will -be done in round-robin fashion. In order to do that, wrap the router -configuration in a :class:`RemoteRouterConfig`, attaching the remote addresses of -the nodes to deploy to. Naturally, this requires you to include the -``akka-remote`` module on your classpath: + In general, any message sent to a router will be sent onwards to its routees. But there are a + few exceptions. These are documented in the :ref:`router-special-messages-scala` section below. + +Creating a Router +***************** + +Routers and routees are closely intertwined. Router actors are created by specifying the desired +*routee* :class:`Props` then attaching the router's :class:`RouterConfig`. When you create a router +actor it will create routees, as needed, as its children. + +For example, the following code and configuration snippets show how to create a :ref:`round-robin +` router that forwards messages to five ``ExampleActor`` routees. The +routees will be created as the router's children. + +.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-round-robin + +.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#configurableRouting + +Here is the same example, but with the router configuration provided programmatically instead of +from configuration. + +.. includecode:: code/docs/routing/RouterViaProgramExample.scala#programmaticRoutingNrOfInstances + +Sometimes, rather than having the router create its routees, it is desirable to create routees +separately and provide them to the router for its use. You can do this by passing an +:class:`Iterable` of routees to the router's configuration. + +The example below shows how to create a router by providing it with the :class:`ActorRef`\s of three +routee actors. + +.. includecode:: code/docs/routing/RouterViaProgramExample.scala#programmaticRoutingRoutees + +Routees can also be specified by providing their path strings instead of their :class:`ActorRef`\s. + +.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#programmaticRoutingRouteePaths + +In addition to being able to supply looked-up remote actors as routees, you can ask the router to +deploy its created children on a set of remote hosts. Routees will be deployed in round-robin +fashion. In order to deploy routees remotely, wrap the router configuration in a +:class:`RemoteRouterConfig`, attaching the remote addresses of the nodes to deploy to. Remote +deployment requires the ``akka-remote`` module to be included in the classpath. .. includecode:: code/docs/routing/RouterViaProgramExample.scala#remoteRoutees -How Routing is Designed within Akka -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +There are a few gotchas to be aware of when creating routers: -Routers behave like single actors, but they should also not hinder scalability. -This apparent contradiction is solved by making routers be represented by a -special :class:`RoutedActorRef` (implementation detail, what the user gets is -an :class:`ActorRef` as usual) which dispatches incoming messages destined -for the routees without actually invoking the router actor’s behavior (and thus -avoiding its mailbox; the single router actor’s task is to manage all aspects -related to the lifecycle of the routees). This means that the code which decides -which route to take is invoked concurrently from all possible senders and hence -must be thread-safe, it cannot live the simple and happy life of code within an -actor. +* If you define the ``router`` in the configuration file then this value will be used instead of any + programmatically provided parameters. +* Although routers can be configured in the configuration file, they must still be created + programmatically, i.e. you cannot make a router through external configuration alone. +* If you provide the ``routees`` in the router configuration then + the value of ``nrOfInstances``, if provided, will be disregarded. +* When you provide routees programmatically the router will generally ignore the routee + :class:`Props`, as it does not need to create routees. However, if you use a :ref:`resizable + router ` then the routee :class:`Props` will be used whenever the + resizer creates new routees. +* The same issues that apply to remotely-deployed actors also apply to remotely-deployed routees. + Read about :ref:`the limitations of remote deployment ` for + more information. -There is one part in the above paragraph which warrants some more background -explanation: Why does a router need a “head” which is actual parent to all the -routees? The initial design tried to side-step this issue, but location -transparency as well as mandatory parental supervision required a redesign. -Each of the actors which the router spawns must have its unique identity, which -translates into a unique actor path. Since the router has only one given name -in its parent’s context, another level in the name space is needed, which -according to the addressing semantics implies the existence of an actor with -the router’s name. This is not only necessary for the internal messaging -involved in creating, restarting and terminating actors, it is also needed when -the pooled actors need to converse with other actors and receive replies in a -deterministic fashion. Since each actor knows its own external representation -as well as that of its parent, the routees decide where replies should be sent -when reacting to a message: +Routers, Routees and Senders +**************************** -.. includecode:: code/docs/actor/ActorDocSpec.scala#reply-with-sender +The router forwards messages onto its routees without changing the original sender. When a routee +replies to a routed message, the reply will be sent to the original sender, not to the router. + +When a router creates routees, they are created as the routers children. This gives each routee its +own identity in the actor system. + +By default, when a routee sends a message, it will :ref:`implicitly set itself as the sender +`. .. includecode:: code/docs/actor/ActorDocSpec.scala#reply-without-sender -It is apparent now why routing needs to be enabled in code rather than being -possible to “bolt on” later: whether or not an actor is routed means a change -to the actor hierarchy, changing the actor paths of all children of the router. -The routees especially do need to know that they are routed to in order to -choose the sender reference for any messages they dispatch as shown above. +However, it is often useful for routees to set the *router* as a sender. For example, you might want +to set the router as the sender if you want to hide the details of the routees behind the router. +The following code snippet shows how to set the parent router as sender. -Routers vs. Supervision +.. includecode:: code/docs/actor/ActorDocSpec.scala#reply-with-sender + +Note that different code would be needed if the routees were not children of the router, i.e. if +they were provided when the router was created. + +Routers and Supervision ^^^^^^^^^^^^^^^^^^^^^^^ -As explained in the previous section, routers create new actor instances as -children of the “head” router, who therefor also is their supervisor. The -supervisor strategy of this actor can be configured by means of the -:meth:`RouterConfig.supervisorStrategy` property, which is supported for all -built-in router types. It defaults to “always escalate”, which leads to the -application of the router’s parent’s supervision directive to all children of -the router uniformly (i.e. not only the one which failed). It should be -mentioned that the router overrides the default behavior of terminating all -children upon restart, which means that a restart—while re-creating them—does -not have an effect on the number of actors in the pool. +Routees can be created by a router or provided to the router when it is created. Any routees that +are created by a router will be created as the router's children. The router is therefore also the +children's supervisor. + +The supervision strategy of the router actor can be configured with the +:meth:`RouterConfig.supervisorStrategy` property. If no configuration is provided, routers default +to a strategy of “always escalate”. This means that errors are passed up to the router's supervisor +for handling. The router's supervisor will decide what to do about any errors. + +Note the router's supervisor will treat the error as an error with the router itself. Therefore a +directive to stop or restart will cause the router *itself* to stop or restart. The router, in +turn, will cause its children to stop and restart. + +It should be mentioned that the router's restart behavior has been overridden so that a restart, +while still re-creating the children, will still preserve the same number of actors in the pool. Setting the strategy is easily done: @@ -134,6 +145,8 @@ Another potentially useful approach is to give the router the same strategy as its parent, which effectively treats all actors in the pool as if they were direct children of their grand-parent instead. +.. _note-router-terminated-children-scala: + .. note:: If the child of a router terminates, the router will not automatically spawn @@ -152,6 +165,7 @@ and .. includecode:: code/docs/routing/RouterTypeExample.scala#fibonacciActor +.. _round-robin-router-scala: RoundRobinRouter **************** @@ -258,6 +272,12 @@ This is an example of how to define a broadcast router in configuration: .. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-broadcast +.. note:: + + Broadcast routers always broadcast *every* message to their routees. If you do not want to + broadcast every message, then you can use a non-broadcasting router and use + :ref:`broadcast-messages-scala` as needed. + ScatterGatherFirstCompletedRouter ********************************* @@ -320,19 +340,94 @@ This is an example of how to define a consistent-hashing router in configuration .. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-consistent-hashing +.. _router-special-messages-scala: + +Handling for Special Messages +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Most messages sent to routers will be forwarded according to the routers' usual routing rules. +However there are a few types of messages that have special behavior. + +.. _broadcast-messages-scala: Broadcast Messages -^^^^^^^^^^^^^^^^^^ +****************** -There is a special type of message that will be sent to all routees regardless of the router. -This message is called ``Broadcast`` and is used in the following manner: +A ``Broadcast`` message can be used to send a message to *all* of a router's routees. When a router +receives a ``Broadcast`` message, it will broadcast that message's *payload* to all routees, no +matter how that router would normally route its messages. -.. code-block:: scala +The example below shows how you would use a ``Broadcast`` message to send a very important message +to every routee of a router. - router ! Broadcast("Watch out for Davy Jones' locker") +.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#broadcastDavyJonesWarning -Only the actual message is forwarded to the routees, i.e. "Watch out for Davy Jones' locker" in the example above. -It is up to the routee implementation whether to handle the broadcast message or not. +In this example the router receives the ``Broadcast`` message, extracts its payload +(``"Watch out for Davy Jones' locker"``), and then sends the payload on to all of the router's +routees. It is up to each each routee actor to handle the received payload message. + +PoisonPill Messages +******************* + +A ``PoisonPill`` message has special handling for all actors, including for routers. When any actor +receives a ``PoisonPill`` message, that actor will be stopped. See the :ref:`poison-pill-scala` +documentation for details. + +.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#poisonPill + +For a router, which normally passes on messages to routees, it is important to realised that +``PoisonPill`` messages are processed by the router only. ``PoisonPill`` messages sent to a router +will *not* be sent on to routees. + +However, a ``PoisonPill`` message sent to a router may still affect its routees, because it will +stop the router and when the router stops it also stops its children. Stopping children is normal +actor behavior. The router will stop routees that it has created as children. Each child will +process its current message and then tstop. This may lead to some messages being unprocessed. +See the documentation on :ref:`stopping-actors-scala` for more information. + +If you wish to stop a router and its routees, but you would like the routees to first process all +the messages currently in their mailboxes, then you should not send a ``PoisonPill`` message to the +router. Instead you should wrap a ``PoisonPill`` message inside a broadcast message so that each +routee will the ``PoisonPill`` message directly. Note that this will stop all routees, even if the +routees aren't children of the router, i.e. even routees programmatically provided to the router. + +.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#broadcastPoisonPill + +With the code shown above, each routee will receive a ``PoisonPill`` message. Each routee will +continue to process its messages as normal, eventually processing the ``PoisonPill``. This will +cause the routee to stop. After all routees have stopped the router will itself be :ref:`stopped +automatically `. + +.. note:: + + Brendan W McAdams' excellent blog post `Distributing Akka Workloads - And Shutting Down Afterwards + `_ + discusses in more detail how ``PoisonPill`` messages can be used to shut down routers and routees. + +Kill Messages +************* + +``Kill`` messages are another type of message that has special handling. See +:ref:`killing-actors-scala` for general information about how actors handle ``Kill`` messages. + +When a ``Kill`` message is sent to a router the router processes the message internally, and does +*not* send it on to its routees. The router will throw an :class:`ActorKilledException` and fail. It +will then be either resumed, restarted or terminated, depending how it is supervised. + +Routees that are children of the router will also be suspended, and will be affected by the +supervision directive that is applied to the router. Routees that are not the routers children, i.e. +those that were created externally to the router, will not be affected. + +.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#kill + +As with the ``PoisonPill`` message, there is a distinction between killing a router, which +indirectly kills its children (who happen to be routees), and killing routees directly (some of whom +may not be children.) To kill routees directly the router should be sent a ``Kill`` message wrapped +in a ``Broadcast`` message. + +.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#broadcastKill + +.. _resizable-routers-scala: Dynamically Resizable Routers ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -366,12 +461,43 @@ will be used instead of any programmatically sent parameters.* this, configure the pool to use a balancing dispatcher, see `Configuring Dispatchers`_ for more information. +.. _router-design-scala: + +How Routing is Designed within Akka +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On the surface routers look like normal actors, but they are actually implemented differently. +Routers are designed to be extremely efficient at receiving messages and passing them quickly on to +routees. + +A normal actor can be used for routing messages, but an actor's single-threaded processing can +become a bottleneck. Routers can achieve much higher throughput with an optimization to the usual +message-processing pipeline that allows concurrent routing. This is achieved by embedding routers' +routing logic directly in their :class:`ActorRef` rather than in the router actor. Messages sent to +a router's :class:`ActorRef` can be immediately routed to the routee, bypassing the single-threaded +router actor entirely. + +The cost to this is, of course, that the internals of routing code are more complicated than if +routers were implemented with normal actors. Fortunately all of this complexity is invisible to +consumers of the routing API. However, it is something to be aware of when implementing your own +routers. + +.. _custom-router-scala: + Custom Router ^^^^^^^^^^^^^ -You can also create your own router should you not find any of the ones provided by Akka sufficient for your needs. +You can create your own router should you not find any of the ones provided by Akka sufficient for your needs. In order to roll your own router you have to fulfill certain criteria which are explained in this section. +Before creating your own router you should consider whether a normal actor with router-like +behavior might do the job just as well as a full-blown router. As explained +:ref:`above `, the primary benefit of routers over normal actors is their +higher performance. But they are somewhat more complicated to write than normal actors. Therefore if +lower maximum throughput is acceptable in your application you may wish to stick with traditional +actors. This section, however, assumes that you wish to get maximum performance and so demonstrates +how you can create your own router. + The router created in this example is a simple vote counter. It will route the votes to specific vote counter actors. In this case we only have two parties the Republicans and the Democrats. We would like a router that forwards all democrat related messages to the Democrat actor and all republican related messages to the Republican actor. diff --git a/akka-testkit/src/main/java/akka/testkit/JavaTestKit.java b/akka-testkit/src/main/java/akka/testkit/JavaTestKit.java index a7ba556e43..18e733bd38 100644 --- a/akka-testkit/src/main/java/akka/testkit/JavaTestKit.java +++ b/akka-testkit/src/main/java/akka/testkit/JavaTestKit.java @@ -263,6 +263,10 @@ public class JavaTestKit { return (T[]) results; } } + + public Object[] receiveN(int n, FiniteDuration max) { + return (Object[]) p.receiveN(n, max).toArray(Util.classTag(Object.class)); + } public abstract class EventFilter { abstract protected T run();