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 a3481e1903..e38ea1c3d4 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala @@ -5,33 +5,20 @@ package akka.actor import akka.testkit.AkkaSpec -import akka.util.duration._ -import DeploymentConfig._ -import akka.remote.RemoteAddress import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions +import akka.routing._ object DeployerSpec { val deployerConf = ConfigFactory.parseString(""" akka.actor.deployment { /user/service1 { } - /user/service2 { - router = round-robin - nr-of-instances = 3 - remote { - nodes = ["wallace:2552", "gromit:2552"] - } - } /user/service3 { create-as { class = "akka.actor.DeployerSpec$RecipeActor" } } - /user/service-auto { - router = round-robin - nr-of-instances = auto - } /user/service-direct { router = direct } @@ -49,31 +36,6 @@ object DeployerSpec { /user/service-scatter-gather { router = scatter-gather } - /user/service-least-cpu { - router = least-cpu - } - /user/service-least-ram { - router = least-ram - } - /user/service-least-messages { - router = least-messages - } - /user/service-custom { - router = org.my.Custom - } - /user/service-cluster1 { - cluster { - preferred-nodes = ["node:wallace", "node:gromit"] - } - } - /user/service-cluster2 { - cluster { - preferred-nodes = ["node:wallace", "node:gromit"] - replication { - strategy = write-behind - } - } - } } """, ConfigParseOptions.defaults) @@ -90,69 +52,40 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { "be able to parse 'akka.actor.deployment._' with all default values" in { val service = "/user/service1" - val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) + val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookup(service) deployment must be('defined) deployment must be(Some( Deploy( service, + deployment.get.config, None, - Direct, - NrOfInstances(1), + NoRouter, LocalScope))) } "use None deployment for undefined service" in { val service = "/user/undefined" - val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) + val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookup(service) deployment must be(None) } - "be able to parse 'akka.actor.deployment._' with specified remote nodes" in { - val service = "/user/service2" - val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) - deployment must be('defined) - - deployment must be(Some( - Deploy( - service, - None, - RoundRobin, - NrOfInstances(3), - RemoteScope(Seq( - RemoteAddress(system.name, "wallace", 2552), RemoteAddress(system.name, "gromit", 2552)))))) - } - "be able to parse 'akka.actor.deployment._' with recipe" in { val service = "/user/service3" - val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) + val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookup(service) deployment must be('defined) deployment must be(Some( Deploy( service, + deployment.get.config, Some(ActorRecipe(classOf[DeployerSpec.RecipeActor])), - Direct, - NrOfInstances(1), - LocalScope))) - } - - "be able to parse 'akka.actor.deployment._' with number-of-instances=auto" in { - val service = "/user/service-auto" - val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) - deployment must be('defined) - - deployment must be(Some( - Deploy( - service, - None, - RoundRobin, - AutoNrOfInstances, + NoRouter, LocalScope))) } "detect invalid number-of-instances" in { - intercept[akka.config.ConfigurationException] { + intercept[com.typesafe.config.ConfigException.WrongType] { val invalidDeployerConf = ConfigFactory.parseString(""" akka.actor.deployment { /user/service-invalid-number-of-instances { @@ -167,81 +100,38 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { } "be able to parse 'akka.actor.deployment._' with direct router" in { - assertRouting(Direct, "/user/service-direct") + assertRouting(NoRouter, "/user/service-direct") } "ignore nr-of-instances with direct router" in { - assertRouting(Direct, "/user/service-direct2") + assertRouting(NoRouter, "/user/service-direct2") } "be able to parse 'akka.actor.deployment._' with round-robin router" in { - assertRouting(RoundRobin, "/user/service-round-robin") + assertRouting(RoundRobinRouter(1), "/user/service-round-robin") } "be able to parse 'akka.actor.deployment._' with random router" in { - assertRouting(Random, "/user/service-random") + assertRouting(RandomRouter(1), "/user/service-random") } "be able to parse 'akka.actor.deployment._' with scatter-gather router" in { - assertRouting(ScatterGather, "/user/service-scatter-gather") + assertRouting(ScatterGatherFirstCompletedRouter(1), "/user/service-scatter-gather") } - "be able to parse 'akka.actor.deployment._' with least-cpu router" in { - assertRouting(LeastCPU, "/user/service-least-cpu") - } - - "be able to parse 'akka.actor.deployment._' with least-ram router" in { - assertRouting(LeastRAM, "/user/service-least-ram") - } - - "be able to parse 'akka.actor.deployment._' with least-messages router" in { - assertRouting(LeastMessages, "/user/service-least-messages") - } - "be able to parse 'akka.actor.deployment._' with custom router" in { - assertRouting(CustomRouter("org.my.Custom"), "/user/service-custom") - } - - def assertRouting(expected: Routing, service: String) { - val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookupDeployment(service) + def assertRouting(expected: RouterConfig, service: String) { + val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookup(service) deployment must be('defined) deployment must be(Some( Deploy( service, + deployment.get.config, None, expected, - NrOfInstances(1), LocalScope))) } - "be able to parse 'akka.actor.deployment._' with specified cluster nodes" in { - 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) - - deployment.get.scope match { - case deploymentConfig.ClusterScope(remoteNodes, replication) ⇒ - remoteNodes must be(Seq(Node("wallace"), Node("gromit"))) - replication must be(Transient) - case other ⇒ fail("Unexpected: " + other) - } - } - - "be able to parse 'akka.actor.deployment._' with specified cluster replication" in { - 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) - - deployment.get.scope match { - case deploymentConfig.ClusterScope(remoteNodes, Replication(storage, strategy)) ⇒ - storage must be(TransactionLog) - strategy must be(WriteBehind) - case other ⇒ fail("Unexpected: " + other) - } - } - } } diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/BalancingDispatcherSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/BalancingDispatcherSpec.scala index 5ad7794f77..6ebc81409e 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/BalancingDispatcherSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/BalancingDispatcherSpec.scala @@ -71,9 +71,9 @@ class BalancingDispatcherSpec extends AkkaSpec { finishedCounter.await(5, TimeUnit.SECONDS) fast.underlying.mailbox.asInstanceOf[Mailbox].hasMessages must be(false) slow.underlying.mailbox.asInstanceOf[Mailbox].hasMessages must be(false) - fast.underlyingActorInstance.asInstanceOf[DelayableActor].invocationCount must be > sentToFast - fast.underlyingActorInstance.asInstanceOf[DelayableActor].invocationCount must be > - (slow.underlyingActorInstance.asInstanceOf[DelayableActor].invocationCount) + fast.underlying.actor.asInstanceOf[DelayableActor].invocationCount must be > sentToFast + fast.underlying.actor.asInstanceOf[DelayableActor].invocationCount must be > + (slow.underlying.actor.asInstanceOf[DelayableActor].invocationCount) slow.stop() fast.stop() } diff --git a/akka-actor-tests/src/test/scala/akka/routing/ConfiguredLocalRoutingSpec.scala b/akka-actor-tests/src/test/scala/akka/routing/ConfiguredLocalRoutingSpec.scala index a62f6712f0..0b6cdae645 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/ConfiguredLocalRoutingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/ConfiguredLocalRoutingSpec.scala @@ -4,29 +4,31 @@ import akka.actor._ import akka.routing._ import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.{ CountDownLatch, TimeUnit } -import akka.testkit.AkkaSpec -import akka.actor.DeploymentConfig._ -import akka.routing.Routing.Broadcast -import akka.testkit.DefaultTimeout +import akka.testkit._ +import akka.util.duration._ @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) -class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout { +class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout with ImplicitSender { val deployer = system.asInstanceOf[ActorSystemImpl].provider.deployer + "RouterConfig" must { + + "be overridable in config" in { + deployer.deploy(Deploy("/config", null, None, RandomRouter(4), LocalScope)) + val actor = system.actorOf(Props(new Actor { + def receive = { + case "get" ⇒ sender ! context.props + } + }).withRouter(RoundRobinRouter(12)), "config") + actor.asInstanceOf[LocalActorRef].underlying.props.routerConfig must be === RandomRouter(4) + } + + } + "round robin router" must { "be able to shut down its instance" in { - val path = system / "round-robin-0" - - deployer.deploy( - Deploy( - path.toString, - None, - RoundRobin, - NrOfInstances(5), - LocalScope)) - val helloLatch = new CountDownLatch(5) val stopLatch = new CountDownLatch(5) @@ -38,7 +40,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout { override def postStop() { stopLatch.countDown() } - }), path.name) + }).withRouter(RoundRobinRouter(5)), "round-robin-shutdown") actor ! "hello" actor ! "hello" @@ -52,16 +54,6 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout { } "deliver messages in a round robin fashion" in { - val path = system / "round-robin-1" - - deployer.deploy( - Deploy( - path.toString, - None, - RoundRobin, - NrOfInstances(10), - LocalScope)) - val connectionCount = 10 val iterationCount = 10 val doneLatch = new CountDownLatch(connectionCount) @@ -69,7 +61,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout { val counter = new AtomicInteger var replies = Map.empty[Int, Int] for (i ← 0 until connectionCount) { - replies = replies + (i -> 0) + replies += i -> 0 } val actor = system.actorOf(Props(new Actor { @@ -78,7 +70,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout { case "hit" ⇒ sender ! id case "end" ⇒ doneLatch.countDown() } - }), path.name) + }).withRouter(RoundRobinRouter(connectionCount)), "round-robin") for (i ← 0 until iterationCount) { for (k ← 0 until connectionCount) { @@ -92,20 +84,10 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout { actor ! Broadcast("end") doneLatch.await(5, TimeUnit.SECONDS) must be(true) - replies.values foreach { _ must be(10) } + replies.values foreach { _ must be(iterationCount) } } "deliver a broadcast message using the !" in { - val path = system / "round-robin-2" - - deployer.deploy( - Deploy( - path.toString, - None, - RoundRobin, - NrOfInstances(5), - LocalScope)) - val helloLatch = new CountDownLatch(5) val stopLatch = new CountDownLatch(5) @@ -117,7 +99,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout { override def postStop() { stopLatch.countDown() } - }), path.name) + }).withRouter(RoundRobinRouter(5)), "round-robin-broadcast") actor ! Broadcast("hello") helloLatch.await(5, TimeUnit.SECONDS) must be(true) @@ -130,27 +112,17 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout { "random router" must { "be able to shut down its instance" in { - val path = system / "random-0" - - deployer.deploy( - Deploy( - path.toString, - None, - Random, - NrOfInstances(7), - LocalScope)) - val stopLatch = new CountDownLatch(7) val actor = system.actorOf(Props(new Actor { def receive = { - case "hello" ⇒ {} + case "hello" ⇒ sender ! "world" } override def postStop() { stopLatch.countDown() } - }), path.name) + }).withRouter(RandomRouter(7)), "random-shutdown") actor ! "hello" actor ! "hello" @@ -158,21 +130,15 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout { actor ! "hello" actor ! "hello" + within(2 seconds) { + for (i ← 1 to 5) expectMsg("world") + } + actor.stop() stopLatch.await(5, TimeUnit.SECONDS) must be(true) } "deliver messages in a random fashion" in { - val path = system / "random-1" - - deployer.deploy( - Deploy( - path.toString, - None, - Random, - NrOfInstances(10), - LocalScope)) - val connectionCount = 10 val iterationCount = 10 val doneLatch = new CountDownLatch(connectionCount) @@ -189,7 +155,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout { case "hit" ⇒ sender ! id case "end" ⇒ doneLatch.countDown() } - }), path.name) + }).withRouter(RandomRouter(connectionCount)), "random") for (i ← 0 until iterationCount) { for (k ← 0 until connectionCount) { @@ -204,19 +170,10 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout { doneLatch.await(5, TimeUnit.SECONDS) must be(true) replies.values foreach { _ must be > (0) } + replies.values.sum must be === iterationCount * connectionCount } "deliver a broadcast message using the !" in { - val path = system / "random-2" - - deployer.deploy( - Deploy( - path.toString, - None, - Random, - NrOfInstances(6), - LocalScope)) - val helloLatch = new CountDownLatch(6) val stopLatch = new CountDownLatch(6) @@ -228,7 +185,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec with DefaultTimeout { override def postStop() { stopLatch.countDown() } - }), path.name) + }).withRouter(RandomRouter(6)), "random-broadcast") actor ! Broadcast("hello") helloLatch.await(5, TimeUnit.SECONDS) must be(true) 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 ac940c8c2c..e6e0f1c898 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala @@ -1,53 +1,84 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ package akka.routing -import akka.routing._ -import akka.config.ConfigurationException import java.util.concurrent.atomic.AtomicInteger import akka.actor._ import collection.mutable.LinkedList -import akka.routing.Routing.Broadcast import java.util.concurrent.{ CountDownLatch, TimeUnit } import akka.testkit._ +import akka.util.duration._ object RoutingSpec { - class TestActor extends Actor with Serializable { + class TestActor extends Actor { def receive = { case _ ⇒ println("Hello") } } + + class Echo extends Actor { + def receive = { + case _ ⇒ sender ! self + } + } + } @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) -class RoutingSpec extends AkkaSpec with DefaultTimeout { +class RoutingSpec extends AkkaSpec with DefaultTimeout with ImplicitSender { val impl = system.asInstanceOf[ActorSystemImpl] import akka.routing.RoutingSpec._ - "direct router" must { - "be started when constructed" in { - val actor1 = system.actorOf(Props[TestActor]) + "routers in general" must { - val props = RoutedProps(routerFactory = () ⇒ new DirectRouter, connectionManager = new LocalConnectionManager(List(actor1))) - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - actor.isTerminated must be(false) + "evict terminated routees" in { + val router = system.actorOf(Props[Echo].withRouter(RoundRobinRouter(2))) + router ! "" + router ! "" + val c1, c2 = expectMsgType[ActorRef] + watch(router) + watch(c2) + c2.stop() + expectMsg(Terminated(c2)) + // it might take a while until the Router has actually processed the Terminated message + awaitCond { + router ! "" + router ! "" + val res = receiveWhile(100 millis, messages = 2) { + case x: ActorRef ⇒ x + } + res == Seq(c1, c1) + } + c1.stop() + expectMsg(Terminated(router)) + } + + } + + "no router" must { + "be started when constructed" in { + val routedActor = system.actorOf(Props(new TestActor).withRouter(NoRouter)) + routedActor.isTerminated must be(false) } "send message to connection" in { val doneLatch = new CountDownLatch(1) val counter = new AtomicInteger(0) - val connection1 = system.actorOf(Props(new Actor { + + class Actor1 extends Actor { def receive = { case "end" ⇒ doneLatch.countDown() case _ ⇒ counter.incrementAndGet } - })) + } - val props = RoutedProps(routerFactory = () ⇒ new DirectRouter, connectionManager = new LocalConnectionManager(List(connection1))) - val routedActor = new RoutedActorRef(system, props, impl.guardian, "foo") + val routedActor = system.actorOf(Props(new Actor1).withRouter(NoRouter)) routedActor ! "hello" routedActor ! "end" @@ -55,38 +86,12 @@ class RoutingSpec extends AkkaSpec with DefaultTimeout { counter.get must be(1) } - - "deliver a broadcast message" in { - val doneLatch = new CountDownLatch(1) - - val counter1 = new AtomicInteger - val connection1 = system.actorOf(Props(new Actor { - def receive = { - case "end" ⇒ doneLatch.countDown() - case msg: Int ⇒ counter1.addAndGet(msg) - } - })) - - val props = RoutedProps(routerFactory = () ⇒ new DirectRouter, connectionManager = new LocalConnectionManager(List(connection1))) - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - - actor ! Broadcast(1) - actor ! "end" - - doneLatch.await(5, TimeUnit.SECONDS) must be(true) - - counter1.get must be(1) - } } "round robin router" must { - "be started when constructed" in { - val actor1 = system.actorOf(Props[TestActor]) - - val props = RoutedProps(routerFactory = () ⇒ new RoundRobinRouter, connectionManager = new LocalConnectionManager(List(actor1))) - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - actor.isTerminated must be(false) + val routedActor = system.actorOf(Props(new TestActor).withRouter(RoundRobinRouter(nrOfInstances = 1))) + routedActor.isTerminated must be(false) } //In this test a bunch of actors are created and each actor has its own counter. @@ -99,32 +104,30 @@ class RoutingSpec extends AkkaSpec with DefaultTimeout { val doneLatch = new CountDownLatch(connectionCount) //lets create some connections. - var connections = new LinkedList[ActorRef] + var actors = new LinkedList[ActorRef] var counters = new LinkedList[AtomicInteger] for (i ← 0 until connectionCount) { counters = counters :+ new AtomicInteger() - val connection = system.actorOf(Props(new Actor { + val actor = system.actorOf(Props(new Actor { def receive = { case "end" ⇒ doneLatch.countDown() case msg: Int ⇒ counters.get(i).get.addAndGet(msg) } })) - connections = connections :+ connection + actors = actors :+ actor } - //create the routed actor. - val props = RoutedProps(routerFactory = () ⇒ new RoundRobinRouter, connectionManager = new LocalConnectionManager(connections)) - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") + val routedActor = system.actorOf(Props(new TestActor).withRouter(RoundRobinRouter(targets = actors))) //send messages to the actor. for (i ← 0 until iterationCount) { for (k ← 0 until connectionCount) { - actor ! (k + 1) + routedActor ! (k + 1) } } - actor ! Broadcast("end") + routedActor ! Broadcast("end") //now wait some and do validations. doneLatch.await(5, TimeUnit.SECONDS) must be(true) @@ -138,7 +141,7 @@ class RoutingSpec extends AkkaSpec with DefaultTimeout { val doneLatch = new CountDownLatch(2) val counter1 = new AtomicInteger - val connection1 = system.actorOf(Props(new Actor { + val actor1 = system.actorOf(Props(new Actor { def receive = { case "end" ⇒ doneLatch.countDown() case msg: Int ⇒ counter1.addAndGet(msg) @@ -146,63 +149,37 @@ class RoutingSpec extends AkkaSpec with DefaultTimeout { })) val counter2 = new AtomicInteger - val connection2 = system.actorOf(Props(new Actor { + val actor2 = system.actorOf(Props(new Actor { def receive = { case "end" ⇒ doneLatch.countDown() case msg: Int ⇒ counter2.addAndGet(msg) } })) - val props = RoutedProps(routerFactory = () ⇒ new RoundRobinRouter, connectionManager = new LocalConnectionManager(List(connection1, connection2))) - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") + val routedActor = system.actorOf(Props(new TestActor).withRouter(RoundRobinRouter(targets = List(actor1, actor2)))) - actor ! Broadcast(1) - actor ! Broadcast("end") + routedActor ! Broadcast(1) + routedActor ! Broadcast("end") doneLatch.await(5, TimeUnit.SECONDS) must be(true) counter1.get must be(1) counter2.get must be(1) } - - "fail to deliver a broadcast message using the ?" in { - val doneLatch = new CountDownLatch(1) - - val counter1 = new AtomicInteger - val connection1 = system.actorOf(Props(new Actor { - def receive = { - case "end" ⇒ doneLatch.countDown() - case _ ⇒ counter1.incrementAndGet() - } - })) - - val props = RoutedProps(routerFactory = () ⇒ new RoundRobinRouter, connectionManager = new LocalConnectionManager(List(connection1))) - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - - intercept[RoutingException] { actor ? Broadcast(1) } - - actor ! "end" - doneLatch.await(5, TimeUnit.SECONDS) must be(true) - counter1.get must be(0) - } } "random router" must { "be started when constructed" in { - - val actor1 = system.actorOf(Props[TestActor]) - - val props = RoutedProps(routerFactory = () ⇒ new RandomRouter, connectionManager = new LocalConnectionManager(List(actor1))) - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - actor.isTerminated must be(false) + val routedActor = system.actorOf(Props(new TestActor).withRouter(RandomRouter(nrOfInstances = 1))) + routedActor.isTerminated must be(false) } "deliver a broadcast message" in { val doneLatch = new CountDownLatch(2) val counter1 = new AtomicInteger - val connection1 = system.actorOf(Props(new Actor { + val actor1 = system.actorOf(Props(new Actor { def receive = { case "end" ⇒ doneLatch.countDown() case msg: Int ⇒ counter1.addAndGet(msg) @@ -210,214 +187,36 @@ class RoutingSpec extends AkkaSpec with DefaultTimeout { })) val counter2 = new AtomicInteger - val connection2 = system.actorOf(Props(new Actor { + val actor2 = system.actorOf(Props(new Actor { def receive = { case "end" ⇒ doneLatch.countDown() case msg: Int ⇒ counter2.addAndGet(msg) } })) - val props = RoutedProps(routerFactory = () ⇒ new RandomRouter, connectionManager = new LocalConnectionManager(List(connection1, connection2))) - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") + val routedActor = system.actorOf(Props(new TestActor).withRouter(RandomRouter(targets = List(actor1, actor2)))) - actor ! Broadcast(1) - actor ! Broadcast("end") + routedActor ! Broadcast(1) + routedActor ! Broadcast("end") doneLatch.await(5, TimeUnit.SECONDS) must be(true) counter1.get must be(1) counter2.get must be(1) } - - "fail to deliver a broadcast message using the ?" in { - val doneLatch = new CountDownLatch(1) - - val counter1 = new AtomicInteger - val connection1 = system.actorOf(Props(new Actor { - def receive = { - case "end" ⇒ doneLatch.countDown() - case _ ⇒ counter1.incrementAndGet() - } - })) - - val props = RoutedProps(routerFactory = () ⇒ new RandomRouter, connectionManager = new LocalConnectionManager(List(connection1))) - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - - try { - actor ? Broadcast(1) - fail() - } catch { - case e: RoutingException ⇒ - } - - actor ! "end" - doneLatch.await(5, TimeUnit.SECONDS) must be(true) - counter1.get must be(0) - } - } - - "Scatter-gather router" must { - - "return response, even if one of the connections has stopped" in { - - val shutdownLatch = new TestLatch(1) - - val props = RoutedProps(routerFactory = () ⇒ new ScatterGatherFirstCompletedRouter, connectionManager = new LocalConnectionManager(List(newActor(0, Some(shutdownLatch)), newActor(1, Some(shutdownLatch))))) - - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - - actor ! Broadcast(Stop(Some(0))) - - shutdownLatch.await - - (actor ? Broadcast(0)).get.asInstanceOf[Int] must be(1) - } - - "throw an exception, if all the connections have stopped" in { - - val shutdownLatch = new TestLatch(2) - - val props = RoutedProps(routerFactory = () ⇒ new ScatterGatherFirstCompletedRouter, connectionManager = new LocalConnectionManager(List(newActor(0, Some(shutdownLatch)), newActor(1, Some(shutdownLatch))))) - - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - - actor ! Broadcast(Stop()) - - shutdownLatch.await - - (intercept[RoutingException] { - actor ? Broadcast(0) - }) must not be (null) - - } - - "return the first response from connections, when all of them replied" in { - - val props = RoutedProps(routerFactory = () ⇒ new ScatterGatherFirstCompletedRouter, connectionManager = new LocalConnectionManager(List(newActor(0), newActor(1)))) - - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - - (actor ? Broadcast("Hi!")).get.asInstanceOf[Int] must be(0) - - } - - "return the first response from connections, when some of them failed to reply" in { - val props = RoutedProps(routerFactory = () ⇒ new ScatterGatherFirstCompletedRouter, connectionManager = new LocalConnectionManager(List(newActor(0), newActor(1)))) - - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - - (actor ? Broadcast(0)).get.asInstanceOf[Int] must be(1) - } - - "be started when constructed" in { - val props = RoutedProps(routerFactory = () ⇒ new ScatterGatherFirstCompletedRouter, connectionManager = new LocalConnectionManager(List(newActor(0)))) - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - - actor.isTerminated must be(false) - } - - "deliver one-way messages in a round robin fashion" in { - val connectionCount = 10 - val iterationCount = 10 - val doneLatch = new TestLatch(connectionCount) - - var connections = new LinkedList[ActorRef] - var counters = new LinkedList[AtomicInteger] - for (i ← 0 until connectionCount) { - counters = counters :+ new AtomicInteger() - - val connection = system.actorOf(Props(new Actor { - def receive = { - case "end" ⇒ doneLatch.countDown() - case msg: Int ⇒ counters.get(i).get.addAndGet(msg) - } - })) - connections = connections :+ connection - } - - val props = RoutedProps(routerFactory = () ⇒ new ScatterGatherFirstCompletedRouter, connectionManager = new LocalConnectionManager(connections)) - - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - - for (i ← 0 until iterationCount) { - for (k ← 0 until connectionCount) { - actor ! (k + 1) - } - } - - actor ! Broadcast("end") - - doneLatch.await - - for (i ← 0 until connectionCount) { - val counter = counters.get(i).get - counter.get must be((iterationCount * (i + 1))) - } - } - - "deliver a broadcast message using the !" in { - val doneLatch = new TestLatch(2) - - val counter1 = new AtomicInteger - val connection1 = system.actorOf(Props(new Actor { - def receive = { - case "end" ⇒ doneLatch.countDown() - case msg: Int ⇒ counter1.addAndGet(msg) - } - })) - - val counter2 = new AtomicInteger - val connection2 = system.actorOf(Props(new Actor { - def receive = { - case "end" ⇒ doneLatch.countDown() - case msg: Int ⇒ counter2.addAndGet(msg) - } - })) - - val props = RoutedProps(routerFactory = () ⇒ new ScatterGatherFirstCompletedRouter, connectionManager = new LocalConnectionManager(List(connection1, connection2))) - - val actor = new RoutedActorRef(system, props, impl.guardian, "foo") - - actor ! Broadcast(1) - actor ! Broadcast("end") - - doneLatch.await - - counter1.get must be(1) - counter2.get must be(1) - } - - case class Stop(id: Option[Int] = None) - - def newActor(id: Int, shudownLatch: Option[TestLatch] = None) = system.actorOf(Props(new Actor { - def receive = { - case Stop(None) ⇒ self.stop() - case Stop(Some(_id)) if (_id == id) ⇒ self.stop() - case _id: Int if (_id == id) ⇒ - case _ ⇒ Thread sleep 100 * id; sender.tell(id) - } - - override def postStop = { - shudownLatch foreach (_.countDown()) - } - })) } "broadcast router" must { - "be started when constructed" in { - val actor1 = system.actorOf(Props[TestActor]) - - val props = RoutedProps(routerFactory = () ⇒ new BroadcastRouter, connectionManager = new LocalConnectionManager(List(actor1))) - val actor = new RoutedActorRef(system, props, system.asInstanceOf[ActorSystemImpl].guardian, "foo") - actor.isTerminated must be(false) + val routedActor = system.actorOf(Props(new TestActor).withRouter(BroadcastRouter(nrOfInstances = 1))) + routedActor.isTerminated must be(false) } "broadcast message using !" in { val doneLatch = new CountDownLatch(2) val counter1 = new AtomicInteger - val connection1 = system.actorOf(Props(new Actor { + val actor1 = system.actorOf(Props(new Actor { def receive = { case "end" ⇒ doneLatch.countDown() case msg: Int ⇒ counter1.addAndGet(msg) @@ -425,18 +224,16 @@ class RoutingSpec extends AkkaSpec with DefaultTimeout { })) val counter2 = new AtomicInteger - val connection2 = system.actorOf(Props(new Actor { + val actor2 = system.actorOf(Props(new Actor { def receive = { case "end" ⇒ doneLatch.countDown() case msg: Int ⇒ counter2.addAndGet(msg) } })) - val props = RoutedProps(routerFactory = () ⇒ new BroadcastRouter, connectionManager = new LocalConnectionManager(List(connection1, connection2))) - val actor = new RoutedActorRef(system, props, system.asInstanceOf[ActorSystemImpl].guardian, "foo") - - actor ! 1 - actor ! "end" + val routedActor = system.actorOf(Props(new TestActor).withRouter(BroadcastRouter(targets = List(actor1, actor2)))) + routedActor ! 1 + routedActor ! "end" doneLatch.await(5, TimeUnit.SECONDS) must be(true) @@ -448,7 +245,7 @@ class RoutingSpec extends AkkaSpec with DefaultTimeout { val doneLatch = new CountDownLatch(2) val counter1 = new AtomicInteger - val connection1 = system.actorOf(Props(new Actor { + val actor1 = system.actorOf(Props(new Actor { def receive = { case "end" ⇒ doneLatch.countDown() case msg: Int ⇒ @@ -458,18 +255,16 @@ class RoutingSpec extends AkkaSpec with DefaultTimeout { })) val counter2 = new AtomicInteger - val connection2 = system.actorOf(Props(new Actor { + val actor2 = system.actorOf(Props(new Actor { def receive = { case "end" ⇒ doneLatch.countDown() case msg: Int ⇒ counter2.addAndGet(msg) } })) - val props = RoutedProps(routerFactory = () ⇒ new BroadcastRouter, connectionManager = new LocalConnectionManager(List(connection1, connection2))) - val actor = new RoutedActorRef(system, props, system.asInstanceOf[ActorSystemImpl].guardian, "foo") - - actor ? 1 - actor ! "end" + val routedActor = system.actorOf(Props(new TestActor).withRouter(BroadcastRouter(targets = List(actor1, actor2)))) + routedActor ? 1 + routedActor ! "end" doneLatch.await(5, TimeUnit.SECONDS) must be(true) @@ -477,4 +272,70 @@ class RoutingSpec extends AkkaSpec with DefaultTimeout { counter2.get must be(1) } } + + "Scatter-gather router" must { + + "be started when constructed" in { + val routedActor = system.actorOf(Props(new TestActor).withRouter(ScatterGatherFirstCompletedRouter(targets = List(newActor(0))))) + routedActor.isTerminated must be(false) + } + + "deliver a broadcast message using the !" in { + val doneLatch = new TestLatch(2) + + val counter1 = new AtomicInteger + val actor1 = system.actorOf(Props(new Actor { + def receive = { + case "end" ⇒ doneLatch.countDown() + case msg: Int ⇒ counter1.addAndGet(msg) + } + })) + + val counter2 = new AtomicInteger + val actor2 = system.actorOf(Props(new Actor { + def receive = { + case "end" ⇒ doneLatch.countDown() + case msg: Int ⇒ counter2.addAndGet(msg) + } + })) + + val routedActor = system.actorOf(Props(new TestActor).withRouter(ScatterGatherFirstCompletedRouter(targets = List(actor1, actor2)))) + routedActor ! Broadcast(1) + routedActor ! Broadcast("end") + + doneLatch.await + + counter1.get must be(1) + counter2.get must be(1) + } + + "return response, even if one of the actors has stopped" in { + val shutdownLatch = new TestLatch(1) + val actor1 = newActor(1, Some(shutdownLatch)) + val actor2 = newActor(22, Some(shutdownLatch)) + val routedActor = system.actorOf(Props(new TestActor).withRouter(ScatterGatherFirstCompletedRouter(targets = List(actor1, actor2)))) + + routedActor ! Broadcast(Stop(Some(1))) + shutdownLatch.await + (routedActor ? Broadcast(0)).as[Int].get must be(22) + } + + case class Stop(id: Option[Int] = None) + + def newActor(id: Int, shudownLatch: Option[TestLatch] = None) = system.actorOf(Props(new Actor { + def receive = { + case Stop(None) ⇒ self.stop() + case Stop(Some(_id)) if (_id == id) ⇒ self.stop() + case _id: Int if (_id == id) ⇒ + case x ⇒ { + Thread sleep 100 * id + sender.tell(id) + } + } + + override def postStop = { + shudownLatch foreach (_.countDown()) + } + }), "Actor:" + id) + } } diff --git a/akka-actor/src/main/java/akka/routing/RouterFactory.java b/akka-actor/src/main/java/akka/routing/RouterFactory.java deleted file mode 100644 index 65ce7a10c6..0000000000 --- a/akka-actor/src/main/java/akka/routing/RouterFactory.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (C) 2009-2011 Typesafe Inc. - */ - -package akka.routing; - -/** - * A Factory responsible for creating {@link Router} instances. It makes Java compatability possible for users that - * want to provide their own router instance. - */ -public interface RouterFactory { - - /** - * Creates a new Router instance. - * - * @return the newly created Router instance. - */ - Router newRouter(); -} diff --git a/akka-actor/src/main/resources/reference.conf b/akka-actor/src/main/resources/reference.conf index 0c759f8079..ecbf916a9a 100644 --- a/akka-actor/src/main/resources/reference.conf +++ b/akka-actor/src/main/resources/reference.conf @@ -46,56 +46,32 @@ akka { deployment { - default { # deployment id pattern, e.g. /app/service-ping + default { # deployment id pattern, e.g. /app/service-ping - router = "direct" # routing (load-balance) scheme to use - # available: "direct", "round-robin", "random", "scatter-gather" - # "least-cpu", "least-ram", "least-messages" - # or: fully qualified class name of the router class - # default is "direct"; - # if 'replication' is used then the only available router is "direct" + router = "direct" # routing (load-balance) scheme to use + # available: "direct", "round-robin", "random", "scatter-gather" + # or: fully qualified class name of the router class + # default is "direct"; + # In case of non-direct routing, the actors to be routed to can be specified + # in several ways: + # - nr-of-instances: will create that many children given the actor factory + # supplied in the source code (overridable using create-as below) + # - target.paths: will look the paths up using actorFor and route to + # them, i.e. will not create children - nr-of-instances = 1 # number of actor instances in the cluster - # available: positive integer (1-N) or the string "auto" for auto-scaling - # default is '1' - # if the "direct" router is used then this element is ignored (always '1') + nr-of-instances = 1 # number of children to create in case of a non-direct router; this setting + # is ignored if target.paths is given - - # optional - create-as { # FIXME document 'create-as' - class = "" # fully qualified class name of recipe implementation + create-as { # FIXME document 'create-as' + class = "" # fully qualified class name of recipe implementation + } + + target { + paths = [] # Alternatively to giving nr-of-instances you can specify the full paths of + # those actors which should be routed to. This setting takes precedence over + # nr-of-instances } - remote { - nodes = [] # A list of hostnames and ports for instantiating the remote actor instances - # The format should be on "hostname:port", where: - # - hostname can be either hostname or IP address the remote actor should connect to - # - port should be the port for the remote server on the other node - } - - cluster { # defines the actor as a clustered actor - # default (if omitted) is local non-clustered actor - - preferred-nodes = [] # a list of preferred nodes for instantiating the actor instances on - # on format "host:", "ip:" or "node:" - - - # optional - replication { # use replication or not? only makes sense for a stateful actor - # serialize-mailbox not implemented, ticket #1412 - serialize-mailbox = off # should the actor mailbox be part of the serialized snapshot? - # default is 'off' - - storage = "transaction-log" # storage model for replication - # available: "transaction-log" and "data-grid" - # default is "transaction-log" - - strategy = "write-through" # guarantees for replication - # available: "write-through" and "write-behind" - # default is "write-through" - - } - } } } diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index 2c7944a69e..ffb941408a 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -4,11 +4,9 @@ package akka.actor -import DeploymentConfig._ import akka.dispatch._ import akka.routing._ import akka.util.Duration -import akka.remote.RemoteSupport import akka.japi.{ Creator, Procedure } import akka.serialization.{ Serializer, Serialization } import akka.event.Logging.Debug @@ -26,7 +24,7 @@ import java.util.regex.Pattern /** * Marker trait to show which Messages are automatically handled by Akka */ -sealed trait AutoReceivedMessage extends Serializable +trait AutoReceivedMessage extends Serializable trait PossiblyHarmful diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 33fc177de6..c4053081cd 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -48,6 +48,11 @@ trait ActorContext extends ActorRefFactory { def self: ActorRef + /** + * Retrieve the Props which were used to create this actor. + */ + def props: Props + /** * Gets the current receive timeout * When specified, the receive method should be able to handle a 'ReceiveTimeout' message. @@ -176,7 +181,7 @@ private[akka] object ActorCell { //ACTORCELL IS 64bytes and should stay that way unless very good reason not to (machine sympathy, cache line fit) //vars don't need volatile since it's protected with the mailbox status //Make sure that they are not read/written outside of a message processing (systemInvoke/invoke) -private[akka] final class ActorCell( +private[akka] class ActorCell( val system: ActorSystemImpl, val self: InternalActorRef, val props: Props, @@ -186,7 +191,7 @@ private[akka] final class ActorCell( import ActorCell._ - def systemImpl = system + final def systemImpl = system protected final def guardian = self @@ -194,11 +199,11 @@ private[akka] final class ActorCell( final def provider = system.provider - override def receiveTimeout: Option[Duration] = if (receiveTimeoutData._1 > 0) Some(Duration(receiveTimeoutData._1, MILLISECONDS)) else None + override final def receiveTimeout: Option[Duration] = if (receiveTimeoutData._1 > 0) Some(Duration(receiveTimeoutData._1, MILLISECONDS)) else None - override def setReceiveTimeout(timeout: Duration): Unit = setReceiveTimeout(Some(timeout)) + override final def setReceiveTimeout(timeout: Duration): Unit = setReceiveTimeout(Some(timeout)) - def setReceiveTimeout(timeout: Option[Duration]): Unit = { + final def setReceiveTimeout(timeout: Option[Duration]): Unit = { val timeoutMs = timeout match { case None ⇒ -1L case Some(duration) ⇒ @@ -211,18 +216,18 @@ private[akka] final class ActorCell( receiveTimeoutData = (timeoutMs, receiveTimeoutData._2) } - override def resetReceiveTimeout(): Unit = setReceiveTimeout(None) + final override def resetReceiveTimeout(): Unit = setReceiveTimeout(None) /** * In milliseconds */ - var receiveTimeoutData: (Long, Cancellable) = + final var receiveTimeoutData: (Long, Cancellable) = if (_receiveTimeout.isDefined) (_receiveTimeout.get.toMillis, emptyCancellable) else emptyReceiveTimeoutData - var childrenRefs: TreeMap[String, ChildRestartStats] = emptyChildrenRefs + final var childrenRefs: TreeMap[String, ChildRestartStats] = emptyChildrenRefs private def _actorOf(props: Props, name: String): ActorRef = { - val actor = provider.actorOf(systemImpl, props, guardian, name, false) + val actor = provider.actorOf(systemImpl, props, self, self.path / name, false, None) childrenRefs = childrenRefs.updated(name, ChildRestartStats(actor)) actor } @@ -237,19 +242,19 @@ private[akka] final class ActorCell( _actorOf(props, name) } - var currentMessage: Envelope = null + final var currentMessage: Envelope = null - var actor: Actor = _ + final var actor: Actor = _ - var stopping = false + final var stopping = false @volatile //This must be volatile since it isn't protected by the mailbox status var mailbox: Mailbox = _ - var nextNameSequence: Long = 0 + final var nextNameSequence: Long = 0 //Not thread safe, so should only be used inside the actor that inhabits this ActorCell - protected def randomName(): String = { + final protected def randomName(): String = { val n = nextNameSequence + 1 nextNameSequence = n Helpers.base64(n) @@ -261,7 +266,7 @@ private[akka] final class ActorCell( /** * UntypedActorContext impl */ - def getDispatcher(): MessageDispatcher = dispatcher + final def getDispatcher(): MessageDispatcher = dispatcher final def isTerminated: Boolean = mailbox.isClosed @@ -281,7 +286,7 @@ private[akka] final class ActorCell( final def resume(): Unit = dispatcher.systemDispatch(this, Resume()) // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ - private[akka] def stop(): Unit = dispatcher.systemDispatch(this, Terminate()) + final def stop(): Unit = dispatcher.systemDispatch(this, Terminate()) override final def watch(subject: ActorRef): ActorRef = { // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ @@ -300,7 +305,7 @@ private[akka] final class ActorCell( /** * Impl UntypedActorContext */ - def getChildren(): java.lang.Iterable[ActorRef] = { + final def getChildren(): java.lang.Iterable[ActorRef] = { import scala.collection.JavaConverters.asJavaIterableConverter asJavaIterableConverter(children).asJava } diff --git a/akka-actor/src/main/scala/akka/actor/ActorPath.scala b/akka-actor/src/main/scala/akka/actor/ActorPath.scala index b364fd5247..399011fd7a 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorPath.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorPath.scala @@ -27,7 +27,7 @@ object ActorPath { * is sorted by path elements FROM RIGHT TO LEFT, where RootActorPath > * ChildActorPath in case the number of elements is different. */ -sealed trait ActorPath extends Comparable[ActorPath] { +sealed trait ActorPath extends Comparable[ActorPath] with Serializable { /** * The Address under which this path can be reached; walks up the tree to * the RootActorPath. diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index abd88f31a5..d705fb1b52 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -9,12 +9,12 @@ import akka.util._ import scala.collection.immutable.Stack import java.lang.{ UnsupportedOperationException, IllegalStateException } import akka.serialization.Serialization -import java.net.InetSocketAddress -import akka.remote.RemoteAddress import java.util.concurrent.TimeUnit import akka.event.EventStream import akka.event.DeathWatch import scala.annotation.tailrec +import java.util.concurrent.ConcurrentHashMap +import akka.event.LoggingAdapter /** * ActorRef is an immutable and serializable handle to an Actor. @@ -48,7 +48,6 @@ import scala.annotation.tailrec */ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable { scalaRef: InternalActorRef ⇒ - // Only mutable for RemoteServer in order to maintain identity across nodes /** * Returns the path for this actor (from this actor up to the root actor). @@ -213,8 +212,8 @@ private[akka] case object Nobody extends MinimalActorRef { /** * Local (serializable) ActorRef that is used when referencing the Actor on its "home" node. */ -class LocalActorRef private[akka] ( - system: ActorSystemImpl, +private[akka] class LocalActorRef private[akka] ( + _system: ActorSystemImpl, _props: Props, _supervisor: InternalActorRef, val path: ActorPath, @@ -233,9 +232,20 @@ class LocalActorRef private[akka] ( * us to use purely factory methods for creating LocalActorRefs. */ @volatile - private var actorCell = new ActorCell(system, this, _props, _supervisor, _receiveTimeout, _hotswap) + private var actorCell = newActorCell(_system, this, _props, _supervisor, _receiveTimeout, _hotswap) actorCell.start() + protected def newActorCell( + system: ActorSystemImpl, + ref: InternalActorRef, + props: Props, + supervisor: InternalActorRef, + receiveTimeout: Option[Duration], + hotswap: Stack[PartialFunction[Any, Unit]]): ActorCell = + new ActorCell(system, ref, props, supervisor, receiveTimeout, hotswap) + + protected def actorContext: ActorContext = actorCell + /** * Is the actor terminated? * If this method returns true, it will never return false again, but if it @@ -250,13 +260,11 @@ class LocalActorRef private[akka] ( * message sends done from the same thread after calling this method will not * be processed until resumed. */ - //FIXME TODO REMOVE THIS, NO REPLACEMENT, ticket #1415 def suspend(): Unit = actorCell.suspend() /** * Resumes a suspended actor. */ - //FIXME TODO REMOVE THIS, NO REPLACEMENT, ticket #1415 def resume(): Unit = actorCell.resume() /** @@ -306,22 +314,20 @@ class LocalActorRef private[akka] ( protected[akka] def underlying: ActorCell = actorCell - // FIXME TODO: remove this method. It is used in testkit. - // @deprecated("This method does a spin-lock to block for the actor, which might never be there, do not use this", "2.0") - protected[akka] def underlyingActorInstance: Actor = { - var instance = actorCell.actor - while ((instance eq null) && !actorCell.isTerminated) { - try { Thread.sleep(1) } catch { case i: InterruptedException ⇒ } - instance = actorCell.actor - } - instance - } - 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) + def ?(message: Any)(implicit timeout: Timeout): Future[Any] = { + actorCell.provider.ask(timeout) match { + case Some(a) ⇒ + this.!(message)(a) + a.result + case None ⇒ + this.!(message)(null) + new DefaultPromise[Any](0)(actorCell.system.dispatcher) + } + } def restart(cause: Throwable): Unit = actorCell.restart(cause) @@ -426,6 +432,41 @@ class DeadLetterActorRef(val eventStream: EventStream) extends MinimalActorRef { private def writeReplace(): AnyRef = DeadLetterActorRef.serialized } +class VirtualPathContainer(val path: ActorPath, override val getParent: InternalActorRef, val log: LoggingAdapter) extends MinimalActorRef { + + private val children = new ConcurrentHashMap[String, InternalActorRef] + + def addChild(name: String, ref: InternalActorRef): Unit = { + children.put(name, ref) match { + case null ⇒ // okay + case old ⇒ log.warning("{} replacing child {} ({} -> {})", path, name, old, ref) + } + } + + def removeChild(name: String): Unit = { + children.remove(name) match { + case null ⇒ log.warning("{} trying to remove non-child {}", path, name) + case _ ⇒ //okay + } + } + + def getChild(name: String): InternalActorRef = children.get(name) + + override def getChild(name: Iterator[String]): InternalActorRef = { + if (name.isEmpty) this + else { + val n = name.next() + if (n.isEmpty) this + else children.get(n) match { + case null ⇒ Nobody + case some ⇒ + if (name.isEmpty) some + else some.getChild(name) + } + } + } +} + class AskActorRef( val path: ActorPath, override val getParent: InternalActorRef, diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index f038d430d0..74762f170b 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -5,21 +5,12 @@ package akka.actor import java.util.concurrent.atomic.AtomicLong -import java.util.concurrent.{ ConcurrentHashMap, TimeUnit } -import scala.annotation.tailrec import org.jboss.netty.akka.util.{ TimerTask, HashedWheelTimer } -import akka.actor.Timeout.intToTimeout -import akka.config.ConfigurationException import akka.dispatch._ import akka.routing._ import akka.AkkaException -import com.eaio.uuid.UUID import akka.util.{ Duration, Switch, Helpers } -import akka.remote.RemoteAddress -import org.jboss.netty.akka.util.internal.ConcurrentIdentityHashMap import akka.event._ -import akka.event.Logging.Error._ -import akka.event.Logging.Warning import java.io.Closeable /** @@ -71,9 +62,9 @@ trait ActorRefProvider { */ def init(system: ActorSystemImpl): Unit - private[akka] def deployer: Deployer + def deployer: Deployer - private[akka] def scheduler: Scheduler + def scheduler: Scheduler /** * Actor factory with create-only semantics: will create an actor as @@ -81,7 +72,7 @@ trait ActorRefProvider { * in case of remote supervision). If systemService is true, deployment is * bypassed (local-only). */ - def actorOf(system: ActorSystemImpl, props: Props, supervisor: InternalActorRef, name: String, systemService: Boolean = false): InternalActorRef + def actorOf(system: ActorSystemImpl, props: Props, supervisor: InternalActorRef, path: ActorPath, systemService: Boolean, deploy: Option[Deploy]): InternalActorRef /** * Create actor reference for a specified local or remote path. If no such @@ -105,18 +96,17 @@ trait ActorRefProvider { */ def actorFor(ref: InternalActorRef, p: Iterable[String]): InternalActorRef - private[akka] def createDeathWatch(): DeathWatch - /** - * Create AskActorRef to hook up message send to recipient with Future receiver. + * Create AskActorRef and register it properly so it can be serialized/deserialized; + * caller needs to send the message. */ - private[akka] def ask(message: Any, recipient: ActorRef, within: Timeout): Future[Any] + def ask(within: Timeout): Option[AskActorRef] /** * This Future is completed upon termination of this ActorRefProvider, which * is usually initiated by stopping the guardian via ActorSystem.stop(). */ - private[akka] def terminationFuture: Future[Unit] + def terminationFuture: Future[Unit] } /** @@ -202,7 +192,7 @@ trait ActorRefFactory { * ... * val target = context.actorFor(Seq("..", "myBrother", "myNephew")) * ... - * } + * } * } * }}} * @@ -225,7 +215,7 @@ trait ActorRefFactory { * path.add("myNephew"); * final ActorRef target = context().actorFor(path); * ... - * } + * } * } * }}} * @@ -266,9 +256,22 @@ class LocalActorRefProvider( val settings: ActorSystem.Settings, val eventStream: EventStream, val scheduler: Scheduler, - val deadLetters: InternalActorRef) extends ActorRefProvider { + val deadLetters: InternalActorRef, + val rootPath: ActorPath, + val deployer: Deployer) extends ActorRefProvider { - val rootPath: ActorPath = new RootActorPath(LocalAddress(_systemName)) + def this(_systemName: String, + settings: ActorSystem.Settings, + eventStream: EventStream, + scheduler: Scheduler, + deadLetters: InternalActorRef) = + this(_systemName, + settings, + eventStream, + scheduler, + deadLetters, + new RootActorPath(LocalAddress(_systemName)), + new Deployer(settings)) // FIXME remove both val nodename: String = "local" @@ -276,8 +279,6 @@ class LocalActorRefProvider( val log = Logging(eventStream, "LocalActorRefProvider") - private[akka] val deployer: Deployer = new Deployer(settings, eventStream, nodename) - /* * generate name for temporary actor refs */ @@ -299,11 +300,8 @@ class LocalActorRefProvider( @volatile var causeOfTermination: Option[Throwable] = None - // FIXME (actor path): move the root path to the new root guardian val path = rootPath / "bubble-walker" - val address = path.toString - override def stop() = stopped switchOn { terminationFuture.complete(causeOfTermination.toLeft(())) } @@ -324,6 +322,11 @@ class LocalActorRefProvider( } } + /* + * Guardians can be asked by ActorSystem to create children, i.e. top-level + * actors. Therefore these need to answer to these requests, forwarding any + * exceptions which might have occurred. + */ private class Guardian extends Actor { def receive = { case Terminated(_) ⇒ context.self.stop() @@ -333,6 +336,11 @@ class LocalActorRefProvider( } } + /* + * Guardians can be asked by ActorSystem to create children, i.e. top-level + * actors. Therefore these need to answer to these requests, forwarding any + * exceptions which might have occurred. + */ private class SystemGuardian extends Actor { def receive = { case Terminated(_) ⇒ @@ -368,46 +376,50 @@ class LocalActorRefProvider( lazy val terminationFuture: DefaultPromise[Unit] = new DefaultPromise[Unit](Timeout.never)(dispatcher) - 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) + @volatile + private var extraNames: Map[String, InternalActorRef] = Map() + + /** + * Higher-level providers (or extensions) might want to register new synthetic + * top-level paths for doing special stuff. This is the way to do just that. + * Just be careful to complete all this before ActorSystem.start() finishes, + * or before you start your own auto-spawned actors. + */ + def registerExtraNames(_extras: Map[String, InternalActorRef]): Unit = extraNames ++= _extras + + lazy val rootGuardian: InternalActorRef = + new LocalActorRef(system, guardianProps, theOneWhoWalksTheBubblesOfSpaceTime, rootPath, true) { + object Extra { + def unapply(s: String): Option[InternalActorRef] = extraNames.get(s) } - } - } - lazy val guardian: InternalActorRef = actorOf(system, guardianProps, rootGuardian, "user", true) + override def getParent: InternalActorRef = this - 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: Iterator[String]): InternalActorRef = { - if (name.isEmpty) this - else { - val n = name.next() - if (n.isEmpty) this - else children.get(n) match { - case null ⇒ Nobody - case some ⇒ - if (name.isEmpty) some - else some.getChild(name) + override def getSingleChild(name: String): InternalActorRef = { + name match { + case "temp" ⇒ tempContainer + case Extra(e) ⇒ e + case _ ⇒ super.getSingleChild(name) } } } - } - val deathWatch = createDeathWatch() + lazy val guardian: InternalActorRef = + actorOf(system, guardianProps, rootGuardian, rootPath / "user", true, None) + + lazy val systemGuardian: InternalActorRef = + actorOf(system, guardianProps.withCreator(new SystemGuardian), rootGuardian, rootPath / "system", true, None) + + lazy val tempContainer = new VirtualPathContainer(tempNode, rootGuardian, log) + + val deathWatch = new LocalDeathWatch def init(_system: ActorSystemImpl) { system = _system // chain death watchers so that killing guardian stops the application deathWatch.subscribe(systemGuardian, guardian) deathWatch.subscribe(rootGuardian, systemGuardian) + eventStream.startDefaultLoggers(_system) } def actorFor(ref: InternalActorRef, path: String): InternalActorRef = path match { @@ -430,79 +442,32 @@ class LocalActorRefProvider( case x ⇒ x } - 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 { - - // 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)) ⇒ - implicit val dispatcher = if (props.dispatcher == Props.defaultDispatcher) system.dispatcher else props.dispatcher - implicit val timeout = system.settings.ActorTimeout - val routerFactory: () ⇒ Router = DeploymentConfig.routerTypeFor(routerType) match { - case RouterType.Direct ⇒ () ⇒ new DirectRouter - case RouterType.Random ⇒ () ⇒ new RandomRouter - case RouterType.RoundRobin ⇒ () ⇒ new RoundRobinRouter - case RouterType.Broadcast ⇒ () ⇒ new BroadcastRouter - 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) + def actorOf(system: ActorSystemImpl, props: Props, supervisor: InternalActorRef, path: ActorPath, systemService: Boolean, deploy: Option[Deploy]): InternalActorRef = { + props.routerConfig match { + case NoRouter ⇒ new LocalActorRef(system, props, supervisor, path, systemService) // create a local actor + case router ⇒ + val depl = deploy orElse { + val lookupPath = path.elements.drop(1).mkString("/", "/", "") + deployer.lookup(lookupPath) } - - 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.name) - - case unknown ⇒ throw new Exception("Don't know how to create this Actor - cause [" + unknown + "]") + new RoutedActorRef(system, props.withRouter(router.adaptFromDeploy(depl)), supervisor, path) } } - /** - * Creates (or fetches) a routed actor reference, configured by the 'props: RoutedProps' configuration. - */ - 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 - //TODO Implement support for configuring by deployment ID etc - //TODO If address matches an already created actor (Ahead-of-time deployed) return that actor - //TODO If address exists in config, it will override the specified Props (should we attempt to merge?) - //TODO If the actor deployed uses a different config, then ignore or throw exception? - if (props.connectionManager.isEmpty) throw new ConfigurationException("RoutedProps used for creating actor [" + name + "] has zero connections configured; can't create a router") - // val clusteringEnabled = ReflectiveAccess.ClusterModule.isEnabled - // val localOnly = props.localOnly - // if (clusteringEnabled && !props.localOnly) ReflectiveAccess.ClusterModule.newClusteredActorRef(props) - // else new RoutedActorRef(props, address) - new RoutedActorRef(system, props, supervisor, name) - } - - private[akka] def createDeathWatch(): DeathWatch = new LocalDeathWatch - - private[akka] def ask(message: Any, recipient: ActorRef, within: Timeout): Future[Any] = { - import akka.dispatch.DefaultPromise + def ask(within: Timeout): Option[AskActorRef] = { (if (within == null) settings.ActorTimeout else within) match { case t if t.duration.length <= 0 ⇒ - new DefaultPromise[Any](0)(dispatcher) //Abort early if nonsensical timeout + None case t ⇒ val path = tempPath() val name = path.name val a = new AskActorRef(path, tempContainer, deathWatch, t, dispatcher) { override def whenDone() { - tempContainer.children.remove(name) + tempContainer.removeChild(name) } } - tempContainer.children.put(name, a) - recipient.tell(message, a) - a.result + tempContainer.addChild(name, a) + Some(a) } } } @@ -534,6 +499,7 @@ class LocalDeathWatch extends DeathWatch with ActorClassification { * returned from stop(). */ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, log: LoggingAdapter, dispatcher: ⇒ MessageDispatcher) extends Scheduler with Closeable { + import org.jboss.netty.akka.util.{ Timeout ⇒ HWTimeout } def schedule(initialDelay: Duration, delay: Duration, receiver: ActorRef, message: Any): Cancellable = diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 66a211def8..af0ec81d7b 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -418,10 +418,10 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor def /(path: Iterable[String]): ActorPath = guardian.path / path private lazy val _start: this.type = { + // the provider is expected to start default loggers, LocalActorRefProvider does this provider.init(this) deadLetters.init(dispatcher, provider.rootPath) // this starts the reaper actor and the user-configured logging subscribers, which are also actors - eventStream.startDefaultLoggers(this) registerOnTermination(stopScheduler()) loadExtensions() if (LogConfigOnStart) logConfiguration() diff --git a/akka-actor/src/main/scala/akka/actor/Deployer.scala b/akka-actor/src/main/scala/akka/actor/Deployer.scala index 62b4e6b818..6939ebf244 100644 --- a/akka-actor/src/main/scala/akka/actor/Deployer.scala +++ b/akka-actor/src/main/scala/akka/actor/Deployer.scala @@ -7,277 +7,71 @@ package akka.actor import collection.immutable.Seq import java.util.concurrent.ConcurrentHashMap import akka.event.Logging -import akka.actor.DeploymentConfig._ import akka.AkkaException import akka.config.ConfigurationException import akka.util.Duration -import java.net.InetSocketAddress -import akka.remote.RemoteAddress import akka.event.EventStream -import com.typesafe.config.Config +import com.typesafe.config._ +import akka.routing._ -trait ActorDeployer { - private[akka] def init(deployments: Seq[Deploy]): Unit - private[akka] def deploy(deployment: Deploy): Unit - private[akka] def lookupDeploymentFor(path: String): Option[Deploy] - def lookupDeployment(path: String): Option[Deploy] = path match { - case null | "" ⇒ None - case s if s.startsWith("$") ⇒ None - case some ⇒ lookupDeploymentFor(some) - } - private[akka] def deploy(deployment: Seq[Deploy]): Unit = deployment foreach (deploy(_)) -} +case class Deploy(path: String, config: Config, recipe: Option[ActorRecipe] = None, routing: RouterConfig = NoRouter, scope: Scope = LocalScope) + +case class ActorRecipe(implementationClass: Class[_ <: Actor]) //TODO Add ActorConfiguration here + +trait Scope +case class LocalScope() extends Scope +case object LocalScope extends Scope /** * Deployer maps actor paths to actor deployments. + * + * @author Jonas Bonér */ -class Deployer(val settings: ActorSystem.Settings, val eventStream: EventStream, val nodename: String) extends ActorDeployer { +class Deployer(val settings: ActorSystem.Settings) { - val deploymentConfig = new DeploymentConfig(nodename) - private val log = Logging(eventStream, "Deployer") + import scala.collection.JavaConverters._ - val instance: ActorDeployer = { - val deployer = new LocalDeployer() - deployer.init(deploymentsInConfig) - deployer - } + private val deployments = new ConcurrentHashMap[String, Deploy] + private val config = settings.config.getConfig("akka.actor.deployment") + protected val default = config.getConfig("default") + config.root.asScala flatMap { + case ("default", _) ⇒ None + case (key, value: ConfigObject) ⇒ parseConfig(key, value.toConfig) + case _ ⇒ None + } foreach deploy - def start(): Unit = instance.toString //Force evaluation + def lookup(path: String): Option[Deploy] = Option(deployments.get(path)) - private[akka] def init(deployments: Seq[Deploy]) = instance.init(deployments) + def deploy(d: Deploy): Unit = deployments.put(d.path, d) - def deploy(deployment: Deploy): Unit = instance.deploy(deployment) - - def isLocal(deployment: Deploy): Boolean = deployment match { - case Deploy(_, _, _, _, LocalScope) | Deploy(_, _, _, _, _: LocalScope) ⇒ true - case _ ⇒ false - } - - def isClustered(deployment: Deploy): Boolean = !isLocal(deployment) - - def isLocal(path: String): Boolean = isLocal(deploymentFor(path)) //TODO Should this throw exception if path not found? - - def isClustered(path: String): Boolean = !isLocal(path) //TODO Should this throw exception if path not found? - - /** - * Same as 'lookupDeploymentFor' but throws an exception if no deployment is bound. - */ - private[akka] def deploymentFor(path: String): Deploy = { - lookupDeploymentFor(path) match { - case Some(deployment) ⇒ deployment - case None ⇒ thrownNoDeploymentBoundException(path) - } - } - - private[akka] def lookupDeploymentFor(path: String): Option[Deploy] = - instance.lookupDeploymentFor(path) - - private def deploymentsInConfig: List[Deploy] = { - val allDeployments = settings.config.getConfig("akka.actor.deployment") - val defaultDeployment = allDeployments.getConfig("default") - // foreach akka.actor.deployment. - for (path ← pathsInConfig) yield parseDeploymentConfig(allDeployments.getConfig(path), defaultDeployment, path) - } - - private def pathsInConfig: List[String] = { - def pathSubstring(path: String) = { - val i = path.indexOf(".") - if (i == -1) path else path.substring(0, i) - } - - import scala.collection.JavaConverters._ - settings.config.getConfig("akka.actor.deployment").root.keySet.asScala - .filterNot("default" ==) - .map(path ⇒ pathSubstring(path)) - .toSet.toList // toSet to force uniqueness - } - - /** - * Parse deployment in supplied deployment Config, using the - * defaultDeployment Config as fallback. - * The path is the actor path and used for error reporting. - * - */ - private def parseDeploymentConfig(deployment: Config, defaultDeployment: Config, path: String): Deploy = { - import scala.collection.JavaConverters._ + protected def parseConfig(key: String, config: Config): Option[Deploy] = { import akka.util.ReflectiveAccess.getClassFor - val deploymentWithFallback = deployment.withFallback(defaultDeployment) - // -------------------------------- - // akka.actor.deployment..router - // -------------------------------- - val router: Routing = deploymentWithFallback.getString("router") match { - case "direct" ⇒ Direct - case "round-robin" ⇒ RoundRobin - case "random" ⇒ Random - case "scatter-gather" ⇒ ScatterGather - case "least-cpu" ⇒ LeastCPU - case "least-ram" ⇒ LeastRAM - case "least-messages" ⇒ LeastMessages - case routerClassName ⇒ CustomRouter(routerClassName) + val deployment = config.withFallback(default) + + val targets = deployment.getStringList("target.paths").asScala.toSeq + + val nrOfInstances = deployment.getInt("nr-of-instances") + + val router: RouterConfig = deployment.getString("router") match { + case "direct" ⇒ NoRouter + case "round-robin" ⇒ RoundRobinRouter(nrOfInstances, targets) + case "random" ⇒ RandomRouter(nrOfInstances, targets) + case "scatter-gather" ⇒ ScatterGatherFirstCompletedRouter(nrOfInstances, targets) + case "broadcast" ⇒ BroadcastRouter(nrOfInstances, targets) + case x ⇒ throw new ConfigurationException("unknown router type " + x + " for path " + key) } - // -------------------------------- - // akka.actor.deployment..nr-of-instances - // -------------------------------- - val nrOfInstances = { - if (router == Direct) OneNrOfInstances - else { - def invalidNrOfInstances(wasValue: Any) = new ConfigurationException( - "Deployment config option [" + path + - ".nr-of-instances] needs to be either [\"auto\"] or [1-N] - was [" + - wasValue + "]") - - deploymentWithFallback.getAnyRef("nr-of-instances").asInstanceOf[Any] match { - case "auto" ⇒ AutoNrOfInstances - case 1 ⇒ OneNrOfInstances - case 0 ⇒ ZeroNrOfInstances - case nrOfReplicas: Number ⇒ - try { - new NrOfInstances(nrOfReplicas.intValue) - } catch { - case e: Exception ⇒ throw invalidNrOfInstances(nrOfReplicas) - } - case unknown ⇒ throw invalidNrOfInstances(unknown) - } - } - } - - // -------------------------------- - // akka.actor.deployment..create-as - // -------------------------------- val recipe: Option[ActorRecipe] = - deploymentWithFallback.getString("create-as.class") match { + deployment.getString("create-as.class") match { case "" ⇒ None case impl ⇒ val implementationClass = getClassFor[Actor](impl).fold(e ⇒ throw new ConfigurationException( - "Deployment config option [" + path + ".create-as.class] load failed", e), identity) + "Config option [akka.actor.deployment." + key + ".create-as.class] load failed", e), identity) Some(ActorRecipe(implementationClass)) } - val remoteNodes = deploymentWithFallback.getStringList("remote.nodes").asScala.toSeq - val clusterPreferredNodes = deploymentWithFallback.getStringList("cluster.preferred-nodes").asScala.toSeq - - // -------------------------------- - // akka.actor.deployment..remote - // -------------------------------- - def parseRemote: Scope = { - def raiseRemoteNodeParsingError() = throw new ConfigurationException( - "Deployment config option [" + path + - ".remote.nodes] needs to be a list with elements on format \":\", was [" + remoteNodes.mkString(", ") + "]") - - val remoteAddresses = remoteNodes map { node ⇒ - val tokenizer = new java.util.StringTokenizer(node, ":") - val hostname = tokenizer.nextElement.toString - if ((hostname eq null) || (hostname == "")) raiseRemoteNodeParsingError() - val port = try tokenizer.nextElement.toString.toInt catch { - case e: Exception ⇒ raiseRemoteNodeParsingError() - } - if (port == 0) raiseRemoteNodeParsingError() - - RemoteAddress(settings.name, hostname, port) - } - - RemoteScope(remoteAddresses) - } - - // -------------------------------- - // akka.actor.deployment..cluster - // -------------------------------- - def parseCluster: Scope = { - def raiseHomeConfigError() = throw new ConfigurationException( - "Deployment config option [" + path + - ".cluster.preferred-nodes] needs to be a list with elements on format\n'host:', 'ip:' or 'node:', was [" + - clusterPreferredNodes + "]") - - val remoteNodes = clusterPreferredNodes map { home ⇒ - if (!(home.startsWith("host:") || home.startsWith("node:") || home.startsWith("ip:"))) raiseHomeConfigError() - - val tokenizer = new java.util.StringTokenizer(home, ":") - val protocol = tokenizer.nextElement - val address = tokenizer.nextElement.asInstanceOf[String] - - // TODO host and ip protocols? - protocol match { - case "node" ⇒ Node(address) - case _ ⇒ raiseHomeConfigError() - } - } - deploymentConfig.ClusterScope(remoteNodes, parseClusterReplication) - } - - // -------------------------------- - // akka.actor.deployment..cluster.replication - // -------------------------------- - def parseClusterReplication: ReplicationScheme = { - deployment.hasPath("cluster.replication") match { - case false ⇒ Transient - case true ⇒ - val replicationConfigWithFallback = deploymentWithFallback.getConfig("cluster.replication") - val storage = replicationConfigWithFallback.getString("storage") match { - case "transaction-log" ⇒ TransactionLog - case "data-grid" ⇒ DataGrid - case unknown ⇒ - throw new ConfigurationException("Deployment config option [" + path + - ".cluster.replication.storage] needs to be either [\"transaction-log\"] or [\"data-grid\"] - was [" + - unknown + "]") - } - val strategy = replicationConfigWithFallback.getString("strategy") match { - case "write-through" ⇒ WriteThrough - case "write-behind" ⇒ WriteBehind - case unknown ⇒ - throw new ConfigurationException("Deployment config option [" + path + - ".cluster.replication.strategy] needs to be either [\"write-through\"] or [\"write-behind\"] - was [" + - unknown + "]") - } - Replication(storage, strategy) - } - } - - val scope = (remoteNodes, clusterPreferredNodes) match { - case (Nil, Nil) ⇒ - LocalScope - case (_, Nil) ⇒ - // we have a 'remote' config section - parseRemote - case (Nil, _) ⇒ - // we have a 'cluster' config section - parseCluster - case (_, _) ⇒ throw new ConfigurationException( - "Configuration for deployment ID [" + path + "] can not have both 'remote' and 'cluster' sections.") - } - - Deploy(path, recipe, router, nrOfInstances, scope) + Some(Deploy(key, deployment, recipe, router, LocalScope)) } - private[akka] def throwDeploymentBoundException(deployment: Deploy): Nothing = { - val e = new DeploymentAlreadyBoundException("Path [" + deployment.path + "] already bound to [" + deployment + "]") - log.error(e, e.getMessage) - throw e - } - - private[akka] def thrownNoDeploymentBoundException(path: String): Nothing = { - val e = new NoDeploymentBoundException("Path [" + path + "] is not bound to a deployment") - log.error(e, e.getMessage) - throw e - } } - -/** - * Simple local deployer, only for internal use. - */ -class LocalDeployer extends ActorDeployer { - private val deployments = new ConcurrentHashMap[String, Deploy] - - private[akka] def init(deployments: Seq[Deploy]): Unit = deployments foreach deploy // deploy - - private[akka] def shutdown(): Unit = deployments.clear() //TODO do something else/more? - - private[akka] def deploy(deployment: Deploy): Unit = deployments.putIfAbsent(deployment.path, deployment) - - private[akka] def lookupDeploymentFor(path: String): Option[Deploy] = Option(deployments.get(path)) -} - -class DeploymentException private[akka] (message: String) extends AkkaException(message) -class DeploymentAlreadyBoundException private[akka] (message: String) extends AkkaException(message) -class NoDeploymentBoundException private[akka] (message: String) extends AkkaException(message) diff --git a/akka-actor/src/main/scala/akka/actor/DeploymentConfig.scala b/akka-actor/src/main/scala/akka/actor/DeploymentConfig.scala deleted file mode 100644 index 03e6aef683..0000000000 --- a/akka-actor/src/main/scala/akka/actor/DeploymentConfig.scala +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Copyright (C) 2009-2011 Typesafe Inc. - */ - -package akka.actor - -import akka.util.Duration -import akka.routing.RouterType -import akka.remote.RemoteAddress - -object DeploymentConfig { - - // -------------------------------- - // --- Deploy - // -------------------------------- - case class Deploy( - path: String, - recipe: Option[ActorRecipe], - routing: Routing = Direct, - nrOfInstances: NrOfInstances = ZeroNrOfInstances, - scope: Scope = LocalScope) - - // -------------------------------- - // --- Actor Recipe - // -------------------------------- - case class ActorRecipe(implementationClass: Class[_ <: Actor]) //TODO Add ActorConfiguration here - - // -------------------------------- - // --- Routing - // -------------------------------- - sealed trait Routing - case class CustomRouter(routerClassName: String) extends Routing - - // For Java API - case class Direct() extends Routing - case class RoundRobin() extends Routing - case class Random() extends Routing - case class ScatterGather() extends Routing - case class LeastCPU() extends Routing - case class LeastRAM() extends Routing - case class LeastMessages() extends Routing - - // For Scala API - case object Direct extends Routing - case object RoundRobin extends Routing - case object Random extends Routing - case object ScatterGather extends Routing - case object LeastCPU extends Routing - case object LeastRAM extends Routing - case object LeastMessages extends Routing - - // -------------------------------- - // --- Scope - // -------------------------------- - sealed trait Scope - - // For Java API - case class LocalScope() extends Scope - - // For Scala API - case object LocalScope extends Scope - - case class RemoteScope(nodes: Iterable[RemoteAddress]) extends Scope - - // -------------------------------- - // --- Home - // -------------------------------- - sealed trait Home - // case class Host(hostName: String) extends Home - case class Node(nodeName: String) extends Home - // case class IP(ipAddress: String) extends Home - - // -------------------------------- - // --- Replicas - // -------------------------------- - - class NrOfInstances(val factor: Int) extends Serializable { - // note that -1 is used for AutoNrOfInstances - if (factor < -1) throw new IllegalArgumentException("nr-of-instances can not be negative") - override def hashCode = 0 + factor.## - override def equals(other: Any) = NrOfInstances.unapply(this) == NrOfInstances.unapply(other) - override def toString = if (factor == -1) "NrOfInstances(auto)" else "NrOfInstances(" + factor + ")" - } - - object NrOfInstances { - def apply(factor: Int): NrOfInstances = factor match { - case -1 ⇒ AutoNrOfInstances - case 0 ⇒ ZeroNrOfInstances - case 1 ⇒ OneNrOfInstances - case x ⇒ new NrOfInstances(x) - } - def unapply(other: Any) = other match { - case x: NrOfInstances ⇒ import x._; Some(factor) - case _ ⇒ None - } - } - - // For Java API - class AutoNrOfInstances extends NrOfInstances(-1) - class ZeroNrOfInstances extends NrOfInstances(0) - class OneNrOfInstances extends NrOfInstances(1) - - // For Scala API - case object AutoNrOfInstances extends AutoNrOfInstances - case object ZeroNrOfInstances extends ZeroNrOfInstances - case object OneNrOfInstances extends OneNrOfInstances - - // -------------------------------- - // --- Replication - // -------------------------------- - sealed trait ReplicationScheme - - // For Java API - case class Transient() extends ReplicationScheme - - // For Scala API - case object Transient extends ReplicationScheme - case class Replication( - storage: ReplicationStorage, - strategy: ReplicationStrategy) extends ReplicationScheme - - // -------------------------------- - // --- ReplicationStorage - // -------------------------------- - sealed trait ReplicationStorage - - // For Java API - case class TransactionLog() extends ReplicationStorage - case class DataGrid() extends ReplicationStorage - - // For Scala API - case object TransactionLog extends ReplicationStorage - case object DataGrid extends ReplicationStorage - - // -------------------------------- - // --- ReplicationStrategy - // -------------------------------- - sealed trait ReplicationStrategy - - // For Java API - sealed class WriteBehind extends ReplicationStrategy - sealed class WriteThrough extends ReplicationStrategy - - // For Scala API - case object WriteBehind extends WriteBehind - case object WriteThrough extends WriteThrough - - // -------------------------------- - // --- Helper methods for parsing - // -------------------------------- - - def nodeNameFor(home: Home): String = home match { - case Node(nodename) ⇒ nodename - // case Host("localhost") ⇒ Config.nodename - // case IP("0.0.0.0") ⇒ Config.nodename - // case IP("127.0.0.1") ⇒ Config.nodename - // case Host(hostname) ⇒ throw new UnsupportedOperationException("Specifying preferred node name by 'hostname' is not yet supported. Use the node name like: preferred-nodes = [\"node:node1\"]") - // case IP(address) ⇒ throw new UnsupportedOperationException("Specifying preferred node name by 'IP address' is not yet supported. Use the node name like: preferred-nodes = [\"node:node1\"]") - } - - def routerTypeFor(routing: Routing): RouterType = routing match { - case _: Direct | Direct ⇒ RouterType.Direct - case _: RoundRobin | RoundRobin ⇒ RouterType.RoundRobin - case _: Random | Random ⇒ RouterType.Random - case _: ScatterGather | ScatterGather ⇒ RouterType.ScatterGather - case _: LeastCPU | LeastCPU ⇒ RouterType.LeastCPU - case _: LeastRAM | LeastRAM ⇒ RouterType.LeastRAM - case _: LeastMessages | LeastMessages ⇒ RouterType.LeastMessages - case CustomRouter(implClass) ⇒ RouterType.Custom(implClass) - } - - def isReplicated(replicationScheme: ReplicationScheme): Boolean = - isReplicatedWithTransactionLog(replicationScheme) || - isReplicatedWithDataGrid(replicationScheme) - - def isWriteBehindReplication(replicationScheme: ReplicationScheme): Boolean = replicationScheme match { - case _: Transient | Transient ⇒ false - case Replication(_, strategy) ⇒ - strategy match { - case _: WriteBehind | WriteBehind ⇒ true - case _: WriteThrough | WriteThrough ⇒ false - } - } - - def isWriteThroughReplication(replicationScheme: ReplicationScheme): Boolean = replicationScheme match { - case _: Transient | Transient ⇒ false - case Replication(_, strategy) ⇒ - strategy match { - case _: WriteBehind | WriteBehind ⇒ true - case _: WriteThrough | WriteThrough ⇒ false - } - } - - def isReplicatedWithTransactionLog(replicationScheme: ReplicationScheme): Boolean = replicationScheme match { - case _: Transient | Transient ⇒ false - case Replication(storage, _) ⇒ - storage match { - case _: TransactionLog | TransactionLog ⇒ true - case _: DataGrid | DataGrid ⇒ throw new UnsupportedOperationException("ReplicationStorage 'DataGrid' is no supported yet") - } - } - - def isReplicatedWithDataGrid(replicationScheme: ReplicationScheme): Boolean = replicationScheme match { - case _: Transient | Transient ⇒ false - case Replication(storage, _) ⇒ - storage match { - case _: TransactionLog | TransactionLog ⇒ false - case _: DataGrid | DataGrid ⇒ throw new UnsupportedOperationException("ReplicationStorage 'DataGrid' is no supported yet") - } - } - -} - -/** - * Module holding the programmatic deployment configuration classes. - * Defines the deployment specification. - * Most values have defaults and can be left out. - */ -class DeploymentConfig(val nodename: String) { - - import DeploymentConfig._ - - case class ClusterScope(preferredNodes: Iterable[Home] = Vector(Node(nodename)), replication: ReplicationScheme = Transient) extends Scope - - def isHomeNode(homes: Iterable[Home]): Boolean = homes exists (home ⇒ nodeNameFor(home) == nodename) - - def replicationSchemeFor(deployment: Deploy): Option[ReplicationScheme] = deployment match { - case Deploy(_, _, _, _, ClusterScope(_, replicationScheme)) ⇒ Some(replicationScheme) - case _ ⇒ None - } - - def isReplicated(deployment: Deploy): Boolean = replicationSchemeFor(deployment) match { - case Some(replicationScheme) ⇒ DeploymentConfig.isReplicated(replicationScheme) - case _ ⇒ false - } - -} diff --git a/akka-actor/src/main/scala/akka/actor/FaultHandling.scala b/akka-actor/src/main/scala/akka/actor/FaultHandling.scala index e239f50de1..87e65002fe 100644 --- a/akka-actor/src/main/scala/akka/actor/FaultHandling.scala +++ b/akka-actor/src/main/scala/akka/actor/FaultHandling.scala @@ -45,12 +45,18 @@ case class ChildRestartStats(val child: ActorRef, var maxNrOfRetriesCount: Int = } object FaultHandlingStrategy { - sealed trait Action //TODO FIXME What is the Java API for using this? + sealed trait Action case object Resume extends Action case object Restart extends Action case object Stop extends Action case object Escalate extends Action + // Java API + def resume = Resume + def restart = Restart + def stop = Stop + def escalate = Escalate + type Decider = PartialFunction[Throwable, Action] type JDecider = akka.japi.Function[Throwable, Action] type CauseAction = (Class[_ <: Throwable], Action) diff --git a/akka-actor/src/main/scala/akka/actor/Props.scala b/akka-actor/src/main/scala/akka/actor/Props.scala index 404966d88f..9824803aea 100644 --- a/akka-actor/src/main/scala/akka/actor/Props.scala +++ b/akka-actor/src/main/scala/akka/actor/Props.scala @@ -8,6 +8,7 @@ import akka.dispatch._ import akka.japi.Creator import akka.util._ import collection.immutable.Stack +import akka.routing.{ NoRouter, RouterConfig } /** * Factory for Props instances. @@ -26,9 +27,12 @@ object Props { case _: Exception ⇒ Restart case _ ⇒ Escalate } + + final val defaultRoutedProps: RouterConfig = NoRouter + final val defaultFaultHandler: FaultHandlingStrategy = OneForOneStrategy(defaultDecider, None, None) final val noHotSwap: Stack[Actor.Receive] = Stack.empty - final val empty = Props(new Actor { def receive = Actor.emptyBehavior }) + final val empty = new Props(() ⇒ new Actor { def receive = Actor.emptyBehavior }) /** * The default Props instance, uses the settings from the Props object starting with default*. @@ -106,7 +110,8 @@ object Props { case class Props(creator: () ⇒ Actor = Props.defaultCreator, @transient dispatcher: MessageDispatcher = Props.defaultDispatcher, timeout: Timeout = Props.defaultTimeout, - faultHandler: FaultHandlingStrategy = Props.defaultFaultHandler) { + faultHandler: FaultHandlingStrategy = Props.defaultFaultHandler, + routerConfig: RouterConfig = Props.defaultRoutedProps) { /** * No-args constructor that sets all the default values. @@ -134,7 +139,8 @@ case class Props(creator: () ⇒ Actor = Props.defaultCreator, creator = () ⇒ actorClass.newInstance, dispatcher = Props.defaultDispatcher, timeout = Props.defaultTimeout, - faultHandler = Props.defaultFaultHandler) + faultHandler = Props.defaultFaultHandler, + routerConfig = Props.defaultRoutedProps) /** * Returns a new Props with the specified creator set. @@ -171,4 +177,10 @@ case class Props(creator: () ⇒ Actor = Props.defaultCreator, * Java API. */ def withFaultHandler(f: FaultHandlingStrategy) = copy(faultHandler = f) + + /** + * Returns a new Props with the specified router config set + * Java API + */ + def withRouter(r: RouterConfig) = copy(routerConfig = r) } diff --git a/akka-actor/src/main/scala/akka/cluster/ClusterInterface.scala b/akka-actor/src/main/scala/akka/cluster/ClusterInterface.scala deleted file mode 100644 index d88b5c3440..0000000000 --- a/akka-actor/src/main/scala/akka/cluster/ClusterInterface.scala +++ /dev/null @@ -1,576 +0,0 @@ -/** - * Copyright (C) 2009-2010 Typesafe Inc. - */ - -package akka.cluster - -import akka.actor._ -import DeploymentConfig._ -import akka.dispatch.Future -import akka.routing._ -import akka.serialization.Serializer -import akka.cluster.metrics._ -import akka.util.Duration -import akka.util.duration._ -import akka.AkkaException - -import com.eaio.uuid.UUID - -import java.net.InetSocketAddress -import java.util.concurrent.{ ConcurrentSkipListSet } - -class ClusterException(message: String) extends AkkaException(message) - -object ChangeListener { - - /** - * Cluster membership change listener. - * For Scala API. - */ - trait ChangeListener { - def notify(event: ChangeNotification, client: ClusterNode) { - event match { - case NodeConnected(name) ⇒ nodeConnected(name, client) - case NodeDisconnected(name) ⇒ nodeDisconnected(name, client) - case NewLeader(name: String) ⇒ newLeader(name, client) - case NewSession ⇒ thisNodeNewSession(client) - case ThisNode.Connected ⇒ thisNodeConnected(client) - case ThisNode.Disconnected ⇒ thisNodeDisconnected(client) - case ThisNode.Expired ⇒ thisNodeExpired(client) - } - } - - def nodeConnected(node: String, client: ClusterNode) {} - - def nodeDisconnected(node: String, client: ClusterNode) {} - - def newLeader(name: String, client: ClusterNode) {} - - def thisNodeNewSession(client: ClusterNode) {} - - def thisNodeConnected(client: ClusterNode) {} - - def thisNodeDisconnected(client: ClusterNode) {} - - def thisNodeExpired(client: ClusterNode) {} - } - - /** - * Cluster membership change listener. - * For Java API. - */ - abstract class ChangeListenerAdapter extends ChangeListener - - sealed trait ChangeNotification - - case class NodeConnected(node: String) extends ChangeNotification - - case class NodeDisconnected(node: String) extends ChangeNotification - - case class NewLeader(name: String) extends ChangeNotification - - case object NewSession extends ChangeNotification - - object ThisNode { - - case object Connected extends ChangeNotification - - case object Disconnected extends ChangeNotification - - case object Expired extends ChangeNotification - - } -} - -/** - * Node address holds the node name and the cluster name and can be used as a hash lookup key for a Node instance. - */ -class NodeAddress(val clusterName: String, val nodeName: String) { - if ((clusterName eq null) || clusterName == "") throw new NullPointerException("Cluster name must not be null or empty string") - if ((nodeName eq null) || nodeName == "") throw new NullPointerException("Node name must not be null or empty string") - - override def toString = "%s:%s".format(clusterName, nodeName) - - override def hashCode = 0 + clusterName.## + nodeName.## - - override def equals(other: Any) = NodeAddress.unapply(this) == NodeAddress.unapply(other) -} - -/** - * NodeAddress companion object and factory. - */ -object NodeAddress { - def apply(clusterName: String, nodeName: String): NodeAddress = new NodeAddress(clusterName, nodeName) - def apply(system: ActorSystem): NodeAddress = new NodeAddress(system.clustername, system.nodename) - - def unapply(other: Any) = other match { - case address: NodeAddress ⇒ Some((address.clusterName, address.nodeName)) - case _ ⇒ None - } -} - -/* - * Allows user to access metrics of a different nodes in the cluster. Changing metrics can be monitored - * using {@link MetricsAlterationMonitor} - * Metrics of the cluster nodes are distributed through ZooKeeper. For better performance, metrics are - * cached internally, and refreshed from ZooKeeper after an interval - */ -trait NodeMetricsManager { - - /* - * Gets metrics of a local node directly from JMX monitoring beans/Hyperic Sigar - */ - def getLocalMetrics: NodeMetrics - - /* - * Gets metrics of a specified node - * @param nodeName metrics of the node specified by the name will be returned - * @param useCached if true, returns metrics cached in the metrics manager, - * gets metrics directly from ZooKeeper otherwise - */ - def getMetrics(nodeName: String, useCached: Boolean = true): Option[NodeMetrics] - - /* - * Gets cached metrics of all nodes in the cluster - */ - def getAllMetrics: Array[NodeMetrics] - - /* - * Adds monitor that reacts, when specific conditions are satisfied - */ - def addMonitor(monitor: MetricsAlterationMonitor): Unit - - /* - * Removes monitor - */ - def removeMonitor(monitor: MetricsAlterationMonitor): Unit - - /* - * Removes metrics of s specified node from ZooKeeper and metrics manager cache - */ - def removeNodeMetrics(nodeName: String): Unit - - /* - * Sets timeout after which metrics, cached in the metrics manager, will be refreshed from ZooKeeper - */ - def refreshTimeout_=(newValue: Duration): Unit - - /* - * Timeout after which metrics, cached in the metrics manager, will be refreshed from ZooKeeper - */ - def refreshTimeout: Duration - - /* - * Starts metrics manager. When metrics manager is started, it refreshes cache from ZooKeeper - * after refreshTimeout, and invokes plugged monitors - */ - def start(): NodeMetricsManager - - /* - * Stops metrics manager. Stopped metrics manager doesn't refresh cache from ZooKeeper, - * and doesn't invoke plugged monitors - */ - def stop(): Unit - - /* - * If the value is true, metrics manages is started and running. Stopped, otherwise - */ - def isRunning: Boolean - -} - -/** - * Interface for cluster node. - */ -trait ClusterNode { - import ChangeListener._ - - private[cluster] val locallyCachedMembershipNodes = new ConcurrentSkipListSet[String]() - - def membershipNodes: Array[String] - - def nodeAddress: NodeAddress - - def zkServerAddresses: String - - def start() - - def shutdown() - - def isShutdown: Boolean - - def disconnect(): ClusterNode - - def reconnect(): ClusterNode - - def metricsManager: NodeMetricsManager - - /** - * Registers a cluster change listener. - */ - def register(listener: ChangeListener): ClusterNode - - /** - * Returns the name of the current leader. - */ - def leader: String - - /** - * Returns true if 'this' node is the current leader. - */ - def isLeader: Boolean - - /** - * Explicitly resign from being a leader. If this node is not a leader then this operation is a no-op. - */ - def resign() - - /** - * Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store[T <: Actor](actorAddress: String, actorClass: Class[T], serializer: Serializer): ClusterNode - - /** - * Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode - - /** - * Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store[T <: Actor](actorAddress: String, actorClass: Class[T], nrOfInstances: Int, serializer: Serializer): ClusterNode - - /** - * Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store[T <: Actor](actorAddress: String, actorClass: Class[T], nrOfInstances: Int, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode - - /** - * Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store[T <: Actor](actorAddress: String, actorClass: Class[T], serializeMailbox: Boolean, serializer: Serializer): ClusterNode - - /** - * Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store[T <: Actor](actorAddress: String, actorClass: Class[T], replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode - - /** - * Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store[T <: Actor](actorAddress: String, actorClass: Class[T], nrOfInstances: Int, serializeMailbox: Boolean, serializer: Serializer): ClusterNode - - /** - * Clusters an actor of a specific type. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store[T <: Actor](address: String, actorClass: Class[T], nrOfInstances: Int, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode - - /** - * Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store(actorAddress: String, actorFactory: () ⇒ ActorRef, serializer: Serializer): ClusterNode - - /** - * Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store(actorAddress: String, actorFactory: () ⇒ ActorRef, serializeMailbox: Boolean, serializer: Serializer): ClusterNode - - /** - * Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store(actorAddress: String, actorFactory: () ⇒ ActorRef, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode - - /** - * Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store(actorAddress: String, actorFactory: () ⇒ ActorRef, nrOfInstances: Int, serializer: Serializer): ClusterNode - - /** - * Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store(actorAddress: String, actorFactory: () ⇒ ActorRef, nrOfInstances: Int, replicationScheme: ReplicationScheme, serializer: Serializer): ClusterNode - - /** - * Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - - /** - * Needed to have reflection through structural typing work. - */ - def store(actorAddress: String, actorFactory: () ⇒ ActorRef, nrOfInstances: Int, serializeMailbox: Boolean, serializer: AnyRef): ClusterNode - - /** - * Needed to have reflection through structural typing work. - */ - def store(actorAddress: String, actorFactory: () ⇒ ActorRef, nrOfInstances: Int, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: AnyRef): ClusterNode - - /** - * Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store(actorAddress: String, actorFactory: () ⇒ ActorRef, nrOfInstances: Int, serializeMailbox: Boolean, serializer: Serializer): ClusterNode - - /** - * Clusters an actor with UUID. If the actor is already clustered then the clustered version will be updated - * with the actor passed in as argument. You can use this to save off snapshots of the actor to a highly - * available durable store. - */ - def store(actorAddress: String, actorFactory: () ⇒ ActorRef, nrOfInstances: Int, replicationScheme: ReplicationScheme, serializeMailbox: Boolean, serializer: Serializer): ClusterNode - - /** - * Removes actor from the cluster. - */ - // def remove(actorRef: ActorRef) - - /** - * Removes actor with address from the cluster. - */ - // def remove(address: String): ClusterNode - - /** - * Is the actor with uuid clustered or not? - */ - def isClustered(actorAddress: String): Boolean - - /** - * Is the actor with uuid in use on 'this' node or not? - */ - def isInUseOnNode(actorAddress: String): Boolean - - /** - * Is the actor with uuid in use or not? - */ - def isInUseOnNode(actorAddress: String, nodeName: String): Boolean - - /** - * Is the actor with uuid in use or not? - */ - def isInUseOnNode(actorAddress: String, node: NodeAddress): Boolean - - /** - * Checks out an actor for use on this node, e.g. checked out as a 'LocalActorRef' but it makes it available - * for remote access through lookup by its UUID. - */ - def use[T <: Actor](actorAddress: String): Option[LocalActorRef] - - /** - * Using (checking out) actor on a specific set of nodes. - */ - def useActorOnNodes(nodes: Array[String], actorAddress: String, replicateFromUuid: Option[UUID]) - - /** - * Using (checking out) actor on all nodes in the cluster. - */ - def useActorOnAllNodes(actorAddress: String, replicateFromUuid: Option[UUID]) - - /** - * Using (checking out) actor on a specific node. - */ - def useActorOnNode(node: String, actorAddress: String, replicateFromUuid: Option[UUID]) - - /** - * Checks in an actor after done using it on this node. - */ - def release(actorRef: ActorRef) - - /** - * Checks in an actor after done using it on this node. - */ - def release(actorAddress: String) - - /** - * Creates an ActorRef with a Router to a set of clustered actors. - */ - def ref(actorAddress: String, router: RouterType): ActorRef - - /** - * Returns the addresses of all actors checked out on this node. - */ - def addressesForActorsInUse: Array[String] - - /** - * Returns the addresses of all actors registered in this cluster. - */ - def addressesForClusteredActors: Array[String] - - /** - * Returns the addresses of all actors in use registered on a specific node. - */ - def addressesForActorsInUseOnNode(nodeName: String): Array[String] - - /** - * Returns Serializer for actor with UUID. - */ - def serializerForActor(actorAddress: String): Serializer - - /** - * Returns home address for actor with UUID. - */ - def inetSocketAddressesForActor(actorAddress: String): Array[(UUID, InetSocketAddress)] - - /** - * Send a function 'Function0[Unit]' to be invoked on a random number of nodes (defined by 'nrOfInstances' argument). - */ - def send(f: Function0[Unit], nrOfInstances: Int) - - /** - * Send a function 'Function0[Any]' to be invoked on a random number of nodes (defined by 'nrOfInstances' argument). - * Returns an 'Array' with all the 'Future's from the computation. - */ - def send(f: Function0[Any], nrOfInstances: Int): List[Future[Any]] - - /** - * Send a function 'Function1[Any, Unit]' to be invoked on a random number of nodes (defined by 'nrOfInstances' argument) - * with the argument speficied. - */ - def send(f: Function1[Any, Unit], arg: Any, nrOfInstances: Int) - - /** - * Send a function 'Function1[Any, Any]' to be invoked on a random number of nodes (defined by 'nrOfInstances' argument) - * with the argument speficied. - * Returns an 'Array' with all the 'Future's from the computation. - */ - def send(f: Function1[Any, Any], arg: Any, nrOfInstances: Int): List[Future[Any]] - - /** - * Stores a configuration element under a specific key. - * If the key already exists then it will be overwritten. - */ - def setConfigElement(key: String, bytes: Array[Byte]) - - /** - * Returns the config element for the key or NULL if no element exists under the key. - * Returns Some(element) if it exists else None - */ - def getConfigElement(key: String): Option[Array[Byte]] - - /** - * Removes configuration element for a specific key. - * Does nothing if the key does not exist. - */ - def removeConfigElement(key: String) - - /** - * Returns a list with all config element keys. - */ - def getConfigElementKeys: Array[String] - - // =============== PRIVATE METHODS =============== - - // FIXME BAD BAD BAD - considering moving all these private[cluster] methods to a separate trait to get them out of the user's view - - private[cluster] def remoteClientLifeCycleHandler: ActorRef - - private[cluster] def remoteDaemon: ActorRef - - /** - * Removes actor with uuid from the cluster. - */ - // private[cluster] def remove(uuid: UUID) - - /** - * Releases (checking in) all actors with a specific UUID on all nodes in the cluster where the actor is in 'use'. - */ - private[cluster] def releaseActorOnAllNodes(actorAddress: String) - - /** - * Returns the UUIDs of all actors checked out on this node. - */ - private[cluster] def uuidsForActorsInUse: Array[UUID] - - /** - * Returns the UUIDs of all actors registered in this cluster. - */ - private[cluster] def uuidsForClusteredActors: Array[UUID] - - /** - * Returns the actor id for the actor with a specific UUID. - */ - private[cluster] def actorAddressForUuid(uuid: UUID): Option[String] - - /** - * Returns the actor ids for all the actors with a specific UUID. - */ - private[cluster] def actorAddressForUuids(uuids: Array[UUID]): Array[String] - - /** - * Returns the actor UUIDs for actor ID. - */ - private[cluster] def uuidsForActorAddress(actorAddress: String): Array[UUID] - - /** - * Returns the UUIDs of all actors in use registered on a specific node. - */ - private[cluster] def uuidsForActorsInUseOnNode(nodeName: String): Array[UUID] - - private[cluster] def boot() - - private[cluster] def publish(change: ChangeNotification) - - private[cluster] def joinCluster() - - private[cluster] def joinLeaderElection: Boolean - - private[cluster] def failOverClusterActorRefConnections(from: InetSocketAddress, to: InetSocketAddress) - - private[cluster] def migrateActorsOnFailedNodes( - failedNodes: List[String], - currentClusterNodes: List[String], - oldClusterNodes: List[String], - disconnectedConnections: Map[String, InetSocketAddress]) - - private[cluster] def connectToAllNewlyArrivedMembershipNodesInCluster( - newlyConnectedMembershipNodes: Traversable[String], - newlyDisconnectedMembershipNodes: Traversable[String]): Map[String, InetSocketAddress] - - private[cluster] def remoteSocketAddressForNode(node: String): Option[InetSocketAddress] - - private[cluster] def membershipPathFor(node: String): String - private[cluster] def configurationPathFor(key: String): String - - private[cluster] def actorAddressToNodesPathFor(actorAddress: String): String - private[cluster] def actorAddressToNodesPathFor(actorAddress: String, nodeName: String): String - - private[cluster] def nodeToUuidsPathFor(node: String): String - private[cluster] def nodeToUuidsPathFor(node: String, uuid: UUID): String - - private[cluster] def actorAddressRegistryPathFor(actorAddress: String): String - private[cluster] def actorAddressRegistrySerializerPathFor(actorAddress: String): String - private[cluster] def actorAddressRegistryUuidPathFor(actorAddress: String): String - - private[cluster] def actorUuidRegistryPathFor(uuid: UUID): String - private[cluster] def actorUuidRegistryNodePathFor(uuid: UUID): String - private[cluster] def actorUuidRegistryAddressPathFor(uuid: UUID): String - - private[cluster] def actorAddressToUuidsPathFor(actorAddress: String): String -} - diff --git a/akka-actor/src/main/scala/akka/cluster/metrics/MetricsAlterationMonitor.scala b/akka-actor/src/main/scala/akka/cluster/metrics/MetricsAlterationMonitor.scala deleted file mode 100644 index 7f16601e3a..0000000000 --- a/akka-actor/src/main/scala/akka/cluster/metrics/MetricsAlterationMonitor.scala +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (C) 2009-2011 Typesafe Inc. - */ - -package akka.cluster.metrics - -/* - * {@link NodeMetricsManager} periodically refershes internal cache with node metrics from MBeans / Sigar. - * Every time local cache is refreshed, monitors plugged to the metrics manager are invoked. - * If updated metrics satisfy conditions, specified in reactsOn, - * react is called - * - * @exampl {{{ - * class PeakCPULoadMonitor extends LocalMetricsAlterationMonitor { - * val id = "peak-cpu-load-monitor" - * - * def reactsOn(metrics: NodeMetrics) = - * metrics.systemLoadAverage > 0.8 - * - * def react(metrics: NodeMetrics) = - * println("Peak average system load at node [%s] is reached!" format (metrics.nodeName)) - * } - * }}} - * - */ -trait LocalMetricsAlterationMonitor extends MetricsAlterationMonitor { - - /* - * Definies conditions that must be satisfied in order to react on the changed metrics - */ - def reactsOn(metrics: NodeMetrics): Boolean - - /* - * Reacts on the changed metrics - */ - def react(metrics: NodeMetrics): Unit - -} - -/* - * {@link NodeMetricsManager} periodically refershes internal cache with metrics of all nodes in the cluster - * from ZooKeeper. Every time local cache is refreshed, monitors plugged to the metrics manager are invoked. - * If updated metrics satisfy conditions, specified in reactsOn, - * react is called - * - * @exampl {{{ - * class PeakCPULoadReached extends ClusterMetricsAlterationMonitor { - * val id = "peak-cpu-load-reached" - * - * def reactsOn(metrics: Array[NodeMetrics]) = - * metrics.forall(_.systemLoadAverage > 0.8) - * - * def react(metrics: Array[NodeMetrics]) = - * println("One of the nodes in the scluster has reached the peak system load!") - * } - * }}} - * - */ -trait ClusterMetricsAlterationMonitor extends MetricsAlterationMonitor { - - /* - * Definies conditions that must be satisfied in order to react on the changed metrics - */ - def reactsOn(allMetrics: Array[NodeMetrics]): Boolean - - /* - * Reacts on the changed metrics - */ - def react(allMetrics: Array[NodeMetrics]): Unit - -} - -sealed trait MetricsAlterationMonitor extends Comparable[MetricsAlterationMonitor] { - - /* - * Unique identiifier of the monitor - */ - def id: String - - def compareTo(otherMonitor: MetricsAlterationMonitor) = id.compareTo(otherMonitor.id) - -} diff --git a/akka-actor/src/main/scala/akka/cluster/metrics/NodeMetrics.scala b/akka-actor/src/main/scala/akka/cluster/metrics/NodeMetrics.scala deleted file mode 100644 index a292035c6a..0000000000 --- a/akka-actor/src/main/scala/akka/cluster/metrics/NodeMetrics.scala +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (C) 2009-2011 Typesafe Inc. - */ - -package akka.cluster.metrics - -/* - * Snapshot of the JVM / system that's the node is running on - */ -trait NodeMetrics { - - /* - * Name of the node the metrics are gathered at - */ - def nodeName: String - - /* - * Amount of heap memory currently used - */ - def usedHeapMemory: Long - - /* - * Amount of heap memory guaranteed to be available - */ - def committedHeapMemory: Long - - /* - * Maximum amount of heap memory that can be used - */ - def maxHeapMemory: Long - - /* - * Number of the processors avalable to the JVM - */ - def avaiableProcessors: Int - - /* - * If OS-specific Hyperic Sigar library is plugged, it's used to calculate - * average load on the CPUs in the system. Otherwise, value is retreived from monitoring MBeans. - * Hyperic Sigar provides more precise values, and, thus, if the library is provided, it's used by default. - */ - def systemLoadAverage: Double - -} diff --git a/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala b/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala index 3c9143674d..0f6091d23b 100644 --- a/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala +++ b/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala @@ -50,6 +50,7 @@ object SystemMessage { * ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ */ sealed trait SystemMessage extends PossiblyHarmful { + @transient var next: SystemMessage = _ } case class Create() extends SystemMessage // send to self from Dispatcher.register diff --git a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala index 27b8f039d1..fdb46f0ec4 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 RK take this out before release (but please leave in until M2!) + // since this is a compile-time constant, scalac will elide code behind if (Mailbox.debug) (RK checked with 2.9.1) final val debug = false } diff --git a/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala deleted file mode 100644 index 3639f056e8..0000000000 --- a/akka-actor/src/main/scala/akka/remote/RemoteInterface.scala +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Copyright (C) 2009-2010 Typesafe Inc. - */ - -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 -import java.net.UnknownHostException - -object RemoteAddress { - def apply(system: String, host: String, port: Int): RemoteAddress = { - // TODO check whether we should not rather bail out early - val ip = try InetAddress.getByName(host) catch { case _: UnknownHostException ⇒ null } - new RemoteAddress(system, host, ip, port) - } - - val RE = """(?:(\w+)@)?(\w+):(\d+)""".r - object Int { - def unapply(s: String) = Some(Integer.parseInt(s)) - } - def apply(stringRep: String, defaultSystem: String): RemoteAddress = stringRep match { - case RE(sys, host, Int(port)) ⇒ apply(if (sys != null) sys else defaultSystem, host, port) - case _ ⇒ throw new IllegalArgumentException(stringRep + " is not a valid remote address [system@host:port]") - } -} - -case class RemoteAddress(system: String, host: String, ip: InetAddress, port: Int) extends Address { - def protocol = "akka" - @transient - 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" || 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 - } - } -} - -class RemoteException(message: String) extends AkkaException(message) - -trait RemoteModule { - protected[akka] def notifyListeners(message: RemoteLifeCycleEvent): Unit -} - -/** - * Remote life-cycle events. - */ -sealed trait RemoteLifeCycleEvent - -/** - * Life-cycle events for RemoteClient. - */ -trait RemoteClientLifeCycleEvent extends RemoteLifeCycleEvent { - def remoteAddress: RemoteAddress -} - -case class RemoteClientError( - @BeanProperty cause: Throwable, - @BeanProperty remote: RemoteSupport, - @BeanProperty remoteAddress: RemoteAddress) extends RemoteClientLifeCycleEvent - -case class RemoteClientDisconnected( - @BeanProperty remote: RemoteSupport, - @BeanProperty remoteAddress: RemoteAddress) extends RemoteClientLifeCycleEvent - -case class RemoteClientConnected( - @BeanProperty remote: RemoteSupport, - @BeanProperty remoteAddress: RemoteAddress) extends RemoteClientLifeCycleEvent - -case class RemoteClientStarted( - @BeanProperty remote: RemoteSupport, - @BeanProperty remoteAddress: RemoteAddress) extends RemoteClientLifeCycleEvent - -case class RemoteClientShutdown( - @BeanProperty remote: RemoteSupport, - @BeanProperty remoteAddress: RemoteAddress) extends RemoteClientLifeCycleEvent - -case class RemoteClientWriteFailed( - @BeanProperty request: AnyRef, - @BeanProperty cause: Throwable, - @BeanProperty remote: RemoteSupport, - @BeanProperty remoteAddress: RemoteAddress) extends RemoteClientLifeCycleEvent - -/** - * Life-cycle events for RemoteServer. - */ -trait RemoteServerLifeCycleEvent extends RemoteLifeCycleEvent - -case class RemoteServerStarted( - @BeanProperty remote: RemoteSupport) extends RemoteServerLifeCycleEvent -case class RemoteServerShutdown( - @BeanProperty remote: RemoteSupport) extends RemoteServerLifeCycleEvent -case class RemoteServerError( - @BeanProperty val cause: Throwable, - @BeanProperty remote: RemoteSupport) extends RemoteServerLifeCycleEvent -case class RemoteServerClientConnected( - @BeanProperty remote: RemoteSupport, - @BeanProperty val clientAddress: Option[RemoteAddress]) extends RemoteServerLifeCycleEvent -case class RemoteServerClientDisconnected( - @BeanProperty remote: RemoteSupport, - @BeanProperty val clientAddress: Option[RemoteAddress]) extends RemoteServerLifeCycleEvent -case class RemoteServerClientClosed( - @BeanProperty remote: RemoteSupport, - @BeanProperty val clientAddress: Option[RemoteAddress]) extends RemoteServerLifeCycleEvent -case class RemoteServerWriteFailed( - @BeanProperty request: AnyRef, - @BeanProperty cause: Throwable, - @BeanProperty server: RemoteSupport, - @BeanProperty remoteAddress: Option[RemoteAddress]) extends RemoteServerLifeCycleEvent - -/** - * Thrown for example when trying to send a message using a RemoteClient that is either not started or shut down. - */ -class RemoteClientException private[akka] ( - message: String, - @BeanProperty val client: RemoteSupport, - val remoteAddress: RemoteAddress, cause: Throwable = null) extends AkkaException(message, cause) - -/** - * Thrown when the remote server actor dispatching fails for some reason. - */ -class RemoteServerException private[akka] (message: String) extends AkkaException(message) - -/** - * Thrown when a remote exception sent over the wire cannot be loaded and instantiated - */ -case class CannotInstantiateRemoteExceptionDueToRemoteProtocolParsingErrorException private[akka] (cause: Throwable, originalClassName: String, originalMessage: String) - extends AkkaException("\nParsingError[%s]\nOriginalException[%s]\nOriginalMessage[%s]" - .format(cause.toString, originalClassName, originalMessage)) { - override def printStackTrace = cause.printStackTrace - override def printStackTrace(printStream: PrintStream) = cause.printStackTrace(printStream) - override def printStackTrace(printWriter: PrintWriter) = cause.printStackTrace(printWriter) -} - -abstract class RemoteSupport(val system: ActorSystem) { - /** - * Shuts down the remoting - */ - def shutdown(): Unit - - /** - * Gets the name of the server instance - */ - def name: String - - /** - * Starts up the remoting - */ - def start(loader: Option[ClassLoader]): Unit - - /** - * Shuts down a specific client connected to the supplied remote address returns true if successful - */ - def shutdownClientConnection(address: RemoteAddress): Boolean - - /** - * Restarts a specific client connected to the supplied remote address, but only if the client is not shut down - */ - def restartClientConnection(address: RemoteAddress): Boolean - - /** Methods that needs to be implemented by a transport **/ - - protected[akka] def send(message: Any, - senderOption: Option[ActorRef], - remoteAddress: RemoteAddress, - recipient: ActorRef, - loader: Option[ClassLoader]): Unit - - protected[akka] def notifyListeners(message: RemoteLifeCycleEvent): Unit = system.eventStream.publish(message) - - override def toString = name -} diff --git a/akka-actor/src/main/scala/akka/routing/ConnectionManager.scala b/akka-actor/src/main/scala/akka/routing/ConnectionManager.scala index 572bd986ee..a417c75bac 100644 --- a/akka-actor/src/main/scala/akka/routing/ConnectionManager.scala +++ b/akka-actor/src/main/scala/akka/routing/ConnectionManager.scala @@ -9,8 +9,6 @@ import akka.actor._ import scala.annotation.tailrec import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger } -import java.net.InetSocketAddress -import akka.remote.RemoteAddress import collection.JavaConverters /** @@ -67,16 +65,6 @@ trait ConnectionManager { * @param ref the dead */ def remove(deadRef: ActorRef) - - /** - * Creates a new connection (ActorRef) if it didn't exist. Atomically. - */ - def putIfAbsent(address: RemoteAddress, newConnectionFactory: () ⇒ ActorRef): ActorRef - - /** - * Fails over connections from one address to another. - */ - def failOver(from: RemoteAddress, to: RemoteAddress) } /** @@ -123,10 +111,4 @@ class LocalConnectionManager(initialConnections: Iterable[ActorRef]) extends Con if (!state.compareAndSet(oldState, newState)) remove(ref) } } - - def failOver(from: RemoteAddress, to: RemoteAddress) {} // do nothing here - - def putIfAbsent(address: RemoteAddress, newConnectionFactory: () ⇒ ActorRef): ActorRef = { - throw new UnsupportedOperationException("Not supported") - } } diff --git a/akka-actor/src/main/scala/akka/routing/Routing.scala b/akka-actor/src/main/scala/akka/routing/Routing.scala index a71f206959..5fb34d3de3 100644 --- a/akka-actor/src/main/scala/akka/routing/Routing.scala +++ b/akka-actor/src/main/scala/akka/routing/Routing.scala @@ -1,470 +1,367 @@ /** * Copyright (C) 2009-2011 Typesafe Inc. */ - package akka.routing -import akka.AkkaException import akka.actor._ -import akka.config.ConfigurationException -import akka.dispatch.{ Future, MessageDispatcher } -import akka.util.{ ReflectiveAccess, Duration } -import java.lang.reflect.InvocationTargetException -import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger } -import scala.annotation.tailrec import akka.japi.Creator - -sealed trait RouterType +import java.lang.reflect.InvocationTargetException +import akka.config.ConfigurationException +import java.util.concurrent.atomic.AtomicInteger +import akka.util.ReflectiveAccess +import akka.AkkaException +import scala.collection.JavaConversions._ +import java.util.concurrent.TimeUnit /** - * Used for declarative configuration of Routing. + * 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. */ -object RouterType { - - object Direct extends RouterType - - /** - * A RouterType that randomly selects a connection to send a message to. - */ - object Random extends RouterType - - /** - * A RouterType that selects the connection by using round robin. - */ - object RoundRobin extends RouterType - - /** - * A RouterType that selects the connection by using scatter gather. - */ - object ScatterGather extends RouterType - - /** - * A RouterType that broadcasts the messages to all connections. - */ - object Broadcast extends RouterType - - /** - * A RouterType that selects the connection based on the least amount of cpu usage - */ - object LeastCPU extends RouterType - - /** - * A RouterType that select the connection based on the least amount of ram used. - */ - object LeastRAM extends RouterType - - /** - * A RouterType that select the connection where the actor has the least amount of messages in its mailbox. - */ - object LeastMessages extends RouterType - - /** - * A user-defined custom RouterType. - */ - case class Custom(implClass: String) extends RouterType -} - -/** - * Contains the configuration to create local and clustered routed actor references. - * Routed ActorRef configuration object, this is thread safe and fully sharable. - */ -case class RoutedProps private[akka] ( - routerFactory: () ⇒ Router, - connectionManager: ConnectionManager, - timeout: Timeout = RoutedProps.defaultTimeout, - localOnly: Boolean = RoutedProps.defaultLocalOnly) { - - // Java API - def this(creator: Creator[Router], connectionManager: ConnectionManager, timeout: Timeout, localOnly: Boolean) { - this(() ⇒ creator.create(), connectionManager, timeout, localOnly) - } - -} - -object RoutedProps { - final val defaultTimeout = Timeout(Duration.MinusInf) - final val defaultLocalOnly = false -} - -/** - * The Router is responsible for sending a message to one (or more) of its connections. Connections are stored in the - * {@link FailureDetector} and each Router should be linked to only one {@link FailureDetector}. - */ -trait Router { - - /** - * Initializes this Router with a given set of Connections. The Router can use this datastructure to ask for - * the current connections, signal that there were problems with one of the connections and see if there have - * been changes in the connections. - * - * This method is not threadsafe, and should only be called once - * - * JMM Guarantees: - * This method guarantees that all changes made in this method, are visible before one of the routing methods is called. - */ - def init(connectionManager: ConnectionManager) - - /** - * Routes the message to one of the connections. - * - * @throws RoutingException if something goes wrong while routing the message - */ - def route(message: Any)(implicit sender: ActorRef) - - /** - * Routes the message using a timeout to one of the connections and returns a Future to synchronize on the - * completion of the processing of the message. - * - * @throws RoutingExceptionif something goes wrong while routing the message. - */ - def route[T](message: Any, timeout: Timeout): Future[T] -} - -/** - * An {@link AkkaException} thrown when something goes wrong while routing a message - */ -class RoutingException(message: String) extends AkkaException(message) - -/** - * A Helper class to create actor references that use routing. - */ -object Routing { - - sealed trait RoutingMessage - - /** - * Used to broadcast a message to all connections in a router. E.g. every connection gets the message - * regardless of their routing algorithm. - */ - case class Broadcast(message: Any) extends RoutingMessage - - def createCustomRouter(implClass: String): Router = { - ReflectiveAccess.createInstance( - implClass, - Array[Class[_]](), - Array[AnyRef]()) match { - case Right(router) ⇒ router.asInstanceOf[Router] - case Left(exception) ⇒ - val cause = exception match { - case i: InvocationTargetException ⇒ i.getTargetException - case _ ⇒ exception - } - throw new ConfigurationException( - "Could not instantiate custom Router of [" + - implClass + "] due to: " + - cause, cause) - } - } -} - -/** - * An Abstract convenience implementation for building an ActorReference that uses a Router. - */ -abstract private[akka] class AbstractRoutedActorRef(val system: ActorSystem, val props: RoutedProps) extends MinimalActorRef { - val router = props.routerFactory() - - override def !(message: Any)(implicit sender: ActorRef = null): Unit = router.route(message)(sender) - - override def ?(message: Any)(implicit timeout: Timeout): Future[Any] = router.route(message, timeout) -} - -/** - * 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: InternalActorRef, name: String) extends AbstractRoutedActorRef(system, routedProps) { - - val path = supervisor.path / name +private[akka] class RoutedActorRef(_system: ActorSystemImpl, _props: Props, _supervisor: InternalActorRef, _path: ActorPath) + extends LocalActorRef( + _system, + _props.copy(creator = () ⇒ _props.routerConfig.createActor()), + _supervisor, + _path) { @volatile - private var running: Boolean = true + private[akka] var _routees: Vector[ActorRef] = _ // this MUST be initialized during createRoute + def routees = _routees - override def isTerminated: Boolean = !running + val route = _props.routerConfig.createRoute(_props.copy(routerConfig = NoRouter), actorContext, this) - override def stop() { - synchronized { - if (running) { - running = false - router.route(Routing.Broadcast(PoisonPill))(this) - supervisor.sendSystemMessage(akka.dispatch.ChildTerminated(this)) - } + def applyRoute(sender: ActorRef, message: Any): Iterable[Destination] = message match { + case _: AutoReceivedMessage ⇒ Nil + case Terminated(_) ⇒ Nil + case _ ⇒ + if (route.isDefinedAt(sender, message)) route(sender, message) + else Nil + } + + _routees match { + case null ⇒ throw new ActorInitializationException("router " + _props.routerConfig + " did not register routees!") + case x ⇒ + _routees = x // volatile write to publish the route before sending messages + // subscribe to Terminated messages for all route destinations, to be handled by Router actor + _routees foreach underlying.watch + } + + override def !(message: Any)(implicit sender: ActorRef = null): Unit = { + val s = if (sender eq null) underlying.system.deadLetters else sender + + val msg = message match { + case Broadcast(m) ⇒ m + case m ⇒ m + } + + applyRoute(s, message) match { + case Nil ⇒ super.!(message)(s) + case refs ⇒ refs foreach (p ⇒ p.recipient.!(msg)(p.sender)) + } + } +} + +/** + * This trait represents a router factory: it produces the actual router actor + * and creates the routing table (a function which determines the recipients + * for each message which is to be dispatched). The resulting RoutedActorRef + * optimizes the sending of the message so that it does NOT go through the + * router’s mailbox unless the route returns an empty recipient set. + * + * '''Caution:''' This means + * that the route function is evaluated concurrently without protection by + * the RoutedActorRef: either provide a reentrant (i.e. pure) implementation or + * do the locking yourself! + * + * '''Caution:''' Please note that the [[akka.routing.Router]] which needs to + * be returned by `apply()` should not send a message to itself in its + * constructor or `preStart()` or publish its self reference from there: if + * someone tries sending a message to that reference before the constructor of + * RoutedActorRef has returned, there will be a `NullPointerException`! + */ +trait RouterConfig { + + def nrOfInstances: Int + + def targets: Iterable[String] + + def createRoute(props: Props, actorContext: ActorContext, ref: RoutedActorRef): Route + + def createActor(): Router = new Router {} + + def adaptFromDeploy(deploy: Option[Deploy]): RouterConfig = { + deploy match { + case Some(Deploy(_, _, _, NoRouter, _)) ⇒ this + case Some(Deploy(_, _, _, r, _)) ⇒ r + case _ ⇒ this } } - router.init(routedProps.connectionManager) + protected def toAll(sender: ActorRef, targets: Iterable[ActorRef]): Iterable[Destination] = targets.map(Destination(sender, _)) + + protected def createRoutees(props: Props, context: ActorContext, nrOfInstances: Int, targets: Iterable[String]): Vector[ActorRef] = (nrOfInstances, targets) match { + case (0, Nil) ⇒ throw new IllegalArgumentException("Insufficient information - missing configuration.") + case (x, Nil) ⇒ (1 to x).map(_ ⇒ context.actorOf(props))(scala.collection.breakOut) + case (_, xs) ⇒ xs.map(context.actorFor(_))(scala.collection.breakOut) + } + + protected def createAndRegisterRoutees(props: Props, context: ActorContext, nrOfInstances: Int, targets: Iterable[String]): Unit = { + val routees = createRoutees(props, context, nrOfInstances, targets) + registerRoutees(context, routees) + } + + protected def registerRoutees(context: ActorContext, routees: Vector[ActorRef]): Unit = { + context.self.asInstanceOf[RoutedActorRef]._routees = routees + } } /** - * An Abstract Router implementation that already provides the basic infrastructure so that a concrete - * Router only needs to implement the next method. + * Base trait for `Router` actors. Override `receive` to handle custom + * messages which the corresponding [[akka.actor.RouterConfig]] lets + * through by returning an empty route. */ -trait BasicRouter extends Router { +trait Router extends Actor { - @volatile - protected var connectionManager: ConnectionManager = _ - - def init(connectionManager: ConnectionManager) = { - this.connectionManager = connectionManager + val ref = self match { + case x: RoutedActorRef ⇒ x + case _ ⇒ throw new ActorInitializationException("Router actor can only be used in RoutedActorRef") } - def route(message: Any)(implicit sender: ActorRef) = message match { - case Routing.Broadcast(message) ⇒ + final def receive = ({ - //it is a broadcast message, we are going to send to message to all connections. - connectionManager.connections.iterable foreach { connection ⇒ - try { - connection.!(message)(sender) // we use original sender, so this is essentially a 'forward' - } catch { - case e: Exception ⇒ - connectionManager.remove(connection) - throw e + case Terminated(child) ⇒ + ref._routees = ref._routees filterNot (_ == child) + if (ref.routees.isEmpty) self.stop() + + }: Receive) orElse routerReceive + + def routerReceive: Receive = { + case _ ⇒ + } +} + +/** + * Used to broadcast a message to all connections in a router; only the + * contained message will be forwarded, i.e. the `Broadcast(...)` + * envelope will be stripped off. + * + * Router implementations may choose to handle this message differently. + */ +case class Broadcast(message: Any) + +/** + * Routing configuration that indicates no routing. + * Oxymoron style. + */ +case object NoRouter extends RouterConfig { + def nrOfInstances: Int = 0 + def targets: Iterable[String] = Nil + def createRoute(props: Props, actorContext: ActorContext, ref: RoutedActorRef): Route = null +} + +object RoundRobinRouter { + def apply(targets: Iterable[ActorRef]) = new RoundRobinRouter(targets = targets map (_.path.toString)) +} +/** + * A Router that uses round-robin to select a connection. For concurrent calls, round robin is just a best effort. + *
+ * Please note that providing both 'nrOfInstances' and 'targets' does not make logical sense as this means + * that the round robin should both create new actors and use the 'targets' actor(s). + * In this case the 'nrOfInstances' will be ignored and the 'targets' will be used. + *
+ * The configuration parameter trumps the constructor arguments. This means that + * if you provide either 'nrOfInstances' or 'targets' to during instantiation they will + * be ignored if the 'nrOfInstances' is defined in the configuration file for the actor being used. + */ +case class RoundRobinRouter(nrOfInstances: Int = 0, targets: Iterable[String] = Nil) extends RouterConfig with RoundRobinLike { + + /** + * Constructor that sets nrOfInstances to be created. + * Java API + */ + def this(nr: Int) = { + this(nrOfInstances = nr) + } + + /** + * Constructor that sets the targets to be used. + * Java API + */ + def this(t: java.util.Collection[String]) = { + this(targets = collectionAsScalaIterable(t)) + } +} + +trait RoundRobinLike { this: RouterConfig ⇒ + def createRoute(props: Props, context: ActorContext, ref: RoutedActorRef): Route = { + createAndRegisterRoutees(props, context, nrOfInstances, targets) + + val next = new AtomicInteger(0) + + def getNext(): ActorRef = { + ref.routees(next.getAndIncrement % ref.routees.size) + } + + { + case (sender, message) ⇒ + message match { + case Broadcast(msg) ⇒ toAll(sender, ref.routees) + case msg ⇒ List(Destination(sender, getNext())) } - } - case _ ⇒ - //it no broadcast message, we are going to select an actor from the connections and send the message to him. - next match { - case Some(connection) ⇒ - try { - connection.!(message)(sender) // we use original sender, so this is essentially a 'forward' - } catch { - case e: Exception ⇒ - connectionManager.remove(connection) - throw e - } - case None ⇒ - throwNoConnectionsError - } - } - - def route[T](message: Any, timeout: Timeout): Future[T] = message match { - case Routing.Broadcast(message) ⇒ - throw new RoutingException("Broadcasting using '?'/'ask' is for the time being is not supported. Use ScatterGatherRouter.") - case _ ⇒ - //it no broadcast message, we are going to select an actor from the connections and send the message to him. - next match { - case Some(connection) ⇒ - try { - connection.?(message, timeout).asInstanceOf[Future[T]] //FIXME this does not preserve the original sender, shouldn't it?? - } catch { - case e: Exception ⇒ - connectionManager.remove(connection) - throw e - } - case None ⇒ - throwNoConnectionsError - } - } - - protected def next: Option[ActorRef] - - private def throwNoConnectionsError = throw new RoutingException("No replica connections for router") -} - -/** - * A Router that uses broadcasts a message to all its connections. - */ -class BroadcastRouter(implicit val dispatcher: MessageDispatcher, timeout: Timeout) extends BasicRouter with Serializable { - override def route(message: Any)(implicit sender: ActorRef) = { - connectionManager.connections.iterable foreach { connection ⇒ - try { - connection.!(message)(sender) // we use original sender, so this is essentially a 'forward' - } catch { - case e: Exception ⇒ - connectionManager.remove(connection) - throw e - } } } - - //protected def gather[S, G >: S](results: Iterable[Future[S]]): Future[G] = - override def route[T](message: Any, timeout: Timeout): Future[T] = { - import Future._ - implicit val t = timeout - val futures = connectionManager.connections.iterable map { connection ⇒ - connection.?(message, timeout).asInstanceOf[Future[T]] - } - Future.firstCompletedOf(futures) - } - - protected def next: Option[ActorRef] = None } -/** - * A DirectRouter a Router that only has a single connected actorRef and forwards all request to that actorRef. - */ -class DirectRouter(implicit val dispatcher: MessageDispatcher, timeout: Timeout) extends BasicRouter { - - private val state = new AtomicReference[DirectRouterState] - - lazy val next: Option[ActorRef] = { - val current = currentState - if (current.ref == null) None else Some(current.ref) - } - - @tailrec - private def currentState: DirectRouterState = { - val current = state.get - - if (current != null && connectionManager.version == current.version) { - //we are lucky since nothing has changed in the connections. - current - } else { - //there has been a change in the connections, or this is the first time this method is called. So we are going to do some updating. - - val connections = connectionManager.connections - - val connectionCount = connections.iterable.size - if (connectionCount > 1) - throw new RoutingException("A DirectRouter can't have more than 1 connected Actor, but found [%s]".format(connectionCount)) - - val newState = new DirectRouterState(connections.iterable.head, connections.version) - if (state.compareAndSet(current, newState)) - //we are lucky since we just updated the state, so we can send it back as the state to use - newState - else //we failed to update the state, lets try again... better luck next time. - currentState // recur - } - } - - private case class DirectRouterState(ref: ActorRef, version: Long) +object RandomRouter { + def apply(targets: Iterable[ActorRef]) = new RandomRouter(targets = targets map (_.path.toString)) } - /** * A Router that randomly selects one of the target connections to send a message to. + *
+ * Please note that providing both 'nrOfInstances' and 'targets' does not make logical sense as this means + * that the random router should both create new actors and use the 'targets' actor(s). + * In this case the 'nrOfInstances' will be ignored and the 'targets' will be used. + *
+ * The configuration parameter trumps the constructor arguments. This means that + * if you provide either 'nrOfInstances' or 'targets' to during instantiation they will + * be ignored if the 'nrOfInstances' is defined in the configuration file for the actor being used. */ -class RandomRouter(implicit val dispatcher: MessageDispatcher, timeout: Timeout) extends BasicRouter { - import java.security.SecureRandom +case class RandomRouter(nrOfInstances: Int = 0, targets: Iterable[String] = Nil) extends RouterConfig with RandomLike { - private val state = new AtomicReference[RandomRouterState] + /** + * Constructor that sets nrOfInstances to be created. + * Java API + */ + def this(nr: Int) = { + this(nrOfInstances = nr) + } + + /** + * Constructor that sets the targets to be used. + * Java API + */ + def this(t: java.util.Collection[String]) = { + this(targets = collectionAsScalaIterable(t)) + } +} + +trait RandomLike { this: RouterConfig ⇒ + + import java.security.SecureRandom private val random = new ThreadLocal[SecureRandom] { override def initialValue = SecureRandom.getInstance("SHA1PRNG") } - def next: Option[ActorRef] = currentState.array match { - case a if a.isEmpty ⇒ None - case a ⇒ Some(a(random.get.nextInt(a.length))) - } + def createRoute(props: Props, context: ActorContext, ref: RoutedActorRef): Route = { + createAndRegisterRoutees(props, context, nrOfInstances, targets) - @tailrec - private def currentState: RandomRouterState = { - val current = state.get - - if (current != null && current.version == connectionManager.version) { - //we are lucky, since there has not been any change in the connections. So therefor we can use the existing state. - current - } else { - //there has been a change in connections, or it was the first try, so we need to update the internal state - - val connections = connectionManager.connections - val newState = new RandomRouterState(connections.iterable.toIndexedSeq, connections.version) - if (state.compareAndSet(current, newState)) - //we are lucky since we just updated the state, so we can send it back as the state to use - newState - else //we failed to update the state, lets try again... better luck next time. - currentState + def getNext(): ActorRef = { + ref.routees(random.get.nextInt(ref.routees.size)) } - } - private case class RandomRouterState(array: IndexedSeq[ActorRef], version: Long) -} - -/** - * A Router that uses round-robin to select a connection. For concurrent calls, round robin is just a best effort. - */ -class RoundRobinRouter(implicit val dispatcher: MessageDispatcher, timeout: Timeout) extends BasicRouter { - - private val state = new AtomicReference[RoundRobinState] - - def next: Option[ActorRef] = currentState.next - - @tailrec - private def currentState: RoundRobinState = { - val current = state.get - - if (current != null && current.version == connectionManager.version) { - //we are lucky, since there has not been any change in the connections. So therefor we can use the existing state. - current - } else { - //there has been a change in connections, or it was the first try, so we need to update the internal state - - val connections = connectionManager.connections - val newState = new RoundRobinState(connections.iterable.toIndexedSeq[ActorRef], connections.version) - if (state.compareAndSet(current, newState)) - //we are lucky since we just updated the state, so we can send it back as the state to use - newState - else //we failed to update the state, lets try again... better luck next time. - currentState - } - } - - private case class RoundRobinState(array: IndexedSeq[ActorRef], version: Long) { - - private val index = new AtomicInteger(0) - - def next: Option[ActorRef] = if (array.isEmpty) None else Some(array(nextIndex)) - - @tailrec - private def nextIndex: Int = { - val oldIndex = index.get - var newIndex = if (oldIndex == array.length - 1) 0 else oldIndex + 1 - - if (!index.compareAndSet(oldIndex, newIndex)) nextIndex - else oldIndex + { + case (sender, message) ⇒ + message match { + case Broadcast(msg) ⇒ toAll(sender, ref.routees) + case msg ⇒ List(Destination(sender, getNext())) + } } } } +object BroadcastRouter { + def apply(targets: Iterable[ActorRef]) = new BroadcastRouter(targets = targets map (_.path.toString)) +} /** - * ScatterGatherRouter broadcasts the message to all connections and gathers results according to the - * specified strategy (specific router needs to implement `gather` method). - * Scatter-gather pattern will be applied only to the messages broadcasted using Future - * (wrapped into {@link Routing.Broadcast} and sent with "?" method). For the messages, sent in a fire-forget - * mode, the router would behave as {@link BasicRouter}, unless it's mixed in with other router type - * - * FIXME: This also is the location where a failover is done in the future if an ActorRef fails and a different one needs to be selected. - * FIXME: this is also the location where message buffering should be done in case of failure. + * A Router that uses broadcasts a message to all its connections. + *
+ * Please note that providing both 'nrOfInstances' and 'targets' does not make logical sense as this means + * that the random router should both create new actors and use the 'targets' actor(s). + * In this case the 'nrOfInstances' will be ignored and the 'targets' will be used. + *
+ * The configuration parameter trumps the constructor arguments. This means that + * if you provide either 'nrOfInstances' or 'targets' to during instantiation they will + * be ignored if the 'nrOfInstances' is defined in the configuration file for the actor being used. */ -trait ScatterGatherRouter extends BasicRouter with Serializable { +case class BroadcastRouter(nrOfInstances: Int = 0, targets: Iterable[String] = Nil) extends RouterConfig with BroadcastLike { /** - * Aggregates the responses into a single Future. - * - * @param results Futures of the responses from connections + * Constructor that sets nrOfInstances to be created. + * Java API */ - protected def gather[S, G >: S](results: Iterable[Future[S]]): Future[G] + def this(nr: Int) = { + this(nrOfInstances = nr) + } - private def scatterGather[S, G >: S](message: Any, timeout: Timeout): Future[G] = { - val responses = connectionManager.connections.iterable.flatMap { actor ⇒ - try { - if (actor.isTerminated) throw ActorInitializationException(actor, "For compatability - check death first", new Exception) // for stack trace - Some(actor.?(message, timeout).asInstanceOf[Future[S]]) - } catch { - case e: Exception ⇒ - connectionManager.remove(actor) - None - } + /** + * Constructor that sets the targets to be used. + * Java API + */ + def this(t: java.util.Collection[String]) = { + this(targets = collectionAsScalaIterable(t)) + } +} + +trait BroadcastLike { this: RouterConfig ⇒ + def createRoute(props: Props, context: ActorContext, ref: RoutedActorRef): Route = { + createAndRegisterRoutees(props, context, nrOfInstances, targets) + + { + case (sender, message) ⇒ + message match { + case _ ⇒ toAll(sender, ref.routees) + } } - - if (responses.isEmpty) - throw new RoutingException("No connections can process the message [%s] sent to scatter-gather router" format (message)) - else gather(responses) - } - - override def route[T](message: Any, timeout: Timeout): Future[T] = message match { - case Routing.Broadcast(message) ⇒ scatterGather(message, timeout) - case message ⇒ super.route(message, timeout) } } +object ScatterGatherFirstCompletedRouter { + def apply(targets: Iterable[ActorRef]) = new ScatterGatherFirstCompletedRouter(targets = targets map (_.path.toString)) +} /** - * Simple router that broadcasts the message to all connections, and replies with the first response - * Scatter-gather pattern will be applied only to the messages broadcasted using Future - * (wrapped into {@link Routing.Broadcast} and sent with "?" method). For the messages sent in a fire-forget - * mode, the router would behave as {@link RoundRobinRouter} + * Simple router that broadcasts the message to all routees, and replies with the first response. + *
+ * Please note that providing both 'nrOfInstances' and 'targets' does not make logical sense as this means + * that the random router should both create new actors and use the 'targets' actor(s). + * In this case the 'nrOfInstances' will be ignored and the 'targets' will be used. + *
+ * The configuration parameter trumps the constructor arguments. This means that + * if you provide either 'nrOfInstances' or 'targets' to during instantiation they will + * be ignored if the 'nrOfInstances' is defined in the configuration file for the actor being used. */ -class ScatterGatherFirstCompletedRouter(implicit dispatcher: MessageDispatcher, timeout: Timeout) extends RoundRobinRouter with ScatterGatherRouter { +case class ScatterGatherFirstCompletedRouter(nrOfInstances: Int = 0, targets: Iterable[String] = Nil) + extends RouterConfig with ScatterGatherFirstCompletedLike { - protected def gather[S, G >: S](results: Iterable[Future[S]]): Future[G] = Future.firstCompletedOf(results) + /** + * Constructor that sets nrOfInstances to be created. + * Java API + */ + def this(nr: Int) = { + this(nrOfInstances = nr) + } + + /** + * Constructor that sets the targets to be used. + * Java API + */ + def this(t: java.util.Collection[String]) = { + this(targets = collectionAsScalaIterable(t)) + } +} + +trait ScatterGatherFirstCompletedLike { this: RouterConfig ⇒ + def createRoute(props: Props, context: ActorContext, ref: RoutedActorRef): Route = { + createAndRegisterRoutees(props, context, nrOfInstances, targets) + + { + case (sender, message) ⇒ + val asker = context.asInstanceOf[ActorCell].systemImpl.provider.ask(Timeout(5, TimeUnit.SECONDS)).get // FIXME, NO REALLY FIXME! + asker.result.pipeTo(sender) + message match { + case _ ⇒ toAll(asker, ref.routees) + } + } + } } diff --git a/akka-actor/src/main/scala/akka/routing/package.scala b/akka-actor/src/main/scala/akka/routing/package.scala new file mode 100644 index 0000000000..64b19f194c --- /dev/null +++ b/akka-actor/src/main/scala/akka/routing/package.scala @@ -0,0 +1,15 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ + +package akka + +import akka.actor.ActorRef + +package object routing { + + case class Destination(sender: ActorRef, recipient: ActorRef) + + type Route = PartialFunction[(ActorRef, Any), Iterable[Destination]] + +} \ No newline at end of file diff --git a/akka-remote/src/main/java/akka/remote/RemoteProtocol.java b/akka-remote/src/main/java/akka/remote/RemoteProtocol.java index 7f10eb6987..0fab0a33f3 100644 --- a/akka-remote/src/main/java/akka/remote/RemoteProtocol.java +++ b/akka-remote/src/main/java/akka/remote/RemoteProtocol.java @@ -922,16 +922,11 @@ public final class RemoteProtocol { akka.remote.RemoteProtocol.ActorRefProtocol getRecipient(); akka.remote.RemoteProtocol.ActorRefProtocolOrBuilder getRecipientOrBuilder(); - // optional .MessageProtocol message = 2; + // required .MessageProtocol message = 2; boolean hasMessage(); akka.remote.RemoteProtocol.MessageProtocol getMessage(); akka.remote.RemoteProtocol.MessageProtocolOrBuilder getMessageOrBuilder(); - // optional .ExceptionProtocol exception = 3; - boolean hasException(); - akka.remote.RemoteProtocol.ExceptionProtocol getException(); - akka.remote.RemoteProtocol.ExceptionProtocolOrBuilder getExceptionOrBuilder(); - // optional .ActorRefProtocol sender = 4; boolean hasSender(); akka.remote.RemoteProtocol.ActorRefProtocol getSender(); @@ -989,7 +984,7 @@ public final class RemoteProtocol { return recipient_; } - // optional .MessageProtocol message = 2; + // required .MessageProtocol message = 2; public static final int MESSAGE_FIELD_NUMBER = 2; private akka.remote.RemoteProtocol.MessageProtocol message_; public boolean hasMessage() { @@ -1002,24 +997,11 @@ public final class RemoteProtocol { return message_; } - // optional .ExceptionProtocol exception = 3; - public static final int EXCEPTION_FIELD_NUMBER = 3; - private akka.remote.RemoteProtocol.ExceptionProtocol exception_; - public boolean hasException() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - public akka.remote.RemoteProtocol.ExceptionProtocol getException() { - return exception_; - } - public akka.remote.RemoteProtocol.ExceptionProtocolOrBuilder getExceptionOrBuilder() { - return exception_; - } - // optional .ActorRefProtocol sender = 4; public static final int SENDER_FIELD_NUMBER = 4; private akka.remote.RemoteProtocol.ActorRefProtocol sender_; public boolean hasSender() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000004) == 0x00000004); } public akka.remote.RemoteProtocol.ActorRefProtocol getSender() { return sender_; @@ -1052,7 +1034,6 @@ public final class RemoteProtocol { private void initFields() { recipient_ = akka.remote.RemoteProtocol.ActorRefProtocol.getDefaultInstance(); message_ = akka.remote.RemoteProtocol.MessageProtocol.getDefaultInstance(); - exception_ = akka.remote.RemoteProtocol.ExceptionProtocol.getDefaultInstance(); sender_ = akka.remote.RemoteProtocol.ActorRefProtocol.getDefaultInstance(); metadata_ = java.util.Collections.emptyList(); } @@ -1065,21 +1046,17 @@ public final class RemoteProtocol { memoizedIsInitialized = 0; return false; } + if (!hasMessage()) { + memoizedIsInitialized = 0; + return false; + } if (!getRecipient().isInitialized()) { memoizedIsInitialized = 0; return false; } - if (hasMessage()) { - if (!getMessage().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - if (hasException()) { - if (!getException().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } + if (!getMessage().isInitialized()) { + memoizedIsInitialized = 0; + return false; } if (hasSender()) { if (!getSender().isInitialized()) { @@ -1107,9 +1084,6 @@ public final class RemoteProtocol { output.writeMessage(2, message_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeMessage(3, exception_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { output.writeMessage(4, sender_); } for (int i = 0; i < metadata_.size(); i++) { @@ -1133,10 +1107,6 @@ public final class RemoteProtocol { .computeMessageSize(2, message_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(3, exception_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(4, sender_); } @@ -1262,7 +1232,6 @@ public final class RemoteProtocol { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { getRecipientFieldBuilder(); getMessageFieldBuilder(); - getExceptionFieldBuilder(); getSenderFieldBuilder(); getMetadataFieldBuilder(); } @@ -1285,21 +1254,15 @@ public final class RemoteProtocol { messageBuilder_.clear(); } bitField0_ = (bitField0_ & ~0x00000002); - if (exceptionBuilder_ == null) { - exception_ = akka.remote.RemoteProtocol.ExceptionProtocol.getDefaultInstance(); - } else { - exceptionBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000004); if (senderBuilder_ == null) { sender_ = akka.remote.RemoteProtocol.ActorRefProtocol.getDefaultInstance(); } else { senderBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000004); if (metadataBuilder_ == null) { metadata_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000008); } else { metadataBuilder_.clear(); } @@ -1360,23 +1323,15 @@ public final class RemoteProtocol { if (((from_bitField0_ & 0x00000004) == 0x00000004)) { to_bitField0_ |= 0x00000004; } - if (exceptionBuilder_ == null) { - result.exception_ = exception_; - } else { - result.exception_ = exceptionBuilder_.build(); - } - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } if (senderBuilder_ == null) { result.sender_ = sender_; } else { result.sender_ = senderBuilder_.build(); } if (metadataBuilder_ == null) { - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { metadata_ = java.util.Collections.unmodifiableList(metadata_); - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000008); } result.metadata_ = metadata_; } else { @@ -1404,9 +1359,6 @@ public final class RemoteProtocol { if (other.hasMessage()) { mergeMessage(other.getMessage()); } - if (other.hasException()) { - mergeException(other.getException()); - } if (other.hasSender()) { mergeSender(other.getSender()); } @@ -1414,7 +1366,7 @@ public final class RemoteProtocol { if (!other.metadata_.isEmpty()) { if (metadata_.isEmpty()) { metadata_ = other.metadata_; - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000008); } else { ensureMetadataIsMutable(); metadata_.addAll(other.metadata_); @@ -1427,7 +1379,7 @@ public final class RemoteProtocol { metadataBuilder_.dispose(); metadataBuilder_ = null; metadata_ = other.metadata_; - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000008); metadataBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getMetadataFieldBuilder() : null; @@ -1445,21 +1397,17 @@ public final class RemoteProtocol { return false; } + if (!hasMessage()) { + + return false; + } if (!getRecipient().isInitialized()) { return false; } - if (hasMessage()) { - if (!getMessage().isInitialized()) { - - return false; - } - } - if (hasException()) { - if (!getException().isInitialized()) { - - return false; - } + if (!getMessage().isInitialized()) { + + return false; } if (hasSender()) { if (!getSender().isInitialized()) { @@ -1517,15 +1465,6 @@ public final class RemoteProtocol { setMessage(subBuilder.buildPartial()); break; } - case 26: { - akka.remote.RemoteProtocol.ExceptionProtocol.Builder subBuilder = akka.remote.RemoteProtocol.ExceptionProtocol.newBuilder(); - if (hasException()) { - subBuilder.mergeFrom(getException()); - } - input.readMessage(subBuilder, extensionRegistry); - setException(subBuilder.buildPartial()); - break; - } case 34: { akka.remote.RemoteProtocol.ActorRefProtocol.Builder subBuilder = akka.remote.RemoteProtocol.ActorRefProtocol.newBuilder(); if (hasSender()) { @@ -1637,7 +1576,7 @@ public final class RemoteProtocol { return recipientBuilder_; } - // optional .MessageProtocol message = 2; + // required .MessageProtocol message = 2; private akka.remote.RemoteProtocol.MessageProtocol message_ = akka.remote.RemoteProtocol.MessageProtocol.getDefaultInstance(); private com.google.protobuf.SingleFieldBuilder< akka.remote.RemoteProtocol.MessageProtocol, akka.remote.RemoteProtocol.MessageProtocol.Builder, akka.remote.RemoteProtocol.MessageProtocolOrBuilder> messageBuilder_; @@ -1727,102 +1666,12 @@ public final class RemoteProtocol { return messageBuilder_; } - // optional .ExceptionProtocol exception = 3; - private akka.remote.RemoteProtocol.ExceptionProtocol exception_ = akka.remote.RemoteProtocol.ExceptionProtocol.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - akka.remote.RemoteProtocol.ExceptionProtocol, akka.remote.RemoteProtocol.ExceptionProtocol.Builder, akka.remote.RemoteProtocol.ExceptionProtocolOrBuilder> exceptionBuilder_; - public boolean hasException() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - public akka.remote.RemoteProtocol.ExceptionProtocol getException() { - if (exceptionBuilder_ == null) { - return exception_; - } else { - return exceptionBuilder_.getMessage(); - } - } - public Builder setException(akka.remote.RemoteProtocol.ExceptionProtocol value) { - if (exceptionBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - exception_ = value; - onChanged(); - } else { - exceptionBuilder_.setMessage(value); - } - bitField0_ |= 0x00000004; - return this; - } - public Builder setException( - akka.remote.RemoteProtocol.ExceptionProtocol.Builder builderForValue) { - if (exceptionBuilder_ == null) { - exception_ = builderForValue.build(); - onChanged(); - } else { - exceptionBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000004; - return this; - } - public Builder mergeException(akka.remote.RemoteProtocol.ExceptionProtocol value) { - if (exceptionBuilder_ == null) { - if (((bitField0_ & 0x00000004) == 0x00000004) && - exception_ != akka.remote.RemoteProtocol.ExceptionProtocol.getDefaultInstance()) { - exception_ = - akka.remote.RemoteProtocol.ExceptionProtocol.newBuilder(exception_).mergeFrom(value).buildPartial(); - } else { - exception_ = value; - } - onChanged(); - } else { - exceptionBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000004; - return this; - } - public Builder clearException() { - if (exceptionBuilder_ == null) { - exception_ = akka.remote.RemoteProtocol.ExceptionProtocol.getDefaultInstance(); - onChanged(); - } else { - exceptionBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - public akka.remote.RemoteProtocol.ExceptionProtocol.Builder getExceptionBuilder() { - bitField0_ |= 0x00000004; - onChanged(); - return getExceptionFieldBuilder().getBuilder(); - } - public akka.remote.RemoteProtocol.ExceptionProtocolOrBuilder getExceptionOrBuilder() { - if (exceptionBuilder_ != null) { - return exceptionBuilder_.getMessageOrBuilder(); - } else { - return exception_; - } - } - private com.google.protobuf.SingleFieldBuilder< - akka.remote.RemoteProtocol.ExceptionProtocol, akka.remote.RemoteProtocol.ExceptionProtocol.Builder, akka.remote.RemoteProtocol.ExceptionProtocolOrBuilder> - getExceptionFieldBuilder() { - if (exceptionBuilder_ == null) { - exceptionBuilder_ = new com.google.protobuf.SingleFieldBuilder< - akka.remote.RemoteProtocol.ExceptionProtocol, akka.remote.RemoteProtocol.ExceptionProtocol.Builder, akka.remote.RemoteProtocol.ExceptionProtocolOrBuilder>( - exception_, - getParentForChildren(), - isClean()); - exception_ = null; - } - return exceptionBuilder_; - } - // optional .ActorRefProtocol sender = 4; private akka.remote.RemoteProtocol.ActorRefProtocol sender_ = akka.remote.RemoteProtocol.ActorRefProtocol.getDefaultInstance(); private com.google.protobuf.SingleFieldBuilder< akka.remote.RemoteProtocol.ActorRefProtocol, akka.remote.RemoteProtocol.ActorRefProtocol.Builder, akka.remote.RemoteProtocol.ActorRefProtocolOrBuilder> senderBuilder_; public boolean hasSender() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000004) == 0x00000004); } public akka.remote.RemoteProtocol.ActorRefProtocol getSender() { if (senderBuilder_ == null) { @@ -1841,7 +1690,7 @@ public final class RemoteProtocol { } else { senderBuilder_.setMessage(value); } - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000004; return this; } public Builder setSender( @@ -1852,12 +1701,12 @@ public final class RemoteProtocol { } else { senderBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000004; return this; } public Builder mergeSender(akka.remote.RemoteProtocol.ActorRefProtocol value) { if (senderBuilder_ == null) { - if (((bitField0_ & 0x00000008) == 0x00000008) && + if (((bitField0_ & 0x00000004) == 0x00000004) && sender_ != akka.remote.RemoteProtocol.ActorRefProtocol.getDefaultInstance()) { sender_ = akka.remote.RemoteProtocol.ActorRefProtocol.newBuilder(sender_).mergeFrom(value).buildPartial(); @@ -1868,7 +1717,7 @@ public final class RemoteProtocol { } else { senderBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000004; return this; } public Builder clearSender() { @@ -1878,11 +1727,11 @@ public final class RemoteProtocol { } else { senderBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000004); return this; } public akka.remote.RemoteProtocol.ActorRefProtocol.Builder getSenderBuilder() { - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000004; onChanged(); return getSenderFieldBuilder().getBuilder(); } @@ -1911,9 +1760,9 @@ public final class RemoteProtocol { private java.util.List metadata_ = java.util.Collections.emptyList(); private void ensureMetadataIsMutable() { - if (!((bitField0_ & 0x00000010) == 0x00000010)) { + if (!((bitField0_ & 0x00000008) == 0x00000008)) { metadata_ = new java.util.ArrayList(metadata_); - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000008; } } @@ -2029,7 +1878,7 @@ public final class RemoteProtocol { public Builder clearMetadata() { if (metadataBuilder_ == null) { metadata_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000008); onChanged(); } else { metadataBuilder_.clear(); @@ -2085,7 +1934,7 @@ public final class RemoteProtocol { metadataBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< akka.remote.RemoteProtocol.MetadataEntryProtocol, akka.remote.RemoteProtocol.MetadataEntryProtocol.Builder, akka.remote.RemoteProtocol.MetadataEntryProtocolOrBuilder>( metadata_, - ((bitField0_ & 0x00000010) == 0x00000010), + ((bitField0_ & 0x00000008) == 0x00000008), getParentForChildren(), isClean()); metadata_ = null; @@ -4365,11 +4214,15 @@ public final class RemoteProtocol { public interface AddressProtocolOrBuilder extends com.google.protobuf.MessageOrBuilder { - // required string hostname = 1; + // required string system = 1; + boolean hasSystem(); + String getSystem(); + + // required string hostname = 2; boolean hasHostname(); String getHostname(); - // required uint32 port = 2; + // required uint32 port = 3; boolean hasPort(); int getPort(); } @@ -4402,11 +4255,43 @@ public final class RemoteProtocol { } private int bitField0_; - // required string hostname = 1; - public static final int HOSTNAME_FIELD_NUMBER = 1; + // required string system = 1; + public static final int SYSTEM_FIELD_NUMBER = 1; + private java.lang.Object system_; + public boolean hasSystem() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public String getSystem() { + java.lang.Object ref = system_; + 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)) { + system_ = s; + } + return s; + } + } + private com.google.protobuf.ByteString getSystemBytes() { + java.lang.Object ref = system_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + system_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // required string hostname = 2; + public static final int HOSTNAME_FIELD_NUMBER = 2; private java.lang.Object hostname_; public boolean hasHostname() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return ((bitField0_ & 0x00000002) == 0x00000002); } public String getHostname() { java.lang.Object ref = hostname_; @@ -4434,17 +4319,18 @@ public final class RemoteProtocol { } } - // required uint32 port = 2; - public static final int PORT_FIELD_NUMBER = 2; + // required uint32 port = 3; + public static final int PORT_FIELD_NUMBER = 3; private int port_; public boolean hasPort() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000004) == 0x00000004); } public int getPort() { return port_; } private void initFields() { + system_ = ""; hostname_ = ""; port_ = 0; } @@ -4453,6 +4339,10 @@ public final class RemoteProtocol { byte isInitialized = memoizedIsInitialized; if (isInitialized != -1) return isInitialized == 1; + if (!hasSystem()) { + memoizedIsInitialized = 0; + return false; + } if (!hasHostname()) { memoizedIsInitialized = 0; return false; @@ -4469,10 +4359,13 @@ public final class RemoteProtocol { throws java.io.IOException { getSerializedSize(); if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getHostnameBytes()); + output.writeBytes(1, getSystemBytes()); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeUInt32(2, port_); + output.writeBytes(2, getHostnameBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeUInt32(3, port_); } getUnknownFields().writeTo(output); } @@ -4485,11 +4378,15 @@ public final class RemoteProtocol { size = 0; if (((bitField0_ & 0x00000001) == 0x00000001)) { size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, getHostnameBytes()); + .computeBytesSize(1, getSystemBytes()); } if (((bitField0_ & 0x00000002) == 0x00000002)) { size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(2, port_); + .computeBytesSize(2, getHostnameBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(3, port_); } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; @@ -4601,7 +4498,7 @@ public final class RemoteProtocol { maybeForceBuilderInitialization(); } - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent) { + private Builder(BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } @@ -4615,10 +4512,12 @@ public final class RemoteProtocol { public Builder clear() { super.clear(); - hostname_ = ""; + system_ = ""; bitField0_ = (bitField0_ & ~0x00000001); - port_ = 0; + hostname_ = ""; bitField0_ = (bitField0_ & ~0x00000002); + port_ = 0; + bitField0_ = (bitField0_ & ~0x00000004); return this; } @@ -4660,10 +4559,14 @@ public final class RemoteProtocol { if (((from_bitField0_ & 0x00000001) == 0x00000001)) { to_bitField0_ |= 0x00000001; } - result.hostname_ = hostname_; + result.system_ = system_; if (((from_bitField0_ & 0x00000002) == 0x00000002)) { to_bitField0_ |= 0x00000002; } + result.hostname_ = hostname_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } result.port_ = port_; result.bitField0_ = to_bitField0_; onBuilt(); @@ -4681,6 +4584,9 @@ public final class RemoteProtocol { public Builder mergeFrom(akka.remote.RemoteProtocol.AddressProtocol other) { if (other == akka.remote.RemoteProtocol.AddressProtocol.getDefaultInstance()) return this; + if (other.hasSystem()) { + setSystem(other.getSystem()); + } if (other.hasHostname()) { setHostname(other.getHostname()); } @@ -4692,6 +4598,10 @@ public final class RemoteProtocol { } public final boolean isInitialized() { + if (!hasSystem()) { + + return false; + } if (!hasHostname()) { return false; @@ -4728,11 +4638,16 @@ public final class RemoteProtocol { } case 10: { bitField0_ |= 0x00000001; + system_ = input.readBytes(); + break; + } + case 18: { + bitField0_ |= 0x00000002; hostname_ = input.readBytes(); break; } - case 16: { - bitField0_ |= 0x00000002; + case 24: { + bitField0_ |= 0x00000004; port_ = input.readUInt32(); break; } @@ -4742,10 +4657,46 @@ public final class RemoteProtocol { private int bitField0_; - // required string hostname = 1; + // required string system = 1; + private java.lang.Object system_ = ""; + public boolean hasSystem() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public String getSystem() { + java.lang.Object ref = system_; + if (!(ref instanceof String)) { + String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + system_ = s; + return s; + } else { + return (String) ref; + } + } + public Builder setSystem(String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + system_ = value; + onChanged(); + return this; + } + public Builder clearSystem() { + bitField0_ = (bitField0_ & ~0x00000001); + system_ = getDefaultInstance().getSystem(); + onChanged(); + return this; + } + void setSystem(com.google.protobuf.ByteString value) { + bitField0_ |= 0x00000001; + system_ = value; + onChanged(); + } + + // required string hostname = 2; private java.lang.Object hostname_ = ""; public boolean hasHostname() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return ((bitField0_ & 0x00000002) == 0x00000002); } public String getHostname() { java.lang.Object ref = hostname_; @@ -4761,39 +4712,39 @@ public final class RemoteProtocol { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + bitField0_ |= 0x00000002; hostname_ = value; onChanged(); return this; } public Builder clearHostname() { - bitField0_ = (bitField0_ & ~0x00000001); + bitField0_ = (bitField0_ & ~0x00000002); hostname_ = getDefaultInstance().getHostname(); onChanged(); return this; } void setHostname(com.google.protobuf.ByteString value) { - bitField0_ |= 0x00000001; + bitField0_ |= 0x00000002; hostname_ = value; onChanged(); } - // required uint32 port = 2; + // required uint32 port = 3; private int port_ ; public boolean hasPort() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000004) == 0x00000004); } public int getPort() { return port_; } public Builder setPort(int value) { - bitField0_ |= 0x00000002; + bitField0_ |= 0x00000004; port_ = value; onChanged(); return this; } public Builder clearPort() { - bitField0_ = (bitField0_ & ~0x00000002); + bitField0_ = (bitField0_ & ~0x00000004); port_ = 0; onChanged(); return this; @@ -5071,7 +5022,7 @@ public final class RemoteProtocol { maybeForceBuilderInitialization(); } - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent) { + private Builder(BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } @@ -5314,6 +5265,10 @@ public final class RemoteProtocol { boolean hasReplicateActorFromUuid(); akka.remote.RemoteProtocol.UuidProtocol getReplicateActorFromUuid(); akka.remote.RemoteProtocol.UuidProtocolOrBuilder getReplicateActorFromUuidOrBuilder(); + + // optional string supervisor = 5; + boolean hasSupervisor(); + String getSupervisor(); } public static final class RemoteSystemDaemonMessageProtocol extends com.google.protobuf.GeneratedMessage @@ -5409,11 +5364,44 @@ public final class RemoteProtocol { return replicateActorFromUuid_; } + // optional string supervisor = 5; + public static final int SUPERVISOR_FIELD_NUMBER = 5; + private java.lang.Object supervisor_; + public boolean hasSupervisor() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + public String getSupervisor() { + java.lang.Object ref = supervisor_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (com.google.protobuf.Internal.isValidUtf8(bs)) { + supervisor_ = s; + } + return s; + } + } + private com.google.protobuf.ByteString getSupervisorBytes() { + java.lang.Object ref = supervisor_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + supervisor_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + private void initFields() { messageType_ = akka.remote.RemoteProtocol.RemoteSystemDaemonMessageType.STOP; actorPath_ = ""; payload_ = com.google.protobuf.ByteString.EMPTY; replicateActorFromUuid_ = akka.remote.RemoteProtocol.UuidProtocol.getDefaultInstance(); + supervisor_ = ""; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -5449,6 +5437,9 @@ public final class RemoteProtocol { if (((bitField0_ & 0x00000008) == 0x00000008)) { output.writeMessage(4, replicateActorFromUuid_); } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeBytes(5, getSupervisorBytes()); + } getUnknownFields().writeTo(output); } @@ -5474,6 +5465,10 @@ public final class RemoteProtocol { size += com.google.protobuf.CodedOutputStream .computeMessageSize(4, replicateActorFromUuid_); } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(5, getSupervisorBytes()); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -5584,7 +5579,7 @@ public final class RemoteProtocol { maybeForceBuilderInitialization(); } - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent) { + private Builder(BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } @@ -5611,6 +5606,8 @@ public final class RemoteProtocol { replicateActorFromUuidBuilder_.clear(); } bitField0_ = (bitField0_ & ~0x00000008); + supervisor_ = ""; + bitField0_ = (bitField0_ & ~0x00000010); return this; } @@ -5669,6 +5666,10 @@ public final class RemoteProtocol { } else { result.replicateActorFromUuid_ = replicateActorFromUuidBuilder_.build(); } + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + result.supervisor_ = supervisor_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -5697,6 +5698,9 @@ public final class RemoteProtocol { if (other.hasReplicateActorFromUuid()) { mergeReplicateActorFromUuid(other.getReplicateActorFromUuid()); } + if (other.hasSupervisor()) { + setSupervisor(other.getSupervisor()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -5768,6 +5772,11 @@ public final class RemoteProtocol { setReplicateActorFromUuid(subBuilder.buildPartial()); break; } + case 42: { + bitField0_ |= 0x00000010; + supervisor_ = input.readBytes(); + break; + } } } } @@ -5948,6 +5957,42 @@ public final class RemoteProtocol { return replicateActorFromUuidBuilder_; } + // optional string supervisor = 5; + private java.lang.Object supervisor_ = ""; + public boolean hasSupervisor() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + public String getSupervisor() { + java.lang.Object ref = supervisor_; + if (!(ref instanceof String)) { + String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + supervisor_ = s; + return s; + } else { + return (String) ref; + } + } + public Builder setSupervisor(String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000010; + supervisor_ = value; + onChanged(); + return this; + } + public Builder clearSupervisor() { + bitField0_ = (bitField0_ & ~0x00000010); + supervisor_ = getDefaultInstance().getSupervisor(); + onChanged(); + return this; + } + void setSupervisor(com.google.protobuf.ByteString value) { + bitField0_ |= 0x00000010; + supervisor_ = value; + onChanged(); + } + // @@protoc_insertion_point(builder_scope:RemoteSystemDaemonMessageProtocol) } @@ -6216,7 +6261,7 @@ public final class RemoteProtocol { maybeForceBuilderInitialization(); } - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent) { + private Builder(BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } @@ -6686,45 +6731,45 @@ public final class RemoteProtocol { descriptor; static { java.lang.String[] descriptorData = { - "\n\024RemoteProtocol.proto\"j\n\022AkkaRemoteProt" + - "ocol\022\'\n\007message\030\001 \001(\0132\026.RemoteMessagePro" + - "tocol\022+\n\013instruction\030\002 \001(\0132\026.RemoteContr" + - "olProtocol\"\324\001\n\025RemoteMessageProtocol\022$\n\t" + - "recipient\030\001 \002(\0132\021.ActorRefProtocol\022!\n\007me" + - "ssage\030\002 \001(\0132\020.MessageProtocol\022%\n\texcepti" + - "on\030\003 \001(\0132\022.ExceptionProtocol\022!\n\006sender\030\004" + - " \001(\0132\021.ActorRefProtocol\022(\n\010metadata\030\005 \003(" + - "\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\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" + "\n\035protocol/RemoteProtocol.proto\"j\n\022AkkaR" + + "emoteProtocol\022\'\n\007message\030\001 \001(\0132\026.RemoteM" + + "essageProtocol\022+\n\013instruction\030\002 \001(\0132\026.Re" + + "moteControlProtocol\"\255\001\n\025RemoteMessagePro" + + "tocol\022$\n\trecipient\030\001 \002(\0132\021.ActorRefProto" + + "col\022!\n\007message\030\002 \002(\0132\020.MessageProtocol\022!" + + "\n\006sender\030\004 \001(\0132\021.ActorRefProtocol\022(\n\010met" + + "adata\030\005 \003(\0132\026.MetadataEntryProtocol\"l\n\025R" + + "emoteControlProtocol\022!\n\013commandType\030\001 \002(" + + "\0162\014.CommandType\022\016\n\006cookie\030\002 \001(\t\022 \n\006origi", + "n\030\003 \001(\0132\020.AddressProtocol\" \n\020ActorRefPro" + + "tocol\022\014\n\004path\030\001 \002(\t\";\n\017MessageProtocol\022\017" + + "\n\007message\030\001 \002(\014\022\027\n\017messageManifest\030\002 \001(\014" + + "\")\n\014UuidProtocol\022\014\n\004high\030\001 \002(\004\022\013\n\003low\030\002 " + + "\002(\004\"3\n\025MetadataEntryProtocol\022\013\n\003key\030\001 \002(" + + "\t\022\r\n\005value\030\002 \002(\014\"A\n\017AddressProtocol\022\016\n\006s" + + "ystem\030\001 \002(\t\022\020\n\010hostname\030\002 \002(\t\022\014\n\004port\030\003 " + + "\002(\r\"7\n\021ExceptionProtocol\022\021\n\tclassname\030\001 " + + "\002(\t\022\017\n\007message\030\002 \002(\t\"\277\001\n!RemoteSystemDae" + + "monMessageProtocol\0223\n\013messageType\030\001 \002(\0162", + "\036.RemoteSystemDaemonMessageType\022\021\n\tactor" + + "Path\030\002 \001(\t\022\017\n\007payload\030\003 \001(\014\022-\n\026replicate" + + "ActorFromUuid\030\004 \001(\0132\r.UuidProtocol\022\022\n\nsu" + + "pervisor\030\005 \001(\t\"y\n\035DurableMailboxMessageP" + + "rotocol\022$\n\trecipient\030\001 \002(\0132\021.ActorRefPro" + + "tocol\022!\n\006sender\030\002 \001(\0132\021.ActorRefProtocol" + + "\022\017\n\007message\030\003 \002(\014*(\n\013CommandType\022\013\n\007CONN" + + "ECT\020\001\022\014\n\010SHUTDOWN\020\002*K\n\026ReplicationStorag" + + "eType\022\r\n\tTRANSIENT\020\001\022\023\n\017TRANSACTION_LOG\020" + + "\002\022\r\n\tDATA_GRID\020\003*>\n\027ReplicationStrategyT", + "ype\022\021\n\rWRITE_THROUGH\020\001\022\020\n\014WRITE_BEHIND\020\002" + + "*\241\002\n\035RemoteSystemDaemonMessageType\022\010\n\004ST" + + "OP\020\001\022\007\n\003USE\020\002\022\013\n\007RELEASE\020\003\022\022\n\016MAKE_AVAIL" + + "ABLE\020\004\022\024\n\020MAKE_UNAVAILABLE\020\005\022\016\n\nDISCONNE" + + "CT\020\006\022\r\n\tRECONNECT\020\007\022\n\n\006RESIGN\020\010\022\n\n\006GOSSI" + + "P\020\t\022\031\n\025FAIL_OVER_CONNECTIONS\020\024\022\026\n\022FUNCTI" + + "ON_FUN0_UNIT\020\025\022\025\n\021FUNCTION_FUN0_ANY\020\026\022\032\n" + + "\026FUNCTION_FUN1_ARG_UNIT\020\027\022\031\n\025FUNCTION_FU" + + "N1_ARG_ANY\020\030B\017\n\013akka.remoteH\001" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -6744,7 +6789,7 @@ public final class RemoteProtocol { internal_static_RemoteMessageProtocol_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_RemoteMessageProtocol_descriptor, - new java.lang.String[] { "Recipient", "Message", "Exception", "Sender", "Metadata", }, + new java.lang.String[] { "Recipient", "Message", "Sender", "Metadata", }, akka.remote.RemoteProtocol.RemoteMessageProtocol.class, akka.remote.RemoteProtocol.RemoteMessageProtocol.Builder.class); internal_static_RemoteControlProtocol_descriptor = @@ -6792,7 +6837,7 @@ public final class RemoteProtocol { internal_static_AddressProtocol_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_AddressProtocol_descriptor, - new java.lang.String[] { "Hostname", "Port", }, + new java.lang.String[] { "System", "Hostname", "Port", }, akka.remote.RemoteProtocol.AddressProtocol.class, akka.remote.RemoteProtocol.AddressProtocol.Builder.class); internal_static_ExceptionProtocol_descriptor = @@ -6808,7 +6853,7 @@ public final class RemoteProtocol { internal_static_RemoteSystemDaemonMessageProtocol_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_RemoteSystemDaemonMessageProtocol_descriptor, - new java.lang.String[] { "MessageType", "ActorPath", "Payload", "ReplicateActorFromUuid", }, + new java.lang.String[] { "MessageType", "ActorPath", "Payload", "ReplicateActorFromUuid", "Supervisor", }, akka.remote.RemoteProtocol.RemoteSystemDaemonMessageProtocol.class, akka.remote.RemoteProtocol.RemoteSystemDaemonMessageProtocol.Builder.class); internal_static_DurableMailboxMessageProtocol_descriptor = diff --git a/akka-remote/src/main/protocol/RemoteProtocol.proto b/akka-remote/src/main/protocol/RemoteProtocol.proto index 6fdc9acaaf..f7763ba2cc 100644 --- a/akka-remote/src/main/protocol/RemoteProtocol.proto +++ b/akka-remote/src/main/protocol/RemoteProtocol.proto @@ -21,8 +21,7 @@ message AkkaRemoteProtocol { */ message RemoteMessageProtocol { required ActorRefProtocol recipient = 1; - optional MessageProtocol message = 2; - optional ExceptionProtocol exception = 3; + required MessageProtocol message = 2; optional ActorRefProtocol sender = 4; repeated MetadataEntryProtocol metadata = 5; } @@ -97,8 +96,9 @@ message MetadataEntryProtocol { * Defines a remote address. */ message AddressProtocol { - required string hostname = 1; - required uint32 port = 2; + required string system = 1; + required string hostname = 2; + required uint32 port = 3; } /** @@ -117,6 +117,7 @@ message RemoteSystemDaemonMessageProtocol { optional string actorPath = 2; optional bytes payload = 3; optional UuidProtocol replicateActorFromUuid = 4; + optional string supervisor = 5; } /** diff --git a/akka-remote/src/main/resources/reference.conf b/akka-remote/src/main/resources/reference.conf index 41eaf2e117..4083a64ea2 100644 --- a/akka-remote/src/main/resources/reference.conf +++ b/akka-remote/src/main/resources/reference.conf @@ -7,6 +7,29 @@ akka { + actor { + + deployment { + + default { + + remote = "" # if this is set to a valid remote address, the named actor will be deployed at that node + # e.g. "akka://sys@host:port" + + target { + nodes = [] # A list of hostnames and ports for instantiating the children of a non-direct router + # The format should be on "akka://sys@host:port", where: + # - sys is the remote actor system name + # - hostname can be either hostname or IP address the remote actor should connect to + # - port should be the port for the remote server on the other node + # The number of actor instances to be spawned is still taken from the nr-of-instances + # setting as for local routers; the instances will be distributed round-robin among the + # given nodes. + } + } + } + } + remote { transport = "akka.remote.netty.NettyRemoteSupport" diff --git a/akka-remote/src/main/scala/akka/remote/AccrualFailureDetector.scala b/akka-remote/src/main/scala/akka/remote/AccrualFailureDetector.scala index 87dda83b71..23043c5303 100644 --- a/akka-remote/src/main/scala/akka/remote/AccrualFailureDetector.scala +++ b/akka-remote/src/main/scala/akka/remote/AccrualFailureDetector.scala @@ -25,12 +25,6 @@ import akka.actor.ActorSystem */ class AccrualFailureDetector(val threshold: Int = 8, val maxSampleSize: Int = 1000) { - def this(system: ActorSystem) { - this( - RemoteExtension(system).FailureDetectorThreshold, - RemoteExtension(system).FailureDetectorMaxSampleSize) - } - private final val PhiFactor = 1.0 / math.log(10.0) private case class FailureStats(mean: Double = 0.0D, variance: Double = 0.0D, deviation: Double = 0.0D) @@ -41,9 +35,9 @@ class AccrualFailureDetector(val threshold: Int = 8, val maxSampleSize: Int = 10 */ private case class State( version: Long = 0L, - failureStats: Map[RemoteAddress, FailureStats] = Map.empty[RemoteAddress, FailureStats], - intervalHistory: Map[RemoteAddress, Vector[Long]] = Map.empty[RemoteAddress, Vector[Long]], - timestamps: Map[RemoteAddress, Long] = Map.empty[RemoteAddress, Long]) + failureStats: Map[ParsedTransportAddress, FailureStats] = Map.empty[ParsedTransportAddress, FailureStats], + intervalHistory: Map[ParsedTransportAddress, Vector[Long]] = Map.empty[ParsedTransportAddress, Vector[Long]], + timestamps: Map[ParsedTransportAddress, Long] = Map.empty[ParsedTransportAddress, Long]) private val state = new AtomicReference[State](State()) @@ -51,13 +45,13 @@ class AccrualFailureDetector(val threshold: Int = 8, val maxSampleSize: Int = 10 * Returns true if the connection is considered to be up and healthy * and returns false otherwise. */ - def isAvailable(connection: RemoteAddress): Boolean = phi(connection) < threshold + def isAvailable(connection: ParsedTransportAddress): Boolean = phi(connection) < threshold /** * Records a heartbeat for a connection. */ @tailrec - final def heartbeat(connection: RemoteAddress) { + final def heartbeat(connection: ParsedTransportAddress) { val oldState = state.get val latestTimestamp = oldState.timestamps.get(connection) @@ -138,7 +132,7 @@ class AccrualFailureDetector(val threshold: Int = 8, val maxSampleSize: Int = 10 * Implementations of 'Cumulative Distribution Function' for Exponential Distribution. * For a discussion on the math read [https://issues.apache.org/jira/browse/CASSANDRA-2597]. */ - def phi(connection: RemoteAddress): Double = { + def phi(connection: ParsedTransportAddress): Double = { val oldState = state.get val oldTimestamp = oldState.timestamps.get(connection) if (oldTimestamp.isEmpty) 0.0D // treat unmanaged connections, e.g. with zero heartbeats, as healthy connections @@ -153,7 +147,7 @@ class AccrualFailureDetector(val threshold: Int = 8, val maxSampleSize: Int = 10 * Removes the heartbeat management for a connection. */ @tailrec - final def remove(connection: RemoteAddress) { + final def remove(connection: ParsedTransportAddress) { val oldState = state.get if (oldState.failureStats.contains(connection)) { diff --git a/akka-remote/src/main/scala/akka/remote/Gossiper.scala b/akka-remote/src/main/scala/akka/remote/Gossiper.scala index 20a047952f..0b8044f9c4 100644 --- a/akka-remote/src/main/scala/akka/remote/Gossiper.scala +++ b/akka-remote/src/main/scala/akka/remote/Gossiper.scala @@ -7,7 +7,6 @@ package akka.remote import akka.actor._ import akka.actor.Status._ import akka.event.Logging -import akka.util.duration._ import akka.util.Duration import akka.remote.RemoteProtocol._ import akka.remote.RemoteProtocol.RemoteSystemDaemonMessageType._ @@ -28,8 +27,8 @@ import com.google.protobuf.ByteString * Interface for node membership change listener. */ trait NodeMembershipChangeListener { - def nodeConnected(node: RemoteAddress) - def nodeDisconnected(node: RemoteAddress) + def nodeConnected(node: ParsedTransportAddress) + def nodeDisconnected(node: ParsedTransportAddress) } /** @@ -37,22 +36,22 @@ trait NodeMembershipChangeListener { */ case class Gossip( version: VectorClock, - node: RemoteAddress, - availableNodes: Set[RemoteAddress] = Set.empty[RemoteAddress], - unavailableNodes: Set[RemoteAddress] = Set.empty[RemoteAddress]) + node: ParsedTransportAddress, + availableNodes: Set[ParsedTransportAddress] = Set.empty[ParsedTransportAddress], + unavailableNodes: Set[ParsedTransportAddress] = Set.empty[ParsedTransportAddress]) // ====== START - NEW GOSSIP IMPLEMENTATION ====== /* case class Gossip( version: VectorClock, - node: RemoteAddress, - leader: RemoteAddress, // FIXME leader is always head of 'members', so we probably don't need this field + node: ParsedTransportAddress, + leader: ParsedTransportAddress, // FIXME leader is always head of 'members', so we probably don't need this field members: SortedSet[Member] = SortetSet.empty[Member](Ordering.fromLessThan[String](_ > _)), // sorted set of members with their status, sorted by name seen: Map[Member, VectorClock] = Map.empty[Member, VectorClock], // for ring convergence pendingChanges: Option[Vector[PendingPartitioningChange]] = None, // for handoff meta: Option[Map[String, Array[Byte]]] = None) // misc meta-data - case class Member(address: RemoteAddress, status: MemberStatus) + case class Member(address: ParsedTransportAddress, status: MemberStatus) sealed trait MemberStatus object MemberStatus { @@ -73,8 +72,8 @@ case class Gossip( type VNodeMod = AnyRef case class PendingPartitioningChange( - owner: RemoteAddress, - nextOwner: RemoteAddress, + owner: ParsedTransportAddress, + nextOwner: ParsedTransportAddress, changes: Vector[VNodeMod], status: PendingPartitioningStatus) */ @@ -95,7 +94,7 @@ case class Gossip( * gossip to random seed with certain probability depending on number of unreachable, seed and live nodes. * */ -class Gossiper(remote: Remote) { +class Gossiper(remote: Remote, system: ActorSystemImpl) { /** * Represents the state for this Gossiper. Implemented using optimistic lockless concurrency, @@ -105,15 +104,21 @@ class Gossiper(remote: Remote) { currentGossip: Gossip, nodeMembershipChangeListeners: Set[NodeMembershipChangeListener] = Set.empty[NodeMembershipChangeListener]) - private val system = remote.system - private val remoteExtension = RemoteExtension(system) - private val serialization = SerializationExtension(system) + private val remoteSettings = remote.remoteSettings + private val serialization = remote.serialization private val log = Logging(system, "Gossiper") private val failureDetector = remote.failureDetector - private val connectionManager = new RemoteConnectionManager(system, remote, Map.empty[RemoteAddress, ActorRef]) + private val connectionManager = new RemoteConnectionManager(system, remote, Map.empty[ParsedTransportAddress, ActorRef]) private val seeds = { - val seeds = remoteExtension.SeedNodes + val seeds = remoteSettings.SeedNodes flatMap { + case x: UnparsedTransportAddress ⇒ + x.parse(remote.transports) match { + case y: ParsedTransportAddress ⇒ Some(y) + case _ ⇒ None + } + case _ ⇒ None + } if (seeds.isEmpty) throw new ConfigurationException( "At least one seed node must be defined in the configuration [akka.cluster.seed-nodes]") else seeds @@ -123,8 +128,8 @@ class Gossiper(remote: Remote) { private val nodeFingerprint = address.## private val random = SecureRandom.getInstance("SHA1PRNG") - private val initalDelayForGossip = remoteExtension.InitalDelayForGossip - private val gossipFrequency = remoteExtension.GossipFrequency + private val initalDelayForGossip = remoteSettings.InitalDelayForGossip + private val gossipFrequency = remoteSettings.GossipFrequency private val state = new AtomicReference[State](State(currentGossip = newGossip())) @@ -161,7 +166,7 @@ class Gossiper(remote: Remote) { node ← oldAvailableNodes if connectionManager.connectionFor(node).isEmpty } { - val connectionFactory = () ⇒ RemoteActorRef(remote.system.provider, remote.server, gossipingNode, remote.remoteDaemon.path, None) + val connectionFactory = () ⇒ system.actorFor(RootActorPath(RemoteSystemAddress(system.name, gossipingNode)) / "remote") connectionManager.putIfAbsent(node, connectionFactory) // create a new remote connection to the new node oldState.nodeMembershipChangeListeners foreach (_ nodeConnected node) // notify listeners about the new nodes } @@ -235,7 +240,7 @@ class Gossiper(remote: Remote) { /** * Gossips set of nodes passed in as argument. Returns 'true' if it gossiped to a "seed" node. */ - private def gossipTo(nodes: Set[RemoteAddress]): Boolean = { + private def gossipTo(nodes: Set[ParsedTransportAddress]): Boolean = { val peers = nodes filter (_ != address) // filter out myself val peer = selectRandomNode(peers) val oldState = state.get @@ -245,7 +250,7 @@ class Gossiper(remote: Remote) { throw new IllegalStateException("Connection for [" + peer + "] is not set up")) try { - (connection ? (toRemoteMessage(newGossip), remoteExtension.RemoteSystemDaemonAckTimeout)).as[Status] match { + (connection ? (toRemoteMessage(newGossip), remoteSettings.RemoteSystemDaemonAckTimeout)).as[Status] match { case Some(Success(receiver)) ⇒ log.debug("Gossip sent to [{}] was successfully received", receiver) @@ -298,8 +303,8 @@ class Gossiper(remote: Remote) { private def newGossip(): Gossip = Gossip( version = VectorClock(), - node = address, - availableNodes = Set(address)) + node = address.transport, + availableNodes = Set(address.transport)) private def incrementVersionForGossip(from: Gossip): Gossip = { val newVersion = from.version.increment(nodeFingerprint, newTimestamp) @@ -327,7 +332,7 @@ class Gossiper(remote: Remote) { } } - private def selectRandomNode(nodes: Set[RemoteAddress]): RemoteAddress = { + private def selectRandomNode(nodes: Set[ParsedTransportAddress]): ParsedTransportAddress = { nodes.toList(random.nextInt(nodes.size)) } } diff --git a/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala b/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala index 23994337f1..6839dc47ae 100644 --- a/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala +++ b/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala @@ -17,10 +17,10 @@ object NetworkEventStream { private sealed trait NetworkEventStreamEvent - private case class Register(listener: Listener, connectionAddress: RemoteAddress) + private case class Register(listener: Listener, connectionAddress: ParsedTransportAddress) extends NetworkEventStreamEvent - private case class Unregister(listener: Listener, connectionAddress: RemoteAddress) + private case class Unregister(listener: Listener, connectionAddress: ParsedTransportAddress) extends NetworkEventStreamEvent /** @@ -35,8 +35,8 @@ object NetworkEventStream { */ private class Channel extends Actor { - val listeners = new mutable.HashMap[RemoteAddress, mutable.Set[Listener]]() { - override def default(k: RemoteAddress) = mutable.Set.empty[Listener] + val listeners = new mutable.HashMap[ParsedTransportAddress, mutable.Set[Listener]]() { + override def default(k: ParsedTransportAddress) = mutable.Set.empty[Listener] } def receive = { @@ -68,12 +68,12 @@ class NetworkEventStream(system: ActorSystemImpl) { /** * Registers a network event stream listener (asyncronously). */ - def register(listener: Listener, connectionAddress: RemoteAddress) = + def register(listener: Listener, connectionAddress: ParsedTransportAddress) = sender ! Register(listener, connectionAddress) /** * Unregisters a network event stream listener (asyncronously) . */ - def unregister(listener: Listener, connectionAddress: RemoteAddress) = + def unregister(listener: Listener, connectionAddress: ParsedTransportAddress) = sender ! Unregister(listener, connectionAddress) } diff --git a/akka-remote/src/main/scala/akka/remote/Remote.scala b/akka-remote/src/main/scala/akka/remote/Remote.scala index 072c45b4c4..35d417be1a 100644 --- a/akka-remote/src/main/scala/akka/remote/Remote.scala +++ b/akka-remote/src/main/scala/akka/remote/Remote.scala @@ -4,94 +4,114 @@ package akka.remote -import akka.actor.ActorSystem import akka.actor._ -import akka.event.Logging +import akka.event._ import akka.actor.Status._ import akka.util._ import akka.util.duration._ import akka.util.Helpers._ -import akka.actor.DeploymentConfig._ import akka.serialization.Compression.LZF import akka.remote.RemoteProtocol._ import akka.remote.RemoteProtocol.RemoteSystemDaemonMessageType._ import java.net.InetSocketAddress import com.eaio.uuid.UUID -import akka.serialization.{ JavaSerializer, Serialization, Serializer, Compression } -import akka.dispatch.{ Terminate, Dispatchers, Future, PinnedDispatcher } +import akka.serialization.{ JavaSerializer, Serialization, Serializer, Compression, SerializationExtension } +import akka.dispatch.{ Terminate, Dispatchers, Future, PinnedDispatcher, MessageDispatcher } import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.TimeUnit.MILLISECONDS -import akka.serialization.SerializationExtension +import akka.dispatch.SystemMessage +import scala.annotation.tailrec /** * Remote module - contains remote client and server config, remote server instance, remote daemon, remote dispatchers etc. */ -class Remote(val system: ActorSystemImpl, val nodename: String) { +class Remote(val settings: ActorSystem.Settings, val remoteSettings: RemoteSettings) { - val log = Logging(system, "Remote") - - import system._ import settings._ - private[remote] val remoteExtension = RemoteExtension(system) - private[remote] val serialization = SerializationExtension(system) - private[remote] val remoteAddress = { - RemoteAddress(system.name, remoteExtension.serverSettings.Hostname, remoteExtension.serverSettings.Port) + // TODO make this really pluggable + val transports: TransportsMap = Map("akka" -> ((h, p) ⇒ Right(RemoteNettyAddress(h, p)))) + val remoteAddress: RemoteSystemAddress[ParsedTransportAddress] = { + val unparsedAddress = remoteSettings.serverSettings.URI match { + case RemoteAddressExtractor(a) ⇒ a + case x ⇒ throw new IllegalArgumentException("cannot parse URI " + x) + } + val parsed = unparsedAddress.parse(transports) match { + case Left(x) ⇒ throw new IllegalArgumentException(x.transport.error) + case Right(x) ⇒ x + } + parsed.copy(system = settings.name) } - val failureDetector = new AccrualFailureDetector(system) + val failureDetector = new AccrualFailureDetector(remoteSettings.FailureDetectorThreshold, remoteSettings.FailureDetectorMaxSampleSize) - // val gossiper = new Gossiper(this) + @volatile + private var _serialization: Serialization = _ + def serialization = _serialization - val remoteDaemonServiceName = "akka-system-remote-daemon".intern + @volatile + private var _computeGridDispatcher: MessageDispatcher = _ + def computeGridDispatcher = _computeGridDispatcher - val computeGridDispatcher = dispatcherFactory.newFromConfig("akka.remote.compute-grid-dispatcher") + @volatile + private var _remoteDaemon: InternalActorRef = _ + def remoteDaemon = _remoteDaemon - // FIXME it is probably better to create another supervisor for handling the children created by handle_*, ticket #1408 - private[remote] lazy val remoteDaemonSupervisor = system.actorOf(Props( - OneForOneStrategy(List(classOf[Exception]), None, None)), "akka-system-remote-supervisor") // is infinite restart what we want? + @volatile + private var _eventStream: NetworkEventStream = _ + def eventStream = _eventStream - private[remote] lazy val remoteDaemon = - system.provider.actorOf(system, - Props(new RemoteSystemDaemon(this)).withDispatcher(dispatcherFactory.newPinnedDispatcher(remoteDaemonServiceName)), - remoteDaemonSupervisor.asInstanceOf[InternalActorRef], - remoteDaemonServiceName, - systemService = true) + @volatile + private var _server: RemoteSupport[ParsedTransportAddress] = _ + def server = _server - private[remote] lazy val remoteClientLifeCycleHandler = system.actorOf(Props(new Actor { - def receive = { - case RemoteClientError(cause, remote, address) ⇒ remote.shutdownClientConnection(address) - case RemoteClientDisconnected(remote, address) ⇒ remote.shutdownClientConnection(address) - case _ ⇒ //ignore other + @volatile + private var _provider: RemoteActorRefProvider = _ + def provider = _provider + + def init(system: ActorSystemImpl, provider: RemoteActorRefProvider) = { + + val log = Logging(system, "Remote") + + _provider = provider + _serialization = SerializationExtension(system) + _computeGridDispatcher = system.dispatcherFactory.newFromConfig("akka.remote.compute-grid-dispatcher") + _remoteDaemon = new RemoteSystemDaemon(system, this, system.provider.rootPath / "remote", system.provider.rootGuardian, log) + _eventStream = new NetworkEventStream(system) + _server = { + val arguments = Seq( + classOf[ActorSystemImpl] -> system, + classOf[Remote] -> this, + classOf[RemoteSystemAddress[_ <: ParsedTransportAddress]] -> remoteAddress) + val types: Array[Class[_]] = arguments map (_._1) toArray + val values: Array[AnyRef] = arguments map (_._2) toArray + + ReflectiveAccess.createInstance[RemoteSupport[ParsedTransportAddress]](remoteSettings.RemoteTransport, types, values) match { + case Left(problem) ⇒ + + log.error(problem, "Could not load remote transport layer") + throw problem + + case Right(remote) ⇒ + + remote.start(None) //TODO Any application loader here? + + val remoteClientLifeCycleHandler = system.systemActorOf(Props(new Actor { + def receive = { + case RemoteClientError(cause, remote, address) ⇒ remote.shutdownClientConnection(address) + case RemoteClientDisconnected(remote, address) ⇒ remote.shutdownClientConnection(address) + case _ ⇒ //ignore other + } + }), "RemoteClientLifeCycleListener") + + system.eventStream.subscribe(eventStream.sender, classOf[RemoteLifeCycleEvent]) + system.eventStream.subscribe(remoteClientLifeCycleHandler, classOf[RemoteLifeCycleEvent]) + + remote + } } - }), "akka.remote.RemoteClientLifeCycleListener") - lazy val eventStream = new NetworkEventStream(system) - - lazy val server: RemoteSupport = { - val arguments = Seq( - classOf[ActorSystem] -> system, - classOf[Remote] -> this) - val types: Array[Class[_]] = arguments map (_._1) toArray - val values: Array[AnyRef] = arguments map (_._2) toArray - - ReflectiveAccess.createInstance[RemoteSupport](remoteExtension.RemoteTransport, types, values) match { - case Left(problem) ⇒ - log.error(problem, "Could not load remote transport layer") - throw problem - case Right(remote) ⇒ - remote.start(None) //TODO Any application loader here? - - system.eventStream.subscribe(eventStream.sender, classOf[RemoteLifeCycleEvent]) - system.eventStream.subscribe(remoteClientLifeCycleHandler, classOf[RemoteLifeCycleEvent]) - - remote - } - } - - def start() { - val daemonPath = remoteDaemon.path //Force init of daemon - log.info("Starting remote server on [{}] and starting remoteDaemon with path [{}]", remoteAddress, daemonPath) + log.info("Starting remote server on [{}]", remoteAddress) } } @@ -100,75 +120,90 @@ class Remote(val system: ActorSystemImpl, val nodename: String) { * * It acts as the brain of the remote that responds to system remote events (messages) and undertakes action. */ -class RemoteSystemDaemon(remote: Remote) extends Actor { +class RemoteSystemDaemon(system: ActorSystemImpl, remote: Remote, _path: ActorPath, _parent: InternalActorRef, _log: LoggingAdapter) + extends VirtualPathContainer(_path, _parent, _log) { - import remote._ - import remote.{ system ⇒ systemImpl } + /** + * Find the longest matching path which we know about and return that ref + * (or ask that ref to continue searching if elements are left). + */ + override def getChild(names: Iterator[String]): InternalActorRef = { - override def preRestart(reason: Throwable, msg: Option[Any]) { - log.debug("RemoteSystemDaemon failed due to [{}] - restarting...", reason) + @tailrec + def rec(s: String, n: Int): (InternalActorRef, Int) = { + getChild(s) match { + case null ⇒ + val last = s.lastIndexOf('/') + if (last == -1) (Nobody, n) + else rec(s.substring(0, last), n + 1) + case ref ⇒ (ref, n) + } + } + + val full = Vector() ++ names + rec(full.mkString("/"), 0) match { + case (Nobody, _) ⇒ Nobody + case (ref, n) if n == 0 ⇒ ref + case (ref, n) ⇒ ref.getChild(full.takeRight(n).iterator) + } } - def receive: Actor.Receive = { + override def !(msg: Any)(implicit sender: ActorRef = null): Unit = msg match { case message: RemoteSystemDaemonMessageProtocol ⇒ - log.debug("Received command [\n{}] to RemoteSystemDaemon on [{}]", message.getMessageType, nodename) + log.debug("Received command [\n{}] to RemoteSystemDaemon on [{}]", message.getMessageType, remote.remoteSettings.NodeName) message.getMessageType match { - case USE ⇒ handleUse(message) - case RELEASE ⇒ handleRelease(message) + case USE ⇒ handleUse(message) + case RELEASE ⇒ handleRelease(message) // case STOP ⇒ cluster.shutdown() // case DISCONNECT ⇒ cluster.disconnect() // case RECONNECT ⇒ cluster.reconnect() // case RESIGN ⇒ cluster.resign() // case FAIL_OVER_CONNECTIONS ⇒ handleFailover(message) - case GOSSIP ⇒ handleGossip(message) - case FUNCTION_FUN0_UNIT ⇒ handle_fun0_unit(message) - case FUNCTION_FUN0_ANY ⇒ handle_fun0_any(message) - case FUNCTION_FUN1_ARG_UNIT ⇒ handle_fun1_arg_unit(message) - case FUNCTION_FUN1_ARG_ANY ⇒ handle_fun1_arg_any(message) - //TODO: should we not deal with unrecognized message types? + case GOSSIP ⇒ handleGossip(message) + // case FUNCTION_FUN0_UNIT ⇒ handle_fun0_unit(message) + // case FUNCTION_FUN0_ANY ⇒ handle_fun0_any(message, sender) + // case FUNCTION_FUN1_ARG_UNIT ⇒ handle_fun1_arg_unit(message) + // case FUNCTION_FUN1_ARG_ANY ⇒ handle_fun1_arg_any(message, sender) + case unknown ⇒ log.warning("Unknown message type {} received by {}", unknown, this) } - case unknown ⇒ log.warning("Unknown message to RemoteSystemDaemon [{}]", unknown) + case Terminated(child) ⇒ removeChild(child.path.elements.drop(1).mkString("/")) + + case unknown ⇒ log.warning("Unknown message {} received by {}", unknown, this) } def handleUse(message: RemoteSystemDaemonMessageProtocol) { - try { - if (message.hasActorPath) { - val actorFactoryBytes = - if (remoteExtension.ShouldCompressData) LZF.uncompress(message.getPayload.toByteArray) else message.getPayload.toByteArray + if (!message.hasActorPath || !message.hasSupervisor) log.error("Ignoring incomplete USE command [{}]", message) + else { - val actorFactory = - serialization.deserialize(actorFactoryBytes, classOf[() ⇒ Actor], None) match { - case Left(error) ⇒ throw error - case Right(instance) ⇒ instance.asInstanceOf[() ⇒ Actor] - } + val actorFactoryBytes = + if (remote.remoteSettings.ShouldCompressData) LZF.uncompress(message.getPayload.toByteArray) + else message.getPayload.toByteArray - message.getActorPath match { - 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 ⇒ - 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) + val actorFactory = + remote.serialization.deserialize(actorFactoryBytes, classOf[() ⇒ Actor], None) match { + case Left(error) ⇒ throw error + case Right(instance) ⇒ instance.asInstanceOf[() ⇒ Actor] } - } else { - log.error("Actor 'address' for actor to instantiate is not defined, ignoring remote system daemon command [{}]", message) + import remote.remoteAddress + implicit val t = remote.transports + + message.getActorPath match { + case ParsedActorPath(`remoteAddress`, elems) if elems.nonEmpty && elems.head == "remote" ⇒ + // TODO RK canonicalize path so as not to duplicate it always #1446 + val subpath = elems.drop(1) + val path = remote.remoteDaemon.path / subpath + val supervisor = system.actorFor(message.getSupervisor).asInstanceOf[InternalActorRef] + val actor = system.provider.actorOf(system, Props(creator = actorFactory), supervisor, path, true, None) + addChild(subpath.mkString("/"), actor) + system.deathWatch.subscribe(this, actor) + case _ ⇒ + log.error("remote path does not match path from message [{}]", message) } - - sender ! Success(remoteAddress) - } catch { - case exc: Exception ⇒ - sender ! Failure(exc) - throw exc } - } // FIXME implement handleRelease @@ -195,45 +230,47 @@ class RemoteSystemDaemon(remote: Remote) extends Actor { /* * generate name for temporary actor refs */ - private val tempNumber = new AtomicLong - def tempName = "$_" + Helpers.base64(tempNumber.getAndIncrement()) - def tempPath = remoteDaemon.path / tempName - - // FIXME: handle real remote supervision, ticket #1408 - def handle_fun0_unit(message: RemoteSystemDaemonMessageProtocol) { - new LocalActorRef(systemImpl, - Props( - context ⇒ { - case f: Function0[_] ⇒ try { f() } finally { context.self.stop() } - }).copy(dispatcher = computeGridDispatcher), remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Function0[Unit]]) - } - - // FIXME: handle real remote supervision, ticket #1408 - def handle_fun0_any(message: RemoteSystemDaemonMessageProtocol) { - new LocalActorRef(systemImpl, - Props( - context ⇒ { - case f: Function0[_] ⇒ try { sender ! f() } finally { context.self.stop() } - }).copy(dispatcher = computeGridDispatcher), remoteDaemon, tempPath, systemService = true) forward payloadFor(message, classOf[Function0[Any]]) - } - - // FIXME: handle real remote supervision, ticket #1408 - def handle_fun1_arg_unit(message: RemoteSystemDaemonMessageProtocol) { - new LocalActorRef(systemImpl, - Props( - context ⇒ { - case (fun: Function[_, _], param: Any) ⇒ try { fun.asInstanceOf[Any ⇒ Unit].apply(param) } finally { context.self.stop() } - }).copy(dispatcher = computeGridDispatcher), remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Tuple2[Function1[Any, Unit], Any]]) - } - - // FIXME: handle real remote supervision, ticket #1408 - def handle_fun1_arg_any(message: RemoteSystemDaemonMessageProtocol) { - new LocalActorRef(systemImpl, - Props( - context ⇒ { - case (fun: Function[_, _], param: Any) ⇒ try { sender ! fun.asInstanceOf[Any ⇒ Any](param) } finally { context.self.stop() } - }).copy(dispatcher = computeGridDispatcher), remoteDaemon, tempPath, systemService = true) forward payloadFor(message, classOf[Tuple2[Function1[Any, Any], Any]]) - } + // private val tempNumber = new AtomicLong + // def tempName = "$_" + Helpers.base64(tempNumber.getAndIncrement()) + // def tempPath = remote.remoteDaemon.path / tempName + // + // // FIXME: handle real remote supervision, ticket #1408 + // def handle_fun0_unit(message: RemoteSystemDaemonMessageProtocol) { + // new LocalActorRef(remote.system, + // Props( + // context ⇒ { + // case f: Function0[_] ⇒ try { f() } finally { context.self.stop() } + // }).copy(dispatcher = remote.computeGridDispatcher), remote.remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Function0[Unit]]) + // } + // + // // FIXME: handle real remote supervision, ticket #1408 + // def handle_fun0_any(message: RemoteSystemDaemonMessageProtocol, sender: ActorRef) { + // implicit val s = sender + // new LocalActorRef(remote.system, + // Props( + // context ⇒ { + // case f: Function0[_] ⇒ try { context.sender ! f() } finally { context.self.stop() } + // }).copy(dispatcher = remote.computeGridDispatcher), remote.remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Function0[Any]]) + // } + // + // // FIXME: handle real remote supervision, ticket #1408 + // def handle_fun1_arg_unit(message: RemoteSystemDaemonMessageProtocol) { + // new LocalActorRef(remote.system, + // Props( + // context ⇒ { + // case (fun: Function[_, _], param: Any) ⇒ try { fun.asInstanceOf[Any ⇒ Unit].apply(param) } finally { context.self.stop() } + // }).copy(dispatcher = remote.computeGridDispatcher), remote.remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Tuple2[Function1[Any, Unit], Any]]) + // } + // + // // FIXME: handle real remote supervision, ticket #1408 + // def handle_fun1_arg_any(message: RemoteSystemDaemonMessageProtocol, sender: ActorRef) { + // implicit val s = sender + // new LocalActorRef(remote.system, + // Props( + // context ⇒ { + // case (fun: Function[_, _], param: Any) ⇒ try { context.sender ! fun.asInstanceOf[Any ⇒ Any](param) } finally { context.self.stop() } + // }).copy(dispatcher = remote.computeGridDispatcher), remote.remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Tuple2[Function1[Any, Any], Any]]) + // } def handleFailover(message: RemoteSystemDaemonMessageProtocol) { // val (from, to) = payloadFor(message, classOf[(InetSocketremoteDaemonServiceName, InetSocketremoteDaemonServiceName)]) @@ -241,50 +278,36 @@ class RemoteSystemDaemon(remote: Remote) extends Actor { } private def payloadFor[T](message: RemoteSystemDaemonMessageProtocol, clazz: Class[T]): T = { - serialization.deserialize(message.getPayload.toByteArray, clazz, None) match { + remote.serialization.deserialize(message.getPayload.toByteArray, clazz, None) match { case Left(error) ⇒ throw error case Right(instance) ⇒ instance.asInstanceOf[T] } } } -class RemoteMessage(input: RemoteMessageProtocol, remote: RemoteSupport, classLoader: Option[ClassLoader] = None) { +class RemoteMessage(input: RemoteMessageProtocol, system: ActorSystemImpl, classLoader: Option[ClassLoader] = None) { - val provider = remote.system.asInstanceOf[ActorSystemImpl].provider + def originalReceiver = input.getRecipient.getPath lazy val sender: ActorRef = - if (input.hasSender) provider.actorFor(provider.rootGuardian, input.getSender.getPath) - else remote.system.deadLetters + if (input.hasSender) system.provider.actorFor(system.provider.rootGuardian, input.getSender.getPath) + else system.deadLetters - lazy val recipient: ActorRef = remote.system.actorFor(input.getRecipient.getPath) + lazy val recipient: InternalActorRef = system.provider.actorFor(system.provider.rootGuardian, originalReceiver) - lazy val payload: Either[Throwable, AnyRef] = - if (input.hasException) Left(parseException()) - else Right(MessageSerializer.deserialize(remote.system, input.getMessage, classLoader)) + lazy val payload: AnyRef = MessageSerializer.deserialize(system, input.getMessage, classLoader) - protected def parseException(): Throwable = { - val exception = input.getException - val classname = exception.getClassname - try { - val exceptionClass = - if (classLoader.isDefined) classLoader.get.loadClass(classname) else Class.forName(classname) - exceptionClass - .getConstructor(Array[Class[_]](classOf[String]): _*) - .newInstance(exception.getMessage).asInstanceOf[Throwable] - } catch { - case problem: Exception ⇒ - remote.system.eventStream.publish(Logging.Error(problem, "RemoteMessage", problem.getMessage)) - CannotInstantiateRemoteExceptionDueToRemoteProtocolParsingErrorException(problem, classname, exception.getMessage) - } - } - - override def toString = "RemoteMessage: " + recipient + "(" + input.getRecipient.getPath + ") from " + sender + override def toString = "RemoteMessage: " + payload + " to " + recipient + "<+{" + originalReceiver + "} from " + sender } trait RemoteMarshallingOps { + def log: LoggingAdapter + def system: ActorSystem + def remote: Remote + protected def useUntrustedMode: Boolean def createMessageSendEnvelope(rmp: RemoteMessageProtocol): AkkaRemoteProtocol = { @@ -307,21 +330,12 @@ trait RemoteMarshallingOps { } def createRemoteMessageProtocolBuilder( - recipient: Either[ActorRef, ActorRefProtocol], - message: Either[Throwable, Any], + recipient: ActorRef, + message: Any, senderOption: Option[ActorRef]): RemoteMessageProtocol.Builder = { - val messageBuilder = RemoteMessageProtocol.newBuilder.setRecipient(recipient.fold(toRemoteActorRefProtocol _, identity)) - - message match { - case Right(message) ⇒ - messageBuilder.setMessage(MessageSerializer.serialize(system, message.asInstanceOf[AnyRef])) - case Left(exception) ⇒ - messageBuilder.setException(ExceptionProtocol.newBuilder - .setClassname(exception.getClass.getName) - .setMessage(Option(exception.getMessage).getOrElse("")) - .build) - } + val messageBuilder = RemoteMessageProtocol.newBuilder.setRecipient(toRemoteActorRefProtocol(recipient)) + messageBuilder.setMessage(MessageSerializer.serialize(system, message.asInstanceOf[AnyRef])) if (senderOption.isDefined) messageBuilder.setSender(toRemoteActorRefProtocol(senderOption.get)) @@ -329,15 +343,38 @@ trait RemoteMarshallingOps { } def receiveMessage(remoteMessage: RemoteMessage) { - val recipient = remoteMessage.recipient + log.debug("received message {}", remoteMessage) - remoteMessage.payload match { - case Left(t) ⇒ throw t - case Right(r) ⇒ r match { - case _: Terminate ⇒ if (useUntrustedMode) throw new SecurityException("RemoteModule server is operating is untrusted mode, can not stop the actor") else recipient.stop() - case _: AutoReceivedMessage if (useUntrustedMode) ⇒ throw new SecurityException("RemoteModule server is operating is untrusted mode, can not pass on a AutoReceivedMessage to the remote actor") - case m ⇒ recipient.!(m)(remoteMessage.sender) - } + val remoteDaemon = remote.remoteDaemon + + remoteMessage.recipient match { + case `remoteDaemon` ⇒ + remoteMessage.payload match { + case m: RemoteSystemDaemonMessageProtocol ⇒ + implicit val timeout = system.settings.ActorTimeout + try remoteDaemon ! m catch { + case e: Exception ⇒ log.error(e, "exception while processing remote command {} from {}", m.getMessageType(), remoteMessage.sender) + } + case x ⇒ log.warning("remoteDaemon received illegal message {} from {}", x, remoteMessage.sender) + } + case l @ (_: LocalActorRef | _: MinimalActorRef) ⇒ + remoteMessage.payload match { + case msg: SystemMessage ⇒ + if (useUntrustedMode) + throw new SecurityException("RemoteModule server is operating is untrusted mode, can not send system message") + else l.sendSystemMessage(msg) + case _: AutoReceivedMessage if (useUntrustedMode) ⇒ + throw new SecurityException("RemoteModule server is operating is untrusted mode, can not pass on a AutoReceivedMessage to the remote actor") + case m ⇒ l.!(m)(remoteMessage.sender) + } + case r: RemoteActorRef ⇒ + implicit val t = remote.transports + remoteMessage.originalReceiver match { + case ParsedActorPath(address, _) if address == remote.remoteDaemon.path.address ⇒ + r.!(remoteMessage.payload)(remoteMessage.sender) + case r ⇒ log.error("dropping message {} for non-local recipient {}", remoteMessage.payload, r) + } + case r ⇒ log.error("dropping message {} for non-local recipient {}", remoteMessage.payload, r) } } } diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index 2b1c1bb528..92582c4168 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -4,25 +4,17 @@ package akka.remote -import akka.AkkaException import akka.actor._ -import akka.actor.Actor._ -import akka.actor.Status._ -import akka.routing._ import akka.dispatch._ -import akka.util.duration._ -import akka.config.ConfigurationException -import akka.event.{ DeathWatch, Logging } +import akka.event.Logging import akka.serialization.Compression.LZF import akka.remote.RemoteProtocol._ import akka.remote.RemoteProtocol.RemoteSystemDaemonMessageType._ import com.google.protobuf.ByteString -import java.util.concurrent.atomic.AtomicBoolean import akka.event.EventStream -import java.util.concurrent.ConcurrentHashMap -import akka.dispatch.Promise -import java.net.InetAddress import akka.serialization.SerializationExtension +import akka.serialization.Serialization +import akka.config.ConfigurationException /** * Remote ActorRefProvider. Starts up actor on remote node and creates a RemoteActorRef representing it. @@ -36,264 +28,189 @@ class RemoteActorRefProvider( val log = Logging(eventStream, "RemoteActorRefProvider") + val remoteSettings = new RemoteSettings(settings.config, systemName) + def deathWatch = local.deathWatch def rootGuardian = local.rootGuardian def guardian = local.guardian def systemGuardian = local.systemGuardian - def nodename = remoteExtension.NodeName - def clustername = remoteExtension.ClusterName + def nodename = remoteSettings.NodeName + def clustername = remoteSettings.ClusterName + def terminationFuture = local.terminationFuture + def dispatcher = local.dispatcher - private val actors = new ConcurrentHashMap[String, AnyRef] + val deployer = new RemoteDeployer(settings) - /* - * The problem is that ActorRefs need a reference to the ActorSystem to - * provide their service. Hence they cannot be created while the - * constructors of ActorSystem and ActorRefProvider are still running. - * The solution is to split out that last part into an init() method, - * but it also requires these references to be @volatile and lazy. - */ - @volatile - private var system: ActorSystemImpl = _ - private lazy val remoteExtension = RemoteExtension(system) - private lazy val serialization = SerializationExtension(system) - lazy val rootPath: ActorPath = { - val remoteAddress = RemoteAddress(system.name, remoteExtension.serverSettings.Hostname, remoteExtension.serverSettings.Port) - new RootActorPath(remoteAddress) - } - 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) + val remote = new Remote(settings, remoteSettings) + implicit val transports = remote.transports - def init(_system: ActorSystemImpl) { - system = _system - local.init(_system) + val rootPath: ActorPath = RootActorPath(remote.remoteAddress) + + private val local = new LocalActorRefProvider(systemName, settings, eventStream, scheduler, _deadLetters, rootPath, deployer) + + def init(system: ActorSystemImpl) { + local.init(system) + remote.init(system, this) + local.registerExtraNames(Map(("remote", remote.remoteDaemon))) terminationFuture.onComplete(_ ⇒ remote.server.shutdown()) } - private[akka] def theOneWhoWalksTheBubblesOfSpaceTime: ActorRef = local.theOneWhoWalksTheBubblesOfSpaceTime - private[akka] def terminationFuture = local.terminationFuture - - private[akka] def deployer: Deployer = local.deployer - - def dispatcher = local.dispatcher - def defaultTimeout = settings.ActorTimeout - - def actorOf(system: ActorSystemImpl, props: Props, supervisor: InternalActorRef, name: String, systemService: Boolean): InternalActorRef = - if (systemService) local.actorOf(system, props, supervisor, name, systemService) + def actorOf(system: ActorSystemImpl, props: Props, supervisor: InternalActorRef, path: ActorPath, systemService: Boolean, deploy: Option[Deploy]): InternalActorRef = { + if (systemService) local.actorOf(system, props, supervisor, path, systemService, deploy) else { - val path = supervisor.path / name - val newFuture = Promise[ActorRef](system.settings.ActorTimeout)(dispatcher) - actors.putIfAbsent(path.toString, newFuture) match { // we won the race -- create the actor and resolve the future - case null ⇒ - val actor: InternalActorRef = try { - deployer.lookupDeploymentFor(path.toString) match { - case Some(DeploymentConfig.Deploy(_, _, routerType, nrOfInstances, DeploymentConfig.RemoteScope(remoteAddresses))) ⇒ + /* + * This needs to deal with “mangled” paths, which are created by remote + * deployment, also in this method. The scheme is the following: + * + * Whenever a remote deployment is found, create a path on that remote + * address below “remote”, including the current system’s identification + * as “sys@host:port” (typically; it will use whatever the remote + * transport uses). This means that on a path up an actor tree each node + * change introduces one layer or “remote/sys@host:port/” within the URI. + * + * Example: + * + * akka://sys@home:1234/remote/sys@remote:6667/remote/sys@other:3333/user/a/b/c + * + * means that the logical parent originates from “sys@other:3333” with + * one child (may be “a” or “b”) being deployed on “sys@remote:6667” and + * finally either “b” or “c” being created on “sys@home:1234”, where + * this whole thing actually resides. Thus, the logical path is + * “/user/a/b/c” and the physical path contains all remote placement + * information. + * + * Deployments are always looked up using the logical path, which is the + * purpose of the lookupRemotes internal method. + */ - def isReplicaNode: Boolean = remoteAddresses exists { _ == remote.remoteAddress } + @scala.annotation.tailrec + def lookupRemotes(p: Iterable[String]): Option[Deploy] = { + p.headOption match { + case None ⇒ None + case Some("remote") ⇒ lookupRemotes(p.drop(2)) + case Some("user") ⇒ deployer.lookup(p.drop(1).mkString("/", "/", "")) + case Some(_) ⇒ None + } + } - //system.eventHandler.debug(this, "%s: Deploy Remote Actor with address [%s] connected to [%s]: isReplica(%s)".format(system.defaultAddress, address, remoteAddresses.mkString, isReplicaNode)) + val elems = path.elements + val deployment = deploy orElse (elems.head match { + case "user" ⇒ deployer.lookup(elems.drop(1).mkString("/", "/", "")) + case "remote" ⇒ lookupRemotes(elems) + case _ ⇒ None + }) - if (isReplicaNode) { - // we are on one of the replica node for this remote actor - local.actorOf(system, props, supervisor, name, true) //FIXME systemService = true here to bypass Deploy, should be fixed when create-or-get is replaced by get-or-create (is this fixed now?) - } else { - - implicit val dispatcher = if (props.dispatcher == Props.defaultDispatcher) system.dispatcher else props.dispatcher - implicit val timeout = system.settings.ActorTimeout - - // we are on the single "reference" node uses the remote actors on the replica nodes - val routerFactory: () ⇒ Router = DeploymentConfig.routerTypeFor(routerType) match { - case RouterType.Direct ⇒ - if (remoteAddresses.size != 1) throw new ConfigurationException( - "Actor [%s] configured with Direct router must have exactly 1 remote node configured. Found [%s]" - .format(name, remoteAddresses.mkString(", "))) - () ⇒ new DirectRouter - - case RouterType.Broadcast ⇒ - if (remoteAddresses.size != 1) throw new ConfigurationException( - "Actor [%s] configured with Broadcast router must have exactly 1 remote node configured. Found [%s]" - .format(name, remoteAddresses.mkString(", "))) - () ⇒ new BroadcastRouter - - case RouterType.Random ⇒ - if (remoteAddresses.size < 1) throw new ConfigurationException( - "Actor [%s] configured with Random router must have at least 1 remote node configured. Found [%s]" - .format(name, remoteAddresses.mkString(", "))) - () ⇒ new RandomRouter - - case RouterType.RoundRobin ⇒ - if (remoteAddresses.size < 1) throw new ConfigurationException( - "Actor [%s] configured with RoundRobin router must have at least 1 remote node configured. Found [%s]" - .format(name, remoteAddresses.mkString(", "))) - () ⇒ new RoundRobinRouter - - case RouterType.ScatterGather ⇒ - if (remoteAddresses.size < 1) throw new ConfigurationException( - "Actor [%s] configured with ScatterGather router must have at least 1 remote node configured. Found [%s]" - .format(name, remoteAddresses.mkString(", "))) - () ⇒ new ScatterGatherFirstCompletedRouter()(dispatcher, defaultTimeout) - - case RouterType.LeastCPU ⇒ sys.error("Router LeastCPU not supported yet") - case RouterType.LeastRAM ⇒ sys.error("Router LeastRAM not supported yet") - case RouterType.LeastMessages ⇒ sys.error("Router LeastMessages not supported yet") - case RouterType.Custom(implClass) ⇒ () ⇒ Routing.createCustomRouter(implClass) - } - - val connections = (Map.empty[RemoteAddress, ActorRef] /: remoteAddresses) { (conns, a) ⇒ - val remoteAddress = RemoteAddress(system.name, a.host, a.port) - conns + (remoteAddress -> RemoteActorRef(remote.system.provider, remote.server, remoteAddress, path, None)) - } - - val connectionManager = new RemoteConnectionManager(system, remote, connections) - - connections.keys foreach { useActorOnNode(system, _, path.toString, props.creator) } - - actorOf(system, RoutedProps(routerFactory = routerFactory, connectionManager = connectionManager), supervisor, name) - } - - case deploy ⇒ local.actorOf(system, props, supervisor, name, systemService) - } - } catch { - case e: Exception ⇒ - newFuture completeWithException e // so the other threads gets notified of error - throw e + deployment match { + case Some(Deploy(_, _, _, _, RemoteScope(address))) ⇒ + // FIXME RK this should be done within the deployer, i.e. the whole parsing business + address.parse(remote.transports) match { + case Left(x) ⇒ + throw new ConfigurationException("cannot parse remote address: " + x) + case Right(addr) ⇒ + if (addr == rootPath.address) local.actorOf(system, props, supervisor, path, false, deployment) + else { + val rpath = RootActorPath(addr) / "remote" / rootPath.address.hostPort / path.elements + useActorOnNode(rpath, props.creator, supervisor) + new RemoteActorRef(this, remote.server, rpath, supervisor, None) + } } - // actor foreach system.registry.register // only for ActorRegistry backward compat, will be removed later - - newFuture completeWithResult actor - actors.replace(path.toString, newFuture, actor) - actor - case actor: InternalActorRef ⇒ actor - case future: Future[_] ⇒ future.get.asInstanceOf[InternalActorRef] + case _ ⇒ local.actorOf(system, props, supervisor, path, systemService, deployment) } } - - /** - * Copied from LocalActorRefProvider... - */ - // FIXME: implement supervision, ticket #1408 - 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) } - def actorFor(path: ActorPath): InternalActorRef = local.actorFor(path) - def actorFor(ref: InternalActorRef, path: String): InternalActorRef = local.actorFor(ref, path) + def actorFor(path: ActorPath): InternalActorRef = path.root match { + case `rootPath` ⇒ actorFor(rootGuardian, path.elements) + case RootActorPath(_: RemoteSystemAddress[_], _) ⇒ new RemoteActorRef(this, remote.server, path, Nobody, None) + case _ ⇒ local.actorFor(path) + } + + def actorFor(ref: InternalActorRef, path: String): InternalActorRef = path match { + case ParsedActorPath(address, elems) ⇒ + if (address == rootPath.address) actorFor(rootGuardian, elems) + else new RemoteActorRef(this, remote.server, new RootActorPath(address) / elems, Nobody, None) + case _ ⇒ local.actorFor(ref, path) + } + def actorFor(ref: InternalActorRef, path: Iterable[String]): InternalActorRef = local.actorFor(ref, path) - // TODO remove me - val optimizeLocal = new AtomicBoolean(true) - def optimizeLocalScoped_?() = optimizeLocal.get - - /** - * Returns true if the actor was in the provider's cache and evicted successfully, else false. - */ - private[akka] def evict(path: ActorPath): Boolean = actors.remove(path) ne null + def ask(within: Timeout): Option[AskActorRef] = local.ask(within) /** * Using (checking out) actor on a specific node. */ - def useActorOnNode(system: ActorSystem, remoteAddress: RemoteAddress, actorPath: String, actorFactory: () ⇒ Actor) { - log.debug("[{}] Instantiating Actor [{}] on node [{}]", rootPath, actorPath, remoteAddress) + def useActorOnNode(path: ActorPath, actorFactory: () ⇒ Actor, supervisor: ActorRef) { + log.debug("[{}] Instantiating Remote Actor [{}]", rootPath, path) val actorFactoryBytes = - serialization.serialize(actorFactory) match { + remote.serialization.serialize(actorFactory) match { case Left(error) ⇒ throw error - case Right(bytes) ⇒ if (remoteExtension.ShouldCompressData) LZF.compress(bytes) else bytes + case Right(bytes) ⇒ if (remoteSettings.ShouldCompressData) LZF.compress(bytes) else bytes } val command = RemoteSystemDaemonMessageProtocol.newBuilder .setMessageType(USE) - .setActorPath(actorPath) + .setActorPath(path.toString) .setPayload(ByteString.copyFrom(actorFactoryBytes)) + .setSupervisor(supervisor.path.toString) .build() - 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) - - sendCommandToRemoteNode(connection, command, withACK = true) // ensure we get an ACK on the USE command + // we don’t wait for the ACK, because the remote end will process this command before any other message to the new actor + actorFor(RootActorPath(path.address) / "remote") ! command } - - private def sendCommandToRemoteNode(connection: ActorRef, command: RemoteSystemDaemonMessageProtocol, withACK: Boolean) { - if (withACK) { - try { - val f = connection ? (command, remoteExtension.RemoteSystemDaemonAckTimeout) - (try f.await.value catch { case _: FutureTimeoutException ⇒ None }) match { - case Some(Right(receiver)) ⇒ - log.debug("Remote system command sent to [{}] successfully received", receiver) - - case Some(Left(cause)) ⇒ - log.error(cause, cause.toString) - throw cause - - case None ⇒ - 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.path, e.toString) - throw e - } - } else { - connection ! command - } - } - - private[akka] def createDeathWatch(): DeathWatch = local.createDeathWatch() //FIXME Implement Remote DeathWatch, ticket ##1190 - - private[akka] def ask(message: Any, recipient: ActorRef, within: Timeout): Future[Any] = local.ask(message, recipient, within) - - private[akka] def tempPath = local.tempPath } /** * Remote ActorRef that is used when referencing the Actor on a different node than its "home" node. * This reference is network-aware (remembers its origin) and immutable. */ -private[akka] case class RemoteActorRef private[akka] ( - provider: ActorRefProvider, - remote: RemoteSupport, - remoteAddress: RemoteAddress, - path: ActorPath, +private[akka] class RemoteActorRef private[akka] ( + provider: RemoteActorRefProvider, + remote: RemoteSupport[ParsedTransportAddress], + val path: ActorPath, + val getParent: InternalActorRef, loader: Option[ClassLoader]) extends InternalActorRef { - // FIXME RK - def getParent = Nobody - def getChild(name: Iterator[String]) = Nobody + def getChild(name: Iterator[String]): InternalActorRef = { + val s = name.toStream + s.headOption match { + case None ⇒ this + case Some("..") ⇒ getParent getChild name + case _ ⇒ new RemoteActorRef(provider, remote, path / s, Nobody, loader) + } + } @volatile private var running: Boolean = true def isTerminated: Boolean = !running - def sendSystemMessage(message: SystemMessage): Unit = throw new UnsupportedOperationException("Not supported for RemoteActorRef") + def sendSystemMessage(message: SystemMessage): Unit = remote.send(message, None, this, loader) - override def !(message: Any)(implicit sender: ActorRef = null): Unit = remote.send(message, Option(sender), remoteAddress, this, loader) + override def !(message: Any)(implicit sender: ActorRef = null): Unit = remote.send(message, Option(sender), this, loader) - override def ?(message: Any)(implicit timeout: Timeout): Future[Any] = provider.ask(message, this, timeout) - - def suspend(): Unit = () - - def resume(): Unit = () - - def stop() { - synchronized { - if (running) { - running = false - remote.send(new Terminate(), None, remoteAddress, this, loader) - } + override def ?(message: Any)(implicit timeout: Timeout): Future[Any] = { + provider.ask(timeout) match { + case Some(a) ⇒ + this.!(message)(a) + a.result + case None ⇒ + this.!(message)(null) + new DefaultPromise[Any](0)(provider.dispatcher) } } + def suspend(): Unit = sendSystemMessage(Suspend()) + + def resume(): Unit = sendSystemMessage(Resume()) + + def stop(): Unit = sendSystemMessage(Terminate()) + + def restart(cause: Throwable): Unit = sendSystemMessage(Recreate(cause)) + @throws(classOf[java.io.ObjectStreamException]) private def writeReplace(): AnyRef = SerializedActorRef(path.toString) - - def restart(cause: Throwable): Unit = () } diff --git a/akka-remote/src/main/scala/akka/remote/RemoteConnectionManager.scala b/akka-remote/src/main/scala/akka/remote/RemoteConnectionManager.scala index aa3f577ba4..9f623ff853 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteConnectionManager.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteConnectionManager.scala @@ -18,15 +18,15 @@ import java.util.concurrent.atomic.AtomicReference * Remote connection manager, manages remote connections, e.g. RemoteActorRef's. */ class RemoteConnectionManager( - system: ActorSystem, + system: ActorSystemImpl, remote: Remote, - initialConnections: Map[RemoteAddress, ActorRef] = Map.empty[RemoteAddress, ActorRef]) + initialConnections: Map[ParsedTransportAddress, ActorRef] = Map.empty[ParsedTransportAddress, ActorRef]) extends ConnectionManager { val log = Logging(system, "RemoteConnectionManager") // FIXME is this VersionedIterable really needed? It is not used I think. Complicates API. See 'def connections' etc. - case class State(version: Long, connections: Map[RemoteAddress, ActorRef]) + case class State(version: Long, connections: Map[ParsedTransportAddress, ActorRef]) extends VersionedIterable[ActorRef] { def iterable: Iterable[ActorRef] = connections.values } @@ -52,7 +52,7 @@ class RemoteConnectionManager( def size: Int = connections.connections.size - def connectionFor(address: RemoteAddress): Option[ActorRef] = connections.connections.get(address) + def connectionFor(address: ParsedTransportAddress): Option[ActorRef] = connections.connections.get(address) def isEmpty: Boolean = connections.connections.isEmpty @@ -61,7 +61,7 @@ class RemoteConnectionManager( } @tailrec - final def failOver(from: RemoteAddress, to: RemoteAddress) { + final def failOver(from: ParsedTransportAddress, to: ParsedTransportAddress) { log.debug("Failing over connection from [{}] to [{}]", from, to) val oldState = state.get @@ -92,8 +92,8 @@ class RemoteConnectionManager( val oldState = state.get() var changed = false - var faultyAddress: RemoteAddress = null - var newConnections = Map.empty[RemoteAddress, ActorRef] + var faultyAddress: ParsedTransportAddress = null + var newConnections = Map.empty[ParsedTransportAddress, ActorRef] oldState.connections.keys foreach { address ⇒ val actorRef: ActorRef = oldState.connections.get(address).get @@ -119,7 +119,7 @@ class RemoteConnectionManager( } @tailrec - final def putIfAbsent(address: RemoteAddress, newConnectionFactory: () ⇒ ActorRef): ActorRef = { + final def putIfAbsent(address: ParsedTransportAddress, newConnectionFactory: () ⇒ ActorRef): ActorRef = { val oldState = state.get() val oldConnections = oldState.connections @@ -146,6 +146,6 @@ class RemoteConnectionManager( } } - private[remote] def newConnection(remoteAddress: RemoteAddress, actorPath: ActorPath) = - RemoteActorRef(remote.system.provider, remote.server, remoteAddress, actorPath, None) + private[remote] def newConnection(remoteAddress: ParsedTransportAddress, actorPath: ActorPath) = + new RemoteActorRef(remote.provider, remote.server, actorPath, Nobody, None) } diff --git a/akka-remote/src/main/scala/akka/remote/RemoteDeployer.scala b/akka-remote/src/main/scala/akka/remote/RemoteDeployer.scala new file mode 100644 index 0000000000..0819d34019 --- /dev/null +++ b/akka-remote/src/main/scala/akka/remote/RemoteDeployer.scala @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.remote + +import akka.actor._ +import akka.routing._ +import com.typesafe.config._ +import akka.config.ConfigurationException + +case class RemoteScope(node: UnparsedSystemAddress[UnparsedTransportAddress]) extends Scope + +class RemoteDeployer(_settings: ActorSystem.Settings) extends Deployer(_settings) { + + override protected def parseConfig(path: String, config: Config): Option[Deploy] = { + import scala.collection.JavaConverters._ + import akka.util.ReflectiveAccess._ + + super.parseConfig(path, config) match { + case d @ Some(deploy) ⇒ + deploy.config.getString("remote") match { + case RemoteAddressExtractor(r) ⇒ Some(deploy.copy(scope = RemoteScope(r))) + case str ⇒ + if (!str.isEmpty) throw new ConfigurationException("unparseable remote node name " + str) + val nodes = deploy.config.getStringList("target.nodes").asScala + if (nodes.isEmpty || deploy.routing == NoRouter) d + else { + val r = deploy.routing match { + case RoundRobinRouter(x, _) ⇒ RemoteRoundRobinRouter(x, nodes) + case RandomRouter(x, _) ⇒ RemoteRandomRouter(x, nodes) + case BroadcastRouter(x, _) ⇒ RemoteBroadcastRouter(x, nodes) + case ScatterGatherFirstCompletedRouter(x, _) ⇒ RemoteScatterGatherFirstCompletedRouter(x, nodes) + } + Some(deploy.copy(routing = r)) + } + } + case None ⇒ None + } + } + +} \ No newline at end of file diff --git a/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala b/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala index 4122c539df..5c5d41b0d4 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala @@ -12,12 +12,7 @@ import com.eaio.uuid.UUID import akka.actor._ import scala.collection.JavaConverters._ -object RemoteExtension extends ExtensionId[RemoteExtensionSettings] with ExtensionIdProvider { - def lookup() = this - def createExtension(system: ActorSystemImpl) = new RemoteExtensionSettings(system.settings.config, system.name) -} - -class RemoteExtensionSettings(val config: Config, val systemName: String) extends Extension { +class RemoteSettings(val config: Config, val systemName: String) extends Extension { import config._ @@ -31,7 +26,9 @@ class RemoteExtensionSettings(val config: Config, val systemName: String) extend // TODO cluster config will go into akka-cluster/reference.conf when we enable that module val ClusterName = getString("akka.cluster.name") - val SeedNodes = Set.empty[RemoteAddress] ++ getStringList("akka.cluster.seed-nodes").asScala.toSeq.map(RemoteAddress(_, systemName)) + val SeedNodes = Set.empty[RemoteNettyAddress] ++ getStringList("akka.cluster.seed-nodes").asScala.collect { + case RemoteAddressExtractor(addr) ⇒ addr.transport + } val NodeName: String = config.getString("akka.cluster.nodename") match { case "" ⇒ throw new ConfigurationException("akka.cluster.nodename configuration property must be defined") @@ -78,5 +75,8 @@ class RemoteExtensionSettings(val config: Config, val systemName: String) extend val ConnectionTimeout = Duration(config.getMilliseconds("akka.remote.server.connection-timeout"), MILLISECONDS) val Backlog = config.getInt("akka.remote.server.backlog") + + // TODO handle the system name right and move this to config file syntax + val URI = "akka://sys@" + Hostname + ":" + Port } } \ No newline at end of file diff --git a/akka-remote/src/main/scala/akka/remote/RemoteInterface.scala b/akka-remote/src/main/scala/akka/remote/RemoteInterface.scala new file mode 100644 index 0000000000..6dbc50eab8 --- /dev/null +++ b/akka-remote/src/main/scala/akka/remote/RemoteInterface.scala @@ -0,0 +1,259 @@ +/** + * Copyright (C) 2009-2010 Typesafe Inc. + */ + +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 +import java.net.UnknownHostException +import java.net.UnknownServiceException + +/** + * Interface for remote transports to encode their addresses. The three parts + * are named according to the URI spec (precisely java.net.URI) which is used + * for parsing. That means that the address’ parts must conform to what an + * URI expects, but otherwise each transport may assign a different meaning + * to these parts. + */ +trait RemoteTransportAddress { + def protocol: String + def host: String + def port: Int +} + +trait ParsedTransportAddress extends RemoteTransportAddress + +case class RemoteNettyAddress(host: String, ip: Option[InetAddress], port: Int) extends ParsedTransportAddress { + def protocol = "akka" +} + +object RemoteNettyAddress { + def apply(host: String, port: Int): RemoteNettyAddress = { + // FIXME this may BLOCK for extended periods of time! + val ip = try Some(InetAddress.getByName(host)) catch { case _: UnknownHostException ⇒ None } + new RemoteNettyAddress(host, ip, port) + } + def apply(s: String): RemoteNettyAddress = { + val RE = """([^:]+):(\d+)""".r + s match { + case RE(h, p) ⇒ apply(h, Integer.parseInt(p)) + case _ ⇒ throw new IllegalArgumentException("cannot parse " + s + " as ") + } + } +} + +case class UnparsedTransportAddress(protocol: String, host: String, port: Int) extends RemoteTransportAddress { + def parse(transports: TransportsMap): RemoteTransportAddress = + transports.get(protocol) + .map(_(host, port)) + .toRight("protocol " + protocol + " not known") + .joinRight.fold(UnparseableTransportAddress(protocol, host, port, _), identity) +} + +case class UnparseableTransportAddress(protocol: String, host: String, port: Int, error: String) extends RemoteTransportAddress + +case class RemoteSystemAddress[+T <: ParsedTransportAddress](system: String, transport: T) extends Address { + def protocol = transport.protocol + @transient + lazy val hostPort = system + "@" + transport.host + ":" + transport.port +} + +case class UnparsedSystemAddress[+T <: RemoteTransportAddress](system: Option[String], transport: T) { + def parse(transports: TransportsMap): Either[UnparsedSystemAddress[UnparseableTransportAddress], RemoteSystemAddress[ParsedTransportAddress]] = + system match { + case Some(sys) ⇒ + transport match { + case x: ParsedTransportAddress ⇒ Right(RemoteSystemAddress(sys, x)) + case y: UnparsedTransportAddress ⇒ + y.parse(transports) match { + case x: ParsedTransportAddress ⇒ Right(RemoteSystemAddress(sys, x)) + case y: UnparseableTransportAddress ⇒ Left(UnparsedSystemAddress(system, y)) + case z ⇒ Left(UnparsedSystemAddress(system, UnparseableTransportAddress(z.protocol, z.host, z.port, "cannot parse " + z))) + } + case z ⇒ Left(UnparsedSystemAddress(system, UnparseableTransportAddress(z.protocol, z.host, z.port, "cannot parse " + z))) + } + case None ⇒ Left(UnparsedSystemAddress(None, UnparseableTransportAddress(transport.protocol, transport.host, transport.port, "no system name specified"))) + } +} + +object RemoteAddressExtractor { + def unapply(s: String): Option[UnparsedSystemAddress[UnparsedTransportAddress]] = { + try { + val uri = new URI(s) + if (uri.getScheme == null || uri.getHost == null || uri.getPort == -1) None + else Some(UnparsedSystemAddress(Option(uri.getUserInfo), UnparsedTransportAddress(uri.getScheme, uri.getHost, uri.getPort))) + } catch { + case _: URISyntaxException ⇒ None + } + } +} + +object RemoteActorPath { + def unapply(addr: String): Option[(UnparsedSystemAddress[UnparsedTransportAddress], Iterable[String])] = { + try { + val uri = new URI(addr) + if (uri.getScheme == null || uri.getUserInfo == null || uri.getHost == null || uri.getPort == -1 || uri.getPath == null) None + else Some(UnparsedSystemAddress(Some(uri.getUserInfo), UnparsedTransportAddress(uri.getScheme, uri.getHost, uri.getPort)), + ActorPath.split(uri.getPath).drop(1)) + } catch { + case _: URISyntaxException ⇒ None + } + } +} + +object ParsedActorPath { + def unapply(addr: String)(implicit transports: TransportsMap): Option[(RemoteSystemAddress[ParsedTransportAddress], Iterable[String])] = { + try { + val uri = new URI(addr) + if (uri.getScheme == null || uri.getUserInfo == null || uri.getHost == null || uri.getPort == -1 || uri.getPath == null) None + else + UnparsedSystemAddress(Some(uri.getUserInfo), UnparsedTransportAddress(uri.getScheme, uri.getHost, uri.getPort)).parse(transports) match { + case Left(_) ⇒ None + case Right(x) ⇒ Some(x, ActorPath.split(uri.getPath).drop(1)) + } + } catch { + case _: URISyntaxException ⇒ None + } + } +} + +class RemoteException(message: String) extends AkkaException(message) + +trait RemoteModule { + protected[akka] def notifyListeners(message: RemoteLifeCycleEvent): Unit +} + +/** + * Remote life-cycle events. + */ +sealed trait RemoteLifeCycleEvent + +/** + * Life-cycle events for RemoteClient. + */ +trait RemoteClientLifeCycleEvent extends RemoteLifeCycleEvent { + def remoteAddress: ParsedTransportAddress +} + +case class RemoteClientError[T <: ParsedTransportAddress]( + @BeanProperty cause: Throwable, + @BeanProperty remote: RemoteSupport[T], + @BeanProperty remoteAddress: T) extends RemoteClientLifeCycleEvent + +case class RemoteClientDisconnected[T <: ParsedTransportAddress]( + @BeanProperty remote: RemoteSupport[T], + @BeanProperty remoteAddress: T) extends RemoteClientLifeCycleEvent + +case class RemoteClientConnected[T <: ParsedTransportAddress]( + @BeanProperty remote: RemoteSupport[T], + @BeanProperty remoteAddress: T) extends RemoteClientLifeCycleEvent + +case class RemoteClientStarted[T <: ParsedTransportAddress]( + @BeanProperty remote: RemoteSupport[T], + @BeanProperty remoteAddress: T) extends RemoteClientLifeCycleEvent + +case class RemoteClientShutdown[T <: ParsedTransportAddress]( + @BeanProperty remote: RemoteSupport[T], + @BeanProperty remoteAddress: T) extends RemoteClientLifeCycleEvent + +case class RemoteClientWriteFailed[T <: ParsedTransportAddress]( + @BeanProperty request: AnyRef, + @BeanProperty cause: Throwable, + @BeanProperty remote: RemoteSupport[T], + @BeanProperty remoteAddress: T) extends RemoteClientLifeCycleEvent + +/** + * Life-cycle events for RemoteServer. + */ +trait RemoteServerLifeCycleEvent extends RemoteLifeCycleEvent + +case class RemoteServerStarted[T <: ParsedTransportAddress]( + @BeanProperty remote: RemoteSupport[T]) extends RemoteServerLifeCycleEvent +case class RemoteServerShutdown[T <: ParsedTransportAddress]( + @BeanProperty remote: RemoteSupport[T]) extends RemoteServerLifeCycleEvent +case class RemoteServerError[T <: ParsedTransportAddress]( + @BeanProperty val cause: Throwable, + @BeanProperty remote: RemoteSupport[T]) extends RemoteServerLifeCycleEvent +case class RemoteServerClientConnected[T <: ParsedTransportAddress]( + @BeanProperty remote: RemoteSupport[T], + @BeanProperty val clientAddress: Option[T]) extends RemoteServerLifeCycleEvent +case class RemoteServerClientDisconnected[T <: ParsedTransportAddress]( + @BeanProperty remote: RemoteSupport[T], + @BeanProperty val clientAddress: Option[T]) extends RemoteServerLifeCycleEvent +case class RemoteServerClientClosed[T <: ParsedTransportAddress]( + @BeanProperty remote: RemoteSupport[T], + @BeanProperty val clientAddress: Option[T]) extends RemoteServerLifeCycleEvent +case class RemoteServerWriteFailed[T <: ParsedTransportAddress]( + @BeanProperty request: AnyRef, + @BeanProperty cause: Throwable, + @BeanProperty server: RemoteSupport[T], + @BeanProperty remoteAddress: Option[T]) extends RemoteServerLifeCycleEvent + +/** + * Thrown for example when trying to send a message using a RemoteClient that is either not started or shut down. + */ +class RemoteClientException[T <: ParsedTransportAddress] private[akka] ( + message: String, + @BeanProperty val client: RemoteSupport[T], + val remoteAddress: T, cause: Throwable = null) extends AkkaException(message, cause) + +/** + * Thrown when the remote server actor dispatching fails for some reason. + */ +class RemoteServerException private[akka] (message: String) extends AkkaException(message) + +/** + * Thrown when a remote exception sent over the wire cannot be loaded and instantiated + */ +case class CannotInstantiateRemoteExceptionDueToRemoteProtocolParsingErrorException private[akka] (cause: Throwable, originalClassName: String, originalMessage: String) + extends AkkaException("\nParsingError[%s]\nOriginalException[%s]\nOriginalMessage[%s]" + .format(cause.toString, originalClassName, originalMessage)) { + override def printStackTrace = cause.printStackTrace + override def printStackTrace(printStream: PrintStream) = cause.printStackTrace(printStream) + override def printStackTrace(printWriter: PrintWriter) = cause.printStackTrace(printWriter) +} + +abstract class RemoteSupport[-T <: ParsedTransportAddress](val system: ActorSystemImpl) { + /** + * Shuts down the remoting + */ + def shutdown(): Unit + + /** + * Gets the name of the server instance + */ + def name: String + + /** + * Starts up the remoting + */ + def start(loader: Option[ClassLoader]): Unit + + /** + * Shuts down a specific client connected to the supplied remote address returns true if successful + */ + def shutdownClientConnection(address: T): Boolean + + /** + * Restarts a specific client connected to the supplied remote address, but only if the client is not shut down + */ + def restartClientConnection(address: T): Boolean + + /** Methods that needs to be implemented by a transport **/ + + protected[akka] def send(message: Any, + senderOption: Option[ActorRef], + recipient: RemoteActorRef, + loader: Option[ClassLoader]): Unit + + protected[akka] def notifyListeners(message: RemoteLifeCycleEvent): Unit = system.eventStream.publish(message) + + override def toString = name +} 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 04e2483345..b9edbc9e60 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -38,7 +38,7 @@ class RemoteClientMessageBufferException(message: String, cause: Throwable = nul */ abstract class RemoteClient private[akka] ( val remoteSupport: NettyRemoteSupport, - val remoteAddress: RemoteAddress) { + val remoteAddress: RemoteNettyAddress) { val log = Logging(remoteSupport.system, "RemoteClient") @@ -54,13 +54,13 @@ abstract class RemoteClient private[akka] ( def shutdown(): Boolean - def isBoundTo(address: RemoteAddress): Boolean = remoteAddress == address + def isBoundTo(address: RemoteNettyAddress): Boolean = remoteAddress == address /** * Converts the message to the wireprotocol and sends the message across the wire */ def send(message: Any, senderOption: Option[ActorRef], recipient: ActorRef): Unit = if (isRunning) { - send(remoteSupport.createRemoteMessageProtocolBuilder(Left(recipient), Right(message), senderOption).build) + send(remoteSupport.createRemoteMessageProtocolBuilder(recipient, message, senderOption).build) } else { val exception = new RemoteClientException("RemoteModule client is not running, make sure you have invoked 'RemoteClient.connect()' before using it.", remoteSupport, remoteAddress) remoteSupport.notifyListeners(RemoteClientError(exception, remoteSupport, remoteAddress)) @@ -71,7 +71,7 @@ abstract class RemoteClient private[akka] ( * Sends the message across the wire */ def send(request: RemoteMessageProtocol): Unit = { - log.debug("Sending message: {}", new RemoteMessage(request, remoteSupport)) + log.debug("Sending message: {}", new RemoteMessage(request, remoteSupport.system)) try { val payload = remoteSupport.createMessageSendEnvelope(request) @@ -95,7 +95,7 @@ abstract class RemoteClient private[akka] ( class PassiveRemoteClient(val currentChannel: Channel, remoteSupport: NettyRemoteSupport, - remoteAddress: RemoteAddress) + remoteAddress: RemoteNettyAddress) extends RemoteClient(remoteSupport, remoteAddress) { def connect(reconnectIfAlreadyConnected: Boolean = false): Boolean = runSwitch switchOn { @@ -116,10 +116,12 @@ class PassiveRemoteClient(val currentChannel: Channel, */ class ActiveRemoteClient private[akka] ( remoteSupport: NettyRemoteSupport, - remoteAddress: RemoteAddress, + remoteAddress: RemoteNettyAddress, val loader: Option[ClassLoader] = None) extends RemoteClient(remoteSupport, remoteAddress) { + if (remoteAddress.ip.isEmpty) throw new java.net.UnknownHostException(remoteAddress.host) + import remoteSupport.clientSettings._ //TODO rewrite to a wrapper object (minimize volatile access and maximize encapsulation) @@ -148,8 +150,9 @@ 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.host) - .setPort(senderRemoteAddress.port) + .setSystem(senderRemoteAddress.system) + .setHostname(senderRemoteAddress.transport.host) + .setPort(senderRemoteAddress.transport.port) .build) connection.getChannel.write(remoteSupport.createControlEnvelope(handshake.build)) } @@ -162,7 +165,7 @@ class ActiveRemoteClient private[akka] ( def attemptReconnect(): Boolean = { log.debug("Remote client reconnecting to [{}]", remoteAddress) - val connection = bootstrap.connect(new InetSocketAddress(remoteAddress.ip, remoteAddress.port)) + val connection = bootstrap.connect(new InetSocketAddress(remoteAddress.ip.get, remoteAddress.port)) openChannels.add(connection.awaitUninterruptibly.getChannel) // Wait until the connection attempt succeeds or fails. if (!connection.isSuccess) { @@ -184,7 +187,7 @@ class ActiveRemoteClient private[akka] ( log.debug("Starting remote client connection to [{}]", remoteAddress) - connection = bootstrap.connect(new InetSocketAddress(remoteAddress.ip, remoteAddress.port)) + connection = bootstrap.connect(new InetSocketAddress(remoteAddress.ip.get, remoteAddress.port)) val channel = connection.awaitUninterruptibly.getChannel openChannels.add(channel) @@ -242,7 +245,7 @@ class ActiveRemoteClient private[akka] ( class ActiveRemoteClientPipelineFactory( name: String, bootstrap: ClientBootstrap, - remoteAddress: RemoteAddress, + remoteAddress: RemoteNettyAddress, client: ActiveRemoteClient) extends ChannelPipelineFactory { import client.remoteSupport.clientSettings._ @@ -263,7 +266,7 @@ class ActiveRemoteClientPipelineFactory( class ActiveRemoteClientHandler( val name: String, val bootstrap: ClientBootstrap, - val remoteAddress: RemoteAddress, + val remoteAddress: RemoteNettyAddress, val timer: HashedWheelTimer, val client: ActiveRemoteClient) extends SimpleChannelUpstreamHandler { @@ -283,7 +286,7 @@ class ActiveRemoteClientHandler( } case arp: AkkaRemoteProtocol if arp.hasMessage ⇒ - client.remoteSupport.receiveMessage(new RemoteMessage(arp.getMessage, client.remoteSupport, client.loader)) + client.remoteSupport.receiveMessage(new RemoteMessage(arp.getMessage, client.remoteSupport.system, client.loader)) case other ⇒ throw new RemoteClientException("Unknown message received in remote client handler: " + other, client.remoteSupport, client.remoteAddress) @@ -341,17 +344,18 @@ class ActiveRemoteClientHandler( /** * Provides the implementation of the Netty remote support */ -class NettyRemoteSupport(_system: ActorSystem, val remote: Remote) extends RemoteSupport(_system) with RemoteMarshallingOps { +class NettyRemoteSupport(_system: ActorSystemImpl, val remote: Remote, val address: RemoteSystemAddress[RemoteNettyAddress]) + extends RemoteSupport[RemoteNettyAddress](_system) with RemoteMarshallingOps { val log = Logging(system, "NettyRemoteSupport") - val serverSettings = RemoteExtension(system).serverSettings - val clientSettings = RemoteExtension(system).clientSettings + val serverSettings = remote.remoteSettings.serverSettings + val clientSettings = remote.remoteSettings.clientSettings val timer: HashedWheelTimer = new HashedWheelTimer _system.registerOnTermination(timer.stop()) //Shut this guy down at the end - private val remoteClients = new HashMap[RemoteAddress, RemoteClient] + private val remoteClients = new HashMap[RemoteNettyAddress, RemoteClient] private val clientsLock = new ReentrantReadWriteLock override protected def useUntrustedMode = serverSettings.UntrustedMode @@ -359,10 +363,17 @@ class NettyRemoteSupport(_system: ActorSystem, val remote: Remote) extends Remot protected[akka] def send( message: Any, senderOption: Option[ActorRef], - recipientAddress: RemoteAddress, - recipient: ActorRef, + recipient: RemoteActorRef, loader: Option[ClassLoader]): Unit = { + val recipientAddress = recipient.path.address match { + case RemoteSystemAddress(sys, transport) ⇒ + transport match { + case x: RemoteNettyAddress ⇒ x + case _ ⇒ throw new IllegalArgumentException("invoking NettyRemoteSupport.send with foreign target address " + transport) + } + } + clientsLock.readLock.lock try { val client = remoteClients.get(recipientAddress) match { @@ -394,7 +405,7 @@ class NettyRemoteSupport(_system: ActorSystem, val remote: Remote) extends Remot } } - def bindClient(remoteAddress: RemoteAddress, client: RemoteClient, putIfAbsent: Boolean = false): Boolean = { + def bindClient(remoteAddress: RemoteNettyAddress, client: RemoteClient, putIfAbsent: Boolean = false): Boolean = { clientsLock.writeLock().lock() try { if (putIfAbsent && remoteClients.contains(remoteAddress)) false @@ -408,7 +419,7 @@ class NettyRemoteSupport(_system: ActorSystem, val remote: Remote) extends Remot } } - def unbindClient(remoteAddress: RemoteAddress): Unit = { + def unbindClient(remoteAddress: RemoteNettyAddress): Unit = { clientsLock.writeLock().lock() try { remoteClients.foreach { case (k, v) ⇒ if (v.isBoundTo(remoteAddress)) { v.shutdown(); remoteClients.remove(k) } } @@ -417,7 +428,7 @@ class NettyRemoteSupport(_system: ActorSystem, val remote: Remote) extends Remot } } - def shutdownClientConnection(remoteAddress: RemoteAddress): Boolean = { + def shutdownClientConnection(remoteAddress: RemoteNettyAddress): Boolean = { clientsLock.writeLock().lock() try { remoteClients.remove(remoteAddress) match { @@ -429,7 +440,7 @@ class NettyRemoteSupport(_system: ActorSystem, val remote: Remote) extends Remot } } - def restartClientConnection(remoteAddress: RemoteAddress): Boolean = { + def restartClientConnection(remoteAddress: RemoteNettyAddress): Boolean = { clientsLock.readLock().lock() try { remoteClients.get(remoteAddress) match { @@ -455,11 +466,13 @@ class NettyRemoteSupport(_system: ActorSystem, val remote: Remote) extends Remot def isRunning = _isRunning.isOn - def start(loader: Option[ClassLoader] = None): Unit = _isRunning switchOn { - try { - currentServer.set(Some(new NettyRemoteServer(this, loader))) - } catch { - case e: Exception ⇒ notifyListeners(RemoteServerError(e, this)) + def start(loader: Option[ClassLoader] = None): Unit = { + _isRunning switchOn { + try { + currentServer.set(Some(new NettyRemoteServer(this, loader, address))) + } catch { + case e: Exception ⇒ notifyListeners(RemoteServerError(e, this)) + } } } @@ -479,11 +492,14 @@ class NettyRemoteSupport(_system: ActorSystem, val remote: Remote) extends Remot } } -class NettyRemoteServer(val remoteSupport: NettyRemoteSupport, val loader: Option[ClassLoader]) { +class NettyRemoteServer( + val remoteSupport: NettyRemoteSupport, + val loader: Option[ClassLoader], + val address: RemoteSystemAddress[RemoteNettyAddress]) { val log = Logging(remoteSupport.system, "NettyRemoteServer") import remoteSupport.serverSettings._ - val address = remoteSupport.remote.remoteAddress + if (address.transport.ip.isEmpty) throw new java.net.UnknownHostException(address.transport.host) val name = "NettyRemoteServer@" + address @@ -502,7 +518,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.ip, address.port))) + openChannels.add(bootstrap.bind(new InetSocketAddress(address.transport.ip.get, address.transport.port))) remoteSupport.notifyListeners(RemoteServerStarted(remoteSupport)) def shutdown() { @@ -510,8 +526,9 @@ class NettyRemoteServer(val remoteSupport: NettyRemoteSupport, val loader: Optio val shutdownSignal = { val b = RemoteControlProtocol.newBuilder.setCommandType(CommandType.SHUTDOWN) b.setOrigin(RemoteProtocol.AddressProtocol.newBuilder - .setHostname(address.host) - .setPort(address.port) + .setSystem(address.system) + .setHostname(address.transport.host) + .setPort(address.transport.port) .build) if (SecureCookie.nonEmpty) b.setCookie(SecureCookie.get) @@ -620,21 +637,20 @@ class RemoteServerHandler( remoteSupport.unbindClient(address) remoteSupport.notifyListeners(RemoteServerClientClosed(remoteSupport, s)) case None ⇒ - remoteSupport.notifyListeners(RemoteServerClientClosed(remoteSupport, None)) + remoteSupport.notifyListeners(RemoteServerClientClosed[RemoteNettyAddress](remoteSupport, None)) } override def messageReceived(ctx: ChannelHandlerContext, event: MessageEvent) = try { event.getMessage match { case remote: AkkaRemoteProtocol if remote.hasMessage ⇒ - remoteSupport.receiveMessage(new RemoteMessage(remote.getMessage, remoteSupport, applicationLoader)) + remoteSupport.receiveMessage(new RemoteMessage(remote.getMessage, remoteSupport.system, applicationLoader)) case remote: AkkaRemoteProtocol if remote.hasInstruction ⇒ val instruction = remote.getInstruction instruction.getCommandType match { case CommandType.CONNECT if UsePassiveConnections ⇒ val origin = instruction.getOrigin - // FIXME RK need to include system-name in remote protocol - val inbound = RemoteAddress("BORKED", origin.getHostname, origin.getPort) + val inbound = RemoteNettyAddress(origin.getHostname, origin.getPort) val client = new PassiveRemoteClient(event.getChannel, remoteSupport, inbound) remoteSupport.bindClient(inbound, client) case CommandType.SHUTDOWN ⇒ //FIXME Dispose passive connection here, ticket #1410 @@ -651,9 +667,9 @@ class RemoteServerHandler( event.getChannel.close() } - private def getClientAddress(c: Channel): Option[RemoteAddress] = + private def getClientAddress(c: Channel): Option[RemoteNettyAddress] = c.getRemoteAddress match { - case inet: InetSocketAddress ⇒ Some(RemoteAddress("BORKED", inet.getHostName, inet.getPort)) // FIXME RK Broken! + case inet: InetSocketAddress ⇒ Some(RemoteNettyAddress(inet.getHostName, inet.getPort)) case _ ⇒ None } } diff --git a/akka-remote/src/main/scala/akka/remote/package.scala b/akka-remote/src/main/scala/akka/remote/package.scala new file mode 100644 index 0000000000..e2514d6592 --- /dev/null +++ b/akka-remote/src/main/scala/akka/remote/package.scala @@ -0,0 +1,8 @@ +/** + * Copyright (C) 2009-2010 Typesafe Inc. + */ +package akka + +package object remote { + type TransportsMap = Map[String, (String, Int) ⇒ Either[String, RemoteTransportAddress]] +} \ No newline at end of file diff --git a/akka-remote/src/main/scala/akka/routing/RemoteRouters.scala b/akka-remote/src/main/scala/akka/routing/RemoteRouters.scala new file mode 100644 index 0000000000..2539b3939d --- /dev/null +++ b/akka-remote/src/main/scala/akka/routing/RemoteRouters.scala @@ -0,0 +1,110 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.routing + +import akka.actor._ +import akka.remote._ +import scala.collection.JavaConverters._ +import java.util.concurrent.atomic.AtomicInteger +import com.typesafe.config.ConfigFactory +import akka.config.ConfigurationException + +trait RemoteRouterConfig extends RouterConfig { + override protected def createRoutees(props: Props, context: ActorContext, nrOfInstances: Int, targets: Iterable[String]): Vector[ActorRef] = (nrOfInstances, targets) match { + case (_, Nil) ⇒ throw new ConfigurationException("must specify list of remote nodes") + case (n, xs) ⇒ + val nodes = targets map { + case RemoteAddressExtractor(a) ⇒ a + case x ⇒ throw new ConfigurationException("unparseable remote node " + x) + } + val node = Stream.continually(nodes).flatten.iterator + val impl = context.system.asInstanceOf[ActorSystemImpl] + Vector.empty[ActorRef] ++ (for (i ← 1 to nrOfInstances) yield { + val name = "c" + i + val deploy = Deploy("", ConfigFactory.empty(), None, props.routerConfig, RemoteScope(node.next)) + impl.provider.actorOf(impl, props, context.self.asInstanceOf[InternalActorRef], context.self.path / name, false, Some(deploy)) + }) + } +} + +/** + * A Router that uses round-robin to select a connection. For concurrent calls, round robin is just a best effort. + *
+ * Please note that providing both 'nrOfInstances' and 'targets' does not make logical sense as this means + * that the round robin should both create new actors and use the 'targets' actor(s). + * In this case the 'nrOfInstances' will be ignored and the 'targets' will be used. + *
+ * The configuration parameter trumps the constructor arguments. This means that + * if you provide either 'nrOfInstances' or 'targets' to during instantiation they will + * be ignored if the 'nrOfInstances' is defined in the configuration file for the actor being used. + */ +case class RemoteRoundRobinRouter(nrOfInstances: Int, targets: Iterable[String]) extends RemoteRouterConfig with RoundRobinLike { + + /** + * Constructor that sets the targets to be used. + * Java API + */ + def this(n: Int, t: java.util.Collection[String]) = this(n, t.asScala) +} + +/** + * A Router that randomly selects one of the target connections to send a message to. + *
+ * Please note that providing both 'nrOfInstances' and 'targets' does not make logical sense as this means + * that the random router should both create new actors and use the 'targets' actor(s). + * In this case the 'nrOfInstances' will be ignored and the 'targets' will be used. + *
+ * The configuration parameter trumps the constructor arguments. This means that + * if you provide either 'nrOfInstances' or 'targets' to during instantiation they will + * be ignored if the 'nrOfInstances' is defined in the configuration file for the actor being used. + */ +case class RemoteRandomRouter(nrOfInstances: Int, targets: Iterable[String]) extends RemoteRouterConfig with RandomLike { + + /** + * Constructor that sets the targets to be used. + * Java API + */ + def this(n: Int, t: java.util.Collection[String]) = this(n, t.asScala) +} + +/** + * A Router that uses broadcasts a message to all its connections. + *
+ * Please note that providing both 'nrOfInstances' and 'targets' does not make logical sense as this means + * that the random router should both create new actors and use the 'targets' actor(s). + * In this case the 'nrOfInstances' will be ignored and the 'targets' will be used. + *
+ * The configuration parameter trumps the constructor arguments. This means that + * if you provide either 'nrOfInstances' or 'targets' to during instantiation they will + * be ignored if the 'nrOfInstances' is defined in the configuration file for the actor being used. + */ +case class RemoteBroadcastRouter(nrOfInstances: Int, targets: Iterable[String]) extends RemoteRouterConfig with BroadcastLike { + + /** + * Constructor that sets the targets to be used. + * Java API + */ + def this(n: Int, t: java.util.Collection[String]) = this(n, t.asScala) +} + +/** + * Simple router that broadcasts the message to all routees, and replies with the first response. + *
+ * Please note that providing both 'nrOfInstances' and 'targets' does not make logical sense as this means + * that the random router should both create new actors and use the 'targets' actor(s). + * In this case the 'nrOfInstances' will be ignored and the 'targets' will be used. + *
+ * The configuration parameter trumps the constructor arguments. This means that + * if you provide either 'nrOfInstances' or 'targets' to during instantiation they will + * be ignored if the 'nrOfInstances' is defined in the configuration file for the actor being used. + */ +case class RemoteScatterGatherFirstCompletedRouter(nrOfInstances: Int, targets: Iterable[String]) + extends RemoteRouterConfig with ScatterGatherFirstCompletedLike { + + /** + * Constructor that sets the targets to be used. + * Java API + */ + def this(n: Int, t: java.util.Collection[String]) = this(n, t.asScala) +} 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 73d709351d..ebda77c369 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,7 @@ akka { actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /user/service-hello.router = "direct" - /user/service-hello.nr-of-instances = 1 - /user/service-hello.remote.nodes = ["localhost:9991"] + /service-hello.remote = "akka://AkkaRemoteSpec@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 5e282b949c..88e1ac4ca5 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,11 +1,9 @@ -qakka { +akka { loglevel = "WARNING" actor { provider = "akka.remote.RemoteActorRefProvider" deployment { - /user/service-hello.router = "direct" - /user/service-hello.nr-of-instances = 1 - /user/service-hello.remote.nodes = ["localhost:9991"] + /service-hello.remote = "akka://AkkaRemoteSpec@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 da6fb8aab0..0a6098f27d 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 @@ -2,7 +2,7 @@ package akka.remote.direct_routed import akka.remote._ import akka.routing._ -import akka.actor.Actor +import akka.actor.{ Actor, Props } import akka.testkit._ object DirectRoutedRemoteActorMultiJvmSpec { @@ -23,10 +23,6 @@ class DirectRoutedRemoteActorMultiJvmNode1 extends AkkaRemoteSpec { "___" must { "___" in { - barrier("setup") - - remote.start() - barrier("start") barrier("done") } @@ -41,14 +37,10 @@ class DirectRoutedRemoteActorMultiJvmNode2 extends AkkaRemoteSpec with DefaultTi "A new remote actor configured with a Direct router" must { "be locally instantiated on a remote node and be able to communicate through its RemoteActorRef" in { - barrier("setup") - - remote.start() - barrier("start") - val actor = system.actorOf(Props[SomeActor]("service-hello") - //actor.isInstanceOf[RoutedActorRef] must be(true) + val actor = system.actorOf(Props[SomeActor], "service-hello") + actor.isInstanceOf[RemoteActorRef] 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 f016f29768..ebda77c369 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 { - /user/service-hello.remote.nodes = ["localhost:9991"] + /service-hello.remote = "akka://AkkaRemoteSpec@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 f016f29768..ebda77c369 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 { - /user/service-hello.remote.nodes = ["localhost:9991"] + /service-hello.remote = "akka://AkkaRemoteSpec@localhost:9991" } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/new_remote_actor/NewRemoteActorMultiJvmSpec.scala b/akka-remote/src/multi-jvm/scala/akka/remote/new_remote_actor/NewRemoteActorMultiJvmSpec.scala index 6d1e3c47cc..1e8c45112e 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/new_remote_actor/NewRemoteActorMultiJvmSpec.scala +++ b/akka-remote/src/multi-jvm/scala/akka/remote/new_remote_actor/NewRemoteActorMultiJvmSpec.scala @@ -1,6 +1,6 @@ package akka.remote.new_remote_actor -import akka.actor.Actor +import akka.actor.{ Actor, Props } import akka.remote._ import akka.testkit.DefaultTimeout @@ -22,10 +22,6 @@ class NewRemoteActorMultiJvmNode1 extends AkkaRemoteSpec { "___" must { "___" in { - barrier("setup") - - remote.start() - barrier("start") barrier("done") @@ -41,13 +37,9 @@ class NewRemoteActorMultiJvmNode2 extends AkkaRemoteSpec with DefaultTimeout { "A new remote actor" must { "be locally instantiated on a remote node and be able to communicate through its RemoteActorRef" in { - barrier("setup") - - remote.start() - barrier("start") - val actor = system.actorOf(Props[SomeActor]("service-hello") + val actor = system.actorOf(Props[SomeActor], "service-hello") val result = (actor ? "identify").get result must equal("node1") 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 7e180ac3fd..bad3912e51 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 { - /user/service-hello.router = "random" - /user/service-hello.nr-of-instances = 3 - /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /service-hello.router = "random" + /service-hello.nr-of-instances = 3 + /service-hello.target.nodes = ["akka://AkkaRemoteSpec@localhost:9991","akka://AkkaRemoteSpec@localhost:9992","akka://AkkaRemoteSpec@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 18a2dfa6db..eeef93bd4d 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./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"] + deployment./service-hello.router = "random" + deployment./service-hello.nr-of-instances = 3 + deployment./service-hello.target.nodes = ["akka://AkkaRemoteSpec@localhost:9991","akka://AkkaRemoteSpec@localhost:9992","akka://AkkaRemoteSpec@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 7e180ac3fd..bad3912e51 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 { - /user/service-hello.router = "random" - /user/service-hello.nr-of-instances = 3 - /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /service-hello.router = "random" + /service-hello.nr-of-instances = 3 + /service-hello.target.nodes = ["akka://AkkaRemoteSpec@localhost:9991","akka://AkkaRemoteSpec@localhost:9992","akka://AkkaRemoteSpec@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 7e180ac3fd..bad3912e51 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 { - /user/service-hello.router = "random" - /user/service-hello.nr-of-instances = 3 - /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /service-hello.router = "random" + /service-hello.nr-of-instances = 3 + /service-hello.target.nodes = ["akka://AkkaRemoteSpec@localhost:9991","akka://AkkaRemoteSpec@localhost:9992","akka://AkkaRemoteSpec@localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmSpec.scala b/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmSpec.scala index 860ae3780c..3efc3c5ce5 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmSpec.scala +++ b/akka-remote/src/multi-jvm/scala/akka/remote/random_routed/RandomRoutedRemoteActorMultiJvmSpec.scala @@ -1,9 +1,8 @@ package akka.remote.random_routed -import akka.actor.Actor +import akka.actor.{ Actor, Props } import akka.remote._ import akka.routing._ -import akka.routing.Routing.Broadcast import akka.testkit.DefaultTimeout object RandomRoutedRemoteActorMultiJvmSpec { @@ -21,8 +20,6 @@ class RandomRoutedRemoteActorMultiJvmNode1 extends AkkaRemoteSpec { val nodes = NrOfNodes "___" must { "___" in { - barrier("setup") - remote.start() barrier("start") barrier("broadcast-end") barrier("end") @@ -36,8 +33,6 @@ class RandomRoutedRemoteActorMultiJvmNode2 extends AkkaRemoteSpec { val nodes = NrOfNodes "___" must { "___" in { - barrier("setup") - remote.start() barrier("start") barrier("broadcast-end") barrier("end") @@ -51,8 +46,6 @@ class RandomRoutedRemoteActorMultiJvmNode3 extends AkkaRemoteSpec { val nodes = NrOfNodes "___" must { "___" in { - barrier("setup") - remote.start() barrier("start") barrier("broadcast-end") barrier("end") @@ -67,11 +60,8 @@ class RandomRoutedRemoteActorMultiJvmNode4 extends AkkaRemoteSpec with DefaultTi "A new remote actor configured with a Random router" must { "be locally instantiated on a remote node and be able to communicate through its RemoteActorRef" in { - barrier("setup") - remote.start() - barrier("start") - val actor = system.actorOf(Props[SomeActor]("service-hello") + val actor = system.actorOf(Props[SomeActor].withRouter(RoundRobinRouter()), "service-hello") actor.isInstanceOf[RoutedActorRef] must be(true) val connectionCount = NrOfNodes - 1 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 520b5faf37..73e4797da7 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 { - /user/service-hello.router = "round-robin" - /user/service-hello.nr-of-instances = 3 - /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /service-hello.router = "round-robin" + /service-hello.nr-of-instances = 3 + /service-hello.target.nodes = ["akka://AkkaRemoteSpec@localhost:9991","akka://AkkaRemoteSpec@localhost:9992","akka://AkkaRemoteSpec@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 520b5faf37..73e4797da7 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 { - /user/service-hello.router = "round-robin" - /user/service-hello.nr-of-instances = 3 - /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /service-hello.router = "round-robin" + /service-hello.nr-of-instances = 3 + /service-hello.target.nodes = ["akka://AkkaRemoteSpec@localhost:9991","akka://AkkaRemoteSpec@localhost:9992","akka://AkkaRemoteSpec@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 520b5faf37..73e4797da7 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 { - /user/service-hello.router = "round-robin" - /user/service-hello.nr-of-instances = 3 - /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /service-hello.router = "round-robin" + /service-hello.nr-of-instances = 3 + /service-hello.target.nodes = ["akka://AkkaRemoteSpec@localhost:9991","akka://AkkaRemoteSpec@localhost:9992","akka://AkkaRemoteSpec@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 520b5faf37..73e4797da7 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 { - /user/service-hello.router = "round-robin" - /user/service-hello.nr-of-instances = 3 - /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /service-hello.router = "round-robin" + /service-hello.nr-of-instances = 3 + /service-hello.target.nodes = ["akka://AkkaRemoteSpec@localhost:9991","akka://AkkaRemoteSpec@localhost:9992","akka://AkkaRemoteSpec@localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmSpec.scala b/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmSpec.scala index 762b26e454..786f278a7e 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmSpec.scala +++ b/akka-remote/src/multi-jvm/scala/akka/remote/round_robin_routed/RoundRobinRoutedRemoteActorMultiJvmSpec.scala @@ -1,9 +1,8 @@ package akka.remote.round_robin_routed -import akka.actor.Actor +import akka.actor.{ Actor, Props } import akka.remote._ import akka.routing._ -import akka.routing.Routing.Broadcast import akka.testkit.DefaultTimeout object RoundRobinRoutedRemoteActorMultiJvmSpec { @@ -21,8 +20,6 @@ class RoundRobinRoutedRemoteActorMultiJvmNode1 extends AkkaRemoteSpec { val nodes = NrOfNodes "___" must { "___" in { - barrier("setup") - remote.start() barrier("start") barrier("broadcast-end") barrier("end") @@ -36,8 +33,6 @@ class RoundRobinRoutedRemoteActorMultiJvmNode2 extends AkkaRemoteSpec { val nodes = NrOfNodes "___" must { "___" in { - barrier("setup") - remote.start() barrier("start") barrier("broadcast-end") barrier("end") @@ -51,8 +46,6 @@ class RoundRobinRoutedRemoteActorMultiJvmNode3 extends AkkaRemoteSpec { val nodes = NrOfNodes "___" must { "___" in { - barrier("setup") - remote.start() barrier("start") barrier("broadcast-end") barrier("end") @@ -67,11 +60,8 @@ class RoundRobinRoutedRemoteActorMultiJvmNode4 extends AkkaRemoteSpec with Defau "A new remote actor configured with a RoundRobin router" must { "be locally instantiated on a remote node and be able to communicate through its RemoteActorRef" in { - barrier("setup") - remote.start() - barrier("start") - val actor = system.actorOf(Props[SomeActor]("service-hello") + val actor = system.actorOf(Props[SomeActor].withRouter(RoundRobinRouter()), "service-hello") actor.isInstanceOf[RoutedActorRef] must be(true) val connectionCount = NrOfNodes - 1 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 1750adf448..81e2fef8f3 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 { - /user/service-hello.router = "scatter-gather" - /user/service-hello.nr-of-instances = 3 - /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /service-hello.router = "scatter-gather" + /service-hello.nr-of-instances = 3 + /service-hello.target.nodes = ["akka://AkkaRemoteSpec@localhost:9991","akka://AkkaRemoteSpec@localhost:9992","akka://AkkaRemoteSpec@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 1750adf448..81e2fef8f3 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 { - /user/service-hello.router = "scatter-gather" - /user/service-hello.nr-of-instances = 3 - /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /service-hello.router = "scatter-gather" + /service-hello.nr-of-instances = 3 + /service-hello.target.nodes = ["akka://AkkaRemoteSpec@localhost:9991","akka://AkkaRemoteSpec@localhost:9992","akka://AkkaRemoteSpec@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 1750adf448..81e2fef8f3 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 { - /user/service-hello.router = "scatter-gather" - /user/service-hello.nr-of-instances = 3 - /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /service-hello.router = "scatter-gather" + /service-hello.nr-of-instances = 3 + /service-hello.target.nodes = ["akka://AkkaRemoteSpec@localhost:9991","akka://AkkaRemoteSpec@localhost:9992","akka://AkkaRemoteSpec@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 1750adf448..81e2fef8f3 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 { - /user/service-hello.router = "scatter-gather" - /user/service-hello.nr-of-instances = 3 - /user/service-hello.remote.nodes = ["localhost:9991","localhost:9992","localhost:9993"] + /service-hello.router = "scatter-gather" + /service-hello.nr-of-instances = 3 + /service-hello.target.nodes = ["akka://AkkaRemoteSpec@localhost:9991","akka://AkkaRemoteSpec@localhost:9992","akka://AkkaRemoteSpec@localhost:9993"] } } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmSpec.scala b/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmSpec.scala index fb511b88b5..10d6e22f58 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmSpec.scala +++ b/akka-remote/src/multi-jvm/scala/akka/remote/scatter_gather_routed/ScatterGatherRoutedRemoteActorMultiJvmSpec.scala @@ -1,10 +1,10 @@ package akka.remote.scatter_gather_routed -import akka.actor.Actor +import akka.actor.{ Actor, Props } import akka.remote._ import akka.routing._ -import akka.routing.Routing.Broadcast -import akka.testkit.DefaultTimeout +import akka.testkit._ +import akka.util.duration._ object ScatterGatherRoutedRemoteActorMultiJvmSpec { val NrOfNodes = 4 @@ -21,8 +21,6 @@ class ScatterGatherRoutedRemoteActorMultiJvmNode1 extends AkkaRemoteSpec { val nodes = NrOfNodes "___" must { "___" in { - barrier("setup") - remote.start() barrier("start") barrier("broadcast-end") barrier("end") @@ -36,8 +34,6 @@ class ScatterGatherRoutedRemoteActorMultiJvmNode2 extends AkkaRemoteSpec { val nodes = NrOfNodes "___" must { "___" in { - barrier("setup") - remote.start() barrier("start") barrier("broadcast-end") barrier("end") @@ -51,8 +47,6 @@ class ScatterGatherRoutedRemoteActorMultiJvmNode3 extends AkkaRemoteSpec { val nodes = NrOfNodes "___" must { "___" in { - barrier("setup") - remote.start() barrier("start") barrier("broadcast-end") barrier("end") @@ -61,40 +55,37 @@ class ScatterGatherRoutedRemoteActorMultiJvmNode3 extends AkkaRemoteSpec { } } -class ScatterGatherRoutedRemoteActorMultiJvmNode4 extends AkkaRemoteSpec with DefaultTimeout { +class ScatterGatherRoutedRemoteActorMultiJvmNode4 extends AkkaRemoteSpec with DefaultTimeout with ImplicitSender { import ScatterGatherRoutedRemoteActorMultiJvmSpec._ val nodes = NrOfNodes "A new remote actor configured with a ScatterGather router" must { "be locally instantiated on a remote node and be able to communicate through its RemoteActorRef" in { - barrier("setup") - remote.start() - barrier("start") - val actor = system.actorOf(Props[SomeActor]("service-hello") + val actor = system.actorOf(Props[SomeActor].withRouter(RoundRobinRouter()), "service-hello") actor.isInstanceOf[RoutedActorRef] must be(true) - actor.asInstanceOf[RoutedActorRef].router.isInstanceOf[ScatterGatherFirstCompletedRouter] must be(true) + //actor.asInstanceOf[RoutedActorRef].router.isInstanceOf[ScatterGatherFirstCompletedRouter] must be(true) val connectionCount = NrOfNodes - 1 val iterationCount = 10 - var replies = Map( - "node1" -> 0, - "node2" -> 0, - "node3" -> 0) - for (i ← 0 until iterationCount) { for (k ← 0 until connectionCount) { - val nodeName = (actor ? "hit").as[String].getOrElse(fail("No id returned by actor")) - replies = replies + (nodeName -> (replies(nodeName) + 1)) + actor ! "hit" } } + val replies = (receiveWhile(5 seconds, messages = connectionCount * iterationCount) { + case name: String ⇒ (name, 1) + }).foldLeft(Map("node1" -> 0, "node2" -> 0, "node3" -> 0)) { + case (m, (n, c)) ⇒ m + (n -> (m(n) + c)) + } + barrier("broadcast-end") actor ! Broadcast("end") barrier("end") - replies.values foreach { _ must be(10) } + replies.values.sum must be === connectionCount * iterationCount barrier("done") } diff --git a/akka-remote/src/test/scala/akka/remote/AccrualFailureDetectorSpec.scala b/akka-remote/src/test/scala/akka/remote/AccrualFailureDetectorSpec.scala index 94e2c0272c..5ac2f281ab 100644 --- a/akka-remote/src/test/scala/akka/remote/AccrualFailureDetectorSpec.scala +++ b/akka-remote/src/test/scala/akka/remote/AccrualFailureDetectorSpec.scala @@ -6,10 +6,10 @@ import akka.testkit.AkkaSpec class AccrualFailureDetectorSpec extends AkkaSpec { "An AccrualFailureDetector" must { - val conn = RemoteAddress("tester", "localhost", 2552) + val conn = RemoteNettyAddress("localhost", 2552) "mark node as available after a series of successful heartbeats" in { - val fd = new AccrualFailureDetector + val fd = new AccrualFailureDetector() fd.heartbeat(conn) diff --git a/akka-remote/src/test/scala/akka/remote/RemoteCommunicationSpec.scala b/akka-remote/src/test/scala/akka/remote/RemoteCommunicationSpec.scala new file mode 100644 index 0000000000..62de045fb5 --- /dev/null +++ b/akka-remote/src/test/scala/akka/remote/RemoteCommunicationSpec.scala @@ -0,0 +1,133 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.remote + +import akka.testkit._ +import akka.actor._ +import com.typesafe.config._ + +object RemoteCommunicationSpec { + class Echo extends Actor { + var target: ActorRef = context.system.deadLetters + + def receive = { + case (p: Props, n: String) ⇒ sender ! context.actorOf(Props[Echo], n) + case ex: Exception ⇒ throw ex + case s: String ⇒ sender ! context.actorFor(s) + case x ⇒ target = sender; sender ! x + } + + override def preStart() {} + override def preRestart(cause: Throwable, msg: Option[Any]) { + target ! "preRestart" + } + override def postRestart(cause: Throwable) {} + override def postStop() { + target ! "postStop" + } + } +} + +class RemoteCommunicationSpec extends AkkaSpec(""" +akka { + actor.provider = "akka.remote.RemoteActorRefProvider" + cluster.nodename = Nonsense + remote.server { + hostname = localhost + port = 12345 + } + actor.deployment { + /blub.remote = "akka://remote_sys@localhost:12346" + /looker/child.remote = "akka://remote_sys@localhost:12346" + /looker/child/grandchild.remote = "akka://RemoteCommunicationSpec@localhost:12345" + } +} +""") with ImplicitSender { + + import RemoteCommunicationSpec._ + + val conf = ConfigFactory.parseString("akka.remote.server.port=12346").withFallback(system.settings.config) + val other = ActorSystem("remote_sys", conf) + + val remote = other.actorOf(Props(new Actor { + def receive = { + case "ping" ⇒ sender ! (("pong", sender)) + } + }), "echo") + + val here = system.actorFor("akka://remote_sys@localhost:12346/user/echo") + + implicit val timeout = system.settings.ActorTimeout + + override def atTermination() { + other.stop() + } + + "Remoting" must { + + "support remote look-ups" in { + here ! "ping" + expectMsgPF() { + case ("pong", s: AnyRef) if s eq testActor ⇒ true + } + } + + "send error message for wrong address" in { + EventFilter.error(start = "dropping", occurrences = 1).intercept { + system.actorFor("akka://remotesys@localhost:12346/user/echo") ! "ping" + }(other) + } + + "support ask" in { + (here ? "ping").get match { + case ("pong", s: AskActorRef) ⇒ // good + case m ⇒ fail(m + " was not (pong, AskActorRef)") + } + } + + "send dead letters on remote if actor does not exist" in { + EventFilter.warning(pattern = "dead.*buh", occurrences = 1).intercept { + system.actorFor("akka://remote_sys@localhost:12346/does/not/exist") ! "buh" + }(other) + } + + "create and supervise children on remote node" in { + val r = system.actorOf(Props[Echo], "blub") + r.path.toString must be === "akka://remote_sys@localhost:12346/remote/RemoteCommunicationSpec@localhost:12345/user/blub" + r ! 42 + expectMsg(42) + EventFilter[Exception]("crash", occurrences = 1).intercept { + r ! new Exception("crash") + }(other) + expectMsg("preRestart") + r ! 42 + expectMsg(42) + r.stop() + expectMsg("postStop") + } + + "look-up actors across node boundaries" in { + val l = system.actorOf(Props(new Actor { + def receive = { + case (p: Props, n: String) ⇒ sender ! context.actorOf(p, n) + case s: String ⇒ sender ! context.actorFor(s) + } + }), "looker") + l ! (Props[Echo], "child") + val r = expectMsgType[ActorRef] + r ! (Props[Echo], "grandchild") + val remref = expectMsgType[ActorRef] + remref.isInstanceOf[LocalActorRef] must be(true) + val myref = system.actorFor(system / "looker" / "child" / "grandchild") + myref.isInstanceOf[RemoteActorRef] must be(true) + myref ! 43 + expectMsg(43) + lastSender must be theSameInstanceAs remref + (l ? "child/..").as[ActorRef].get must be theSameInstanceAs l + (system.actorFor(system / "looker" / "child") ? "..").as[ActorRef].get must be theSameInstanceAs l + } + + } + +} \ No newline at end of file diff --git a/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala b/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala index 991e9ca887..d1c87e3c13 100644 --- a/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala +++ b/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala @@ -8,7 +8,7 @@ class RemoteConfigSpec extends AkkaSpec("akka.cluster.nodename = node1") { "RemoteExtension" must { "be able to parse remote and cluster config elements" in { - val config = RemoteExtension(system).config + val config = system.settings.config import config._ //akka.remote diff --git a/akka-remote/src/test/scala/akka/remote/RemoteDeployerSpec.scala b/akka-remote/src/test/scala/akka/remote/RemoteDeployerSpec.scala new file mode 100644 index 0000000000..bb219f3d55 --- /dev/null +++ b/akka-remote/src/test/scala/akka/remote/RemoteDeployerSpec.scala @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.remote + +import akka.testkit._ +import akka.actor._ +import akka.routing._ +import com.typesafe.config._ + +object RemoteDeployerSpec { + val deployerConf = ConfigFactory.parseString(""" + akka.actor.provider = "akka.remote.RemoteActorRefProvider" + akka.cluster.nodename = Whatever + akka.actor.deployment { + /user/service2 { + router = round-robin + nr-of-instances = 3 + remote = "akka://sys@wallace:2552" + } + } + """, ConfigParseOptions.defaults) + + class RecipeActor extends Actor { + def receive = { case _ ⇒ } + } + +} + +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) +class RemoteDeployerSpec extends AkkaSpec(RemoteDeployerSpec.deployerConf) { + + "A RemoteDeployer" must { + + "be able to parse 'akka.actor.deployment._' with specified remote nodes" in { + val service = "/user/service2" + val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookup(service) + deployment must be('defined) + + deployment must be(Some( + Deploy( + service, + deployment.get.config, + None, + RoundRobinRouter(3), + RemoteScope(UnparsedSystemAddress(Some("sys"), UnparsedTransportAddress("akka", "wallace", 2552)))))) + } + + } + +} \ No newline at end of file diff --git a/akka-remote/src/test/scala/akka/remote/RemoteRouterSpec.scala b/akka-remote/src/test/scala/akka/remote/RemoteRouterSpec.scala new file mode 100644 index 0000000000..60209d087b --- /dev/null +++ b/akka-remote/src/test/scala/akka/remote/RemoteRouterSpec.scala @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.remote + +import akka.testkit._ +import akka.routing._ +import akka.actor._ +import com.typesafe.config._ + +object RemoteRouterSpec { + class Echo extends Actor { + def receive = { + case _ ⇒ sender ! self.path + } + } +} + +class RemoteRouterSpec extends AkkaSpec(""" +akka { + actor.provider = "akka.remote.RemoteActorRefProvider" + cluster.nodename = Nonsense + remote.server { + hostname = localhost + port = 12345 + } + actor.deployment { + /blub { + router = "round-robin" + nr-of-instances = 2 + target.nodes = ["akka://remote_sys@localhost:12346"] + } + } +} +""") with ImplicitSender { + + import RemoteRouterSpec._ + + val conf = ConfigFactory.parseString("akka.remote.server.port=12346").withFallback(system.settings.config) + val other = ActorSystem("remote_sys", conf) + + override def atTermination() { + other.stop() + } + + "A Remote Router" must { + + "deploy its children on remote host driven by configuration" in { + val router = system.actorOf(Props[Echo].withRouter(RoundRobinRouter(2)), "blub") + router ! "" + expectMsgType[ActorPath].toString must be === "akka://remote_sys@localhost:12346/remote/RemoteRouterSpec@localhost:12345/user/blub/c1" + router ! "" + expectMsgType[ActorPath].toString must be === "akka://remote_sys@localhost:12346/remote/RemoteRouterSpec@localhost:12345/user/blub/c2" + } + + } + +} \ No newline at end of file diff --git a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala index 20db50f487..71a5e9fc22 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala @@ -5,13 +5,14 @@ package akka.testkit import akka.actor._ -import akka.util.ReflectiveAccess +import akka.util.{ ReflectiveAccess, Duration } import com.eaio.uuid.UUID import akka.actor.Props._ import akka.actor.ActorSystem import java.util.concurrent.atomic.AtomicLong import akka.event.EventStream -import akka.dispatch.{ DefaultDispatcherPrerequisites, DispatcherPrerequisites, Mailbox } +import akka.dispatch.{ DefaultDispatcherPrerequisites, DispatcherPrerequisites, Mailbox, Envelope } +import scala.collection.immutable.Stack /** * This special ActorRef is exclusively for use during unit testing in a single-threaded environment. Therefore, it @@ -27,20 +28,51 @@ class TestActorRef[T <: Actor]( _props: Props, _supervisor: InternalActorRef, name: String) - extends LocalActorRef(_system, _props.withDispatcher(new CallingThreadDispatcher(_prerequisites)), _supervisor, _supervisor.path / name, false) { + extends LocalActorRef( + _system, + _props.withDispatcher(new CallingThreadDispatcher(_prerequisites)), + _supervisor, + _supervisor.path / name, + false) { + + private case object InternalGetActor extends AutoReceivedMessage + + override def newActorCell( + system: ActorSystemImpl, + ref: InternalActorRef, + props: Props, + supervisor: InternalActorRef, + receiveTimeout: Option[Duration], + hotswap: Stack[PartialFunction[Any, Unit]]): ActorCell = + new ActorCell(system, ref, props, supervisor, receiveTimeout, hotswap) { + override def autoReceiveMessage(msg: Envelope) { + msg.message match { + case InternalGetActor ⇒ sender ! actor + case _ ⇒ super.autoReceiveMessage(msg) + } + } + } + /** * Directly inject messages into actor receive behavior. Any exceptions * thrown will be available to you, while still being able to use - * become/unbecome and their message counterparts. + * become/unbecome. */ - def apply(o: Any) { underlyingActorInstance.apply(o) } + def apply(o: Any) { underlyingActor.apply(o) } /** * Retrieve reference to the underlying actor, where the static type matches the factory used inside the * constructor. Beware that this reference is discarded by the ActorRef upon restarting the actor (should this * reference be linked to a supervisor). The old Actor may of course still be used in post-mortem assertions. */ - def underlyingActor: T = underlyingActorInstance.asInstanceOf[T] + def underlyingActor: T = { + // volatile mailbox read to bring in actor field + if (isTerminated) throw new IllegalActorStateException("underlying actor is terminated") + underlying.actor.asInstanceOf[T] match { + case null ⇒ ?(InternalGetActor)(underlying.system.settings.ActorTimeout).get.asInstanceOf[T] + case ref ⇒ ref + } + } /** * Registers this actor to be a death monitor of the provided ActorRef diff --git a/akka-testkit/src/main/scala/akka/testkit/TestEventListener.scala b/akka-testkit/src/main/scala/akka/testkit/TestEventListener.scala index afa174ead6..7da8d84eba 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestEventListener.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestEventListener.scala @@ -464,6 +464,7 @@ class TestEventListener extends Logging.DefaultLogger { val event = Warning(rcp.path.toString, "received dead letter from " + snd + ": " + msg) if (!filter(event)) print(event) } + case m ⇒ print(Debug(context.system.name, m)) } def filter(event: LogEvent): Boolean = filters exists (f ⇒ try { f(event) } catch { case e: Exception ⇒ false }) diff --git a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala index d295757f85..d87a313589 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala @@ -16,6 +16,8 @@ object TestActor { type Ignore = Option[PartialFunction[AnyRef, Boolean]] case class SetIgnore(i: Ignore) + case class Watch(ref: ActorRef) + case class UnWatch(ref: ActorRef) trait Message { def msg: AnyRef @@ -34,7 +36,9 @@ class TestActor(queue: BlockingDeque[TestActor.Message]) extends Actor { var ignore: Ignore = None def receive = { - case SetIgnore(ign) ⇒ ignore = ign + case SetIgnore(ign) ⇒ ignore = ign + case x @ Watch(ref) ⇒ context.watch(ref); queue.offerLast(RealMessage(x, self)) + case x @ UnWatch(ref) ⇒ context.unwatch(ref); queue.offerLast(RealMessage(x, self)) case x: AnyRef ⇒ val observe = ignore map (ignoreFunc ⇒ if (ignoreFunc isDefinedAt x) !ignoreFunc(x) else true) getOrElse true if (observe) queue.offerLast(RealMessage(x, sender)) @@ -123,6 +127,26 @@ class TestKit(_system: ActorSystem) { */ def ignoreNoMsg() { testActor ! TestActor.SetIgnore(None) } + /** + * Have the testActor watch someone (i.e. `context.watch(...)`). Waits until + * the Watch message is received back using expectMsg. + */ + def watch(ref: ActorRef) { + val msg = TestActor.Watch(ref) + testActor ! msg + expectMsg(msg) + } + + /** + * Have the testActor stop watching someone (i.e. `context.unwatch(...)`). Waits until + * the Watch message is received back using expectMsg. + */ + def unwatch(ref: ActorRef) { + val msg = TestActor.UnWatch(ref) + testActor ! msg + expectMsg(msg) + } + /** * Obtain current time (`System.nanoTime`) as Duration. */ 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 52786abc36..d890bfaf30 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 @@ -7,13 +7,12 @@ package akka.tutorial.first.java; import akka.actor.Props; import akka.actor.ActorRef; import akka.actor.ActorSystem; -import akka.actor.InternalActorRef; import akka.actor.UntypedActor; import akka.actor.UntypedActorFactory; -import akka.japi.Creator; -import akka.routing.*; +import akka.routing.RoundRobinRouter; -import java.util.LinkedList; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; public class Pi { @@ -106,19 +105,8 @@ public class Pi { this.nrOfMessages = nrOfMessages; this.nrOfElements = nrOfElements; this.latch = latch; - Creator routerCreator = new Creator() { - public Router create() { - return new RoundRobinRouter(getContext().dispatcher(), new akka.actor.Timeout(-1)); - } - }; - LinkedList actors = new LinkedList() { - { - for (int i = 0; i < nrOfWorkers; i++) add(getContext().actorOf(new Props(Worker.class))); - } - }; - // FIXME routers are intended to be used like this - RoutedProps props = new RoutedProps(routerCreator, new LocalConnectionManager(actors), new akka.actor.Timeout(-1), true); - router = new RoutedActorRef(getContext().system(), props, (InternalActorRef) getSelf(), "pi"); + + router = this.getContext().actorOf(new Props().withCreator(Worker.class).withRouter(new RoundRobinRouter(5)), "pi"); } // message handler diff --git a/akka-tutorials/akka-tutorial-first/src/main/resources/akka.conf b/akka-tutorials/akka-tutorial-first/src/main/resources/akka.conf new file mode 100644 index 0000000000..17517bccbe --- /dev/null +++ b/akka-tutorials/akka-tutorial-first/src/main/resources/akka.conf @@ -0,0 +1,6 @@ +akka.actor.deployment { + /user/master/pi { + router = round-robin + nr-of-instances = 10 + } +} 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 1c09ee4838..242bababca 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -4,8 +4,9 @@ package akka.tutorial.first.scala import java.util.concurrent.CountDownLatch -import akka.routing.{ RoutedActorRef, LocalConnectionManager, RoundRobinRouter, RoutedProps } import akka.actor._ +import akka.routing._ +import com.typesafe.config.{ ConfigFactory, Config } object Pi extends App { @@ -37,7 +38,8 @@ object Pi extends App { } def receive = { - case Work(start, nrOfElements) ⇒ sender ! Result(calculatePiFor(start, nrOfElements)) // perform the work + case Work(start, nrOfElements) ⇒ + sender ! Result(calculatePiFor(start, nrOfElements)) // perform the work } } @@ -51,15 +53,8 @@ object Pi extends App { var nrOfResults: Int = _ var start: Long = _ - // create the workers - val workers = Vector.fill(nrOfWorkers)(context.actorOf(Props[Worker])) - - // wrap them with a load-balancing router - // FIXME routers are intended to be used like this - implicit val timout = context.system.settings.ActorTimeout - implicit val dispatcher = context.dispatcher - val props = RoutedProps(routerFactory = () ⇒ new RoundRobinRouter, connectionManager = new LocalConnectionManager(workers)) - val router = new RoutedActorRef(context.system, props, self.asInstanceOf[InternalActorRef], "pi") + // create a round robin router for the workers + val router = context.actorOf(Props(new Worker).withRouter(RoundRobinRouter(nrOfInstances = 5)), "pi") // message handler def receive = { @@ -92,13 +87,13 @@ object Pi extends App { // ===== Run it ===== // ================== def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { - val system = ActorSystem() + val system = ActorSystem("PiSystem") // this latch is only plumbing to know when the calculation is completed val latch = new CountDownLatch(1) // create the master - val master = system.actorOf(Props(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch))) + val master = system.actorOf(Props(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)), "master") // start the calculation master ! Calculate diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index 76d5dd1705..cbad5fda90 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -79,8 +79,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( + 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 diff --git a/scripts/authors.pl b/scripts/authors.pl index d1f98ecaad..89502c6985 100755 --- a/scripts/authors.pl +++ b/scripts/authors.pl @@ -15,9 +15,16 @@ our $insertions; our $deletions; our $author; -while (<>) { +my $input; +if (@ARGV > 0) { + open $input, "git log --shortstat -z --minimal -w -C $ARGV[0]|" or die "cannot open pipe for $ARGV[0]: $!\n"; +} else { + $input = \*STDIN; +} + +while (<$input>) { ($author) = /Author: (.*)