From accdd63e7d19c70685209de05c2df9a4e176dd74 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 8 Apr 2013 13:19:44 +0200 Subject: [PATCH] Don't stop dynamic router when all routees terminated, see #2734 --- .../test/scala/akka/routing/RoutingSpec.scala | 20 +++++++++++++++++++ .../src/main/scala/akka/routing/Routing.scala | 9 ++++++++- .../cluster/routing/ClusterRouterConfig.scala | 2 ++ akka-docs/rst/java/routing.rst | 8 +++++--- akka-docs/rst/scala/routing.rst | 6 ++++-- 5 files changed, 39 insertions(+), 6 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala index f1150688f2..cd134623ea 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala @@ -100,6 +100,26 @@ class RoutingSpec extends AkkaSpec(RoutingSpec.config) with DefaultTimeout with } } + "not terminate when resizer is used" in { + val latch = TestLatch(1) + val resizer = new Resizer { + def isTimeForResize(messageCounter: Long): Boolean = messageCounter == 0 + def resize(routeeProvider: RouteeProvider): Unit = { + routeeProvider.createRoutees(nrOfInstances = 2) + latch.countDown() + } + } + val router = system.actorOf(Props[TestActor].withRouter(RoundRobinRouter(resizer = Some(resizer)))) + watch(router) + Await.ready(latch, remaining) + router ! CurrentRoutees + val routees = expectMsgType[RouterRoutees].routees + routees.size must be(2) + routees foreach system.stop + // expect no Terminated + expectNoMsg(2.seconds) + } + "be able to send their routees" in { case class TestRun(id: String, names: immutable.Iterable[String], actors: Int) val actor = system.actorOf(Props(new Actor { diff --git a/akka-actor/src/main/scala/akka/routing/Routing.scala b/akka-actor/src/main/scala/akka/routing/Routing.scala index 43aac74b10..2ae7c91d7c 100644 --- a/akka-actor/src/main/scala/akka/routing/Routing.scala +++ b/akka-actor/src/main/scala/akka/routing/Routing.scala @@ -215,6 +215,12 @@ trait RouterConfig { */ def verifyConfig(): Unit = () + /* + * Specify that this router should stop itself when all routees have terminated (been removed). + * By Default it is `true`, unless a `resizer` is used. + */ + def stopRouterWhenAllRouteesRemoved: Boolean = resizer.isEmpty + } /** @@ -373,7 +379,8 @@ trait Router extends Actor { case Terminated(child) ⇒ ref.removeRoutees(child :: Nil) - if (ref.routees.isEmpty) context.stop(self) + if (ref.routees.isEmpty && ref.routerConfig.stopRouterWhenAllRouteesRemoved) + context.stop(self) }: Receive) orElse routerReceive diff --git a/akka-cluster/src/main/scala/akka/cluster/routing/ClusterRouterConfig.scala b/akka-cluster/src/main/scala/akka/cluster/routing/ClusterRouterConfig.scala index 12ac7b7206..e355204de9 100644 --- a/akka-cluster/src/main/scala/akka/cluster/routing/ClusterRouterConfig.scala +++ b/akka-cluster/src/main/scala/akka/cluster/routing/ClusterRouterConfig.scala @@ -60,6 +60,8 @@ final case class ClusterRouterConfig(local: RouterConfig, settings: ClusterRoute override def resizer: Option[Resizer] = local.resizer + override def stopRouterWhenAllRouteesRemoved: Boolean = false + override def withFallback(other: RouterConfig): RouterConfig = other match { case ClusterRouterConfig(_: RemoteRouterConfig, _) ⇒ throw new IllegalStateException( "ClusterRouterConfig is not allowed to wrap a RemoteRouterConfig") diff --git a/akka-docs/rst/java/routing.rst b/akka-docs/rst/java/routing.rst index 16c72bd876..d14f57a706 100644 --- a/akka-docs/rst/java/routing.rst +++ b/akka-docs/rst/java/routing.rst @@ -149,7 +149,8 @@ direct children of their grand-parent instead. If the child of a router terminates, the router will not automatically spawn a new child. In the event that all children of a router have terminated the - router will terminate itself. + router will terminate itself, unless it is a dynamic router, e.g. using + a resizer. Router usage ^^^^^^^^^^^^ @@ -378,7 +379,7 @@ 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 +For a router, which normally passes on messages to routees, it is important to realise that ``PoisonPill`` messages are processed by the router only. ``PoisonPill`` messages sent to a router will *not* be sent on to routees. @@ -399,7 +400,8 @@ routees aren't children of the router, i.e. even routees programmatically provid 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 `. +automatically ` unless it is a dynamic router, e.g. using +a resizer. .. note:: diff --git a/akka-docs/rst/scala/routing.rst b/akka-docs/rst/scala/routing.rst index 4b6e8c5011..8f224c460e 100644 --- a/akka-docs/rst/scala/routing.rst +++ b/akka-docs/rst/scala/routing.rst @@ -151,7 +151,8 @@ direct children of their grand-parent instead. If the child of a router terminates, the router will not automatically spawn a new child. In the event that all children of a router have terminated the - router will terminate itself. + router will terminate itself unless it is a dynamic router, e.g. using + a resizer. Router usage ^^^^^^^^^^^^ @@ -396,7 +397,8 @@ routees aren't children of the router, i.e. even routees programmatically provid 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 `. +automatically ` unless it is a dynamic router, e.g. using +a resizer. .. note::