diff --git a/akka-actor-tests/src/main/scala/akka/testing/Serializers.scala b/akka-actor-tests/src/main/scala/akka/testing/Serializers.scala index b3836bc201..6412619963 100644 --- a/akka-actor-tests/src/main/scala/akka/testing/Serializers.scala +++ b/akka-actor-tests/src/main/scala/akka/testing/Serializers.scala @@ -14,6 +14,8 @@ import sjson.json._ class ProtobufSerializer extends Serializer { val ARRAY_OF_BYTE_ARRAY = Array[Class[_]](classOf[Array[Byte]]) + def identifier = 2:Byte + def toBinary(obj: AnyRef): Array[Byte] = { if (!obj.isInstanceOf[Message]) throw new IllegalArgumentException( "Can't serialize a non-protobuf message using protobuf [" + obj + "]") @@ -31,6 +33,8 @@ object ProtobufSerializer extends ProtobufSerializer class JavaJSONSerializer extends Serializer { private val mapper = new ObjectMapper + def identifier = 3:Byte + def toBinary(obj: AnyRef): Array[Byte] = { val bos = new ByteArrayOutputStream val out = new ObjectOutputStream(bos) @@ -54,6 +58,8 @@ object JavaJSONSerializer extends JavaJSONSerializer class SJSONSerializer extends Serializer { + def identifier = 4:Byte + def toBinary(obj: AnyRef): Array[Byte] = sjson.json.Serializer.SJSON.out(obj) diff --git a/akka-actor-tests/src/test/java/akka/actor/JavaAPITestActor.java b/akka-actor-tests/src/test/java/akka/actor/JavaAPITestActor.java index fe729970cc..172dd6c6e9 100644 --- a/akka-actor-tests/src/test/java/akka/actor/JavaAPITestActor.java +++ b/akka-actor-tests/src/test/java/akka/actor/JavaAPITestActor.java @@ -2,6 +2,6 @@ package akka.actor; public class JavaAPITestActor extends UntypedActor { public void onReceive(Object msg) { - getContext().replySafe("got it!"); + getContext().tryReply("got it!"); } } diff --git a/akka-actor-tests/src/test/scala/akka/AkkaExceptionSpec.scala b/akka-actor-tests/src/test/scala/akka/AkkaExceptionSpec.scala new file mode 100644 index 0000000000..ac0b2b75cf --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/AkkaExceptionSpec.scala @@ -0,0 +1,28 @@ +package akka; + +import akka.actor._ +import org.scalatest.matchers.MustMatchers +import org.scalatest.WordSpec; + +/** + * A spec that verified that the AkkaException has at least a single argument constructor of type String. + * + * This is required to make Akka Exceptions be friends with serialization/deserialization. + */ +class AkkaExceptionSpec extends WordSpec with MustMatchers { + + "AkkaException" must { + "have a AkkaException(String msg) constructor to be serialization friendly" in { + //if the call to this method completes, we know what there is at least a single constructor which has + //the expected argument type. + verify(classOf[AkkaException]) + + //lets also try it for the exception that triggered this bug to be discovered. + verify(classOf[ActorKilledException]) + } + } + + def verify(clazz:java.lang.Class[_]):Unit = { + clazz.getConstructor(Array(classOf[String]): _*) + } +} diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/ActorRefSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/ActorRefSpec.scala index 28c68b15eb..ea5b620057 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/ActorRefSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/ActorRefSpec.scala @@ -305,8 +305,8 @@ class ActorRefSpec extends WordSpec with MustMatchers { val ref = Actor.actorOf( new Actor { def receive = { - case 5 ⇒ self reply_? "five" - case null ⇒ self reply_? "null" + case 5 ⇒ self tryReply "five" + case null ⇒ self tryReply "null" } }).start() @@ -334,7 +334,7 @@ class ActorRefSpec extends WordSpec with MustMatchers { val ref = Actor.actorOf( new Actor { def receive = { case _ ⇒ } - override def preRestart(reason: Throwable) = latch.countDown() + override def preRestart(reason: Throwable, msg: Option[Any]) = latch.countDown() override def postRestart(reason: Throwable) = latch.countDown() }).start() diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/ActorRestartSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/ActorRestartSpec.scala new file mode 100644 index 0000000000..d2b9a42ee5 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/ActorRestartSpec.scala @@ -0,0 +1,161 @@ +/** + * Copyright (C) 2009-2011 Scalable Solutions AB + */ + +package akka.actor + +import org.scalatest.{ WordSpec, BeforeAndAfterAll, BeforeAndAfterEach } +import org.scalatest.matchers.MustMatchers + +import Actor.actorOf +import akka.testkit._ +import akka.util.duration._ +import akka.config.Supervision.OneForOneStrategy + +import java.util.concurrent.atomic._ + +object ActorRestartSpec { + + private var _gen = new AtomicInteger(0) + def generation = _gen.incrementAndGet + def generation_=(x: Int) { _gen.set(x) } + + sealed trait RestartType + case object Normal extends RestartType + case object Nested extends RestartType + case object Handover extends RestartType + case object Fail extends RestartType + + class Restarter(val testActor: ActorRef) extends Actor { + val gen = generation + var xx = 0 + var restart: RestartType = Normal + def receive = { + case x: Int ⇒ xx = x + case t: RestartType ⇒ restart = t + case "get" ⇒ self reply xx + } + override def preStart { testActor ! (("preStart", gen)) } + override def preRestart(cause: Throwable, msg: Option[Any]) { testActor ! (("preRestart", msg, gen)) } + override def postRestart(cause: Throwable) { testActor ! (("postRestart", gen)) } + override def freshInstance() = { + restart match { + case Normal ⇒ None + case Nested ⇒ + val ref = TestActorRef(new Actor { + def receive = { case _ ⇒ } + override def preStart { testActor ! ((this, self)) } + }).start() + testActor ! ((ref.underlyingActor, ref)) + None + case Handover ⇒ + val fresh = new Restarter(testActor) + fresh.xx = xx + Some(fresh) + case Fail ⇒ + throw new IllegalActorStateException("expected") + } + } + } + + class Supervisor extends Actor { + self.faultHandler = OneForOneStrategy(List(classOf[Throwable]), 5, 5000) + def receive = { + case _ ⇒ + } + } +} + +class ActorRestartSpec extends WordSpec with MustMatchers with TestKit with BeforeAndAfterEach { + import ActorRestartSpec._ + + override def beforeEach { generation = 0 } + override def afterEach { + val it = toStop.iterator + while (it.hasNext) { + it.next.stop() + it.remove + } + } + + private var toStop = new java.util.concurrent.ConcurrentSkipListSet[ActorRef] + private def newActor(f: ⇒ Actor): ActorRef = { + val ref = actorOf(f) + toStop add ref + ref.start() + } + + "An Actor restart" must { + + "invoke preRestart, preStart, postRestart" in { + val actor = newActor(new Restarter(testActor)) + expectMsg(1 second, ("preStart", 1)) + val supervisor = newActor(new Supervisor) + supervisor link actor + actor ! Kill + within(1 second) { + expectMsg(("preRestart", Some(Kill), 1)) + expectMsg(("preStart", 2)) + expectMsg(("postRestart", 2)) + expectNoMsg + } + } + + "support creation of nested actors in freshInstance()" in { + val actor = newActor(new Restarter(testActor)) + expectMsg(1 second, ("preStart", 1)) + val supervisor = newActor(new Supervisor) + supervisor link actor + actor ! Nested + actor ! Kill + within(1 second) { + expectMsg(("preRestart", Some(Kill), 1)) + val (tActor, tRef) = expectMsgType[(Actor, TestActorRef[Actor])] + tRef.underlyingActor must be(tActor) + expectMsg((tActor, tRef)) + tRef.stop() + expectMsg(("preStart", 2)) + expectMsg(("postRestart", 2)) + expectNoMsg + } + } + + "use freshInstance() if available" in { + val actor = newActor(new Restarter(testActor)) + expectMsg(1 second, ("preStart", 1)) + val supervisor = newActor(new Supervisor) + supervisor link actor + actor ! 42 + actor ! Handover + actor ! Kill + within(1 second) { + expectMsg(("preRestart", Some(Kill), 1)) + expectMsg(("preStart", 2)) + expectMsg(("postRestart", 2)) + expectNoMsg + } + actor ! "get" + expectMsg(1 second, 42) + } + + "fall back to default factory if freshInstance() fails" in { + val actor = newActor(new Restarter(testActor)) + expectMsg(1 second, ("preStart", 1)) + val supervisor = newActor(new Supervisor) + supervisor link actor + actor ! 42 + actor ! Fail + actor ! Kill + within(1 second) { + expectMsg(("preRestart", Some(Kill), 1)) + expectMsg(("preStart", 2)) + expectMsg(("postRestart", 2)) + expectNoMsg + } + actor ! "get" + expectMsg(1 second, 0) + } + + } + +} diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/ClusterSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/ClusterSpec.scala new file mode 100644 index 0000000000..9569e1bed5 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/ClusterSpec.scala @@ -0,0 +1,59 @@ +package akka.actor.actor + +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers +import akka.config.Config + +class ClusterSpec extends WordSpec with MustMatchers { + + "A Deployer" must { + "be able to parse 'akka.actor.cluster._' config elements" in { + import Config.config._ + + //akka.cluster + getString("akka.cluster.name") must equal(Some("test-cluster")) + getString("akka.cluster.zookeeper-server-addresses") must equal(Some("localhost:2181")) + getInt("akka.cluster.server.port") must equal(Some(2552)) + getInt("akka.cluster.max-time-to-wait-until-connected") must equal(Some(30)) + getInt("akka.cluster.session-timeout") must equal(Some(60)) + getInt("akka.cluster.connection-timeout") must equal(Some(60)) + getBool("akka.cluster.use-compression") must equal(Some(false)) + getInt("akka.cluster.connection-timeout") must equal(Some(60)) + getInt("akka.cluster.remote-daemon-ack-timeout") must equal(Some(30)) + getBool("akka.cluster.include-ref-node-in-replica-set") must equal(Some(true)) + getString("akka.cluster.compression-scheme") must equal(Some("zlib")) + getInt("akka.cluster.zlib-compression-level") must equal(Some(6)) + getString("akka.cluster.layer") must equal(Some("akka.remote.netty.NettyRemoteSupport")) + getString("akka.cluster.secure-cookie") must equal(Some("")) + getString("akka.cluster.log-directory") must equal(Some("_akka_cluster")) + + //akka.cluster.replication + getString("akka.cluster.replication.digest-type") must equal(Some("MAC")) + getString("akka.cluster.replication.password") must equal(Some("secret")) + getInt("akka.cluster.replication.ensemble-size") must equal(Some(3)) + getInt("akka.cluster.replication.quorum-size") must equal(Some(2)) + getInt("akka.cluster.replication.snapshot-frequency") must equal(Some(1000)) + getInt("akka.cluster.replication.timeout") must equal(Some(30)) + + //akka.cluster.server + getInt("akka.cluster.server.port") must equal(Some(2552)) + getInt("akka.cluster.server.message-frame-size") must equal(Some(1048576)) + getInt("akka.cluster.server.connection-timeout") must equal(Some(1)) + getBool("akka.cluster.server.require-cookie") must equal(Some(false)) + getBool("akka.cluster.server.untrusted-mode") must equal(Some(false)) + getInt("akka.cluster.server.backlog") must equal(Some(4096)) + getInt("akka.cluster.server.execution-pool-keepalive") must equal(Some(60)) + getInt("akka.cluster.server.execution-pool-size") must equal(Some(16)) + getInt("akka.cluster.server.max-channel-memory-size") must equal(Some(0)) + getInt("akka.cluster.server.max-total-memory-size") must equal(Some(0)) + + //akka.cluster.client + getBool("akka.cluster.client.buffering.retry-message-send-on-failure") must equal(Some(true)) + getInt("akka.cluster.client.buffering.capacity") must equal(Some(-1)) + getInt("akka.cluster.client.reconnect-delay") must equal(Some(5)) + getInt("akka.cluster.client.read-timeout") must equal(Some(10)) + getInt("akka.cluster.client.reap-futures-delay") must equal(Some(5)) + getInt("akka.cluster.client.reconnection-time-window") must equal(Some(600)) + } + } +} diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/DeployerSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/DeployerSpec.scala index 3ae5ea2397..423098a49a 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/DeployerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/DeployerSpec.scala @@ -14,13 +14,14 @@ class DeployerSpec extends WordSpec with MustMatchers { "be able to parse 'akka.actor.deployment._' config elements" in { val deployment = Deployer.lookupInConfig("service-ping") deployment must be('defined) + deployment must equal(Some( Deploy( "service-ping", LeastCPU, Clustered( Vector(Node("node1")), - Replicate(3), + ReplicationFactor(3), Replication( TransactionLog, WriteThrough))))) diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTransitionSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTransitionSpec.scala index 131dd5cb96..c552345192 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTransitionSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTransitionSpec.scala @@ -32,7 +32,7 @@ object FSMTransitionSpec { case Ev("reply") ⇒ stay replying "reply" } initialize - override def preRestart(reason: Throwable) { target ! "restarted" } + override def preRestart(reason: Throwable, msg: Option[Any]) { target ! "restarted" } } class Forwarder(target: ActorRef) extends Actor { diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/IOActor.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/IOActor.scala index 768e961eee..5ed73160d0 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/IOActor.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/IOActor.scala @@ -106,9 +106,9 @@ object IOActorSpec { case msg: NewClient ⇒ self startLink createWorker forward msg case ('set, key: String, value: ByteString) ⇒ kvs += (key -> value) - self reply_? (()) - case ('get, key: String) ⇒ self reply_? kvs.get(key) - case 'getall ⇒ self reply_? kvs + self tryReply (()) + case ('get, key: String) ⇒ self tryReply kvs.get(key) + case 'getall ⇒ self tryReply kvs } } @@ -124,15 +124,15 @@ object IOActorSpec { def receiveIO = { case ('set, key: String, value: ByteString) ⇒ socket write (ByteString("SET " + key + " " + value.length + "\r\n") ++ value) - self reply_? readResult + self tryReply readResult case ('get, key: String) ⇒ socket write ByteString("GET " + key + "\r\n") - self reply_? readResult + self tryReply readResult case 'getall ⇒ socket write ByteString("GETALL\r\n") - self reply_? readResult + self tryReply readResult } def readResult = { diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/TypedActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/TypedActorSpec.scala index 31c1e5fbc5..7c09de930e 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/TypedActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/TypedActorSpec.scala @@ -39,6 +39,8 @@ object TypedActorSpec { def incr() def read(): Int + + def testMethodCallSerialization(foo: Foo, s: String, i: Int): Unit = throw new IllegalStateException("expected") } class Bar extends Foo with Serializable { @@ -307,7 +309,7 @@ class TypedActorSpec extends WordSpec with MustMatchers with BeforeAndAfterEach "be able to serialize and deserialize invocations' parameters" in { import java.io._ val someFoo: Foo = new Bar - val m = MethodCall(classOf[Foo].getDeclaredMethod("futureComposePigdogFrom", Array[Class[_]](classOf[Foo]): _*), Array[AnyRef](someFoo)) + val m = MethodCall(classOf[Foo].getDeclaredMethod("testMethodCallSerialization", Array[Class[_]](classOf[Foo], classOf[String], classOf[Int]): _*), Array[AnyRef](someFoo, null, 1.asInstanceOf[AnyRef])) val baos = new ByteArrayOutputStream(8192 * 4) val out = new ObjectOutputStream(baos) @@ -319,9 +321,12 @@ class TypedActorSpec extends WordSpec with MustMatchers with BeforeAndAfterEach val mNew = in.readObject().asInstanceOf[MethodCall] mNew.method must be(m.method) - mNew.parameters must have size 1 + mNew.parameters must have size 3 mNew.parameters(0) must not be null mNew.parameters(0).getClass must be === classOf[Bar] + mNew.parameters(1) must be(null) + mNew.parameters(2) must not be null + mNew.parameters(2).asInstanceOf[Int] must be === 1 } } } diff --git a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorSpec.scala index 6f076c0648..dcdcb47a98 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorSpec.scala @@ -45,7 +45,7 @@ object SupervisorSpec { def receive = { case Ping ⇒ messageLog.put(PingMessage) - self.reply_?(PongMessage) + self.tryReply(PongMessage) case Die ⇒ throw new RuntimeException(ExceptionMessage) } @@ -374,7 +374,7 @@ class SupervisorSpec extends WordSpec with MustMatchers with BeforeAndAfterEach if (inits.get % 2 == 0) throw new IllegalStateException("Don't wanna!") def receive = { - case Ping ⇒ self.reply_?(PongMessage) + case Ping ⇒ self.tryReply(PongMessage) case Die ⇒ throw new Exception("expected") } }) diff --git a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorTreeSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorTreeSpec.scala index d6e15d40c5..4c5deb8b51 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorTreeSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorTreeSpec.scala @@ -25,7 +25,7 @@ class SupervisorTreeSpec extends WordSpec with MustMatchers { case Die ⇒ throw new Exception(self.address + " is dying...") } - override def preRestart(reason: Throwable) { + override def preRestart(reason: Throwable, msg: Option[Any]) { log += self.address } } diff --git a/akka-actor-tests/src/test/scala/akka/actor/supervisor/Ticket669Spec.scala b/akka-actor-tests/src/test/scala/akka/actor/supervisor/Ticket669Spec.scala index cf6ed866c8..c39d51f6f3 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/supervisor/Ticket669Spec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/supervisor/Ticket669Spec.scala @@ -65,12 +65,12 @@ object Ticket669Spec { case msg ⇒ throw new Exception("test") } - override def preRestart(reason: scala.Throwable) { - self.reply_?("failure1") + override def preRestart(reason: scala.Throwable, msg: Option[Any]) { + self.tryReply("failure1") } override def postStop() { - self.reply_?("failure2") + self.tryReply("failure2") } } } diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala index 62297ca495..327d6dda10 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala @@ -20,7 +20,7 @@ object ActorModelSpec { sealed trait ActorModelMessage - case class Reply_?(expect: Any) extends ActorModelMessage + case class TryReply(expect: Any) extends ActorModelMessage case class Reply(expect: Any) extends ActorModelMessage @@ -73,7 +73,7 @@ object ActorModelSpec { case Wait(time) ⇒ ack; Thread.sleep(time); busy.switchOff() case WaitAck(time, l) ⇒ ack; Thread.sleep(time); l.countDown(); busy.switchOff() case Reply(msg) ⇒ ack; self.reply(msg); busy.switchOff() - case Reply_?(msg) ⇒ ack; self.reply_?(msg); busy.switchOff() + case TryReply(msg) ⇒ ack; self.tryReply(msg); busy.switchOff() case Forward(to, msg) ⇒ ack; to.forward(msg); busy.switchOff() case CountDown(latch) ⇒ ack; latch.countDown(); busy.switchOff() case Increment(count) ⇒ ack; count.incrementAndGet(); busy.switchOff() diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala index f761268a73..cc89b60119 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala @@ -275,7 +275,7 @@ class FutureSpec extends WordSpec with MustMatchers with Checkers with BeforeAnd "fold" in { val actors = (1 to 10).toList map { _ ⇒ actorOf(new Actor { - def receive = { case (add: Int, wait: Int) ⇒ Thread.sleep(wait); self reply_? add } + def receive = { case (add: Int, wait: Int) ⇒ Thread.sleep(wait); self tryReply add } }).start() } val timeout = 10000 @@ -286,7 +286,7 @@ class FutureSpec extends WordSpec with MustMatchers with Checkers with BeforeAnd "fold by composing" in { val actors = (1 to 10).toList map { _ ⇒ actorOf(new Actor { - def receive = { case (add: Int, wait: Int) ⇒ Thread.sleep(wait); self reply_? add } + def receive = { case (add: Int, wait: Int) ⇒ Thread.sleep(wait); self tryReply add } }).start() } def futures = actors.zipWithIndex map { case (actor: ActorRef, idx: Int) ⇒ actor.?((idx, idx * 200), 10000).mapTo[Int] } @@ -300,7 +300,7 @@ class FutureSpec extends WordSpec with MustMatchers with Checkers with BeforeAnd case (add: Int, wait: Int) ⇒ Thread.sleep(wait) if (add == 6) throw new IllegalArgumentException("shouldFoldResultsWithException: expected") - self reply_? add + self tryReply add } }).start() } @@ -332,7 +332,7 @@ class FutureSpec extends WordSpec with MustMatchers with Checkers with BeforeAnd "shouldReduceResults" in { val actors = (1 to 10).toList map { _ ⇒ actorOf(new Actor { - def receive = { case (add: Int, wait: Int) ⇒ Thread.sleep(wait); self reply_? add } + def receive = { case (add: Int, wait: Int) ⇒ Thread.sleep(wait); self tryReply add } }).start() } val timeout = 10000 @@ -347,7 +347,7 @@ class FutureSpec extends WordSpec with MustMatchers with Checkers with BeforeAnd case (add: Int, wait: Int) ⇒ Thread.sleep(wait) if (add == 6) throw new IllegalArgumentException("shouldFoldResultsWithException: expected") - self reply_? add + self tryReply add } }).start() } diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala index 5ddd9fa411..c2fbc2d09c 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala @@ -33,7 +33,7 @@ class PriorityDispatcherSpec extends WordSpec with MustMatchers { def receive = { case i: Int ⇒ acc = i :: acc - case 'Result ⇒ self reply_? acc + case 'Result ⇒ self tryReply acc } }).start() diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchmarkScenarios.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchmarkScenarios.scala index 6cbd6ee4ca..5442deacd5 100755 --- a/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchmarkScenarios.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchmarkScenarios.scala @@ -29,12 +29,14 @@ trait BenchmarkScenarios extends PerformanceTest { def complexScenario80 = complexScenario(80) @Test def complexScenario100 = complexScenario(100) + /* @Test def complexScenario200 = complexScenario(200) @Test def complexScenario300 = complexScenario(300) @Test def complexScenario400 = complexScenario(400) + */ def complexScenario(numberOfClients: Int) { Assume.assumeTrue(numberOfClients >= minClients) diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/OrderReceiver.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/OrderReceiver.scala index e9162299d1..869c186524 100755 --- a/akka-actor-tests/src/test/scala/akka/performance/trading/common/OrderReceiver.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/OrderReceiver.scala @@ -7,11 +7,13 @@ import akka.event.EventHandler trait OrderReceiver { type ME - val matchingEngines: List[ME] - var matchingEnginePartitionsIsStale = true var matchingEngineForOrderbook: Map[String, ME] = Map() - def refreshMatchingEnginePartitions() { + def refreshMatchingEnginePartitions(routing: MatchingEngineRouting[ME]) { + + val matchingEngines: List[ME] = routing.mapping.keys.toList + def supportedOrderbooks(me: ME): List[String] = routing.mapping(me) + val m = Map() ++ (for { me ← matchingEngines @@ -19,14 +21,11 @@ trait OrderReceiver { } yield (orderbookSymbol, me)) matchingEngineForOrderbook = m - matchingEnginePartitionsIsStale = false } - def supportedOrderbooks(me: ME): List[String] - } -class AkkaOrderReceiver(matchingEngineRouting: Map[ActorRef, List[String]], disp: Option[MessageDispatcher]) +class AkkaOrderReceiver(disp: Option[MessageDispatcher]) extends Actor with OrderReceiver { type ME = ActorRef @@ -34,21 +33,13 @@ class AkkaOrderReceiver(matchingEngineRouting: Map[ActorRef, List[String]], disp self.dispatcher = d } - override val matchingEngines: List[ActorRef] = matchingEngineRouting.keys.toList - - override def preStart() { - refreshMatchingEnginePartitions() - } - def receive = { + case routing@MatchingEngineRouting(mapping) ⇒ + refreshMatchingEnginePartitions(routing.asInstanceOf[MatchingEngineRouting[ActorRef]]) case order: Order ⇒ placeOrder(order) case unknown ⇒ EventHandler.warning(this, "Received unknown message: " + unknown) } - override def supportedOrderbooks(me: ActorRef): List[String] = { - matchingEngineRouting(me) - } - def placeOrder(order: Order) = { val matchingEngine = matchingEngineForOrderbook.get(order.orderbookSymbol) matchingEngine match { @@ -60,3 +51,5 @@ class AkkaOrderReceiver(matchingEngineRouting: Map[ActorRef, List[String]], disp } } } + +case class MatchingEngineRouting[ME](mapping: Map[ME, List[String]]) diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/PerformanceTest.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/PerformanceTest.scala index 69a7b4bd08..3c160a09ab 100755 --- a/akka-actor-tests/src/test/scala/akka/performance/trading/common/PerformanceTest.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/PerformanceTest.scala @@ -1,7 +1,5 @@ package akka.performance.trading.common -import java.text.SimpleDateFormat -import java.util.Date import java.util.Random import scala.collection.immutable.TreeMap @@ -12,11 +10,13 @@ import org.junit.After import org.junit.Before import org.scalatest.junit.JUnitSuite -import akka.event.EventHandler import akka.performance.trading.domain.Ask import akka.performance.trading.domain.Bid import akka.performance.trading.domain.Order import akka.performance.trading.domain.TotalTradeCounter +import akka.performance.workbench.BenchResultRepository +import akka.performance.workbench.Report +import akka.performance.workbench.Stats trait PerformanceTest extends JUnitSuite { diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/Report.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/Report.scala deleted file mode 100644 index 9160fa631e..0000000000 --- a/akka-actor-tests/src/test/scala/akka/performance/trading/common/Report.scala +++ /dev/null @@ -1,179 +0,0 @@ -package akka.performance.trading.common -import java.io.File -import java.text.SimpleDateFormat -import java.io.PrintWriter -import java.io.FileWriter -import akka.event.EventHandler -import java.util.Date - -class Report( - resultRepository: BenchResultRepository, - compareResultWith: Option[String] = None) { - - private val dir = System.getProperty("benchmark.resultDir", "target/benchmark") - - private def dirExists: Boolean = new File(dir).exists - private def log = System.getProperty("benchmark.logResult", "false").toBoolean - - val dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm") - val legendTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm") - val fileTimestampFormat = new SimpleDateFormat("yyyyMMddHHmmss") - - def html(statistics: Seq[Stats]): Unit = if (dirExists) { - - val current = statistics.last - val sb = new StringBuilder - - val title = current.name + " " + dateTimeFormat.format(new Date(current.timestamp)) - sb.append(header(title)) - sb.append("

%s

\n".format(title)) - - sb.append("
\n")
-    sb.append(formatResultsTable(statistics))
-    sb.append("\n
\n") - - sb.append(img(percentilesChart(current))) - sb.append(img(latencyAndThroughputChart(current))) - - for (stats ← statistics) { - compareWithHistoricalPercentiliesChart(stats).foreach(url ⇒ sb.append(img(url))) - } - - for (stats ← statistics) { - comparePercentilesChart(stats).foreach(url ⇒ sb.append(img(url))) - } - - if (dirExists) { - val timestamp = fileTimestampFormat.format(new Date(current.timestamp)) - val name = current.name + "--" + timestamp + ".html" - write(sb.toString, name) - } - - } - - private def img(url: String): String = { - """""".format( - url, GoogleChartBuilder.ChartWidth, GoogleChartBuilder.ChartHeight) + "\n" - } - - def percentilesChart(stats: Stats): String = { - val chartTitle = stats.name + " Percentiles (microseconds)" - val chartUrl = GoogleChartBuilder.percentilChartUrl(resultRepository.get(stats.name), chartTitle, _.load + " clients") - if (log) EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl) - chartUrl - } - - def comparePercentilesChart(stats: Stats): Seq[String] = { - for { - compareName ← compareResultWith.toSeq - compareStats ← resultRepository.get(compareName, stats.load) - } yield { - val chartTitle = stats.name + " vs. " + compareName + ", " + stats.load + " clients" + ", Percentiles (microseconds)" - val chartUrl = GoogleChartBuilder.percentilChartUrl(Seq(compareStats, stats), chartTitle, _.name) - if (log) EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl) - chartUrl - } - } - - def compareWithHistoricalPercentiliesChart(stats: Stats): Option[String] = { - val withHistorical = resultRepository.getWithHistorical(stats.name, stats.load) - if (withHistorical.size > 1) { - val chartTitle = stats.name + " vs. historical, " + stats.load + " clients" + ", Percentiles (microseconds)" - val chartUrl = GoogleChartBuilder.percentilChartUrl(withHistorical, chartTitle, - stats ⇒ legendTimeFormat.format(new Date(stats.timestamp))) - if (log) EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl) - Some(chartUrl) - } else { - None - } - } - - def latencyAndThroughputChart(stats: Stats): String = { - val chartTitle = stats.name + " Latency (microseconds) and Throughput (TPS)" - val chartUrl = GoogleChartBuilder.latencyAndThroughputChartUrl(resultRepository.get(stats.name), chartTitle) - if (log) EventHandler.info(this, chartTitle + " Chart:\n" + chartUrl) - chartUrl - } - - def formatResultsTable(statsSeq: Seq[Stats]): String = { - - val name = statsSeq.head.name - - val spaces = " " - val headerScenarioCol = ("Scenario" + spaces).take(name.length) - - val headerLine = (headerScenarioCol :: "clients" :: "TPS" :: "mean" :: "5% " :: "25% " :: "50% " :: "75% " :: "95% " :: "Durat." :: "N" :: Nil) - .mkString("\t") - val headerLine2 = (spaces.take(name.length) :: " " :: " " :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(s) " :: " " :: Nil) - .mkString("\t") - val line = List.fill(formatStats(statsSeq.head).replaceAll("\t", " ").length)("-").mkString - val formattedStats = "\n" + - line.replace('-', '=') + "\n" + - headerLine + "\n" + - headerLine2 + "\n" + - line + "\n" + - statsSeq.map(formatStats(_)).mkString("\n") + "\n" + - line + "\n" - - if (log) EventHandler.info(this, formattedStats) - - formattedStats - - } - - def formatStats(stats: Stats): String = { - val durationS = stats.durationNanos.toDouble / 1000000000.0 - val duration = durationS.formatted("%.0f") - - val tpsStr = stats.tps.formatted("%.0f") - val meanStr = stats.mean.formatted("%.0f") - - val summaryLine = - stats.name :: - stats.load.toString :: - tpsStr :: - meanStr :: - stats.percentiles(5).toString :: - stats.percentiles(25).toString :: - stats.percentiles(50).toString :: - stats.percentiles(75).toString :: - stats.percentiles(95).toString :: - duration :: - stats.n.toString :: - Nil - - summaryLine.mkString("\t") - - } - - def write(content: String, fileName: String) { - val f = new File(dir, fileName) - var writer: PrintWriter = null - try { - writer = new PrintWriter(new FileWriter(f)) - writer.print(content) - writer.flush() - } catch { - case e: Exception ⇒ - EventHandler.error(this, "Failed to save report to [%s], due to [%s]". - format(f.getAbsolutePath, e.getMessage)) - } finally { - if (writer ne null) try { writer.close() } catch { case ignore: Exception ⇒ } - } - } - - def header(title: String) = - """| - | - | - | - |%s - | - | - |""".stripMargin.format(title) - - def footer = - """|" - |""".stripMargin - -} \ No newline at end of file diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/TradingSystem.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/common/TradingSystem.scala index 44951879c5..ed822cf1be 100755 --- a/akka-actor-tests/src/test/scala/akka/performance/trading/common/TradingSystem.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/trading/common/TradingSystem.scala @@ -75,7 +75,7 @@ class AkkaTradingSystem extends TradingSystem { (1 to 10).toList map (i ⇒ createOrderReceiver()) } - def matchingEngineRouting: Map[ActorRef, List[String]] = { + def matchingEngineRouting: MatchingEngineRouting[ActorRef] = { val rules = for { info ← matchingEngines @@ -84,11 +84,11 @@ class AkkaTradingSystem extends TradingSystem { (info.primary, orderbookSymbols) } - Map() ++ rules + MatchingEngineRouting(Map() ++ rules) } def createOrderReceiver() = - actorOf(new AkkaOrderReceiver(matchingEngineRouting, orDispatcher)) + actorOf(new AkkaOrderReceiver(orDispatcher)) override def start() { for (MatchingEngineInfo(p, s, o) ← matchingEngines) { @@ -97,7 +97,11 @@ class AkkaTradingSystem extends TradingSystem { s.foreach(_.start()) s.foreach(p ! _) } - orderReceivers.foreach(_.start()) + val routing = matchingEngineRouting + for (or ← orderReceivers) { + or.start() + or ! routing + } } override def shutdown() { diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayOrderReceiver.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayOrderReceiver.scala index d64639d3fa..16e3a41048 100755 --- a/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayOrderReceiver.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayOrderReceiver.scala @@ -6,8 +6,8 @@ import akka.event.EventHandler import akka.performance.trading.domain._ import akka.performance.trading.common.AkkaOrderReceiver -class OneWayOrderReceiver(matchingEngineRouting: Map[ActorRef, List[String]], disp: Option[MessageDispatcher]) - extends AkkaOrderReceiver(matchingEngineRouting, disp) { +class OneWayOrderReceiver(disp: Option[MessageDispatcher]) + extends AkkaOrderReceiver(disp) { override def placeOrder(order: Order) = { val matchingEngine = matchingEngineForOrderbook.get(order.orderbookSymbol) diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayTradingSystem.scala b/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayTradingSystem.scala index d6fcafbf7c..57737e9c0e 100755 --- a/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayTradingSystem.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/trading/oneway/OneWayTradingSystem.scala @@ -11,6 +11,6 @@ class OneWayTradingSystem extends AkkaTradingSystem { actorOf(new OneWayMatchingEngine(meId, orderbooks, meDispatcher)) override def createOrderReceiver() = - actorOf(new OneWayOrderReceiver(matchingEngineRouting, orDispatcher)) + actorOf(new OneWayOrderReceiver(orDispatcher)) } \ No newline at end of file diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchResultRepository.scala b/akka-actor-tests/src/test/scala/akka/performance/workbench/BenchResultRepository.scala similarity index 72% rename from akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchResultRepository.scala rename to akka-actor-tests/src/test/scala/akka/performance/workbench/BenchResultRepository.scala index 2f9ea89dd8..0c8e5f0cb2 100644 --- a/akka-actor-tests/src/test/scala/akka/performance/trading/common/BenchResultRepository.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/workbench/BenchResultRepository.scala @@ -1,16 +1,18 @@ -package akka.performance.trading.common +package akka.performance.workbench import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.io.File import java.io.FileInputStream import java.io.FileOutputStream +import java.io.FileWriter import java.io.ObjectInputStream import java.io.ObjectOutputStream +import java.io.PrintWriter import java.text.SimpleDateFormat import java.util.Date -import scala.collection.mutable.{ Map ⇒ MutableMap } +import scala.collection.mutable.{Map => MutableMap} import akka.event.EventHandler @@ -23,6 +25,10 @@ trait BenchResultRepository { def getWithHistorical(name: String, load: Int): Seq[Stats] + def saveHtmlReport(content: String, name: String): Unit + + def htmlReportUrl(name: String): String + } object BenchResultRepository { @@ -34,8 +40,10 @@ class FileBenchResultRepository extends BenchResultRepository { private val statsByName = MutableMap[String, Seq[Stats]]() private val baselineStats = MutableMap[Key, Stats]() private val historicalStats = MutableMap[Key, Seq[Stats]]() - private val dir = System.getProperty("benchmark.resultDir", "target/benchmark") - private def dirExists: Boolean = new File(dir).exists + private val serDir = System.getProperty("benchmark.resultDir", "target/benchmark") + "/ser" + private def serDirExists: Boolean = new File(serDir).exists + private val htmlDir = System.getProperty("benchmark.resultDir", "target/benchmark") + "/html" + private def htmlDirExists: Boolean = new File(htmlDir).exists protected val maxHistorical = 7 case class Key(name: String, load: Int) @@ -64,10 +72,10 @@ class FileBenchResultRepository extends BenchResultRepository { } private def loadFiles() { - if (dirExists) { + if (serDirExists) { val files = for { - f ← new File(dir).listFiles + f ← new File(serDir).listFiles if f.isFile if f.getName.endsWith(".ser") } yield f @@ -86,11 +94,11 @@ class FileBenchResultRepository extends BenchResultRepository { } private def save(stats: Stats) { - new File(dir).mkdirs - if (!dirExists) return + new File(serDir).mkdirs + if (!serDirExists) return val timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date(stats.timestamp)) val name = stats.name + "--" + timestamp + "--" + stats.load + ".ser" - val f = new File(dir, name) + val f = new File(serDir, name) var out: ObjectOutputStream = null try { out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(f))) @@ -127,5 +135,24 @@ class FileBenchResultRepository extends BenchResultRepository { loadFiles() + def saveHtmlReport(content: String, fileName: String) { + new File(htmlDir).mkdirs + if (!htmlDirExists) return + val f = new File(htmlDir, fileName) + var writer: PrintWriter = null + try { + writer = new PrintWriter(new FileWriter(f)) + writer.print(content) + writer.flush() + } catch { + case e: Exception ⇒ + EventHandler.error(this, "Failed to save report to [%s], due to [%s]". + format(f.getAbsolutePath, e.getMessage)) + } finally { + if (writer ne null) try { writer.close() } catch { case ignore: Exception ⇒ } + } + } + + def htmlReportUrl(fileName: String): String = new File(htmlDir, fileName).getAbsolutePath } diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/GoogleChartBuilder.scala b/akka-actor-tests/src/test/scala/akka/performance/workbench/GoogleChartBuilder.scala similarity index 85% rename from akka-actor-tests/src/test/scala/akka/performance/trading/common/GoogleChartBuilder.scala rename to akka-actor-tests/src/test/scala/akka/performance/workbench/GoogleChartBuilder.scala index d7f6c965a3..a2d2a381a8 100644 --- a/akka-actor-tests/src/test/scala/akka/performance/trading/common/GoogleChartBuilder.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/workbench/GoogleChartBuilder.scala @@ -1,10 +1,9 @@ -package akka.performance.trading.common +package akka.performance.workbench import java.io.UnsupportedEncodingException import java.net.URLEncoder + import scala.collection.immutable.TreeMap -import java.util.Locale -import java.util.Formatter /** * Generates URLs to Google Chart API http://code.google.com/apis/chart/ @@ -15,9 +14,9 @@ object GoogleChartBuilder { val ChartHeight = 400 /** - * Builds a bar chart for all percentiles in the statistics. + * Builds a bar chart for all percentiles and the mean in the statistics. */ - def percentilChartUrl(statistics: Seq[Stats], title: String, legend: Stats ⇒ String): String = { + def percentilesAndMeanChartUrl(statistics: Seq[Stats], title: String, legend: Stats ⇒ String): String = { if (statistics.isEmpty) return "" val current = statistics.last @@ -38,6 +37,7 @@ object GoogleChartBuilder { sb.append("&") // labels percentileLabels(current.percentiles, sb) + sb.append("|mean") sb.append("|2:|min|mean|median") sb.append("&") // label positions @@ -63,7 +63,7 @@ object GoogleChartBuilder { // data series val maxValue = statistics.map(_.percentiles.last._2).max sb.append("chd=t:") - dataSeries(statistics.map(_.percentiles), sb) + dataSeries(statistics.map(_.percentiles), statistics.map(_.mean), sb) // y range sb.append("&") @@ -98,13 +98,18 @@ object GoogleChartBuilder { sb.append(s) } - private def dataSeries(allPercentiles: Seq[TreeMap[Int, Long]], sb: StringBuilder) { - val series = + private def dataSeries(allPercentiles: Seq[TreeMap[Int, Long]], meanValues: Seq[Double], sb: StringBuilder) { + val percentileSeries = for { percentiles ← allPercentiles } yield { percentiles.values.mkString(",") } + + val series = + for ((s, m) ← percentileSeries.zip(meanValues)) + yield s + "," + formatDouble(m) + sb.append(series.mkString("|")) } @@ -144,11 +149,11 @@ object GoogleChartBuilder { sb.append("chxs=0,676767,11.5,0,lt,676767|1,676767,11.5,0,lt,676767|2,676767,11.5,0,lt,676767") sb.append("&") sb.append("chco=") - val seriesColors = List("25B33B", "3072F3", "FF0000", "FF9900") + val seriesColors = List("25B33B", "3072F3", "FF0000", "37F0ED", "FF9900") sb.append(seriesColors.mkString(",")) sb.append("&") // legend - sb.append("chdl=5th Percentile|Median|95th Percentile|Throughput") + sb.append("chdl=5th%20Percentile|Median|95th%20Percentile|Mean|Throughput") sb.append("&") sb.append("chdlp=b") @@ -160,6 +165,7 @@ object GoogleChartBuilder { sb.append("chls=1|1|1") sb.append("&") + // margins sb.append("chma=5,5,5,25") sb.append("&") @@ -181,6 +187,11 @@ object GoogleChartBuilder { } sb.append(percentileSeries.mkString("|")) + sb.append("|") + sb.append(loadStr).append("|") + val meanSeries = statistics.map(s ⇒ formatDouble(s.mean)).mkString(",") + sb.append(meanSeries) + sb.append("|") val maxTps: Double = statistics.map(_.tps).max sb.append(loadStr).append("|") @@ -192,7 +203,7 @@ object GoogleChartBuilder { // y range sb.append("&") - sb.append("chxr=0,").append(minLoad).append(",").append(maxLoad).append("|1,0,").append(maxValue).append("|2,0,") + sb.append("chxr=0,").append(minLoad).append(",").append(maxLoad).append(",4").append("|1,0,").append(maxValue).append("|2,0,") .append(formatDouble(maxTps)) sb.append("&") @@ -203,6 +214,9 @@ object GoogleChartBuilder { sb.append(",") } sb.append(minLoad).append(",").append(maxLoad) + sb.append(",0,").append(formatDouble(maxValue)) + sb.append(",") + sb.append(minLoad).append(",").append(maxLoad) sb.append(",0,").append(formatDouble(maxTps)) sb.append("&") diff --git a/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala b/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala new file mode 100644 index 0000000000..dce6017203 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala @@ -0,0 +1,220 @@ +package akka.performance.workbench + +import java.lang.management.ManagementFactory +import java.text.SimpleDateFormat +import java.util.Date + +import scala.collection.JavaConversions.asScalaBuffer +import scala.collection.JavaConversions.enumerationAsScalaIterator + +import akka.event.EventHandler +import akka.config.Config +import akka.config.Config.config + +class Report( + resultRepository: BenchResultRepository, + compareResultWith: Option[String] = None) { + + private def log = System.getProperty("benchmark.logResult", "true").toBoolean + + val dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm") + val legendTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm") + val fileTimestampFormat = new SimpleDateFormat("yyyyMMddHHmmss") + + def html(statistics: Seq[Stats]): Unit = { + + val current = statistics.last + val sb = new StringBuilder + + val title = current.name + " " + dateTimeFormat.format(new Date(current.timestamp)) + sb.append(header(title)) + sb.append("

%s

\n".format(title)) + + sb.append("
\n")
+    val resultTable = formatResultsTable(statistics)
+    sb.append(resultTable)
+    sb.append("\n
\n") + + sb.append(img(percentilesAndMeanChart(current))) + sb.append(img(latencyAndThroughputChart(current))) + + for (stats ← statistics) { + compareWithHistoricalPercentiliesAndMeanChart(stats).foreach(url ⇒ sb.append(img(url))) + } + + for (stats ← statistics) { + comparePercentilesAndMeanChart(stats).foreach(url ⇒ sb.append(img(url))) + } + + sb.append("
\n") + sb.append("
\n")
+    sb.append(systemInformation)
+    sb.append("\n
\n") + + val timestamp = fileTimestampFormat.format(new Date(current.timestamp)) + val reportName = current.name + "--" + timestamp + ".html" + resultRepository.saveHtmlReport(sb.toString, reportName) + + if (log) { + EventHandler.info(this, resultTable + "Charts in html report: " + resultRepository.htmlReportUrl(reportName)) + } + + } + + def img(url: String): String = { + """""".format( + url, GoogleChartBuilder.ChartWidth, GoogleChartBuilder.ChartHeight) + "\n" + } + + def percentilesAndMeanChart(stats: Stats): String = { + val chartTitle = stats.name + " Percentiles and Mean (microseconds)" + val chartUrl = GoogleChartBuilder.percentilesAndMeanChartUrl(resultRepository.get(stats.name), chartTitle, _.load + " clients") + chartUrl + } + + def comparePercentilesAndMeanChart(stats: Stats): Seq[String] = { + for { + compareName ← compareResultWith.toSeq + compareStats ← resultRepository.get(compareName, stats.load) + } yield { + val chartTitle = stats.name + " vs. " + compareName + ", " + stats.load + " clients" + ", Percentiles and Mean (microseconds)" + val chartUrl = GoogleChartBuilder.percentilesAndMeanChartUrl(Seq(compareStats, stats), chartTitle, _.name) + chartUrl + } + } + + def compareWithHistoricalPercentiliesAndMeanChart(stats: Stats): Option[String] = { + val withHistorical = resultRepository.getWithHistorical(stats.name, stats.load) + if (withHistorical.size > 1) { + val chartTitle = stats.name + " vs. historical, " + stats.load + " clients" + ", Percentiles and Mean (microseconds)" + val chartUrl = GoogleChartBuilder.percentilesAndMeanChartUrl(withHistorical, chartTitle, + stats ⇒ legendTimeFormat.format(new Date(stats.timestamp))) + Some(chartUrl) + } else { + None + } + } + + def latencyAndThroughputChart(stats: Stats): String = { + val chartTitle = stats.name + " Latency (microseconds) and Throughput (TPS)" + val chartUrl = GoogleChartBuilder.latencyAndThroughputChartUrl(resultRepository.get(stats.name), chartTitle) + chartUrl + } + + def formatResultsTable(statsSeq: Seq[Stats]): String = { + + val name = statsSeq.head.name + + val spaces = " " + val headerScenarioCol = ("Scenario" + spaces).take(name.length) + + val headerLine = (headerScenarioCol :: "clients" :: "TPS" :: "mean" :: "5% " :: "25% " :: "50% " :: "75% " :: "95% " :: "Durat." :: "N" :: Nil) + .mkString("\t") + val headerLine2 = (spaces.take(name.length) :: " " :: " " :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(us)" :: "(s) " :: " " :: Nil) + .mkString("\t") + val line = List.fill(formatStats(statsSeq.head).replaceAll("\t", " ").length)("-").mkString + val formattedStats = "\n" + + line.replace('-', '=') + "\n" + + headerLine + "\n" + + headerLine2 + "\n" + + line + "\n" + + statsSeq.map(formatStats(_)).mkString("\n") + "\n" + + line + "\n" + + formattedStats + + } + + def formatStats(stats: Stats): String = { + val durationS = stats.durationNanos.toDouble / 1000000000.0 + val duration = durationS.formatted("%.0f") + + val tpsStr = stats.tps.formatted("%.0f") + val meanStr = stats.mean.formatted("%.0f") + + val summaryLine = + stats.name :: + stats.load.toString :: + tpsStr :: + meanStr :: + stats.percentiles(5).toString :: + stats.percentiles(25).toString :: + stats.percentiles(50).toString :: + stats.percentiles(75).toString :: + stats.percentiles(95).toString :: + duration :: + stats.n.toString :: + Nil + + summaryLine.mkString("\t") + + } + + def systemInformation: String = { + val runtime = ManagementFactory.getRuntimeMXBean + val os = ManagementFactory.getOperatingSystemMXBean + val threads = ManagementFactory.getThreadMXBean + val mem = ManagementFactory.getMemoryMXBean + val heap = mem.getHeapMemoryUsage + + val sb = new StringBuilder + + sb.append("Benchmark properties:") + import scala.collection.JavaConversions._ + val propNames: Seq[String] = System.getProperties.propertyNames.toSeq.map(_.toString) + for (name ← propNames if name.startsWith("benchmark")) { + sb.append("\n ").append(name).append("=").append(System.getProperty(name)) + } + sb.append("\n") + + sb.append("Operating system: ").append(os.getName).append(", ").append(os.getArch).append(", ").append(os.getVersion) + sb.append("\n") + sb.append("JVM: ").append(runtime.getVmName).append(" ").append(runtime.getVmVendor). + append(" ").append(runtime.getVmVersion) + sb.append("\n") + sb.append("Processors: ").append(os.getAvailableProcessors) + sb.append("\n") + sb.append("Load average: ").append(os.getSystemLoadAverage) + sb.append("\n") + sb.append("Thread count: ").append(threads.getThreadCount).append(" (").append(threads.getPeakThreadCount).append(")") + sb.append("\n") + sb.append("Heap: ").append(formatDouble(heap.getUsed.toDouble / 1024 / 1024)). + append(" (").append(formatDouble(heap.getInit.toDouble / 1024 / 1024)). + append(" - "). + append(formatDouble(heap.getMax.toDouble / 1024 / 1024)). + append(")").append(" MB") + sb.append("\n") + + val args = runtime.getInputArguments.filterNot(_.contains("classpath")).mkString("\n ") + sb.append("Args:\n ").append(args) + sb.append("\n") + + sb.append("Akka version: ").append(Config.CONFIG_VERSION) + sb.append("\n") + sb.append("Akka config:") + for (key ← config.keys) { + sb.append("\n ").append(key).append("=").append(config(key)) + } + + sb.toString + } + + def formatDouble(value: Double): String = { + new java.math.BigDecimal(value).setScale(2, java.math.RoundingMode.HALF_EVEN).toString + } + + def header(title: String) = + """| + | + | + | + |%s + | + | + |""".stripMargin.format(title) + + def footer = + """|" + |""".stripMargin + +} \ No newline at end of file diff --git a/akka-actor-tests/src/test/scala/akka/performance/trading/common/Stats.scala b/akka-actor-tests/src/test/scala/akka/performance/workbench/Stats.scala similarity index 84% rename from akka-actor-tests/src/test/scala/akka/performance/trading/common/Stats.scala rename to akka-actor-tests/src/test/scala/akka/performance/workbench/Stats.scala index 1b1b854cb0..c307d997e3 100644 --- a/akka-actor-tests/src/test/scala/akka/performance/trading/common/Stats.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/workbench/Stats.scala @@ -1,7 +1,8 @@ -package akka.performance.trading.common +package akka.performance.workbench import scala.collection.immutable.TreeMap +@SerialVersionUID(1L) case class Stats( name: String, load: Int, 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 13d5e3ac28..6654943886 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala @@ -10,6 +10,7 @@ import akka.util.duration._ import akka.actor._ import akka.actor.Actor._ import akka.routing._ +import akka.event.EventHandler import java.util.concurrent.atomic.AtomicInteger import akka.dispatch.{ KeptPromise, Future } @@ -186,13 +187,13 @@ class RoutingSpec extends WordSpec with MustMatchers { val count = new AtomicInteger(0) val pool = actorOf( - new Actor with DefaultActorPool with FixedCapacityStrategy with SmallestMailboxSelector { + new Actor with DefaultActorPool with DefaultActorPoolSupervisionConfig with FixedCapacityStrategy with SmallestMailboxSelector { def factory = actorOf(new Actor { def receive = { case _ ⇒ count.incrementAndGet latch.countDown() - self reply_? "success" + self tryReply "success" } }).start() @@ -226,7 +227,7 @@ class RoutingSpec extends WordSpec with MustMatchers { "pass ticket #705" in { val pool = actorOf( - new Actor with DefaultActorPool with BoundedCapacityStrategy with MailboxPressureCapacitor with SmallestMailboxSelector with BasicFilter { + new Actor with DefaultActorPool with DefaultActorPoolSupervisionConfig with BoundedCapacityStrategy with MailboxPressureCapacitor with SmallestMailboxSelector with BasicFilter { def lowerBound = 2 def upperBound = 20 def rampupRate = 0.1 @@ -241,7 +242,7 @@ class RoutingSpec extends WordSpec with MustMatchers { def receive = { case req: String ⇒ { sleepFor(10 millis) - self.reply_?("Response") + self.tryReply("Response") } } }) @@ -264,7 +265,7 @@ class RoutingSpec extends WordSpec with MustMatchers { val count = new AtomicInteger(0) val pool = actorOf( - new Actor with DefaultActorPool with BoundedCapacityStrategy with ActiveFuturesPressureCapacitor with SmallestMailboxSelector with BasicNoBackoffFilter { + new Actor with DefaultActorPool with DefaultActorPoolSupervisionConfig with BoundedCapacityStrategy with ActiveFuturesPressureCapacitor with SmallestMailboxSelector with BasicNoBackoffFilter { def factory = actorOf(new Actor { def receive = { case n: Int ⇒ @@ -329,7 +330,7 @@ class RoutingSpec extends WordSpec with MustMatchers { val count = new AtomicInteger(0) val pool = actorOf( - new Actor with DefaultActorPool with BoundedCapacityStrategy with MailboxPressureCapacitor with SmallestMailboxSelector with BasicNoBackoffFilter { + new Actor with DefaultActorPool with DefaultActorPoolSupervisionConfig with BoundedCapacityStrategy with MailboxPressureCapacitor with SmallestMailboxSelector with BasicNoBackoffFilter { def factory = actorOf(new Actor { def receive = { case n: Int ⇒ @@ -383,7 +384,7 @@ class RoutingSpec extends WordSpec with MustMatchers { val delegates = new java.util.concurrent.ConcurrentHashMap[String, String] val pool1 = actorOf( - new Actor with DefaultActorPool with FixedCapacityStrategy with RoundRobinSelector with BasicNoBackoffFilter { + new Actor with DefaultActorPool with DefaultActorPoolSupervisionConfig with FixedCapacityStrategy with RoundRobinSelector with BasicNoBackoffFilter { def factory = actorOf(new Actor { def receive = { case _ ⇒ @@ -412,7 +413,7 @@ class RoutingSpec extends WordSpec with MustMatchers { delegates.clear() val pool2 = actorOf( - new Actor with DefaultActorPool with FixedCapacityStrategy with RoundRobinSelector with BasicNoBackoffFilter { + new Actor with DefaultActorPool with DefaultActorPoolSupervisionConfig with FixedCapacityStrategy with RoundRobinSelector with BasicNoBackoffFilter { def factory = actorOf(new Actor { def receive = { case _ ⇒ @@ -442,7 +443,7 @@ class RoutingSpec extends WordSpec with MustMatchers { val latch = TestLatch(10) val pool = actorOf( - new Actor with DefaultActorPool with BoundedCapacityStrategy with MailboxPressureCapacitor with SmallestMailboxSelector with Filter with RunningMeanBackoff with BasicRampup { + new Actor with DefaultActorPool with DefaultActorPoolSupervisionConfig with BoundedCapacityStrategy with MailboxPressureCapacitor with SmallestMailboxSelector with Filter with RunningMeanBackoff with BasicRampup { def factory = actorOf(new Actor { def receive = { case n: Int ⇒ @@ -488,7 +489,7 @@ class RoutingSpec extends WordSpec with MustMatchers { "support typed actors" in { import RoutingSpec._ import TypedActor._ - def createPool = new Actor with DefaultActorPool with BoundedCapacityStrategy with MailboxPressureCapacitor with SmallestMailboxSelector with Filter with RunningMeanBackoff with BasicRampup { + def createPool = new Actor with DefaultActorPool with DefaultActorPoolSupervisionConfig with BoundedCapacityStrategy with MailboxPressureCapacitor with SmallestMailboxSelector with Filter with RunningMeanBackoff with BasicRampup { def lowerBound = 1 def upperBound = 5 def pressureThreshold = 1 @@ -507,6 +508,214 @@ class RoutingSpec extends WordSpec with MustMatchers { for ((i, r) ← results) r.get must equal(i * i) } + + "provide default supervision of pooled actors" in { + import akka.config.Supervision._ + val pingCount = new AtomicInteger(0) + val deathCount = new AtomicInteger(0) + var keepDying = false + + val pool1 = actorOf( + new Actor with DefaultActorPool with DefaultActorPoolSupervisionConfig with BoundedCapacityStrategy with ActiveFuturesPressureCapacitor with SmallestMailboxSelector with BasicFilter { + def lowerBound = 2 + def upperBound = 5 + def rampupRate = 0.1 + def backoffRate = 0.1 + def backoffThreshold = 0.5 + def partialFill = true + def selectionCount = 1 + def instance = factory + def receive = _route + def pressureThreshold = 1 + def factory = actorOf(new Actor { + if (deathCount.get > 5) deathCount.set(0) + if (deathCount.get > 0) {deathCount.incrementAndGet;throw new IllegalStateException("keep dying")} + def receive = { + case akka.Die ⇒ + if (keepDying) deathCount.incrementAndGet + throw new RuntimeException + case _ => pingCount.incrementAndGet + } + }).start() + }).start() + + val pool2 = actorOf( + new Actor with DefaultActorPool with DefaultActorPoolSupervisionConfig with BoundedCapacityStrategy with ActiveFuturesPressureCapacitor with SmallestMailboxSelector with BasicFilter { + def lowerBound = 2 + def upperBound = 5 + def rampupRate = 0.1 + def backoffRate = 0.1 + def backoffThreshold = 0.5 + def partialFill = true + def selectionCount = 1 + def instance = factory + def receive = _route + def pressureThreshold = 1 + def factory = actorOf(new Actor { + self.lifeCycle = Permanent + if (deathCount.get > 5) deathCount.set(0) + if (deathCount.get > 0) {deathCount.incrementAndGet;throw new IllegalStateException("keep dying")} + def receive = { + case akka.Die ⇒ + if (keepDying) deathCount.incrementAndGet + throw new RuntimeException + case _ => pingCount.incrementAndGet + } + }).start() + }).start() + + val pool3 = actorOf( + new Actor with DefaultActorPool with DefaultActorPoolSupervisionConfig with BoundedCapacityStrategy with ActiveFuturesPressureCapacitor with RoundRobinSelector with BasicFilter { + def lowerBound = 2 + def upperBound = 5 + def rampupRate = 0.1 + def backoffRate = 0.1 + def backoffThreshold = 0.5 + def partialFill = true + def selectionCount = 1 + def instance = factory + def receive = _route + def pressureThreshold = 1 + def factory = actorOf(new Actor { + self.lifeCycle = Temporary + if (deathCount.get > 5) deathCount.set(0) + if (deathCount.get > 0) {deathCount.incrementAndGet;throw new IllegalStateException("keep dying")} + def receive = { + case akka.Die ⇒ + if (keepDying) deathCount.incrementAndGet + throw new RuntimeException + case _ => pingCount.incrementAndGet + } + }).start() + }).start() + + // default lifecycle + // actor comes back right away + pingCount.set(0) + keepDying = false + pool1 ! "ping" + (pool1 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pool1 ! akka.Die + sleepFor(2 seconds) + (pool1 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pingCount.get must be (1) + + // default lifecycle + // actor dies completely + pingCount.set(0) + keepDying = true + pool1 ! "ping" + (pool1 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pool1 ! akka.Die + sleepFor(2 seconds) + (pool1 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(1) + pool1 ! "ping" + (pool1 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pingCount.get must be (2) + + // permanent lifecycle + // actor comes back right away + pingCount.set(0) + keepDying = false + pool2 ! "ping" + (pool2 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pool2 ! akka.Die + sleepFor(2 seconds) + (pool2 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pingCount.get must be (1) + + // permanent lifecycle + // actor dies completely + pingCount.set(0) + keepDying = true + pool2 ! "ping" + (pool2 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pool2 ! akka.Die + sleepFor(2 seconds) + (pool2 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(1) + pool2 ! "ping" + (pool2 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pingCount.get must be (2) + + // temporary lifecycle + pingCount.set(0) + keepDying = false + pool3 ! "ping" + (pool3 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pool3 ! akka.Die + sleepFor(2 seconds) + (pool3 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(1) + pool3 ! "ping" + pool3 ! "ping" + pool3 ! "ping" + (pool3 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pingCount.get must be (4) + } + + "support customizable supervision config of pooled actors" in { + import akka.config.Supervision._ + val pingCount = new AtomicInteger(0) + val deathCount = new AtomicInteger(0) + var keepDying = false + + trait LimitedTrapSupervisionConfig extends ActorPoolSupervisionConfig { + def poolFaultHandler = OneForOneStrategy(List(classOf[IllegalStateException]), 5, 1000) + } + + object BadState + + val pool1 = actorOf( + new Actor with DefaultActorPool with LimitedTrapSupervisionConfig with BoundedCapacityStrategy with ActiveFuturesPressureCapacitor with SmallestMailboxSelector with BasicFilter { + def lowerBound = 2 + def upperBound = 5 + def rampupRate = 0.1 + def backoffRate = 0.1 + def backoffThreshold = 0.5 + def partialFill = true + def selectionCount = 1 + def instance = factory + def receive = _route + def pressureThreshold = 1 + def factory = actorOf(new Actor { + if (deathCount.get > 5) deathCount.set(0) + if (deathCount.get > 0) {deathCount.incrementAndGet;throw new IllegalStateException("keep dying")} + def receive = { + case BadState ⇒ + if (keepDying) deathCount.incrementAndGet + throw new IllegalStateException + case akka.Die => + throw new RuntimeException + case _ => pingCount.incrementAndGet + } + }).start() + }).start() + + + // actor comes back right away + pingCount.set(0) + keepDying = false + pool1 ! "ping" + (pool1 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pool1 ! BadState + sleepFor(2 seconds) + (pool1 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pingCount.get must be (1) + + // actor dies completely + pingCount.set(0) + keepDying = true + pool1 ! "ping" + (pool1 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pool1 ! BadState + sleepFor(2 seconds) + (pool1 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(1) + pool1 ! "ping" + (pool1 ? ActorPool.Stat).as[ActorPool.Stats].get.size must be(2) + pingCount.get must be (2) + + // kill it + intercept[RuntimeException](pool1.?(akka.Die).get) + } } } diff --git a/akka-actor-tests/src/test/scala/akka/ticket/Ticket703Spec.scala b/akka-actor-tests/src/test/scala/akka/ticket/Ticket703Spec.scala index b838f33efc..849070752b 100644 --- a/akka-actor-tests/src/test/scala/akka/ticket/Ticket703Spec.scala +++ b/akka-actor-tests/src/test/scala/akka/ticket/Ticket703Spec.scala @@ -11,7 +11,7 @@ class Ticket703Spec extends WordSpec with MustMatchers { "A ? call to an actor pool" should { "reuse the proper timeout" in { val actorPool = actorOf( - new Actor with DefaultActorPool with BoundedCapacityStrategy with MailboxPressureCapacitor with SmallestMailboxSelector with BasicNoBackoffFilter { + new Actor with DefaultActorPool with DefaultActorPoolSupervisionConfig with BoundedCapacityStrategy with MailboxPressureCapacitor with SmallestMailboxSelector with BasicNoBackoffFilter { def lowerBound = 2 def upperBound = 20 def rampupRate = 0.1 @@ -24,7 +24,7 @@ class Ticket703Spec extends WordSpec with MustMatchers { def receive = { case req: String ⇒ Thread.sleep(6000L) - self.reply_?("Response") + self.tryReply("Response") } }) }).start() diff --git a/akka-actor/src/main/scala/akka/AkkaException.scala b/akka-actor/src/main/scala/akka/AkkaException.scala index f87453db10..24831ff589 100644 --- a/akka-actor/src/main/scala/akka/AkkaException.scala +++ b/akka-actor/src/main/scala/akka/AkkaException.scala @@ -26,6 +26,8 @@ class AkkaException(message: String = "", cause: Throwable = null) extends Runti lazy val toLongString = "%s: %s\n[%s]\n%s".format(getClass.getName, message, uuid, stackTraceToString) + def this(msg:String) = this(msg, null); + def stackTraceToString = { val trace = getStackTrace val sb = new StringBuffer diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index ec955a9b9d..94a830c4ba 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -5,6 +5,7 @@ package akka.actor import DeploymentConfig._ +import akka.experimental import akka.dispatch._ import akka.config._ import Config._ @@ -79,12 +80,29 @@ case class MaximumNumberOfRestartsWithinTimeRangeReached( @BeanProperty lastExceptionCausingRestart: Throwable) extends LifeCycleMessage // Exceptions for Actors -class ActorStartException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) -class IllegalActorStateException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) -class ActorKilledException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) -class ActorInitializationException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) -class ActorTimeoutException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) -class InvalidMessageException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) +class ActorStartException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause){ + def this(msg:String) = this(msg, null); +} + +class IllegalActorStateException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) { + def this(msg:String) = this(msg, null); +} + +class ActorKilledException private[akka] (message: String, cause: Throwable) extends AkkaException(message, cause){ + def this(msg: String) = this(msg, null); +} + +class ActorInitializationException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) { + def this(msg:String) = this(msg, null); +} + +class ActorTimeoutException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) { + def this(msg:String) = this(msg, null); +} + +class InvalidMessageException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) { + def this(msg:String) = this(msg, null); +} /** * This message is thrown by default when an Actors behavior doesn't match a message @@ -477,13 +495,10 @@ object Actor extends ListenerManagement { "Remote server is not running") val isHomeNode = DeploymentConfig.isHomeNode(preferredHomeNodes) - val nrOfReplicas = DeploymentConfig.replicaValueFor(replicas) - - def serializerErrorDueTo(reason: String) = throw new akka.config.ConfigurationException( - "Could not create Serializer for actor [" + address + "] due to: " + reason) + val nrOfReplicas = replicas.factor val serializer: Serializer = - Serialization.serializerFor(this.getClass).fold(x ⇒ serializerErrorDueTo(x.toString), s ⇒ s) + Serialization.serializerFor(this.getClass) def storeActorAndGetClusterRef(replicationScheme: ReplicationScheme, serializer: Serializer): ActorRef = { // add actor to cluster registry (if not already added) @@ -680,9 +695,23 @@ trait Actor { /** * User overridable callback. *

- * Is called on a crashed Actor right BEFORE it is restarted to allow clean up of resources before Actor is terminated. + * Is called on a crashed Actor right BEFORE it is restarted to allow clean + * up of resources before Actor is terminated. */ - def preRestart(reason: Throwable) {} + def preRestart(reason: Throwable, message: Option[Any]) {} + + /** + * User overridable callback. + *

+ * Is called on the crashed Actor to give it the option of producing the + * Actor's reincarnation. If it returns None, which is the default, the + * initially provided actor factory is used. + *

+ * Warning: Propagating state from a crashed actor carries the risk + * of proliferating the cause of the error. Consider let-it-crash first. + */ + @experimental("1.2") + def freshInstance(): Option[Actor] = None /** * User overridable callback. diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index fb7ecfbdb5..fcb0684131 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -12,8 +12,6 @@ import akka.util._ import akka.serialization.{Serializer, Serialization} import ReflectiveAccess._ import ClusterModule._ -import DeploymentConfig.{TransactionLog ⇒ TransactionLogConfig, _} - import java.net.InetSocketAddress import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.{ScheduledFuture, ConcurrentHashMap, TimeUnit} @@ -243,6 +241,13 @@ trait ActorRef extends ActorRefShared with ForwardableChannel with java.lang.Com */ def ask(message: AnyRef): Future[AnyRef] = ask(message, timeout, null) + /** + * Akka Java API.

+ * @see ask(message: AnyRef, sender: ActorRef): Future[_] + * Uses the specified timeout (milliseconds) + */ + def ask(message: AnyRef, timeout: Long): Future[Any] = ask(message, timeout, null) + /** * Akka Java API.

* @see ask(message: AnyRef, sender: ActorRef): Future[_] @@ -274,24 +279,29 @@ trait ActorRef extends ActorRefShared with ForwardableChannel with java.lang.Com } /** - * Akka Java API.

- * Use getContext().replyUnsafe(..) to reply with a message to the original sender of the message currently - * being processed. + * Akka Scala & Java API + * Use self.reply(..) to reply with a message to the original sender of the message currently + * being processed. This method fails if the original sender of the message could not be determined with an + * IllegalStateException. + * + * If you don't want deal with this IllegalStateException, but just a boolean, just use the tryReply(...) + * version. + * *

* Throws an IllegalStateException if unable to determine what to reply to. */ - def replyUnsafe(message: AnyRef) { - reply(message) - } + def reply(message: Any) = channel.!(message)(this) /** - * Akka Java API.

- * Use getContext().replySafe(..) to reply with a message to the original sender of the message currently - * being processed. + * Akka Scala & Java API + * Use tryReply(..) to try reply with a message to the original sender of the message currently + * being processed. This method *

* Returns true if reply was sent, and false if unable to determine what to reply to. + * + * If you would rather have an exception, check the reply(..) version. */ - def replySafe(message: AnyRef): Boolean = reply_?(message) + def tryReply(message: Any): Boolean = channel.safe_!(message)(this) /** * Sets the dispatcher for this actor. Needs to be invoked before the actor is started. @@ -470,29 +480,22 @@ class LocalActorRef private[akka](private[this] val actorFactory: () ⇒ Actor, "]") private val serializer: Serializer = - Serialization.serializerFor(this.getClass).fold(x ⇒ serializerErrorDueTo(x.toString), s ⇒ s) + try { Serialization.serializerFor(this.getClass) } catch { case e: Exception => serializerErrorDueTo(e.toString)} - private lazy val replicationScheme: ReplicationScheme = - DeploymentConfig.replicationSchemeFor(Deployer.deploymentFor(address)).getOrElse(Transient) + private lazy val replicationStorage: Option[TransactionLog] = { + import DeploymentConfig._ + val replicationScheme = replicationSchemeFor(Deployer.deploymentFor(address)).getOrElse(Transient) + if(isReplicated(replicationScheme)) { + if (isReplicatedWithTransactionLog(replicationScheme)) { + EventHandler.debug(this, "Creating a transaction log for Actor [%s] with replication strategy [%s]".format(address, replicationScheme)) - private lazy val isReplicated: Boolean = DeploymentConfig.isReplicated(replicationScheme) - - private lazy val isWriteBehindReplication: Boolean = DeploymentConfig.isWriteBehindReplication(replicationScheme) - - private lazy val replicationStorage: Either[TransactionLog, AnyRef] = { - if (DeploymentConfig.isReplicatedWithTransactionLog(replicationScheme)) { - EventHandler.debug(this, - "Creating a transaction log for Actor [%s] with replication strategy [%s]" - .format(address, replicationScheme)) - - Left(transactionLog.newLogFor(_uuid.toString, isWriteBehindReplication, replicationScheme)) - - } else if (DeploymentConfig.isReplicatedWithDataGrid(replicationScheme)) { - throw new ConfigurationException("Replication storage type \"data-grid\" is not yet supported") - - } else { - throw new ConfigurationException("Unknown replication storage type [" + replicationScheme + "]") - } + Some(transactionLog.newLogFor(_uuid.toString, isWriteBehindReplication(replicationScheme), replicationScheme)) //TODO FIXME @jboner shouldn't this be address? + } else if (isReplicatedWithDataGrid(replicationScheme)) { + throw new ConfigurationException("Replication storage type \"data-grid\" is not yet supported") + } else { + throw new ConfigurationException("Unknown replication storage type [" + replicationScheme + "]") + } + } else None } // If it was started inside "newActor", initialize it @@ -586,9 +589,7 @@ class LocalActorRef private[akka](private[this] val actorFactory: () ⇒ Actor, } } //else if (isBeingRestarted) throw new ActorKilledException("Actor [" + toString + "] is being restarted.") - if (isReplicated) { - if (replicationStorage.isLeft) replicationStorage.left.get.delete() - } + if (replicationStorage.isDefined) replicationStorage.get.delete() } } @@ -717,9 +718,7 @@ class LocalActorRef private[akka](private[this] val actorFactory: () ⇒ Actor, } } finally { guard.lock.unlock() - if (isReplicated) { - if (replicationStorage.isLeft) replicationStorage.left.get.recordEntry(messageHandle, this) - } + if (replicationStorage.isDefined) replicationStorage.get.recordEntry(messageHandle, this) } } @@ -775,7 +774,8 @@ class LocalActorRef private[akka](private[this] val actorFactory: () ⇒ Actor, def performRestart() { val failedActor = actorInstance.get if (Actor.debugLifecycle) EventHandler.debug(failedActor, "restarting") - failedActor.preRestart(reason) + val message = if (currentMessage ne null) Some(currentMessage.message) else None + failedActor.preRestart(reason, message) val freshActor = newActor setActorSelfFields(failedActor, null) // Only null out the references if we could instantiate the new actor actorInstance.set(freshActor) // Assign it here so if preStart fails, we can null out the sef-refs next call @@ -798,7 +798,7 @@ class LocalActorRef private[akka](private[this] val actorFactory: () ⇒ Actor, lifeCycle match { case Temporary ⇒ - shutDownTemporaryActor(this) + shutDownTemporaryActor(this, reason) true case _ ⇒ // either permanent or none where default is permanent @@ -841,8 +841,8 @@ class LocalActorRef private[akka](private[this] val actorFactory: () ⇒ Actor, val actorRef = i.next actorRef.lifeCycle match { // either permanent or none where default is permanent - case Temporary ⇒ shutDownTemporaryActor(actorRef) - case _ ⇒ actorRef.restart(reason, maxNrOfRetries, withinTimeRange) + case Temporary ⇒ shutDownTemporaryActor(actorRef, reason) + case _ ⇒ actorRef.restart(reason, maxNrOfRetries, withinTimeRange) } } } @@ -856,7 +856,20 @@ class LocalActorRef private[akka](private[this] val actorFactory: () ⇒ Actor, val stackBefore = refStack.get refStack.set(stackBefore.push(this)) try { - actorFactory() + if (_status == ActorRefInternals.BEING_RESTARTED) { + val a = actor + val fresh = try a.freshInstance catch { + case e ⇒ + EventHandler.error(e, a, "freshInstance() failed, falling back to initial actor factory") + None + } + fresh match { + case Some(ref) ⇒ ref + case None ⇒ actorFactory() + } + } else { + actorFactory() + } } finally { val stackAfter = refStack.get if (stackAfter.nonEmpty) @@ -867,9 +880,11 @@ class LocalActorRef private[akka](private[this] val actorFactory: () ⇒ Actor, case valid ⇒ valid } - private def shutDownTemporaryActor(temporaryActor: ActorRef) { + private def shutDownTemporaryActor(temporaryActor: ActorRef, reason: Throwable) { temporaryActor.stop() _linkedActors.remove(temporaryActor.uuid) // remove the temporary actor + // when this comes down through the handleTrapExit path, we get here when the temp actor is restarted + notifySupervisorWithMessage(MaximumNumberOfRestartsWithinTimeRangeReached(temporaryActor, Some(0), None, reason)) // if last temporary actor is gone, then unlink me from supervisor if (_linkedActors.isEmpty) notifySupervisorWithMessage(UnlinkAndStop(this)) true @@ -886,8 +901,8 @@ class LocalActorRef private[akka](private[this] val actorFactory: () ⇒ Actor, if (supervisor.isDefined) notifySupervisorWithMessage(Death(this, reason)) else { lifeCycle match { - case Temporary ⇒ shutDownTemporaryActor(this) - case _ ⇒ dispatcher.resume(this) //Resume processing for this actor + case Temporary ⇒ shutDownTemporaryActor(this, reason) + case _ ⇒ dispatcher.resume(this) //Resume processing for this actor } } } @@ -1008,8 +1023,8 @@ private[akka] case class RemoteActorRef private[akka]( case _ ⇒ None } val chFuture = channel match { - case f: Promise[Any] ⇒ Some(f) - case _ ⇒ None + case f: Promise[_] ⇒ Some(f.asInstanceOf[Promise[Any]]) + case _ ⇒ None } val future = Actor.remote.send[Any](message, chSender, chFuture, remoteAddress, timeout.duration.toMillis, false, this, loader) if (future.isDefined) ActorPromise(future.get) @@ -1226,22 +1241,6 @@ trait ScalaActorRef extends ActorRefShared with ForwardableChannel { } else throw new ActorInitializationException( "Actor has not been started, you need to invoke 'actor.start()' before using it") } - - /** - * Use self.reply(..) to reply with a message to the original sender of the message currently - * being processed. - *

- * Throws an IllegalStateException if unable to determine what to reply to. - */ - def reply(message: Any) = channel.!(message)(this) - - /** - * Use reply_?(..) to reply with a message to the original sender of the message currently - * being processed. - *

- * Returns true if reply was sent, and false if unable to determine what to reply to. - */ - def reply_?(message: Any): Boolean = channel.safe_!(message)(this) } case class SerializedActorRef(uuid: Uuid, diff --git a/akka-actor/src/main/scala/akka/actor/Deployer.scala b/akka-actor/src/main/scala/akka/actor/Deployer.scala index c754d1733d..3e98716722 100644 --- a/akka-actor/src/main/scala/akka/actor/Deployer.scala +++ b/akka-actor/src/main/scala/akka/actor/Deployer.scala @@ -11,9 +11,7 @@ import java.util.concurrent.ConcurrentHashMap import akka.event.EventHandler import akka.actor.DeploymentConfig._ import akka.config.{ ConfigurationException, Config } -import akka.routing.RouterType import akka.util.ReflectiveAccess._ -import akka.serialization._ import akka.AkkaException /** @@ -204,15 +202,15 @@ object Deployer { // -------------------------------- // akka.actor.deployment.

.clustered.replicas // -------------------------------- - val replicas = { - if (router == Direct) Replicate(1) + val replicationFactor = { + if (router == Direct) ReplicationFactor(1) else { - clusteredConfig.getAny("replicas", "0") match { - case "auto" ⇒ AutoReplicate - case "0" ⇒ NoReplicas + clusteredConfig.getAny("replication-factor", "0") match { + case "auto" ⇒ AutoReplicationFactor + case "0" ⇒ ZeroReplicationFactor case nrOfReplicas: String ⇒ try { - Replicate(nrOfReplicas.toInt) + ReplicationFactor(nrOfReplicas.toInt) } catch { case e: NumberFormatException ⇒ throw new ConfigurationException( @@ -229,7 +227,7 @@ object Deployer { // -------------------------------- clusteredConfig.getSection("replication") match { case None ⇒ - Some(Deploy(address, router, Clustered(preferredNodes, replicas, Transient))) + Some(Deploy(address, router, Clustered(preferredNodes, replicationFactor, Transient))) case Some(replicationConfig) ⇒ val storage = replicationConfig.getString("storage", "transaction-log") match { @@ -248,7 +246,7 @@ object Deployer { ".clustered.replication.strategy] needs to be either [\"write-through\"] or [\"write-behind\"] - was [" + unknown + "]") } - Some(Deploy(address, router, Clustered(preferredNodes, replicas, Replication(storage, strategy)))) + Some(Deploy(address, router, Clustered(preferredNodes, replicationFactor, Replication(storage, strategy)))) } } } diff --git a/akka-actor/src/main/scala/akka/actor/DeploymentConfig.scala b/akka-actor/src/main/scala/akka/actor/DeploymentConfig.scala index bc058e87f0..f5206b7668 100644 --- a/akka-actor/src/main/scala/akka/actor/DeploymentConfig.scala +++ b/akka-actor/src/main/scala/akka/actor/DeploymentConfig.scala @@ -6,7 +6,6 @@ package akka.actor import akka.config.Config import akka.routing.RouterType -import akka.serialization.Serializer /** * Module holding the programmatic deployment configuration classes. @@ -53,7 +52,7 @@ object DeploymentConfig { sealed trait Scope case class Clustered( preferredNodes: Iterable[Home] = Vector(Host("localhost")), - replicas: Replicas = NoReplicas, + replicas: ReplicationFactor = ZeroReplicationFactor, replication: ReplicationScheme = Transient) extends Scope // For Java API @@ -73,18 +72,17 @@ object DeploymentConfig { // -------------------------------- // --- Replicas // -------------------------------- - sealed trait Replicas - case class Replicate(factor: Int) extends Replicas { - if (factor < 1) throw new IllegalArgumentException("Replicas factor can not be negative or zero") + sealed case class ReplicationFactor(val factor: Int) { + if (factor < 0) throw new IllegalArgumentException("replication-factor can not be negative") } // For Java API - case class AutoReplicate() extends Replicas - case class NoReplicas() extends Replicas + case class AutoReplicationFactor() extends ReplicationFactor(-1) + case class ZeroReplicationFactor() extends ReplicationFactor(0) // For Scala API - case object AutoReplicate extends Replicas - case object NoReplicas extends Replicas + case object AutoReplicationFactor extends ReplicationFactor(-1) + case object ZeroReplicationFactor extends ReplicationFactor(0) // -------------------------------- // --- Replication @@ -141,14 +139,6 @@ object DeploymentConfig { def isHomeNode(homes: Iterable[Home]): Boolean = homes exists (home ⇒ nodeNameFor(home) == Config.nodename) - def replicaValueFor(replicas: Replicas): Int = replicas match { - case Replicate(replicas) ⇒ replicas - case AutoReplicate ⇒ -1 - case AutoReplicate() ⇒ -1 - case NoReplicas ⇒ 0 - case NoReplicas() ⇒ 0 - } - def routerTypeFor(routing: Routing): RouterType = routing match { case Direct ⇒ RouterType.Direct case Direct() ⇒ RouterType.Direct diff --git a/akka-actor/src/main/scala/akka/actor/Scheduler.scala b/akka-actor/src/main/scala/akka/actor/Scheduler.scala index efbec15239..9c93e2dbdd 100644 --- a/akka-actor/src/main/scala/akka/actor/Scheduler.scala +++ b/akka-actor/src/main/scala/akka/actor/Scheduler.scala @@ -22,7 +22,6 @@ import java.util.concurrent._ import java.lang.RuntimeException object Scheduler { - import Actor._ case class SchedulerException(msg: String, e: Throwable) extends AkkaException(msg, e) diff --git a/akka-actor/src/main/scala/akka/actor/Supervisor.scala b/akka-actor/src/main/scala/akka/actor/Supervisor.scala index 5063ca994a..95532e8fb7 100644 --- a/akka-actor/src/main/scala/akka/actor/Supervisor.scala +++ b/akka-actor/src/main/scala/akka/actor/Supervisor.scala @@ -10,10 +10,11 @@ import ReflectiveAccess._ import Actor._ import java.util.concurrent.{ CopyOnWriteArrayList, ConcurrentHashMap } -import java.net.InetSocketAddress import akka.config.Supervision._ -class SupervisorException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) +class SupervisorException private[akka] (message: String, cause: Throwable = null) extends AkkaException(message, cause) { + def this(msg:String) = this(msg, null); +} /** * Factory object for creating supervisors declarative. It creates instances of the 'Supervisor' class. diff --git a/akka-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-actor/src/main/scala/akka/actor/TypedActor.scala index 083d42b403..cf15c7d520 100644 --- a/akka-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-actor/src/main/scala/akka/actor/TypedActor.scala @@ -10,8 +10,8 @@ import akka.dispatch.{ MessageDispatcher, Dispatchers, Future, FutureTimeoutExce import java.lang.reflect.{ InvocationTargetException, Method, InvocationHandler, Proxy } import akka.util.{ Duration } import java.util.concurrent.atomic.{ AtomicReference ⇒ AtomVar } -import akka.serialization.Serialization import com.sun.xml.internal.ws.developer.MemberSubmissionAddressing.Validation +import akka.serialization.{Serializer, Serialization} //TODO Document this class, not only in Scaladoc, but also in a dedicated typed-actor.rst, for both java and scala /** @@ -89,33 +89,35 @@ object TypedActor { } } catch { case i: InvocationTargetException ⇒ throw i.getTargetException } - private def writeReplace(): AnyRef = { - val serializedParameters: Array[(Array[Byte],String)] = parameters match { - case null => null - case a if a.length == 0 => Array[(Array[Byte],String)]() - case a => a.map( { - case null => null - case value => Serialization.serializerFor(value.getClass).fold(throw _, s => (s.toBinary(value), s.getClass.getName)) - }) - } - new SerializedMethodCall(method.getDeclaringClass, method.getName, method.getParameterTypes, serializedParameters) + private def writeReplace(): AnyRef = parameters match { + case null => SerializedMethodCall(method.getDeclaringClass, method.getName, method.getParameterTypes, null, null) + case ps if ps.length == 0 => SerializedMethodCall(method.getDeclaringClass, method.getName, method.getParameterTypes, Array[Serializer.Identifier](), Array[Array[Byte]]()) + case ps => + val serializers: Array[Serializer] = ps map Serialization.findSerializerFor + val serializedParameters: Array[Array[Byte]] = Array.ofDim[Array[Byte]](serializers.length) + for(i <- 0 until serializers.length) + serializedParameters(i) = serializers(i) toBinary parameters(i) //Mutable for the sake of sanity + + SerializedMethodCall(method.getDeclaringClass, method.getName, method.getParameterTypes, serializers.map(_.identifier), serializedParameters) } } /** * Represents the serialized form of a MethodCall, uses readResolve and writeReplace to marshall the call */ - case class SerializedMethodCall(ownerType: Class[_], methodName: String, parameterTypes: Array[Class[_]], serializedParameters: Array[(Array[Byte],String)]) { + case class SerializedMethodCall(ownerType: Class[_], methodName: String, parameterTypes: Array[Class[_]], serializerIdentifiers: Array[Serializer.Identifier], serializedParameters: Array[Array[Byte]]) { //TODO implement writeObject and readObject to serialize //TODO Possible optimization is to special encode the parameter-types to conserve space private def readResolve(): AnyRef = { MethodCall(ownerType.getDeclaredMethod(methodName, parameterTypes: _*), serializedParameters match { case null => null case a if a.length == 0 => Array[AnyRef]() - case a => a.map( { - case null => null - case (bytes, serializerFQN) => Serialization.serializerOf(serializerFQN).fold(throw _, _.fromBinary(bytes)) - }) + case a => + val deserializedParameters: Array[AnyRef] = Array.ofDim[AnyRef](a.length) //Mutable for the sake of sanity + for(i <- 0 until a.length) + deserializedParameters(i) = Serialization.serializerByIdentity(serializerIdentifiers(i)).fromBinary(serializedParameters(i)) + + deserializedParameters }) } } diff --git a/akka-actor/src/main/scala/akka/actor/UntypedActor.scala b/akka-actor/src/main/scala/akka/actor/UntypedActor.scala index d23002c5bc..a89a9bccc0 100644 --- a/akka-actor/src/main/scala/akka/actor/UntypedActor.scala +++ b/akka-actor/src/main/scala/akka/actor/UntypedActor.scala @@ -19,8 +19,8 @@ import akka.japi.{ Creator, Procedure } * String msg = (String)message; * * if (msg.equals("UseReply")) { - * // Reply to original sender of message using the 'replyUnsafe' method - * getContext().replyUnsafe(msg + ":" + getContext().getUuid()); + * // Reply to original sender of message using the 'reply' method + * getContext().reply(msg + ":" + getContext().getUuid()); * * } else if (msg.equals("UseSender") && getContext().getSender().isDefined()) { * // Reply to original sender of message using the sender reference @@ -102,7 +102,7 @@ abstract class UntypedActor extends Actor { *

* Is called on a crashed Actor right BEFORE it is restarted to allow clean up of resources before Actor is terminated. */ - override def preRestart(reason: Throwable) {} + override def preRestart(reason: Throwable, lastMessage: Option[Any]) {} /** * User overridable callback. diff --git a/akka-actor/src/main/scala/akka/cluster/ClusterInterface.scala b/akka-actor/src/main/scala/akka/cluster/ClusterInterface.scala index 690a69841f..f59d2ff4ab 100644 --- a/akka-actor/src/main/scala/akka/cluster/ClusterInterface.scala +++ b/akka-actor/src/main/scala/akka/cluster/ClusterInterface.scala @@ -10,18 +10,14 @@ import akka.actor._ import DeploymentConfig._ import akka.dispatch.Future import akka.config.Config -import akka.util._ import akka.routing.RouterType import akka.AkkaException import com.eaio.uuid.UUID import java.net.InetSocketAddress -import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger } -import java.util.concurrent.{ ConcurrentSkipListSet, ConcurrentHashMap } +import java.util.concurrent.{ ConcurrentSkipListSet} -import scala.collection.mutable.ConcurrentMap -import scala.collection.JavaConversions._ class ClusterException(message: String) extends AkkaException(message) diff --git a/akka-actor/src/main/scala/akka/config/Config.scala b/akka-actor/src/main/scala/akka/config/Config.scala index 0c1505ae21..40f84a30d2 100644 --- a/akka-actor/src/main/scala/akka/config/Config.scala +++ b/akka-actor/src/main/scala/akka/config/Config.scala @@ -10,8 +10,13 @@ import java.net.InetAddress import com.eaio.uuid.UUID -class ConfigurationException(message: String, cause: Throwable = null) extends AkkaException(message, cause) -class ModuleNotAvailableException(message: String, cause: Throwable = null) extends AkkaException(message, cause) +class ConfigurationException(message: String, cause: Throwable = null) extends AkkaException(message, cause) { + def this(msg:String) = this(msg, null); +} + +class ModuleNotAvailableException(message: String, cause: Throwable = null) extends AkkaException(message, cause) { + def this(msg:String) = this(msg, null); +} /** * Loads up the configuration (from the akka.conf file). diff --git a/akka-actor/src/main/scala/akka/config/Configuration.scala b/akka-actor/src/main/scala/akka/config/Configuration.scala index 8213d853eb..b32c4eeb72 100644 --- a/akka-actor/src/main/scala/akka/config/Configuration.scala +++ b/akka-actor/src/main/scala/akka/config/Configuration.scala @@ -64,7 +64,6 @@ class Configuration(val map: Map[String, Any]) { private def outputIfDesiredAndReturnInput[T](key: String, t: T): T = { if (Configuration.outputConfigSources) println("Akka config is using default value for: " + key) - t } @@ -149,10 +148,11 @@ class Configuration(val map: Map[String, Any]) { getDouble(key).getOrElse(outputIfDesiredAndReturnInput(key, defaultValue)) def getBoolean(key: String): Option[Boolean] = { - getString(key) flatMap { s ⇒ - val isTrue = trueValues.contains(s) - if (!isTrue && !falseValues.contains(s)) None - else Some(isTrue) + getString(key) flatMap { + s ⇒ + val isTrue = trueValues.contains(s) + if (!isTrue && !falseValues.contains(s)) None + else Some(isTrue) } } @@ -165,18 +165,24 @@ class Configuration(val map: Map[String, Any]) { getBoolean(key, defaultValue) def apply(key: String): String = getString(key) match { - case None ⇒ throw new ConfigurationException("undefined config: " + key) + case None ⇒ throw new ConfigurationException("undefined config: " + key) case Some(v) ⇒ v } def apply(key: String, defaultValue: String) = getString(key, defaultValue) + def apply(key: String, defaultValue: Int) = getInt(key, defaultValue) + def apply(key: String, defaultValue: Long) = getLong(key, defaultValue) + def apply(key: String, defaultValue: Boolean) = getBool(key, defaultValue) def getSection(name: String): Option[Configuration] = { val l = name.length + 1 - val m = map.collect { case (k, v) if k.startsWith(name) ⇒ (k.substring(l), v) } + val pattern = name+"." + val m = map.collect { + case (k, v) if k.startsWith(pattern) ⇒ (k.substring(l), v) + } if (m.isEmpty) None else Some(new Configuration(m)) } diff --git a/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala b/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala index 9af29eed98..64c0c5afb2 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala @@ -197,14 +197,15 @@ object Dispatchers { case "GlobalDispatcher" ⇒ GlobalDispatcherConfigurator case fqn ⇒ ReflectiveAccess.getClassFor[MessageDispatcherConfigurator](fqn) match { - case r: Right[_, Class[MessageDispatcherConfigurator]] ⇒ - ReflectiveAccess.createInstance[MessageDispatcherConfigurator](r.b, Array[Class[_]](), Array[AnyRef]()) match { - case r: Right[Exception, MessageDispatcherConfigurator] ⇒ r.b - case l: Left[Exception, MessageDispatcherConfigurator] ⇒ - throw new IllegalArgumentException("Cannot instantiate MessageDispatcherConfigurator type [%s], make sure it has a default no-args constructor" format fqn, l.a) + case Right(clazz) ⇒ + ReflectiveAccess.createInstance[MessageDispatcherConfigurator](clazz, Array[Class[_]](), Array[AnyRef]()) match { + case Right(configurator) ⇒ configurator + case Left(exception)⇒ + throw new IllegalArgumentException( + "Cannot instantiate MessageDispatcherConfigurator type [%s], make sure it has a default no-args constructor" format fqn, exception) } - case l: Left[Exception, _] ⇒ - throw new IllegalArgumentException("Unknown MessageDispatcherConfigurator type [%s]" format fqn, l.a) + case Left(exception) ⇒ + throw new IllegalArgumentException("Unknown MessageDispatcherConfigurator type [%s]" format fqn, exception) } } map { _ configure cfg diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index b4a54b5e5b..8cc6d2fb89 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -103,8 +103,8 @@ object Futures { val aggregate: Future[T] ⇒ Unit = f ⇒ if (done.isOff && !result.isCompleted) { //TODO: This is an optimization, is it premature? f.value.get match { - case r: Right[Throwable, T] ⇒ - val added = results add r.b + case Right(value) ⇒ + val added = results add value if (added && results.size == allDone) { //Only one thread can get here if (done.switchOn) { try { @@ -122,9 +122,9 @@ object Futures { } } } - case l: Left[Throwable, T] ⇒ + case Left(exception) ⇒ if (done.switchOn) { - result completeWithException l.a + result completeWithException exception results.clear } } @@ -165,10 +165,8 @@ object Futures { val seedFold: Future[T] ⇒ Unit = f ⇒ { if (seedFound.compareAndSet(false, true)) { //Only the first completed should trigger the fold f.value.get match { - case r: Right[Throwable, T] ⇒ - result.completeWith(fold(r.b, timeout)(futures.filterNot(_ eq f))(op)) - case l: Left[Throwable, T] ⇒ - result.completeWithException(l.a) + case Right(value) ⇒ result.completeWith(fold(value, timeout)(futures.filterNot(_ eq f))(op)) + case Left(exception) ⇒ result.completeWithException(exception) } } } diff --git a/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala b/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala index d3be422396..1477e986d8 100644 --- a/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala +++ b/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala @@ -68,6 +68,11 @@ trait MessageDispatcher { */ private[akka] def createMailbox(actorRef: ActorRef): AnyRef + /** + * Name of this dispatcher. + */ + def name: String + /** * Attaches the specified actorRef to this dispatcher */ diff --git a/akka-actor/src/main/scala/akka/event/EventHandler.scala b/akka-actor/src/main/scala/akka/event/EventHandler.scala index 134543e284..4b1fc2e1aa 100644 --- a/akka-actor/src/main/scala/akka/event/EventHandler.scala +++ b/akka-actor/src/main/scala/akka/event/EventHandler.scala @@ -8,7 +8,7 @@ import akka.actor._ import akka.dispatch.Dispatchers import akka.config.Config._ import akka.config.ConfigurationException -import akka.util.{ ListenerManagement, ReflectiveAccess } +import akka.util.{ListenerManagement, ReflectiveAccess} import akka.serialization._ import akka.AkkaException @@ -26,7 +26,7 @@ import akka.AkkaException * case EventHandler.Info(instance, message) ⇒ ... * case EventHandler.Debug(instance, message) ⇒ ... * case genericEvent ⇒ ... - * } + * } * }) * * EventHandler.addListener(eventHandlerListener) @@ -95,10 +95,10 @@ object EventHandler extends ListenerManagement { @volatile var level: Int = config.getString("akka.event-handler-level", "INFO") match { - case "ERROR" ⇒ ErrorLevel + case "ERROR" ⇒ ErrorLevel case "WARNING" ⇒ WarningLevel - case "INFO" ⇒ InfoLevel - case "DEBUG" ⇒ DebugLevel + case "INFO" ⇒ InfoLevel + case "DEBUG" ⇒ DebugLevel case unknown ⇒ throw new ConfigurationException( "Configuration option 'akka.event-handler-level' is invalid [" + unknown + "]") } @@ -106,21 +106,22 @@ object EventHandler extends ListenerManagement { def start() { try { val defaultListeners = config.getList("akka.event-handlers") match { - case Nil ⇒ "akka.event.EventHandler$DefaultListener" :: Nil + case Nil ⇒ "akka.event.EventHandler$DefaultListener" :: Nil case listeners ⇒ listeners } - defaultListeners foreach { listenerName ⇒ - try { - ReflectiveAccess.getClassFor[Actor](listenerName) match { - case r: Right[_, Class[Actor]] ⇒ addListener(Actor.localActorOf(r.b).start()) - case l: Left[Exception, _] ⇒ throw l.a + defaultListeners foreach { + listenerName ⇒ + try { + ReflectiveAccess.getClassFor[Actor](listenerName) match { + case Right(actorClass) ⇒ addListener(Actor.localActorOf(actorClass).start()) + case Left(exception) ⇒ throw exception + } + } catch { + case e: Exception ⇒ + throw new ConfigurationException( + "Event Handler specified in config can't be loaded [" + listenerName + + "] due to [" + e.toString + "]", e) } - } catch { - case e: Exception ⇒ - throw new ConfigurationException( - "Event Handler specified in config can't be loaded [" + listenerName + - "] due to [" + e.toString + "]", e) - } } info(this, "Starting up EventHandler") } catch { @@ -145,7 +146,7 @@ object EventHandler extends ListenerManagement { notifyListeners(event) } - def notify[T <: Event: ClassManifest](event: ⇒ T) { + def notify[T <: Event : ClassManifest](event: ⇒ T) { if (level >= levelFor(classManifest[T].erasure.asInstanceOf[Class[_ <: Event]])) notifyListeners(event) } @@ -181,6 +182,7 @@ object EventHandler extends ListenerManagement { if (level >= InfoLevel) notifyListeners(Info(instance, message)) } + def debug(instance: AnyRef, message: ⇒ String) { if (level >= DebugLevel) notifyListeners(Debug(instance, message)) } @@ -194,7 +196,7 @@ object EventHandler extends ListenerManagement { def isDebugEnabled = level >= DebugLevel def stackTraceFor(e: Throwable) = { - import java.io.{ StringWriter, PrintWriter } + import java.io.{StringWriter, PrintWriter} val sw = new StringWriter val pw = new PrintWriter(sw) e.printStackTrace(pw) @@ -210,6 +212,7 @@ object EventHandler extends ListenerManagement { } class DefaultListener extends Actor { + import java.text.SimpleDateFormat import java.util.Date diff --git a/akka-actor/src/main/scala/akka/experimental.scala b/akka-actor/src/main/scala/akka/experimental.scala new file mode 100644 index 0000000000..cfc976551a --- /dev/null +++ b/akka-actor/src/main/scala/akka/experimental.scala @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2009-2011 Scalable Solutions AB + */ + +package akka + +import annotation.target._ + +/** + * This annotation marks a feature which is not yet considered stable and may + * change or be removed in a future release. + * + * @author Roland Kuhn + * @since 1.2 + */ +@getter +@setter +@beanGetter +@beanSetter +final class experimental(since: String) extends annotation.StaticAnnotation diff --git a/akka-actor/src/main/scala/akka/routing/Pool.scala b/akka-actor/src/main/scala/akka/routing/Pool.scala index 4196a16233..6bfd4c18b3 100644 --- a/akka-actor/src/main/scala/akka/routing/Pool.scala +++ b/akka-actor/src/main/scala/akka/routing/Pool.scala @@ -4,8 +4,9 @@ package akka.routing -import akka.actor.{ Actor, ActorRef, PoisonPill } +import akka.actor.{ Actor, ActorRef, PoisonPill, Death, MaximumNumberOfRestartsWithinTimeRangeReached } import akka.dispatch.{ Promise } +import akka.config.Supervision._ /** * Actor pooling @@ -35,44 +36,102 @@ object ActorPool { * Defines the nature of an actor pool. */ trait ActorPool { - def instance(): ActorRef //Question, Instance of what? - def capacity(delegates: Seq[ActorRef]): Int //Question, What is the semantics of this return value? - def select(delegates: Seq[ActorRef]): Tuple2[Iterator[ActorRef], Int] //Question, Why does select return this instead of an ordered Set? + /** + * Adds a new actor to the pool. The DefaultActorPool implementation will start and link (supervise) this actor. + * This method is invoked whenever the pool determines it must boost capacity. + * @return A new actor for the pool + */ + def instance(): ActorRef + /** + * Returns the overall desired change in pool capacity. This method is used by non-static pools as the means + * for the capacity strategy to influence the pool. + * @param _delegates The current sequence of pooled actors + * @return the number of delegates by which the pool should be adjusted (positive, negative or zero) + */ + def capacity(delegates: Seq[ActorRef]): Int + /** + * Provides the results of the selector, one or more actors, to which an incoming message is forwarded. + * This method returns an iterator since a selector might return more than one actor to handle the message. + * You might want to do this to perform redundant processing of particularly error-prone messages. + * @param _delegates The current sequence of pooled actors + * @return a list of actors to which the message will be delivered + */ + def select(delegates: Seq[ActorRef]): Seq[ActorRef] } /** - * A default implementation of a pool, on each message to route, - * - checks the current capacity and adjusts accordingly if needed - * - routes the incoming message to a selection set of delegate actors + * Defines the configuration options for how the pool supervises the actors. */ -trait DefaultActorPool extends ActorPool { this: Actor ⇒ +trait ActorPoolSupervisionConfig { + /** + * Defines the default fault handling strategy to be employed by the pool. + */ + def poolFaultHandler: FaultHandlingStrategy +} + +/** + * Provides a default implementation of the supervision configuration by + * defining a One-for-One fault handling strategy, trapping exceptions, + * limited to 5 retries within 1 second. + * + * This is just a basic strategy and implementors are encouraged to define + * something more appropriate for their needs. + */ +trait DefaultActorPoolSupervisionConfig extends ActorPoolSupervisionConfig { + def poolFaultHandler = OneForOneStrategy(List(classOf[Exception]), 5, 1000) +} + +/** + * A default implementation of a pool that: + * First, invokes the pool's capacitor that tells it, based on the current delegate count + * and it's own heuristic by how many delegates the pool should be resized. Resizing can + * can be incremental, decremental or flat. If there is a change to capacity, new delegates + * are added or existing ones are removed. Removed actors are sent the PoisonPill message. + * New actors are automatically started and linked. The pool supervises the actors and will + * use the fault handling strategy specified by the mixed-in ActorPoolSupervisionConfig. + * Pooled actors may be any lifecycle. If you're testing pool sizes during runtime, take a + * look at the unit tests... Any delegate with a Permanent lifecycle will be + * restarted and the pool size will be level with what it was prior to the fault. In just + * about every other case, e.g. the delegates are Temporary or the delegate cannot be + * restarted within the time interval specified in the fault handling strategy, the pool will + * be temporarily shy by that actor (it will have been removed by not back-filled). The + * back-fill if any is required, will occur on the next message [as usual]. + * + * Second, invokes the pool's selector that returns a list of delegates that are to receive + * the incoming message. Selectors may return more than one actor. If partialFill + * is true then it might also the case that fewer than number of desired actors will be + * returned. + * + * Lastly, routes by forwarding, the incoming message to each delegate in the selected set. + */ +trait DefaultActorPool extends ActorPool { this: Actor with ActorPoolSupervisionConfig ⇒ import ActorPool._ - import akka.actor.MaximumNumberOfRestartsWithinTimeRangeReached - protected var _delegates = Vector[ActorRef]() - private var _lastCapacityChange = 0 - private var _lastSelectorCount = 0 + protected[akka] var _delegates = Vector[ActorRef]() - override def postStop() = _delegates foreach { delegate ⇒ - try { - delegate ! PoisonPill - } catch { case e: Exception ⇒ } //Ignore any exceptions here + override def preStart() { + self.faultHandler = poolFaultHandler + } + override def postStop() { + _delegates foreach { delegate ⇒ + try { + delegate ! PoisonPill + } catch { case e: Exception ⇒ } //Ignore any exceptions here + } } protected def _route(): Receive = { // for testing... case Stat ⇒ - self reply_? Stats(_delegates length) - case max: MaximumNumberOfRestartsWithinTimeRangeReached ⇒ - _delegates = _delegates filterNot { _.uuid == max.victim.uuid } + self tryReply Stats(_delegates length) + case MaximumNumberOfRestartsWithinTimeRangeReached(victim, _, _, _) ⇒ + _delegates = _delegates filterNot { _.uuid == victim.uuid } + case Death(victim, _) => + _delegates = _delegates filterNot { _.uuid == victim.uuid } case msg ⇒ resizeIfAppropriate() - select(_delegates) match { - case (selectedDelegates, count) ⇒ - _lastSelectorCount = count - selectedDelegates foreach { _ forward msg } //Should we really send the same message to several actors? - } + select(_delegates) foreach { _ forward msg } } private def resizeIfAppropriate() { @@ -95,14 +154,15 @@ trait DefaultActorPool extends ActorPool { this: Actor ⇒ case _ ⇒ _delegates //No change } - _lastCapacityChange = requestedCapacity _delegates = newDelegates } } /** * Selectors - * These traits define how, when a message needs to be routed, delegate(s) are chosen from the pool + * + * These traits define how, when a message needs to be routed, delegate(s) are chosen from the pool. + * Note that it's acceptable to return more than one actor to handle a given message. */ /** @@ -112,7 +172,7 @@ trait SmallestMailboxSelector { def selectionCount: Int def partialFill: Boolean - def select(delegates: Seq[ActorRef]): Tuple2[Iterator[ActorRef], Int] = { + def select(delegates: Seq[ActorRef]): Seq[ActorRef] = { var set: Seq[ActorRef] = Nil var take = if (partialFill) math.min(selectionCount, delegates.length) else selectionCount @@ -121,7 +181,7 @@ trait SmallestMailboxSelector { take -= set.size } - (set.iterator, set.size) + set } } @@ -134,7 +194,7 @@ trait RoundRobinSelector { def selectionCount: Int def partialFill: Boolean - def select(delegates: Seq[ActorRef]): Tuple2[Iterator[ActorRef], Int] = { + def select(delegates: Seq[ActorRef]): Seq[ActorRef] = { val length = delegates.length val take = if (partialFill) math.min(selectionCount, length) else selectionCount @@ -145,13 +205,16 @@ trait RoundRobinSelector { delegates(_last) } - (set.iterator, set.size) + set } } /** * Capacitors - * These traits define how to alter the size of the pool + * + * These traits define how to alter the size of the pool according to some desired behavior. + * Capacitors are required (minimally) by the pool to establish bounds on the number of delegates + * that may exist in the pool. */ /** @@ -163,7 +226,13 @@ trait FixedSizeCapacitor { } /** - * Constrains the pool capacity to a bounded range + * Constrains the pool capacity to a bounded range. + * This capacitor employs 'pressure capacitors' (sorry for the unforunate confusing naming) + * to feed a 'pressure' delta into the capacity function. This measure is + * basically the difference between the current pressure level and a pre-established threshhold. + * When using this capacitor you must provide a method called 'pressure' or mix-in + * one of the PressureCapacitor traits below. + * */ trait BoundedCapacitor { def lowerBound: Int @@ -196,21 +265,43 @@ trait MailboxPressureCapacitor { */ trait ActiveFuturesPressureCapacitor { def pressure(delegates: Seq[ActorRef]): Int = - delegates count { _.channel.isInstanceOf[Promise[Any]] } + delegates count { _.channel.isInstanceOf[Promise[_]] } } /** + * */ trait CapacityStrategy { import ActorPool._ + /** + * This method returns a 'pressure level' that will be fed into the capacitor and + * evaluated against the established threshhold. For instance, in general, if + * the current pressure level exceeds the capacity of the pool, new delegates will + * be added. + */ def pressure(delegates: Seq[ActorRef]): Int + /** + * This method can be used to smooth the response of the capacitor by considering + * the current pressure and current capacity. + */ def filter(pressure: Int, capacity: Int): Int protected def _eval(delegates: Seq[ActorRef]): Int = filter(pressure(delegates), delegates.size) } +/** + * Use this trait to setup a pool that uses a fixed delegate count. + */ trait FixedCapacityStrategy extends FixedSizeCapacitor + +/** + * Use this trait to setup a pool that may have a variable number of + * delegates but always within an established upper and lower limit. + * + * If mix this into your pool implementation, you must also provide a + * PressureCapacitor and a Filter. + */ trait BoundedCapacityStrategy extends CapacityStrategy with BoundedCapacitor /** diff --git a/akka-actor/src/main/scala/akka/serialization/Format.scala b/akka-actor/src/main/scala/akka/serialization/Format.scala index 85dc0abec0..d53a852379 100644 --- a/akka-actor/src/main/scala/akka/serialization/Format.scala +++ b/akka-actor/src/main/scala/akka/serialization/Format.scala @@ -43,6 +43,9 @@ import java.io.{ ObjectOutputStream, ByteArrayOutputStream, ObjectInputStream, B * in.close() * obj * } + * + * def identifier: Byte = 111 //Pick a number and hope no one has chosen the same :-) 0 - 16 is reserved for Akka internals + * * } * * val defaultSerializerName = Default.getClass.getName diff --git a/akka-actor/src/main/scala/akka/serialization/Serialization.scala b/akka-actor/src/main/scala/akka/serialization/Serialization.scala index 49dd527be6..e065bbc24c 100644 --- a/akka-actor/src/main/scala/akka/serialization/Serialization.scala +++ b/akka-actor/src/main/scala/akka/serialization/Serialization.scala @@ -19,27 +19,26 @@ case class NoSerializerFoundException(m: String) extends AkkaException(m) * locating a Serializer for a particular class as defined in the mapping in the 'akka.conf' file. */ object Serialization { + //TODO document me - def serialize(o: AnyRef): Either[Exception, Array[Byte]] = serializerFor(o.getClass) match { - case Left(ex) ⇒ Left(ex) - case Right(serializer) ⇒ Right(serializer.toBinary(o)) - } + def serialize(o: AnyRef): Either[Exception, Array[Byte]] = + try { Right(findSerializerFor(o).toBinary(o)) } catch { case e: Exception => Left(e) } + //TODO document me def deserialize( bytes: Array[Byte], clazz: Class[_], classLoader: Option[ClassLoader]): Either[Exception, AnyRef] = - serializerFor(clazz) match { - case Left(e) ⇒ Left(e) - case Right(serializer) ⇒ Right(serializer.fromBinary(bytes, Some(clazz), classLoader)) - } + try { Right(serializerFor(clazz).fromBinary(bytes, Some(clazz), classLoader)) } catch { case e: Exception => Left(e) } + + def findSerializerFor(o: AnyRef): Serializer = o match { + case null => NullSerializer + case other => serializerFor(other.getClass) + } + //TODO document me - //TODO memoize the lookups - def serializerFor(clazz: Class[_]): Either[Exception, Serializer] = //TODO fall back on BestMatchClass THEN default - getClassFor(serializerMap.get(clazz.getName).getOrElse(serializers("default"))) match { - case Right(serializer) ⇒ Right(serializer.newInstance.asInstanceOf[Serializer]) - case Left(e) => Left(e) - } + def serializerFor(clazz: Class[_]): Serializer = //TODO fall back on BestMatchClass THEN default AND memoize the lookups + serializerMap.get(clazz.getName).getOrElse(serializers("default")) /** * Tries to load the specified Serializer by the FQN @@ -64,16 +63,18 @@ object Serialization { } /** - * A Map of serializer from alias to implementation (FQN of a class implementing akka.serialization.Serializer) - * By default always contains the following mapping: "default" -> "akka.serialization.JavaSerializer" + * A Map of serializer from alias to implementation (class implementing akka.serialization.Serializer) + * By default always contains the following mapping: "default" -> akka.serialization.JavaSerializer * But "default" can be overridden in config */ - val serializers: Map[String, String] = config.getSection("akka.actor.serializers") map { - _.map.foldLeft(Map("default" -> "akka.serialization.JavaSerializer")) { - case (result, (k: String, v: String)) => result + (k -> v) - case (result, _) => result - } - } getOrElse Map("default" -> "akka.serialization.JavaSerializer") + val serializers: Map[String, Serializer] = + config.getSection("akka.actor.serializers") + .map(_.map) + .getOrElse(Map()) + .foldLeft(Map[String, Serializer]("default" -> akka.serialization.JavaSerializer)) { + case (result, (k: String, v: String)) => result + (k -> serializerOf(v).fold(throw _, identity)) + case (result, _) => result + } /** * bindings is a Map whose keys = FQN of class that is serializable and values = the alias of the serializer to be used @@ -88,5 +89,11 @@ object Serialization { /** * serializerMap is a Map whose keys = FQN of class that is serializable and values = the FQN of the serializer to be used for that class */ - val serializerMap: Map[String, String] = bindings mapValues serializers + val serializerMap: Map[String, Serializer] = bindings mapValues serializers + + /** + * Maps from a Serializer.Identifier (Byte) to a Serializer instance (optimization) + */ + val serializerByIdentity: Map[Serializer.Identifier, Serializer] = + Map(NullSerializer.identifier -> NullSerializer) ++ serializers map { case (_, v) => (v.identifier,v) } } diff --git a/akka-actor/src/main/scala/akka/serialization/Serializer.scala b/akka-actor/src/main/scala/akka/serialization/Serializer.scala index e79b2bcd37..aa57c4b47d 100644 --- a/akka-actor/src/main/scala/akka/serialization/Serializer.scala +++ b/akka-actor/src/main/scala/akka/serialization/Serializer.scala @@ -6,14 +6,40 @@ package akka.serialization import java.io.{ ObjectOutputStream, ByteArrayOutputStream, ObjectInputStream, ByteArrayInputStream } import akka.util.ClassLoaderObjectInputStream -import akka.actor.ActorRef +object Serializer { + val defaultSerializerName = classOf[JavaSerializer].getName + type Identifier = Byte +} + +/** + * A Serializer represents a bimap between an object and an array of bytes representing that object + */ trait Serializer extends scala.Serializable { + /** + * Completely unique Byte value to identify this implementation of Serializer, used to optimize network traffic + * Values from 0 to 16 is reserved for Akka internal usage + */ + def identifier: Serializer.Identifier + + /** + * Serializes the given object into an Array of Byte + */ def toBinary(o: AnyRef): Array[Byte] + + /** + * Produces an object from an array of bytes, with an optional type-hint and a classloader to load the class into + */ def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]] = None, classLoader: Option[ClassLoader] = None): AnyRef } +object JavaSerializer extends JavaSerializer +object NullSerializer extends NullSerializer + class JavaSerializer extends Serializer { + + def identifier = 1:Byte + def toBinary(o: AnyRef): Array[Byte] = { val bos = new ByteArrayOutputStream val out = new ObjectOutputStream(bos) @@ -33,7 +59,11 @@ class JavaSerializer extends Serializer { } } -object JavaSerializer extends JavaSerializer -object Serializer { - val defaultSerializerName = JavaSerializer.getClass.getName +class NullSerializer extends Serializer { + + val nullAsBytes = Array[Byte]() + + def identifier = 0:Byte + def toBinary(o: AnyRef) = nullAsBytes + def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]] = None, classLoader: Option[ClassLoader] = None): AnyRef = null } diff --git a/akka-camel/src/main/scala/akka/camel/Producer.scala b/akka-camel/src/main/scala/akka/camel/Producer.scala index 3281feff83..b868498cba 100644 --- a/akka-camel/src/main/scala/akka/camel/Producer.scala +++ b/akka-camel/src/main/scala/akka/camel/Producer.scala @@ -58,7 +58,7 @@ trait ProducerSupport { this: Actor ⇒ * Default implementation of Actor.preRestart for freeing resources needed * to actually send messages to endpointUri. */ - override def preRestart(reason: Throwable) { + override def preRestart(reason: Throwable, msg: Option[Any]) { try { preRestartProducer(reason) } finally { processor.stop } } diff --git a/akka-camel/src/test/java/akka/camel/SampleUntypedConsumer.java b/akka-camel/src/test/java/akka/camel/SampleUntypedConsumer.java index 99300836c1..3576e472bd 100644 --- a/akka-camel/src/test/java/akka/camel/SampleUntypedConsumer.java +++ b/akka-camel/src/test/java/akka/camel/SampleUntypedConsumer.java @@ -15,7 +15,7 @@ public class SampleUntypedConsumer extends UntypedConsumerActor { Message msg = (Message)message; String body = msg.getBodyAs(String.class); String header = msg.getHeaderAs("test", String.class); - getContext().replySafe(String.format("%s %s", body, header)); + getContext().tryReply(String.format("%s %s", body, header)); } } diff --git a/akka-camel/src/test/java/akka/camel/SampleUntypedConsumerBlocking.java b/akka-camel/src/test/java/akka/camel/SampleUntypedConsumerBlocking.java index b5b22a04ae..03f7f0648f 100644 --- a/akka-camel/src/test/java/akka/camel/SampleUntypedConsumerBlocking.java +++ b/akka-camel/src/test/java/akka/camel/SampleUntypedConsumerBlocking.java @@ -17,7 +17,7 @@ public class SampleUntypedConsumerBlocking extends UntypedConsumerActor { Message msg = (Message)message; String body = msg.getBodyAs(String.class); String header = msg.getHeaderAs("test", String.class); - getContext().replySafe(String.format("%s %s", body, header)); + getContext().tryReply(String.format("%s %s", body, header)); } } diff --git a/akka-camel/src/test/scala/akka/camel/ConsumerScalaTest.scala b/akka-camel/src/test/scala/akka/camel/ConsumerScalaTest.scala index 61909b0db0..52a1e91246 100644 --- a/akka-camel/src/test/scala/akka/camel/ConsumerScalaTest.scala +++ b/akka-camel/src/test/scala/akka/camel/ConsumerScalaTest.scala @@ -251,12 +251,12 @@ object ConsumerScalaTest { case "succeed" ⇒ self.reply("ok") } - override def preRestart(reason: scala.Throwable) { - self.reply_?("pr") + override def preRestart(reason: scala.Throwable, msg: Option[Any]) { + self.tryReply("pr") } override def postStop { - self.reply_?("ps") + self.tryReply("ps") } } diff --git a/akka-cluster/src/main/scala/akka/cluster/Cluster.scala b/akka-cluster/src/main/scala/akka/cluster/Cluster.scala index 9a26ad985b..39c57c0778 100644 --- a/akka-cluster/src/main/scala/akka/cluster/Cluster.scala +++ b/akka-cluster/src/main/scala/akka/cluster/Cluster.scala @@ -703,8 +703,6 @@ class DefaultClusterNode private[akka]( serializeMailbox: Boolean, serializer: Serializer): ClusterNode = if (isConnected.isOn) { - val serializerClassName = serializer.getClass.getName - EventHandler.debug(this, "Storing actor with address [%s] in cluster".format(actorAddress)) @@ -739,9 +737,9 @@ class DefaultClusterNode private[akka]( // create ADDRESS -> SERIALIZER CLASS NAME mapping try { - zkClient.createPersistent(actorAddressRegistrySerializerPathFor(actorAddress), serializerClassName) + zkClient.createPersistent(actorAddressRegistrySerializerPathFor(actorAddress), serializer.identifier.toString) } catch { - case e: ZkNodeExistsException ⇒ zkClient.writeData(actorAddressRegistrySerializerPathFor(actorAddress), serializerClassName) + case e: ZkNodeExistsException ⇒ zkClient.writeData(actorAddressRegistrySerializerPathFor(actorAddress), serializer.identifier.toString) } // create ADDRESS -> NODE mapping @@ -1084,21 +1082,10 @@ class DefaultClusterNode private[akka]( /** * Returns Serializer for actor with specific address. */ - def serializerForActor(actorAddress: String): Serializer = { - val serializerClassName = - try { - zkClient.readData(actorAddressRegistrySerializerPathFor(actorAddress), new Stat).asInstanceOf[String] - } catch { - case e: ZkNoNodeException ⇒ throw new IllegalStateException("No serializer found for actor with address [%s]".format(actorAddress)) - } - - ReflectiveAccess.getClassFor(serializerClassName) match { - // FIXME need to pass in a user provide class loader? Now using default in ReflectiveAccess. - case Right(clazz) ⇒ clazz.newInstance.asInstanceOf[Serializer] - case Left(error) ⇒ - EventHandler.error(error, this, "Could not load serializer class [%s] due to: %s".format(serializerClassName, error.toString)) - throw error - } + def serializerForActor(actorAddress: String): Serializer = try { + Serialization.serializerByIdentity(zkClient.readData(actorAddressRegistrySerializerPathFor(actorAddress), new Stat).asInstanceOf[String].toByte) + } catch { + case e: ZkNoNodeException ⇒ throw new IllegalStateException("No serializer found for actor with address [%s]".format(actorAddress)) } /** @@ -1790,7 +1777,7 @@ class RemoteClusterDaemon(cluster: ClusterNode) extends Actor { self.dispatcher = Dispatchers.newPinnedDispatcher(self) - override def preRestart(reason: Throwable) { + override def preRestart(reason: Throwable, msg: Option[Any]) { EventHandler.debug(this, "RemoteClusterDaemon failed due to [%s] restarting...".format(reason)) } @@ -1930,7 +1917,7 @@ class RemoteClusterDaemon(cluster: ClusterNode) extends Actor { self.dispatcher = computeGridDispatcher def receive = { - case f: Function0[Unit] ⇒ try { + case f: Function0[_] ⇒ try { f() } finally { self.stop() @@ -1943,7 +1930,7 @@ class RemoteClusterDaemon(cluster: ClusterNode) extends Actor { self.dispatcher = computeGridDispatcher def receive = { - case f: Function0[Any] ⇒ try { + case f: Function0[_] ⇒ try { self.reply(f()) } finally { self.stop() @@ -1956,8 +1943,8 @@ class RemoteClusterDaemon(cluster: ClusterNode) extends Actor { self.dispatcher = computeGridDispatcher def receive = { - case (fun: Function[Any, Unit], param: Any) ⇒ try { - fun(param) + case (fun: Function[_, _], param: Any) ⇒ try { + fun.asInstanceOf[Any => Unit].apply(param) } finally { self.stop() } @@ -1969,8 +1956,8 @@ class RemoteClusterDaemon(cluster: ClusterNode) extends Actor { self.dispatcher = computeGridDispatcher def receive = { - case (fun: Function[Any, Unit], param: Any) ⇒ try { - self.reply(fun(param)) + case (fun: Function[_, _], param: Any) ⇒ try { + self.reply(fun.asInstanceOf[Any => Any](param)) } finally { self.stop() } diff --git a/akka-cluster/src/main/scala/akka/cluster/TransactionLog.scala b/akka-cluster/src/main/scala/akka/cluster/TransactionLog.scala index ae98e6848e..2261829934 100644 --- a/akka-cluster/src/main/scala/akka/cluster/TransactionLog.scala +++ b/akka-cluster/src/main/scala/akka/cluster/TransactionLog.scala @@ -14,17 +14,16 @@ import akka.config._ import Config._ import akka.util._ import akka.actor._ -import DeploymentConfig.{ ReplicationScheme, ReplicationStrategy, Transient, WriteThrough, WriteBehind } +import DeploymentConfig.{ ReplicationScheme} import akka.event.EventHandler import akka.dispatch.{ DefaultPromise, Promise, MessageInvocation } import akka.remote.MessageSerializer import akka.cluster.zookeeper._ -import akka.serialization.{ Serializer, Serialization, Compression } +import akka.serialization.Compression import Compression.LZF import akka.serialization.ActorSerialization._ import java.util.Enumeration -import java.util.concurrent.atomic.AtomicLong // FIXME allow user to choose dynamically between 'async' and 'sync' tx logging (asyncAddEntry(byte[] data, AddCallback cb, Object ctx)) // FIXME clean up old entries in log after doing a snapshot diff --git a/akka-cluster/src/main/scala/akka/cluster/storage/Storage.scala b/akka-cluster/src/main/scala/akka/cluster/storage/Storage.scala index ef7b1a1aa4..0129c39eb0 100755 --- a/akka-cluster/src/main/scala/akka/cluster/storage/Storage.scala +++ b/akka-cluster/src/main/scala/akka/cluster/storage/Storage.scala @@ -118,23 +118,31 @@ class VersionedData(val data: Array[Byte], val version: Long) {} /** * An AkkaException thrown by the Storage module. */ -class StorageException(msg: String = null, cause: java.lang.Throwable = null) extends AkkaException(msg, cause) +class StorageException(msg: String = null, cause: java.lang.Throwable = null) extends AkkaException(msg, cause){ + def this(msg:String) = this(msg, null); +} /** * * * A StorageException thrown when an operation is done on a non existing node. */ -class MissingDataException(msg: String = null, cause: java.lang.Throwable = null) extends StorageException(msg, cause) +class MissingDataException(msg: String = null, cause: java.lang.Throwable = null) extends StorageException(msg, cause) { + def this(msg:String) = this(msg, null); +} /** * A StorageException thrown when an operation is done on an existing node, but no node was expected. */ -class DataExistsException(msg: String = null, cause: java.lang.Throwable = null) extends StorageException(msg, cause) +class DataExistsException(msg: String = null, cause: java.lang.Throwable = null) extends StorageException(msg, cause){ + def this(msg:String) = this(msg, null); +} /** * A StorageException thrown when an operation causes an optimistic locking failure. */ -class BadVersionException(msg: String = null, cause: java.lang.Throwable = null) extends StorageException(msg, cause) +class BadVersionException(msg: String = null, cause: java.lang.Throwable = null) extends StorageException(msg, cause) { + def this(msg:String) = this(msg, null); +} /** * A Storage implementation based on ZooKeeper. diff --git a/akka-cluster/src/main/scala/akka/remote/BootableRemoteActorService.scala b/akka-cluster/src/main/scala/akka/remote/BootableRemoteActorService.scala index 95492a30f5..f214e12f52 100644 --- a/akka-cluster/src/main/scala/akka/remote/BootableRemoteActorService.scala +++ b/akka-cluster/src/main/scala/akka/remote/BootableRemoteActorService.scala @@ -6,6 +6,7 @@ package akka.remote import akka.actor.{ Actor, BootableActorLoaderService } import akka.util.{ ReflectiveAccess, Bootable } +import akka.event.EventHandler /** * This bundle/service is responsible for booting up and shutting down the remote actors facility. @@ -23,14 +24,19 @@ trait BootableRemoteActorService extends Bootable { abstract override def onLoad() { if (ReflectiveAccess.ClusterModule.isEnabled && RemoteServerSettings.isRemotingEnabled) { + EventHandler.info(this, "Initializing Remote Actors Service...") startRemoteService() + EventHandler.info(this, "Remote Actors Service initialized") } super.onLoad() } abstract override def onUnload() { + EventHandler.info(this, "Shutting down Remote Actors Service") + Actor.remote.shutdown() if (remoteServerThread.isAlive) remoteServerThread.join(1000) + EventHandler.info(this, "Remote Actors Service has been shut down") super.onUnload() } } diff --git a/akka-cluster/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-cluster/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index e79e426d94..b676e9f254 100644 --- a/akka-cluster/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-cluster/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -4,24 +4,22 @@ package akka.remote.netty -import akka.dispatch.{ ActorPromise, DefaultPromise, Promise, Future } -import akka.remote.{ MessageSerializer, RemoteClientSettings, RemoteServerSettings } +import akka.dispatch.{ActorPromise, DefaultPromise, Promise} +import akka.remote.{MessageSerializer, RemoteClientSettings, RemoteServerSettings} import akka.remote.protocol.RemoteProtocol._ import akka.serialization.RemoteActorSerialization import akka.serialization.RemoteActorSerialization._ import akka.remoteinterface._ import akka.actor.{ - PoisonPill, - LocalActorRef, - Actor, - RemoteActorRef, - ActorRef, - IllegalActorStateException, - RemoteActorSystemMessage, - uuidFrom, - Uuid, - Death, - LifeCycleMessage +PoisonPill, +Actor, +RemoteActorRef, +ActorRef, +IllegalActorStateException, +RemoteActorSystemMessage, +uuidFrom, +Uuid, +LifeCycleMessage } import akka.actor.Actor._ import akka.config.Config @@ -30,27 +28,28 @@ import akka.util._ import akka.event.EventHandler import org.jboss.netty.channel._ -import org.jboss.netty.channel.group.{ DefaultChannelGroup, ChannelGroup, ChannelGroupFuture } +import org.jboss.netty.channel.group.{DefaultChannelGroup, ChannelGroup, ChannelGroupFuture} import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory -import org.jboss.netty.bootstrap.{ ServerBootstrap, ClientBootstrap } -import org.jboss.netty.handler.codec.frame.{ LengthFieldBasedFrameDecoder, LengthFieldPrepender } -import org.jboss.netty.handler.codec.compression.{ ZlibDecoder, ZlibEncoder } -import org.jboss.netty.handler.codec.protobuf.{ ProtobufDecoder, ProtobufEncoder } -import org.jboss.netty.handler.timeout.{ ReadTimeoutHandler, ReadTimeoutException } -import org.jboss.netty.handler.execution.{ OrderedMemoryAwareThreadPoolExecutor, ExecutionHandler } -import org.jboss.netty.util.{ TimerTask, Timeout, HashedWheelTimer } +import org.jboss.netty.bootstrap.{ServerBootstrap, ClientBootstrap} +import org.jboss.netty.handler.codec.frame.{LengthFieldBasedFrameDecoder, LengthFieldPrepender} +import org.jboss.netty.handler.codec.compression.{ZlibDecoder, ZlibEncoder} +import org.jboss.netty.handler.codec.protobuf.{ProtobufDecoder, ProtobufEncoder} +import org.jboss.netty.handler.timeout.{ReadTimeoutHandler, ReadTimeoutException} +import org.jboss.netty.handler.execution.{OrderedMemoryAwareThreadPoolExecutor, ExecutionHandler} +import org.jboss.netty.util.{TimerTask, Timeout, HashedWheelTimer} import scala.collection.mutable.HashMap import scala.collection.JavaConversions._ import java.net.InetSocketAddress -import java.lang.reflect.InvocationTargetException -import java.util.concurrent.atomic.{ AtomicReference, AtomicBoolean } +import java.util.concurrent.atomic.{AtomicReference, AtomicBoolean} import java.util.concurrent._ import akka.AkkaException -class RemoteClientMessageBufferException(message: String, cause: Throwable = null) extends AkkaException(message, cause) +class RemoteClientMessageBufferException(message: String, cause: Throwable = null) extends AkkaException(message, cause){ + def this(msg:String) = this(msg, null); +} object RemoteEncoder { def encode(rmp: RemoteMessageProtocol): AkkaRemoteProtocol = { @@ -66,7 +65,8 @@ object RemoteEncoder { } } -trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagement ⇒ +trait NettyRemoteClientModule extends RemoteClientModule { + self: ListenerManagement ⇒ private val remoteClients = new HashMap[Address, RemoteClient] private val remoteActors = new Index[Address, Uuid] private val lock = new ReadWriteGuard @@ -82,44 +82,51 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem withClientFor(remoteAddress, loader)(_.send[T](message, senderOption, senderFuture, remoteAddress, timeout, isOneWay, actorRef)) private[akka] def withClientFor[T]( - address: InetSocketAddress, loader: Option[ClassLoader])(fun: RemoteClient ⇒ T): T = { + address: InetSocketAddress, loader: Option[ClassLoader])(fun: RemoteClient ⇒ T): T = { // loader.foreach(MessageSerializer.setClassLoader(_)) val key = Address(address) lock.readLock.lock try { val c = remoteClients.get(key) match { - case s: Some[RemoteClient] ⇒ s.get + case Some(client) ⇒ client case None ⇒ lock.readLock.unlock lock.writeLock.lock //Lock upgrade, not supported natively try { try { - remoteClients.get(key) match { //Recheck for addition, race between upgrades - case s: Some[RemoteClient] ⇒ s.get //If already populated by other writer + remoteClients.get(key) match { + //Recheck for addition, race between upgrades + case Some(client) ⇒ client //If already populated by other writer case None ⇒ //Populate map val client = new ActiveRemoteClient(this, address, loader, self.notifyListeners _) client.connect() remoteClients += key -> client client } - } finally { lock.readLock.lock } //downgrade - } finally { lock.writeLock.unlock } + } finally { + lock.readLock.lock + } //downgrade + } finally { + lock.writeLock.unlock + } } fun(c) - } finally { lock.readLock.unlock } + } finally { + lock.readLock.unlock + } } def shutdownClientConnection(address: InetSocketAddress): Boolean = lock withWriteGuard { remoteClients.remove(Address(address)) match { - case s: Some[RemoteClient] ⇒ s.get.shutdown() - case None ⇒ false + case Some(client) ⇒ client.shutdown() + case None ⇒ false } } def restartClientConnection(address: InetSocketAddress): Boolean = lock withReadGuard { remoteClients.get(Address(address)) match { - case s: Some[RemoteClient] ⇒ s.get.connect(reconnectIfAlreadyConnected = true) - case None ⇒ false + case Some(client) ⇒ client.connect(reconnectIfAlreadyConnected = true) + case None ⇒ false } } @@ -133,7 +140,9 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem } def shutdownRemoteClients() = lock withWriteGuard { - remoteClients.foreach({ case (addr, client) ⇒ client.shutdown() }) + remoteClients.foreach({ + case (addr, client) ⇒ client.shutdown() + }) remoteClients.clear() } } @@ -143,9 +152,9 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem * ActiveRemoteClient, but others could be feasible, like a PassiveRemoteClient that * reuses an already established connection. */ -abstract class RemoteClient private[akka] ( - val module: NettyRemoteClientModule, - val remoteAddress: InetSocketAddress) { +abstract class RemoteClient private[akka]( + val module: NettyRemoteClientModule, + val remoteAddress: InetSocketAddress) { val useTransactionLog = config.getBool("akka.cluster.client.buffering.retry-message-send-on-failure", true) val transactionLogCapacity = config.getInt("akka.cluster.client.buffering.capacity", -1) @@ -189,13 +198,13 @@ abstract class RemoteClient private[akka] ( * Converts the message to the wireprotocol and sends the message across the wire */ def send[T]( - message: Any, - senderOption: Option[ActorRef], - senderFuture: Option[Promise[T]], - remoteAddress: InetSocketAddress, - timeout: Long, - isOneWay: Boolean, - actorRef: ActorRef): Option[Promise[T]] = + message: Any, + senderOption: Option[ActorRef], + senderFuture: Option[Promise[T]], + remoteAddress: InetSocketAddress, + timeout: Long, + isOneWay: Boolean, + actorRef: ActorRef): Option[Promise[T]] = send(createRemoteMessageProtocolBuilder( Some(actorRef), Left(actorRef.uuid), actorRef.address, timeout, Right(message), isOneWay, senderOption).build, senderFuture) @@ -204,8 +213,8 @@ abstract class RemoteClient private[akka] ( * Sends the message across the wire */ def send[T]( - request: RemoteMessageProtocol, - senderFuture: Option[Promise[T]]): Option[Promise[T]] = { + request: RemoteMessageProtocol, + senderFuture: Option[Promise[T]]): Option[Promise[T]] = { if (isRunning) { EventHandler.debug(this, "Sending to connection [%s] message [\n%s]".format(remoteAddress, request)) @@ -266,20 +275,23 @@ abstract class RemoteClient private[akka] ( } } - private[remote] def sendPendingRequests() = pendingRequests synchronized { // ensure only one thread at a time can flush the log + private[remote] def sendPendingRequests() = pendingRequests synchronized { + // ensure only one thread at a time can flush the log val nrOfMessages = pendingRequests.size if (nrOfMessages > 0) EventHandler.info(this, "Resending [%s] previously failed messages after remote client reconnect" format nrOfMessages) var pendingRequest = pendingRequests.peek while (pendingRequest ne null) { val (isOneWay, futureUuid, message) = pendingRequest - if (isOneWay) { // sendOneWay + if (isOneWay) { + // sendOneWay val future = currentChannel.write(RemoteEncoder.encode(message)) future.awaitUninterruptibly() if (!future.isCancelled && !future.isSuccess) { notifyListeners(RemoteClientWriteFailed(message, future.getCause, module, remoteAddress)) throw future.getCause } - } else { // sendRequestReply + } else { + // sendRequestReply val future = currentChannel.write(RemoteEncoder.encode(message)) future.awaitUninterruptibly() if (future.isCancelled) futures.remove(futureUuid) // Clean up future @@ -300,9 +312,10 @@ abstract class RemoteClient private[akka] ( * * @author Jonas Bonér */ -class ActiveRemoteClient private[akka] ( - module: NettyRemoteClientModule, remoteAddress: InetSocketAddress, - val loader: Option[ClassLoader] = None, notifyListenersFun: (⇒ Any) ⇒ Unit) extends RemoteClient(module, remoteAddress) { +class ActiveRemoteClient private[akka]( + module: NettyRemoteClientModule, remoteAddress: InetSocketAddress, + val loader: Option[ClassLoader] = None, notifyListenersFun: (⇒ Any) ⇒ Unit) extends RemoteClient(module, remoteAddress) { + import RemoteClientSettings._ //FIXME rewrite to a wrapper object (minimize volatile access and maximize encapsulation) @@ -318,6 +331,7 @@ class ActiveRemoteClient private[akka] ( private var reconnectionTimeWindowStart = 0L def notifyListeners(msg: ⇒ Any): Unit = notifyListenersFun(msg) + def currentChannel = connection.getChannel def connect(reconnectIfAlreadyConnected: Boolean = false): Boolean = { @@ -330,15 +344,18 @@ class ActiveRemoteClient private[akka] ( bootstrap.setOption("tcpNoDelay", true) bootstrap.setOption("keepAlive", true) + EventHandler.debug(this, "Starting remote client connection to [%s]".format(remoteAddress)) + + // Wait until the connection attempt succeeds or fails. connection = bootstrap.connect(remoteAddress) openChannels.add(connection.awaitUninterruptibly.getChannel) if (!connection.isSuccess) { notifyListeners(RemoteClientError(connection.getCause, module, remoteAddress)) + EventHandler.error(connection.getCause, "Remote client connection to [%s] has failed".format(remoteAddress), this) false } else { - //Send cookie val handshake = RemoteControlProtocol.newBuilder.setCommandType(CommandType.CONNECT) if (SECURE_COOKIE.nonEmpty) @@ -365,12 +382,16 @@ class ActiveRemoteClient private[akka] ( } match { case true ⇒ true case false if reconnectIfAlreadyConnected ⇒ + EventHandler.debug(this, "Remote client reconnecting to [%s]".format(remoteAddress)) + openChannels.remove(connection.getChannel) connection.getChannel.close connection = bootstrap.connect(remoteAddress) openChannels.add(connection.awaitUninterruptibly.getChannel) // Wait until the connection attempt succeeds or fails. if (!connection.isSuccess) { notifyListeners(RemoteClientError(connection.getCause, module, remoteAddress)) + EventHandler.error(connection.getCause, "Reconnection to [%s] has failed".format(remoteAddress),this) + false } else { //Send cookie @@ -387,6 +408,8 @@ class ActiveRemoteClient private[akka] ( //Please note that this method does _not_ remove the ARC from the NettyRemoteClientModule's map of clients def shutdown() = runSwitch switchOff { + EventHandler.info(this, "Shutting down [%s]".format(name)) + notifyListeners(RemoteClientShutdown(module, remoteAddress)) timer.stop() timer = null @@ -396,6 +419,8 @@ class ActiveRemoteClient private[akka] ( bootstrap = null connection = null pendingRequests.clear() + + EventHandler.info(this, "[%s] has been shut down".format(name)) } private[akka] def isWithinReconnectionTimeWindow: Boolean = { @@ -403,7 +428,11 @@ class ActiveRemoteClient private[akka] ( reconnectionTimeWindowStart = System.currentTimeMillis true } else { - /*Time left > 0*/ (RECONNECTION_TIME_WINDOW - (System.currentTimeMillis - reconnectionTimeWindowStart)) > 0 + val timeLeft = (RECONNECTION_TIME_WINDOW - (System.currentTimeMillis - reconnectionTimeWindowStart)) > 0 + if (timeLeft) { + EventHandler.info(this, "Will try to reconnect to remote server for another [%s] milliseconds".format(timeLeft)) + } + timeLeft } } @@ -414,12 +443,12 @@ class ActiveRemoteClient private[akka] ( * @author Jonas Bonér */ class ActiveRemoteClientPipelineFactory( - name: String, - futures: ConcurrentMap[Uuid, Promise[_]], - bootstrap: ClientBootstrap, - remoteAddress: InetSocketAddress, - timer: HashedWheelTimer, - client: ActiveRemoteClient) extends ChannelPipelineFactory { + name: String, + futures: ConcurrentMap[Uuid, Promise[_]], + bootstrap: ClientBootstrap, + remoteAddress: InetSocketAddress, + timer: HashedWheelTimer, + client: ActiveRemoteClient) extends ChannelPipelineFactory { def getPipeline: ChannelPipeline = { val timeout = new ReadTimeoutHandler(timer, RemoteClientSettings.READ_TIMEOUT.length, RemoteClientSettings.READ_TIMEOUT.unit) @@ -429,7 +458,7 @@ class ActiveRemoteClientPipelineFactory( val protobufEnc = new ProtobufEncoder val (enc, dec) = RemoteServerSettings.COMPRESSION_SCHEME match { case "zlib" ⇒ (new ZlibEncoder(RemoteServerSettings.ZLIB_COMPRESSION_LEVEL) :: Nil, new ZlibDecoder :: Nil) - case _ ⇒ (Nil, Nil) + case _ ⇒ (Nil, Nil) } val remoteClient = new ActiveRemoteClientHandler(name, futures, bootstrap, remoteAddress, timer, client) @@ -443,12 +472,12 @@ class ActiveRemoteClientPipelineFactory( */ @ChannelHandler.Sharable class ActiveRemoteClientHandler( - val name: String, - val futures: ConcurrentMap[Uuid, Promise[_]], - val bootstrap: ClientBootstrap, - val remoteAddress: InetSocketAddress, - val timer: HashedWheelTimer, - val client: ActiveRemoteClient) + val name: String, + val futures: ConcurrentMap[Uuid, Promise[_]], + val bootstrap: ClientBootstrap, + val remoteAddress: InetSocketAddress, + val timer: HashedWheelTimer, + val client: ActiveRemoteClient) extends SimpleChannelUpstreamHandler { override def messageReceived(ctx: ChannelHandlerContext, event: MessageEvent) { @@ -457,19 +486,27 @@ class ActiveRemoteClientHandler( case arp: AkkaRemoteProtocol if arp.hasInstruction ⇒ val rcp = arp.getInstruction rcp.getCommandType match { - case CommandType.SHUTDOWN ⇒ spawn { client.module.shutdownClientConnection(remoteAddress) } + case CommandType.SHUTDOWN ⇒ spawn { + client.module.shutdownClientConnection(remoteAddress) + } } case arp: AkkaRemoteProtocol if arp.hasMessage ⇒ val reply = arp.getMessage val replyUuid = uuidFrom(reply.getActorInfo.getUuid.getHigh, reply.getActorInfo.getUuid.getLow) - val future = futures.remove(replyUuid).asInstanceOf[Promise[Any]] + EventHandler.debug(this, "Remote client received RemoteMessageProtocol[\n%s]".format(reply)) + EventHandler.debug(this, "Trying to map back to future: %s".format(replyUuid)) - if (reply.hasMessage) { - if (future eq null) throw new IllegalActorStateException("Future mapped to UUID " + replyUuid + " does not exist") - val message = MessageSerializer.deserialize(reply.getMessage) - future.completeWithResult(message) - } else { - future.completeWithException(parseException(reply, client.loader)) + futures.remove(replyUuid).asInstanceOf[Promise[Any]] match { + case null => + client.notifyListeners(RemoteClientError(new IllegalActorStateException("Future mapped to UUID " + replyUuid + " does not exist"), client.module, client.remoteAddress)) + + case future => + if (reply.hasMessage) { + val message = MessageSerializer.deserialize(reply.getMessage) + future.completeWithResult(message) + } else { + future.completeWithException(parseException(reply, client.loader)) + } } case other ⇒ throw new RemoteClientException("Unknown message received in remote client handler: " + other, client.module, client.remoteAddress) @@ -492,13 +529,16 @@ class ActiveRemoteClientHandler( } } }, RemoteClientSettings.RECONNECT_DELAY.toMillis, TimeUnit.MILLISECONDS) - } else spawn { client.module.shutdownClientConnection(remoteAddress) } + } else spawn { + client.module.shutdownClientConnection(remoteAddress) + } } override def channelConnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { try { if (client.useTransactionLog) client.sendPendingRequests() // try to send pending requests (still there after client/server crash ard reconnect client.notifyListeners(RemoteClientConnected(client.module, client.remoteAddress)) + EventHandler.debug(this, "Remote client connected to [%s]".format(ctx.getChannel.getRemoteAddress)) client.resetReconnectionTimeWindow } catch { case e: Throwable ⇒ @@ -510,12 +550,20 @@ class ActiveRemoteClientHandler( override def channelDisconnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { client.notifyListeners(RemoteClientDisconnected(client.module, client.remoteAddress)) + EventHandler.debug(this, "Remote client disconnected from [%s]".format(ctx.getChannel.getRemoteAddress)) } override def exceptionCaught(ctx: ChannelHandlerContext, event: ExceptionEvent) = { + if (event.getCause ne null) + EventHandler.error(event.getCause, "Unexpected exception from downstream in remote client", this) + else + EventHandler.error(this, "Unexpected exception from downstream in remote client: %s".format(event)) + event.getCause match { case e: ReadTimeoutException ⇒ - spawn { client.module.shutdownClientConnection(remoteAddress) } + spawn { + client.module.shutdownClientConnection(remoteAddress) + } case e ⇒ client.notifyListeners(RemoteClientError(e, client.module, client.remoteAddress)) event.getChannel.close //FIXME Is this the correct behavior? @@ -550,17 +598,18 @@ class NettyRemoteSupport extends RemoteSupport with NettyRemoteServerModule with def optimizeLocalScoped_?() = optimizeLocal.get protected[akka] def actorFor( - actorAddress: String, - timeout: Long, - host: String, - port: Int, - loader: Option[ClassLoader]): ActorRef = { + actorAddress: String, + timeout: Long, + host: String, + port: Int, + loader: Option[ClassLoader]): ActorRef = { val homeInetSocketAddress = this.address if (optimizeLocalScoped_?) { if ((host == homeInetSocketAddress.getAddress.getHostAddress || host == homeInetSocketAddress.getHostName) && - port == homeInetSocketAddress.getPort) { //TODO: switch to InetSocketAddress.equals? + port == homeInetSocketAddress.getPort) { + //TODO: switch to InetSocketAddress.equals? val localRef = findActorByAddressOrUuid(actorAddress, actorAddress) if (localRef ne null) return localRef //Code significantly simpler with the return statement } @@ -575,7 +624,9 @@ class NettyRemoteSupport extends RemoteSupport with NettyRemoteServerModule with } class NettyRemoteServer(serverModule: NettyRemoteServerModule, val host: String, val port: Int, val loader: Option[ClassLoader]) { + import RemoteServerSettings._ + val name = "NettyRemoteServer@" + host + ":" + port val address = new InetSocketAddress(host, port) @@ -626,18 +677,18 @@ class NettyRemoteServer(serverModule: NettyRemoteServerModule, val host: String, } } -trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule ⇒ - import RemoteServerSettings._ +trait NettyRemoteServerModule extends RemoteServerModule { + self: RemoteModule ⇒ private[akka] val currentServer = new AtomicReference[Option[NettyRemoteServer]](None) def address = currentServer.get match { - case s: Some[NettyRemoteServer] ⇒ s.get.address - case None ⇒ ReflectiveAccess.RemoteModule.configDefaultAddress + case Some(server) ⇒ server.address + case None ⇒ ReflectiveAccess.RemoteModule.configDefaultAddress } def name = currentServer.get match { - case s: Some[NettyRemoteServer] ⇒ s.get.name + case Some(server) ⇒ server.name case None ⇒ val a = ReflectiveAccess.RemoteModule.configDefaultAddress "NettyRemoteServer@" + a.getAddress.getHostAddress + ":" + a.getPort @@ -650,6 +701,8 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule def start(_hostname: String, _port: Int, loader: Option[ClassLoader] = None): RemoteServerModule = guard withGuard { try { _isRunning switchOn { + EventHandler.debug(this, "Starting up remote server on %s:s".format(_hostname, _port)) + currentServer.set(Some(new NettyRemoteServer(this, _hostname, _port, loader))) } } catch { @@ -662,8 +715,11 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule def shutdownServerModule() = guard withGuard { _isRunning switchOff { - currentServer.getAndSet(None) foreach { instance ⇒ - instance.shutdown() + currentServer.getAndSet(None) foreach { + instance ⇒ + EventHandler.debug(this, "Shutting down remote server on %s:%s".format(instance.host, instance.port)) + + instance.shutdown() } } } @@ -707,7 +763,10 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule * Unregister RemoteModule Actor that is registered using its 'id' field (not custom ID). */ def unregister(actorRef: ActorRef): Unit = guard withGuard { + if (_isRunning.isOn) { + EventHandler.debug(this, "Unregister server side remote actor with id [%s]".format(actorRef.uuid)) + actors.remove(actorRef.address, actorRef) actorsByUuid.remove(actorRef.uuid.toString, actorRef) } @@ -719,7 +778,10 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule * NOTE: You need to call this method if you have registered an actor by a custom ID. */ def unregister(id: String): Unit = guard withGuard { + if (_isRunning.isOn) { + EventHandler.debug(this, "Unregister server side remote actor with id [%s]".format(id)) + if (id.startsWith(UUID_PREFIX)) actorsByUuid.remove(id.substring(UUID_PREFIX.length)) else { val actorRef = actors get id @@ -735,7 +797,10 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule * NOTE: You need to call this method if you have registered an actor by a custom ID. */ def unregisterPerSession(id: String): Unit = { + if (_isRunning.isOn) { + EventHandler.info(this, "Unregistering server side remote actor with id [%s]".format(id)) + actorsFactories.remove(id) } } @@ -745,11 +810,12 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule * @author Jonas Bonér */ class RemoteServerPipelineFactory( - val name: String, - val openChannels: ChannelGroup, - val executor: ExecutionHandler, - val loader: Option[ClassLoader], - val server: NettyRemoteServerModule) extends ChannelPipelineFactory { + val name: String, + val openChannels: ChannelGroup, + val executor: ExecutionHandler, + val loader: Option[ClassLoader], + val server: NettyRemoteServerModule) extends ChannelPipelineFactory { + import RemoteServerSettings._ def getPipeline: ChannelPipeline = { @@ -759,7 +825,7 @@ class RemoteServerPipelineFactory( val protobufEnc = new ProtobufEncoder val (enc, dec) = COMPRESSION_SCHEME match { case "zlib" ⇒ (new ZlibEncoder(ZLIB_COMPRESSION_LEVEL) :: Nil, new ZlibDecoder :: Nil) - case _ ⇒ (Nil, Nil) + case _ ⇒ (Nil, Nil) } val authenticator = if (REQUIRE_COOKIE) new RemoteServerAuthenticationHandler(SECURE_COOKIE) :: Nil else Nil val remoteServer = new RemoteServerHandler(name, openChannels, loader, server) @@ -799,10 +865,11 @@ class RemoteServerAuthenticationHandler(secureCookie: Option[String]) extends Si */ @ChannelHandler.Sharable class RemoteServerHandler( - val name: String, - val openChannels: ChannelGroup, - val applicationLoader: Option[ClassLoader], - val server: NettyRemoteServerModule) extends SimpleChannelUpstreamHandler { + val name: String, + val openChannels: ChannelGroup, + val applicationLoader: Option[ClassLoader], + val server: NettyRemoteServerModule) extends SimpleChannelUpstreamHandler { + import RemoteServerSettings._ // applicationLoader.foreach(MessageSerializer.setClassLoader(_)) //TODO: REVISIT: THIS FEELS A BIT DODGY @@ -819,7 +886,7 @@ class RemoteServerHandler( } else if (!future.isSuccess) { val socketAddress = future.getChannel.getRemoteAddress match { case i: InetSocketAddress ⇒ Some(i) - case _ ⇒ None + case _ ⇒ None } server.notifyListeners(RemoteServerWriteFailed(payload, future.getCause, server, socketAddress)) } @@ -835,6 +902,8 @@ class RemoteServerHandler( override def channelConnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { val clientAddress = getClientAddress(ctx) + EventHandler.debug(this,"Remote client [%s] connected to [%s]".format(clientAddress, server.name)) + sessionActors.set(event.getChannel(), new ConcurrentHashMap[String, ActorRef]()) server.notifyListeners(RemoteServerClientConnected(server, clientAddress)) } @@ -842,12 +911,18 @@ class RemoteServerHandler( override def channelDisconnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { val clientAddress = getClientAddress(ctx) + EventHandler.debug(this, "Remote client [%s] disconnected from [%s]".format(clientAddress, server.name)) + // stop all session actors for ( map ← Option(sessionActors.remove(event.getChannel)); actor ← collectionAsScalaIterable(map.values) ) { - try { actor ! PoisonPill } catch { case e: Exception ⇒ } + try { + actor ! PoisonPill + } catch { + case e: Exception ⇒ EventHandler.error(e, "Couldn't stop %s".format(actor),this) + } } server.notifyListeners(RemoteServerClientDisconnected(server, clientAddress)) @@ -855,6 +930,8 @@ class RemoteServerHandler( override def channelClosed(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { val clientAddress = getClientAddress(ctx) + EventHandler.debug("Remote client [%s] channel closed from [%s]".format(clientAddress, server.name),this) + server.notifyListeners(RemoteServerClientClosed(server, clientAddress)) } @@ -870,6 +947,8 @@ class RemoteServerHandler( } override def exceptionCaught(ctx: ChannelHandlerContext, event: ExceptionEvent) = { + EventHandler.error(event.getCause, "Unexpected exception from remote downstream", this) + event.getChannel.close server.notifyListeners(RemoteServerError(event.getCause, server)) } @@ -877,7 +956,7 @@ class RemoteServerHandler( private def getClientAddress(ctx: ChannelHandlerContext): Option[InetSocketAddress] = ctx.getChannel.getRemoteAddress match { case inet: InetSocketAddress ⇒ Some(inet) - case _ ⇒ None + case _ ⇒ None } private def handleRemoteMessageProtocol(request: RemoteMessageProtocol, channel: Channel) = try { @@ -891,8 +970,13 @@ class RemoteServerHandler( private def dispatchToActor(request: RemoteMessageProtocol, channel: Channel) { val actorInfo = request.getActorInfo + + EventHandler.debug(this, "Dispatching to remote actor [%s]".format(actorInfo.getUuid)) + val actorRef = - try { createActor(actorInfo, channel) } catch { + try { + createActor(actorInfo, channel) + } catch { case e: SecurityException ⇒ EventHandler.error(e, this, e.getMessage) write(channel, createErrorReplyMessage(e, request)) @@ -905,7 +989,8 @@ class RemoteServerHandler( if (request.hasSender) Some(RemoteActorSerialization.fromProtobufToRemoteActorRef(request.getSender, applicationLoader)) else None - message match { // first match on system messages + message match { + // first match on system messages case RemoteActorSystemMessage.Stop ⇒ if (UNTRUSTED_MODE) throw new SecurityException("RemoteModule server is operating is untrusted mode, can not stop the actor") else actorRef.stop() @@ -920,22 +1005,22 @@ class RemoteServerHandler( request.getActorInfo.getTimeout, new ActorPromise(request.getActorInfo.getTimeout). onComplete(_.value.get match { - case l: Left[Throwable, Any] ⇒ write(channel, createErrorReplyMessage(l.a, request)) - case r: Right[Throwable, Any] ⇒ - val messageBuilder = RemoteActorSerialization.createRemoteMessageProtocolBuilder( - Some(actorRef), - Right(request.getUuid), - actorInfo.getAddress, - actorInfo.getTimeout, - r, - true, - Some(actorRef)) + case Left(exception) ⇒ write(channel, createErrorReplyMessage(exception, request)) + case r: Right[_, _] ⇒ + val messageBuilder = RemoteActorSerialization.createRemoteMessageProtocolBuilder( + Some(actorRef), + Right(request.getUuid), + actorInfo.getAddress, + actorInfo.getTimeout, + r.asInstanceOf[Either[Throwable, Any]], + isOneWay = true, + Some(actorRef)) - // FIXME lift in the supervisor uuid management into toh createRemoteMessageProtocolBuilder method - if (request.hasSupervisorUuid) messageBuilder.setSupervisorUuid(request.getSupervisorUuid) + // FIXME lift in the supervisor uuid management into toh createRemoteMessageProtocolBuilder method + if (request.hasSupervisorUuid) messageBuilder.setSupervisorUuid(request.getSupervisorUuid) - write(channel, RemoteEncoder.encode(messageBuilder.build)) - })) + write(channel, RemoteEncoder.encode(messageBuilder.build)) + })) } } @@ -985,7 +1070,7 @@ class RemoteServerHandler( private def findSessionActor(id: String, channel: Channel): ActorRef = sessionActors.get(channel) match { case null ⇒ null - case map ⇒ map get id + case map ⇒ map get id } private def createErrorReplyMessage(exception: Throwable, request: RemoteMessageProtocol): AkkaRemoteProtocol = { diff --git a/akka-cluster/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-cluster/src/main/scala/akka/serialization/SerializationProtocol.scala index e86665295b..810f813efb 100644 --- a/akka-cluster/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-cluster/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -21,6 +21,7 @@ import java.net.InetSocketAddress import com.google.protobuf.ByteString import com.eaio.uuid.UUID +import akka.event.EventHandler /** * Module for local actor serialization. @@ -95,10 +96,10 @@ object ActorSerialization { if (actorRef.mailbox eq null) throw new IllegalActorStateException("Can't serialize an actor that has not been started.") val messages = actorRef.mailbox match { - case q: java.util.Queue[MessageInvocation] ⇒ + case q: java.util.Queue[_] ⇒ val l = new scala.collection.mutable.ListBuffer[MessageInvocation] val it = q.iterator - while (it.hasNext == true) l += it.next + while (it.hasNext) l += it.next.asInstanceOf[MessageInvocation] l } @@ -142,6 +143,8 @@ object ActorSerialization { overriddenUuid: Option[UUID], loader: Option[ClassLoader]): ActorRef = { + EventHandler.debug(this, "Deserializing SerializedActorRefProtocol to LocalActorRef:\n%s".format(protocol)) + val lifeCycle = if (protocol.hasLifeCycle) { protocol.getLifeCycle.getLifeCycle match { @@ -243,11 +246,17 @@ object RemoteActorSerialization { * Deserializes a RemoteActorRefProtocol Protocol Buffers (protobuf) Message into an RemoteActorRef instance. */ private[akka] def fromProtobufToRemoteActorRef(protocol: RemoteActorRefProtocol, loader: Option[ClassLoader]): ActorRef = { - RemoteActorRef( + EventHandler.debug(this, "Deserializing RemoteActorRefProtocol to RemoteActorRef:\n %s".format(protocol)) + + val ref = RemoteActorRef( JavaSerializer.fromBinary(protocol.getInetSocketAddress.toByteArray, Some(classOf[InetSocketAddress]), loader).asInstanceOf[InetSocketAddress], protocol.getAddress, protocol.getTimeout, loader) + + EventHandler.debug(this, "Newly deserialized RemoteActorRef has uuid: %s".format(ref.uuid)) + + ref } /** @@ -263,6 +272,9 @@ object RemoteActorSerialization { case _ ⇒ ReflectiveAccess.RemoteModule.configDefaultAddress } + + EventHandler.debug(this, "Register serialized Actor [%s] as remote @ [%s]".format(actor.uuid, remoteAddress)) + RemoteActorRefProtocol.newBuilder .setInetSocketAddress(ByteString.copyFrom(JavaSerializer.toBinary(remoteAddress))) .setAddress(actor.address) diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmSpec.scala index ae0eadd97f..349626445c 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmSpec.scala +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/api/registry/RegistryStoreMultiJvmSpec.scala @@ -57,16 +57,14 @@ class RegistryStoreMultiJvmNode1 extends MasterClusterTestNode { } barrier("store-1-in-node-1", NrOfNodes) { - val serializer = Serialization.serializerFor(classOf[HelloWorld1]).fold(x ⇒ fail("No serializer found"), s ⇒ s) - node.store("hello-world-1", classOf[HelloWorld1], serializer) + node.store("hello-world-1", classOf[HelloWorld1], Serialization.serializerFor(classOf[HelloWorld1])) } barrier("use-1-in-node-2", NrOfNodes) { } barrier("store-2-in-node-1", NrOfNodes) { - val serializer = Serialization.serializerFor(classOf[HelloWorld1]).fold(x ⇒ fail("No serializer found"), s ⇒ s) - node.store("hello-world-2", classOf[HelloWorld1], false, serializer) + node.store("hello-world-2", classOf[HelloWorld1], false, Serialization.serializerFor(classOf[HelloWorld1])) } barrier("use-2-in-node-2", NrOfNodes) { diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.conf index e23553c931..9c4b6e6c48 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode1.conf @@ -1,4 +1,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" -akka.actor.deployment.service-hello.clustered.replicas = 1 +akka.actor.deployment.service-hello.clustered.replication-factor = 1 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.conf index e23553c931..9c4b6e6c48 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/deployment/DeploymentMultiJvmNode2.conf @@ -1,4 +1,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" -akka.actor.deployment.service-hello.clustered.replicas = 1 +akka.actor.deployment.service-hello.clustered.replication-factor = 1 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode1.conf index a17a4d98ab..8308c61bfa 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode1.conf @@ -1,4 +1,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 \ No newline at end of file +akka.actor.deployment.hello-world.clustered.replication-factor = 1 \ No newline at end of file diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode2.conf index a17a4d98ab..8308c61bfa 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode2.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode2.conf @@ -1,4 +1,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 \ No newline at end of file +akka.actor.deployment.hello-world.clustered.replication-factor = 1 \ No newline at end of file diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode3.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode3.conf index a17a4d98ab..8308c61bfa 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode3.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/migration/automatic/MigrationAutomaticMultiJvmNode3.conf @@ -1,4 +1,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 \ No newline at end of file +akka.actor.deployment.hello-world.clustered.replication-factor = 1 \ No newline at end of file diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf index 8a5bd70eec..370167dcad 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf @@ -1,7 +1,7 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 +akka.actor.deployment.hello-world.clustered.replication-factor = 1 akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log" akka.actor.deployment.hello-world.clustered.replication.strategy = "write-behind" akka.cluster.replication.snapshot-frequency = 1000 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf index 8a5bd70eec..370167dcad 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf @@ -1,7 +1,7 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 +akka.actor.deployment.hello-world.clustered.replication-factor = 1 akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log" akka.actor.deployment.hello-world.clustered.replication.strategy = "write-behind" akka.cluster.replication.snapshot-frequency = 1000 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode1.conf index 84969a04e5..97a0fb3687 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode1.conf @@ -1,7 +1,7 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 +akka.actor.deployment.hello-world.clustered.replication-factor = 1 akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log" akka.actor.deployment.hello-world.clustered.replication.strategy = "write-behind" akka.cluster.replication.snapshot-frequency = 7 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode2.conf index 84969a04e5..97a0fb3687 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode2.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writebehind/snapshot/ReplicationTransactionLogWriteBehindSnapshotMultiJvmNode2.conf @@ -1,7 +1,7 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 +akka.actor.deployment.hello-world.clustered.replication-factor = 1 akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log" akka.actor.deployment.hello-world.clustered.replication.strategy = "write-behind" akka.cluster.replication.snapshot-frequency = 7 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf index 211cdbd6ee..42e57847b5 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode1.conf @@ -1,7 +1,7 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 +akka.actor.deployment.hello-world.clustered.replication-factor = 1 akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log" akka.actor.deployment.hello-world.clustered.replication.strategy = "write-through" diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf index 567b03b9cb..cc2fb1ef3b 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteBehindNoSnapshotMultiJvmNode2.conf @@ -1,7 +1,7 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 +akka.actor.deployment.hello-world.clustered.replication-factor = 1 akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log" akka.actor.deployment.hello-world.clustered.replication.strategy = "write-through" akka.cluster.replication.snapshot-frequency = 1000 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode1.conf index 211cdbd6ee..42e57847b5 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode1.conf @@ -1,7 +1,7 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 +akka.actor.deployment.hello-world.clustered.replication-factor = 1 akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log" akka.actor.deployment.hello-world.clustered.replication.strategy = "write-through" diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode2.conf index 567b03b9cb..cc2fb1ef3b 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode2.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/nosnapshot/ReplicationTransactionLogWriteThroughNoSnapshotMultiJvmNode2.conf @@ -1,7 +1,7 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 +akka.actor.deployment.hello-world.clustered.replication-factor = 1 akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log" akka.actor.deployment.hello-world.clustered.replication.strategy = "write-through" akka.cluster.replication.snapshot-frequency = 1000 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode1.conf index 58c66d3e42..c38191ce28 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode1.conf @@ -1,7 +1,7 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 +akka.actor.deployment.hello-world.clustered.replication-factor = 1 akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log" akka.actor.deployment.hello-world.clustered.replication.strategy = "write-through" akka.cluster.replication.snapshot-frequency = 7 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode2.conf index 58c66d3e42..c38191ce28 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode2.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/replication/transactionlog/writethrough/snapshot/ReplicationTransactionLogWriteThroughSnapshotMultiJvmNode2.conf @@ -1,7 +1,7 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.hello-world.router = "direct" -akka.actor.deployment.hello-world.clustered.replicas = 1 +akka.actor.deployment.hello-world.clustered.replication-factor = 1 akka.actor.deployment.hello-world.clustered.replication.storage = "transaction-log" akka.actor.deployment.hello-world.clustered.replication.strategy = "write-through" akka.cluster.replication.snapshot-frequency = 7 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/bad_address/BadAddressDirectRoutingMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/bad_address/BadAddressDirectRoutingMultiJvmNode1.conf index 1345a2287c..e12f87e912 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/bad_address/BadAddressDirectRoutingMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/bad_address/BadAddressDirectRoutingMultiJvmNode1.conf @@ -2,4 +2,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" akka.actor.deployment.service-hello.clustered.home = "node:node1" -akka.actor.deployment.service-hello.clustered.replicas = 1 \ No newline at end of file +akka.actor.deployment.service-hello.clustered.replication-factor = 1 \ No newline at end of file diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode1.conf index 2a3d9ba765..0a47105d03 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/direct/single_replica/SingleReplicaDirectRoutingMultiJvmNode1.conf @@ -1,4 +1,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "direct" -akka.actor.deployment.service-hello.clustered.replicas = 1 \ No newline at end of file +akka.actor.deployment.service-hello.clustered.replication-factor = 1 \ No newline at end of file diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode1.conf index d7e17c84d8..366cf9111c 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode1.conf @@ -2,4 +2,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"] -akka.actor.deployment.service-hello.clustered.replicas = 1 \ No newline at end of file +akka.actor.deployment.service-hello.clustered.replication-factor = 1 \ No newline at end of file diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode2.conf index d7e17c84d8..366cf9111c 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode2.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/homenode/HomeNodeMultiJvmNode2.conf @@ -2,4 +2,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"] -akka.actor.deployment.service-hello.clustered.replicas = 1 \ No newline at end of file +akka.actor.deployment.service-hello.clustered.replication-factor = 1 \ No newline at end of file diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.conf index dcbd276918..0fb82878ea 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_1_replica/RoundRobin1ReplicaMultiJvmNode1.conf @@ -1,4 +1,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" -akka.actor.deployment.service-hello.clustered.replicas = 1 \ No newline at end of file +akka.actor.deployment.service-hello.clustered.replication-factor = 1 \ No newline at end of file diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.conf index 09f4cfc93a..f4a6f8868c 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode1.conf @@ -2,4 +2,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1","node:node2"] -akka.actor.deployment.service-hello.clustered.replicas = 2 +akka.actor.deployment.service-hello.clustered.replication-factor = 2 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.conf index 09f4cfc93a..f4a6f8868c 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_2_replicas/RoundRobin2ReplicasMultiJvmNode2.conf @@ -2,4 +2,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1","node:node2"] -akka.actor.deployment.service-hello.clustered.replicas = 2 +akka.actor.deployment.service-hello.clustered.replication-factor = 2 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode1.conf index 75249c7713..2217a81073 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode1.conf @@ -1,4 +1,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" -akka.actor.deployment.service-hello.clustered.replicas = 3 +akka.actor.deployment.service-hello.clustered.replication-factor = 3 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode2.conf index 75249c7713..4be28d60a2 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode2.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode2.conf @@ -1,4 +1,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" -akka.actor.deployment.service-hello.clustered.replicas = 3 +akka.actor.deployment.service-hello.clustered.repliction-factor = 3 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode3.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode3.conf index 75249c7713..2217a81073 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode3.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_3_replicas/RoundRobin3ReplicasMultiJvmNode3.conf @@ -1,4 +1,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" -akka.actor.deployment.service-hello.clustered.replicas = 3 +akka.actor.deployment.service-hello.clustered.replication-factor = 3 diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode1.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode1.conf index d7e17c84d8..366cf9111c 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode1.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode1.conf @@ -2,4 +2,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"] -akka.actor.deployment.service-hello.clustered.replicas = 1 \ No newline at end of file +akka.actor.deployment.service-hello.clustered.replication-factor = 1 \ No newline at end of file diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode2.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode2.conf index d7e17c84d8..366cf9111c 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode2.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode2.conf @@ -2,4 +2,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"] -akka.actor.deployment.service-hello.clustered.replicas = 1 \ No newline at end of file +akka.actor.deployment.service-hello.clustered.replication-factor = 1 \ No newline at end of file diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode3.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode3.conf index d7e17c84d8..366cf9111c 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode3.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode3.conf @@ -2,4 +2,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"] -akka.actor.deployment.service-hello.clustered.replicas = 1 \ No newline at end of file +akka.actor.deployment.service-hello.clustered.replication-factor = 1 \ No newline at end of file diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode4.conf b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode4.conf index d7e17c84d8..366cf9111c 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode4.conf +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/routing/roundrobin_failover/RoundRobinFailoverMultiJvmNode4.conf @@ -2,4 +2,4 @@ akka.enabled-modules = ["cluster"] akka.event-handler-level = "WARNING" akka.actor.deployment.service-hello.router = "round-robin" akka.actor.deployment.service-hello.clustered.preferred-nodes = ["node:node1"] -akka.actor.deployment.service-hello.clustered.replicas = 1 \ No newline at end of file +akka.actor.deployment.service-hello.clustered.replication-factor = 1 \ No newline at end of file diff --git a/akka-docs/intro/getting-started-first-java.rst b/akka-docs/intro/getting-started-first-java.rst index a0fc3eeaac..17cab7cf41 100644 --- a/akka-docs/intro/getting-started-first-java.rst +++ b/akka-docs/intro/getting-started-first-java.rst @@ -291,13 +291,13 @@ Now we can create the worker actor. This is done by extending in the ``UntypedA double result = calculatePiFor(work.getStart(), work.getNrOfElements()); // reply with the result - getContext().replyUnsafe(new Result(result)); + getContext().reply(new Result(result)); } else throw new IllegalArgumentException("Unknown message [" + message + "]"); } } -As you can see we have now created an ``UntypedActor`` with a ``onReceive`` method as a handler for the ``Work`` message. In this handler we invoke the ``calculatePiFor(..)`` method, wrap the result in a ``Result`` message and send it back to the original sender using ``getContext().replyUnsafe(..)``. In Akka the sender reference is implicitly passed along with the message so that the receiver can always reply or store away the sender reference for future use. +As you can see we have now created an ``UntypedActor`` with a ``onReceive`` method as a handler for the ``Work`` message. In this handler we invoke the ``calculatePiFor(..)`` method, wrap the result in a ``Result`` message and send it back to the original sender using ``getContext().reply(..)``. In Akka the sender reference is implicitly passed along with the message so that the receiver can always reply or store away the sender reference for future use. The only thing missing in our ``Worker`` actor is the implementation on the ``calculatePiFor(..)`` method:: @@ -587,7 +587,7 @@ Before we package it up and run it, let's take a look at the full code now, with double result = calculatePiFor(work.getStart(), work.getNrOfElements()) // reply with the result - getContext().replyUnsafe(new Result(result)); + getContext().reply(new Result(result)); } else throw new IllegalArgumentException("Unknown message [" + message + "]"); } diff --git a/akka-docs/java/fault-tolerance.rst b/akka-docs/java/fault-tolerance.rst index 512e914d2b..087f6cb0be 100644 --- a/akka-docs/java/fault-tolerance.rst +++ b/akka-docs/java/fault-tolerance.rst @@ -342,22 +342,22 @@ Supervised actors have the option to reply to the initial sender within preResta // do something that may throw an exception // ... - getContext().replySafe("ok"); + getContext().tryReply("ok"); } @Override public void preRestart(Throwable reason) { - getContext().replySafe(reason.getMessage()); + getContext().tryReply(reason.getMessage()); } @Override public void postStop() { - getContext().replySafe("stopped by supervisor"); + getContext().tryReply("stopped by supervisor"); } } -- A reply within preRestart or postRestart must be a safe reply via getContext().replySafe() because a getContext().replyUnsafe() will throw an exception when the actor is restarted without having failed. This can be the case in context of AllForOne restart strategies. -- A reply within postStop must be a safe reply via getContext().replySafe() because a getContext().replyUnsafe() will throw an exception when the actor has been stopped by the application (and not by a supervisor) after successful execution of receive (or no execution at all). +- A reply within preRestart or postRestart must be a safe reply via getContext().tryReply() because a getContext().reply() will throw an exception when the actor is restarted without having failed. This can be the case in context of AllForOne restart strategies. +- A reply within postStop must be a safe reply via getContext().tryReply() because a getContext().reply() will throw an exception when the actor has been stopped by the application (and not by a supervisor) after successful execution of receive (or no execution at all). Handling too many actor restarts within a specific time limit ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/akka-docs/java/serialization.rst b/akka-docs/java/serialization.rst index 0a41941ba5..aff5db501c 100644 --- a/akka-docs/java/serialization.rst +++ b/akka-docs/java/serialization.rst @@ -20,7 +20,7 @@ Step 1: Define the Actor public class SerializationTestActor extends UntypedActor { public void onReceive(Object msg) { - getContext().replySafe("got it!"); + getContext().tryReply("got it!"); } } @@ -101,10 +101,10 @@ Step 1: Define the Actor public void onReceive(Object msg) { if (msg.equals("hello")) { count = count + 1; - getContext().replyUnsafe("world " + count); + getContext().reply("world " + count); } else if (msg instanceof String) { count = count + 1; - getContext().replyUnsafe("hello " + msg + " " + count); + getContext().reply("hello " + msg + " " + count); } else { throw new IllegalArgumentException("invalid message type"); } diff --git a/akka-docs/java/transactors.rst b/akka-docs/java/transactors.rst index b724ef89b6..3655bd290f 100644 --- a/akka-docs/java/transactors.rst +++ b/akka-docs/java/transactors.rst @@ -95,7 +95,7 @@ Here is an example of coordinating two simple counter UntypedActors so that they }); } } else if (incoming.equals("GetCount")) { - getContext().replyUnsafe(count.get()); + getContext().reply(count.get()); } } } diff --git a/akka-docs/java/untyped-actors.rst b/akka-docs/java/untyped-actors.rst index 3e95cf4aae..fd6912be11 100644 --- a/akka-docs/java/untyped-actors.rst +++ b/akka-docs/java/untyped-actors.rst @@ -247,10 +247,10 @@ which you do by Channel.sendOneWay(msg) We recommend that you as first choice use the channel abstraction instead of the other ways described in the following sections. -Reply using the 'replySafe' and 'replyUnsafe' methods +Reply using the 'tryReply' and 'reply' methods ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you want to send a message back to the original sender of the message you just received then you can use the 'getContext().replyUnsafe(..)' method. +If you want to send a message back to the original sender of the message you just received then you can use the 'getContext().reply(..)' method. .. code-block:: java @@ -258,15 +258,15 @@ If you want to send a message back to the original sender of the message you jus if (message instanceof String) { String msg = (String)message; if (msg.equals("Hello")) { - // Reply to original sender of message using the 'replyUnsafe' method - getContext().replyUnsafe(msg + " from " + getContext().getUuid()); + // Reply to original sender of message using the 'reply' method + getContext().reply(msg + " from " + getContext().getUuid()); } } } In this case we will a reply back to the Actor that sent the message. -The 'replyUnsafe' method throws an 'IllegalStateException' if unable to determine what to reply to, e.g. the sender has not been passed along with the message when invoking one of 'send*' methods. You can also use the more forgiving 'replySafe' method which returns 'true' if reply was sent, and 'false' if unable to determine what to reply to. +The 'reply' method throws an 'IllegalStateException' if unable to determine what to reply to, e.g. the sender has not been passed along with the message when invoking one of 'send*' methods. You can also use the more forgiving 'tryReply' method which returns 'true' if reply was sent, and 'false' if unable to determine what to reply to. .. code-block:: java @@ -274,8 +274,8 @@ The 'replyUnsafe' method throws an 'IllegalStateException' if unable to determin if (message instanceof String) { String msg = (String)message; if (msg.equals("Hello")) { - // Reply to original sender of message using the 'replyUnsafe' method - if (getContext().replySafe(msg + " from " + getContext().getUuid())) ... // success + // Reply to original sender of message using the 'reply' method + if (getContext().tryReply(msg + " from " + getContext().getUuid())) ... // success else ... // handle failure } } diff --git a/akka-docs/modules/camel.rst b/akka-docs/modules/camel.rst index 9b0d2ca3d3..b1bf200e91 100644 --- a/akka-docs/modules/camel.rst +++ b/akka-docs/modules/camel.rst @@ -246,7 +246,7 @@ from localhost on port 8877. public void onReceive(Object message) { Message msg = (Message)message; String body = msg.getBodyAs(String.class); - getContext().replySafe(String.format("Hello %s", body)); + getContext().tryReply(String.format("Hello %s", body)); } } @@ -659,10 +659,10 @@ acknowledgement). public void onReceive(Object message) { // ... - getContext().replyUnsafe(ack()) // on success + getContext().reply(ack()) // on success // ... val e: Exception = ... - getContext().replyUnsafe(new Failure(e)) // on failure + getContext().reply(new Failure(e)) // on failure } } @@ -855,7 +855,7 @@ following consumer actor class. } public void onReceive(Object message) { - getContext().replySafe("response from remote actor 1"); + getContext().tryReply("response from remote actor 1"); } } @@ -1423,7 +1423,7 @@ For initiating a a two-way message exchange, one of the public class SampleUntypedActor extends UntypedActor { public void onReceive(Object msg) { - getContext().replySafe(CamelContextManager.getMandatoryTemplate().requestBody("direct:news", msg)); + getContext().tryReply(CamelContextManager.getMandatoryTemplate().requestBody("direct:news", msg)); } } @@ -1995,7 +1995,7 @@ ends at the target actor. public void onReceive(Object message) { Message msg = (Message) message; String body = msg.getBodyAs(String.class); - getContext().replySafe(String.format("Hello %s", body)); + getContext().tryReply(String.format("Hello %s", body)); } } diff --git a/akka-docs/project/migration-guide-1.1.x-1.2.x.rst b/akka-docs/project/migration-guide-1.1.x-1.2.x.rst new file mode 100644 index 0000000000..e4988b9460 --- /dev/null +++ b/akka-docs/project/migration-guide-1.1.x-1.2.x.rst @@ -0,0 +1,6 @@ +.. _migration-1.2: + +################################ + Migration Guide 1.1.x to 1.2.x +################################ + diff --git a/akka-docs/project/migration-guide-1.2.x-2.0.x.rst b/akka-docs/project/migration-guide-1.2.x-2.0.x.rst new file mode 100644 index 0000000000..7eabcf2f10 --- /dev/null +++ b/akka-docs/project/migration-guide-1.2.x-2.0.x.rst @@ -0,0 +1,20 @@ +.. _migration-2.0: + +################################ + Migration Guide 1.2.x to 2.0.x +################################ + +Actors +====== + +The 2.0 release contains several new features which require source-level +changes in client code. This API cleanup is planned to be the last one for a +significant amount of time. + +Lifecycle Callbacks +------------------- + +The :meth:`preRestart(cause: Throwable)` method has been replaced by +:meth:`preRestart(cause: Throwable, lastMessage: Any)`, hence you must insert +the second argument in all overriding methods. The good news is that any missed +actor will not compile without error. diff --git a/akka-docs/project/migration-guides.rst b/akka-docs/project/migration-guides.rst index 7eb063811c..7af815f241 100644 --- a/akka-docs/project/migration-guides.rst +++ b/akka-docs/project/migration-guides.rst @@ -6,6 +6,8 @@ Migration Guides .. toctree:: :maxdepth: 1 + migration-guide-1.2.x-2.0.x + migration-guide-1.1.x-1.2.x migration-guide-1.0.x-1.1.x migration-guide-0.10.x-1.0.x migration-guide-0.9.x-0.10.x diff --git a/akka-docs/scala/actors.rst b/akka-docs/scala/actors.rst index 20ceda4285..16bdc06079 100644 --- a/akka-docs/scala/actors.rst +++ b/akka-docs/scala/actors.rst @@ -92,6 +92,90 @@ Here we create a light-weight actor-based thread, that can be used to spawn off ... // do stuff } +Actor Internal API +------------------ + +The :class:`Actor` trait defines only one abstract method, the abovementioned +:meth:`receive`. In addition, it offers two convenience methods +:meth:`become`/:meth:`unbecome` for modifying the hotswap behavior stack as +described in :ref:`Actor.HotSwap` and the :obj:`self` reference to this actor’s +:class:`ActorRef` object. If the current actor behavior does not match a +received message, :meth:`unhandled` is called, which by default throws an +:class:`UnhandledMessageException`. + +The remaining visible methods are user-overridable life-cycle hooks which are +described in the following:: + + def preStart() {} + def preRestart(cause: Throwable, message: Option[Any]) {} + def freshInstance(): Option[Actor] = None + def postRestart(cause: Throwable) {} + def postStop() {} + +The implementations shown above are the defaults provided by the :class:`Actor` +trait. + +Start Hook +^^^^^^^^^^ + +Right after starting the actor, its :meth:`preStart` method is invoked. This is +guaranteed to happen before the first message from external sources is queued +to the actor’s mailbox. + +:: + + override def preStart { + // e.g. send initial message to self + self ! GetMeStarted + // or do any other stuff, e.g. registering with other actors + someService ! Register(self) + } + +Restart Hooks +^^^^^^^^^^^^^ + +A supervised actor, i.e. one which is linked to another actor with a fault +handling strategy, will be restarted in case an exception is thrown while +processing a message. This restart involves four of the hooks mentioned above: + +1. The old actor is informed by calling :meth:`preRestart` with the exception + which caused the restart and the message which triggered that exception; the + latter may be ``None`` if the restart was not caused by processing a + message, e.g. when a supervisor does not trap the exception and is restarted + in turn by its supervisor. This method is the best place for cleaning up, + preparing hand-over to the fresh actor instance, etc. +2. The old actor’s :meth:`freshInstance` factory method is invoked, which may + optionally produce the new actor instance which will replace this actor. If + this method returns :obj:`None` or throws an exception, the initial factory + from the ``Actor.actorOf`` call is used to produce the fresh instance. +3. The new actor’s :meth:`preStart` method is invoked, just as in the normal + start-up case. +4. The new actor’s :meth:`postRestart` method is called with the exception + which caused the restart. + +.. warning:: + + The :meth:`freshInstance` hook may be used to propagate (part of) the failed + actor’s state to the fresh instance. This carries the risk of proliferating + the cause for the crash which triggered the restart. If you are tempted to + take this route, it is strongly advised to step back and consider other + possible approaches, e.g. distributing the state in question using other + means or spawning short-lived worker actors for carrying out “risky” tasks. + +An actor restart replaces only the actual actor object; the contents of the +mailbox and the hotswap stack are unaffected by the restart, so processing of +messages will resume after the :meth:`postRestart` hook returns. Any message +sent to an actor while it is being restarted will be queued to its mailbox as +usual. + +Stop Hook +^^^^^^^^^ + +After stopping an actor, its :meth:`postStop` hook is called, which may be used +e.g. for deregistering this actor from other services. This hook is guaranteed +to run after message queuing has been disabled for this actor, i.e. sending +messages would fail with an :class:`IllegalActorStateException`. + Identifying Actors ------------------ @@ -252,43 +336,6 @@ This method should return a ``PartialFunction``, e.g. a ‘match/case’ clause } } -Actor internal API ------------------- - -The Actor trait contains almost no member fields or methods to invoke, you just use the Actor trait to implement the: - -#. ``receive`` message handler -#. life-cycle callbacks: - - #. preStart - #. postStop - #. preRestart - #. postRestart - -The ``Actor`` trait has one single member field: - -.. code-block:: scala - - val self: ActorRef - -This ``self`` field holds a reference to its ``ActorRef`` and it is this reference you want to access the Actor's API. Here, for example, you find methods to reply to messages, send yourself messages, define timeouts, fault tolerance etc., start and stop etc. - -However, for convenience you can import these functions and fields like below, which will allow you do drop the ``self`` prefix: - -.. code-block:: scala - - class MyActor extends Actor { - import self._ - id = ... - dispatcher = ... - start - ... - } - -But in this documentation we will always prefix the calls with ``self`` for clarity. - -Let's start by looking how we can reply to messages in a convenient way using this ``ActorRef`` API. - Reply to messages ----------------- @@ -335,13 +382,13 @@ If you want to send a message back to the original sender of the message you jus In this case the ``result`` will be send back to the Actor that sent the ``request``. -The ``reply`` method throws an ``IllegalStateException`` if unable to determine what to reply to, e.g. the sender is not an actor. You can also use the more forgiving ``reply_?`` method which returns ``true`` if reply was sent, and ``false`` if unable to determine what to reply to. +The ``reply`` method throws an ``IllegalStateException`` if unable to determine what to reply to, e.g. the sender is not an actor. You can also use the more forgiving ``tryReply`` method which returns ``true`` if reply was sent, and ``false`` if unable to determine what to reply to. .. code-block:: scala case request => val result = process(request) - if (self.reply_?(result)) ...// success + if (self.tryReply(result)) ...// success else ... // handle failure Summary of reply semantics and options @@ -441,6 +488,8 @@ You can also send an actor the ``akka.actor.PoisonPill`` message, which will sto If the sender is a ``Future`` (e.g. the message is sent with ``?``), the ``Future`` will be completed with an ``akka.actor.ActorKilledException("PoisonPill")``. +.. _Actor.HotSwap: + HotSwap ------- diff --git a/akka-docs/scala/dispatchers.rst b/akka-docs/scala/dispatchers.rst index 4f385364a2..abcfd6d9bf 100644 --- a/akka-docs/scala/dispatchers.rst +++ b/akka-docs/scala/dispatchers.rst @@ -93,7 +93,7 @@ You can also set the rejection policy that should be used, e.g. what should be d * java.util.concurrent.ThreadPoolExecutor.DiscardPolicy - discards the message (throws it away) * java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy - discards the oldest message in the mailbox (throws it away) -You cane read more about these policies `here `_. +You can read more about these policies `here `_. Here is an example: @@ -104,7 +104,7 @@ Here is an example: import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy class MyActor extends Actor { - self.dispatcher = Dispatchers.newDispatcher(name) + self.dispatcher = Dispatchers.newDispatcher(name, throughput = 15) .withNewThreadPoolWithLinkedBlockingQueueWithCapacity(100) .setCorePoolSize(16) .setMaxPoolSize(128) @@ -114,8 +114,14 @@ Here is an example: ... } -This 'Dispatcher' allows you to define the 'throughput' it should have. This defines the number of messages for a specific Actor the dispatcher should process in one single sweep. -Setting this to a higher number will increase throughput but lower fairness, and vice versa. If you don't specify it explicitly then it uses the default value defined in the 'akka.conf' configuration file: +The standard :class:`Dispatcher` allows you to define the ``throughput`` it +should have, as shown above. This defines the number of messages for a specific +Actor the dispatcher should process in one single sweep; in other words, the +dispatcher will bunch up to ``throughput`` message invocations together when +having elected an actor to run. Setting this to a higher number will increase +throughput but lower fairness, and vice versa. If you don't specify it +explicitly then it uses the default value defined in the 'akka.conf' +configuration file: .. code-block:: ruby diff --git a/akka-docs/scala/fault-tolerance.rst b/akka-docs/scala/fault-tolerance.rst index c7ac83fd7e..507c3a3b88 100644 --- a/akka-docs/scala/fault-tolerance.rst +++ b/akka-docs/scala/fault-tolerance.rst @@ -228,11 +228,11 @@ A child actor can tell the supervising actor to unlink him by sending him the 'U .. code-block:: scala - if (supervisor.isDefined) supervisor.get ! Unlink(this) + if (supervisor.isDefined) supervisor.get ! Unlink(self) // Or shorter using 'foreach': - supervisor.foreach(_ ! Unlink(this)) + supervisor.foreach(_ ! Unlink(self)) The supervising actor's side of things ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -322,16 +322,16 @@ Supervised actors have the option to reply to the initial sender within preResta } override def preRestart(reason: scala.Throwable) { - self.reply_?(reason.getMessage) + self.tryReply(reason.getMessage) } override def postStop() { - self.reply_?("stopped by supervisor") + self.tryReply("stopped by supervisor") } } -- A reply within preRestart or postRestart must be a safe reply via `self.reply_?` because an unsafe self.reply will throw an exception when the actor is restarted without having failed. This can be the case in context of AllForOne restart strategies. -- A reply within postStop must be a safe reply via `self.reply_?` because an unsafe self.reply will throw an exception when the actor has been stopped by the application (and not by a supervisor) after successful execution of receive (or no execution at all). +- A reply within preRestart or postRestart must be a safe reply via `self.tryReply` because an unsafe self.reply will throw an exception when the actor is restarted without having failed. This can be the case in context of AllForOne restart strategies. +- A reply within postStop must be a safe reply via `self.tryReply` because an unsafe self.reply will throw an exception when the actor has been stopped by the application (and not by a supervisor) after successful execution of receive (or no execution at all). Handling too many actor restarts within a specific time limit ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/akka-docs/scala/http.rst b/akka-docs/scala/http.rst index ba25234f93..44448b14fc 100644 --- a/akka-docs/scala/http.rst +++ b/akka-docs/scala/http.rst @@ -1,4 +1,3 @@ - .. _http-module: HTTP @@ -462,4 +461,7 @@ Using the Akka Mist module with the Facebook Graph API and WebGL Example project using Akka Mist with the Facebook Graph API and WebGL ``_ +Using Akka Mist on Amazon ElasticBeanstalk +****************************************** +``_ diff --git a/akka-docs/scala/testing.rst b/akka-docs/scala/testing.rst index f3bb07c5bb..65066f3d7f 100644 --- a/akka-docs/scala/testing.rst +++ b/akka-docs/scala/testing.rst @@ -288,6 +288,13 @@ assertions concerning received messages. Here is the full list: does a conformance check; if you need the class to be equal, have a look at :meth:`expectMsgAllClassOf` with a single given class argument. + * :meth:`expectMsgType[T: Manifest](d: Duration)` + + An object which is an instance of the given type (after erasure) must be + received within the allotted time frame; the object will be returned. This + method is approximately equivalent to + ``expectMsgClass(manifest[T].erasure)``. + * :meth:`expectMsgAnyOf[T](d: Duration, obj: T*): T` An object must be received within the given time, and it must be equal ( @@ -375,6 +382,19 @@ with message flows: This feature is useful e.g. when testing a logging system, where you want to ignore regular messages and are only interested in your specific ones. +Expecting Exceptions +-------------------- + +One case which is not handled by the :obj:`testActor` is if an exception is +thrown while processing the message sent to the actor under test. This can be +tested by using a :class:`Future` based invocation:: + + // assuming ScalaTest ShouldMatchers + + evaluating { + (someActor ? badOperation).await.get + } should produce [UnhandledMessageException] + .. _TestKit.within: Timing Assertions @@ -398,21 +418,25 @@ is between :obj:`min` and :obj:`max`, where the former defaults to zero. The deadline calculated by adding the :obj:`max` parameter to the block's start time is implicitly available within the block to all examination methods, if you do not specify it, is is inherited from the innermost enclosing -:meth:`within` block. It should be noted that using :meth:`expectNoMsg` will -terminate upon reception of a message or at the deadline, whichever occurs -first; it follows that this examination usually is the last statement in a :meth:`within` block. +It should be noted that if the last message-receiving assertion of the block is +:meth:`expectNoMsg` or :meth:`receiveWhile`, the final check of the +:meth:`within` is skipped in order to avoid false positives due to wake-up +latencies. This means that while individual contained assertions still use the +maximum time bound, the overall block may take arbitrarily longer in this case. + .. code-block:: scala class SomeSpec extends WordSpec with MustMatchers with TestKit { "A Worker" must { "send timely replies" in { val worker = actorOf(...) - within (50 millis) { + within (500 millis) { worker ! "some work" expectMsg("some result") - expectNoMsg + expectNoMsg // will block for the rest of the 500ms + Thread.sleep(1000) // will NOT make this block fail } } } @@ -468,7 +492,7 @@ using a small example:: var dest1 : ActorRef = _ var dest2 : ActorRef = _ def receive = { - case (d1, d2) => + case (d1: ActorRef, d2: ActorRef) => dest1 = d1 dest2 = d2 case x => diff --git a/akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/scala/akka/actor/mailbox/BeanstalkBasedMailbox.scala b/akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/scala/akka/actor/mailbox/BeanstalkBasedMailbox.scala index 35ec40edee..8138d04b14 100644 --- a/akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/scala/akka/actor/mailbox/BeanstalkBasedMailbox.scala +++ b/akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/scala/akka/actor/mailbox/BeanstalkBasedMailbox.scala @@ -15,7 +15,7 @@ import akka.event.EventHandler import com.surftools.BeanstalkClient._ import com.surftools.BeanstalkClientImpl._ -class BeanstalkBasedMailboxException(message: String) extends AkkaException(message) +class BeanstalkBasedMailboxException(message: String) extends AkkaException(message) {} /** * @author Jonas Bonér diff --git a/akka-samples/akka-sample-camel/src/main/java/sample/camel/UntypedConsumer1.java b/akka-samples/akka-sample-camel/src/main/java/sample/camel/UntypedConsumer1.java index 39d910fc28..b84792ed89 100644 --- a/akka-samples/akka-sample-camel/src/main/java/sample/camel/UntypedConsumer1.java +++ b/akka-samples/akka-sample-camel/src/main/java/sample/camel/UntypedConsumer1.java @@ -15,6 +15,6 @@ public class UntypedConsumer1 extends UntypedConsumerActor { public void onReceive(Object message) { Message msg = (Message)message; String body = msg.getBodyAs(String.class); - getContext().replySafe(String.format("received %s", body)); + getContext().tryReply(String.format("received %s", body)); } } diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala index 5bf50a5e2c..8f3d16b318 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala @@ -7,7 +7,8 @@ import org.apache.camel.spring.spi.ApplicationContextRegistry import org.springframework.context.support.ClassPathXmlApplicationContext import akka.actor.Actor._ -import akka.actor.{TypedActor, Supervisor} +import akka.actor.TypedActor +import akka.actor.TypedActor.Configuration._ import akka.camel.CamelContextManager import akka.config.Supervision._ @@ -89,7 +90,8 @@ class Boot { // Active object example // ----------------------------------------------------------------------- - //TypedActor.newInstance(classOf[TypedConsumer1], classOf[TypedConsumer1Impl]) + // TODO: investigate why this consumer is not published + TypedActor.typedActorOf(classOf[TypedConsumer1], classOf[TypedConsumer1Impl], defaultConfiguration) } /** diff --git a/akka-samples/akka-sample-camel/src/test/java/sample/camel/SampleRemoteUntypedConsumer.java b/akka-samples/akka-sample-camel/src/test/java/sample/camel/SampleRemoteUntypedConsumer.java index 5dea328e59..04f414a89b 100644 --- a/akka-samples/akka-sample-camel/src/test/java/sample/camel/SampleRemoteUntypedConsumer.java +++ b/akka-samples/akka-sample-camel/src/test/java/sample/camel/SampleRemoteUntypedConsumer.java @@ -15,7 +15,7 @@ public class SampleRemoteUntypedConsumer extends UntypedConsumerActor { Message msg = (Message)message; String body = msg.getBodyAs(String.class); String header = msg.getHeaderAs("test", String.class); - getContext().replySafe(String.format("%s %s", body, header)); + getContext().tryReply(String.format("%s %s", body, header)); } } diff --git a/akka-samples/akka-sample-camel/src/test/scala/sample/camel/HttpConcurrencyTestStress.scala b/akka-samples/akka-sample-camel/src/test/scala/sample/camel/HttpConcurrencyTestStress.scala index 255a6262c0..bbe039af22 100644 --- a/akka-samples/akka-sample-camel/src/test/scala/sample/camel/HttpConcurrencyTestStress.scala +++ b/akka-samples/akka-sample-camel/src/test/scala/sample/camel/HttpConcurrencyTestStress.scala @@ -21,6 +21,8 @@ class HttpConcurrencyTestStress extends JUnitSuite { import HttpConcurrencyTestStress._ @Test def shouldProcessMessagesConcurrently = { + /* TODO: fix stress test + val num = 50 val latch1 = new CountDownLatch(num) val latch2 = new CountDownLatch(num) @@ -38,7 +40,7 @@ class HttpConcurrencyTestStress extends JUnitSuite { latch3.await assert(num == (client1 ? "getCorrelationIdCount").as[Int].get) assert(num == (client2 ? "getCorrelationIdCount").as[Int].get) - assert(num == (client3 ? "getCorrelationIdCount").as[Int].get) + assert(num == (client3 ? "getCorrelationIdCount").as[Int].get)*/ } } @@ -50,9 +52,9 @@ object HttpConcurrencyTestStress { val workers = for (i <- 1 to 8) yield actorOf[HttpServerWorker].start val balancer = loadBalancerActor(new CyclicIterator(workers.toList)) - service.get.awaitEndpointActivation(1) { - actorOf(new HttpServerActor(balancer)).start - } + //service.get.awaitEndpointActivation(1) { + // actorOf(new HttpServerActor(balancer)).start + //} } @AfterClass diff --git a/akka-samples/akka-sample-camel/src/test/scala/sample/camel/RemoteConsumerTest.scala b/akka-samples/akka-sample-camel/src/test/scala/sample/camel/RemoteConsumerTest.scala index 31586311fa..92b204ec53 100644 --- a/akka-samples/akka-sample-camel/src/test/scala/sample/camel/RemoteConsumerTest.scala +++ b/akka-samples/akka-sample-camel/src/test/scala/sample/camel/RemoteConsumerTest.scala @@ -5,8 +5,8 @@ import org.scalatest.{GivenWhenThen, BeforeAndAfterAll, FeatureSpec} import akka.actor.Actor._ import akka.actor._ import akka.camel._ -import akka.remote.netty.NettyRemoteSupport -import akka.remoteinterface.RemoteServerModule +//import akka.remote.netty.NettyRemoteSupport +//import akka.remoteinterface.RemoteServerModule /** * @author Martin Krasser diff --git a/akka-sbt-plugin/build.sbt b/akka-sbt-plugin/build.sbt index e01a2e9809..b7fa421544 100644 --- a/akka-sbt-plugin/build.sbt +++ b/akka-sbt-plugin/build.sbt @@ -9,6 +9,3 @@ version := "2.0-SNAPSHOT" publishMavenStyle := true -publishTo := Some("Typesafe Publish Repo" at "http://repo.typesafe.com/typesafe/maven-releases/") - -credentials += Credentials(Path.userHome / ".ivy2" / "typesafe-credentials") diff --git a/akka-sbt-plugin/src/main/scala/AkkaKernelPlugin.scala b/akka-sbt-plugin/src/main/scala/AkkaKernelPlugin.scala index 65f24ae9d8..03f431ee6d 100644 --- a/akka-sbt-plugin/src/main/scala/AkkaKernelPlugin.scala +++ b/akka-sbt-plugin/src/main/scala/AkkaKernelPlugin.scala @@ -4,26 +4,40 @@ import sbt._ import sbt.Keys._ +import sbt.Load.BuildStructure import sbt.classpath.ClasspathUtilities import sbt.Project.Initialize +import sbt.CommandSupport._ import java.io.File +import scala.collection.mutable.{ Set => MutableSet } object AkkaMicrokernelPlugin extends Plugin { + case class DistConfig( + outputDirectory: File, + configSourceDirs: Seq[File], + distJvmOptions: String, + distMainClass: String, + libFilter: File ⇒ Boolean, + additionalLibs: Seq[File]) + val Dist = config("dist") extend (Runtime) val dist = TaskKey[File]("dist", "Builds an Akka microkernel directory") - // TODO how to reuse keyword "clean" here instead (dist:clean) - val distClean = TaskKey[File]("clean-dist", "Removes Akka microkernel directory") + val distClean = TaskKey[Unit]("clean", "Removes Akka microkernel directory") + val outputDirectory = SettingKey[File]("output-directory") val configSourceDirs = TaskKey[Seq[File]]("config-source-directories", "Configuration files are copied from these directories") - val distJvmOptions = SettingKey[String]("jvm-options", "JVM parameters to use in start script") + val distJvmOptions = SettingKey[String]("kernel-jvm-options", "JVM parameters to use in start script") val distMainClass = SettingKey[String]("kernel-main-class", "Kernel main class to use in start script") val libFilter = SettingKey[File ⇒ Boolean]("lib-filter", "Filter of dependency jar files") val additionalLibs = TaskKey[Seq[File]]("additional-libs", "Additional dependency jar files") + val distConfig = TaskKey[DistConfig]("dist-config") + val distNeedsPackageBin = dist <<= dist.dependsOn(packageBin in Compile) + override lazy val settings = inConfig(Dist)(Seq( dist <<= packageBin.identity, @@ -36,46 +50,59 @@ object AkkaMicrokernelPlugin extends Plugin { distJvmOptions := "-Xms1024M -Xmx1024M -Xss1M -XX:MaxPermSize=256M -XX:+UseParallelGC", distMainClass := "akka.kernel.Main", libFilter := { f ⇒ true }, - additionalLibs <<= defaultAdditionalLibs)) ++ + additionalLibs <<= defaultAdditionalLibs, + distConfig <<= (outputDirectory, configSourceDirs, distJvmOptions, distMainClass, libFilter, additionalLibs) map DistConfig)) ++ Seq( - dist <<= (dist in Dist).identity) + dist <<= (dist in Dist).identity, distNeedsPackageBin) private def distTask: Initialize[Task[File]] = - (outputDirectory, sourceDirectory, crossTarget, dependencyClasspath, - configSourceDirs, distJvmOptions, distMainClass, libFilter, streams) map { - (outDir, src, tgt, cp, configSrc, jvmOptions, mainClass, libFilt, s) ⇒ - val log = s.log - val distBinPath = outDir / "bin" - val distConfigPath = outDir / "config" - val distDeployPath = outDir / "deploy" - val distLibPath = outDir / "lib" - // TODO how do I grab the additionalLibs setting? Can't add it in input tuple, limitation of number of elements in map of tuple. - val addLibs = Seq.empty[File] - - log.info("Creating distribution %s ..." format outDir) - IO.createDirectory(outDir) - Scripts(jvmOptions, mainClass).writeScripts(distBinPath) - copyDirectories(configSrc, distConfigPath) + (distConfig, sourceDirectory, crossTarget, dependencyClasspath, projectDependencies, allDependencies, buildStructure, state) map { + (conf, src, tgt, cp, projDeps, allDeps, buildStruct, st) ⇒ + + if (isKernelProject(allDeps)) { + val log = logger(st) + val distBinPath = conf.outputDirectory / "bin" + val distConfigPath = conf.outputDirectory / "config" + val distDeployPath = conf.outputDirectory / "deploy" + val distLibPath = conf.outputDirectory / "lib" + + val subProjectDependencies: Set[SubProjectInfo] = allSubProjectDependencies(projDeps, buildStruct, st) + + log.info("Creating distribution %s ..." format conf.outputDirectory) + IO.createDirectory(conf.outputDirectory) + Scripts(conf.distJvmOptions, conf.distMainClass).writeScripts(distBinPath) + copyDirectories(conf.configSourceDirs, distConfigPath) copyJars(tgt, distDeployPath) - copyFiles(libFiles(cp, libFilt), distLibPath) - copyFiles(addLibs, distLibPath) + + copyFiles(libFiles(cp, conf.libFilter), distLibPath) + copyFiles(conf.additionalLibs, distLibPath) + for (subTarget <- subProjectDependencies.map(_.target)) { + copyJars(subTarget, distLibPath) + } log.info("Distribution created.") - outDir } - - private def distCleanTask: Initialize[Task[File]] = - (outputDirectory, streams) map { (outDir, s) ⇒ - val log = s.log - log.info("Cleaning " + outDir) - IO.delete(outDir) - outDir + conf.outputDirectory } - def defaultConfigSourceDirs = (sourceDirectory, unmanagedResourceDirectories) map { (src, resources) ⇒ - Seq(src / "main" / "config") ++ resources + private def distCleanTask: Initialize[Task[Unit]] = + (outputDirectory, allDependencies, streams) map { (outDir, deps, s) ⇒ + + if (isKernelProject(deps)) { + val log = s.log + log.info("Cleaning " + outDir) + IO.delete(outDir) + } + } + + def isKernelProject(dependencies: Seq[ModuleID]): Boolean = { + dependencies.exists(moduleId => moduleId.organization == "se.scalablesolutions.akka" && moduleId.name == "akka-kernel") + } + + private def defaultConfigSourceDirs = (sourceDirectory, unmanagedResourceDirectories) map { (src, resources) ⇒ + Seq(src / "config", src / "main" / "config") ++ resources } - def defaultAdditionalLibs = (libraryDependencies) map { (libs) ⇒ + private def defaultAdditionalLibs = (libraryDependencies) map { (libs) ⇒ Seq.empty[File] } @@ -146,6 +173,67 @@ object AkkaMicrokernelPlugin extends Plugin { val (libs, directories) = classpath.map(_.data).partition(ClasspathUtilities.isArchive) libs.map(_.asFile).filter(libFilter) } + + private def allSubProjectDependencies(projDeps: Seq[ModuleID], buildStruct: BuildStructure, state: State): Set[SubProjectInfo] = { + val buildUnit = buildStruct.units(buildStruct.root) + val uri = buildStruct.root + val allProjects = buildUnit.defined.map { + case (id, proj) => (ProjectRef(uri, id) -> proj) + } + + val projDepsNames = projDeps.map(_.name) + def include(project: ResolvedProject): Boolean = projDepsNames.exists(_ == project.id) + val subProjects: Seq[SubProjectInfo] = allProjects.collect { + case (projRef, project) if include(project) => projectInfo(projRef, project, buildStruct, state, allProjects) + }.toList + + val allSubProjects = subProjects.map(_.recursiveSubProjects).flatten.toSet + allSubProjects +} + + private def projectInfo(projectRef: ProjectRef, project: ResolvedProject, buildStruct: BuildStructure, state: State, + allProjects: Map[ProjectRef, ResolvedProject]): SubProjectInfo = { + + def optionalSetting[A](key: ScopedSetting[A]) = key in projectRef get buildStruct.data + + def setting[A](key: ScopedSetting[A], errorMessage: => String) = { + optionalSetting(key) getOrElse { + logger(state).error(errorMessage); + throw new IllegalArgumentException() + } + } + + def evaluateTask[T](taskKey: sbt.Project.ScopedKey[sbt.Task[T]]) = { + EvaluateTask.evaluateTask(buildStruct, taskKey, state, projectRef, false, EvaluateTask.SystemProcessors) + } + + val projDeps: Seq[ModuleID] = evaluateTask(Keys.projectDependencies) match { + case Some(Value(moduleIds)) => moduleIds + case _ => Seq.empty + } + + val projDepsNames = projDeps.map(_.name) + def include(project: ResolvedProject): Boolean = projDepsNames.exists(_ == project.id) + val subProjects = allProjects.collect { + case (projRef, proj) if include(proj) => projectInfo(projRef, proj, buildStruct, state, allProjects) + }.toList + + val target = setting(Keys.crossTarget, "Missing crossTarget directory") + SubProjectInfo(project.id, target, subProjects) + } + + private case class SubProjectInfo(id: String, target: File, subProjects: Seq[SubProjectInfo]) { + + def recursiveSubProjects: Set[SubProjectInfo] = { + val flatSubProjects = for { + x <- subProjects + y <- x.recursiveSubProjects + } yield y + + flatSubProjects.toSet + this + } + + } } diff --git a/akka-spring/src/test/java/akka/spring/RemoteTypedActorOneImpl.java b/akka-spring/src/test/java/akka/spring/RemoteTypedActorOneImpl.java index c7ad0f3de6..f6ab0e56e7 100644 --- a/akka-spring/src/test/java/akka/spring/RemoteTypedActorOneImpl.java +++ b/akka-spring/src/test/java/akka/spring/RemoteTypedActorOneImpl.java @@ -2,6 +2,8 @@ package akka.spring; import akka.actor.*; +import scala.Option; + import java.util.concurrent.CountDownLatch; public class RemoteTypedActorOneImpl extends TypedActor implements RemoteTypedActorOne { @@ -22,7 +24,7 @@ public class RemoteTypedActorOneImpl extends TypedActor implements RemoteTypedAc } @Override - public void preRestart(Throwable e) { + public void preRestart(Throwable e, Option msg) { try { RemoteTypedActorLog.messageLog().put(e.getMessage()); } catch(Exception ex) {} latch.countDown(); } diff --git a/akka-spring/src/test/java/akka/spring/RemoteTypedActorTwoImpl.java b/akka-spring/src/test/java/akka/spring/RemoteTypedActorTwoImpl.java index 1479db0d8c..2ee07195e2 100644 --- a/akka-spring/src/test/java/akka/spring/RemoteTypedActorTwoImpl.java +++ b/akka-spring/src/test/java/akka/spring/RemoteTypedActorTwoImpl.java @@ -2,6 +2,8 @@ package akka.spring; import akka.actor.*; +import scala.Option; + import java.util.concurrent.CountDownLatch; public class RemoteTypedActorTwoImpl extends TypedActor implements RemoteTypedActorTwo { @@ -22,7 +24,7 @@ public class RemoteTypedActorTwoImpl extends TypedActor implements RemoteTypedAc } @Override - public void preRestart(Throwable e) { + public void preRestart(Throwable e, Option msg) { try { RemoteTypedActorLog.messageLog().put(e.getMessage()); } catch(Exception ex) {} latch.countDown(); } diff --git a/akka-spring/src/test/java/akka/spring/foo/PongActor.java b/akka-spring/src/test/java/akka/spring/foo/PongActor.java index d4f19078a6..575327572a 100644 --- a/akka-spring/src/test/java/akka/spring/foo/PongActor.java +++ b/akka-spring/src/test/java/akka/spring/foo/PongActor.java @@ -10,7 +10,7 @@ public class PongActor extends UntypedActor { public void onReceive(Object message) throws Exception { if (message instanceof String) { System.out.println("Pongeceived String message: " + message); - getContext().replyUnsafe(message + " from " + getContext().getUuid()); + getContext().reply(message + " from " + getContext().getUuid()); } else { throw new IllegalArgumentException("Unknown message: " + message); } diff --git a/akka-stm/src/main/scala/akka/agent/Agent.scala b/akka-stm/src/main/scala/akka/agent/Agent.scala index fb525cc376..dca421c941 100644 --- a/akka-stm/src/main/scala/akka/agent/Agent.scala +++ b/akka-stm/src/main/scala/akka/agent/Agent.scala @@ -282,8 +282,8 @@ class AgentUpdater[T](agent: Agent[T]) extends Actor { val txFactory = TransactionFactory(familyName = "AgentUpdater", readonly = false) def receive = { - case update: Update[T] ⇒ - self.reply_?(atomic(txFactory) { agent.ref alter update.function }) + case update: Update[_] ⇒ + self.tryReply(atomic(txFactory) { agent.ref alter update.function.asInstanceOf[T => T] }) case Get ⇒ self reply agent.get case _ ⇒ () } @@ -298,8 +298,8 @@ class ThreadBasedAgentUpdater[T](agent: Agent[T]) extends Actor { val txFactory = TransactionFactory(familyName = "ThreadBasedAgentUpdater", readonly = false) def receive = { - case update: Update[T] ⇒ try { - self.reply_?(atomic(txFactory) { agent.ref alter update.function }) + case update: Update[_] ⇒ try { + self.tryReply(atomic(txFactory) { agent.ref alter update.function.asInstanceOf[T => T] }) } finally { agent.resume self.stop() diff --git a/akka-stm/src/main/scala/akka/transactor/Coordinated.scala b/akka-stm/src/main/scala/akka/transactor/Coordinated.scala index a43cf39786..bd50926d72 100644 --- a/akka-stm/src/main/scala/akka/transactor/Coordinated.scala +++ b/akka-stm/src/main/scala/akka/transactor/Coordinated.scala @@ -17,7 +17,9 @@ import org.multiverse.api.exceptions.ControlFlowError /** * Akka-specific exception for coordinated transactions. */ -class CoordinatedTransactionException(message: String, cause: Throwable = null) extends AkkaException(message, cause) +class CoordinatedTransactionException(message: String, cause: Throwable = null) extends AkkaException(message, cause){ + def this(msg:String) = this(msg, null); +} /** * Coordinated transactions across actors. diff --git a/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedCounter.java b/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedCounter.java index 0fd24ac9a7..167db3a3c4 100644 --- a/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedCounter.java +++ b/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedCounter.java @@ -32,7 +32,7 @@ public class UntypedCoordinatedCounter extends UntypedActor { } else if (incoming instanceof String) { String message = (String) incoming; if (message.equals("GetCount")) { - getContext().replyUnsafe(count.get()); + getContext().reply(count.get()); } } } diff --git a/akka-stm/src/test/java/akka/transactor/example/UntypedCounter.java b/akka-stm/src/test/java/akka/transactor/example/UntypedCounter.java index 56cc12f6c2..54f03eb809 100644 --- a/akka-stm/src/test/java/akka/transactor/example/UntypedCounter.java +++ b/akka-stm/src/test/java/akka/transactor/example/UntypedCounter.java @@ -26,7 +26,7 @@ public class UntypedCounter extends UntypedTransactor { @Override public boolean normally(Object message) { if ("GetCount".equals(message)) { - getContext().replyUnsafe(count.get()); + getContext().reply(count.get()); return true; } else return false; } diff --git a/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedCounter.java b/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedCounter.java index 3fc2ca502b..24a720dba8 100644 --- a/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedCounter.java +++ b/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedCounter.java @@ -57,7 +57,7 @@ public class UntypedCoordinatedCounter extends UntypedActor { } else if (incoming instanceof String) { String message = (String) incoming; if (message.equals("GetCount")) { - getContext().replyUnsafe(count.get()); + getContext().reply(count.get()); } } } diff --git a/akka-stm/src/test/java/akka/transactor/test/UntypedCounter.java b/akka-stm/src/test/java/akka/transactor/test/UntypedCounter.java index 325b06ba73..65923e7f4b 100644 --- a/akka-stm/src/test/java/akka/transactor/test/UntypedCounter.java +++ b/akka-stm/src/test/java/akka/transactor/test/UntypedCounter.java @@ -70,7 +70,7 @@ public class UntypedCounter extends UntypedTransactor { @Override public boolean normally(Object message) { if ("GetCount".equals(message)) { - getContext().replyUnsafe(count.get()); + getContext().reply(count.get()); return true; } else return false; } diff --git a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala index 9128afab7f..0f0344cc49 100644 --- a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala +++ b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala @@ -32,7 +32,7 @@ import scala.annotation.tailrec object CallingThreadDispatcher { - lazy val global = new CallingThreadDispatcher + lazy val global = new CallingThreadDispatcher("global-calling-thread") // PRIVATE DATA @@ -104,7 +104,7 @@ object CallingThreadDispatcher { * @author Roland Kuhn * @since 1.1 */ -class CallingThreadDispatcher(val warnings: Boolean = true) extends MessageDispatcher { +class CallingThreadDispatcher(val name: String = "calling-thread", val warnings: Boolean = true) extends MessageDispatcher { import CallingThreadDispatcher._ private[akka] override def createMailbox(actor: ActorRef) = new CallingThreadMailbox diff --git a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala index 6920faea64..116450665a 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala @@ -70,12 +70,12 @@ object TestActorRef { import ReflectiveAccess.{ createInstance, noParams, noArgs } createInstance[T](manifest[T].erasure, noParams, noArgs) match { - case r: Right[_, T] ⇒ r.b - case l: Left[Exception, _] ⇒ throw new ActorInitializationException( + case Right(value) ⇒ value + case Left(exception) ⇒ throw new ActorInitializationException( "Could not instantiate Actor" + "\nMake sure Actor is NOT defined inside a class/trait," + "\nif so put it outside the class/trait, f.e. in a companion object," + - "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.", l.a) + "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.", exception) } }, address) } diff --git a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala index e7c23d30d7..1dd1498ad2 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala @@ -110,13 +110,12 @@ trait TestKitLight { val senderOption = Some(testActor) private var end: Duration = Duration.Inf - /* - * THIS IS A HACK: expectNoMsg and receiveWhile are bounded by `end`, but - * running them should not trigger an AssertionError, so mark their end - * time here and do not fail at the end of `within` if that time is not - * long gone. + + /** + * if last assertion was expectNoMsg, disable timing failure upon within() + * block end. */ - private var lastSoftTimeout: Duration = now - 5.millis + private var lastWasNoMsg = false /** * Stop test actor. Should be done at the end of the test unless relying on @@ -211,6 +210,8 @@ trait TestKitLight { val rem = end - start assert(rem >= min, "required min time " + min + " not possible, only " + format(min.unit, rem) + " left") + lastWasNoMsg = false + val max_diff = _max min rem val prev_end = end end = start + max_diff @@ -219,13 +220,8 @@ trait TestKitLight { val diff = now - start assert(min <= diff, "block took " + format(min.unit, diff) + ", should at least have been " + min) - /* - * caution: HACK AHEAD - */ - if (now - lastSoftTimeout > 5.millis) { + if (!lastWasNoMsg) { assert(diff <= max_diff, "block took " + format(_max.unit, diff) + ", exceeding " + format(_max.unit, max_diff)) - } else { - lastSoftTimeout -= 5.millis } ret @@ -302,6 +298,20 @@ trait TestKitLight { f(o) } + /** + * Same as `expectMsgType[T](remaining)`, but correctly treating the timeFactor. + */ + def expectMsgType[T](implicit m: Manifest[T]): T = expectMsgClass_internal(remaining, m.erasure.asInstanceOf[Class[T]]) + + /** + * Receive one message from the test actor and assert that it conforms to the + * given type (after erasure). Wait time is bounded by the given duration, + * with an AssertionFailure being thrown in case of timeout. + * + * @return the received object + */ + def expectMsgType[T](max: Duration)(implicit m: Manifest[T]): T = expectMsgClass_internal(max.dilated, m.erasure.asInstanceOf[Class[T]]) + /** * Same as `expectMsgClass(remaining, c)`, but correctly treating the timeFactor. */ @@ -378,11 +388,9 @@ trait TestKitLight { * given duration, with an AssertionFailure being thrown in case of timeout. * *
-   * within(1 second) {
    *   dispatcher ! SomeWork1()
    *   dispatcher ! SomeWork2()
-   *   expectMsgAllOf(Result1(), Result2())
-   * }
+   *   expectMsgAllOf(1 second, Result1(), Result2())
    * 
*/ def expectMsgAllOf[T](max: Duration, obj: T*): Seq[T] = expectMsgAllOf_internal(max.dilated, obj: _*) @@ -454,7 +462,7 @@ trait TestKitLight { private def expectNoMsg_internal(max: Duration) { val o = receiveOne(max) assert(o eq null, "received unexpected message " + o) - lastSoftTimeout = now + lastWasNoMsg = true } /** @@ -503,7 +511,7 @@ trait TestKitLight { } val ret = doit(Nil) - lastSoftTimeout = now + lastWasNoMsg = true ret } @@ -543,6 +551,7 @@ trait TestKitLight { } else { queue.takeFirst } + lastWasNoMsg = false message match { case null ⇒ lastMessage = NullMessage diff --git a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala index 93264524c2..64c729f5dc 100644 --- a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala @@ -174,7 +174,7 @@ class TestActorRefSpec extends WordSpec with MustMatchers with BeforeAndAfterEac self.faultHandler = OneForOneStrategy(List(classOf[Throwable]), Some(2), Some(1000)) val ref = TestActorRef(new TActor { def receiveT = { case _ ⇒ } - override def preRestart(reason: Throwable) { counter -= 1 } + override def preRestart(reason: Throwable, msg: Option[Any]) { counter -= 1 } override def postRestart(reason: Throwable) { counter -= 1 } }).start() self.dispatcher = CallingThreadDispatcher.global 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 fc950ff2ff..2bf9a3bc73 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 @@ -76,7 +76,7 @@ public class Pi { double result = calculatePiFor(work.getStart(), work.getNrOfElements()); // reply with the result - getContext().replyUnsafe(new Result(result)); + getContext().reply(new Result(result)); } else throw new IllegalArgumentException("Unknown message [" + message + "]"); } diff --git a/akka-tutorials/akka-tutorial-second/src/main/java/akka/tutorial/java/second/Pi.java b/akka-tutorials/akka-tutorial-second/src/main/java/akka/tutorial/java/second/Pi.java index baa75fc501..91c7829278 100644 --- a/akka-tutorials/akka-tutorial-second/src/main/java/akka/tutorial/java/second/Pi.java +++ b/akka-tutorials/akka-tutorial-second/src/main/java/akka/tutorial/java/second/Pi.java @@ -73,7 +73,7 @@ public class Pi { public void onReceive(Object message) { if (message instanceof Work) { Work work = (Work) message; - getContext().replyUnsafe(new Result(calculatePiFor(work.getArg(), work.getNrOfElements()))); // perform the work + getContext().reply(new Result(calculatePiFor(work.getArg(), work.getNrOfElements()))); // perform the work } else throw new IllegalArgumentException("Unknown message [" + message + "]"); } } diff --git a/config/akka-reference.conf b/config/akka-reference.conf index eb8b8262c0..218a824220 100644 --- a/config/akka-reference.conf +++ b/config/akka-reference.conf @@ -55,7 +55,7 @@ akka { # available: "host:", "ip:" and "node:" # default is "host:localhost" - replicas = 3 # number of actor instances in the cluster + replication-factor = 3 # number of actor instances in the cluster # available: positive integer (0-N) or the string "auto" for auto-scaling # if "auto" is used then 'home' has no meaning # default is '0', meaning no replicas; diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index 3d20f8b3b9..2b4e3859fb 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -1,6 +1,8 @@ import sbt._ import Keys._ import MultiJvmPlugin.{ MultiJvm, extraOptions } +import com.github.oforero.sbtformatter.SbtFormatter._ +import com.github.oforero.sbtformatter.SbtFormatterSettings._ object AkkaBuild extends Build { System.setProperty("akka.mode", "test") // Is there better place for this? @@ -202,7 +204,7 @@ object AkkaBuild extends Build { id = "akka-samples", base = file("akka-samples"), settings = parentSettings, - aggregate = Seq(fsmSample) + aggregate = Seq(fsmSample, camelSample) ) // lazy val antsSample = Project( @@ -226,6 +228,16 @@ object AkkaBuild extends Build { settings = defaultSettings ) + lazy val camelSample = Project( + id = "akka-sample-camel", + base = file("akka-samples/akka-sample-camel"), + dependencies = Seq(actor, camelTyped), + settings = defaultSettings ++ Seq( + ivyXML := Dependencies.sampleCamelXML, + libraryDependencies ++= Dependencies.sampleCamel + ) + ) + // lazy val helloSample = Project( // id = "akka-sample-hello", // base = file("akka-samples/akka-sample-hello"), @@ -265,7 +277,7 @@ object AkkaBuild extends Build { override lazy val settings = super.settings ++ buildSettings ++ Publish.versionSettings - lazy val baseSettings = Defaults.defaultSettings ++ Publish.settings + lazy val baseSettings = Defaults.defaultSettings ++ Publish.settings ++ formatterPreferences ++ formatterTasks lazy val parentSettings = baseSettings ++ Seq( publishArtifact in Compile := false @@ -358,6 +370,23 @@ object Dependencies { jettyUtil, jettyXml, jettyServlet, jerseyCore, jerseyJson, jerseyScala, jacksonCore, staxApi, Provided.jerseyServer ) + + val sampleCamel = Seq(camelCore, commonsCodec, Runtime.activemq, Runtime.springJms, + Test.junit, Test.scalatest, Test.logback) + + val sampleCamelXML = + + + + + + + + + + } object Dependency { @@ -428,7 +457,11 @@ object Dependency { // Runtime object Runtime { - val logback = "ch.qos.logback" % "logback-classic" % V.Logback % "runtime" // MIT + val activemq = "org.apache.activemq" % "activemq-core" % "5.4.2" % "runtime" // ApacheV2 + val camelJetty = "org.apache.camel" % "camel-jetty" % V.CamelPatch % "runtime" // ApacheV2 + val camelJms = "org.apache.camel" % "camel-jms" % V.Camel % "runtime" // ApacheV2 + val logback = "ch.qos.logback" % "logback-classic" % V.Logback % "runtime" // MIT + val springJms = "org.springframework" % "spring-jms" % V.Spring % "runtime" // ApacheV2 } // Test diff --git a/project/plugins/project/BuildPlugin.scala b/project/plugins/project/BuildPlugin.scala new file mode 100644 index 0000000000..3648f1e926 --- /dev/null +++ b/project/plugins/project/BuildPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object PluginDef extends Build { + + lazy val formatterPlugin = ProjectRef( uri("git://github.com/viktorklang/sbt-cool-plugins.git"), "Formatter") + + lazy override val projects = Seq(root) + + lazy val root = Project("plugins", file(".")) dependsOn (formatterPlugin) +} \ No newline at end of file