From 03ae6100cd9a92b21b80a10d8890a4da244dd45a Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Sun, 27 Mar 2011 10:25:01 +0200 Subject: [PATCH 001/147] document CTD adaption of ActorModelSpec --- .../testkit/CallingThreadDispatcherModelSpec.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/akka-testkit/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala b/akka-testkit/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala index 22e16abdd9..8b52fb3fc4 100644 --- a/akka-testkit/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala @@ -7,8 +7,15 @@ class CallingThreadDispatcherModelSpec extends ActorModelSpec { import ActorModelSpec._ def newInterceptedDispatcher = new CallingThreadDispatcher with MessageDispatcherInterceptor + // A CallingThreadDispatcher can by design not process messages in parallel, + // so disable this test override def dispatcherShouldProcessMessagesInParallel {} + // This test needs to be adapted: CTD runs the flood completely sequentially + // with start, invocation, stop, schedule shutdown, abort shutdown, repeat; + // add "keeper" actor to lock down the dispatcher instance, since the + // frequent attempted shutdown seems rather costly (random timing failures + // without this fix) override def dispatcherShouldHandleWavesOfActors { implicit val dispatcher = newInterceptedDispatcher @@ -27,6 +34,7 @@ class CallingThreadDispatcherModelSpec extends ActorModelSpec { assertDispatcher(dispatcher)(starts = run, stops = run) } } + } -// vim: set ts=4 sw=4 et: +// vim: set ts=2 sw=2 et: From b169f356434696532dc82ea43c2289153b40f9dd Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Sun, 27 Mar 2011 18:15:40 +0200 Subject: [PATCH 002/147] fix error handling in TestKit.within restore outer deadline in case the code block does throw an exception. This is done in order to reduce the number of follow-on test failures. --- akka-testkit/src/main/scala/akka/testkit/TestKit.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala index a2d26ac4a8..5ce90509ba 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala @@ -157,7 +157,7 @@ trait TestKit { val prev_end = end end = start + max_diff - val ret = f + val ret = try f finally end = prev_end val diff = now - start assert (min <= diff, "block took "+format(min.unit, diff)+", should at least have been "+min) @@ -170,7 +170,6 @@ trait TestKit { lastSoftTimeout -= 5.millis } - end = prev_end ret } From 3d04acf314122cd9120e087cffcaf6dd4848a244 Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 4 Apr 2011 15:21:55 +0200 Subject: [PATCH 003/147] make onTransition easier to use - now takes PartialFunction, which allows nice { case ... } literals without default case - now stacks new after old applications, so you can put your transition handling with the states if you want to - add -> extractor for writing nicely: { case Old -> New => ... } --- .../src/main/scala/akka/actor/FSM.scala | 19 +++++++++++++++---- .../scala/akka/actor/actor/FSMActorSpec.scala | 6 ++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/FSM.scala b/akka-actor/src/main/scala/akka/actor/FSM.scala index 046685f22d..5b9c472c90 100644 --- a/akka-actor/src/main/scala/akka/actor/FSM.scala +++ b/akka-actor/src/main/scala/akka/actor/FSM.scala @@ -42,6 +42,14 @@ object FSM { } } + /* + * This extractor is just convenience for matching a (S, S) pair, including a + * reminder what the new state is. + */ + object -> { + def unapply[S](in : (S, S)) = Some(in) + } + /* * With these implicits in scope, you can write "5 seconds" anywhere a * Duration or Option[Duration] is expected. This is conveniently true @@ -119,7 +127,7 @@ trait FSM[S, D] { type StateFunction = scala.PartialFunction[Event[D], State] type Timeout = Option[Duration] - type TransitionHandler = (S, S) => Unit + type TransitionHandler = PartialFunction[(S, S), Unit] /* DSL */ @@ -242,7 +250,7 @@ trait FSM[S, D] { * staying in the same state. */ protected final def onTransition(transitionHandler: TransitionHandler) = { - transitionEvent = transitionHandler + transitionEvent :+= transitionHandler } /** @@ -300,7 +308,10 @@ trait FSM[S, D] { case StopEvent(reason, _, _) => } - private var transitionEvent: TransitionHandler = (from, to) => { + private var transitionEvent: List[TransitionHandler] = Nil + private def handleTransition(prev : S, next : S) { + val tuple = (prev, next) + for (te <- transitionEvent) { if (te.isDefinedAt(tuple)) te(tuple) } } override final protected def receive: Receive = { @@ -351,7 +362,7 @@ trait FSM[S, D] { terminate(Failure("Next state %s does not exist".format(nextState.stateName))) } else { if (currentState.stateName != nextState.stateName) { - transitionEvent.apply(currentState.stateName, nextState.stateName) + handleTransition(currentState.stateName, nextState.stateName) if (!transitionCallBackList.isEmpty) { val transition = Transition(self, currentState.stateName, nextState.stateName) transitionCallBackList.foreach(_ ! transition) diff --git a/akka-actor/src/test/scala/akka/actor/actor/FSMActorSpec.scala b/akka-actor/src/test/scala/akka/actor/actor/FSMActorSpec.scala index 91e6f92873..42510aaad4 100644 --- a/akka-actor/src/test/scala/akka/actor/actor/FSMActorSpec.scala +++ b/akka-actor/src/test/scala/akka/actor/actor/FSMActorSpec.scala @@ -64,10 +64,8 @@ object FSMActorSpec { } } - onTransition(transitionHandler) - - def transitionHandler(from: LockState, to: LockState) = { - if (from == Locked && to == Open) transitionLatch.open + onTransition { + case Locked -> Open => transitionLatch.open } onTermination { From 42b72f0c4b8807b9dc546f62eef571cee5f69af3 Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Thu, 7 Apr 2011 10:52:14 +0200 Subject: [PATCH 004/147] add documentation and compat implicit to FSM --- .../src/main/scala/akka/actor/FSM.scala | 52 ++++++++++++++++++- .../scala/akka/actor/actor/FSMActorSpec.scala | 7 +++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/FSM.scala b/akka-actor/src/main/scala/akka/actor/FSM.scala index 5b9c472c90..f0e22909d5 100644 --- a/akka-actor/src/main/scala/akka/actor/FSM.scala +++ b/akka-actor/src/main/scala/akka/actor/FSM.scala @@ -98,6 +98,23 @@ object FSM { * Each of the above also supports the method replying(AnyRef) for * sending a reply before changing state. * + * While changing state, custom handlers may be invoked which are registered + * using onTransition. This is meant to enable concentrating + * different concerns in different places; you may choose to use + * when for describing the properties of a state, including of + * course initiating transitions, but you can describe the transitions using + * onTransision to avoid having to duplicate that code among + * multiple paths which lead to a transition: + * + *
+ * onTransition {
+ *   case Active -> _ => cancelTimer("activeTimer")
+ * }
+ * 
+ * + * Multiple such blocks are supported and all of them will be called, not only + * the first matching one. + * * Another feature is that other actors may subscribe for transition events by * sending a SubscribeTransitionCallback message to this actor; * use UnsubscribeTransitionCallback before stopping the other @@ -247,12 +264,43 @@ trait FSM[S, D] { /** * Set handler which is called upon each state transition, i.e. not when - * staying in the same state. + * staying in the same state. This may use the pair extractor defined in the + * FSM companion object like so: + * + *
+   * onTransition {
+   *   case Old -> New => doSomething
+   * }
+   * 
+ * + * It is also possible to supply a 2-ary function object: + * + *
+   * onTransition(handler _)
+   *
+   * private def handler(from: S, to: S) { ... }
+   * 
+ * + * The underscore is unfortunately necessary to enable the nicer syntax shown + * above (it uses the implicit conversion total2pf under the hood). + * + * Multiple handlers may be installed, and every one of them will be + * called, not only the first one matching. */ - protected final def onTransition(transitionHandler: TransitionHandler) = { + protected final def onTransition(transitionHandler: TransitionHandler) { transitionEvent :+= transitionHandler } + /** + * Convenience wrapper for using a total function instead of a partial + * function literal. To be used with onTransition. + */ + implicit protected final def total2pf(transitionHandler: (S, S) => Unit) = + new PartialFunction[(S, S), Unit] { + def isDefinedAt(in : (S, S)) = true + def apply(in : (S, S)) { transitionHandler(in._1, in._2) } + } + /** * Set handler which is called upon termination of this FSM actor. */ diff --git a/akka-actor/src/test/scala/akka/actor/actor/FSMActorSpec.scala b/akka-actor/src/test/scala/akka/actor/actor/FSMActorSpec.scala index 42510aaad4..31d09c8ebf 100644 --- a/akka-actor/src/test/scala/akka/actor/actor/FSMActorSpec.scala +++ b/akka-actor/src/test/scala/akka/actor/actor/FSMActorSpec.scala @@ -68,6 +68,13 @@ object FSMActorSpec { case Locked -> Open => transitionLatch.open } + // verify that old-style does still compile + onTransition (transitionHandler _) + + def transitionHandler(from: LockState, to: LockState) = { + // dummy + } + onTermination { case StopEvent(Shutdown, Locked, _) => // stop is called from lockstate with shutdown as reason... From e8ee6b321a3e5ffff5615d9f37f802d19f4a4b2f Mon Sep 17 00:00:00 2001 From: patriknw Date: Thu, 7 Apr 2011 12:47:47 +0200 Subject: [PATCH 005/147] notify with call-by-name included again --- .../main/scala/akka/event/EventHandler.scala | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/akka-actor/src/main/scala/akka/event/EventHandler.scala b/akka-actor/src/main/scala/akka/event/EventHandler.scala index fe75a31dc0..176e441e5b 100644 --- a/akka-actor/src/main/scala/akka/event/EventHandler.scala +++ b/akka-actor/src/main/scala/akka/event/EventHandler.scala @@ -94,10 +94,15 @@ object EventHandler extends ListenerManagement { "Configuration option 'akka.event-handler-level' is invalid [" + unknown + "]") } - def notify(event: Any) { notifyListeners(event) } + def notify(event: Any) { + if (event.isInstanceOf[Event]) { + if (level >= event.asInstanceOf[Event].level) notifyListeners(event) + } else + notifyListeners(event) + } - def notify(event: Event) { - if (level >= event.level) notifyListeners(event) + def notify[T <: Event : ClassManifest](event: => T) { + if (level >= levelFor(classManifest[T].erasure.asInstanceOf[Class[_ <: Event]])) notifyListeners(event) } def error(cause: Throwable, instance: AnyRef, message: => String) { @@ -149,6 +154,14 @@ object EventHandler extends ListenerManagement { sw.toString } + private def levelFor(eventClass: Class[_ <: Event]) = { + if (eventClass.isInstanceOf[Error]) ErrorLevel + else if (eventClass.isInstanceOf[Warning]) WarningLevel + else if (eventClass.isInstanceOf[Info]) InfoLevel + else if (eventClass.isInstanceOf[Debug]) DebugLevel + else DebugLevel + } + class DefaultListener extends Actor { self.id = ID self.dispatcher = EventHandlerDispatcher From 96badb23ba0e6720735cff717a03d2841a90a4ce Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Thu, 7 Apr 2011 13:05:09 +0200 Subject: [PATCH 006/147] Deprecating two newRemoteInstance methods in TypedActor --- akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala index 813c26ab94..bb5d08dd57 100644 --- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala @@ -487,6 +487,7 @@ object TypedActor { * @param host hostanme of the remote server * @param port port of the remote server */ + @deprecated("Will be removed after 1.1") def newRemoteInstance[T](intfClass: Class[T], targetClass: Class[_], hostname: String, port: Int): T = { newInstance(intfClass, targetClass, TypedActorConfiguration(hostname, port)) } @@ -498,6 +499,7 @@ object TypedActor { * @param host hostanme of the remote server * @param port port of the remote server */ + @deprecated("Will be removed after 1.1") def newRemoteInstance[T](intfClass: Class[T], factory: => AnyRef, hostname: String, port: Int): T = { newInstance(intfClass, factory, TypedActorConfiguration(hostname, port)) } From 30c2bd2ccf12ed606f934a3890850a554695f05d Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Thu, 7 Apr 2011 14:10:13 +0200 Subject: [PATCH 007/147] Changing the complete* signature from : CompletableFuture to Future, since they can only be written once anyway --- akka-actor/src/main/scala/akka/dispatch/Future.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index cfe64a8992..3116c80811 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -508,26 +508,26 @@ trait CompletableFuture[T] extends Future[T] { * Completes this Future with the specified result, if not already completed. * @return this */ - def complete(value: Either[Throwable, T]): CompletableFuture[T] + def complete(value: Either[Throwable, T]): Future[T] /** * Completes this Future with the specified result, if not already completed. * @return this */ - final def completeWithResult(result: T): CompletableFuture[T] = complete(Right(result)) + final def completeWithResult(result: T): Future[T] = complete(Right(result)) /** * Completes this Future with the specified exception, if not already completed. * @return this */ - final def completeWithException(exception: Throwable): CompletableFuture[T] = complete(Left(exception)) + final def completeWithException(exception: Throwable): Future[T] = complete(Left(exception)) /** * Completes this Future with the specified other Future, when that Future is completed, * unless this Future has already been completed. * @return this. */ - final def completeWith(other: Future[T]): CompletableFuture[T] = { + final def completeWith(other: Future[T]): Future[T] = { other onComplete { f => complete(f.value.get) } this } @@ -535,12 +535,12 @@ trait CompletableFuture[T] extends Future[T] { /** * Alias for complete(Right(value)). */ - final def << (value: T): CompletableFuture[T] = complete(Right(value)) + final def << (value: T): Future[T] = complete(Right(value)) /** * Alias for completeWith(other). */ - final def << (other : Future[T]): CompletableFuture[T] = completeWith(other) + final def << (other : Future[T]): Future[T] = completeWith(other) } /** From 15bcaefdb34f439024efb23abd2382f0a516a673 Mon Sep 17 00:00:00 2001 From: patriknw Date: Thu, 7 Apr 2011 14:17:47 +0200 Subject: [PATCH 008/147] minor correction of typos --- akka-docs/manual/getting-started-first.rst | 24 ++++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst index f7683e285f..d0f1d78254 100755 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -26,12 +26,14 @@ In this particular algorithm the master splits the series into chunks which are Prerequisite ------------ -This tutorial assumes that you have Jave 1.6 or later installed on you machine and ``java`` on your ``PATH``. I also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a decent text editor or IDE to type in the Scala code in. +This tutorial assumes that you have Jave 1.6 or later installed on you machine and ``java`` on your ``PATH``. You also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a decent text editor or IDE to type in the Scala code in. Downloading and installing Akka ------------------------------- -The first thing we need to do is to download Akka. Let's get the 1.1 distribution from `http://akka.io/downloads `_. Once you have downloaded the distribution unzip it in the folder you would like to have Akka installed in, in my case I choose to install it in ``/Users/jboner/tools/``, simply by unzipping it to this directory. +If you want to be able to build and run the tutorial sample from the command line then you have to download Akka. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. + +Let's get the 1.1 distribution from `http://akka.io/downloads `_. Once you have downloaded the distribution unzip it in the folder you would like to have Akka installed in, in my case I choose to install it in ``/Users/jboner/tools/``, simply by unzipping it to this directory. You need to do one more thing in order to install Akka properly and that is to set the ``AKKA_HOME`` environment variable to the root of the distribution. In my case I'm opening up a shell and navigating down to the distribution and setting the ``AKKA_HOME`` variable:: @@ -58,7 +60,7 @@ If we now take a look at what we have in this distribution, looks like this:: - In the ``scripts`` directory we have scripts for running Akka. - Finallly the ``scala-library.jar`` is the JAR for the latest Scala distribution that Akka depends on. -The only JAR we will need for this tutorial (apart from the ``scala-library.jar`` JAR) is the ``akka-actor-1.1.jar`` JAR in the ``dist`` directory. This is a self-contained JAR with zero dependencies and contains the everything we need to write a system using Actors. +The only JAR we will need for this tutorial (apart from the ``scala-library.jar`` JAR) is the ``akka-actor-1.1.jar`` JAR in the ``dist`` directory. This is a self-contained JAR with zero dependencies and contains everything we need to write a system using Actors. Akka is very modular and has many JARs for containing different features. The core distribution has seven modules: @@ -83,7 +85,7 @@ We also have Akka Modules containing add-on modules for the core of Akka. You ca Downloading and installing Scala -------------------------------- -If you want to be able to build and run the tutorial sample from the command line then you have to install the Scala distribution. If you prefer to use SBT to build and run the sample then you need can skip this section and jump to the next one. +If you want to be able to build and run the tutorial sample from the command line then you have to install the Scala distribution. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0 final release. If you pick the ``tgz`` or ``zip`` distributions then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. @@ -182,13 +184,13 @@ As you can see we are using the ``actorOf`` factory method to create actors, thi import akka.actor.Actor._ -Now we have a routes are representing all our workers in a single abstraction. If you paid attention to the code above to see that we were using the ``nrOfWorkers`` variable. This variable and others we have to pass to the ``Master`` actor in its constructor. So now let's create the master actor. We had to pass in three integer variables needed: +Now we have a router that is representing all our workers in a single abstraction. If you paid attention to the code above to see that we were using the ``nrOfWorkers`` variable. This variable and others we have to pass to the ``Master`` actor in its constructor. So now let's create the master actor. We had to pass in three integer variables needed: - ``nrOfWorkers`` -- defining how many workers we should start up - ``nrOfMessages`` -- defining how many number chunks should send out to the workers - ``nrOfElements`` -- defining how big the number chunks sent to each worker should be -Let's not write the master actor:: +Let's now write the master actor:: class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) extends Actor { @@ -218,16 +220,16 @@ Let's not write the master actor:: Couple of things are worth explaining further. -First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing, to have a simple way letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch. +First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing, to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch. -Second, we are adding a couple of life-cycle callback methods; ``preStart`` and ``postStop``. In the ``preStart`` callback we are recording the time when the actor is started and in the ``postStop`` callback we are printing out the result (the approximation of Pi) and the time it took to calculate it. In this call but we also invoke ``latch.countDown`` to tell the outside world that we are done. +Second, we are adding a couple of life-cycle callback methods; ``preStart`` and ``postStop``. In the ``preStart`` callback we are recording the time when the actor is started and in the ``postStop`` callback we are printing out the result (the approximation of Pi) and the time it took to calculate it. In this call we also invoke ``latch.countDown`` to tell the outside world that we are done. But we are not done yet. We are missing the message handler for the ``Master`` actor. This message handler needs to be able to react to two different messages: - ``Calculate`` -- which should start the calculation - ``Result`` -- which should aggregate the different results -The ``Calculate`` handler is sending out work to all the ``Worker`` actors and after doing that also sends a ``Broadcast(PoisonPill)`` message to the router, this will make the route or send out the ``PoisonPill`` message to all the actors in this representing (in our case all the ``Worker`` actors). The ``PoisonPill`` is a special kind of message that tells the receiver to shut himself down using the normal shutdown; ``self.stop``. Then we also send a ``PoisonPill`` to the router itself (since it's also an actor that we want to shut down). +The ``Calculate`` handler is sending out work to all the ``Worker`` actors and after doing that it also sends a ``Broadcast(PoisonPill)`` message to the router, which will send out the ``PoisonPill`` message to all the actors it is representing (in our case all the ``Worker`` actors). The ``PoisonPill`` is a special kind of message that tells the receiver to shut himself down using the normal shutdown; ``self.stop``. Then we also send a ``PoisonPill`` to the router itself (since it's also an actor that we want to shut down). The ``Result`` handler is simpler, here we just get the value from the ``Result`` message and aggregate it to our ``pi`` member variable. We also keep track of how many results we have received back and if it matches the number of tasks sent out the ``Master`` actor considers itself done and shuts himself down. @@ -255,7 +257,7 @@ Now, let's capture this in code:: Bootstrap the calculation ------------------------- -Now the only thing that is left to implement his the runner that should bootstrap and run his calculation for us. We do that by creating an object that we call ``Pi``, here we can extend the ``App`` trait in Scala which means that we will be able to run this as an application directly from the command line. The ``Pi`` object is a perfect container module for our actors and messages, so let's put them all there. We also create a method ``calculate`` in which we start up the ``Master`` actor and waits for it to finish:: +Now the only thing that is left to implement is the runner that should bootstrap and run his calculation for us. We do that by creating an object that we call ``Pi``, here we can extend the ``App`` trait in Scala which means that we will be able to run this as an application directly from the command line. The ``Pi`` object is a perfect container module for our actors and messages, so let's put them all there. We also create a method ``calculate`` in which we start up the ``Master`` actor and waits for it to finish:: object Pi extends App { @@ -452,6 +454,6 @@ Conclusion Now we have learned how to create our first Akka project utilizing Akka's actors to speed up a computation intensive problem by scaling out on multi-core processors (also known as scaling up). We have also learned how to compile and run an Akka project utilizing either the tools on the command line or the SBT build system. -Now we are ready to take on more advanced problems. In the next tutorial we will build upon this one, refactor it into more idiomatic Akka and Scala code and introduce a few new concepts and abstractions. Whenever you feel ready, join my in the `Getting Started Tutorial: Second Chapter `_. +Now we are ready to take on more advanced problems. In the next tutorial we will build upon this one, refactor it into more idiomatic Akka and Scala code and introduce a few new concepts and abstractions. Whenever you feel ready, join me in the `Getting Started Tutorial: Second Chapter `_. Happy hakking. From 3da35fb191c9a40dcf2eae2ccbe556332eba28ff Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Thu, 7 Apr 2011 22:37:36 +0200 Subject: [PATCH 009/147] reverted back to call-by-name parameter --- .../main/scala/akka/remoteinterface/RemoteInterface.scala | 4 ++-- akka-actor/src/main/scala/akka/util/ListenerManagement.scala | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index 29d8185ef2..e9e4168995 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -20,7 +20,7 @@ trait RemoteModule { val UUID_PREFIX = "uuid:".intern def optimizeLocalScoped_?(): Boolean //Apply optimizations for remote operations in local scope - protected[akka] def notifyListeners(message: Any): Unit + protected[akka] def notifyListeners(message: => Any): Unit private[akka] def actors: ConcurrentHashMap[String, ActorRef] private[akka] def actorsByUuid: ConcurrentHashMap[String, ActorRef] @@ -227,7 +227,7 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule } protected override def manageLifeCycleOfListeners = false - protected[akka] override def notifyListeners(message: Any): Unit = super.notifyListeners(message) + protected[akka] override def notifyListeners(message: => Any): Unit = super.notifyListeners(message) private[akka] val actors = new ConcurrentHashMap[String, ActorRef] private[akka] val actorsByUuid = new ConcurrentHashMap[String, ActorRef] diff --git a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala index 9417f954ee..916fac9c6a 100644 --- a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala +++ b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala @@ -50,12 +50,13 @@ trait ListenerManagement { */ def hasListener(listener: ActorRef): Boolean = listeners.contains(listener) - protected[akka] def notifyListeners(message: Any) { + protected[akka] def notifyListeners(message: => Any) { if (hasListeners) { + val msg = message val iterator = listeners.iterator while (iterator.hasNext) { val listener = iterator.next - if (listener.isRunning) listener ! message + if (listener.isRunning) listener ! msg } } } From 63357fe3a8b1f30d224a3180912704aa35cf2089 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Fri, 8 Apr 2011 16:40:41 +1200 Subject: [PATCH 010/147] Small fix to make tailrec pi the same as other implementations --- akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index d644518431..45b0ddd89a 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -73,7 +73,7 @@ object Pi extends App { def calculatePiFor(arg: Int, nrOfElements: Int): Double = { val end = (arg + 1) * nrOfElements - 1 @tailrec def doCalculatePiFor(cursor: Int, acc: Double): Double = { - if (end == cursor) acc + if (cursor > end) acc else doCalculatePiFor(cursor + 1, acc + (4 * math.pow(-1, cursor) / (2 * cursor + 1))) } doCalculatePiFor(arg * nrOfElements, 0.0D) From 07e428cc34dbe1dbbcaa039e7c8b5d6e11e573c3 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Fri, 8 Apr 2011 16:46:29 +1200 Subject: [PATCH 011/147] Drop the -1 in pi range instead --- akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index 45b0ddd89a..1ee7b3bbd6 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -62,7 +62,7 @@ object Pi extends App { /* // FIXME tail-recursive fun instead val calculatePiFor = (arg: Int, nrOfElements: Int) => { - val range = (arg * nrOfElements) to ((arg + 1) * nrOfElements - 1) + val range = (arg * nrOfElements) until ((arg + 1) * nrOfElements) var acc = 0.0D range foreach (i => acc += 4 * math.pow(-1, i) / (2 * i + 1)) acc @@ -71,9 +71,9 @@ object Pi extends App { } */ def calculatePiFor(arg: Int, nrOfElements: Int): Double = { - val end = (arg + 1) * nrOfElements - 1 + val end = (arg + 1) * nrOfElements @tailrec def doCalculatePiFor(cursor: Int, acc: Double): Double = { - if (cursor > end) acc + if (cursor == end) acc else doCalculatePiFor(cursor + 1, acc + (4 * math.pow(-1, cursor) / (2 * cursor + 1))) } doCalculatePiFor(arg * nrOfElements, 0.0D) From 9ca3072edba5d67174409adb5368f60eabd78fcf Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 09:05:42 +0200 Subject: [PATCH 012/147] added isInfoEnabled and isDebugEnabled, needed for Java api --- akka-actor/src/main/scala/akka/event/EventHandler.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/akka-actor/src/main/scala/akka/event/EventHandler.scala b/akka-actor/src/main/scala/akka/event/EventHandler.scala index 176e441e5b..87b6462750 100644 --- a/akka-actor/src/main/scala/akka/event/EventHandler.scala +++ b/akka-actor/src/main/scala/akka/event/EventHandler.scala @@ -145,6 +145,10 @@ object EventHandler extends ListenerManagement { if (level >= DebugLevel) notifyListeners(Debug(instance, message)) } + def isInfoEnabled = level >= InfoLevel + + def isDebugEnabled = level >= DebugLevel + def formattedTimestamp = DateFormat.getInstance.format(new Date) def stackTraceFor(e: Throwable) = { From f9ced6820bffd42cd9989c1885809c5caf48b888 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 09:23:04 +0200 Subject: [PATCH 013/147] Added akka-sample-chat again --- akka-samples/akka-sample-chat/Buildfile | 48 ++++ akka-samples/akka-sample-chat/README | 32 +++ .../src/main/scala/ChatServer.scala | 228 ++++++++++++++++++ 3 files changed, 308 insertions(+) create mode 100644 akka-samples/akka-sample-chat/Buildfile create mode 100644 akka-samples/akka-sample-chat/README create mode 100644 akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala diff --git a/akka-samples/akka-sample-chat/Buildfile b/akka-samples/akka-sample-chat/Buildfile new file mode 100644 index 0000000000..814e6e4149 --- /dev/null +++ b/akka-samples/akka-sample-chat/Buildfile @@ -0,0 +1,48 @@ +require 'buildr/scala' + +VERSION_NUMBER = "0.6" +GROUP = "se.scalablesolutions.akka" + +repositories.remote << "http://www.ibiblio.org/maven2/" +repositories.remote << "http://www.lag.net/repo" +repositories.remote << "http://multiverse.googlecode.com/svn/maven-repository/releases" + +AKKA = group('akka-remote', 'akka-comet', 'akka-util','akka-kernel', 'akka-rest', 'akka-util-java', + 'akka-security','akka-persistence-common', 'akka-persistence-redis', + 'akka-amqp', + :under=> 'se.scalablesolutions.akka', + :version => '0.6') +ASPECTJ = "org.codehaus.aspectwerkz:aspectwerkz-nodeps-jdk5:jar:2.1" +SBINARY = "sbinary:sbinary:jar:0.3" +COMMONS_IO = "commons-io:commons-io:jar:1.4" +CONFIGGY = "net.lag:configgy:jar:1.4.7" +JACKSON = group('jackson-core-asl', 'jackson-mapper-asl', + :under=> 'org.codehaus.jackson', + :version => '1.2.1') +MULTIVERSE = "org.multiverse:multiverse-alpha:jar:jar-with-dependencies:0.3" +NETTY = "org.jboss.netty:netty:jar:3.2.0.ALPHA2" +PROTOBUF = "com.google.protobuf:protobuf-java:jar:2.2.0" +REDIS = "com.redis:redisclient:jar:1.0.1" +SJSON = "sjson.json:sjson:jar:0.3" + +Project.local_task "run" + +desc "Akka Chat Sample Module" +define "akka-sample-chat" do + project.version = VERSION_NUMBER + project.group = GROUP + + compile.with AKKA, CONFIGGY + + p artifact(MULTIVERSE).to_s + + package(:jar) + + task "run" do + Java.java "scala.tools.nsc.MainGenericRunner", + :classpath => [ compile.dependencies, compile.target, + ASPECTJ, COMMONS_IO, JACKSON, NETTY, MULTIVERSE, PROTOBUF, REDIS, + SBINARY, SJSON], + :java_args => ["-server"] + end +end \ No newline at end of file diff --git a/akka-samples/akka-sample-chat/README b/akka-samples/akka-sample-chat/README new file mode 100644 index 0000000000..dff045d6f8 --- /dev/null +++ b/akka-samples/akka-sample-chat/README @@ -0,0 +1,32 @@ +Akka Chat Client/Server Sample Application + +First we need to download, build and start up Redis: + +1. Download Redis from http://code.google.com/p/redis/downloads/list. +2. Step into the distribution. +3. Build: ‘make install’. +4. Run: ‘./redis-server’. +For details on how to set up Redis server have a look at http://code.google.com/p/redis/wiki/QuickStart. + +Then to run the sample: + +1. Install the Redis network storage. Download it from [http://code.google.com/p/redis/]. +2. Open up a shell and start up an instance of Redis. +3. Fire up two shells. For each of them: + - Step down into to the root of the Akka distribution. + - Set 'export AKKA_HOME=. + - Run 'sbt console' to start up a REPL (interpreter). +4. In the first REPL you get execute: + - scala> import sample.chat._ + - scala> import akka.actor.Actor._ + - scala> val chatService = actorOf[ChatService].start +5. In the second REPL you get execute: + - scala> import sample.chat._ + - scala> Runner.run +6. See the chat simulation run. +7. Run it again to see full speed after first initialization. + +Now you could test client reconnect by killing the console running the ChatService and start it up again. See the client reconnect take place in the REPL shell. + +That’s it. Have fun. + diff --git a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala new file mode 100644 index 0000000000..aa34824bab --- /dev/null +++ b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala @@ -0,0 +1,228 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB . + */ + +package sample.chat + +import scala.collection.mutable.HashMap + +import akka.actor.{SupervisorFactory, Actor, ActorRef, RemoteActor} +import akka.remote.{RemoteNode, RemoteClient} +import akka.persistence.common.PersistentVector +import akka.persistence.redis.RedisStorage +import akka.stm._ +import akka.config.Supervision.{OneForOneStrategy,Permanent} +import akka.util.Logging +import Actor._ + +/****************************************************************************** +Akka Chat Client/Server Sample Application + +First we need to download, build and start up Redis: + +1. Download Redis from http://code.google.com/p/redis/downloads/list. +2. Step into the distribution. +3. Build: ‘make install’. +4. Run: ‘./redis-server’. +For details on how to set up Redis server have a look at http://code.google.com/p/redis/wiki/QuickStart. + +Then to run the sample: + +1. Fire up two shells. For each of them: + - Step down into to the root of the Akka distribution. + - Set 'export AKKA_HOME=. + - Run 'sbt console' to start up a REPL (interpreter). +2. In the first REPL you get execute: + - scala> import sample.chat._ + - scala> import akka.actor.Actor._ + - scala> val chatService = actorOf[ChatService].start +3. In the second REPL you get execute: + - scala> import sample.chat._ + - scala> Runner.run +4. See the chat simulation run. +5. Run it again to see full speed after first initialization. + +That’s it. Have fun. + +******************************************************************************/ + +/** + * ChatServer's internal events. + */ +sealed trait Event +case class Login(user: String) extends Event +case class Logout(user: String) extends Event +case class GetChatLog(from: String) extends Event +case class ChatLog(log: List[String]) extends Event +case class ChatMessage(from: String, message: String) extends Event + +/** + * Chat client. + */ +class ChatClient(val name: String) { + val chat = RemoteClient.actorFor("chat:service", "localhost", 2552) + + def login = chat ! Login(name) + def logout = chat ! Logout(name) + def post(message: String) = chat ! ChatMessage(name, name + ": " + message) + def chatLog = (chat !! GetChatLog(name)).as[ChatLog].getOrElse(throw new Exception("Couldn't get the chat log from ChatServer")) +} + +/** + * Internal chat client session. + */ +class Session(user: String, storage: ActorRef) extends Actor { + private val loginTime = System.currentTimeMillis + private var userLog: List[String] = Nil + + log.info("New session for user [%s] has been created at [%s]", user, loginTime) + + def receive = { + case msg @ ChatMessage(from, message) => + userLog ::= message + storage ! msg + + case msg @ GetChatLog(_) => + storage forward msg + } +} + +/** + * Abstraction of chat storage holding the chat log. + */ +trait ChatStorage extends Actor + +/** + * Redis-backed chat storage implementation. + */ +class RedisChatStorage extends ChatStorage { + self.lifeCycle = Permanent + val CHAT_LOG = "akka.chat.log" + + private var chatLog = RedisStorage.getVector(CHAT_LOG) + + log.info("Redis-based chat storage is starting up...") + + def receive = { + case msg @ ChatMessage(from, message) => + log.debug("New chat message [%s]", message) + atomic { chatLog + message.getBytes("UTF-8") } + + case GetChatLog(_) => + val messageList = atomic { chatLog.map(bytes => new String(bytes, "UTF-8")).toList } + self.reply(ChatLog(messageList)) + } + + override def postRestart(reason: Throwable) = chatLog = RedisStorage.getVector(CHAT_LOG) +} + +/** + * Implements user session management. + *

+ * Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor. + */ +trait SessionManagement { this: Actor => + + val storage: ActorRef // needs someone to provide the ChatStorage + val sessions = new HashMap[String, ActorRef] + + protected def sessionManagement: Receive = { + case Login(username) => + log.info("User [%s] has logged in", username) + val session = actorOf(new Session(username, storage)) + session.start + sessions += (username -> session) + + case Logout(username) => + log.info("User [%s] has logged out", username) + val session = sessions(username) + session.stop + sessions -= username + } + + protected def shutdownSessions = + sessions.foreach { case (_, session) => session.stop } +} + +/** + * Implements chat management, e.g. chat message dispatch. + *

+ * Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor. + */ +trait ChatManagement { this: Actor => + val sessions: HashMap[String, ActorRef] // needs someone to provide the Session map + + protected def chatManagement: Receive = { + case msg @ ChatMessage(from, _) => sessions(from) ! msg + case msg @ GetChatLog(from) => sessions(from) forward msg + } +} + +/** + * Creates and links a RedisChatStorage. + */ +trait RedisChatStorageFactory { this: Actor => + val storage = this.self.spawnLink[RedisChatStorage] // starts and links ChatStorage +} + +/** + * Chat server. Manages sessions and redirects all other messages to the Session for the client. + */ +trait ChatServer extends Actor { + self.faultHandler = OneForOneStrategy(List(classOf[Exception]),5, 5000) + val storage: ActorRef + + log.info("Chat server is starting up...") + + // actor message handler + def receive = sessionManagement orElse chatManagement + + // abstract methods to be defined somewhere else + protected def chatManagement: Receive + protected def sessionManagement: Receive + protected def shutdownSessions(): Unit + + override def postStop = { + log.info("Chat server is shutting down...") + shutdownSessions + self.unlink(storage) + storage.stop + } +} + +/** + * Class encapsulating the full Chat Service. + * Start service by invoking: + *

+ * val chatService = Actor.actorOf[ChatService].start
+ * 
+ */ +class ChatService extends + ChatServer with + SessionManagement with + ChatManagement with + RedisChatStorageFactory { + override def preStart = { + RemoteNode.start("localhost", 2552) + RemoteNode.register("chat:service", self) + } +} + +/** + * Test runner emulating a chat session. + */ +object Runner { + def run = { + val client = new ChatClient("jonas") + + client.login + + client.post("Hi there") + println("CHAT LOG:\n\t" + client.chatLog.log.mkString("\n\t")) + + client.post("Hi again") + println("CHAT LOG:\n\t" + client.chatLog.log.mkString("\n\t")) + + client.logout + } +} From c882b2c5e1bf31158d5da012fdd27bc6d1718cee Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 13:09:19 +0200 Subject: [PATCH 014/147] Adjusted chat sample to run with latest, and without Redis --- akka-samples/akka-sample-chat/Buildfile | 48 -------- akka-samples/akka-sample-chat/README | 32 ++--- .../src/main/scala/ChatServer.scala | 116 +++++++++++------- project/build/AkkaProject.scala | 4 + 4 files changed, 88 insertions(+), 112 deletions(-) delete mode 100644 akka-samples/akka-sample-chat/Buildfile diff --git a/akka-samples/akka-sample-chat/Buildfile b/akka-samples/akka-sample-chat/Buildfile deleted file mode 100644 index 814e6e4149..0000000000 --- a/akka-samples/akka-sample-chat/Buildfile +++ /dev/null @@ -1,48 +0,0 @@ -require 'buildr/scala' - -VERSION_NUMBER = "0.6" -GROUP = "se.scalablesolutions.akka" - -repositories.remote << "http://www.ibiblio.org/maven2/" -repositories.remote << "http://www.lag.net/repo" -repositories.remote << "http://multiverse.googlecode.com/svn/maven-repository/releases" - -AKKA = group('akka-remote', 'akka-comet', 'akka-util','akka-kernel', 'akka-rest', 'akka-util-java', - 'akka-security','akka-persistence-common', 'akka-persistence-redis', - 'akka-amqp', - :under=> 'se.scalablesolutions.akka', - :version => '0.6') -ASPECTJ = "org.codehaus.aspectwerkz:aspectwerkz-nodeps-jdk5:jar:2.1" -SBINARY = "sbinary:sbinary:jar:0.3" -COMMONS_IO = "commons-io:commons-io:jar:1.4" -CONFIGGY = "net.lag:configgy:jar:1.4.7" -JACKSON = group('jackson-core-asl', 'jackson-mapper-asl', - :under=> 'org.codehaus.jackson', - :version => '1.2.1') -MULTIVERSE = "org.multiverse:multiverse-alpha:jar:jar-with-dependencies:0.3" -NETTY = "org.jboss.netty:netty:jar:3.2.0.ALPHA2" -PROTOBUF = "com.google.protobuf:protobuf-java:jar:2.2.0" -REDIS = "com.redis:redisclient:jar:1.0.1" -SJSON = "sjson.json:sjson:jar:0.3" - -Project.local_task "run" - -desc "Akka Chat Sample Module" -define "akka-sample-chat" do - project.version = VERSION_NUMBER - project.group = GROUP - - compile.with AKKA, CONFIGGY - - p artifact(MULTIVERSE).to_s - - package(:jar) - - task "run" do - Java.java "scala.tools.nsc.MainGenericRunner", - :classpath => [ compile.dependencies, compile.target, - ASPECTJ, COMMONS_IO, JACKSON, NETTY, MULTIVERSE, PROTOBUF, REDIS, - SBINARY, SJSON], - :java_args => ["-server"] - end -end \ No newline at end of file diff --git a/akka-samples/akka-sample-chat/README b/akka-samples/akka-sample-chat/README index dff045d6f8..2c812593fa 100644 --- a/akka-samples/akka-sample-chat/README +++ b/akka-samples/akka-sample-chat/README @@ -1,32 +1,26 @@ Akka Chat Client/Server Sample Application -First we need to download, build and start up Redis: +How to run the sample: -1. Download Redis from http://code.google.com/p/redis/downloads/list. -2. Step into the distribution. -3. Build: ‘make install’. -4. Run: ‘./redis-server’. -For details on how to set up Redis server have a look at http://code.google.com/p/redis/wiki/QuickStart. - -Then to run the sample: - -1. Install the Redis network storage. Download it from [http://code.google.com/p/redis/]. -2. Open up a shell and start up an instance of Redis. -3. Fire up two shells. For each of them: +1. Fire up two shells. For each of them: - Step down into to the root of the Akka distribution. - Set 'export AKKA_HOME=. - Run 'sbt console' to start up a REPL (interpreter). -4. In the first REPL you get execute: +2. In the first REPL you get execute: - scala> import sample.chat._ - scala> import akka.actor.Actor._ - scala> val chatService = actorOf[ChatService].start -5. In the second REPL you get execute: +3. In the second REPL you get execute: - scala> import sample.chat._ - - scala> Runner.run -6. See the chat simulation run. -7. Run it again to see full speed after first initialization. - -Now you could test client reconnect by killing the console running the ChatService and start it up again. See the client reconnect take place in the REPL shell. + - scala> ClientRunner.run +4. See the chat simulation run. +5. Run it again to see full speed after first initialization. +6. In the client REPL, or in a new REPL, you can also create your own client + - scala> import sample.chat._ + - scala> val myClient = new ChatClient("") + - scala> myClient.login + - scala> myClient.post("Can I join?") + - scala> println("CHAT LOG:\n\t" + myClient.chatLog.log.mkString("\n\t")) That’s it. Have fun. diff --git a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala index aa34824bab..695e463815 100644 --- a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala +++ b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala @@ -6,27 +6,16 @@ package sample.chat import scala.collection.mutable.HashMap -import akka.actor.{SupervisorFactory, Actor, ActorRef, RemoteActor} -import akka.remote.{RemoteNode, RemoteClient} -import akka.persistence.common.PersistentVector -import akka.persistence.redis.RedisStorage +import akka.actor.{SupervisorFactory, Actor, ActorRef} import akka.stm._ import akka.config.Supervision.{OneForOneStrategy,Permanent} -import akka.util.Logging import Actor._ +import akka.event.EventHandler /****************************************************************************** Akka Chat Client/Server Sample Application -First we need to download, build and start up Redis: - -1. Download Redis from http://code.google.com/p/redis/downloads/list. -2. Step into the distribution. -3. Build: ‘make install’. -4. Run: ‘./redis-server’. -For details on how to set up Redis server have a look at http://code.google.com/p/redis/wiki/QuickStart. - -Then to run the sample: +How to run the sample: 1. Fire up two shells. For each of them: - Step down into to the root of the Akka distribution. @@ -38,9 +27,16 @@ Then to run the sample: - scala> val chatService = actorOf[ChatService].start 3. In the second REPL you get execute: - scala> import sample.chat._ - - scala> Runner.run + - scala> ClientRunner.run 4. See the chat simulation run. 5. Run it again to see full speed after first initialization. +6. In the client REPL, or in a new REPL, you can also create your own client + - scala> import sample.chat._ + - scala> val myClient = new ChatClient("") + - scala> myClient.login + - scala> myClient.post("Can I join?") + - scala> println("CHAT LOG:\n\t" + myClient.chatLog.log.mkString("\n\t")) + That’s it. Have fun. @@ -60,7 +56,7 @@ case class ChatMessage(from: String, message: String) extends Event * Chat client. */ class ChatClient(val name: String) { - val chat = RemoteClient.actorFor("chat:service", "localhost", 2552) + val chat = Actor.remote.actorFor("chat:service", "localhost", 2552) def login = chat ! Login(name) def logout = chat ! Logout(name) @@ -75,7 +71,7 @@ class Session(user: String, storage: ActorRef) extends Actor { private val loginTime = System.currentTimeMillis private var userLog: List[String] = Nil - log.info("New session for user [%s] has been created at [%s]", user, loginTime) + EventHandler.info(this, "New session for user [%s] has been created at [%s]".format(user, loginTime)) def receive = { case msg @ ChatMessage(from, message) => @@ -93,19 +89,18 @@ class Session(user: String, storage: ActorRef) extends Actor { trait ChatStorage extends Actor /** - * Redis-backed chat storage implementation. + * Memory-backed chat storage implementation. */ -class RedisChatStorage extends ChatStorage { +class MemoryChatStorage extends ChatStorage { self.lifeCycle = Permanent - val CHAT_LOG = "akka.chat.log" - private var chatLog = RedisStorage.getVector(CHAT_LOG) + private var chatLog = TransactionalVector[Array[Byte]]() - log.info("Redis-based chat storage is starting up...") + EventHandler.info(this, "Memory-based chat storage is starting up...") def receive = { case msg @ ChatMessage(from, message) => - log.debug("New chat message [%s]", message) + EventHandler.debug(this, "New chat message [%s]".format(message)) atomic { chatLog + message.getBytes("UTF-8") } case GetChatLog(_) => @@ -113,7 +108,7 @@ class RedisChatStorage extends ChatStorage { self.reply(ChatLog(messageList)) } - override def postRestart(reason: Throwable) = chatLog = RedisStorage.getVector(CHAT_LOG) + override def postRestart(reason: Throwable) = chatLog = TransactionalVector() } /** @@ -128,13 +123,13 @@ trait SessionManagement { this: Actor => protected def sessionManagement: Receive = { case Login(username) => - log.info("User [%s] has logged in", username) + EventHandler.info(this, "User [%s] has logged in".format(username)) val session = actorOf(new Session(username, storage)) session.start sessions += (username -> session) case Logout(username) => - log.info("User [%s] has logged out", username) + EventHandler.info(this, "User [%s] has logged out".format(username)) val session = sessions(username) session.stop sessions -= username @@ -153,16 +148,25 @@ trait ChatManagement { this: Actor => val sessions: HashMap[String, ActorRef] // needs someone to provide the Session map protected def chatManagement: Receive = { - case msg @ ChatMessage(from, _) => sessions(from) ! msg - case msg @ GetChatLog(from) => sessions(from) forward msg + case msg @ ChatMessage(from, _) => getSession(from).foreach(_ ! msg) + case msg @ GetChatLog(from) => getSession(from).foreach(_ forward msg) + } + + private def getSession(from: String) : Option[ActorRef] = { + if (sessions.contains(from)) + Some(sessions(from)) + else { + EventHandler.info(this, "Session expired for %s".format(from)) + None + } } } /** - * Creates and links a RedisChatStorage. + * Creates and links a MemoryChatStorage. */ -trait RedisChatStorageFactory { this: Actor => - val storage = this.self.spawnLink[RedisChatStorage] // starts and links ChatStorage +trait MemoryChatStorageFactory { this: Actor => + val storage = this.self.spawnLink[MemoryChatStorage] // starts and links ChatStorage } /** @@ -172,10 +176,10 @@ trait ChatServer extends Actor { self.faultHandler = OneForOneStrategy(List(classOf[Exception]),5, 5000) val storage: ActorRef - log.info("Chat server is starting up...") + EventHandler.info(this, "Chat server is starting up...") // actor message handler - def receive = sessionManagement orElse chatManagement + def receive: Receive = sessionManagement orElse chatManagement // abstract methods to be defined somewhere else protected def chatManagement: Receive @@ -183,7 +187,7 @@ trait ChatServer extends Actor { protected def shutdownSessions(): Unit override def postStop = { - log.info("Chat server is shutting down...") + EventHandler.info(this, "Chat server is shutting down...") shutdownSessions self.unlink(storage) storage.stop @@ -201,28 +205,50 @@ class ChatService extends ChatServer with SessionManagement with ChatManagement with - RedisChatStorageFactory { + MemoryChatStorageFactory { override def preStart = { - RemoteNode.start("localhost", 2552) - RemoteNode.register("chat:service", self) + remote.start("localhost", 2552); + remote.register("chat:service", self) //Register the actor with the specified service id + } +} + +/** + * Test runner starting ChatService. + */ +object ServerRunner { + + def main(args: Array[String]): Unit = ServerRunner.run + + def run = { + actorOf[ChatService].start } } /** * Test runner emulating a chat session. */ -object Runner { +object ClientRunner { + + def main(args: Array[String]): Unit = ClientRunner.run + def run = { - val client = new ChatClient("jonas") - client.login + val client1 = new ChatClient("jonas") + client1.login + val client2 = new ChatClient("patrik") + client2.login - client.post("Hi there") - println("CHAT LOG:\n\t" + client.chatLog.log.mkString("\n\t")) + client1.post("Hi there") + println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t")) - client.post("Hi again") - println("CHAT LOG:\n\t" + client.chatLog.log.mkString("\n\t")) + client2.post("Hello") + println("CHAT LOG:\n\t" + client2.chatLog.log.mkString("\n\t")) - client.logout + client1.post("Hi again") + println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t")) + + client1.logout + client2.logout } } + diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index 061084bac0..fa2dd9268d 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -370,6 +370,8 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { class AkkaSampleRemoteProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) + class AkkaSampleChatProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) + class AkkaSampleFSMProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) class AkkaSamplesParentProject(info: ProjectInfo) extends ParentProject(info) { @@ -381,6 +383,8 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { new AkkaSampleFSMProject(_), akka_actor) lazy val akka_sample_remote = project("akka-sample-remote", "akka-sample-remote", new AkkaSampleRemoteProject(_), akka_remote) + lazy val akka_sample_chat = project("akka-sample-chat", "akka-sample-chat", + new AkkaSampleChatProject(_), akka_remote) lazy val publishRelease = { val releaseConfiguration = new DefaultPublishConfiguration(localReleaseRepository, "release", false) From 79f6133f067abfb5d46cd2e10c4134ef0a053747 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 13:15:12 +0200 Subject: [PATCH 015/147] Adjusted chat sample to run with latest, and without Redis --- .../src/main/scala/ChatServer.scala | 438 +++++++++--------- 1 file changed, 219 insertions(+), 219 deletions(-) diff --git a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala index 695e463815..46e0283874 100644 --- a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala +++ b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala @@ -1,254 +1,254 @@ -/** - * Copyright (C) 2009-2010 Scalable Solutions AB . - */ + /** + * Copyright (C) 2009-2010 Scalable Solutions AB . + */ -package sample.chat + package sample.chat -import scala.collection.mutable.HashMap + import scala.collection.mutable.HashMap -import akka.actor.{SupervisorFactory, Actor, ActorRef} -import akka.stm._ -import akka.config.Supervision.{OneForOneStrategy,Permanent} -import Actor._ -import akka.event.EventHandler + import akka.actor.{SupervisorFactory, Actor, ActorRef} + import akka.stm._ + import akka.config.Supervision.{OneForOneStrategy,Permanent} + import Actor._ + import akka.event.EventHandler -/****************************************************************************** -Akka Chat Client/Server Sample Application + /****************************************************************************** + Akka Chat Client/Server Sample Application + + How to run the sample: -How to run the sample: - -1. Fire up two shells. For each of them: - - Step down into to the root of the Akka distribution. - - Set 'export AKKA_HOME=. - - Run 'sbt console' to start up a REPL (interpreter). -2. In the first REPL you get execute: - - scala> import sample.chat._ - - scala> import akka.actor.Actor._ - - scala> val chatService = actorOf[ChatService].start -3. In the second REPL you get execute: + 1. Fire up two shells. For each of them: + - Step down into to the root of the Akka distribution. + - Set 'export AKKA_HOME=. + - Run 'sbt console' to start up a REPL (interpreter). + 2. In the first REPL you get execute: - scala> import sample.chat._ - - scala> ClientRunner.run -4. See the chat simulation run. -5. Run it again to see full speed after first initialization. -6. In the client REPL, or in a new REPL, you can also create your own client - - scala> import sample.chat._ - - scala> val myClient = new ChatClient("") - - scala> myClient.login - - scala> myClient.post("Can I join?") - - scala> println("CHAT LOG:\n\t" + myClient.chatLog.log.mkString("\n\t")) + - scala> import akka.actor.Actor._ + - scala> val chatService = actorOf[ChatService].start + 3. In the second REPL you get execute: + - scala> import sample.chat._ + - scala> ClientRunner.run + 4. See the chat simulation run. + 5. Run it again to see full speed after first initialization. + 6. In the client REPL, or in a new REPL, you can also create your own client + - scala> import sample.chat._ + - scala> val myClient = new ChatClient("") + - scala> myClient.login + - scala> myClient.post("Can I join?") + - scala> println("CHAT LOG:\n\t" + myClient.chatLog.log.mkString("\n\t")) -That’s it. Have fun. + That’s it. Have fun. -******************************************************************************/ + ******************************************************************************/ -/** - * ChatServer's internal events. - */ -sealed trait Event -case class Login(user: String) extends Event -case class Logout(user: String) extends Event -case class GetChatLog(from: String) extends Event -case class ChatLog(log: List[String]) extends Event -case class ChatMessage(from: String, message: String) extends Event + /** + * ChatServer's internal events. + */ + sealed trait Event + case class Login(user: String) extends Event + case class Logout(user: String) extends Event + case class GetChatLog(from: String) extends Event + case class ChatLog(log: List[String]) extends Event + case class ChatMessage(from: String, message: String) extends Event -/** - * Chat client. - */ -class ChatClient(val name: String) { - val chat = Actor.remote.actorFor("chat:service", "localhost", 2552) + /** + * Chat client. + */ + class ChatClient(val name: String) { + val chat = Actor.remote.actorFor("chat:service", "localhost", 2552) - def login = chat ! Login(name) - def logout = chat ! Logout(name) - def post(message: String) = chat ! ChatMessage(name, name + ": " + message) - def chatLog = (chat !! GetChatLog(name)).as[ChatLog].getOrElse(throw new Exception("Couldn't get the chat log from ChatServer")) -} - -/** - * Internal chat client session. - */ -class Session(user: String, storage: ActorRef) extends Actor { - private val loginTime = System.currentTimeMillis - private var userLog: List[String] = Nil - - EventHandler.info(this, "New session for user [%s] has been created at [%s]".format(user, loginTime)) - - def receive = { - case msg @ ChatMessage(from, message) => - userLog ::= message - storage ! msg - - case msg @ GetChatLog(_) => - storage forward msg - } -} - -/** - * Abstraction of chat storage holding the chat log. - */ -trait ChatStorage extends Actor - -/** - * Memory-backed chat storage implementation. - */ -class MemoryChatStorage extends ChatStorage { - self.lifeCycle = Permanent - - private var chatLog = TransactionalVector[Array[Byte]]() - - EventHandler.info(this, "Memory-based chat storage is starting up...") - - def receive = { - case msg @ ChatMessage(from, message) => - EventHandler.debug(this, "New chat message [%s]".format(message)) - atomic { chatLog + message.getBytes("UTF-8") } - - case GetChatLog(_) => - val messageList = atomic { chatLog.map(bytes => new String(bytes, "UTF-8")).toList } - self.reply(ChatLog(messageList)) + def login = chat ! Login(name) + def logout = chat ! Logout(name) + def post(message: String) = chat ! ChatMessage(name, name + ": " + message) + def chatLog = (chat !! GetChatLog(name)).as[ChatLog].getOrElse(throw new Exception("Couldn't get the chat log from ChatServer")) } - override def postRestart(reason: Throwable) = chatLog = TransactionalVector() -} + /** + * Internal chat client session. + */ + class Session(user: String, storage: ActorRef) extends Actor { + private val loginTime = System.currentTimeMillis + private var userLog: List[String] = Nil -/** - * Implements user session management. - *

- * Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor. - */ -trait SessionManagement { this: Actor => + EventHandler.info(this, "New session for user [%s] has been created at [%s]".format(user, loginTime)) - val storage: ActorRef // needs someone to provide the ChatStorage - val sessions = new HashMap[String, ActorRef] + def receive = { + case msg @ ChatMessage(from, message) => + userLog ::= message + storage ! msg - protected def sessionManagement: Receive = { - case Login(username) => - EventHandler.info(this, "User [%s] has logged in".format(username)) - val session = actorOf(new Session(username, storage)) - session.start - sessions += (username -> session) - - case Logout(username) => - EventHandler.info(this, "User [%s] has logged out".format(username)) - val session = sessions(username) - session.stop - sessions -= username - } - - protected def shutdownSessions = - sessions.foreach { case (_, session) => session.stop } -} - -/** - * Implements chat management, e.g. chat message dispatch. - *

- * Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor. - */ -trait ChatManagement { this: Actor => - val sessions: HashMap[String, ActorRef] // needs someone to provide the Session map - - protected def chatManagement: Receive = { - case msg @ ChatMessage(from, _) => getSession(from).foreach(_ ! msg) - case msg @ GetChatLog(from) => getSession(from).foreach(_ forward msg) - } - - private def getSession(from: String) : Option[ActorRef] = { - if (sessions.contains(from)) - Some(sessions(from)) - else { - EventHandler.info(this, "Session expired for %s".format(from)) - None + case msg @ GetChatLog(_) => + storage forward msg } } -} -/** - * Creates and links a MemoryChatStorage. - */ -trait MemoryChatStorageFactory { this: Actor => - val storage = this.self.spawnLink[MemoryChatStorage] // starts and links ChatStorage -} + /** + * Abstraction of chat storage holding the chat log. + */ + trait ChatStorage extends Actor -/** - * Chat server. Manages sessions and redirects all other messages to the Session for the client. - */ -trait ChatServer extends Actor { - self.faultHandler = OneForOneStrategy(List(classOf[Exception]),5, 5000) - val storage: ActorRef + /** + * Memory-backed chat storage implementation. + */ + class MemoryChatStorage extends ChatStorage { + self.lifeCycle = Permanent - EventHandler.info(this, "Chat server is starting up...") + private var chatLog = TransactionalVector[Array[Byte]]() - // actor message handler - def receive: Receive = sessionManagement orElse chatManagement + EventHandler.info(this, "Memory-based chat storage is starting up...") - // abstract methods to be defined somewhere else - protected def chatManagement: Receive - protected def sessionManagement: Receive - protected def shutdownSessions(): Unit + def receive = { + case msg @ ChatMessage(from, message) => + EventHandler.debug(this, "New chat message [%s]".format(message)) + atomic { chatLog + message.getBytes("UTF-8") } - override def postStop = { - EventHandler.info(this, "Chat server is shutting down...") - shutdownSessions - self.unlink(storage) - storage.stop + case GetChatLog(_) => + val messageList = atomic { chatLog.map(bytes => new String(bytes, "UTF-8")).toList } + self.reply(ChatLog(messageList)) + } + + override def postRestart(reason: Throwable) = chatLog = TransactionalVector() } -} -/** - * Class encapsulating the full Chat Service. - * Start service by invoking: - *

- * val chatService = Actor.actorOf[ChatService].start
- * 
- */ -class ChatService extends - ChatServer with - SessionManagement with - ChatManagement with - MemoryChatStorageFactory { - override def preStart = { - remote.start("localhost", 2552); - remote.register("chat:service", self) //Register the actor with the specified service id + /** + * Implements user session management. + *

+ * Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor. + */ + trait SessionManagement { this: Actor => + + val storage: ActorRef // needs someone to provide the ChatStorage + val sessions = new HashMap[String, ActorRef] + + protected def sessionManagement: Receive = { + case Login(username) => + EventHandler.info(this, "User [%s] has logged in".format(username)) + val session = actorOf(new Session(username, storage)) + session.start + sessions += (username -> session) + + case Logout(username) => + EventHandler.info(this, "User [%s] has logged out".format(username)) + val session = sessions(username) + session.stop + sessions -= username + } + + protected def shutdownSessions = + sessions.foreach { case (_, session) => session.stop } } -} -/** - * Test runner starting ChatService. - */ -object ServerRunner { + /** + * Implements chat management, e.g. chat message dispatch. + *

+ * Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor. + */ + trait ChatManagement { this: Actor => + val sessions: HashMap[String, ActorRef] // needs someone to provide the Session map - def main(args: Array[String]): Unit = ServerRunner.run + protected def chatManagement: Receive = { + case msg @ ChatMessage(from, _) => getSession(from).foreach(_ ! msg) + case msg @ GetChatLog(from) => getSession(from).foreach(_ forward msg) + } - def run = { - actorOf[ChatService].start + private def getSession(from: String) : Option[ActorRef] = { + if (sessions.contains(from)) + Some(sessions(from)) + else { + EventHandler.info(this, "Session expired for %s".format(from)) + None + } + } } -} -/** - * Test runner emulating a chat session. - */ -object ClientRunner { - - def main(args: Array[String]): Unit = ClientRunner.run - - def run = { - - val client1 = new ChatClient("jonas") - client1.login - val client2 = new ChatClient("patrik") - client2.login - - client1.post("Hi there") - println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t")) - - client2.post("Hello") - println("CHAT LOG:\n\t" + client2.chatLog.log.mkString("\n\t")) - - client1.post("Hi again") - println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t")) - - client1.logout - client2.logout + /** + * Creates and links a MemoryChatStorage. + */ + trait MemoryChatStorageFactory { this: Actor => + val storage = this.self.spawnLink[MemoryChatStorage] // starts and links ChatStorage + } + + /** + * Chat server. Manages sessions and redirects all other messages to the Session for the client. + */ + trait ChatServer extends Actor { + self.faultHandler = OneForOneStrategy(List(classOf[Exception]),5, 5000) + val storage: ActorRef + + EventHandler.info(this, "Chat server is starting up...") + + // actor message handler + def receive: Receive = sessionManagement orElse chatManagement + + // abstract methods to be defined somewhere else + protected def chatManagement: Receive + protected def sessionManagement: Receive + protected def shutdownSessions(): Unit + + override def postStop = { + EventHandler.info(this, "Chat server is shutting down...") + shutdownSessions + self.unlink(storage) + storage.stop + } + } + + /** + * Class encapsulating the full Chat Service. + * Start service by invoking: + *

+   * val chatService = Actor.actorOf[ChatService].start
+   * 
+ */ + class ChatService extends + ChatServer with + SessionManagement with + ChatManagement with + MemoryChatStorageFactory { + override def preStart = { + remote.start("localhost", 2552); + remote.register("chat:service", self) //Register the actor with the specified service id + } + } + + /** + * Test runner starting ChatService. + */ + object ServerRunner { + + def main(args: Array[String]): Unit = ServerRunner.run + + def run = { + actorOf[ChatService].start + } + } + + /** + * Test runner emulating a chat session. + */ + object ClientRunner { + + def main(args: Array[String]): Unit = ClientRunner.run + + def run = { + + val client1 = new ChatClient("jonas") + client1.login + val client2 = new ChatClient("patrik") + client2.login + + client1.post("Hi there") + println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t")) + + client2.post("Hello") + println("CHAT LOG:\n\t" + client2.chatLog.log.mkString("\n\t")) + + client1.post("Hi again") + println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t")) + + client1.logout + client2.logout + } } -} From ab05bf943066a9fa6d687d7219ed1a46875c5a24 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 14:10:39 +0200 Subject: [PATCH 016/147] fixed warnings, @serializable -> extends scala.Serializable --- akka-actor/src/main/scala/akka/actor/Actor.scala | 2 +- .../scala/akka/serialization/SerializationProtocol.scala | 4 ++-- .../src/main/scala/akka/serialization/Serializer.scala | 2 +- .../src/test/scala/remote/RemoteSupervisorSpec.scala | 6 +++--- .../serialization/SerializableTypeClassActorSpec.scala | 4 ++-- akka-stm/src/main/scala/akka/stm/Ref.scala | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index 718c3b94bb..db06a04418 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -18,7 +18,7 @@ import akka.japi. {Creator, Procedure} /** * Life-cycle messages for the Actors */ -@serializable sealed trait LifeCycleMessage +sealed trait LifeCycleMessage extends Serializable /* Marker trait to show which Messages are automatically handled by Akka */ sealed trait AutoReceivedMessage { self: LifeCycleMessage => } diff --git a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala index 7ad0c1e443..090f483309 100644 --- a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -44,7 +44,7 @@ trait Format[T <: Actor] extends FromBinary[T] with ToBinary[T] * } * */ -@serializable trait StatelessActorFormat[T <: Actor] extends Format[T] { +trait StatelessActorFormat[T <: Actor] extends Format[T] with Serializable { def fromBinary(bytes: Array[Byte], act: T) = act def toBinary(ac: T) = Array.empty[Byte] @@ -64,7 +64,7 @@ trait Format[T <: Actor] extends FromBinary[T] with ToBinary[T] * } * */ -@serializable trait SerializerBasedActorFormat[T <: Actor] extends Format[T] { +trait SerializerBasedActorFormat[T <: Actor] extends Format[T] with Serializable { val serializer: Serializer def fromBinary(bytes: Array[Byte], act: T) = serializer.fromBinary(bytes, Some(act.self.actorClass)).asInstanceOf[T] diff --git a/akka-remote/src/main/scala/akka/serialization/Serializer.scala b/akka-remote/src/main/scala/akka/serialization/Serializer.scala index 3a292e0de0..3fc661afce 100644 --- a/akka-remote/src/main/scala/akka/serialization/Serializer.scala +++ b/akka-remote/src/main/scala/akka/serialization/Serializer.scala @@ -17,7 +17,7 @@ import sjson.json.{Serializer => SJSONSerializer} /** * @author Jonas Bonér */ -@serializable trait Serializer { +trait Serializer extends scala.Serializable { @volatile var classLoader: Option[ClassLoader] = None def deepClone(obj: AnyRef): AnyRef = fromBinary(toBinary(obj), Some(obj.getClass)) diff --git a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala index 4026418d18..a0b6440287 100644 --- a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala +++ b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala @@ -23,7 +23,7 @@ object Log { } } -@serializable class RemotePingPong1Actor extends Actor { +class RemotePingPong1Actor extends Actor with Serializable { def receive = { case "Ping" => Log.messageLog.put("ping") @@ -41,7 +41,7 @@ object Log { } } -@serializable class RemotePingPong2Actor extends Actor { +class RemotePingPong2Actor extends Actor with Serializable { def receive = { case "Ping" => Log.messageLog.put("ping") @@ -55,7 +55,7 @@ object Log { } } -@serializable class RemotePingPong3Actor extends Actor { +class RemotePingPong3Actor extends Actor with Serializable { def receive = { case "Ping" => Log.messageLog.put("ping") diff --git a/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala b/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala index 2eec948698..20ccc076b0 100644 --- a/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala +++ b/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala @@ -221,7 +221,7 @@ class MyActorWithDualCounter extends Actor { } } -@serializable class MyActor extends Actor { +class MyActor extends Actor with scala.Serializable { var count = 0 def receive = { @@ -249,7 +249,7 @@ class MyStatelessActorWithMessagesInMailbox extends Actor { } } -@serializable class MyJavaSerializableActor extends Actor { +class MyJavaSerializableActor extends Actor with Serializable { var count = 0 self.receiveTimeout = Some(1000) diff --git a/akka-stm/src/main/scala/akka/stm/Ref.scala b/akka-stm/src/main/scala/akka/stm/Ref.scala index 74b1bf5a9e..5d1aa9dc96 100644 --- a/akka-stm/src/main/scala/akka/stm/Ref.scala +++ b/akka-stm/src/main/scala/akka/stm/Ref.scala @@ -11,7 +11,7 @@ import org.multiverse.transactional.refs.BasicRef /** * Common trait for all the transactional objects. */ -@serializable trait Transactional { +trait Transactional extends Serializable { val uuid: String } From 523c5a4b34ba09dfc26338279a8eb7a833aaf6e2 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 14:41:32 +0200 Subject: [PATCH 017/147] fixed warnings, error -> sys.error --- akka-actor/src/main/scala/akka/util/Duration.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-actor/src/main/scala/akka/util/Duration.scala b/akka-actor/src/main/scala/akka/util/Duration.scala index fb5673277c..933e3cd9a9 100644 --- a/akka-actor/src/main/scala/akka/util/Duration.scala +++ b/akka-actor/src/main/scala/akka/util/Duration.scala @@ -37,7 +37,7 @@ object Duration { * Construct a Duration by parsing a String. In case of a format error, a * RuntimeException is thrown. See `unapply(String)` for more information. */ - def apply(s : String) : Duration = unapply(s) getOrElse error("format error") + def apply(s : String) : Duration = unapply(s) getOrElse sys.error("format error") /** * Deconstruct a Duration into length and unit if it is finite. @@ -77,7 +77,7 @@ object Duration { if ( ms ne null) Some(Duration(JDouble.parseDouble(length), MILLISECONDS)) else if (mus ne null) Some(Duration(JDouble.parseDouble(length), MICROSECONDS)) else if ( ns ne null) Some(Duration(JDouble.parseDouble(length), NANOSECONDS)) else - error("made some error in regex (should not be possible)") + sys.error("made some error in regex (should not be possible)") case REinf() => Some(Inf) case REminf() => Some(MinusInf) case _ => None From d4510831f339a3066c61e21d8dac57dcd81e3340 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 14:43:15 +0200 Subject: [PATCH 018/147] fixed warnings, error -> sys.error --- .../src/main/scala/akka/testkit/CallingThreadDispatcher.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala index ce198be6bf..e6fd8ebbce 100644 --- a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala +++ b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala @@ -206,8 +206,8 @@ class NestingQueue { def pop = q.poll @volatile private var active = false - def enter { if (active) error("already active") else active = true } - def leave { if (!active) error("not active") else active = false } + def enter { if (active) sys.error("already active") else active = true } + def leave { if (!active) sys.error("not active") else active = false } def isActive = active } From 521643782e7b927f0f3e52dda5e08cac496cda57 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 15:12:39 +0200 Subject: [PATCH 019/147] fixed warnings, asScalaIterable -> collectionAsScalaIterable --- .../src/main/scala/akka/remote/netty/NettyRemoteSupport.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index d9ded4b0e2..29c2ba595a 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -904,14 +904,14 @@ class RemoteServerHandler( // stop all session actors for (map <- Option(sessionActors.remove(event.getChannel)); - actor <- asScalaIterable(map.values)) { + actor <- collectionAsScalaIterable(map.values)) { try { actor ! PoisonPill } catch { case e: Exception => } } //FIXME switch approach or use other thread to execute this // stop all typed session actors for (map <- Option(typedSessionActors.remove(event.getChannel)); - actor <- asScalaIterable(map.values)) { + actor <- collectionAsScalaIterable(map.values)) { try { TypedActor.stop(actor) } catch { case e: Exception => } } From c22ca54eb415ceaa753f6e899a024e8bea5282af Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 15:13:12 +0200 Subject: [PATCH 020/147] fixed warnings, unchecked --- .../src/main/java/akka/config/TypedActorGuiceModule.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/akka-typed-actor/src/main/java/akka/config/TypedActorGuiceModule.java b/akka-typed-actor/src/main/java/akka/config/TypedActorGuiceModule.java index 8c370a8218..2452eeb706 100644 --- a/akka-typed-actor/src/main/java/akka/config/TypedActorGuiceModule.java +++ b/akka-typed-actor/src/main/java/akka/config/TypedActorGuiceModule.java @@ -26,7 +26,10 @@ public class TypedActorGuiceModule extends AbstractModule { final DependencyBinding db = bindings.get(i); //if (db.getInterface() ne null) bind((Class) db.getInterface()).to((Class) db.getTarget()).in(Singleton.class); //else - this.bind(db.getInterface()).toInstance(db.getTarget()); + + @SuppressWarnings("unchecked") + Class intf = db.getInterface(); + this.bind(intf).toInstance(db.getTarget()); } } } From e13fb600eae64c2326e3fc2ce6d2b35bce25f897 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 15:55:04 +0200 Subject: [PATCH 021/147] fixed warnings, asScalaIterable -> collectionAsScalaIterable --- akka-actor/src/main/scala/akka/dispatch/Future.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index 3116c80811..1f86613b47 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -61,7 +61,7 @@ object Futures { * Returns a Future to the result of the first future in the list that is completed */ def firstCompletedOf[T <: AnyRef](futures: java.lang.Iterable[Future[T]], timeout: Long): Future[T] = - firstCompletedOf(scala.collection.JavaConversions.asScalaIterable(futures),timeout) + firstCompletedOf(scala.collection.JavaConversions.iterableAsScalaIterable(futures),timeout) /** * A non-blocking fold over the specified futures. @@ -87,7 +87,7 @@ object Futures { results add r.b if (results.size == allDone) { //Only one thread can get here try { - result completeWithResult scala.collection.JavaConversions.asScalaIterable(results).foldLeft(zero)(foldFun) + result completeWithResult scala.collection.JavaConversions.collectionAsScalaIterable(results).foldLeft(zero)(foldFun) } catch { case e: Exception => EventHandler.error(e, this, e.getMessage) @@ -115,7 +115,7 @@ object Futures { * or the result of the fold. */ def fold[T <: AnyRef, R <: AnyRef](zero: R, timeout: Long, futures: java.lang.Iterable[Future[T]], fun: akka.japi.Function2[R, T, R]): Future[R] = - fold(zero, timeout)(scala.collection.JavaConversions.asScalaIterable(futures))( fun.apply _ ) + fold(zero, timeout)(scala.collection.JavaConversions.iterableAsScalaIterable(futures))( fun.apply _ ) /** * Initiates a fold over the supplied futures where the fold-zero is the result value of the Future that's completed first @@ -150,7 +150,7 @@ object Futures { * Initiates a fold over the supplied futures where the fold-zero is the result value of the Future that's completed first */ def reduce[T <: AnyRef, R >: T](futures: java.lang.Iterable[Future[T]], timeout: Long, fun: akka.japi.Function2[R, T, T]): Future[R] = - reduce(scala.collection.JavaConversions.asScalaIterable(futures), timeout)(fun.apply _) + reduce(scala.collection.JavaConversions.iterableAsScalaIterable(futures), timeout)(fun.apply _) import scala.collection.mutable.Builder import scala.collection.generic.CanBuildFrom From ac3376440e048ba17a0ee02731437cdffe914002 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 15:55:30 +0200 Subject: [PATCH 022/147] fixed warnings, serializable --- .../scala/serialization/SerializableTypeClassActorSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala b/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala index 20ccc076b0..7aa0cede07 100644 --- a/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala +++ b/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala @@ -249,7 +249,7 @@ class MyStatelessActorWithMessagesInMailbox extends Actor { } } -class MyJavaSerializableActor extends Actor with Serializable { +class MyJavaSerializableActor extends Actor with scala.Serializable { var count = 0 self.receiveTimeout = Some(1000) From 9be19a42c2e469f3736d9df1cc928363de0c9cf1 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 16:04:04 +0200 Subject: [PATCH 023/147] fixed warnings, serializable --- .../main/scala/akka/serialization/SerializationProtocol.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala index 090f483309..f41351f5bc 100644 --- a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -44,7 +44,7 @@ trait Format[T <: Actor] extends FromBinary[T] with ToBinary[T] * } * */ -trait StatelessActorFormat[T <: Actor] extends Format[T] with Serializable { +trait StatelessActorFormat[T <: Actor] extends Format[T] with scala.Serializable { def fromBinary(bytes: Array[Byte], act: T) = act def toBinary(ac: T) = Array.empty[Byte] @@ -64,7 +64,7 @@ trait StatelessActorFormat[T <: Actor] extends Format[T] with Serializable { * } * */ -trait SerializerBasedActorFormat[T <: Actor] extends Format[T] with Serializable { +trait SerializerBasedActorFormat[T <: Actor] extends Format[T] with scala.Serializable { val serializer: Serializer def fromBinary(bytes: Array[Byte], act: T) = serializer.fromBinary(bytes, Some(act.self.actorClass)).asInstanceOf[T] From 614f58c22bbcaf2be8bf5a1221d1f8585589f77f Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 8 Apr 2011 16:05:03 +0200 Subject: [PATCH 024/147] fixed warnings, compile without -Xmigration --- project/build/AkkaProject.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index fa2dd9268d..75dc11e74a 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -18,7 +18,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { val scalaCompileSettings = Seq("-deprecation", - "-Xmigration", + //"-Xmigration", "-optimise", "-encoding", "utf8") From c91d74653277ba9a2c3ab8d47580749976e5e40e Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Sat, 9 Apr 2011 11:42:58 +1200 Subject: [PATCH 025/147] Override lifecycle methods in TypedActor to avoid warnings about bridge methods --- .../main/scala/akka/actor/TypedActor.scala | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala index bb5d08dd57..e21cdc395d 100644 --- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala @@ -155,6 +155,34 @@ abstract class TypedActor extends Actor with Proxyable { */ def getContext: TypedActorContext = context + /** + * User overridable callback. + *

+ * Is called when an Actor is started by invoking 'actor.start'. + */ + override def preStart {} + + /** + * User overridable callback. + *

+ * Is called when 'actor.stop' is invoked. + */ + override def postStop {} + + /** + * User overridable callback. + *

+ * 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) {} + + /** + * User overridable callback. + *

+ * Is called right AFTER restart on the newly created Actor to allow reinitialization after an Actor crash. + */ + override def postRestart(reason: Throwable) {} + /** * This method is used to resolve the Future for TypedActor methods that are defined to return a * {@link akka.actor.dispatch.Future }. From 3976e3012bc9679a6318baf8a548851c7866d26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Sat, 9 Apr 2011 14:26:52 +0200 Subject: [PATCH 026/147] Incorporated feedback on tutorial text plus added sections on SBT and some other stuff here and there --- akka-docs/manual/getting-started-first.rst | 130 ++++++++++++------ .../src/main/scala/Pi.scala | 32 ++--- 2 files changed, 101 insertions(+), 61 deletions(-) diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst index d0f1d78254..36918037a0 100755 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -23,8 +23,13 @@ Here is the formula for the algorithm we will use: In this particular algorithm the master splits the series into chunks which are sent out to each worker actor to be processed, when each worker has processed its chunk it sends a result back to the master which aggregates to total result. -Prerequisite ------------- +Tutorial source code +-------------------- + +If you want don't want to type in the code and/or set up an SBT project then you can check out the full tutorial from the Akka GitHub repository. It is in the ``akka-tutorials/akka-tutorial-first`` module. You can also browse it online `here `_, with the actual source code `here `_. + +Prerequisites +------------- This tutorial assumes that you have Jave 1.6 or later installed on you machine and ``java`` on your ``PATH``. You also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a decent text editor or IDE to type in the Scala code in. @@ -33,7 +38,7 @@ Downloading and installing Akka If you want to be able to build and run the tutorial sample from the command line then you have to download Akka. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. -Let's get the 1.1 distribution from `http://akka.io/downloads `_. Once you have downloaded the distribution unzip it in the folder you would like to have Akka installed in, in my case I choose to install it in ``/Users/jboner/tools/``, simply by unzipping it to this directory. +Let's get the ``akka-1.1`` distribution of Akka core (not Akka Modules) from `http://akka.io/downloads `_. Once you have downloaded the distribution unzip it in the folder you would like to have Akka installed in, in my case I choose to install it in ``/Users/jboner/tools/``, simply by unzipping it to this directory. You need to do one more thing in order to install Akka properly and that is to set the ``AKKA_HOME`` environment variable to the root of the distribution. In my case I'm opening up a shell and navigating down to the distribution and setting the ``AKKA_HOME`` variable:: @@ -89,9 +94,9 @@ If you want to be able to build and run the tutorial sample from the command lin Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0 final release. If you pick the ``tgz`` or ``zip`` distributions then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. -You also need to make sure that the ``scala-2.9.0/bin`` (if that is the directory where you installed Scala) is on your ``PATH``:: +You also need to make sure that the ``scala-2.9.0-final/bin`` (if that is the directory where you installed Scala) is on your ``PATH``:: - $ export PATH=$PATH:scala-2.9.0/bin + $ export PATH=$PATH:scala-2.9.0-final/bin Now you can test you installation by invoking and see the printout:: @@ -105,26 +110,84 @@ Some tools requires you to set the ``SCALA_HOME`` environment variable to the ro Downloading and installing SBT ------------------------------ -SBT, short for 'Simple Build Tool' is an excellent build system written in Scala. You are using Scala to write the build scripts which gives you a lot of power. It has a plugin architecture with many plugins available, something that we will take advantage of soon. SBT is the preferred way of building software in Scala. If you want to use SBT for this tutorial then follow the following instructions, if not you can skip this section. +SBT, short for 'Simple Build Tool' is an excellent build system written in Scala. You are using Scala to write the build scripts which gives you a lot of power. It has a plugin architecture with many plugins available, something that we will take advantage of soon. SBT is the preferred way of building software in Scala. If you want to use SBT for this tutorial then follow the following instructions, if not you can skip this section and the next. To install SBT and create a project for this tutorial it is easiest to follow the instructions on `this page `_. The preferred SBT version to install is ``0.7.6``. -If you have created an SBT project then step into the newly created SBT project, create a source file ``Pi.scala`` for the tutorial sample and put it in the ``src/main/scala/`` directory. +If you have created an SBT project then step into the newly created SBT project, create a source file ``Pi.scala`` for the tutorial sample and put it in the ``src/main/scala`` directory. -So far we only have a standard Scala project but now we need to make our project an Akka project. You could add the dependencies manually to the build script, but the easiest way is to use Akka's SBT Plugin. +So far we only have a standard Scala project but now we need to make our project an Akka project. You could add the dependencies manually to the build script, but the easiest way is to use Akka's SBT Plugin, covered in the next section. -TODO: write up about Akka's SBT Plugin +Creating an Akka SBT project +---------------------------- -Now you need to make SBT download all dependencies it needs. That is done by invoking:: +If you have not already done so, now is the time to create an SBT project for our tutorial. You do that by stepping into the directory you want to create your project in and invoking the ``sbt`` command answering the questions for setting up your project (just pressing ENTER will choose the default in square brackets):: + + $ sbt + Project does not exist, create new project? (y/N/s) y + Name: Tutorial 1 + Organization: Hakkers Inc + Version [1.0]: + Scala version [2.9.0]: + sbt version [0.7.6]: + +Now we have the basis for an SBT project. Akka has an SBT Plugin that makes it very easy to use Akka is an SBT-based project so let's use that. + +To use the plugin, first add a plugin definition to your SBT project by creating a ``Plugins.scala`` file in the ``project/plugins`` directory containing:: + + import sbt._ + + class Plugins(info: ProjectInfo) extends PluginDefinition(info) { + val akkaRepo = "Akka Repo" at "http://akka.io/repository" + val akkaPlugin = "se.scalablesolutions.akka" % "akka-sbt-plugin" % "1.1" + } + +Now we need to create a project definition using our Akka SBT plugin. We do that by creating a ``Project.scala`` file in the ``build`` directory containing:: + + class TutorialOneProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject + +The magic is in mixing in the ``AkkaProject`` trait. + +Not needed in this tutorial, but if you would like to use additional Akka modules than ``akka-actor`` then you can add these as "module configurations" in the project file. Here is an example adding ``akka-remote`` and ``akka-stm``:: + + class AkkaSampleProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject { + val akkaSTM = akkaModule("stm") + val akkaRemote = akkaModule("remote") + } + +So, now we are all set. Just one final thing to do; make SBT download all dependencies it needs. That is done by invoking:: $ sbt update SBT itself needs a whole bunch of dependencies but our project will only need one; ``akka-actor-1.1.jar``. SBT downloads that as well. +Imports needed for the tutorial code +------------------------------------ + +Now let's start hacking. + +We start by creating a ``Pi.scala`` file and add these import statements at the top of the file:: + + package akka.tutorial.scala.first + + import akka.actor.{Actor, ActorRef, PoisonPill} + import Actor._ + import akka.routing.{Routing, CyclicIterator} + import Routing._ + import akka.dispatch.Dispatchers + + import java.util.concurrent.CountDownLatch + +If you are using SBT in this tutorial then create the file in the ``src/main/scala`` directory. + +If you are using the command line tools then just create the file wherever you want, I will create it in a directory called ``tutorial`` the root of the Akka distribution, e.g. in ``$AKKA_HOME/tutorial/Pi.scala``. + Creating the messages --------------------- -First we need to create the messages is that we want to have flowing in the system. Let's create three different messages: +The design we are aiming for is to have one ``Master`` actor initiating the computation, creating a set of ``Worker`` actors. Then it splits up the work into discrete chunks, sends out these work chunks to the different workers in a round-robin fashion. The master then waits until all the workers have completed all the work and sent back the result for aggregation. When computation is completed the master prints out the result, shuts down all workers an then himself. + +With this in mind, let's now create the messages that we want to have flowing in the system. We need three different messages: - ``Calculate`` -- starts the calculation - ``Work`` -- contains the work assignment @@ -154,21 +217,15 @@ Now we can create the worker actor. This is done by mixing in the ``Actor`` tra As you can see we have now created an ``Actor`` with a ``receive`` method that as a handler for the ``Work`` message. In this handler we invoke the ``calculatePiFor(..)`` method, wraps the result in a ``Result`` message and sends it back to the original sender using ``self.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 use. -The only thing missing in our ``Worker`` actor is the implementation on the ``calculatePiFor(..)`` method. There are many ways we can implement this algorithm in Scala, now let's try to balance functional programming with efficiency and use a tail recursive function:: +The only thing missing in our ``Worker`` actor is the implementation on the ``calculatePiFor(..)`` method. There are many ways we can implement this algorithm in Scala, in this introductory tutorial we have chosen an imperative style using a for comprehension and an accumulator:: - def calculatePiFor(arg: Int, nrOfElements: Int): Double = { - val end = (arg + 1) * nrOfElements - 1 - - @tailrec def doCalculatePiFor(cursor: Int, acc: Double): Double = { - if (end == cursor) acc - else doCalculatePiFor(cursor + 1, acc + (4 * math.pow(-1, cursor) / (2 * cursor + 1))) - } - - doCalculatePiFor(arg * nrOfElements, 0.0D) + def calculatePiFor(start: Int, elems: Int): Double = { + var acc = 0.0 + for (i <- start until (start + elems)) + acc += 4 * math.pow(-1, i) / (2 * i + 1) + acc } -Here we use the classic trick with a local nested method to make sure that the compiler can perform a tail call optimization. We can ensure that the compiler will be able to do that by annotate tail recursive function with ``@tailrec``, with this annotation the compiler will emit an error if it can optimize it. With this implementation the calculation is really fast. - Creating the master ------------------- @@ -207,13 +264,13 @@ Let's now write the master actor:: def receive = { ... } - override def preStart = start = now + override def preStart = start = System.currentTimeMillis override def postStop = { // tell the world that the calculation is complete println( "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" - .format(pi, (now - start))) + .format(pi, (System.currentTimeMillis - start))) latch.countDown } } @@ -239,7 +296,7 @@ Now, let's capture this in code:: def receive = { case Calculate => // schedule work - for (arg <- 0 until nrOfMessages) router ! Work(arg, nrOfElements) + for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) // send a PoisonPill to all workers telling them to shut down themselves router ! Broadcast(PoisonPill) @@ -293,11 +350,8 @@ But before we package it up and run it, let's take a look at the full code now, import Routing._ import akka.dispatch.Dispatchers - import System.{currentTimeMillis => now} import java.util.concurrent.CountDownLatch - import scala.annotation.tailrec - object Pi extends App { calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) @@ -316,13 +370,11 @@ But before we package it up and run it, let's take a look at the full code now, class Worker extends Actor { // define the work - def calculatePiFor(arg: Int, nrOfElements: Int): Double = { - val end = (arg + 1) * nrOfElements - 1 - @tailrec def doCalculatePiFor(cursor: Int, acc: Double): Double = { - if (end == cursor) acc - else doCalculatePiFor(cursor + 1, acc + (4 * math.pow(-1, cursor) / (2 * cursor + 1))) - } - doCalculatePiFor(arg * nrOfElements, 0.0D) + def calculatePiFor(start: Int, elems: Int): Double = { + var acc = 0.0 + for (i <- start until (start + elems)) + acc += 4 * math.pow(-1, i) / (2 * i + 1) + acc } def receive = { @@ -351,7 +403,7 @@ But before we package it up and run it, let's take a look at the full code now, def receive = { case Calculate => // schedule work - for (arg <- 0 until nrOfMessages) router ! Work(arg, nrOfElements) + for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) // send a PoisonPill to all workers telling them to shut down themselves router ! Broadcast(PoisonPill) @@ -366,13 +418,13 @@ But before we package it up and run it, let's take a look at the full code now, if (nrOfResults == nrOfMessages) self.stop } - override def preStart = start = now + override def preStart = start = System.currentTimeMillis override def postStop = { // tell the world that the calculation is complete println( "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" - .format(pi, (now - start))) + .format(pi, (System.currentTimeMillis - start))) latch.countDown } } diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index 1ee7b3bbd6..568a4a092f 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -13,8 +13,6 @@ import akka.dispatch.Dispatchers import System.{currentTimeMillis => now} import java.util.concurrent.CountDownLatch -import scala.annotation.tailrec - /** * First part in Akka tutorial. *

@@ -57,26 +55,13 @@ object Pi extends App { // ===== Worker ===== // ================== class Worker extends Actor { - // define the work -/* - // FIXME tail-recursive fun instead - val calculatePiFor = (arg: Int, nrOfElements: Int) => { - val range = (arg * nrOfElements) until ((arg + 1) * nrOfElements) - var acc = 0.0D - range foreach (i => acc += 4 * math.pow(-1, i) / (2 * i + 1)) + // define the work + def calculatePiFor(start: Int, elems: Int): Double = { + var acc = 0.0 + for (i <- start until (start + elems)) + acc += 4 * math.pow(-1, i) / (2 * i + 1) acc - // Use this for more functional style but is twice as slow - // range.foldLeft(0.0D)( (acc, i) => acc + 4 * math.pow(-1, i) / (2 * i + 1) ) - } -*/ - def calculatePiFor(arg: Int, nrOfElements: Int): Double = { - val end = (arg + 1) * nrOfElements - @tailrec def doCalculatePiFor(cursor: Int, acc: Double): Double = { - if (cursor == end) acc - else doCalculatePiFor(cursor + 1, acc + (4 * math.pow(-1, cursor) / (2 * cursor + 1))) - } - doCalculatePiFor(arg * nrOfElements, 0.0D) } def receive = { @@ -88,7 +73,9 @@ object Pi extends App { // ================== // ===== Master ===== // ================== - class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) extends Actor { + class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) + extends Actor { + var pi: Double = _ var nrOfResults: Int = _ var start: Long = _ @@ -103,7 +90,8 @@ object Pi extends App { def receive = { case Calculate => // schedule work - for (arg <- 0 until nrOfMessages) router ! Work(arg, nrOfElements) + //for (arg <- 0 until nrOfMessages) router ! Work(arg, nrOfElements) + for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) // send a PoisonPill to all workers telling them to shut down themselves router ! Broadcast(PoisonPill) From 58ddf8b66198d7fcd930ce1ec36cd533235f5b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Sat, 9 Apr 2011 05:30:44 -0700 Subject: [PATCH 027/147] Changed a sub-heading in the tutorial --- akka-docs/manual/getting-started-first.rst | 1022 ++++++++++---------- 1 file changed, 511 insertions(+), 511 deletions(-) mode change 100755 => 100644 akka-docs/manual/getting-started-first.rst diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst old mode 100755 new mode 100644 index 36918037a0..7cb00bff28 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -1,511 +1,511 @@ -Getting Started Tutorial: First Chapter -======================================= - -Introduction ------------- - -Welcome to the first tutorial on how to get started with Akka and Scala. We assume that you already know what Akka and Scala is and will now focus on the steps necessary to start your first project. - -There are two variations of this first tutorial: - -- creating a standalone project and run it from the command line -- creating a SBT (Simple Build Tool) project and running it from within SBT - -Since they are so similar we will present them both in this tutorial. - -The sample application that we will create is using actors to calculate the value of Pi. Calculating Pi is a CPU intensive operation and we will utilize Akka Actors to write a concurrent solution that scales out to multi-core processors. This sample will be extended in future tutorials to use Akka Remote Actors to scale out on multiple machines in a cluster. - -We will be using an algorithm that is what is called "embarrassingly parallel" which just means that each job is completely isolated and not coupled with any other job. Since this algorithm is so parallelizable it suits the actor model very well. - -Here is the formula for the algorithm we will use: - -.. image:: pi-formula.png - -In this particular algorithm the master splits the series into chunks which are sent out to each worker actor to be processed, when each worker has processed its chunk it sends a result back to the master which aggregates to total result. - -Tutorial source code --------------------- - -If you want don't want to type in the code and/or set up an SBT project then you can check out the full tutorial from the Akka GitHub repository. It is in the ``akka-tutorials/akka-tutorial-first`` module. You can also browse it online `here `_, with the actual source code `here `_. - -Prerequisites -------------- - -This tutorial assumes that you have Jave 1.6 or later installed on you machine and ``java`` on your ``PATH``. You also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a decent text editor or IDE to type in the Scala code in. - -Downloading and installing Akka -------------------------------- - -If you want to be able to build and run the tutorial sample from the command line then you have to download Akka. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. - -Let's get the ``akka-1.1`` distribution of Akka core (not Akka Modules) from `http://akka.io/downloads `_. Once you have downloaded the distribution unzip it in the folder you would like to have Akka installed in, in my case I choose to install it in ``/Users/jboner/tools/``, simply by unzipping it to this directory. - -You need to do one more thing in order to install Akka properly and that is to set the ``AKKA_HOME`` environment variable to the root of the distribution. In my case I'm opening up a shell and navigating down to the distribution and setting the ``AKKA_HOME`` variable:: - - $ cd /Users/jboner/tools/akka-1.1 - $ export AKKA_HOME=`pwd` - $ echo $AKKA_HOME - /Users/jboner/tools/akka-1.1 - -If we now take a look at what we have in this distribution, looks like this:: - - $ ls -l - total 16944 - drwxr-xr-x 7 jboner staff 238 Apr 6 11:15 . - drwxr-xr-x 28 jboner staff 952 Apr 6 11:16 .. - drwxr-xr-x 17 jboner staff 578 Apr 6 11:16 deploy - drwxr-xr-x 26 jboner staff 884 Apr 6 11:16 dist - drwxr-xr-x 3 jboner staff 102 Apr 6 11:15 lib_managed - -rwxr-xr-x 1 jboner staff 8674105 Apr 6 11:15 scala-library.jar - drwxr-xr-x 4 jboner staff 136 Apr 6 11:16 scripts - -- In the ``dist`` directory we have all the Akka JARs, including sources and docs. -- In the ``lib_managed/compile`` directory we have all the Akka's dependency JARs. -- In the ``deploy`` directory we have all the sample JARs. -- In the ``scripts`` directory we have scripts for running Akka. -- Finallly the ``scala-library.jar`` is the JAR for the latest Scala distribution that Akka depends on. - -The only JAR we will need for this tutorial (apart from the ``scala-library.jar`` JAR) is the ``akka-actor-1.1.jar`` JAR in the ``dist`` directory. This is a self-contained JAR with zero dependencies and contains everything we need to write a system using Actors. - -Akka is very modular and has many JARs for containing different features. The core distribution has seven modules: - -- ``akka-actor-1.1.jar`` -- Standard Actors -- ``akka-typed-actor-1.1.jar`` -- Typed Actors -- ``akka-remote-1.1.jar`` -- Remote Actors -- ``akka-stm-1.1.jar`` -- STM (Software Transactional Memory) and transactional datastructures -- ``akka-http-1.1.jar`` -- Akka Mist for continuation-based asynchronous HTTP and also Jersey integration -- ``akka-slf4j-1.1.jar`` -- SLF4J Event Handler Listener -- ``akka-testkit-1.1.jar`` -- Toolkit for testing Actors - -We also have Akka Modules containing add-on modules for the core of Akka. You can download the Akka Modules distribution from TODO. It contains Akka core as well. We will not be needing any modules there today but for your information the module JARs are these: - -- ``akka-kernel-1.1.jar`` -- Akka microkernel for running a bare-bones mini application server (embeds Jetty etc.) -- ``akka-amqp-1.1.jar`` -- AMQP integration -- ``akka-camel-1.1.jar`` -- Apache Camel Actors integration (it's the best way to have your Akka application communicate with the rest of the world) -- ``akka-camel-typed-1.1.jar`` -- Apache Camel Typed Actors integration -- ``akka-scalaz-1.1.jar`` -- Support for the Scalaz library -- ``akka-spring-1.1.jar`` -- Spring framework integration -- ``akka-osgi-dependencies-bundle-1.1.jar`` -- OSGi support - -Downloading and installing Scala --------------------------------- - -If you want to be able to build and run the tutorial sample from the command line then you have to install the Scala distribution. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. - -Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0 final release. If you pick the ``tgz`` or ``zip`` distributions then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. - -You also need to make sure that the ``scala-2.9.0-final/bin`` (if that is the directory where you installed Scala) is on your ``PATH``:: - - $ export PATH=$PATH:scala-2.9.0-final/bin - -Now you can test you installation by invoking and see the printout:: - - $ scala -version - Scala code runner version 2.9.0.final -- Copyright 2002-2011, LAMP/EPFL - -Looks like we are all good. Finally let's create a source file ``Pi.scala`` for the tutorial and put it in the root of the Akka distribution in the ``tutorial`` directory (you have to create it first). - -Some tools requires you to set the ``SCALA_HOME`` environment variable to the root of the Scala distribution, however Akka does not require that. - -Downloading and installing SBT ------------------------------- - -SBT, short for 'Simple Build Tool' is an excellent build system written in Scala. You are using Scala to write the build scripts which gives you a lot of power. It has a plugin architecture with many plugins available, something that we will take advantage of soon. SBT is the preferred way of building software in Scala. If you want to use SBT for this tutorial then follow the following instructions, if not you can skip this section and the next. - -To install SBT and create a project for this tutorial it is easiest to follow the instructions on `this page `_. The preferred SBT version to install is ``0.7.6``. - -If you have created an SBT project then step into the newly created SBT project, create a source file ``Pi.scala`` for the tutorial sample and put it in the ``src/main/scala`` directory. - -So far we only have a standard Scala project but now we need to make our project an Akka project. You could add the dependencies manually to the build script, but the easiest way is to use Akka's SBT Plugin, covered in the next section. - -Creating an Akka SBT project ----------------------------- - -If you have not already done so, now is the time to create an SBT project for our tutorial. You do that by stepping into the directory you want to create your project in and invoking the ``sbt`` command answering the questions for setting up your project (just pressing ENTER will choose the default in square brackets):: - - $ sbt - Project does not exist, create new project? (y/N/s) y - Name: Tutorial 1 - Organization: Hakkers Inc - Version [1.0]: - Scala version [2.9.0]: - sbt version [0.7.6]: - -Now we have the basis for an SBT project. Akka has an SBT Plugin that makes it very easy to use Akka is an SBT-based project so let's use that. - -To use the plugin, first add a plugin definition to your SBT project by creating a ``Plugins.scala`` file in the ``project/plugins`` directory containing:: - - import sbt._ - - class Plugins(info: ProjectInfo) extends PluginDefinition(info) { - val akkaRepo = "Akka Repo" at "http://akka.io/repository" - val akkaPlugin = "se.scalablesolutions.akka" % "akka-sbt-plugin" % "1.1" - } - -Now we need to create a project definition using our Akka SBT plugin. We do that by creating a ``Project.scala`` file in the ``build`` directory containing:: - - class TutorialOneProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject - -The magic is in mixing in the ``AkkaProject`` trait. - -Not needed in this tutorial, but if you would like to use additional Akka modules than ``akka-actor`` then you can add these as "module configurations" in the project file. Here is an example adding ``akka-remote`` and ``akka-stm``:: - - class AkkaSampleProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject { - val akkaSTM = akkaModule("stm") - val akkaRemote = akkaModule("remote") - } - -So, now we are all set. Just one final thing to do; make SBT download all dependencies it needs. That is done by invoking:: - - $ sbt update - -SBT itself needs a whole bunch of dependencies but our project will only need one; ``akka-actor-1.1.jar``. SBT downloads that as well. - -Imports needed for the tutorial code ------------------------------------- - -Now let's start hacking. - -We start by creating a ``Pi.scala`` file and add these import statements at the top of the file:: - - package akka.tutorial.scala.first - - import akka.actor.{Actor, ActorRef, PoisonPill} - import Actor._ - import akka.routing.{Routing, CyclicIterator} - import Routing._ - import akka.dispatch.Dispatchers - - import java.util.concurrent.CountDownLatch - -If you are using SBT in this tutorial then create the file in the ``src/main/scala`` directory. - -If you are using the command line tools then just create the file wherever you want, I will create it in a directory called ``tutorial`` the root of the Akka distribution, e.g. in ``$AKKA_HOME/tutorial/Pi.scala``. - -Creating the messages ---------------------- - -The design we are aiming for is to have one ``Master`` actor initiating the computation, creating a set of ``Worker`` actors. Then it splits up the work into discrete chunks, sends out these work chunks to the different workers in a round-robin fashion. The master then waits until all the workers have completed all the work and sent back the result for aggregation. When computation is completed the master prints out the result, shuts down all workers an then himself. - -With this in mind, let's now create the messages that we want to have flowing in the system. We need three different messages: - -- ``Calculate`` -- starts the calculation -- ``Work`` -- contains the work assignment -- ``Result`` -- contains the result from the worker's calculation - -Messages sent to actors should always be immutable to avoid sharing mutable state. In scala we have 'case classes' which make excellent messages. So let's start by creating three messages as case classes. We also create a common base trait for our messages (that we define as being ``sealed`` in order to prevent creating messages outside our control):: - - sealed trait PiMessage - - case object Calculate extends PiMessage - - case class Work(arg: Int, nrOfElements: Int) extends PiMessage - - case class Result(value: Double) extends PiMessage - -Creating the worker -------------------- - -Now we can create the worker actor. This is done by mixing in the ``Actor`` trait and defining the ``receive`` method. The ``receive`` method defines our message handler. We expect it to be able to handle the ``Work`` message so we need to add a handler for this message:: - - class Worker extends Actor { - def receive = { - case Work(arg, nrOfElements) => - self reply Result(calculatePiFor(arg, nrOfElements)) // perform the work - } - } - -As you can see we have now created an ``Actor`` with a ``receive`` method that as a handler for the ``Work`` message. In this handler we invoke the ``calculatePiFor(..)`` method, wraps the result in a ``Result`` message and sends it back to the original sender using ``self.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 use. - -The only thing missing in our ``Worker`` actor is the implementation on the ``calculatePiFor(..)`` method. There are many ways we can implement this algorithm in Scala, in this introductory tutorial we have chosen an imperative style using a for comprehension and an accumulator:: - - def calculatePiFor(start: Int, elems: Int): Double = { - var acc = 0.0 - for (i <- start until (start + elems)) - acc += 4 * math.pow(-1, i) / (2 * i + 1) - acc - } - -Creating the master -------------------- - -The master actor is a little bit more involved. In its constructor we need to create the workers (the ``Worker`` actors) and start them. We will also wrap them in a load-balancing router to make it easier to spread out the work evenly between the workers. Let's do that first:: - - // create the workers - val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start) - - // wrap them with a load-balancing router - val router = Routing.loadBalancerActor(CyclicIterator(workers)).start - -As you can see we are using the ``actorOf`` factory method to create actors, this method returns as an ``ActorRef`` which is a reference to our newly created actor. This method is available in the ``Actor`` object but is usually imported:: - - import akka.actor.Actor._ - -Now we have a router that is representing all our workers in a single abstraction. If you paid attention to the code above to see that we were using the ``nrOfWorkers`` variable. This variable and others we have to pass to the ``Master`` actor in its constructor. So now let's create the master actor. We had to pass in three integer variables needed: - -- ``nrOfWorkers`` -- defining how many workers we should start up -- ``nrOfMessages`` -- defining how many number chunks should send out to the workers -- ``nrOfElements`` -- defining how big the number chunks sent to each worker should be - -Let's now write the master actor:: - - class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) - extends Actor { - - var pi: Double = _ - var nrOfResults: Int = _ - var start: Long = _ - - // create the workers - val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start) - - // wrap them with a load-balancing router - val router = Routing.loadBalancerActor(CyclicIterator(workers)).start - - def receive = { ... } - - override def preStart = start = System.currentTimeMillis - - override def postStop = { - // tell the world that the calculation is complete - println( - "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" - .format(pi, (System.currentTimeMillis - start))) - latch.countDown - } - } - -Couple of things are worth explaining further. - -First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing, to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch. - -Second, we are adding a couple of life-cycle callback methods; ``preStart`` and ``postStop``. In the ``preStart`` callback we are recording the time when the actor is started and in the ``postStop`` callback we are printing out the result (the approximation of Pi) and the time it took to calculate it. In this call we also invoke ``latch.countDown`` to tell the outside world that we are done. - -But we are not done yet. We are missing the message handler for the ``Master`` actor. This message handler needs to be able to react to two different messages: - -- ``Calculate`` -- which should start the calculation -- ``Result`` -- which should aggregate the different results - -The ``Calculate`` handler is sending out work to all the ``Worker`` actors and after doing that it also sends a ``Broadcast(PoisonPill)`` message to the router, which will send out the ``PoisonPill`` message to all the actors it is representing (in our case all the ``Worker`` actors). The ``PoisonPill`` is a special kind of message that tells the receiver to shut himself down using the normal shutdown; ``self.stop``. Then we also send a ``PoisonPill`` to the router itself (since it's also an actor that we want to shut down). - -The ``Result`` handler is simpler, here we just get the value from the ``Result`` message and aggregate it to our ``pi`` member variable. We also keep track of how many results we have received back and if it matches the number of tasks sent out the ``Master`` actor considers itself done and shuts himself down. - -Now, let's capture this in code:: - - // message handler - def receive = { - case Calculate => - // schedule work - for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) - - // send a PoisonPill to all workers telling them to shut down themselves - router ! Broadcast(PoisonPill) - - // send a PoisonPill to the router, telling him to shut himself down - router ! PoisonPill - - case Result(value) => - // handle result from the worker - pi += value - nrOfResults += 1 - if (nrOfResults == nrOfMessages) self.stop - } - -Bootstrap the calculation -------------------------- - -Now the only thing that is left to implement is the runner that should bootstrap and run his calculation for us. We do that by creating an object that we call ``Pi``, here we can extend the ``App`` trait in Scala which means that we will be able to run this as an application directly from the command line. The ``Pi`` object is a perfect container module for our actors and messages, so let's put them all there. We also create a method ``calculate`` in which we start up the ``Master`` actor and waits for it to finish:: - - object Pi extends App { - - calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) - - ... // actors and messages - - def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { - - // this latch is only plumbing to know when the calculation is completed - val latch = new CountDownLatch(1) - - // create the master - val master = actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start - - // start the calculation - master ! Calculate - - // wait for master to shut down - latch.await - } - } - -That's it. Now we are done. - -But before we package it up and run it, let's take a look at the full code now, with package declaration, imports and all of:: - - package akka.tutorial.scala.first - - import akka.actor.{Actor, ActorRef, PoisonPill} - import Actor._ - import akka.routing.{Routing, CyclicIterator} - import Routing._ - import akka.dispatch.Dispatchers - - import java.util.concurrent.CountDownLatch - - object Pi extends App { - - calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) - - // ==================== - // ===== Messages ===== - // ==================== - sealed trait PiMessage - case object Calculate extends PiMessage - case class Work(arg: Int, nrOfElements: Int) extends PiMessage - case class Result(value: Double) extends PiMessage - - // ================== - // ===== Worker ===== - // ================== - class Worker extends Actor { - - // define the work - def calculatePiFor(start: Int, elems: Int): Double = { - var acc = 0.0 - for (i <- start until (start + elems)) - acc += 4 * math.pow(-1, i) / (2 * i + 1) - acc - } - - def receive = { - case Work(arg, nrOfElements) => - self reply Result(calculatePiFor(arg, nrOfElements)) // perform the work - } - } - - // ================== - // ===== Master ===== - // ================== - class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) - extends Actor { - - var pi: Double = _ - var nrOfResults: Int = _ - var start: Long = _ - - // create the workers - val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start) - - // wrap them with a load-balancing router - val router = Routing.loadBalancerActor(CyclicIterator(workers)).start - - // message handler - def receive = { - case Calculate => - // schedule work - for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) - - // send a PoisonPill to all workers telling them to shut down themselves - router ! Broadcast(PoisonPill) - - // send a PoisonPill to the router, telling him to shut himself down - router ! PoisonPill - - case Result(value) => - // handle result from the worker - pi += value - nrOfResults += 1 - if (nrOfResults == nrOfMessages) self.stop - } - - override def preStart = start = System.currentTimeMillis - - override def postStop = { - // tell the world that the calculation is complete - println( - "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" - .format(pi, (System.currentTimeMillis - start))) - latch.countDown - } - } - - // ================== - // ===== Run it ===== - // ================== - def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { - - // this latch is only plumbing to know when the calculation is completed - val latch = new CountDownLatch(1) - - // create the master - val master = actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start - - // start the calculation - master ! Calculate - - // wait for master to shut down - latch.await - } - } - -Run it as a command line application ------------------------------------- - -If you have not typed (or copied) in the code for the tutorial in the ``$AKKA_HOME/tutorial/Pi.scala`` then now is the time. When that is done open up a shell and step in to the Akka distribution (``cd $AKKA_HOME``). - -First we need to compile the source file. That is done with Scala's compiler ``scalac``. Our application depends on the ``akka-actor-1.1.jar`` JAR file, so let's add that to the compiler classpath when we compile the source:: - - $ scalac -cp dist/akka-actor-1.1.jar tutorial/Pi.scala - -When we have compiled the source file we are ready to run the application. This is done with ``java`` but yet again we need to add the ``akka-actor-1.1.jar`` JAR file to the classpath, this time we also need to add the Scala runtime library ``scala-library.jar`` and the classes we compiled ourselves to the classpath:: - - $ java -cp dist/akka-actor-1.1.jar:scala-library.jar:tutorial akka.tutorial.scala.first.Pi - AKKA_HOME is defined as [/Users/jboner/src/akka-stuff/akka-core], loading config from \ - [/Users/jboner/src/akka-stuff/akka-core/config/akka.conf]. - - Pi estimate: 3.1435501812459323 - Calculation time: 858 millis - -Yippee! It is working. - -Run it inside SBT ------------------ - -If you have based the tutorial on SBT then you can run the application directly inside SBT. First you need to compile the project:: - - $ sbt - > update - ... - > compile - ... - -When this in done we can start up a Scala REPL (console/interpreter) directly inside SBT with our dependencies and classes on the classpath:: - - > console - ... - scala> - -In this REPL we can now evaluate Scala code. For example run our application:: - - scala> akka.tutorial.scala.first.Pi.calculate( - | nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) - AKKA_HOME is defined as [/Users/jboner/src/akka-stuff/akka-core], loading config from \ - [/Users/jboner/src/akka-stuff/akka-core/config/akka.conf]. - - Pi estimate: 3.1435501812459323 - Calculation time: 942 millis - -See it complete the calculation and print out the result. When that is done we can exit the REPL:: - - > :quit - -Yippee! It is working. - -Conclusion ----------- - -Now we have learned how to create our first Akka project utilizing Akka's actors to speed up a computation intensive problem by scaling out on multi-core processors (also known as scaling up). We have also learned how to compile and run an Akka project utilizing either the tools on the command line or the SBT build system. - -Now we are ready to take on more advanced problems. In the next tutorial we will build upon this one, refactor it into more idiomatic Akka and Scala code and introduce a few new concepts and abstractions. Whenever you feel ready, join me in the `Getting Started Tutorial: Second Chapter `_. - -Happy hakking. +Getting Started Tutorial: First Chapter +======================================= + +Introduction +------------ + +Welcome to the first tutorial on how to get started with Akka and Scala. We assume that you already know what Akka and Scala is and will now focus on the steps necessary to start your first project. + +There are two variations of this first tutorial: + +- creating a standalone project and run it from the command line +- creating a SBT (Simple Build Tool) project and running it from within SBT + +Since they are so similar we will present them both in this tutorial. + +The sample application that we will create is using actors to calculate the value of Pi. Calculating Pi is a CPU intensive operation and we will utilize Akka Actors to write a concurrent solution that scales out to multi-core processors. This sample will be extended in future tutorials to use Akka Remote Actors to scale out on multiple machines in a cluster. + +We will be using an algorithm that is what is called "embarrassingly parallel" which just means that each job is completely isolated and not coupled with any other job. Since this algorithm is so parallelizable it suits the actor model very well. + +Here is the formula for the algorithm we will use: + +.. image:: pi-formula.png + +In this particular algorithm the master splits the series into chunks which are sent out to each worker actor to be processed, when each worker has processed its chunk it sends a result back to the master which aggregates to total result. + +Tutorial source code +-------------------- + +If you want don't want to type in the code and/or set up an SBT project then you can check out the full tutorial from the Akka GitHub repository. It is in the ``akka-tutorials/akka-tutorial-first`` module. You can also browse it online `here `_, with the actual source code `here `_. + +Prerequisites +------------- + +This tutorial assumes that you have Jave 1.6 or later installed on you machine and ``java`` on your ``PATH``. You also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a decent text editor or IDE to type in the Scala code in. + +Downloading and installing Akka +------------------------------- + +If you want to be able to build and run the tutorial sample from the command line then you have to download Akka. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. + +Let's get the ``akka-1.1`` distribution of Akka core (not Akka Modules) from `http://akka.io/downloads `_. Once you have downloaded the distribution unzip it in the folder you would like to have Akka installed in, in my case I choose to install it in ``/Users/jboner/tools/``, simply by unzipping it to this directory. + +You need to do one more thing in order to install Akka properly and that is to set the ``AKKA_HOME`` environment variable to the root of the distribution. In my case I'm opening up a shell and navigating down to the distribution and setting the ``AKKA_HOME`` variable:: + + $ cd /Users/jboner/tools/akka-1.1 + $ export AKKA_HOME=`pwd` + $ echo $AKKA_HOME + /Users/jboner/tools/akka-1.1 + +If we now take a look at what we have in this distribution, looks like this:: + + $ ls -l + total 16944 + drwxr-xr-x 7 jboner staff 238 Apr 6 11:15 . + drwxr-xr-x 28 jboner staff 952 Apr 6 11:16 .. + drwxr-xr-x 17 jboner staff 578 Apr 6 11:16 deploy + drwxr-xr-x 26 jboner staff 884 Apr 6 11:16 dist + drwxr-xr-x 3 jboner staff 102 Apr 6 11:15 lib_managed + -rwxr-xr-x 1 jboner staff 8674105 Apr 6 11:15 scala-library.jar + drwxr-xr-x 4 jboner staff 136 Apr 6 11:16 scripts + +- In the ``dist`` directory we have all the Akka JARs, including sources and docs. +- In the ``lib_managed/compile`` directory we have all the Akka's dependency JARs. +- In the ``deploy`` directory we have all the sample JARs. +- In the ``scripts`` directory we have scripts for running Akka. +- Finallly the ``scala-library.jar`` is the JAR for the latest Scala distribution that Akka depends on. + +The only JAR we will need for this tutorial (apart from the ``scala-library.jar`` JAR) is the ``akka-actor-1.1.jar`` JAR in the ``dist`` directory. This is a self-contained JAR with zero dependencies and contains everything we need to write a system using Actors. + +Akka is very modular and has many JARs for containing different features. The core distribution has seven modules: + +- ``akka-actor-1.1.jar`` -- Standard Actors +- ``akka-typed-actor-1.1.jar`` -- Typed Actors +- ``akka-remote-1.1.jar`` -- Remote Actors +- ``akka-stm-1.1.jar`` -- STM (Software Transactional Memory) and transactional datastructures +- ``akka-http-1.1.jar`` -- Akka Mist for continuation-based asynchronous HTTP and also Jersey integration +- ``akka-slf4j-1.1.jar`` -- SLF4J Event Handler Listener +- ``akka-testkit-1.1.jar`` -- Toolkit for testing Actors + +We also have Akka Modules containing add-on modules for the core of Akka. You can download the Akka Modules distribution from TODO. It contains Akka core as well. We will not be needing any modules there today but for your information the module JARs are these: + +- ``akka-kernel-1.1.jar`` -- Akka microkernel for running a bare-bones mini application server (embeds Jetty etc.) +- ``akka-amqp-1.1.jar`` -- AMQP integration +- ``akka-camel-1.1.jar`` -- Apache Camel Actors integration (it's the best way to have your Akka application communicate with the rest of the world) +- ``akka-camel-typed-1.1.jar`` -- Apache Camel Typed Actors integration +- ``akka-scalaz-1.1.jar`` -- Support for the Scalaz library +- ``akka-spring-1.1.jar`` -- Spring framework integration +- ``akka-osgi-dependencies-bundle-1.1.jar`` -- OSGi support + +Downloading and installing Scala +-------------------------------- + +If you want to be able to build and run the tutorial sample from the command line then you have to install the Scala distribution. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. + +Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0 final release. If you pick the ``tgz`` or ``zip`` distributions then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. + +You also need to make sure that the ``scala-2.9.0-final/bin`` (if that is the directory where you installed Scala) is on your ``PATH``:: + + $ export PATH=$PATH:scala-2.9.0-final/bin + +Now you can test you installation by invoking and see the printout:: + + $ scala -version + Scala code runner version 2.9.0.final -- Copyright 2002-2011, LAMP/EPFL + +Looks like we are all good. Finally let's create a source file ``Pi.scala`` for the tutorial and put it in the root of the Akka distribution in the ``tutorial`` directory (you have to create it first). + +Some tools requires you to set the ``SCALA_HOME`` environment variable to the root of the Scala distribution, however Akka does not require that. + +Downloading and installing SBT +------------------------------ + +SBT, short for 'Simple Build Tool' is an excellent build system written in Scala. You are using Scala to write the build scripts which gives you a lot of power. It has a plugin architecture with many plugins available, something that we will take advantage of soon. SBT is the preferred way of building software in Scala. If you want to use SBT for this tutorial then follow the following instructions, if not you can skip this section and the next. + +To install SBT and create a project for this tutorial it is easiest to follow the instructions on `this page `_. The preferred SBT version to install is ``0.7.6``. + +If you have created an SBT project then step into the newly created SBT project, create a source file ``Pi.scala`` for the tutorial sample and put it in the ``src/main/scala`` directory. + +So far we only have a standard Scala project but now we need to make our project an Akka project. You could add the dependencies manually to the build script, but the easiest way is to use Akka's SBT Plugin, covered in the next section. + +Creating an Akka SBT project +---------------------------- + +If you have not already done so, now is the time to create an SBT project for our tutorial. You do that by stepping into the directory you want to create your project in and invoking the ``sbt`` command answering the questions for setting up your project (just pressing ENTER will choose the default in square brackets):: + + $ sbt + Project does not exist, create new project? (y/N/s) y + Name: Tutorial 1 + Organization: Hakkers Inc + Version [1.0]: + Scala version [2.9.0]: + sbt version [0.7.6]: + +Now we have the basis for an SBT project. Akka has an SBT Plugin that makes it very easy to use Akka is an SBT-based project so let's use that. + +To use the plugin, first add a plugin definition to your SBT project by creating a ``Plugins.scala`` file in the ``project/plugins`` directory containing:: + + import sbt._ + + class Plugins(info: ProjectInfo) extends PluginDefinition(info) { + val akkaRepo = "Akka Repo" at "http://akka.io/repository" + val akkaPlugin = "se.scalablesolutions.akka" % "akka-sbt-plugin" % "1.1" + } + +Now we need to create a project definition using our Akka SBT plugin. We do that by creating a ``Project.scala`` file in the ``build`` directory containing:: + + class TutorialOneProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject + +The magic is in mixing in the ``AkkaProject`` trait. + +Not needed in this tutorial, but if you would like to use additional Akka modules than ``akka-actor`` then you can add these as "module configurations" in the project file. Here is an example adding ``akka-remote`` and ``akka-stm``:: + + class AkkaSampleProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject { + val akkaSTM = akkaModule("stm") + val akkaRemote = akkaModule("remote") + } + +So, now we are all set. Just one final thing to do; make SBT download all dependencies it needs. That is done by invoking:: + + $ sbt update + +SBT itself needs a whole bunch of dependencies but our project will only need one; ``akka-actor-1.1.jar``. SBT downloads that as well. + +Start writing the code +---------------------- + +Now it's about time that we start hacking. + +We start by creating a ``Pi.scala`` file and add these import statements at the top of the file:: + + package akka.tutorial.scala.first + + import akka.actor.{Actor, ActorRef, PoisonPill} + import Actor._ + import akka.routing.{Routing, CyclicIterator} + import Routing._ + import akka.dispatch.Dispatchers + + import java.util.concurrent.CountDownLatch + +If you are using SBT in this tutorial then create the file in the ``src/main/scala`` directory. + +If you are using the command line tools then just create the file wherever you want, I will create it in a directory called ``tutorial`` the root of the Akka distribution, e.g. in ``$AKKA_HOME/tutorial/Pi.scala``. + +Creating the messages +--------------------- + +The design we are aiming for is to have one ``Master`` actor initiating the computation, creating a set of ``Worker`` actors. Then it splits up the work into discrete chunks, sends out these work chunks to the different workers in a round-robin fashion. The master then waits until all the workers have completed all the work and sent back the result for aggregation. When computation is completed the master prints out the result, shuts down all workers an then himself. + +With this in mind, let's now create the messages that we want to have flowing in the system. We need three different messages: + +- ``Calculate`` -- starts the calculation +- ``Work`` -- contains the work assignment +- ``Result`` -- contains the result from the worker's calculation + +Messages sent to actors should always be immutable to avoid sharing mutable state. In scala we have 'case classes' which make excellent messages. So let's start by creating three messages as case classes. We also create a common base trait for our messages (that we define as being ``sealed`` in order to prevent creating messages outside our control):: + + sealed trait PiMessage + + case object Calculate extends PiMessage + + case class Work(arg: Int, nrOfElements: Int) extends PiMessage + + case class Result(value: Double) extends PiMessage + +Creating the worker +------------------- + +Now we can create the worker actor. This is done by mixing in the ``Actor`` trait and defining the ``receive`` method. The ``receive`` method defines our message handler. We expect it to be able to handle the ``Work`` message so we need to add a handler for this message:: + + class Worker extends Actor { + def receive = { + case Work(arg, nrOfElements) => + self reply Result(calculatePiFor(arg, nrOfElements)) // perform the work + } + } + +As you can see we have now created an ``Actor`` with a ``receive`` method that as a handler for the ``Work`` message. In this handler we invoke the ``calculatePiFor(..)`` method, wraps the result in a ``Result`` message and sends it back to the original sender using ``self.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 use. + +The only thing missing in our ``Worker`` actor is the implementation on the ``calculatePiFor(..)`` method. There are many ways we can implement this algorithm in Scala, in this introductory tutorial we have chosen an imperative style using a for comprehension and an accumulator:: + + def calculatePiFor(start: Int, elems: Int): Double = { + var acc = 0.0 + for (i <- start until (start + elems)) + acc += 4 * math.pow(-1, i) / (2 * i + 1) + acc + } + +Creating the master +------------------- + +The master actor is a little bit more involved. In its constructor we need to create the workers (the ``Worker`` actors) and start them. We will also wrap them in a load-balancing router to make it easier to spread out the work evenly between the workers. Let's do that first:: + + // create the workers + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start) + + // wrap them with a load-balancing router + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start + +As you can see we are using the ``actorOf`` factory method to create actors, this method returns as an ``ActorRef`` which is a reference to our newly created actor. This method is available in the ``Actor`` object but is usually imported:: + + import akka.actor.Actor._ + +Now we have a router that is representing all our workers in a single abstraction. If you paid attention to the code above to see that we were using the ``nrOfWorkers`` variable. This variable and others we have to pass to the ``Master`` actor in its constructor. So now let's create the master actor. We had to pass in three integer variables needed: + +- ``nrOfWorkers`` -- defining how many workers we should start up +- ``nrOfMessages`` -- defining how many number chunks should send out to the workers +- ``nrOfElements`` -- defining how big the number chunks sent to each worker should be + +Let's now write the master actor:: + + class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) + extends Actor { + + var pi: Double = _ + var nrOfResults: Int = _ + var start: Long = _ + + // create the workers + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start) + + // wrap them with a load-balancing router + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start + + def receive = { ... } + + override def preStart = start = System.currentTimeMillis + + override def postStop = { + // tell the world that the calculation is complete + println( + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" + .format(pi, (System.currentTimeMillis - start))) + latch.countDown + } + } + +Couple of things are worth explaining further. + +First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing, to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch. + +Second, we are adding a couple of life-cycle callback methods; ``preStart`` and ``postStop``. In the ``preStart`` callback we are recording the time when the actor is started and in the ``postStop`` callback we are printing out the result (the approximation of Pi) and the time it took to calculate it. In this call we also invoke ``latch.countDown`` to tell the outside world that we are done. + +But we are not done yet. We are missing the message handler for the ``Master`` actor. This message handler needs to be able to react to two different messages: + +- ``Calculate`` -- which should start the calculation +- ``Result`` -- which should aggregate the different results + +The ``Calculate`` handler is sending out work to all the ``Worker`` actors and after doing that it also sends a ``Broadcast(PoisonPill)`` message to the router, which will send out the ``PoisonPill`` message to all the actors it is representing (in our case all the ``Worker`` actors). The ``PoisonPill`` is a special kind of message that tells the receiver to shut himself down using the normal shutdown; ``self.stop``. Then we also send a ``PoisonPill`` to the router itself (since it's also an actor that we want to shut down). + +The ``Result`` handler is simpler, here we just get the value from the ``Result`` message and aggregate it to our ``pi`` member variable. We also keep track of how many results we have received back and if it matches the number of tasks sent out the ``Master`` actor considers itself done and shuts himself down. + +Now, let's capture this in code:: + + // message handler + def receive = { + case Calculate => + // schedule work + for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) + + // send a PoisonPill to all workers telling them to shut down themselves + router ! Broadcast(PoisonPill) + + // send a PoisonPill to the router, telling him to shut himself down + router ! PoisonPill + + case Result(value) => + // handle result from the worker + pi += value + nrOfResults += 1 + if (nrOfResults == nrOfMessages) self.stop + } + +Bootstrap the calculation +------------------------- + +Now the only thing that is left to implement is the runner that should bootstrap and run his calculation for us. We do that by creating an object that we call ``Pi``, here we can extend the ``App`` trait in Scala which means that we will be able to run this as an application directly from the command line. The ``Pi`` object is a perfect container module for our actors and messages, so let's put them all there. We also create a method ``calculate`` in which we start up the ``Master`` actor and waits for it to finish:: + + object Pi extends App { + + calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) + + ... // actors and messages + + def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { + + // this latch is only plumbing to know when the calculation is completed + val latch = new CountDownLatch(1) + + // create the master + val master = actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start + + // start the calculation + master ! Calculate + + // wait for master to shut down + latch.await + } + } + +That's it. Now we are done. + +But before we package it up and run it, let's take a look at the full code now, with package declaration, imports and all of:: + + package akka.tutorial.scala.first + + import akka.actor.{Actor, ActorRef, PoisonPill} + import Actor._ + import akka.routing.{Routing, CyclicIterator} + import Routing._ + import akka.dispatch.Dispatchers + + import java.util.concurrent.CountDownLatch + + object Pi extends App { + + calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) + + // ==================== + // ===== Messages ===== + // ==================== + sealed trait PiMessage + case object Calculate extends PiMessage + case class Work(arg: Int, nrOfElements: Int) extends PiMessage + case class Result(value: Double) extends PiMessage + + // ================== + // ===== Worker ===== + // ================== + class Worker extends Actor { + + // define the work + def calculatePiFor(start: Int, elems: Int): Double = { + var acc = 0.0 + for (i <- start until (start + elems)) + acc += 4 * math.pow(-1, i) / (2 * i + 1) + acc + } + + def receive = { + case Work(arg, nrOfElements) => + self reply Result(calculatePiFor(arg, nrOfElements)) // perform the work + } + } + + // ================== + // ===== Master ===== + // ================== + class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) + extends Actor { + + var pi: Double = _ + var nrOfResults: Int = _ + var start: Long = _ + + // create the workers + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start) + + // wrap them with a load-balancing router + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start + + // message handler + def receive = { + case Calculate => + // schedule work + for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) + + // send a PoisonPill to all workers telling them to shut down themselves + router ! Broadcast(PoisonPill) + + // send a PoisonPill to the router, telling him to shut himself down + router ! PoisonPill + + case Result(value) => + // handle result from the worker + pi += value + nrOfResults += 1 + if (nrOfResults == nrOfMessages) self.stop + } + + override def preStart = start = System.currentTimeMillis + + override def postStop = { + // tell the world that the calculation is complete + println( + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" + .format(pi, (System.currentTimeMillis - start))) + latch.countDown + } + } + + // ================== + // ===== Run it ===== + // ================== + def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { + + // this latch is only plumbing to know when the calculation is completed + val latch = new CountDownLatch(1) + + // create the master + val master = actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start + + // start the calculation + master ! Calculate + + // wait for master to shut down + latch.await + } + } + +Run it as a command line application +------------------------------------ + +If you have not typed (or copied) in the code for the tutorial in the ``$AKKA_HOME/tutorial/Pi.scala`` then now is the time. When that is done open up a shell and step in to the Akka distribution (``cd $AKKA_HOME``). + +First we need to compile the source file. That is done with Scala's compiler ``scalac``. Our application depends on the ``akka-actor-1.1.jar`` JAR file, so let's add that to the compiler classpath when we compile the source:: + + $ scalac -cp dist/akka-actor-1.1.jar tutorial/Pi.scala + +When we have compiled the source file we are ready to run the application. This is done with ``java`` but yet again we need to add the ``akka-actor-1.1.jar`` JAR file to the classpath, this time we also need to add the Scala runtime library ``scala-library.jar`` and the classes we compiled ourselves to the classpath:: + + $ java -cp dist/akka-actor-1.1.jar:scala-library.jar:tutorial akka.tutorial.scala.first.Pi + AKKA_HOME is defined as [/Users/jboner/src/akka-stuff/akka-core], loading config from \ + [/Users/jboner/src/akka-stuff/akka-core/config/akka.conf]. + + Pi estimate: 3.1435501812459323 + Calculation time: 858 millis + +Yippee! It is working. + +Run it inside SBT +----------------- + +If you have based the tutorial on SBT then you can run the application directly inside SBT. First you need to compile the project:: + + $ sbt + > update + ... + > compile + ... + +When this in done we can start up a Scala REPL (console/interpreter) directly inside SBT with our dependencies and classes on the classpath:: + + > console + ... + scala> + +In this REPL we can now evaluate Scala code. For example run our application:: + + scala> akka.tutorial.scala.first.Pi.calculate( + | nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) + AKKA_HOME is defined as [/Users/jboner/src/akka-stuff/akka-core], loading config from \ + [/Users/jboner/src/akka-stuff/akka-core/config/akka.conf]. + + Pi estimate: 3.1435501812459323 + Calculation time: 942 millis + +See it complete the calculation and print out the result. When that is done we can exit the REPL:: + + > :quit + +Yippee! It is working. + +Conclusion +---------- + +Now we have learned how to create our first Akka project utilizing Akka's actors to speed up a computation intensive problem by scaling out on multi-core processors (also known as scaling up). We have also learned how to compile and run an Akka project utilizing either the tools on the command line or the SBT build system. + +Now we are ready to take on more advanced problems. In the next tutorial we will build upon this one, refactor it into more idiomatic Akka and Scala code and introduce a few new concepts and abstractions. Whenever you feel ready, join me in the `Getting Started Tutorial: Second Chapter `_. + +Happy hakking. From fb1a248b5dbf1e7692ad186d0359f45d1191cd9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Sat, 9 Apr 2011 14:35:51 +0200 Subject: [PATCH 028/147] added more text about why we are using a 'latch' and alternatives to it --- akka-docs/manual/getting-started-first.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst index 7cb00bff28..8d41572471 100644 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -72,7 +72,7 @@ Akka is very modular and has many JARs for containing different features. The co - ``akka-actor-1.1.jar`` -- Standard Actors - ``akka-typed-actor-1.1.jar`` -- Typed Actors - ``akka-remote-1.1.jar`` -- Remote Actors -- ``akka-stm-1.1.jar`` -- STM (Software Transactional Memory) and transactional datastructures +- ``akka-stm-1.1.jar`` -- STM (Software Transactional Memory), transactors and transactional datastructures - ``akka-http-1.1.jar`` -- Akka Mist for continuation-based asynchronous HTTP and also Jersey integration - ``akka-slf4j-1.1.jar`` -- SLF4J Event Handler Listener - ``akka-testkit-1.1.jar`` -- Toolkit for testing Actors @@ -277,7 +277,7 @@ Let's now write the master actor:: Couple of things are worth explaining further. -First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing, to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch. +First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for doing plumbing (in this specific tutorial), to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch but other abstractions and functions like ``Channel``, ``Future`` and ``!!!`` to achive the same thing in a non-blocking way. But for simplicity let's stick to a ``CountDownLatch`` for now. Second, we are adding a couple of life-cycle callback methods; ``preStart`` and ``postStop``. In the ``preStart`` callback we are recording the time when the actor is started and in the ``postStop`` callback we are printing out the result (the approximation of Pi) and the time it took to calculate it. In this call we also invoke ``latch.countDown`` to tell the outside world that we are done. @@ -340,7 +340,7 @@ Now the only thing that is left to implement is the runner that should bootstrap That's it. Now we are done. -But before we package it up and run it, let's take a look at the full code now, with package declaration, imports and all of:: +But before we package it up and run it, let's take a look at the full code now, with package declaration, imports and all:: package akka.tutorial.scala.first From 4ab8bbea3d7e06c2320782c391d86dc55ac0a0af Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Sat, 9 Apr 2011 19:55:46 -0600 Subject: [PATCH 029/147] Add converted wiki pages to akka-docs --- akka-docs/index.rst | 69 +- .../pending/Feature Stability Matrix.rst | 31 + akka-docs/pending/Home.rst | 62 ++ akka-docs/pending/Migration-1.0-1.1.rst | 32 + akka-docs/pending/Recipes.rst | 6 + akka-docs/pending/actor-registry-java.rst | 51 + akka-docs/pending/actor-registry-scala.rst | 78 ++ akka-docs/pending/actors-scala.rst | 568 ++++++++++ akka-docs/pending/agents-scala.rst | 121 +++ akka-docs/pending/articles.rst | 125 +++ akka-docs/pending/benchmarks.rst | 31 + akka-docs/pending/building-akka.rst | 319 ++++++ akka-docs/pending/buildr.rst | 55 + akka-docs/pending/cluster-membership.rst | 90 ++ akka-docs/pending/companies-using-akka.rst | 171 +++ akka-docs/pending/configuration.rst | 180 ++++ akka-docs/pending/dataflow-java.rst | 191 ++++ akka-docs/pending/dataflow-scala.rst | 233 +++++ akka-docs/pending/deployment-scenarios.rst | 100 ++ akka-docs/pending/developer-guidelines.rst | 42 + akka-docs/pending/dispatchers-java.rst | 267 +++++ akka-docs/pending/dispatchers-scala.rst | 214 ++++ akka-docs/pending/event-handler.rst | 96 ++ .../pending/external-sample-projects.rst | 242 +++++ akka-docs/pending/fault-tolerance-java.rst | 466 +++++++++ akka-docs/pending/fault-tolerance-scala.rst | 423 ++++++++ akka-docs/pending/fsm-scala.rst | 218 ++++ akka-docs/pending/futures-scala.rst | 197 ++++ akka-docs/pending/getting-started.rst | 124 +++ akka-docs/pending/guice-integration.rst | 50 + akka-docs/pending/http.rst | 527 ++++++++++ akka-docs/pending/issue-tracking.rst | 41 + akka-docs/pending/language-bindings.rst | 24 + akka-docs/pending/licenses.rst | 197 ++++ akka-docs/pending/logging.rst | 4 + .../pending/migration-guide-0.10.x-1.0.x.rst | 432 ++++++++ .../pending/migration-guide-0.7.x-0.8.x.rst | 94 ++ .../pending/migration-guide-0.8.x-0.9.x.rst | 169 +++ .../pending/migration-guide-0.9.x-0.10.x.rst | 45 + akka-docs/pending/migration-guides.rst | 8 + akka-docs/pending/release-notes.rst | 656 ++++++++++++ akka-docs/pending/remote-actors-java.rst | 617 +++++++++++ akka-docs/pending/remote-actors-scala.rst | 722 +++++++++++++ akka-docs/pending/routing-java.rst | 93 ++ akka-docs/pending/routing-scala.rst | 263 +++++ akka-docs/pending/scheduler.rst | 16 + akka-docs/pending/security.rst | 261 +++++ akka-docs/pending/serialization-java.rst | 178 ++++ akka-docs/pending/serialization-scala.rst | 978 ++++++++++++++++++ akka-docs/pending/servlet.rst | 41 + akka-docs/pending/slf4j.rst | 24 + akka-docs/pending/sponsors.rst | 15 + akka-docs/pending/stm-java.rst | 522 ++++++++++ akka-docs/pending/stm-scala.rst | 544 ++++++++++ akka-docs/pending/stm.rst | 60 ++ akka-docs/pending/team.rst | 22 + akka-docs/pending/test.rst | 55 + akka-docs/pending/testkit-example.rst | 138 +++ akka-docs/pending/testkit.rst | 49 + .../pending/third-party-integrations.rst | 21 + akka-docs/pending/transactors-java.rst | 265 +++++ akka-docs/pending/transactors-scala.rst | 244 +++++ .../pending/tutorial-chat-server-java.rst | 7 + .../pending/tutorial-chat-server-scala.rst | 515 +++++++++ akka-docs/pending/typed-actors-java.rst | 186 ++++ akka-docs/pending/typed-actors-scala.rst | 167 +++ akka-docs/pending/untyped-actors-java.rst | 417 ++++++++ akka-docs/pending/use-cases.rst | 31 + akka-docs/pending/web.rst | 99 ++ 69 files changed, 13598 insertions(+), 1 deletion(-) create mode 100644 akka-docs/pending/Feature Stability Matrix.rst create mode 100644 akka-docs/pending/Home.rst create mode 100644 akka-docs/pending/Migration-1.0-1.1.rst create mode 100644 akka-docs/pending/Recipes.rst create mode 100644 akka-docs/pending/actor-registry-java.rst create mode 100644 akka-docs/pending/actor-registry-scala.rst create mode 100644 akka-docs/pending/actors-scala.rst create mode 100644 akka-docs/pending/agents-scala.rst create mode 100644 akka-docs/pending/articles.rst create mode 100644 akka-docs/pending/benchmarks.rst create mode 100644 akka-docs/pending/building-akka.rst create mode 100644 akka-docs/pending/buildr.rst create mode 100644 akka-docs/pending/cluster-membership.rst create mode 100644 akka-docs/pending/companies-using-akka.rst create mode 100644 akka-docs/pending/configuration.rst create mode 100644 akka-docs/pending/dataflow-java.rst create mode 100644 akka-docs/pending/dataflow-scala.rst create mode 100644 akka-docs/pending/deployment-scenarios.rst create mode 100644 akka-docs/pending/developer-guidelines.rst create mode 100644 akka-docs/pending/dispatchers-java.rst create mode 100644 akka-docs/pending/dispatchers-scala.rst create mode 100644 akka-docs/pending/event-handler.rst create mode 100644 akka-docs/pending/external-sample-projects.rst create mode 100644 akka-docs/pending/fault-tolerance-java.rst create mode 100644 akka-docs/pending/fault-tolerance-scala.rst create mode 100644 akka-docs/pending/fsm-scala.rst create mode 100644 akka-docs/pending/futures-scala.rst create mode 100644 akka-docs/pending/getting-started.rst create mode 100644 akka-docs/pending/guice-integration.rst create mode 100644 akka-docs/pending/http.rst create mode 100644 akka-docs/pending/issue-tracking.rst create mode 100644 akka-docs/pending/language-bindings.rst create mode 100644 akka-docs/pending/licenses.rst create mode 100644 akka-docs/pending/logging.rst create mode 100644 akka-docs/pending/migration-guide-0.10.x-1.0.x.rst create mode 100644 akka-docs/pending/migration-guide-0.7.x-0.8.x.rst create mode 100644 akka-docs/pending/migration-guide-0.8.x-0.9.x.rst create mode 100644 akka-docs/pending/migration-guide-0.9.x-0.10.x.rst create mode 100644 akka-docs/pending/migration-guides.rst create mode 100644 akka-docs/pending/release-notes.rst create mode 100644 akka-docs/pending/remote-actors-java.rst create mode 100644 akka-docs/pending/remote-actors-scala.rst create mode 100644 akka-docs/pending/routing-java.rst create mode 100644 akka-docs/pending/routing-scala.rst create mode 100644 akka-docs/pending/scheduler.rst create mode 100644 akka-docs/pending/security.rst create mode 100644 akka-docs/pending/serialization-java.rst create mode 100644 akka-docs/pending/serialization-scala.rst create mode 100644 akka-docs/pending/servlet.rst create mode 100644 akka-docs/pending/slf4j.rst create mode 100644 akka-docs/pending/sponsors.rst create mode 100644 akka-docs/pending/stm-java.rst create mode 100644 akka-docs/pending/stm-scala.rst create mode 100644 akka-docs/pending/stm.rst create mode 100644 akka-docs/pending/team.rst create mode 100644 akka-docs/pending/test.rst create mode 100644 akka-docs/pending/testkit-example.rst create mode 100644 akka-docs/pending/testkit.rst create mode 100644 akka-docs/pending/third-party-integrations.rst create mode 100644 akka-docs/pending/transactors-java.rst create mode 100644 akka-docs/pending/transactors-scala.rst create mode 100644 akka-docs/pending/tutorial-chat-server-java.rst create mode 100644 akka-docs/pending/tutorial-chat-server-scala.rst create mode 100644 akka-docs/pending/typed-actors-java.rst create mode 100644 akka-docs/pending/typed-actors-scala.rst create mode 100644 akka-docs/pending/untyped-actors-java.rst create mode 100644 akka-docs/pending/use-cases.rst create mode 100644 akka-docs/pending/web.rst diff --git a/akka-docs/index.rst b/akka-docs/index.rst index 95a317778e..c7b2486170 100644 --- a/akka-docs/index.rst +++ b/akka-docs/index.rst @@ -5,7 +5,74 @@ Contents :maxdepth: 2 manual/getting-started-first - + pending/actor-registry-java + pending/actor-registry-scala + pending/actors-scala + pending/agents-scala + pending/articles + pending/benchmarks + pending/building-akka + pending/buildr + pending/cluster-membership + pending/companies-using-akka + pending/configuration + pending/dataflow-java + pending/dataflow-scala + pending/deployment-scenarios + pending/developer-guidelines + pending/dispatchers-java + pending/dispatchers-scala + pending/event-handler + pending/external-sample-projects + pending/fault-tolerance-java + pending/fault-tolerance-scala + pending/Feature Stability Matrix + pending/fsm-scala + pending/futures-scala + pending/getting-started + pending/guice-integration + pending/Home + pending/http + pending/issue-tracking + pending/language-bindings + pending/licenses + pending/logging + pending/Migration-1.0-1.1 + pending/migration-guide-0.10.x-1.0.x + pending/migration-guide-0.7.x-0.8.x + pending/migration-guide-0.8.x-0.9.x + pending/migration-guide-0.9.x-0.10.x + pending/migration-guides + pending/Recipes + pending/release-notes + pending/remote-actors-java + pending/remote-actors-scala + pending/routing-java + pending/routing-scala + pending/scheduler + pending/security + pending/serialization-java + pending/serialization-scala + pending/servlet + pending/slf4j + pending/sponsors + pending/stm + pending/stm-java + pending/stm-scala + pending/team + pending/test + pending/testkit + pending/testkit-example + pending/third-party-integrations + pending/transactors-java + pending/transactors-scala + pending/tutorial-chat-server-java + pending/tutorial-chat-server-scala + pending/typed-actors-java + pending/typed-actors-scala + pending/untyped-actors-java + pending/use-cases + pending/web Links ===== diff --git a/akka-docs/pending/Feature Stability Matrix.rst b/akka-docs/pending/Feature Stability Matrix.rst new file mode 100644 index 0000000000..cdbd6b3ad9 --- /dev/null +++ b/akka-docs/pending/Feature Stability Matrix.rst @@ -0,0 +1,31 @@ +Feature Stability Matrix +======================== + +Akka is comprised of a number if modules, with different levels of maturity and in different parts of their lifecycle, the matrix below gives you get current stability level of the modules. + +Explanation of the different levels of stability +------------------------------------------------ + +* **Solid** - Proven solid in heavy production usage +* **Stable** - Ready for use in production environment +* **In progress** - Not enough feedback/use to claim it's ready for production use + +||~ Feature ||~ Solid ||~ Stable ||~ In progress || +||= ====`Actors (Scala) `_ ==== ||= Solid ||= ||= || +||= ====`Actors (Java) `_ ==== ||= Solid ||= ||= || +||= ====` Typed Actors (Scala) `_ ==== ||= Solid ||= ||= || +||= ====` Typed Actors (Java) `_ ==== ||= Solid ||= ||= || +||= ====`STM (Scala) `_ ==== ||= Solid ||= ||= || +||= ====`STM (Java) `_ ==== ||= Solid ||= ||= || +||= ====`Transactors (Scala) `_ ==== ||= Solid ||= ||= || +||= ====`Transactors (Java) `_ ==== ||= Solid ||= ||= || +||= ====`Remote Actors (Scala) `_ ==== ||= Solid ||= ||= || +||= ====`Remote Actors (Java) `_ ==== ||= Solid ||= ||= || +||= ====`Camel `_ ==== ||= Solid ||= ||= || +||= ====`AMQP `_ ==== ||= Solid ||= ||= || +||= ====`HTTP `_ ==== ||= Solid ||= ||= || +||= ====`Integration Guice `_ ==== ||= ||= Stable ||= || +||= ====`Integration Spring `_ ==== ||= ||= Stable ||= || +||= ====`JTA `_ ==== ||= ||= Stable ||= || +||= ====`Scheduler `_ ==== ||= Solid ||= ||= || +||= ====`Redis Pub Sub `_ ==== ||= ||= ||= In progress || diff --git a/akka-docs/pending/Home.rst b/akka-docs/pending/Home.rst new file mode 100644 index 0000000000..33bb0a08c6 --- /dev/null +++ b/akka-docs/pending/Home.rst @@ -0,0 +1,62 @@ +Akka +==== + +**Simpler Scalability, Fault-Tolerance, Concurrency & Remoting through Actors** + +---- +We believe that writing correct concurrent, fault-tolerant and scalable applications is too hard. Most of the time it's because we are using the wrong tools and the wrong level of abstraction. Akka is here to change that. Using the Actor Model together with Software Transactional Memory we raise the abstraction level and provide a better platform to build correct concurrent and scalable applications. For fault-tolerance we adopt the "Let it crash" / "Embrace failure" model which have been used with great success in the telecom industry to build applications that self-heals, systems that never stop. Actors also provides the abstraction for transparent distribution and the basis for truly scalable and fault-tolerant applications. Akka is Open Source and available under the Apache 2 License. +---- + +Akka is split up into two different parts: +* Akka - Reflects all the sections under 'Scala API' and 'Java API' in the navigation bar. +* Akka Modules - Reflects all the sections under 'Add-on modules' in the navigation bar. + +Download from ``_ + +News: Akka 1.0 final is released +================================ + +1.0 documentation: +================== + +This documentation covers the latest release ready code in 'master' branch in the repository. +If you want the documentation for the 1.0 release you can find it `here `_. + +You can watch the recording of the `Akka talk at JFokus in Feb 2011 `_. + +``_ + +**Akka implements a unique hybrid of:** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* `Actors `_, which gives you: +** Simple and high-level abstractions for concurrency and parallelism. +** Asynchronous, non-blocking and highly performant event-driven programming model. +** Very lightweight event-driven processes (create ~6.5 million actors on 4 G RAM). +* `Failure management `_ through supervisor hierarchies with `let-it-crash `_ semantics. Excellent for writing highly fault-tolerant systems that never stop, systems that self-heal. +* `Software Transactional Memory `_ (STM). (Distributed transactions coming soon). +* `Transactors `_: combine actors and STM into transactional actors. Allows you to compose atomic message flows with automatic retry and rollback. +* `Remote actors `_: highly performant distributed actors with remote supervision and error management. +* Java and Scala API. + +**Akka also has a set of add-on modules:** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* `Camel `_: Expose actors as Apache Camel endpoints. +* `Spring `_: Wire up typed actors in the Spring config using Akka's namespace. +* `REST `_ (JAX-RS): Expose actors as REST services. +* `OSGi `_: Akka and all its dependency is OSGi enabled. +* `Mist `_: Expose actors as asynchronous HTTP services. +* `Security `_: Basic, Digest and Kerberos based security. +* `Microkernel `_: Run Akka as a stand-alone self-hosted kernel. +* `FSM `_: Finite State Machine support. +* `JTA `_: Let the STM interoperate with other transactional resources. +* `Pub/Sub `_: Publish-Subscribe across remote nodes. + +**Akka can be used in two different ways:** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* As a library: used by a web app, to be put into ‘WEB-INF/lib’ or as a regular JAR on your classpath. +* As a microkernel: stand-alone kernel, embedding a servlet container and all the other modules. + +See the `Use-case and Deployment Scenarios `_ for details. diff --git a/akka-docs/pending/Migration-1.0-1.1.rst b/akka-docs/pending/Migration-1.0-1.1.rst new file mode 100644 index 0000000000..b9f88bf4fc --- /dev/null +++ b/akka-docs/pending/Migration-1.0-1.1.rst @@ -0,0 +1,32 @@ +Moved to Scala 2.9.x +^^^^^^^^^^^^^^^^^^^^ + +Akka HTTP +========= + +# akka.servlet.Initializer has been moved to akka-kernel to be able to have akka-http not depend on akka-remote, if you don't want to use the class for kernel, just create your own version of akka.servlet.Initializer, it's just a couple of lines of code and there is instructions here: `Akka Http Docs `_ +# akka.http.ListWriter has been removed in full, if you use it and want to keep using it, here's the code: `ListWriter `_ +# Jersey-server is now a "provided" dependency for Akka-http, so you'll need to add the dependency to your project, it's built against Jersey 1.3 + +Akka Actor +========== + +# is now dependency free, with the exception of the dependency on the scala-library.jar +# does not bundle any logging anymore, but you can subscribe to events within Akka by registering an event handler on akka.aevent.EventHandler or by specifying the FQN of an Actor in the akka.conf under akka.event-handlers; there is an akka-slf4j module which still provides the Logging trait and a default SLF4J logger adapter. +# If you used HawtDispatcher and want to continue using it, you need to include akka-dispatcher-extras.jar from Akka Modules, in your akka.conf you need to specify: "akka.dispatch.HawtDispatcherConfigurator" instead of "HawtDispatcher" +# FSM: the onTransition method changed from Function1 to PartialFunction; there is an implicit conversion for the precise types in place, but it may be necessary to add an underscore if you are passing an eta-expansion (using a method as function value). + +Akka Typed Actor +================ + +All methods starting with 'get*' are deprecated and will be removed in post 1.1 release. + +Akka Remote +=========== + +# UnparsebleException => CannotInstantiateRemoteExceptionDueToRemoteProtocolParsingErrorException(exception, classname, message) + +Akka Testkit +============ + +The TestKit moved into the akka-testkit subproject and correspondingly into the akka.testkit package. diff --git a/akka-docs/pending/Recipes.rst b/akka-docs/pending/Recipes.rst new file mode 100644 index 0000000000..55bc4085a1 --- /dev/null +++ b/akka-docs/pending/Recipes.rst @@ -0,0 +1,6 @@ +Here is a list of recipies for all things Akka +============================================== + +* PostStart => `Link to Klangism `_ +* `Consumer actors best practices `_ +* `Producer actors best practices `_ diff --git a/akka-docs/pending/actor-registry-java.rst b/akka-docs/pending/actor-registry-java.rst new file mode 100644 index 0000000000..713ad8bba6 --- /dev/null +++ b/akka-docs/pending/actor-registry-java.rst @@ -0,0 +1,51 @@ +ActorRegistry (Java) +==================== + +Module stability: **SOLID** + +ActorRegistry: Finding Actors +----------------------------- + +Actors can be looked up using the 'akka.actor.Actors.registry()' object. Through this registry you can look up actors by: +* uuid string – this uses the ‘uuid’ field in the Actor class, returns all actor instances with that uuid +* id string – this uses the ‘id’ field in the Actor class, which can be set by the user (default is the class name), returns instances of a specific Actor +* parameterized type - returns a 'ActorRef[]' with all actors that are a subtype of this specific type +* specific actor class - returns a 'ActorRef[]' with all actors of this exact class + +Actors are automatically registered in the ActorRegistry when they are started and removed when they are stopped. But you can explicitly register and unregister ActorRef's if you need to using the 'register' and 'unregister' methods. + +Here is a summary of the API for finding actors: + +.. code-block:: java + + import static akka.actor.Actors.*; + Option actor = registry().actorFor(String uuid); + Actor[] actors = registry().actors(); + Actor[] otherActors = registry().actorsFor(String id); + Actor[] moreActors = registry().actorsFor(Class clazz); + +You can shut down all Actors in the system by invoking: + +.. code-block:: java + + registry().shutdownAll(); + +If you want to know when a new Actor is added or to or removed from the registry, you can use the subscription API. You can register an Actor that should be notified when an event happens in the ActorRegistry: + +.. code-block:: java + + void addListener(ActorRef listener) + void removeListener(ActorRef listener) + +The messages sent to this Actor are: + +.. code-block:: java + + class ActorRegistered { + ActorRef actor(); + } + class ActorUnregistered { + ActorRef actor(); + } + +So your listener Actor needs to be able to handle these two messages. diff --git a/akka-docs/pending/actor-registry-scala.rst b/akka-docs/pending/actor-registry-scala.rst new file mode 100644 index 0000000000..9741383147 --- /dev/null +++ b/akka-docs/pending/actor-registry-scala.rst @@ -0,0 +1,78 @@ +ActorRegistry (Scala) +===================== + +Module stability: **SOLID** + +ActorRegistry: Finding Actors +----------------------------- + +Actors can be looked up by using the **akka.actor.Actor.registry: akka.actor.ActorRegistry**. Lookups for actors through this registry can be done by: +* uuid string – this uses the ‘**uuid**’ field in the Actor class, returns all actor instances with the specified uuid +* id string – this uses the ‘**id**’ field in the Actor class, which can be set by the user (default is the class name), returns instances of a specific Actor +* specific actor class - returns an '**Array[Actor]**' with all actors of this exact class +* parameterized type - returns an '**Array[Actor]**' with all actors that are a subtype of this specific type + +Actors are automatically registered in the ActorRegistry when they are started, removed or stopped. You can explicitly register and unregister ActorRef's by using the '**register**' and '**unregister**' methods. The ActorRegistry contains many convenience methods for looking up typed actors. + +Here is a summary of the API for finding actors: + +.. code-block:: scala + + def actors: Array[ActorRef] + def actorFor(uuid: String): Option[ActorRef] + def actorsFor(id : String): Array[ActorRef] + def actorsFor[T <: Actor](implicit manifest: Manifest[T]): Array[ActorRef] + def actorsFor[T <: Actor](clazz: Class[T]): Array[ActorRef] + + // finding typed actors + def typedActors: Array[AnyRef] + def typedActorFor(uuid: Uuid): Option[AnyRef] + def typedActorsFor(id: String): Array[AnyRef] + def typedActorsFor[T <: AnyRef](implicit manifest: Manifest[T]): Array[AnyRef] + def typedActorsFor[T <: AnyRef](clazz: Class[T]): Array[AnyRef] + +Examples of how to use them: + +.. code-block:: scala + + val actor = Actor.registry.actorFor(uuid) + val pojo = Actor.registry.typedActorFor(uuid) + +.. code-block:: scala + + val actors = Actor.registry.actorsFor(classOf[...]) + val pojos = Actor.registry.typedActorsFor(classOf[...]) + +.. code-block:: scala + + val actors = Actor.registry.actorsFor(id) + val pojos = Actor.registry.typedActorsFor(id) + +.. code-block:: scala + + val actors = Actor.registry.actorsFor[MyActorType] + val pojos = Actor.registry.typedActorsFor[MyTypedActorImpl] + +The ActorRegistry also has a 'shutdownAll' and 'foreach' methods: + +.. code-block:: scala + + def foreach(f: (ActorRef) => Unit) + def foreachTypedActor(f: (AnyRef) => Unit) + def shutdownAll + +If you need to know when a new Actor is added or removed from the registry, you can use the subscription API. You can register an Actor that should be notified when an event happens in the ActorRegistry: + +.. code-block:: scala + + def addListener(listener: ActorRef) + def removeListener(listener: ActorRef) + +The messages sent to this Actor are: + +.. code-block:: scala + + case class ActorRegistered(actor: ActorRef) + case class ActorUnregistered(actor: ActorRef) + +So your listener Actor needs to be able to handle these two messages. diff --git a/akka-docs/pending/actors-scala.rst b/akka-docs/pending/actors-scala.rst new file mode 100644 index 0000000000..360e7607c9 --- /dev/null +++ b/akka-docs/pending/actors-scala.rst @@ -0,0 +1,568 @@ +Actors (Scala) +============== + += + +Module stability: **SOLID** + +The `Actor Model `_ provides a higher level of abstraction for writing concurrent and distributed systems. It alleviates the developer from having to deal with explicit locking and thread management, making it easier to write correct concurrent and parallel systems. Actors were defined in the 1973 paper by Carl Hewitt but have been popularized by the Erlang language, and used for example at Ericsson with great success to build highly concurrent and reliable telecom systems. + +The API of Akka’s Actors is similar to Scala Actors which has borrowed some of its syntax from Erlang. + +The Akka 0.9 release introduced a new concept; ActorRef, which requires some refactoring. If you are new to Akka just read along, but if you have used Akka 0.6.x, 0.7.x and 0.8.x then you might be helped by the `0.8.x => 0.9.x migration guide `_ + +Creating Actors +--------------- + +Actors can be created either by: +* Extending the Actor class and implementing the receive method. +* Create an anonymous actor using one of the actor methods. + +Defining an Actor class +^^^^^^^^^^^^^^^^^^^^^^^ + +Actor classes are implemented by extending the Actor class and implementing the 'receive' method. The 'receive' method should definie a series of case statements (which has the type 'PartialFunction[Any, Unit]') that defines which messages your Actor can handle, using standard Scala pattern matching, along with the implementation of how the messages should be processed. + +Here is an example: + +.. code-block:: scala + + class MyActor extends Actor { + def receive = { + case "test" => log.info("received test") + case _ => log.info("received unknown message") + } + } + +Please note that the Akka Actor 'receive' message loop is exhaustive, which is different compared to Erlang and Scala Actors. This means that you need to provide a pattern match for all messages that it can accept and if you want to be able to handle unknown messages then you need to have a default case as in the example above. + +The 'Actor' trait mixes in the 'akka.util.Logging' trait which defines a logger in the 'log' field that you can use to log. This logger is configured in the 'akka.conf' configuration file (and is based on the Configgy library which is using Java Logging). + +Creating Actors +^^^^^^^^^^^^^^^ + +.. code-block:: scala + + val myActor = Actor.actorOf[MyActor] + myActor.start + +Normally you would want to import the 'actorOf' method like this: + +.. code-block:: scala + + import akka.actor.Actor._ + + val myActor = actorOf[MyActor] + +To avoid prefixing it with 'Actor' every time you use it. + +You can also start it in the same statement: + +.. code-block:: scala + + val myActor = actorOf[MyActor].start + +The call to 'actorOf' returns an instance of 'ActorRef'. This is a handle to the 'Actor' instance which you can use to interact with the Actor, like send messages to it etc. more on this shortly. The 'ActorRef' is immutable and has a one to one relationship with the Actor it represents. The 'ActorRef' is also serializable and network-aware. This means that you can serialize it, send it over the wire and use it on a remote host and it will still be representing the same Actor on the original node, across the network. + +Creating Actors with non-default constructor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If your Actor has a constructor that takes parameters then you can't create it using 'actorOf[TYPE]'. Instead you can use a variant of 'actorOf' that takes a call-by-name block in which you can create the Actor in any way you like. + +Here is an example: + +.. code-block:: scala + + val a = actorOf(new MyActor(..)).start // allows passing in arguments into the MyActor constructor + +Running a block of code asynchronously +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Here we create a light-weight actor-based thread, that can be used to spawn off a task. Code blocks spawned up like this are always implicitly started, shut down and made eligible for garbage collection. The actor that is created "under the hood" is not reachable from the outside and there is no way of sending messages to it. It being an actor is only an implementation detail. It will only run the block in an event-based thread and exit once the block has run to completion. + +.. code-block:: scala + + spawn { + ... // do stuff + } + +Identifying Actors +------------------ + +Each Actor has two fields: +* self.uuid +* self.id + +The difference is that the 'uuid' is generated by the runtime, guaranteed to be unique and can't be modified. While the 'id' is modifiable by the user, and defaults to the Actor class name. You can retrieve Actors by both UUID and ID using the 'ActorRegistry', see the section further down for details. + +Messages and immutability +------------------------- + +**IMPORTANT**: Messages can be any kind of object but have to be immutable. Scala can’t enforce immutability (yet) so this has to be by convention. Primitives like String, Int, Boolean are always immutable. Apart from these the recommended approach is to use Scala case classes which are immutable (if you don’t explicitly expose the state) and works great with pattern matching at the receiver side. + +Here is an example: + +.. code-block:: scala + + // define the case class + case class Register(user: User) + + // create a new case class message + val message = Register(user) + +Other good messages types are 'scala.Tuple2', 'scala.List', 'scala.Map' which are all immutable and great for pattern matching. + +Send messages +------------- + +Messages are sent to an Actor through one of the “bang” methods. +* ! means “fire-and-forget”, e.g. send a message asynchronously and return immediately. +* !! means “send-and-reply-eventually”, e.g. send a message asynchronously and wait for a reply through aFuture. Here you can specify a timeout. Using timeouts is very important. If no timeout is specified then the actor’s default timeout (set by the this.timeout variable in the actor) is used. This method returns an 'Option[Any]' which will be either 'Some(result)' if returning successfully or None if the call timed out. +* !!! sends a message asynchronously and returns a 'Future'. + +You can check if an Actor can handle a specific message by invoking the 'isDefinedAt' method: + +.. code-block:: scala + + if (actor.isDefinedAt(message) actor ! message + else ... + +Fire-forget +^^^^^^^^^^^ + +This is the preferred way of sending messages. No blocking waiting for a message. This gives the best concurrency and scalability characteristics. + +.. code-block:: scala + + actor ! "Hello" + +If invoked from within an Actor, then the sending actor reference will be implicitly passed along with the message and available to the receiving Actor in its 'sender: Option[AnyRef]' member field. He can use this to reply to the original sender or use the 'reply(message: Any)' method. + +If invoked from an instance that is **not** an Actor there will be no implicit sender passed along the message and you will get an IllegalStateException if you call 'self.reply(..)'. + +Send-And-Receive-Eventually +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Using '!!' will send a message to the receiving Actor asynchronously but it will wait for a reply on a 'Future', blocking the sender Actor until either: + +* A reply is received, or +* The Future times out + +You can pass an explicit time-out to the '!!' method and if none is specified then the default time-out defined in the sender Actor will be used. + +The '!!' method returns an 'Option[Any]' which will be either 'Some(result)' if returning successfully, or None if the call timed out. +Here are some examples: + +.. code-block:: scala + + val resultOption = actor !! ("Hello", 1000) + if (resultOption.isDefined) ... // handle reply + else ... // handle timeout + + val result: Option[String] = actor !! "Hello" + resultOption match { + case Some(reply) => ... // handle reply + case None => ... // handle timeout + } + + val result = (actor !! "Hello").getOrElse(throw new RuntimeException("TIMEOUT")) + + (actor !! "Hello").foreach(result => ...) // handle result + +Send-And-Receive-Future +^^^^^^^^^^^^^^^^^^^^^^^ + +Using '!!!' will send a message to the receiving Actor asynchronously and will return a 'Future': + +.. code-block:: scala + + val future = actor !!! "Hello" + +See `Futures `_ for more information. + +Forward message +^^^^^^^^^^^^^^^ + +You can forward a message from one actor to another. This means that the original sender address/reference is maintained even though the message is going through a 'mediator'. This can be useful when writing actors that work as routers, load-balancers, replicators etc. + +.. code-block:: scala + + actor.forward(message) + +Receive messages +---------------- + +An Actor has to implement the ‘receive’ method to receive messages: + +.. code-block:: scala + + protected def receive: PartialFunction[Any, Unit] + +Note: Akka has an alias to the 'PartialFunction[Any, Unit]' type called 'Receive', so you can use this type instead for clarity. But most often you don't need to spell it out. + +This method should return a PartialFunction, e.g. a ‘match/case’ clause in which the message can be matched against the different case clauses using Scala pattern matching. Here is an example: + +.. code-block:: scala + + class MyActor extends Actor { + def receive = { + case "Hello" => + log.info("Received 'Hello'") + + case _ => + throw new RuntimeException("unknown message") + } + } + +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 (apart from the 'log' field from the mixed in 'Logging' trait): + +.. 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 +----------------- + +Reply using the reply 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 'reply(..)' method. + +.. code-block:: scala + + case request => + val result = process(request) + self.reply(result) + +In this case the 'result' will be send back to the Actor that send 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. + +.. code-block:: scala + + case request => + val result = process(request) + if (self.reply_?(result)) ...// success + else ... // handle failure + +Reply using the sender reference +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If the sender is an Actor then its reference will be implicitly passed along together with the message and will end up in the 'sender: Option[ActorRef]' member field in the 'ActorRef. This means that you can use this field to send a message back to the sender. + +.. code-block:: scala + + // receiver code + case request => + val result = process(request) + self.sender.get ! result + +It's important to know that 'sender.get' will throw an exception if the 'sender' is not defined, e.g. the 'Option' is 'None'. You can check if it is defined by invoking the 'sender.isDefined' method, but a more elegant solution is to use 'foreach' which will only be executed if the sender is defined in the 'sender' member 'Option' field. If it is not, then the operation in the 'foreach' method is ignored. + +.. code-block:: scala + + // receiver code + case request => + val result = process(request) + self.sender.foreach(_ ! result) + +The same pattern holds for using the 'senderFuture' in the section below. + +Reply using the sender future +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If a message was sent with the '!!' or '!!!' methods, which both implements request-reply semantics using Future's, then you either have the option of replying using the 'reply' method as above. This method will then resolve the Future. But you can also get a reference to the Future directly and resolve it yourself or if you would like to store it away to resolve it later, or pass it on to some other Actor to resolve it. + +The reference to the Future resides in the 'senderFuture: Option[CompletableFuture[]]' member field in the 'ActorRef' class. + +Here is an example of how it can be used: + +.. code-block:: scala + + case request => + try { + val result = process(request) + self.senderFuture.foreach(_.completeWithResult(result)) + } catch { + case e => + senderFuture.foreach(_.completeWithException(this, e)) + } + +Reply using the channel +^^^^^^^^^^^^^^^^^^^^^^^ + +If you want to have a handle to an object to whom you can reply to the message, you can use the Channel abstraction. +Simply call self.channel and then you can forward that to others, store it away or otherwise until you want to reply, which you do by 'Channel ! response': + +.. code-block:: scala + + case request => + val result = process(request) + self.channel ! result + +.. code-block:: scala + + case request => + friend forward self.channel + +Summary of reply semantics and options +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* self.reply(...) can be used to reply to an Actor or a Future. +* self.sender is a reference to the actor you can reply to, if it exists +* self.senderFuture is a reference to the future you can reply to, if it exists +* self.channel is a reference providing an abstraction to either self.sender or self.senderFuture if one is set, providing a single reference to store and reply to (the reference equivalent to the 'reply(...)' method). +* self.sender and self.senderFuture will never be set at the same time, as there can only be one reference to accept a reply. + +Initial receive timeout +----------------------- + +A timeout mechanism can be used to receive a message when no initial message is received within a certain time. To receive this timeout you have to set the 'receiveTimeout' property and declare a case handing the ReceiveTimeout object. + +.. code-block:: scala + + self.receiveTimeout = Some(30000L) // 30 seconds + + def receive = { + case "Hello" => + log.info("Received 'Hello'") + case ReceiveTimeout => + throw new RuntimeException("received timeout") + } + +This mechanism also work for hotswapped receive functions. Every time a 'HotSwap' is sent, the receive timeout is reset and rescheduled. + +Starting actors +--------------- + +Actors are started by invoking the ‘start’ method. + +.. code-block:: scala + + val actor = actorOf[MyActor] + actor.start + +You can create and start the Actor in a oneliner like this: + +.. code-block:: scala + + val actor = actorOf[MyActor].start + +When you start the actor then it will automatically call the 'def preStart' callback method on the 'Actor' trait. This is an excellent place to add initialization code for the actor. + +.. code-block:: scala + + override def preStart = { + ... // initialization code + } + +Stopping actors +--------------- + +Actors are stopped by invoking the ‘stop’ method. + +.. code-block:: scala + + actor.stop + +When stop is called then a calll to the ‘def postStop’ callback method will take place. The Actor can use this callback to implement shutdown behavior. + +.. code-block:: scala + + override def postStop = { + ... // clean up resources + } + +You can shut down all Actors in the system by invoking: + +.. code-block:: scala + + Actor.registry.shutdownAll + +- + +PoisonPill +---------- + +You can also send an actor the 'akka.actor.PoisonPill' message, which will stop the actor when the message is processed. + +If the sender is a 'Future' (e.g. the message is sent with '!!' or '!!!'), the 'Future' will be completed with an 'akka.actor.ActorKilledException("PoisonPill")'. + +HotSwap +------- + +Upgrade +^^^^^^^ + +Akka supports hotswapping the Actor’s message loop (e.g. its implementation) at runtime. There are two ways you can do that: +* Send a 'HotSwap' message to the Actor +* Invoke the 'become' method from within the Actor. + +Both of these takes a 'ActorRef => PartialFunction[Any, Unit]' that implements the new message handler. The hotswapped code is kept in a Stack which can be pushed and popped. + +To hotswap the Actor body using the 'HotSwap' message: + +.. code-block:: scala + + actor ! HotSwap( self => { + case message => self.reply("hotswapped body") + }) + +Using the 'HotSwap' message for hotswapping has its limitations. You can not replace it with any code that uses the Actor's 'self' reference. If you need to do that the the 'become' method is better. + +To hotswap the Actor using 'become': + +.. code-block:: scala + + def angry: Receive = { + case "foo" => self reply "I am already angry!!!" + case "bar" => become(happy) + } + + def happy: Receive = { + case "bar" => self reply "I am already happy :-)" + case "foo" => become(angry) + } + + def receive = { + case "foo" => become(angry) + case "bar" => become(happy) + } + +The 'become' method is useful for many different things, but a particular nice example of it is in example where it is used to implement a Finite State Machine (FSM): `Dining Hakkers `_ + +Here is another little cute example of 'become' and 'unbecome' in action: + +.. code-block:: scala + + case object Swap + class Swapper extends Actor { + def receive = { + case Swap => + println("Hi") + become { + case Swap => + println("Ho") + unbecome // resets the latest 'become' (just for fun) + } + } + } + + val swap = actorOf[Swapper].start + + swap ! Swap // prints Hi + swap ! Swap // prints Ho + swap ! Swap // prints Hi + swap ! Swap // prints Ho + swap ! Swap // prints Hi + swap ! Swap // prints Ho + +Encoding Scala Actors nested receives without accidentally leaking memory: `UnnestedReceive `_ +------------------------------------------------------------------------------------------------------------------------------ + +Downgrade +^^^^^^^^^ + +Since the hotswapped code is pushed to a Stack you can downgrade the code as well. There are two ways you can do that: + +* Send the Actor a RevertHotswap message +* Invoke the 'unbecome' method from within the Actor. + +Both of these will pop the Stack and replace the Actor's implementation with the PartialFunction[Any, Unit] that is at the top of the Stack. + +Revert the Actor body using the 'RevertHotSwap' message: + +.. code-block:: scala + + actor ! RevertHotSwap + +Revert the Actor body using the 'unbecome' method: + +.. code-block:: scala + + def receive: Receive = { + case "revert" => unbecome + } + +Killing an Actor +---------------- + +You can kill an actor by sending a 'Kill' message. This will restart the actor through regular supervisor semantics. + +Use it like this: + +.. code-block:: scala + + // kill the actor called 'victim' + victim ! Kill + +Actor life-cycle +---------------- + +The actor has a well-defined non-circular life-cycle. + +:: + + NEW (newly created actor) - can't receive messages (yet) + => STARTED (when 'start' is invoked) - can receive messages + => SHUT DOWN (when 'exit' or 'stop' is invoked) - can't do anything + +Extending Actors using PartialFunction chaining +----------------------------------------------- + +A bit advanced but very useful way of defining a base message handler and then extend that, either through inheritance or delegation, is to use 'PartialFunction' 'orElse' chaining. + +In generic base Actor: + +.. code-block:: scala + + abstract class GenericActor extends Actor { + + // to be defined in subclassing actor + def specificMessageHandler: PartialFunction[Any, Unit] + + // generic message handler + def genericMessageHandler = { + ... // generic message handler + } + + def receive = specificMessageHandler orElse genericMessageHandler + } + +In subclassing Actor: +``_ +class SpecificActor extends GenericActor { + def specificMessageHandler = { + ... // specific message handler + } +} +``_ diff --git a/akka-docs/pending/agents-scala.rst b/akka-docs/pending/agents-scala.rst new file mode 100644 index 0000000000..cd4c486fec --- /dev/null +++ b/akka-docs/pending/agents-scala.rst @@ -0,0 +1,121 @@ +Agents (Scala) +============== + +Module stability: **SOLID** + +Agents in Akka were inspired by `agents in Clojure `_. + +Agents provide asynchronous change of individual locations. Agents are bound to a single storage location for their lifetime, and only allow mutation of that location (to a new state) to occur as a result of an action. Update actions are functions that are asynchronously applied to the Agent's state and whose return value becomes the Agent's new state. The state of an Agent should be immutable. + +While updates to Agents are asynchronous, the state of an Agent is always immediately available for reading by any thread (using ``get`` or ``apply``) without any messages. + +Agents are reactive. The update actions of all Agents get interleaved amongst threads in a thread pool. At any point in time, at most one ''send'' action for each Agent is being executed. Actions dispatched to an agent from another thread will occur in the order they were sent, potentially interleaved with actions dispatched to the same agent from other sources. + +If an Agent is used within an enclosing transaction, then it will participate in that transaction. Agents are integrated with the STM - any dispatches made in a transaction are held until that transaction commits, and are discarded if it is retried or aborted. + +Creating and stopping Agents +---------------------------- + +Agents are created by invoking ``Agent(value)`` passing in the Agent's initial value. + +.. code-block:: scala + + val agent = Agent(5) + +An Agent will be running until you invoke ``close`` on it. Then it will be eligible for garbage collection (unless you hold on to it in some way). + +.. code-block:: scala + + agent.close + +Updating Agents +--------------- + +You update an Agent by sending a function that transforms the current value or by sending just a new value. The Agent will apply the new value or function atomically and asynchronously. The update is done in a fire-forget manner and you are only guaranteed that it will be applied. There is no guarantee of when the update will be applied but dispatches to an Agent from a single thread will occur in order. You apply a value or a function by invoking the ``send`` function. + +.. code-block:: scala + + // send a value + agent send 7 + + // send a function + agent send (_ + 1) + agent send (_ * 2) + +You can also dispatch a function to update the internal state but on its own thread. This does not use the reactive thread pool and can be used for long-running or blocking operations. You do this with the ``sendOff`` method. Dispatches using either ``sendOff`` or ``send`` will still be executed in order. + +.. code-block:: scala + + // sendOff a function + agent sendOff (longRunningOrBlockingFunction) + +Reading an Agent's value +------------------------ + +Agents can be dereferenced, e.g. you can get an Agent's value, by invoking the Agent with parenthesis like this: + +.. code-block:: scala + + val result = agent() + +Or by using the get method. + +.. code-block:: scala + + val result = agent.get + +Reading an Agent's current value does not involve any message passing and happens immediately. So while updates to an Agent are asynchronous, reading the state of an Agent is synchronous. + +Awaiting an Agent's value +------------------------- + +It is also possible to read the value after all currently queued ``send``s have completed. You can do this with ``await``: + +.. code-block:: scala + + val result = agent.await + +You can also get a ``Future`` to this value, that will be completed after the currently queued updates have completed: + +.. code-block:: scala + + val future = agent.future + // ... + val result = future.await.result.get + +Transactional Agents +-------------------- + +If an Agent is used within an enclosing transaction, then it will participate in that transaction. If you send to an Agent within a transaction then the dispatch to the Agent will be held until that transaction commits, and discarded if the transaction is aborted. + +Monadic usage +------------- + +Agents are also monadic, allowing you to compose operations using for-comprehensions. In a monadic usage, new Agents are created leaving the original Agents untouched. So the old values (Agents) are still available as-is. They are so-called 'persistent'. + +Example of a monadic usage: + +``_ +val agent1 = Agent(3) +val agent2 = Agent(5) + +// uses foreach +for (value <- agent1) { + result = value + 1 +} + +// uses map +val agent3 = + for (value <- agent1) yield value + 1 + +// uses flatMap +val agent4 = for { + value1 <- agent1 + value2 <- agent2 +} yield value1 + value2 + +agent1.close +agent2.close +agent3.close +agent4.close +``_ diff --git a/akka-docs/pending/articles.rst b/akka-docs/pending/articles.rst new file mode 100644 index 0000000000..e8b411f47d --- /dev/null +++ b/akka-docs/pending/articles.rst @@ -0,0 +1,125 @@ +Articles & Presentations +======================== + +Videos +====== + +`Functional Programming eXchange - March 2011 `_ + +`NE Scala - Feb 2011 `_ + +`JFokus - Feb 2011 `_. + +`London Scala User Group - Oct 2010 `_ + +`Akka LinkedIn Tech Talk - Sept 2010 `_ + +`Akka talk at Scala Days - March 2010 `_ + +Articles +======== + +`Remote Actor Class Loading with Akka `_ + +`Akka Producer Actors: New Features and Best Practices `_ + +`Akka Consumer Actors: New Features and Best Practices `_ + +`Compute Grid with Cloudy Akka `_ + +`Clustered Actors with Cloudy Akka `_ + +`Unit testing Akka Actors with the TestKit `_ + +`Starting with Akka 1.0 `_ + +`Akka Does Async `_ + +`CQRS with Akka actors and functional domain models `_ + +`High Level Concurrency with JRuby and Akka Actors `_ + +`Container-managed actor dispatchers `_ + +`Even simpler scalability with Akka through RegistryActor `_ + +`FSM in Akka (in Vietnamese) `_ + +`Repeater and Idempotent Receiver implementation in Akka `_ + +`EDA Akka as EventBus `_ + +`Upgrading examples to Akka master (0.10) and Scala 2.8.0 Final `_ + +`Testing Akka Remote Actor using Serializable.Protobuf `_ + +`Flexible load balancing with Akka in Scala `_ + +`Eventually everything, and actors `_ + +`Join messages with Akka `_ + +`Starting with Akka part 2, Intellij IDEA, Test Driven Development `_ + +`Starting with Akka and Scala `_ + +`PubSub using Redis and Akka Actors `_ + +`Akka's grown-up hump `_ + +`Akka features for application integration `_ + +`Load Balancing Actors with Work Stealing Techniques `_ + +`Domain Services and Bounded Context using Akka - Part 2 `_ + +`Thinking Asynchronous - Domain Modeling using Akka Transactors - Part 1 `_ + +`Introducing Akka – Simpler Scalability, Fault-Tolerance, Concurrency & Remoting through Actors `_ + +`Using Cassandra with Scala and Akka `_ + +`No Comet, Hacking with WebSocket and Akka `_ + +`MongoDB for Akka Persistence `_ + +`Pluggable Persistent Transactors with Akka `_ + +`Enterprise scala actors: introducing the Akka framework `_ + +Books +===== + +`Akka and Camel `_ (appendix E of `Camel in Action `_) +`Ett första steg i Scala `_ (Kapitel "Aktörer och Akka") (en. "A first step in Scala", chapter "Actors and Akka", book in Swedish) + +Presentations +============= + +`Slides from Akka talk at Scala Days 2010, good short intro to Akka `_ + +`Akka: Simpler Scalability, Fault-Tolerance, Concurrency & Remoting through Actors `_ + +``_ + +``_ + +Podcasts +======== + +`Episode 16 – Scala and Akka an Interview with Jonas Boner `_ + +`Jonas Boner on the Akka framework, Scala, and highly scalable applications `_ + +Interviews +========== + +`JetBrains/DZone interview: Talking about Akka, Scala and life with Jonas Bonér `_ + +`Artima interview of Jonas on Akka 1.0 `_ + +`InfoQ interview of Jonas on Akka 1.0 `_ + +`InfoQ interview of Jonas on Akka 0.7 `_ + +``_ diff --git a/akka-docs/pending/benchmarks.rst b/akka-docs/pending/benchmarks.rst new file mode 100644 index 0000000000..6352040d32 --- /dev/null +++ b/akka-docs/pending/benchmarks.rst @@ -0,0 +1,31 @@ +Benchmarks +========== + +Scalability, Throughput and Latency benchmark +--------------------------------------------- + +``_ + +Simple Trading system. +* `Here is the result with some graphs `_ +* `Here is the article `_ +* `Here is the code `_ + +Compares: +* Synchronous Scala solution +* Scala library Actors +** Fire-forget +** Request-reply +* Akka +** Request-reply +** Fire-forget with default dispatcher +** Fire-forget with Hawt dispatcher + +Performance benchmark +--------------------- + +Benchmarking Akka against: +* Scala Library Actors +* Raw Java concurrency +* Jetlang (Java actors lib) +``_ diff --git a/akka-docs/pending/building-akka.rst b/akka-docs/pending/building-akka.rst new file mode 100644 index 0000000000..aeeec7409d --- /dev/null +++ b/akka-docs/pending/building-akka.rst @@ -0,0 +1,319 @@ +Building Akka +============= + +This page describes how to build and run Akka from the latest source code. + +Get the source code +=================== + +Akka uses `Git `_ and is hosted at `Github `_. + +You first need Git installed on your machine. You can then clone the source repositories: +* Akka repository from ``_ +* Akka Modules repository from ``_ + +For example: + +:: + + git clone git://github.com/jboner/akka.git + git clone git://github.com/jboner/akka-modules.git + +If you have already cloned the repositories previously then you can update the code with ``git pull``: + +:: + + git pull origin master + +SBT - Simple Build Tool +======================= + +Akka is using the excellent `SBT `_ build system. So the first thing you have to do is to download and install SBT. You can read more about how to do that `here `_ . + +The SBT commands that you'll need to build Akka are all included below. If you want to find out more about SBT and using it for your own projects do read the `SBT documentation `_. + +The Akka SBT build file is ``project/build/AkkaProject.scala`` with some properties defined in ``project/build.properties``. + +---- + +Building Akka +============= + +First make sure that you are in the akka code directory: + +:: + + cd akka + +Fetching dependencies +--------------------- + +SBT does not fetch dependencies automatically. You need to manually do this with the ``update`` command: + +:: + + sbt update + +Once finished, all the dependencies for Akka will be in the ``lib_managed`` directory under each module: akka-actor, akka-stm, and so on. + +*Note: you only need to run {{update}} the first time you are building the code, or when the dependencies have changed.* + +Building +-------- + +To compile all the Akka core modules use the ``compile`` command: + +:: + + sbt compile + +You can run all tests with the ``test`` command: + +:: + + sbt test + +If compiling and testing are successful then you have everything working for the latest Akka development version. + +Publish to local Ivy repository +------------------------------- + +If you want to deploy the artifacts to your local Ivy repository (for example, to use from an SBT project) use the ``publish-local`` command: + +:: + + sbt publish-local + +Publish to local Maven repository +--------------------------------- + +If you want to deploy the artifacts to your local Maven repository use: + +:: + + sbt publish-local publish + +SBT interactive mode +-------------------- + +Note that in the examples above we are calling ``sbt compile`` and ``sbt test`` and so on. SBT also has an interactive mode. If you just run ``sbt`` you enter the interactive SBT prompt and can enter the commands directly. This saves starting up a new JVM instance for each command and can be much faster and more convenient. + +For example, building Akka as above is more commonly done like this: + +:: + + % sbt + [info] Building project akka 1.1-SNAPSHOT against Scala 2.8.1 + [info] using AkkaParentProject with sbt 0.7.5.RC0 and Scala 2.7.7 + > update + [info] + [info] == akka-actor / update == + ... + [success] Successful. + [info] + [info] Total time ... + > compile + ... + > test + ... + +SBT batch mode +-------------- + +It's also possible to combine commands in a single call. For example, updating, testing, and publishing Akka to the local Ivy repository can be done with: + +:: + + sbt update test publish-local + +---- + +Building Akka Modules +===================== + +To build Akka Modules first build and publish Akka to your local Ivy repository as described above. Or using: + +:: + + cd akka + sbt update publish-local + +Then you can build Akka Modules using the same steps as building Akka. First update to get all dependencies (including the Akka core modules), then compile, test, or publish-local as needed. For example: + +:: + + cd akka-modules + sbt update publish-local + +Microkernel distribution +------------------------ + +To build the Akka Modules microkernel (the same as the Akka Modules distribution download) use the ``dist`` command: + +:: + + sbt dist + +The distribution zip can be found in the dist directory and is called ``akka-modules-{version}.zip``. + +To run the mircokernel, unzip the zip file, change into the unzipped directory, set the ``AKKA_HOME`` environment variable, and run the main jar file. For example: + +:: + + unzip dist/akka-modules-1.1-SNAPSHOT.zip + cd akka-modules-1.1-SNAPSHOT + export AKKA_HOME=`pwd` + java -jar akka-modules-1.1-SNAPSHOT.jar + +The microkernel will boot up and install the sample applications that reside in the distribution's ``deploy`` directory. You can deploy your own applications into the ``deploy`` directory as well. + +---- + +Scripts +======= + +Linux/Unix init script +---------------------- + +Here is a Linux/Unix init script that can be very useful: + +``_ + +Copy and modify as needed. + +Simple startup shell script +--------------------------- + +This little script might help a bit. Just make sure you have the Akka distribution in the '$AKKA_HOME/dist' directory and then invoke this script to start up the kernel. The distribution is created in the './dist' dir for you if you invoke 'sbt dist'. + +``_ + +Copy and modify as needed. + +---- + +Dependencies +============ + +If you are managing dependencies by hand you can find out what all the compile dependencies are for each module by looking in the ``lib_managed/compile`` directories. For example, you can run this to create a listing of dependencies (providing you have the source code and have run ``sbt update``): + +:: + + cd akka + ls -1 */lib_managed/compile + +Here are the dependencies used by the Akka core modules. + +akka-actor +^^^^^^^^^^ + +* No dependencies + +akka-stm +^^^^^^^^ + +* Depends on akka-actor +* multiverse-alpha-0.6.2.jar + +akka-typed-actor +^^^^^^^^^^^^^^^^ + +* Depends on akka-stm +* aopalliance-1.0.jar +* aspectwerkz-2.2.3.jar +* guice-all-2.0.jar + +akka-remote +^^^^^^^^^^^ + +* Depends on akka-typed-actor +* commons-codec-1.4.jar +* commons-io-2.0.1.jar +* dispatch-json_2.8.1-0.7.8.jar +* guice-all-2.0.jar +* h2-lzf-1.0.jar +* jackson-core-asl-1.7.1.jar +* jackson-mapper-asl-1.7.1.jar +* junit-4.8.1.jar +* netty-3.2.3.Final.jar +* objenesis-1.2.jar +* protobuf-java-2.3.0.jar +* sjson_2.8.1-0.9.1.jar + +akka-http +^^^^^^^^^ + +* Depends on akka-remote +* jsr250-api-1.0.jar +* jsr311-api-1.1.jar + +---- +Here are the dependencies used by the Akka modules. + +akka-amqp +^^^^^^^^^ + +* Depends on akka-remote +* commons-cli-1.1.jar +* amqp-client-1.8.1.jar + +akka-camel +^^^^^^^^^^ + +* Depends on akka-actor +* camel-core-2.5.0.jar +* commons-logging-api-1.1.jar +* commons-management-1.0.jar + +akka-camel-typed +^^^^^^^^^^^^^^^^ + +* Depends on akka-typed-actor +* camel-core-2.5.0.jar +* commons-logging-api-1.1.jar +* commons-management-1.0.jar + +akka-spring +^^^^^^^^^^^ + +* Depends on akka-camel +* akka-camel-typed +* commons-logging-1.1.1.jar +* spring-aop-3.0.4.RELEASE.jar +* spring-asm-3.0.4.RELEASE.jar +* spring-beans-3.0.4.RELEASE.jar +* spring-context-3.0.4.RELEASE.jar +* spring-core-3.0.4.RELEASE.jar +* spring-expression-3.0.4.RELEASE.jar + +akka-scalaz +^^^^^^^^^^^ + +* Depends on akka-actor +* hawtdispatch-1.1.jar +* hawtdispatch-scala-1.1.jar +* scalaz-core_2.8.1-6.0-SNAPSHOT.jar + +akka-kernel +^^^^^^^^^^^ + +* Depends on akka-http, akka-amqp, and akka-spring +* activation-1.1.jar +* asm-3.1.jar +* jaxb-api-2.1.jar +* jaxb-impl-2.1.12.jar +* jersey-core-1.3.jar +* jersey-json-1.3.jar +* jersey-scala-1.3.jar +* jersey-server-1.3.jar +* jettison-1.1.jar +* jetty-continuation-7.1.6.v20100715.jar +* jetty-http-7.1.6.v20100715.jar +* jetty-io-7.1.6.v20100715.jar +* jetty-security-7.1.6.v20100715.jar +* jetty-server-7.1.6.v20100715.jar +* jetty-servlet-7.1.6.v20100715.jar +* jetty-util-7.1.6.v20100715.jar +* jetty-xml-7.1.6.v20100715.jar +* servlet-api-2.5.jar +* stax-api-1.0.1.jar diff --git a/akka-docs/pending/buildr.rst b/akka-docs/pending/buildr.rst new file mode 100644 index 0000000000..85fb9bcd19 --- /dev/null +++ b/akka-docs/pending/buildr.rst @@ -0,0 +1,55 @@ +Using Akka in a Buildr project +============================== + +This is an example on how to use Akka in a project based on Buildr + +``_ +require 'buildr/scala' + +VERSION_NUMBER = "0.6" +GROUP = "se.scalablesolutions.akka" + +repositories.remote << "http://www.ibiblio.org/maven2/" +repositories.remote << "http://www.lag.net/repo" +repositories.remote << "http://multiverse.googlecode.com/svn/maven-repository/releases" + +AKKA = group('akka-core', 'akka-comet', 'akka-util','akka-kernel', 'akka-rest', 'akka-util-java', + 'akka-security','akka-persistence-common', 'akka-persistence-redis', + 'akka-amqp', + :under=> 'se.scalablesolutions.akka', + :version => '0.6') +ASPECTJ = "org.codehaus.aspectwerkz:aspectwerkz-nodeps-jdk5:jar:2.1" +SBINARY = "sbinary:sbinary:jar:0.3" +COMMONS_IO = "commons-io:commons-io:jar:1.4" +CONFIGGY = "net.lag:configgy:jar:1.4.7" +JACKSON = group('jackson-core-asl', 'jackson-mapper-asl', + :under=> 'org.codehaus.jackson', + :version => '1.2.1') +MULTIVERSE = "org.multiverse:multiverse-alpha:jar:jar-with-dependencies:0.3" +NETTY = "org.jboss.netty:netty:jar:3.2.0.ALPHA2" +PROTOBUF = "com.google.protobuf:protobuf-java:jar:2.2.0" +REDIS = "com.redis:redisclient:jar:1.0.1" +SJSON = "sjson.json:sjson:jar:0.3" + +Project.local_task "run" + +desc "Akka Chat Sample Module" +define "akka-sample-chat" do + project.version = VERSION_NUMBER + project.group = GROUP + + compile.with AKKA, CONFIGGY + + p artifact(MULTIVERSE).to_s + + package(:jar) + + task "run" do + Java.java "scala.tools.nsc.MainGenericRunner", + :classpath => [ compile.dependencies, compile.target, + ASPECTJ, COMMONS_IO, JACKSON, NETTY, MULTIVERSE, PROTOBUF, REDIS, + SBINARY, SJSON], + :java_args => ["-server"] + end +end +``_ diff --git a/akka-docs/pending/cluster-membership.rst b/akka-docs/pending/cluster-membership.rst new file mode 100644 index 0000000000..9c7d75194a --- /dev/null +++ b/akka-docs/pending/cluster-membership.rst @@ -0,0 +1,90 @@ +Cluster Membership (Scala) +========================== + +Module stability: **IN PROGRESS** + +Akka supports a Cluster Membership through a `JGroups `_ based implementation. JGroups is is a `P2P `_ clustering API + +Configuration +============= + +The cluster is configured in 'akka.conf' by adding the Fully Qualified Name (FQN) of the actor class and serializer: + +.. code-block:: ruby + + remote { + cluster { + service = on + name = "default" # The name of the cluster + serializer = "akka.serialization.Serializer$Java" # FQN of the serializer class + } + } + +How to join the cluster +======================= + +The node joins the cluster when the 'RemoteNode' and/or 'RemoteServer' servers are started. + +Cluster API +=========== + +Interaction with the cluster is done through the 'akka.remote.Cluster' object. + +To send a message to all actors of a specific type on other nodes in the cluster use the 'relayMessage' function: + +.. code-block:: scala + + def relayMessage(to: Class[_ <: Actor], msg: AnyRef): Unit + +Here is an example: + +.. code-block:: scala + + Cluster.relayMessage(classOf[ATypeOfActor], message) + +Traversing the remote nodes in the cluster to spawn remote actors: + +Cluster.foreach: + +.. code-block:: scala + + def foreach(f : (RemoteAddress) => Unit) : Unit + +Here's an example: + +.. code-block:: scala + + for(endpoint <- Cluster) spawnRemote[KungFuActor](endpoint.hostname,endpoint.port) + +and: + +.. code-block:: scala + + Cluster.foreach( endpoint => spawnRemote[KungFuActor](endpoint.hostname,endpoint.port) ) + +Cluster.lookup: + +.. code-block:: scala + + def lookup[T](handleRemoteAddress : PartialFunction[RemoteAddress, T]) : Option[T] + +Here is an example: + +.. code-block:: scala + + val myRemoteActor: Option[SomeActorType] = Cluster.lookup({ + case RemoteAddress(hostname, port) => spawnRemote[SomeActorType](hostname, port) + }) + + myRemoteActor.foreach(remoteActor => ...) + +Here is another example: + +``_ +Cluster.lookup({ + case remoteAddress @ RemoteAddress(_,_) => remoteAddress +}) match { + case Some(remoteAddress) => spawnAllRemoteActors(remoteAddress) + case None => handleNoRemoteNodeFound +} +``_ diff --git a/akka-docs/pending/companies-using-akka.rst b/akka-docs/pending/companies-using-akka.rst new file mode 100644 index 0000000000..7186ae3deb --- /dev/null +++ b/akka-docs/pending/companies-using-akka.rst @@ -0,0 +1,171 @@ +Companies and Open Source projects using Akka +============================================= + +Production Users +================ + +These are some of the production Akka users that are able to talk about their use publicly. + +CSC +--- + +CSC is a global provider of information technology services. The Traffic Management business unit in the Netherlands is a systems integrator for the implementation of Traffic Information and Traffic Enforcement Systems, such as section control, weigh in motion, travel time and traffic jam detection and national data warehouse for traffic information. CSC Traffic Management is using Akka for their latest Traffic Information and Traffic Enforcement Systems. + +``_ + +*"Akka has been in use for almost a year now (since 0.7) and has been used successfully for two projects so far. Akka has enabled us to deliver very flexible, scalable and high performing systems with as little friction as possible. The Actor model has simplified a lot of concerns in the type of systems that we build and is now part of our reference architecture. With Akka we deliver systems that meet the most strict performance requirements of our clients in a near-realtime environment. We have found the Akka framework and it's support team invaluable."* + +Thatcham Motor Insurance Repair Research Centre +----------------------------------------------- + +Thatcham is a EuroNCAP member. They research efficient, safe, cost effective repair of vehicles, and work with manufacturers to influence the design of new vehicles Thatcham are using Akka as the implementation for their distributed modules. All Scala based research software now talks to an Akka based publishing platform. Using Akka enables Thatcham to 'free their domain', and ensures that the platform is cloud enabled and scalable, and that the team is confident that they are flexible. Akka has been in use, tested under load at Thatcham for almost a year, with no problems migrating up through the different versions. An old website currently under redesign on a new Scala powered platform: `www.thatcham.org `_ + +*“We have been in production with Akka for over 18 months with zero downtime. The core is rock solid, never a problem, performance is great, integration capabilities are diverse and ever growing, and the toolkit is just a pleasure to work with. Combine that with the excellent response you get from the devs and users on this list and you have a winner. Absolutely no regrets on our part for choosing to work with Akka.”* + +*"Scala and Akka are now enabling improvements in the standard of vehicle damage assessment, and in the safety of vehicle repair across the UK, with Europe, USA, Asia and Australasia to follow. Thatcham (Motor Insurance Repair Research Centre) are delivering crash specific information with linked detailed repair information for over 7000 methods.* + +*For Thatcham, the technologies enable scalability and elegance when dealing with complicated design constraints. Because of the complexity of interlinked methods, caching is virtually impossible in most cases, so in steps the 'actors' paradigm. Where previously something like JMS would have provided a stable but heavyweight, rigid solution, Thatcham are now more flexible, and can expand into the cloud in a far simpler, more rewarding way.* + +*Thatcham's customers, body shop repairers and insurers receive up to date repair information in the form of crash repair documents of the quality necessary to ensure that every vehicle is repaired back to the original safety standard. In a market as important as this, availability is key, as is performance. Scala and Akka have delivered consistently so far.* + +*While recently introduced, growing numbers of UK repairers are receiving up to date repair information from this service, with the rest to follow shortly. Plans are already in motion to build new clusters to roll the service out across Europe, USA, Asia and Australasia.* + +*The sheer opportunities opened up to teams by Scala and Akka, in terms of integration, concise expression of intent and scalability are of huge benefit."* + +SVT (Swedish Television) +------------------------ + +``_ + +*“I’m currently working in a project at the Swedish Television where we’re developing a subtitling system with collaboration capabilities similar to Google Wave. It’s a mission critical system and the design and server implementation is all based on Akka and actors etc. We’ve been running in production for about 6 months and have been upgrading Akka whenever a new release comes out. We’ve never had a single bug due to Akka, and it’s been a pure pleasure to work with. I would choose Akka any day of the week!* + +*Our system is highly asynchronous so the actor style of doing things is a perfect fit. I don’t know about how you feel about concurrency in a big system, but rolling your own abstractions is not a very easy thing to do. When using Akka you can almost forget about all that. Synchronizing between threads, locking and protecting access to state etc. Akka is not just about actors, but that’s one of the most pleasurable things to work with. It’s easy to add new ones and it’s easy to design with actors. You can fire up work actors tied to a specific dispatcher etc. I could make the list of benefits much longer, but I’m at work right now. I suggest you try it out and see how it fits your requirements.* + +*We saw a perfect businness reson for using Akka. It lets you concentrate on the business logic instead of the low level things. It’s easy to teach others and the business intent is clear just by reading the code. We didn’t chose Akka just for fun. It’s a business critical application that’s used in broadcasting. Even live broadcasting. We wouldn’t have been where we are today in such a short time without using Akka. We’re two developers that have done great things in such a short amount of time and part of this is due to Akka. As I said, it lets us focus on the business logic instead of low level things such as concurrency, locking, performence etc."* + +Tapad +----- + +``_ + +*"Tapad is building a real-time ad exchange platform for advertising on mobile and connected devices. Real-time ad exchanges allows for advertisers (among other things) to target audiences instead of buying fixed set of ad slots that will be displayed “randomly” to users. To developers without experience in the ad space, this might seem boring, but real-time ad exchanges present some really interesting technical challenges.* + +*Take for instance the process backing a page view with ads served by a real-time ad exchange auction (somewhat simplified):* + +*1. A user opens a site (or app) which has ads in it.* +*2. As the page / app loads, the ad serving components fires off a request to the ad exchange (this might just be due to an image tag on the page).* +*3. The ad exchange enriches the request with any information about the current user (tracking cookies are often employed for this) and and display context information (“news article about parenting”, “blog about food” etc).* +*4. The ad exchange forwards the enriched request to all bidders registered with the ad exchange.* +*5. The bidders consider the provided user information and responds with what price they are willing to pay for this particular ad slot.* +*6. The ad exchange picks the highest bidder and ensures that the winning bidder’s ad is shown to to user.* + +*Any latency in this process directly influences user experience latency, so this has to happen really fast. All-in-all, the total time should not exceed about 100ms and most ad exchanges allow bidders to spend about 60ms (including network time) to return their bids. That leaves the ad exchange with less than 40ms to facilitate the auction. At Tapad, this happens billions of times per month / tens of thousands of times per second.* + +*Tapad is building bidders which will participate in auctions facilitated by other ad exchanges, but we’re also building our own. We are using Akka in several ways in several parts of the system. Here are some examples:* + +*Plain old parallelization* +*During an auction in the real-time exchange, it’s obvious that all bidders must receive the bid requests in parallel. An auctioneer actor sends the bid requests to bidder actors which in turn handles throttling and eventually IO. We use futures in these requests and the auctioneer discards any responses which arrive too late.* + +*Inside our bidders, we also rely heavily on parallel execution. In order to determine how much to pay for an ad slot, several data stores are queried for information pertinent to the current user. In a “traditional” system, we’d be doing this sequentially, but again, due to the extreme latency constraints, we’re doing this concurrently. Again, this is done with futures and data that is not available in time, get cut from the decision making (and logged :)).* + +*Maintaining state under concurrent load* +*This is probably the de facto standard use case for the actors model. Bidders internal to our system are actors backed by a advertiser campaign. A campaign includes, among other things, budget and “pacing” information. The budget determines how much money to spend for the duration of the campaign, whereas pacing information might set constraints on how quickly or slowly the money should be spent. Ad traffic changes from day to day and from hour to hour and our spending algorithms considers past performance in order to spend the right amount of money at the right time. Needless to say, these algorithms use a lot of state and this state is in constant flux. A bidder with a high budget may see tens of thousands of bid requests per second. Luckily, due to round-robin load-balancing and the predictability of randomness under heavy traffic, the bidder actors do not share state across cluster nodes, they just share their instance count so they know which fraction of the campaign budget to try to spend.* + +*Pacing is also done for external bidders. Each 3rd party bidder end-point has an actor coordinating requests and measuring latency and throughput. The actor never blocks itself, but when an incoming bid request is received, it considers the current performance of the 3rd party system and decides whether to pass on the request and respond negatively immediately, or forward the request to the 3rd party request executor component (which handles the IO).* + +*Batch processing* +*We store a lot of data about every single ad request we serve and this is stored in a key-value data store. Due to the performance characteristics of the data store, it is not feasible to store every single data point one at at time - it must be batched up and performed in parallel. We don’t need a durable messaging system for this (losing a couple of hundred data points is no biggie). All our data logging happens asynchronously and we have a basic load-balanced actors which batches incoming messages and writes on regular intervals (using Scheduler) or whenever the specified batch size has been reached.* + +*Analytics* +*Needless to say, it’s not feasible / useful to store our traffic information in a relational database. A lot of analytics and data analysis is done “offline” with map / reduce on top the data store, but this doesn’t work well for real-time analytics which our customers love. We therefore have metrics actors that receives campaign bidding and click / impression information in real-time, aggregates this information over configurable periods of time and flushes it to the database used for customer dashboards for “semi-real-time” display. Five minute history is considered real-time in this business, but in theory, we could have queried the actors directly for really real-time data. :)* + +*Our Akka journey started as a prototyping project, but Akka has now become a crucial part of our system. All of the above mentioned components, except the 3rd party bidder integration, have been running in production for a couple of weeks (on Akka 1.0RC3) and we have not seen any issues at all so far."* + +Flowdock +-------- + +Flowdock delivers Google Wave for the corporate world. + +*"Flowdock makes working together a breeze. Organize the flow of information, task things over and work together towards common goals seamlessly on the web - in real time."* + +``_ + +Travel Budget +------------- + +``_ + +Says.US +------- + +*"says.us is a gathering place for people to connect in real time - whether an informal meeting of people who love Scala or a chance for people anywhere to speak out about the latest headlines."* + +``_ + +LShift +------ + +*"Diffa is an open source data analysis tool that automatically establishes data differences between two or more real-time systems.* + +*Diffa will help you compare local or distributed systems for data consistency, without having to stop them running or implement manual cross-system comparisons. The interface provides you with simple visual summary of any consistency breaks and tools to investigate the issues.* + +*Diffa is the ideal tool to use to investigate where or when inconsistencies are occuring, or simply to provide confidence that your systems are running in perfect sync. It can be used operationally as an early warning system, in deployment for release verification, or in development with other enterprise diagnosis tools to help troubleshoot faults."* + +``_ + +Twimpact +-------- + +*"Real-time twitter trends and user impact"* + +``_ + +Rocket Pack Platform +-------------------- + +*"Rocket Pack Platform is the only fully integrated solution for plugin-free browser game development."* + +``_ + +Open Source Projects using Akka +=============================== + +Redis client +------------ + +*A Redis client written Scala, using Akka actors, HawtDispath and non-blocking IO. Supports Redis 2.0+* + +``_ + +Narrator +-------- + +*"Narrator is a a library which can be used to create story driven clustered load-testing packages through a very readable and understandable api."* + +``_ + +Kandash +------- + +*"Kandash is a lightweight kanban web-based board and set of analytics tools."* + +``_ +``_ + +Wicket Cassandra Datastore +-------------------------- + +This project provides an org.apache.wicket.pageStore.IDataStore implementation that writes pages to an Apache Cassandra cluster using Akka. + +``_ + +Spray +----- + +//"spray is a lightweight Scala framework for building RESTful web services on top of Akka actors and Akka Mist. It sports the following main features: +* Completely asynchronous, non-blocking, actor-based request processing for efficiently handling very high numbers of concurrent connections +* Powerful, flexible and extensible internal Scala DSL for declaratively defining your web service behavior +* Immutable model of the HTTP protocol, decoupled from the underlying servlet container +* Full testability of your REST services, without the need to fire up containers or actors"// + +``_ diff --git a/akka-docs/pending/configuration.rst b/akka-docs/pending/configuration.rst new file mode 100644 index 0000000000..19d4a1a566 --- /dev/null +++ b/akka-docs/pending/configuration.rst @@ -0,0 +1,180 @@ +Configuration +============= + +Specifying the configuration file +--------------------------------- + +If you don't specify a configuration file then Akka is using default values. If you want to override these then you should edit the 'akka.conf' file in the 'AKKA_HOME/config' directory. This config inherits from the 'akka-reference.conf' file that you see below, use your 'akka.conf' to override any property in the reference config. + +The config can be specified in a various of ways: + +* Define the '-Dakka.config=...' system property option. +* Put the 'akka.conf' file on the classpath. +* Define 'AKKA_HOME' environment variable pointing to the root of the Akka distribution, in which the config is taken from the 'AKKA_HOME/config' directory, you can also point to the AKKA_HOME by specifying the '-Dakka.home=...' system property option. + +Defining the configuration file +------------------------------- + +``_ +#################### +# Akka Config File # +#################### + +# This file has all the default settings, so all these could be removed with no visible effect. +# Modify as needed. + +akka { + version = "1.1-SNAPSHOT" # Akka version, checked against the runtime version of Akka. + + enabled-modules = [] # Comma separated list of the enabled modules. Options: ["remote", "camel", "http"] + + time-unit = "seconds" # Time unit for all timeout properties throughout the config + + event-handlers = ["akka.event.EventHandler$DefaultListener"] # event handlers to register at boot time (EventHandler$DefaultListener logs to STDOUT) + event-handler-level = "DEBUG" # Options: ERROR, WARNING, INFO, DEBUG + + # These boot classes are loaded (and created) automatically when the Akka Microkernel boots up + # Can be used to bootstrap your application(s) + # Should be the FQN (Fully Qualified Name) of the boot class which needs to have a default constructor + # boot = ["sample.camel.Boot", + # "sample.rest.java.Boot", + # "sample.rest.scala.Boot", + # "sample.security.Boot"] + boot = [] + + actor { + timeout = 5 # Default timeout for Future based invocations + # - Actor: !! && !!! + # - UntypedActor: sendRequestReply && sendRequestReplyFuture + # - TypedActor: methods with non-void return type + serialize-messages = off # Does a deep clone of (non-primitive) messages to ensure immutability + throughput = 5 # Default throughput for all ExecutorBasedEventDrivenDispatcher, set to 1 for complete fairness + throughput-deadline-time = -1 # Default throughput deadline for all ExecutorBasedEventDrivenDispatcher, set to 0 or negative for no deadline + dispatcher-shutdown-timeout = 1 # Using the akka.time-unit, how long dispatchers by default will wait for new actors until they shut down + + default-dispatcher { + type = "GlobalExecutorBasedEventDriven" # Must be one of the following, all "Global*" are non-configurable + # - ExecutorBasedEventDriven + # - ExecutorBasedEventDrivenWorkStealing + # - GlobalExecutorBasedEventDriven + keep-alive-time = 60 # Keep alive time for threads + core-pool-size-factor = 1.0 # No of core threads ... ceil(available processors * factor) + max-pool-size-factor = 4.0 # Max no of threads ... ceil(available processors * factor) + executor-bounds = -1 # Makes the Executor bounded, -1 is unbounded + allow-core-timeout = on # Allow core threads to time out + rejection-policy = "caller-runs" # abort, caller-runs, discard-oldest, discard + throughput = 5 # Throughput for ExecutorBasedEventDrivenDispatcher, set to 1 for complete fairness + throughput-deadline-time = -1 # Throughput deadline for ExecutorBasedEventDrivenDispatcher, set to 0 or negative for no deadline + mailbox-capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) + # If positive then a bounded mailbox is used and the capacity is set using the property + # NOTE: setting a mailbox to 'blocking' can be a bit dangerous, + # could lead to deadlock, use with care + # + # The following are only used for ExecutorBasedEventDriven + # and only if mailbox-capacity > 0 + mailbox-push-timeout-time = 10 # Specifies the timeout to add a new message to a mailbox that is full - negative number means infinite timeout + # (in unit defined by the time-unit property) + } + } + + stm { + fair = on # Should global transactions be fair or non-fair (non fair yield better performance) + max-retries = 1000 + timeout = 5 # Default timeout for blocking transactions and transaction set (in unit defined by + # the time-unit property) + write-skew = true + blocking-allowed = false + interruptible = false + speculative = true + quick-release = true + propagation = "requires" + trace-level = "none" + } + + jta { + provider = "from-jndi" # Options: - "from-jndi" (means that Akka will try to detect a TransactionManager in the JNDI) + # - "atomikos" (means that Akka will use the Atomikos based JTA impl in 'akka-jta', + # e.g. you need the akka-jta JARs on classpath). + timeout = 60 + } + + http { + hostname = "localhost" + port = 9998 + + #If you are using akka.http.AkkaRestServlet + filters = ["se.scalablesolutions.akka.security.AkkaSecurityFilterFactory"] # List with all jersey filters to use + # resource-packages = ["sample.rest.scala", + # "sample.rest.java", + # "sample.security"] # List with all resource packages for your Jersey services + resource-packages = [] + + # The authentication service to use. Need to be overridden (sample now) + # authenticator = "sample.security.BasicAuthenticationService" + authenticator = "N/A" + + # Uncomment if you are using the KerberosAuthenticationActor + # kerberos { + # servicePrincipal = "HTTP/localhost@EXAMPLE.COM" + # keyTabLocation = "URL to keytab" + # kerberosDebug = "true" + # realm = "EXAMPLE.COM" + # } + kerberos { + servicePrincipal = "N/A" + keyTabLocation = "N/A" + kerberosDebug = "N/A" + realm = "" + } + + #If you are using akka.http.AkkaMistServlet + mist-dispatcher { + #type = "GlobalExecutorBasedEventDriven" # Uncomment if you want to use a different dispatcher than the default one for Comet + } + connection-close = true # toggles the addition of the "Connection" response header with a "close" value + root-actor-id = "_httproot" # the id of the actor to use as the root endpoint + root-actor-builtin = true # toggles the use of the built-in root endpoint base class + timeout = 1000 # the default timeout for all async requests (in ms) + expired-header-name = "Async-Timeout" # the name of the response header to use when an async request expires + expired-header-value = "expired" # the value of the response header to use when an async request expires + } + + remote { + + # secure-cookie = "050E0A0D0D06010A00000900040D060F0C09060B" # generate your own with '$AKKA_HOME/scripts/generate_secure_cookie.sh' or using 'Crypt.generateSecureCookie' + secure-cookie = "" + + compression-scheme = "zlib" # Options: "zlib" (lzf to come), leave out for no compression + zlib-compression-level = 6 # Options: 0-9 (1 being fastest and 9 being the most compressed), default is 6 + + layer = "akka.remote.netty.NettyRemoteSupport" + + server { + hostname = "localhost" # The hostname or IP that clients should connect to + port = 2552 # The port clients should connect to. Default is 2552 (AKKA) + message-frame-size = 1048576 # Increase this if you want to be able to send messages with large payloads + connection-timeout = 1 + require-cookie = off # Should the remote server require that it peers share the same secure-cookie (defined in the 'remote' section)? + untrusted-mode = off # Enable untrusted mode for full security of server managed actors, allows untrusted clients to connect. + backlog = 4096 # Sets the size of the connection backlog + execution-pool-keepalive = 60# Length in akka.time-unit how long core threads will be kept alive if idling + execution-pool-size = 16# Size of the core pool of the remote execution unit + max-channel-memory-size = 0 # Maximum channel size, 0 for off + max-total-memory-size = 0 # Maximum total size of all channels, 0 for off + } + + client { + buffering { + retry-message-send-on-failure = on + capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) + # If positive then a bounded mailbox is used and the capacity is set using the property + } + reconnect-delay = 5 + read-timeout = 10 + message-frame-size = 1048576 + reap-futures-delay = 5 + reconnection-time-window = 600 # Maximum time window that a client should try to reconnect for + } + } +} +``_ diff --git a/akka-docs/pending/dataflow-java.rst b/akka-docs/pending/dataflow-java.rst new file mode 100644 index 0000000000..69d7cec42e --- /dev/null +++ b/akka-docs/pending/dataflow-java.rst @@ -0,0 +1,191 @@ +Dataflow Concurrency (Java) +=========================== + +Introduction +============ + +IMPORTANT: As of Akka 1.1, Akka Future, CompletableFuture and DefaultCompletableFuture have all the functionality of DataFlowVariables, they also support non-blocking composition and advanced features like fold and reduce, Akka DataFlowVariable is therefor deprecated and will probably resurface in the following release as a DSL on top of Futures. +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +Akka implements `Oz-style dataflow concurrency `_ through dataflow (single assignment) variables and lightweight (event-based) processes/threads. + +Dataflow concurrency is deterministic. This means that it will always behave the same. If you run it once and it yields output 5 then it will do that **every time**, run it 10 million times, same result. If it on the other hand deadlocks the first time you run it, then it will deadlock **every single time** you run it. Also, there is **no difference** between sequential code and concurrent code. These properties makes it very easy to reason about concurrency. The limitation is that the code needs to be side-effect free, e.g. deterministic. You can't use exceptions, time, random etc., but need to treat the part of your program that uses dataflow concurrency as a pure function with input and output. + +The best way to learn how to program with dataflow variables is to read the fantastic book `Concepts, Techniques, and Models of Computer Programming `_. By Peter Van Roy and Seif Haridi. + +The documentation is not as complete as it should be, something we will improve shortly. For now, besides above listed resources on dataflow concurrency, I recommend you to read the documentation for the GPars implementation, which is heavily influenced by the Akka implementation: +* ``_ +* ``_ + +Dataflow Variables +------------------ + +Dataflow Variable defines three different operations: + +1. Define a Dataflow Variable + +.. code-block:: java + + import static akka.dataflow.DataFlow.*; + + DataFlowVariable x = new DataFlowVariable(); + +2. Wait for Dataflow Variable to be bound + +.. code-block:: java + + x.get(); + +3. Bind Dataflow Variable + +.. code-block:: java + + x.set(3); + +A Dataflow Variable can only be bound once. Subsequent attempts to bind the variable will throw an exception. + +You can also shutdown a dataflow variable like this: + +.. code-block:: java + + x.shutdown(); + +Threads +------- + +You can easily create millions lightweight (event-driven) threads on a regular workstation. + +.. code-block:: java + + import static akka.dataflow.DataFlow.*; + import akka.japi.Effect; + + thread(new Effect() { + public void apply() { ... } + }); + +You can also set the thread to a reference to be able to control its life-cycle: + +.. code-block:: java + + import static akka.dataflow.DataFlow.*; + import akka.japi.Effect; + + ActorRef t = thread(new Effect() { + public void apply() { ... } + }); + + ... // time passes + + t.sendOneWay(new Exit()); // shut down the thread + +Examples +======== + +Most of these examples are taken from the `Oz wikipedia page `_ + +Simple DataFlowVariable example +------------------------------- + +This example is from Oz wikipedia page: http://en.wikipedia.org/wiki/Oz_(programming_language). +Sort of the "Hello World" of dataflow concurrency. + +Example in Oz: + +.. code-block:: ruby + + thread + Z = X+Y % will wait until both X and Y are bound to a value. + {Browse Z} % shows the value of Z. + end + thread X = 40 end + thread Y = 2 end + +Example in Akka: + +.. code-block:: java + + import static akka.dataflow.DataFlow.*; + import akka.japi.Effect; + + DataFlowVariable x = new DataFlowVariable(); + DataFlowVariable y = new DataFlowVariable(); + DataFlowVariable z = new DataFlowVariable(); + + thread(new Effect() { + public void apply() { + z.set(x.get() + y.get()); + System.out.println("z = " + z.get()); + } + }); + + thread(new Effect() { + public void apply() { + x.set(40); + } + }); + + thread(new Effect() { + public void apply() { + y.set(40); + } + }); + +Example on life-cycle management of DataFlowVariables +----------------------------------------------------- + +Shows how to shutdown dataflow variables and bind threads to values to be able to interact with them (exit etc.). + +Example in Akka: + +``_ +import static akka.dataflow.DataFlow.*; +import akka.japi.Effect; + +// create four 'int' data flow variables +DataFlowVariable x = new DataFlowVariable(); +DataFlowVariable y = new DataFlowVariable(); +DataFlowVariable z = new DataFlowVariable(); +DataFlowVariable v = new DataFlowVariable(); + +ActorRef main = thread(new Effect() { + public void apply() { + System.out.println("Thread 'main'") + if (x.get() > y.get()) { + z.set(x); + System.out.println("'z' set to 'x': " + z.get()); + } else { + z.set(y); + System.out.println("'z' set to 'y': " + z.get()); + } + + // main completed, shut down the data flow variables + x.shutdown(); + y.shutdown(); + z.shutdown(); + v.shutdown(); + } +}); + +ActorRef setY = thread(new Effect() { + public void apply() { + System.out.println("Thread 'setY', sleeping..."); + Thread.sleep(5000); + y.set(2); + System.out.println("'y' set to: " + y.get()); + } +}); + +ActorRef setV = thread(new Effect() { + public void apply() { + System.out.println("Thread 'setV'"); + y.set(2); + System.out.println("'v' set to y: " + v.get()); + } +}); + +// shut down the threads +main.sendOneWay(new Exit()); +setY.sendOneWay(new Exit()); +setV.sendOneWay(new Exit()); +``_ diff --git a/akka-docs/pending/dataflow-scala.rst b/akka-docs/pending/dataflow-scala.rst new file mode 100644 index 0000000000..135a1f535f --- /dev/null +++ b/akka-docs/pending/dataflow-scala.rst @@ -0,0 +1,233 @@ +Dataflow Concurrency (Scala) +============================ + +Description +=========== + +IMPORTANT: As of Akka 1.1, Akka Future, CompletableFuture and DefaultCompletableFuture have all the functionality of DataFlowVariables, they also support non-blocking composition and advanced features like fold and reduce, Akka DataFlowVariable is therefor deprecated and will probably resurface in the following release as a DSL on top of Futures. +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +Akka implements `Oz-style dataflow concurrency `_ through dataflow (single assignment) variables and lightweight (event-based) processes/threads. + +Dataflow concurrency is deterministic. This means that it will always behave the same. If you run it once and it yields output 5 then it will do that **every time**, run it 10 million times, same result. If it on the other hand deadlocks the first time you run it, then it will deadlock **every single time** you run it. Also, there is **no difference** between sequential code and concurrent code. These properties makes it very easy to reason about concurrency. The limitation is that the code needs to be side-effect free, e.g. deterministic. You can't use exceptions, time, random etc., but need to treat the part of your program that uses dataflow concurrency as a pure function with input and output. + +The best way to learn how to program with dataflow variables is to read the fantastic book `Concepts, Techniques, and Models of Computer Programming `_. By Peter Van Roy and Seif Haridi. + +The documentation is not as complete as it should be, something we will improve shortly. For now, besides above listed resources on dataflow concurrency, I recommend you to read the documentation for the GPars implementation, which is heavily influenced by the Akka implementation: +* ``_ +* ``_ + +Dataflow Variables +------------------ + +Dataflow Variable defines three different operations: + +1. Define a Dataflow Variable + +.. code-block:: scala + + val x = new DataFlowVariable[Int] + +2. Wait for Dataflow Variable to be bound + +.. code-block:: scala + + x() + +3. Bind Dataflow Variable + +.. code-block:: scala + + x << 3 + +A Dataflow Variable can only be bound once. Subsequent attempts to bind the variable will throw an exception. + +You can also shutdown a dataflow variable like this: + +.. code-block:: scala + + x.shutdown + +Threads +------- + +You can easily create millions lightweight (event-driven) threads on a regular workstation. + +.. code-block:: scala + + thread { ... } + +You can also set the thread to a reference to be able to control its life-cycle: + +.. code-block:: scala + + val t = thread { ... } + + ... // time passes + + t ! 'exit // shut down the thread + +Examples +======== + +Most of these examples are taken from the `Oz wikipedia page `_ + +To run these examples: + +1. Start REPL + +:: + + $ sbt + > project akka-actor + > console + +:: + + Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_22). + Type in expressions to have them evaluated. + Type :help for more information. + + scala> + +2. Paste the examples (below) into the Scala REPL. +Note: Do not try to run the Oz version, it is only there for reference. + +3. Have fun. + +Simple DataFlowVariable example +------------------------------- + +This example is from Oz wikipedia page: http://en.wikipedia.org/wiki/Oz_(programming_language). +Sort of the "Hello World" of dataflow concurrency. + +Example in Oz: + +.. code-block:: ruby + + thread + Z = X+Y % will wait until both X and Y are bound to a value. + {Browse Z} % shows the value of Z. + end + thread X = 40 end + thread Y = 2 end + +Example in Akka: + +.. code-block:: scala + + import akka.dataflow.DataFlow._ + + val x, y, z = new DataFlowVariable[Int] + + thread { + z << x() + y() + println("z = " + z()) + } + thread { x << 40 } + thread { y << 2 } + +Example of using DataFlowVariable with recursion +------------------------------------------------ + +Using DataFlowVariable and recursion to calculate sum. + +Example in Oz: + +.. code-block:: ruby + + fun {Ints N Max} + if N == Max then nil + else + {Delay 1000} + N|{Ints N+1 Max} + end + end + + fun {Sum S Stream} + case Stream of nil then S + [] H|T then S|{Sum H+S T} end + end + + local X Y in + thread X = {Ints 0 1000} end + thread Y = {Sum 0 X} end + {Browse Y} + end + +Example in Akka: + +.. code-block:: scala + + import akka.dataflow.DataFlow._ + + def ints(n: Int, max: Int): List[Int] = + if (n == max) Nil + else n :: ints(n + 1, max) + + def sum(s: Int, stream: List[Int]): List[Int] = stream match { + case Nil => s :: Nil + case h :: t => s :: sum(h + s, t) + } + + val x = new DataFlowVariable[List[Int]] + val y = new DataFlowVariable[List[Int]] + + thread { x << ints(0, 1000) } + thread { y << sum(0, x()) } + thread { println("List of sums: " + y()) } + +Example on life-cycle management of DataFlowVariables +----------------------------------------------------- + +Shows how to shutdown dataflow variables and bind threads to values to be able to interact with them (exit etc.). + +Example in Akka: + +``_ +import akka.dataflow.DataFlow._ + +// create four 'Int' data flow variables +val x, y, z, v = new DataFlowVariable[Int] + +val main = thread { + println("Thread 'main'") + + x << 1 + println("'x' set to: " + x()) + + println("Waiting for 'y' to be set...") + + if (x() > y()) { + z << x + println("'z' set to 'x': " + z()) + } else { + z << y + println("'z' set to 'y': " + z()) + } + + // main completed, shut down the data flow variables + x.shutdown + y.shutdown + z.shutdown + v.shutdown +} + +val setY = thread { + println("Thread 'setY', sleeping...") + Thread.sleep(5000) + y << 2 + println("'y' set to: " + y()) +} + +val setV = thread { + println("Thread 'setV'") + v << y + println("'v' set to 'y': " + v()) +} + +// shut down the threads +main ! 'exit +setY ! 'exit +setV ! 'exit +``_ diff --git a/akka-docs/pending/deployment-scenarios.rst b/akka-docs/pending/deployment-scenarios.rst new file mode 100644 index 0000000000..9c67cda10d --- /dev/null +++ b/akka-docs/pending/deployment-scenarios.rst @@ -0,0 +1,100 @@ + + +Use-case and Deployment Scenarios +================================= + += + +How and in which use-case and deployment scenarios can I use Akka? +================================================================== + +Akka can be used in two different ways: +* As a library: used as a regular JAR on the classpath and/or in a web app, to be put into ‘WEB-INF/lib’ +* As a microkernel: stand-alone microkernel, embedding a servlet container along with many other services. + +Using Akka as library +--------------------- + +This is most likely what you want if you are building Web applications. +There are several ways you can use Akka in Library mode by adding more and more modules to the stack. + +Actors as services +^^^^^^^^^^^^^^^^^^ + +The simplest way you can use Akka is to use the actors as services in your Web application. All that’s needed to do that is to put the Akka charts as well as its dependency jars into ‘WEB-INF/lib’. You also need to put the ‘akka.conf’ config file in the ‘$AKKA_HOME/config’ directory. +Now you can create your Actors as regular services referenced from your Web application. You should also be able to use the Remoting service, e.g. be able to make certain Actors remote on other hosts. Please note that remoting service does not speak HTTP over port 80, but a custom protocol over the port is specified in ‘akka.conf’. +``_ + +^ + +Actors as services with Software Transactional Memory (STM) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As in the above, but with the addition of using the STM module to allow transactional memory across many Actors (no persistence, just in-memory). +``_ + +^ + +Actors as services with Persistence module as cache +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As in the above, but with the addition of using the Persistence module to allow transactional persistent cache. This use case scenario you would still use a regular relational database (RDBMS) but use Akka’s transactional persistent storage as a performant scalable cache alongside the RDBMS. +``_ + +^ + +Actors as services with Persistence module as primary storage/Service of Record (SoR) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As in the above, but with the addition of using the Persistence module as the primary storage/SoR. In this use case you wouldn’t use a RDBMS at all but rely on one of the Akka backends (Cassandra, Terracotta, Redis, MongoDB etc.) as transactional persistent storage. This is great if have either high performance, scalability or high-availability requirements where a RDBMS would be either single point of failure or single point of bottleneck or just be too slow. +If the storage API (Maps, Vectors or Refs) is too constrained for some use cases we can bypass it and use the storage directly. However, please note that then we will lose the transactional semantics. +``_ + +^ + +Actors as REST/Comet (push) services +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can also expose your library Actors directly as REST (`JAX `_`-RS `_) or Comet (`Atmosphere `_) services by deploying the ‘AkkaServlet’ in your servlet container. In order for this to work in each define a so-called “boot” class which bootstraps the Actor configuration, wiring and startup. This is done in the ‘akka.conf’ file. +``_ + +- + +Using Akka as a stand alone microkernel +--------------------------------------- + +Akka can also be run as a stand-alone microkernel. It implements a full enterprise stack: + +^ + +Web/REST/Comet layer +^^^^^^^^^^^^^^^^^^^^ + +Akka currently embeds the `Grizzly/GlassFish `_ servlet container (but will soon be pluggable with Jetty as well) which allows to build REST-based using `JAX `_`-RS `_ and Comet-based services using `Atmosphere `_ as well as regular Web applications using JAX-RS’s `implicit views `_ (see also `James Strachan’s article `_). + +^ + +Service layer +^^^^^^^^^^^^^ + +The service layer is implemented using fault tolerant, asynchronous, throttled message passing; like `SEDA-in-a-box `_ using Actors. + +Persistence layer +^^^^^^^^^^^^^^^^^ + + Implemented using pluggable storage engines for both partitioned distributed massively scalable storage (like Cassandra) as well as single node storage (like MongoDB). A different storage and gives also provides different consistency/availability trade-offs implementing either Eventually Consistency (BASE) or Atomicity (ACID). + +Monitoring and Management layer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Providing both JMX management and monitoring as well as w3c logging. + ``_ + +Use BivySack for packaging your application +------------------------------------------- + +"BivySack" For Akka - SBT plugin which creates a full akka microkernel deployment for your project. + +Quick and dirty SBT Plugin for creating Akka Microkernel deployments of your SBT Project. This creates a proper "akka deploy" setup with all of your dependencies and configuration files loaded, with a bootable version of your project that you can run cleanly. + +Read more about it here ``_. diff --git a/akka-docs/pending/developer-guidelines.rst b/akka-docs/pending/developer-guidelines.rst new file mode 100644 index 0000000000..f1fcbc8c96 --- /dev/null +++ b/akka-docs/pending/developer-guidelines.rst @@ -0,0 +1,42 @@ +Developer Guidelines +==================== + +Code Style +========== + +The Akka code style follows `this document `_ . + +Here is a code style settings file for IntelliJ IDEA. +``_ + +Please follow the code style. Look at the code around you and mimic. + +Testing +======= + +All code that is checked in should have tests. All testing is done with ScalaTest and ScalaCheck. +* Name tests as *Test.scala if they do not depend on any external stuff. That keeps surefire happy. +* Name tests as *Spec.scala if they have external dependencies. +There is a testing standard that should be followed: `Ticket001Spec <@https://github.com/jboner/akka/blob/master/akka-actor/src/test/scala/akka/ticket/Ticket001Spec.scala>`_ + +Actor TestKit +------------- + +There is a useful test kit for testing actors: `akka.util.TestKit <@https://github.com/jboner/akka/tree/master/akka-actor/src/main/scala/akka/util/TestKit.scala>`_. It enables assertions concerning replies received and their timing, there is more documentation in the ``_ module. + +NetworkFailureTest +------------------ + +You can use the 'NetworkFailureTest' trait to test network failure. See the 'RemoteErrorHandlingNetworkTest' test. Your tests needs to end with 'NetworkTest'. They are disabled by default. To run them you need to enable a flag. + +Example: + +:: + + project akka-remote + set akka.test.network true + test-only akka.actor.remote.RemoteErrorHandlingNetworkTest + +It uses 'ipfw' for network management. Mac OSX comes with it installed but if you are on another platform you might need to install it yourself. Here is a port: + +``_ diff --git a/akka-docs/pending/dispatchers-java.rst b/akka-docs/pending/dispatchers-java.rst new file mode 100644 index 0000000000..5882f4d426 --- /dev/null +++ b/akka-docs/pending/dispatchers-java.rst @@ -0,0 +1,267 @@ +Dispatchers (Java) +================== + +Module stability: **SOLID** + +The Dispatcher is an important piece that allows you to configure the right semantics and parameters for optimal performance, throughput and scalability. Different Actors have different needs. + +Akka supports dispatchers for both event-driven lightweight threads, allowing creation of millions threads on a single workstation, and thread-based Actors, where each dispatcher is bound to a dedicated OS thread. + +The event-based Actors currently consume ~600 bytes per Actor which means that you can create more than 6.5 million Actors on 4 G RAM. + +Default dispatcher +------------------ + +For most scenarios the default settings are the best. Here we have one single event-based dispatcher for all Actors created. The dispatcher used is this one: + +.. code-block:: java + + Dispatchers.globalExecutorBasedEventDrivenDispatcher(); + +But if you feel that you are starting to contend on the single dispatcher (the 'Executor' and its queue) or want to group a specific set of Actors for a dedicated dispatcher for better flexibility and configurability then you can override the defaults and define your own dispatcher. See below for details on which ones are available and how they can be configured. + +Setting the dispatcher +---------------------- + +Normally you set the dispatcher from within the Actor itself. The dispatcher is defined by the 'dispatcher: MessageDispatcher' member field in 'ActorRef'. + +.. code-block:: java + + class MyActor extends UntypedActor { + public MyActor() { + getContext().setDispatcher(..); // set the dispatcher + } + ... + } + +You can also set the dispatcher for an Actor **before** it has been started: + +.. code-block:: java + + actorRef.setDispatcher(dispatcher); + +Types of dispatchers +-------------------- + +There are six different types of message dispatchers: + +* Thread-based +* Event-based +* Work-stealing event-based +* HawtDispatch-based event-driven + +Factory methods for all of these, including global versions of some of them, are in the 'akka.dispatch.Dispatchers' object. + +Let's now walk through the different dispatchers in more detail. + +Thread-based +^^^^^^^^^^^^ + +The 'ThreadBasedDispatcher' binds a dedicated OS thread to each specific Actor. The messages are posted to a 'LinkedBlockingQueue' which feeds the messages to the dispatcher one by one. A 'ThreadBasedDispatcher' cannot be shared between actors. This dispatcher has worse performance and scalability than the event-based dispatcher but works great for creating "daemon" Actors that consumes a low frequency of messages and are allowed to go off and do their own thing for a longer period of time. Another advantage with this dispatcher is that Actors do not block threads for each other. + +.. code-block:: java + + Dispatcher dispatcher = Dispatchers.newThreadBasedDispatcher(actorRef); + +It would normally by used from within the actor like this: + +.. code-block:: java + + class MyActor extends Actor { + public MyActor() { + getContext().setDispatcher(Dispatchers.newThreadBasedDispatcher(getContext())); + } + ... + } + +Event-based +^^^^^^^^^^^ + +The 'ExecutorBasedEventDrivenDispatcher' binds a set of Actors to a thread pool backed up by a 'BlockingQueue'. This dispatcher is highly configurable and supports a fluent configuration API to configure the 'BlockingQueue' (type of queue, max items etc.) as well as the thread pool. + +The event-driven dispatchers **must be shared** between multiple Typed Actors and/or Actors. One best practice is to let each top-level Actor, e.g. the Actors you define in the declarative supervisor config, to get their own dispatcher but reuse the dispatcher for each new Actor that the top-level Actor creates. But you can also share dispatcher between multiple top-level Actors. This is very use-case specific and needs to be tried out on a case by case basis. The important thing is that Akka tries to provide you with the freedom you need to design and implement your system in the most efficient way in regards to performance, throughput and latency. + +It comes with many different predefined BlockingQueue configurations: +* Bounded LinkedBlockingQueue +* Unbounded LinkedBlockingQueue +* Bounded ArrayBlockingQueue +* Unbounded ArrayBlockingQueue +* SynchronousQueue + +You can also set the rejection policy that should be used, e.g. what should be done if the dispatcher (e.g. the Actor) can't keep up and the mailbox is growing up to the limit defined. You can choose between four different rejection policies: + +* java.util.concurrent.ThreadPoolExecutor.CallerRuns - will run the message processing in the caller's thread as a way to slow him down and balance producer/consumer +* java.util.concurrent.ThreadPoolExecutor.AbortPolicy - rejected messages by throwing a 'RejectedExecutionException' +* 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 `_. + +Here is an example: + +.. code-block:: java + + class MyActor extends UntypedActor { + public MyActor() { + getContext().setDispatcher(Dispatchers.newExecutorBasedEventDrivenDispatcher(name) + .withNewThreadPoolWithBoundedBlockingQueue(100) + .setCorePoolSize(16) + .setMaxPoolSize(128) + .setKeepAliveTimeInMillis(60000) + .setRejectionPolicy(new CallerRunsPolicy) + .build()); + } + ... + } + +This 'ExecutorBasedEventDrivenDispatcher' 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: + +.. code-block:: xml + + actor { + throughput = 5 + } + +If you don't define a the 'throughput' option in the configuration file then the default value of '5' will be used. + +Browse the `ScalaDoc `_ or look at the code for all the options available. + +Work-stealing event-based +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The 'ExecutorBasedEventDrivenWorkStealingDispatcher' is a variation of the 'ExecutorBasedEventDrivenDispatcher' in which Actors of the same type can be set up to share this dispatcher and during execution time the different actors will steal messages from other actors if they have less messages to process. This can be a great way to improve throughput at the cost of a little higher latency. + +Normally the way you use it is to create an Actor companion object to hold the dispatcher and then set in in the Actor explicitly. + +.. code-block:: java + + class MyActor extends UntypedActor { + public static Dispatcher dispatcher = Dispatchers.newExecutorEventBasedWorkStealingDispatcher(name); + + public MyActor() { + getContext().setDispatcher(dispatcher); + } + ... + } + +Here is an article with some more information: `Load Balancing Actors with Work Stealing Techniques `_ +Here is another article discussing this particular dispatcher: `Flexible load balancing with Akka in Scala `_ + +HawtDispatch-based event-driven +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The 'HawtDispatcher' uses the `HawtDispatch threading library `_ which is a Java clone of libdispatch. All actors with this type of dispatcher are executed on a single system wide fixed sized thread pool. The number of of threads will match the number of cores available on your system. The dispatcher delivers messages to the actors in the order that they were producer at the sender. + +A 'HawtDispatcher' instance can be shared by many actors. Normally the way you use it is to create an Actor companion object to hold the dispatcher and then set in in the Actor explicitly. + +.. code-block:: java + + import akka.actor.dispatch.HawtDispatcher; + + class MyActor extends Actor { + public static Dispatcher dispatcher = new HawtDispatcher(); + + public MyActor() { + getContext().setDispatcher(dispatcher); + } + ... + } + +Since a fixed thread pool is being used, an actor using a 'HawtDispatcher' is restricted to executing non blocking operations. For example, the actor is NOT alllowed to: +* synchronously call another actor +* call 3rd party libraries that can block +* use sockets that are in blocking mode + +HawtDispatch supports integrating non-blocking Socket IO events with your actors. Every thread in the HawtDispatch thread pool is parked in an IO event loop when it is not executing an actors. The IO events can be configured to be get delivered to the actor in either the reactor or proactor style. For an example, see `HawtDispacherEchoServer.scala `_. + +A `HawtDispatcher` will aggregate cross actor messages by default. This means that if Actor *A* is executing and sends actor *B* 10 messages, those messages will not be delivered to actor *B* until *A*'s execution ends. HawtDispatch will aggregate the 10 messages into 1 single enqueue operation on to actor *B*'s inbox. This an significantly reduce mailbox contention when actors are very chatty. If you want to avoid this aggregation behavior, then create the `HawtDispatcher` like this: + +.. code-block:: java + + Dispatcher dispatcher = new HawtDispatcher(false); + +The `HawtDispatcher` provides a companion object that lets you use more advanced HawtDispatch features. For example to pin an actor so that it always executed on the same thread in the thread poool you would: + +.. code-block:: java + + ActorRef a = ... + HawtDispatcher.pin(a); + +If you have an Actor *b* which will be sending many messages to an Actor *a*, then you may want to consider setting *b*'s dispatch target to be *a*'s dispatch queue. When this is the case, messages sent from *b* to a will avoid cross thread mailbox contention. A side-effect of this is that the *a* and *b* actors will execute as if they shared a single mailbox. + +.. code-block:: java + + ActorRef a = ... + ActorRef b = ... + HawtDispatcher.target(b, HawtDispatcher.queue(a)); + +**Java API** + +.. code-block:: java + + MessageDispatcher dispatcher = Dispatchers.newExecutorEventBasedThreadPoolDispatcher(name); + +The dispatcher for an Typed Actor can be defined in the declarative configuration: + +.. code-block:: java + + ... // part of configuration + new Component( + MyTypedActor.class, + MyTypedActorImpl.class, + new LifeCycle(new Permanent()), + dispatcher, // <<== set it here + 1000); + ... + +It can also be set when creating a new Typed Actor programmatically. + +.. code-block:: java + + MyPOJO pojo = (MyPOJO) TypedActor.newInstance(MyPOJO.class, MyPOJOImpl.class, 1000, dispatcher); + +Making the Actor mailbox bounded +-------------------------------- + +Global configuration +^^^^^^^^^^^^^^^^^^^^ + +You can make the Actor mailbox bounded by a capacity in two ways. Either you define it in the configuration file under 'default-dispatcher'. This will set it globally. + +.. code-block:: ruby + + actor { + default-dispatcher { + mailbox-capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) + # If positive then a bounded mailbox is used and the capacity is set to the number specificed + } + } + +Per-instance based configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can also do it on a specific dispatcher instance. + +For the 'ExecutorBasedEventDrivenDispatcher' and the 'ExecutorBasedWorkStealingDispatcher' you can do it through their constructor + +.. code-block:: java + + class MyActor extends UntypedActor { + public MyActor() { + getContext().setDispatcher(Dispatchers.newExecutorBasedEventDrivenDispatcher(name, throughput, mailboxCapacity)); + } + ... + } + +For the 'ThreadBasedDispatcher', it is non-shareable between actors, and associates a dedicated Thread with the actor. +Making it bounded (by specifying a capacity) is optional, but if you do, you need to provide a pushTimeout (default is 10 seconds). When trying to send a message to the Actor it will throw a MessageQueueAppendFailedException("BlockingMessageTransferQueue transfer timed out") if the message cannot be added to the mailbox within the time specified by the pushTimeout. + +``_ +class MyActor extends UntypedActor { + public MyActor() { + getContext().setDispatcher(Dispatchers.newThreadBasedDispatcher(getContext(), mailboxCapacity, pushTimeout, pushTimeUnit)); + } + ... +} +``_ diff --git a/akka-docs/pending/dispatchers-scala.rst b/akka-docs/pending/dispatchers-scala.rst new file mode 100644 index 0000000000..76ca982c65 --- /dev/null +++ b/akka-docs/pending/dispatchers-scala.rst @@ -0,0 +1,214 @@ +Dispatchers (Scala) +=================== + +Module stability: **SOLID** + +The Dispatcher is an important piece that allows you to configure the right semantics and parameters for optimal performance, throughput and scalability. Different Actors have different needs. + +Akka supports dispatchers for both event-driven lightweight threads, allowing creation of millions threads on a single workstation, and thread-based Actors, where each dispatcher is bound to a dedicated OS thread. + +The event-based Actors currently consume ~600 bytes per Actor which means that you can create more than 6.5 million Actors on 4 G RAM. + +Default dispatcher +------------------ + +For most scenarios the default settings are the best. Here we have one single event-based dispatcher for all Actors created. The dispatcher used is this one: + +.. code-block:: scala + + Dispatchers.globalExecutorBasedEventDrivenDispatcher + +But if you feel that you are starting to contend on the single dispatcher (the 'Executor' and its queue) or want to group a specific set of Actors for a dedicated dispatcher for better flexibility and configurability then you can override the defaults and define your own dispatcher. See below for details on which ones are available and how they can be configured. + +Setting the dispatcher +---------------------- + +Normally you set the dispatcher from within the Actor itself. The dispatcher is defined by the 'dispatcher: MessageDispatcher' member field in 'ActorRef'. + +.. code-block:: scala + + class MyActor extends Actor { + self.dispatcher = ... // set the dispatcher + ... + } + +You can also set the dispatcher for an Actor **before** it has been started: + +.. code-block:: scala + + actorRef.dispatcher = dispatcher + +Types of dispatchers +-------------------- + +There are six different types of message dispatchers: + +* Thread-based +* Event-based +* Work-stealing +* HawtDispatch-based event-driven + +Factory methods for all of these, including global versions of some of them, are in the 'akka.dispatch.Dispatchers' object. + +Let's now walk through the different dispatchers in more detail. + +Event-based +^^^^^^^^^^^ + +The 'ExecutorBasedEventDrivenDispatcher' binds a set of Actors to a thread pool backed up by a 'BlockingQueue'. This dispatcher is highly configurable and supports a fluent configuration API to configure the 'BlockingQueue' (type of queue, max items etc.) as well as the thread pool. + +The event-driven dispatchers **must be shared** between multiple Actors. One best practice is to let each top-level Actor, e.g. the Actors you define in the declarative supervisor config, to get their own dispatcher but reuse the dispatcher for each new Actor that the top-level Actor creates. But you can also share dispatcher between multiple top-level Actors. This is very use-case specific and needs to be tried out on a case by case basis. The important thing is that Akka tries to provide you with the freedom you need to design and implement your system in the most efficient way in regards to performance, throughput and latency. + +It comes with many different predefined BlockingQueue configurations: +* Bounded LinkedBlockingQueue +* Unbounded LinkedBlockingQueue +* Bounded ArrayBlockingQueue +* Unbounded ArrayBlockingQueue +* SynchronousQueue + +You can also set the rejection policy that should be used, e.g. what should be done if the dispatcher (e.g. the Actor) can't keep up and the mailbox is growing up to the limit defined. You can choose between four different rejection policies: + +* java.util.concurrent.ThreadPoolExecutor.CallerRuns - will run the message processing in the caller's thread as a way to slow him down and balance producer/consumer +* java.util.concurrent.ThreadPoolExecutor.AbortPolicy - rejected messages by throwing a 'RejectedExecutionException' +* 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 `_. + +Here is an example: + +.. code-block:: scala + + class MyActor extends Actor { + self.dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher(name) + .withNewThreadPoolWithBoundedBlockingQueue(100) + .setCorePoolSize(16) + .setMaxPoolSize(128) + .setKeepAliveTimeInMillis(60000) + .setRejectionPolicy(new CallerRunsPolicy) + .build + ... + } + +This 'ExecutorBasedEventDrivenDispatcher' 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: + +.. code-block:: ruby + + actor { + throughput = 5 + } + +If you don't define a the 'throughput' option in the configuration file then the default value of '5' will be used. + +Browse the `ScalaDoc `_ or look at the code for all the options available. + +Work-stealing event-based +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The 'ExecutorBasedEventDrivenWorkStealingDispatcher' is a variation of the 'ExecutorBasedEventDrivenDispatcher' in which Actors of the same type can be set up to share this dispatcher and during execution time the different actors will steal messages from other actors if they have less messages to process. This can be a great way to improve throughput at the cost of a little higher latency. + +Normally the way you use it is to create an Actor companion object to hold the dispatcher and then set in in the Actor explicitly. + +.. code-block:: scala + + object MyActor { + val dispatcher = Dispatchers.newExecutorEventBasedWorkStealingDispatcher(name) + } + + class MyActor extends Actor { + self.dispatcher = MyActor.dispatcher + ... + } + +Here is an article with some more information: `Load Balancing Actors with Work Stealing Techniques `_ +Here is another article discussing this particular dispatcher: `Flexible load balancing with Akka in Scala `_ + +HawtDispatch-based event-driven +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The 'HawtDispatcher' uses the `HawtDispatch threading library `_ which is a Java clone of libdispatch. All actors with this type of dispatcher are executed on a single system wide fixed sized thread pool. The number of of threads will match the number of cores available on your system. The dispatcher delivers messages to the actors in the order that they were producer at the sender. + +A 'HawtDispatcher' instance can be shared by many actors. Normally the way you use it is to create an Actor companion object to hold the dispatcher and then set in in the Actor explicitly. + +.. code-block:: scala + + import akka.dispatch.HawtDispatcher + + object MyActor { + val dispatcher = new HawtDispatcher + } + + class MyActor extends Actor { + self.dispatcher = MyActor.dispatcher + ... + } + +Since a fixed thread pool is being used, an actor using a 'HawtDispatcher' is restricted to executing non blocking operations. For example, the actor is NOT alllowed to: +* synchronously call another actor +* call 3rd party libraries that can block +* use sockets that are in blocking mode + +HawtDispatch supports integrating non-blocking Socket IO events with your actors. Every thread in the HawtDispatch thread pool is parked in an IO event loop when it is not executing an actors. The IO events can be configured to be get delivered to the actor in either the reactor or proactor style. For an example, see `HawtDispacherEchoServer.scala `_. + +A `HawtDispatcher` will aggregate cross actor messages by default. This means that if Actor *A* is executing and sends actor *B* 10 messages, those messages will not be delivered to actor *B* until *A*'s execution ends. HawtDispatch will aggregate the 10 messages into 1 single enqueue operation on to actor *B*'s inbox. This an significantly reduce mailbox contention when actors are very chatty. If you want to avoid this aggregation behavior, then create the `HawtDispatcher` like this: + +.. code-block:: scala + + val dispatcher = new HawtDispatcher(false) + +The `HawtDispatcher` provides a companion object that lets you use more advanced HawtDispatch features. For example to pin an actor so that it always executed on the same thread in the thread poool you would: + +.. code-block:: scala + + val a: ActorRef = ... + HawtDispatcher.pin(a) + +If you have an Actor *b* which will be sending many messages to an Actor *a*, then you may want to consider setting *b*'s dispatch target to be *a*'s dispatch queue. When this is the case, messages sent from *b* to a will avoid cross thread mailbox contention. A side-effect of this is that the *a* and *b* actors will execute as if they shared a single mailbox. + +.. code-block:: scala + + val a: ActorRef = ... + val b: ActorRef = ... + HawtDispatcher.target(b, HawtDispatcher.queue(a)) + +Making the Actor mailbox bounded +-------------------------------- + +Global configuration +^^^^^^^^^^^^^^^^^^^^ + +You can make the Actor mailbox bounded by a capacity in two ways. Either you define it in the configuration file under 'default-dispatcher'. This will set it globally. + +.. code-block:: ruby + + actor { + default-dispatcher { + mailbox-capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) + # If positive then a bounded mailbox is used and the capacity is set to the number specificed + } + } + +Per-instance based configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can also do it on a specific dispatcher instance. + +For the 'ExecutorBasedEventDrivenDispatcher' and the 'ExecutorBasedWorkStealingDispatcher' you can do it through their constructor + +.. code-block:: scala + + class MyActor extends Actor { + self.dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher(name, throughput, mailboxCapacity) + ... + } + +For the 'ThreadBasedDispatcher', it is non-shareable between actors, and associates a dedicated Thread with the actor. +Making it bounded (by specifying a capacity) is optional, but if you do, you need to provide a pushTimeout (default is 10 seconds). When trying to send a message to the Actor it will throw a MessageQueueAppendFailedException("BlockingMessageTransferQueue transfer timed out") if the message cannot be added to the mailbox within the time specified by the pushTimeout. + +``_ +class MyActor extends Actor { + self.dispatcher = Dispatchers.newThreadBasedDispatcher(self, mailboxCapacity, pushTimeout, pushTimeoutUnit) + ... +} +``_ diff --git a/akka-docs/pending/event-handler.rst b/akka-docs/pending/event-handler.rst new file mode 100644 index 0000000000..18eefefb0a --- /dev/null +++ b/akka-docs/pending/event-handler.rst @@ -0,0 +1,96 @@ +Event Handler +============= + +There is an Event Handler which takes the place of a logging system in Akka: + +.. code-block:: scala + + akka.event.EventHandler + +You can configure which event handlers should be registered at boot time. That is done using the 'event-handlers' element in akka.conf. Here you can also define the log level. + +.. code-block:: ruby + + akka { + event-handlers = ["akka.event.EventHandler$DefaultListener"] # event handlers to register at boot time (EventHandler$DefaultListener logs to STDOUT) + event-handler-level = "DEBUG" # Options: ERROR, WARNING, INFO, DEBUG + } + +The default one logs to STDOUT and is registered by default. It is not intended to be used for production. There is also an SLF4J event handler available in the 'akka-slf4j.jar' module. Read more about it `here `_. + +Example of creating a listener from Scala (from Java you just have to create an 'UntypedActor' and create a handler for these messages): + +.. code-block:: scala + + val errorHandlerEventListener = Actor.actorOf(new Actor { + self.dispatcher = EventHandler.EventHandlerDispatcher + + def receive = { + case EventHandler.Error(cause, instance, message) => ... + case EventHandler.Warning(instance, message) => ... + case EventHandler.Info(instance, message) => ... + case EventHandler.Debug(instance, message) => ... + case genericEvent => ... + } + }) + +To add the listener: + +.. code-block:: scala + + EventHandler.addListener(errorHandlerEventListener) + +To remove the listener: + +.. code-block:: scala + + EventHandler.removeListener(errorHandlerEventListener) + +To log an event: + +.. code-block:: scala + + EventHandler.notify(EventHandler.Error(exception, this, message)) + + EventHandler.notify(EventHandler.Warning(this, message)) + + EventHandler.notify(EventHandler.Info(this, message)) + + EventHandler.notify(EventHandler.Debug(this, message)) + + EventHandler.notify(object) + +You can also use one of the direct methods (for a bit better performance): + +.. code-block:: scala + + EventHandler.error(exception, this, message) + + EventHandler.error(this, message) + + EventHandler.warning(this, message) + + EventHandler.info(this, message) + + EventHandler.debug(this, message) + +The event handler allows you to send an arbitrary object to the handler which you can handle in your event handler listener. The default listener prints it's toString String out to STDOUT. + +.. code-block:: scala + + EventHandler.notify(anyRef) + +The methods take a call-by-name parameter for the message to avoid object allocation and execution if level is disabled. The following formatting function will not be evaluated if level is INFO, WARNING, or ERROR. + +.. code-block:: scala + + EventHandler.debug(this, "Processing took %s ms".format(duration)) + +From Java you need to nest the call in an if statement to achieve the same thing. + +``_ +if (EventHandler.isDebugEnabled()) { + EventHandler.debug(this, String.format("Processing took %s ms", duration)); +} + +``_ diff --git a/akka-docs/pending/external-sample-projects.rst b/akka-docs/pending/external-sample-projects.rst new file mode 100644 index 0000000000..35a54c3c80 --- /dev/null +++ b/akka-docs/pending/external-sample-projects.rst @@ -0,0 +1,242 @@ +External Sample Projects +======================== + +Here are some external sample projects created by Akka's users. + +Camel in Action - Akka samples +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Akka samples for the upcoming Camel in Action book by Martin Krasser. +``_ + +CQRS impl using Scalaz and Akka +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +An implementation of CQRS using scalaz for functional domain models and Akka for event sourcing. +``_ + +Example of using Comet with Akka Mist +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Movie store +^^^^^^^^^^^ + +Code for a book on Scala/Akka. +Showcasing Remote Actors. +``_ + +Estimating Pi with Akka +^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Running Akka on Android +^^^^^^^^^^^^^^^^^^^^^^^ + +Sample showing Dining Philosophers running in UI on Android. +``_ +``_ + +Remote chat application using Java API +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Remote chat application using Java API +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A sample chat application using the Java API for Akka. +Port of the Scala API chat sample application in the Akka repository. +``_ + +Sample parallel computing with Akka and Scala API +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Akka, Facebook Graph API, WebGL sample +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Showcasing Akka Mist HTTP module +``_ + +Akka Mist Sample +^^^^^^^^^^^^^^^^ + +``_ + +Another Akka Mist Sample +^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Bank application +^^^^^^^^^^^^^^^^ + +Showcasing Transactors and STM. +``_ + +Ant simulation 1 +^^^^^^^^^^^^^^^^ + +Traveling salesman problem. Inspired by Clojure's Ant demo. Uses SPDE for GUI. Idiomatic Scala/Akka code. +Good example on how to use Actors and STM +``_ + +Ant simulation 2 +^^^^^^^^^^^^^^^^ + +Traveling salesman problem. Close to straight port by Clojure's Ant demo. Uses Swing for GUI. +Another nice example on how to use Actors and STM +``_ + +The santa clause STM example by SPJ using Akka +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Akka trading system +^^^^^^^^^^^^^^^^^^^ + +``_ + +Snowing version of Game of Life in Akka +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Akka Web (REST/Comet) template project +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A sbt-based, scala Akka project that sets up a web project with REST and comet support +``_ + +Various samples on how to use Akka +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +From the May Chciago-Area Scala Enthusiasts Meeting +``_ + +Absurd concept for a ticket sales & inventory system, using Akka framework +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Akka sports book sample: Java API +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Sample of using the Finite State Machine (FSM) DSL +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Akka REST, Jetty, SBT template project +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Great starting point for building an Akka application. +``_ + +Samples of various Akka features (in Scala) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ +Fork at ``_ + +A sample sbt setup for running the akka-sample-chat +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Akka Benchmark project +^^^^^^^^^^^^^^^^^^^^^^ + +Benches Akka against various other actors and concurrency tools +``_ + +Typed Actor (Java API) sample project +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Akka PI calculation sample project +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Akka Vaadin Ice sample +^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Port of Jersey (JAX-RS) samples to Akka +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Akka Expect Testing +^^^^^^^^^^^^^^^^^^^ + +``_ + +Akka Java API playground +^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Family web page build with Scala, Lift, Akka, Redis, and Facebook Connect +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +An example of queued computation tasks using Akka +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +The samples for the New York Scala Enthusiasts Meetup discussing Akka +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ +``_ + +Container managed thread pools for Akka Dispatchers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +"Lock" Finite State Machine demo with Akka +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Template w/ Intellij stuff for random akka playing around (with Bivvy) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Akka chat using Akka Java API by Mario Fusco +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Projects using the removed Akka Persistence modules +=================================================== + +Akka Terrastore sample +^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Akka Persistence for Force.com +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Template for Akka and Redis +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ diff --git a/akka-docs/pending/fault-tolerance-java.rst b/akka-docs/pending/fault-tolerance-java.rst new file mode 100644 index 0000000000..11e03c5aef --- /dev/null +++ b/akka-docs/pending/fault-tolerance-java.rst @@ -0,0 +1,466 @@ +Fault Tolerance Through Supervisor Hierarchies (Java) +===================================================== + +Module stability: **SOLID** + +The "let it crash" approach to fault/error handling, implemented by linking actors, is very different to what Java and most non-concurrency oriented languages/frameworks have adopted. It’s a way of dealing with failure that is designed for concurrent and distributed systems. + +Concurrency +^^^^^^^^^^^ + +Throwing an exception in concurrent code (let’s assume we are using non-linked actors), will just simply blow up the thread that currently executes the actor. + +# There is no way to find out that things went wrong (apart from inspecting the stack trace). +# There is nothing you can do about it. + +Here actors provide a clean way of getting notification of the error and do something about it. + +Linking actors also allow you to create sets of actors where you can be sure that either: +# All are dead +# None are dead + +This is very useful when you have thousands of concurrent actors. Some actors might have implicit dependencies and together implement a service, computation, user session etc. + +It encourages non-defensive programming. Don’t try to prevent things from go wrong, because they will, whether you want it or not. Instead; expect failure as a natural state in the life-cycle of your app, crash early and let someone else (that sees the whole picture), deal with it. + +Distributed actors +^^^^^^^^^^^^^^^^^^ + +You can’t build a fault-tolerant system with just one single box - you need at least two. Also, you (usually) need to know if one box is down and/or the service you are talking to on the other box is down. Here actor supervision/linking is a critical tool for not only monitoring the health of remote services, but to actually manage the service, do something about the problem if the actor or node is down. Such as restarting actors on the same node or on another node. + +In short, it is a very different way of thinking, but a way that is very useful (if not critical) to building fault-tolerant highly concurrent and distributed applications, which is as valid if you are writing applications for the JVM or the Erlang VM (the origin of the idea of "let-it-crash" and actor supervision). + +Supervision +=========== + +Supervisor hierarchies originate from `Erlang’s OTP framework `_. + +A supervisor is responsible for starting, stopping and monitoring its child processes. The basic idea of a supervisor is that it should keep its child processes alive by restarting them when necessary. This makes for a completely different view on how to write fault-tolerant servers. Instead of trying all things possible to prevent an error from happening, this approach embraces failure. It shifts the view to look at errors as something natural and something that **will** happen, instead of trying to prevent it; embraces it. Just ‘Let It Crash™’, since the components will be reset to a stable state and restarted upon failure. + +Akka has two different restart strategies; All-For-One and One-For-One. Best explained using some pictures (referenced from `erlang.org `_ ): + +OneForOne +^^^^^^^^^ + +The OneForOne fault handler will restart only the component that has crashed. +``_ + +^ + +AllForOne +^^^^^^^^^ + +The AllForOne fault handler will restart all the components that the supervisor is managing, including the one that have crashed. This strategy should be used when you have a certain set of components that are coupled in some way that if one is crashing they all need to be reset to a stable state before continuing. +``_ + +^ + +Restart callbacks +^^^^^^^^^^^^^^^^^ + +There are two different callbacks that an UntypedActor or TypedActor can hook in to: +* Pre restart +* Post restart + +These are called prior to and after the restart upon failure and can be used to clean up and reset/reinitialize state upon restart. This is important in order to reset the component failure and leave the component in a fresh and stable state before consuming further messages. + +Defining a supervisor's restart strategy +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Both the Typed Actor supervisor configuration and the Actor supervisor configuration take a ‘FaultHandlingStrategy’ instance which defines the fault management. The different strategies are: +* AllForOne +* OneForOne +These have the semantics outlined in the section above. + +Here is an example of how to define a restart strategy: + +.. code-block:: java + + new AllForOneStrategy( //Or OneForOneStrategy + new Class[]{ Exception.class }, //List of Exceptions/Throwables to handle + 3, // maximum number of restart retries + 5000 // within time in millis + ) + +Defining actor life-cycle +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The other common configuration element is the ‘LifeCycle’ which defines the life-cycle. The supervised actor can define one of two different life-cycle configurations: +* Permanent: which means that the actor will always be restarted. +* Temporary: which means that the actor will **not** be restarted, but it will be shut down through the regular shutdown process so the 'postStop' callback function will called. + +Here is an example of how to define the life-cycle: + +.. code-block:: java + + import static akka.config.Supervision.*; + + getContext().setLifeCycle(permanent()); //permanent() means that the component will always be restarted + getContext().setLifeCycle(temporary()); //temporary() means that the component will not be restarted, but rather shut down normally + +Supervising Untyped Actor +------------------------- + +Declarative supervisor configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Actor’s supervision can be declaratively defined by creating a ‘Supervisor’ factory object. Here is an example: + +.. code-block:: java + + import static akka.config.Supervision.*; + import static akka.actor.Actors.*; + + Supervisor supervisor = new Supervisor( + new SupervisorConfig( + new AllForOneStrategy(new Class[]{Exception.class}, 3, 5000), + new Supervise[] { + new Supervise( + actorOf(MyActor1.class), + permanent()), + Supervise( + actorOf(MyActor2.class), + permanent()) + })); + +Supervisors created like this are implicitly instantiated and started. + +You can link and unlink actors from a declaratively defined supervisor using the 'link' and 'unlink' methods: + +.. code-block:: java + + Supervisor supervisor = new Supervisor(...); + supervisor.link(..); + supervisor.unlink(..); + +You can also create declarative supervisors through the 'SupervisorFactory' factory object. Use this factory instead of the 'Supervisor' factory object if you want to control instantiation and starting of the Supervisor, if not then it is easier and better to use the 'Supervisor' factory object. + +Example usage: + +.. code-block:: java + + import static akka.config.Supervision.*; + import static akka.actor.Actors.*; + + SupervisorFactory factory = new SupervisorFactory( + new SupervisorConfig( + new OneForOneStrategy(new Class[]{Exception.class}, 3, 5000), + new Supervise[] { + new Supervise( + actorOf(MyActor1.class), + permanent()), + Supervise( + actorOf(MyActor2.class), + temporary()) + })); + +Then create a new instance our Supervisor and start it up explicitly. + +.. code-block:: java + + SupervisorFactory supervisor = factory.newInstance(); + supervisor.start(); // start up all managed servers + +Declaratively define actors as remote services +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can expose your actors as remote services by specifying the registerAsRemote to **true** in Supervise. + +Here is an example: + +.. code-block:: java + + import static akka.config.Supervision.*; + import static akka.actor.Actors.*; + + Supervisor supervisor = new Supervisor( + new SupervisorConfig( + new AllForOneStrategy(new Class[]{Exception.class}, 3, 5000), + new Supervise[] { + new Supervise( + actorOf(MyActor1.class), + permanent(), + true) + })); + +Programmatical linking and supervision of Untyped Actors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Untyped Actors can at runtime create, spawn, link and supervise other actors. Linking and unlinking is done using one of the 'link' and 'unlink' methods available in the 'ActorRef' (therefore prefixed with getContext() in these examples). + +Here is the API and how to use it from within an 'Actor': + +.. code-block:: java + + // link and unlink actors + getContext().link(actorRef); + getContext().unlink(actorRef); + + // starts and links Actors atomically + getContext().startLink(actorRef); + getContext().startLinkRemote(actorRef); + + // spawns (creates and starts) actors + getContext().spawn(MyActor.class); + getContext().spawnRemote(MyActor.class); + + // spawns and links Actors atomically + getContext().spawnLink(MyActor.class); + getContext().spawnLinkRemote(MyActor.class); + +A child actor can tell the supervising actor to unlink him by sending him the 'Unlink(this)' message. When the supervisor receives the message he will unlink and shut down the child. The supervisor for an actor is available in the 'supervisor: Option[Actor]' method in the 'ActorRef' class. Here is how it can be used. + +.. code-block:: java + + ActorRef supervisor = getContext().getSupervisor(); + if (supervisor != null) supervisor.sendOneWay(new Unlink(getContext())) + +The supervising actor's side of things +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If a linked Actor is failing and throws an exception then an ‘new Exit(deadActor, cause)’ message will be sent to the supervisor (however you should never try to catch this message in your own message handler, it is managed by the runtime). + +The supervising Actor also needs to define a fault handler that defines the restart strategy the Actor should accommodate when it traps an ‘Exit’ message. This is done by setting the ‘setFaultHandler’ method. + +The different options are: +* AllForOneStrategy(trapExit, maxNrOfRetries, withinTimeRange) +** trapExit is an Array of classes inheriting from Throwable, they signal which types of exceptions this actor will handle +* OneForOneStrategy(trapExit, maxNrOfRetries, withinTimeRange) +** trapExit is an Array of classes inheriting from Throwable, they signal which types of exceptions this actor will handle + +Here is an example: + +.. code-block:: java + + getContext().setFaultHandler(new AllForOneStrategy(new Class[]{MyException.class, IOException.class}, 3, 1000)); + +Putting all this together it can look something like this: + +.. code-block:: java + + class MySupervisor extends UntypedActor { + public MySupervisor() { + getContext().setFaultHandler(new AllForOneStrategy(new Class[]{MyException.class, IOException.class}, 3, 1000)); + } + + public void onReceive(Object message) throws Exception { + if (message instanceof Register) { + Register event = (Register)message; + UntypedActorRef actor = event.getActor(); + context.link(actor); + } else throw new IllegalArgumentException("Unknown message: " + message); + } + } + +You can also link an actor from outside the supervisor like this: + +.. code-block:: java + + UntypedActor supervisor = Actors.registry().actorsFor(MySupervisor.class])[0]; + supervisor.link(actorRef); + +The supervised actor's side of things +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The supervised actor needs to define a life-cycle. This is done by setting the lifeCycle field as follows: + +.. code-block:: java + + import static akka.config.Supervision.*; + + getContext().setLifeCycle(permanent()); // Permanent or Temporary + +Default is 'Permanent' so if you don't set the life-cycle then that is what you get. + +In the supervised Actor you can override the ‘preRestart’ and ‘postRestart’ callback methods to add hooks into the restart process. These methods take the reason for the failure, e.g. the exception that caused termination and restart of the actor as argument. It is in these methods that **you** have to add code to do cleanup before termination and initialization after restart. Here is an example: + +.. code-block:: java + + class FaultTolerantService extends UntypedActor { + + @Override + public void preRestart(Throwable reason) { + ... // clean up before restart + } + + @Override + public void postRestart(Throwable reason) { + ... // reinit stable state after restart + } + } + +Reply to initial senders +^^^^^^^^^^^^^^^^^^^^^^^^ + +Supervised actors have the option to reply to the initial sender within preRestart, postRestart and postStop. A reply within these methods is possible after receive has thrown an exception. When receive returns normally it is expected that any necessary reply has already been done within receive. Here's an example. + +.. code-block:: java + + public class FaultTolerantService extends UntypedActor { + public void onReceive(Object msg) { + // do something that may throw an exception + // ... + + getContext().replySafe("ok"); + } + + @Override + public void preRestart(Throwable reason) { + getContext().replySafe(reason.getMessage()); + } + + @Override + public void postStop() { + getContext().replySafe("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). + +Handling too many actor restarts within a specific time limit +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you remember, when you define the 'RestartStrategy' you also defined maximum number of restart retries within time in millis. + +.. code-block:: java + + new AllForOneStrategy( // FaultHandlingStrategy policy (AllForOneStrategy or OneForOneStrategy) + new Class[]{MyException.class, IOException.class}, //What types of errors will be handled + 3, // maximum number of restart retries + 5000 // within time in millis + ); + +Now, what happens if this limit is reached? + +What will happen is that the failing actor will send a system message to its supervisor called 'MaximumNumberOfRestartsWithinTimeRangeReached' with the following these properties: +* victim: ActorRef +* maxNrOfRetries: int +* withinTimeRange: int +* lastExceptionCausingRestart: Throwable + +If you want to be able to take action upon this event (highly recommended) then you have to create a message handle for it in the supervisor. + +Here is an example: + +.. code-block:: java + + public class SampleUntypedActorSupervisor extends UntypedActor { + ... + + public void onReceive(Object message) throws Exception { + if (message instanceof MaximumNumberOfRestartsWithinTimeRangeReached) { + MaximumNumberOfRestartsWithinTimeRangeReached event = (MaximumNumberOfRestartsWithinTimeRangeReached)message; + ... = event.getVictim(); + ... = event.getMaxNrOfRetries(); + ... = event.getWithinTimeRange(); + ... = event.getLastExceptionCausingRestart(); + } else throw new IllegalArgumentException("Unknown message: " + message); + } + } + +You will also get this log warning similar to this: + +.. code-block:: console + + WAR [20100715-14:05:25.821] actor: Maximum number of restarts [5] within time range [5000] reached. + WAR [20100715-14:05:25.821] actor: Will *not* restart actor [Actor[akka.actor.SupervisorHierarchySpec$CountDownActor:1279195525812]] anymore. + WAR [20100715-14:05:25.821] actor: Last exception causing restart was [akka.actor.SupervisorHierarchySpec$FireWorkerException: Fire the worker!]. + +If you don't define a message handler for this message then you don't get an error but the message is simply not sent to the supervisor. Instead you will get a log warning. + +- + +Supervising Typed Actors +------------------------ + +Declarative supervisor configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To configure Typed Actors for supervision you have to consult the ‘TypedActorConfigurator’ and its ‘configure’ method. This method takes a ‘RestartStrategy’ and an array of ‘Component’ definitions defining the Typed Actors and their ‘LifeCycle’. Finally you call the ‘supervise’ method to start everything up. The Java configuration elements reside in the ‘akka.config.JavaConfig’ class and need to be imported statically. + +Here is an example: + +.. code-block:: java + + import static akka.config.Supervision.*; + import static akka.config.SupervisorConfig.*; + + TypedActorConfigurator manager = new TypedActorConfigurator(); + + manager.configure( + new AllForOneStrategy(new Class[]{Exception.class}, 3, 1000), + new SuperviseTypedActor[] { + new SuperviseTypedActor( + Foo.class, + FooImpl.class, + temporary(), + 1000), + new SuperviseTypedActor( + Bar.class, + BarImpl.class, + permanent(), + 1000) + }).supervise(); + +Then you can retrieve the Typed Actor as follows: + +.. code-block:: java + + Foo foo = (Foo) manager.getInstance(Foo.class); + +^ + +Restart callbacks +^^^^^^^^^^^^^^^^^ + +In the supervised TypedActor you can override the ‘preRestart’ and ‘postRestart’ callback methods to add hooks into the restart process. These methods take the reason for the failure, e.g. the exception that caused termination and restart of the actor as argument. It is in these methods that **you** have to add code to do cleanup before termination and initialization after restart. Here is an example: + +.. code-block:: java + + class FaultTolerantService extends TypedActor { + + @Override + public void preRestart(Throwable reason) { + ... // clean up before restart + } + + @Override + public void postRestart(Throwable reason) { + ... // reinit stable state after restart + } + } + +Programatical linking and supervision of TypedActors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +TypedActors can be linked an unlinked just like UntypedActors: + +.. code-block:: java + + TypedActor.link(supervisor, supervised); + + TypedActor.unlink(supervisor, supervised); + +If the parent TypedActor (supervisor) wants to be able to do handle failing child TypedActors, e.g. be able restart the linked TypedActor according to a given fault handling scheme then it has to set its ‘trapExit’ flag to an array of Exceptions that it wants to be able to trap: + +.. code-block:: java + + TypedActor.faultHandler(supervisor, new AllForOneStrategy(new Class[]{IOException.class}, 3, 2000)); + +For convenience there is an overloaded link that takes trapExit and faultHandler for the supervisor as arguments. Here is an example: + +``_ +import static akka.actor.TypedActor.*; +import static akka.config.Supervision.*; + +foo = newInstance(Foo.class, FooImpl.class, 1000); +bar = newInstance(Bar.class, BarImpl.class, 1000); + +link(foo, bar, new AllForOneStrategy(new Class[]{IOException.class}, 3, 2000)); + +// alternative: chaining +bar = faultHandler(foo, new AllForOneStrategy(new Class[]{IOException.class}, 3, 2000)).newInstance(Bar.class, 1000); + +link(foo, bar); +``_ diff --git a/akka-docs/pending/fault-tolerance-scala.rst b/akka-docs/pending/fault-tolerance-scala.rst new file mode 100644 index 0000000000..790bf984d5 --- /dev/null +++ b/akka-docs/pending/fault-tolerance-scala.rst @@ -0,0 +1,423 @@ +Fault Tolerance Through Supervisor Hierarchies (Scala) +====================================================== + +Module stability: **SOLID** + +The "let it crash" approach to fault/error handling, implemented by linking actors, is very different to what Java and most non-concurrency oriented languages/frameworks have adopted. It's a way of dealing with failure that is designed for concurrent and distributed systems. + +Concurrency +^^^^^^^^^^^ + +Throwing an exception in concurrent code (let's assume we are using non-linked actors), will just simply blow up the thread that currently executes the actor. + +# There is no way to find out that things went wrong (apart from inspecting the stack trace). +# There is nothing you can do about it. + +Here actors provide a clean way of getting notification of the error and do something about it. + +Linking actors also allow you to create sets of actors where you can be sure that either: +# All are dead +# None are dead + +This is very useful when you have thousands of concurrent actors. Some actors might have implicit dependencies and together implement a service, computation, user session etc. + +It encourages non-defensive programming. Don't try to prevent things from go wrong, because they will, whether you want it or not. Instead; expect failure as a natural state in the life-cycle of your app, crash early and let someone else (that sees the whole picture), deal with it. + +Distributed actors +^^^^^^^^^^^^^^^^^^ + +You can't build a fault-tolerant system with just one single box - you need at least two. Also, you (usually) need to know if one box is down and/or the service you are talking to on the other box is down. Here actor supervision/linking is a critical tool for not only monitoring the health of remote services, but to actually manage the service, do something about the problem if the actor or node is down. Such as restarting actors on the same node or on another node. + +In short, it is a very different way of thinking, but a way that is very useful (if not critical) to building fault-tolerant highly concurrent and distributed applications, which is as valid if you are writing applications for the JVM or the Erlang VM (the origin of the idea of "let-it-crash" and actor supervision). + +Supervision +=========== + +Supervisor hierarchies originate from `Erlang's OTP framework `_. + +A supervisor is responsible for starting, stopping and monitoring its child processes. The basic idea of a supervisor is that it should keep its child processes alive by restarting them when necessary. This makes for a completely different view on how to write fault-tolerant servers. Instead of trying all things possible to prevent an error from happening, this approach embraces failure. It shifts the view to look at errors as something natural and something that **will** happen, instead of trying to prevent it; embraces it. Just "Let It Crash", since the components will be reset to a stable state and restarted upon failure. + +Akka has two different restart strategies; All-For-One and One-For-One. Best explained using some pictures (referenced from `erlang.org `_ ): + +OneForOne +^^^^^^^^^ + +The OneForOne fault handler will restart only the component that has crashed. +``_ + +^ + +AllForOne +^^^^^^^^^ + +The AllForOne fault handler will restart all the components that the supervisor is managing, including the one that have crashed. This strategy should be used when you have a certain set of components that are coupled in some way that if one is crashing they all need to be reset to a stable state before continuing. +``_ + +^ + +Restart callbacks +^^^^^^^^^^^^^^^^^ + +There are two different callbacks that the Typed Actor and Actor can hook in to: +* Pre restart +* Post restart + +These are called prior to and after the restart upon failure and can be used to clean up and reset/reinitialize state upon restart. This is important in order to reset the component failure and leave the component in a fresh and stable state before consuming further messages. + +Defining a supervisor's restart strategy +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Both the Typed Actor supervisor configuration and the Actor supervisor configuration take a 'FaultHandlingStrategy' instance which defines the fault management. The different strategies are: +* AllForOne +* OneForOne +These have the semantics outlined in the section above. + +Here is an example of how to define a restart strategy: + +.. code-block:: scala + + AllForOneStrategy( //FaultHandlingStrategy; AllForOneStrategy or OneForOneStrategy + List(classOf[Exception]), //What exceptions will be handled + 3, // maximum number of restart retries + 5000 // within time in millis + ) + +Defining actor life-cycle +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The other common configuration element is the "LifeCycle' which defines the life-cycle. The supervised actor can define one of two different life-cycle configurations: +* Permanent: which means that the actor will always be restarted. +* Temporary: which means that the actor will **not** be restarted, but it will be shut down through the regular shutdown process so the 'postStop' callback function will called. + +Here is an example of how to define the life-cycle: + +.. code-block:: scala + + Permanent // means that the component will always be restarted + Temporary // means that it will not be restarted, but it will be shut + // down through the regular shutdown process so the 'postStop' hook will called + +Supervising Actors +------------------ + +Declarative supervisor configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Actor's supervision can be declaratively defined by creating a "Supervisor' factory object. Here is an example: + +.. code-block:: scala + + val supervisor = Supervisor( + SupervisorConfig( + AllForOneStrategy(List(classOf[Exception]), 3, 1000), + Supervise( + actorOf[MyActor1], + Permanent) :: + Supervise( + actorOf[MyActor2], + Permanent) :: + Nil)) + +Supervisors created like this are implicitly instantiated and started. + +You can link and unlink actors from a declaratively defined supervisor using the 'link' and 'unlink' methods: + +.. code-block:: scala + + val supervisor = Supervisor(...) + supervisor.link(..) + supervisor.unlink(..) + +You can also create declarative supervisors through the 'SupervisorFactory' factory object. Use this factory instead of the 'Supervisor' factory object if you want to control instantiation and starting of the Supervisor, if not then it is easier and better to use the 'Supervisor' factory object. + +Example usage: + +.. code-block:: scala + + val factory = SupervisorFactory( + SupervisorConfig( + OneForOneStrategy(List(classOf[Exception]), 3, 10), + Supervise( + myFirstActor, + Permanent) :: + Supervise( + mySecondActor, + Permanent) :: + Nil)) + +Then create a new instance our Supervisor and start it up explicitly. + +.. code-block:: scala + + val supervisor = factory.newInstance + supervisor.start // start up all managed servers + +Declaratively define actors as remote services +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can declaratively define an actor to be available as a remote actor by specifying **true** for registerAsRemoteService. + +Here is an example: + +.. code-block:: scala + + val supervisor = Supervisor( + SupervisorConfig( + AllForOneStrategy(List(classOf[Exception]), 3, 1000), + Supervise( + actorOf[MyActor1], + Permanent, + **true**) + :: Nil)) + +Programmatical linking and supervision of Actors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Actors can at runtime create, spawn, link and supervise other actors. Linking and unlinking is done using one of the 'link' and 'unlink' methods available in the 'ActorRef' (therefore prefixed with 'self' in these examples). + +Here is the API and how to use it from within an 'Actor': + +.. code-block:: scala + + // link and unlink actors + self.link(actorRef) + self.unlink(actorRef) + + // starts and links Actors atomically + self.startLink(actorRef) + + // spawns (creates and starts) actors + self.spawn[MyActor] + self.spawnRemote[MyActor] + + // spawns and links Actors atomically + self.spawnLink[MyActor] + self.spawnLinkRemote[MyActor] + +A child actor can tell the supervising actor to unlink him by sending him the 'Unlink(this)' message. When the supervisor receives the message he will unlink and shut down the child. The supervisor for an actor is available in the 'supervisor: Option[Actor]' method in the 'ActorRef' class. Here is how it can be used. + +.. code-block:: scala + + if (supervisor.isDefined) supervisor.get ! Unlink(this) + + // Or shorter using 'foreach': + + supervisor.foreach(_ ! Unlink(this)) + +The supervising actor's side of things +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If a linked Actor is failing and throws an exception then an "Exit(deadActor, cause)' message will be sent to the supervisor (however you should never try to catch this message in your own message handler, it is managed by the runtime). + +The supervising Actor also needs to define a fault handler that defines the restart strategy the Actor should accommodate when it traps an "Exit' message. This is done by setting the "faultHandler' field. + +.. code-block:: scala + + protected var faultHandler: FaultHandlingStrategy + +The different options are: +* AllForOneStrategy(trapExit, maxNrOfRetries, withinTimeRange) +** trapExit is a List or Array of classes inheriting from Throwable, they signal which types of exceptions this actor will handle +* OneForOneStrategy(trapExit, maxNrOfRetries, withinTimeRange) +** trapExit is a List or Array of classes inheriting from Throwable, they signal which types of exceptions this actor will handle + +Here is an example: + +.. code-block:: scala + + self.faultHandler = AllForOneStrategy(List(classOf[Throwable]), 3, 1000) + +Putting all this together it can look something like this: + +.. code-block:: scala + + class MySupervisor extends Actor { + self.faultHandler = OneForOneStrategy(List(classOf[Throwable]), 5, 5000) + + def receive = { + case Register(actor) => + self.link(actor) + } + } + +You can also link an actor from outside the supervisor like this: + +.. code-block:: scala + + val supervisor = Actor.registry.actorsFor(classOf[MySupervisor]).head + supervisor.link(actor) + +The supervised actor's side of things +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The supervised actor needs to define a life-cycle. This is done by setting the lifeCycle field as follows: + +.. code-block:: scala + + self.lifeCycle = Permanent // Permanent or Temporary or UndefinedLifeCycle + +In the supervised Actor you can override the "preRestart' and "postRestart' callback methods to add hooks into the restart process. These methods take the reason for the failure, e.g. the exception that caused termination and restart of the actor as argument. It is in these methods that **you** have to add code to do cleanup before termination and initialization after restart. Here is an example: + +.. code-block:: scala + + class FaultTolerantService extends Actor { + override def preRestart(reason: Throwable) { + ... // clean up before restart + } + + override def postRestart(reason: Throwable) { + ... // reinit stable state after restart + } + } + +Reply to initial senders +^^^^^^^^^^^^^^^^^^^^^^^^ + +Supervised actors have the option to reply to the initial sender within preRestart, postRestart and postStop. A reply within these methods is possible after receive has thrown an exception. When receive returns normally it is expected that any necessary reply has already been done within receive. Here's an example. + +.. code-block:: scala + + class FaultTolerantService extends Actor { + def receive = { + case msg => { + // do something that may throw an exception + // ... + + self.reply("ok") + } + } + + override def preRestart(reason: scala.Throwable) { + self.reply_?(reason.getMessage) + } + + override def postStop { + self.reply_?("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). + +Handling too many actor restarts within a specific time limit +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you remember, when you define the 'RestartStrategy' you also defined maximum number of restart retries within time in millis. + +.. code-block:: scala + + AllForOneStrategy( //Restart policy, AllForOneStrategy or OneForOneStrategy + List(classOf[Exception]), //What kinds of exception it will handle + 3, // maximum number of restart retries + 5000 // within time in millis + ) + +Now, what happens if this limit is reached? + +What will happen is that the failing actor will send a system message to its supervisor called 'MaximumNumberOfRestartsWithinTimeRangeReached' with the following signature: + +.. code-block:: scala + + case class MaximumNumberOfRestartsWithinTimeRangeReached( + victim: ActorRef, maxNrOfRetries: Int, withinTimeRange: Int, lastExceptionCausingRestart: Throwable) + +If you want to be able to take action upon this event (highly recommended) then you have to create a message handle for it in the supervisor. + +Here is an example: + +.. code-block:: scala + + val supervisor = actorOf(new Actor{ + self.faultHandler = OneForOneStrategy(List(classOf[Throwable]), 5, 5000) + protected def receive = { + case MaximumNumberOfRestartsWithinTimeRangeReached( + victimActorRef, maxNrOfRetries, withinTimeRange, lastExceptionCausingRestart) => + ... // handle the error situation + } + }).start + +You will also get this log warning similar to this: + +.. code-block:: console + + WAR [20100715-14:05:25.821] actor: Maximum number of restarts [5] within time range [5000] reached. + WAR [20100715-14:05:25.821] actor: Will *not* restart actor [Actor[akka.actor.SupervisorHierarchySpec$CountDownActor:1279195525812]] anymore. + WAR [20100715-14:05:25.821] actor: Last exception causing restart was [akka.actor.SupervisorHierarchySpec$FireWorkerException: Fire the worker!]. + +If you don't define a message handler for this message then you don't get an error but the message is simply not sent to the supervisor. Instead you will get a log warning. + +- + +Supervising Typed Actors +------------------------ + +Declarative supervisor configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To configure Typed Actors for supervision you have to consult the "TypedActorConfigurator' and its "configure' method. This method takes a "RestartStrategy' and an array of "Component' definitions defining the Typed Actors and their "LifeCycle'. Finally you call the "supervise' method to start everything up. The configuration elements reside in the "akka.config.JavaConfig' class and need to be imported statically. + +Here is an example: + +.. code-block:: scala + + import akka.config.Supervision._ + + val manager = new TypedActorConfigurator + + manager.configure( + AllForOneStrategy(List(classOf[Exception]), 3, 1000), + List( + SuperviseTypedActor( + Foo.class, + FooImpl.class, + Permanent, + 1000), + new SuperviseTypedActor( + Bar.class, + BarImpl.class, + Permanent, + 1000) + )).supervise + +Then you can retrieve the Typed Actor as follows: + +.. code-block:: java + + Foo foo = manager.getInstance(classOf[Foo]) + +Restart callbacks +^^^^^^^^^^^^^^^^^ + +Programatical linking and supervision of TypedActors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +TypedActors can be linked an unlinked just like actors - in fact the linking is done on the underlying actor: + +.. code-block:: scala + + TypedActor.link(supervisor, supervised) + + TypedActor.unlink(supervisor, supervised) + +If the parent TypedActor (supervisor) wants to be able to do handle failing child TypedActors, e.g. be able restart the linked TypedActor according to a given fault handling scheme then it has to set its 'trapExit' flag to an array of Exceptions that it wants to be able to trap: + +.. code-block:: scala + + TypedActor.faultHandler(supervisor, AllForOneStrategy(Array(classOf[IOException]), 3, 2000)) + +For convenience there is an overloaded link that takes trapExit and faultHandler for the supervisor as arguments. Here is an example: + +``_ +import akka.actor.TypedActor._ + +val foo = newInstance(classOf[Foo], 1000) +val bar = newInstance(classOf[Bar], 1000) + +link(foo, bar, new AllForOneStrategy(Array(classOf[IOException]), 3, 2000)) + +// alternative: chaining +bar = faultHandler(foo, new AllForOneStrategy(Array(classOf[IOException]), 3, 2000)) + .newInstance(Bar.class, 1000) + +link(foo, bar +``_ diff --git a/akka-docs/pending/fsm-scala.rst b/akka-docs/pending/fsm-scala.rst new file mode 100644 index 0000000000..d9f3690b24 --- /dev/null +++ b/akka-docs/pending/fsm-scala.rst @@ -0,0 +1,218 @@ +FSM +=== + +Module stability: **STABLE** + +The FSM (Finite State Machine) is available as a mixin for the akka Actor and is best described in the `Erlang design principals <@http://www.erlang.org/documentation/doc-4.8.2/doc/design_principles/fsm.html>`_ + +A FSM can be described as a set of relations of the form: +> **State(S) x Event(E) -> Actions (A), State(S')** + +These relations are interpreted as meaning: +> *If we are in state S and the event E occurs, we should perform the actions A and make a transition to the state S'.* + +State Definitions +----------------- + +To demonstrate the usage of states we start with a simple state only FSM without state data. The state can be of any type so for this example we create the states A, B and C. + +.. code-block:: scala + + sealed trait ExampleState + case object A extends ExampleState + case object B extends ExampleState + case object C extends ExampleState + +Now lets create an object to influence the FSM and define the states and their behaviour. + +.. code-block:: scala + + import akka.actor.{Actor, FSM} + import FSM._ + import akka.util.duration._ + + case object Move + + class ABC extends Actor with FSM[ExampleState,Unit] { + + startWith(A, Unit) + + when(A) { + case Event(Move, _) => + log.info("Go to B and move on after 5 seconds") + goto(B) forMax (5 seconds) + } + + when(B) { + case Event(StateTimeout, _) => + log.info("Moving to C") + goto(C) + } + + when(C) { + case Event(Move, _) => + log.info("Stopping") + stop + } + + initialize // this checks validity of the initial state and sets up timeout if needed + } + +So we use 'when' to specify a state and define what needs to happen when we receive an event. We use 'goto' to go to another state. We use 'forMax' to tell for how long we maximum want to stay in that state before we receive a timeout notification. We use 'stop' to stop the FSM. And we use 'startWith' to specify which state to start with. The call to 'initialize' should be the last action done in the actor constructor. + +If we want to stay in the current state we can use (I'm hoping you can guess this by now) 'stay'. That can also be combined with the 'forMax' + +.. code-block:: scala + + when(C) { + case Event(unknown, _) => + stay forMax (2 seconds) + } + +The timeout can also be associated with the state itself, the choice depends on whether most of the transitions to the state require the same value for the timeout: + +.. code-block:: scala + + when(A) { + case Ev(Start(msg)) => // convenience extractor when state data not needed + goto(Timer) using msg + } + + when(B, stateTimeout = 12 seconds) { + case Event(StateTimeout, msg) => + target ! msg + case Ev(DifferentPause(dur : Duration)) => + stay forMax dur // overrides default state timeout for this single transition + } + +Unhandled Events +---------------- + +If a state doesn't handle a received event a warning is logged. If you want to do something with this events you can specify that with 'whenUnhandled' + +.. code-block:: scala + + whenUnhandled { + case Event(x, _) => log.info("Received unhandled event: " + x) + } + +Termination +----------- + +You can use 'onTermination' to specify custom code that is executed when the FSM is stopped. A reason is passed to tell how the FSM was stopped. + +.. code-block:: scala + + onTermination { + case Normal => log.info("Stopped normal") + case Shutdown => log.info("Stopped because of shutdown") + case Failure(cause) => log.error("Stopped because of failure: " + cause) + } + +State Transitions +----------------- + +When state transitions to another state we might want to know about this and take action. To specify this we can use 'onTransition' to capture the transitions. + +.. code-block:: scala + + onTransition { + case A -> B => log.info("Moving from A to B") + case _ -> C => log.info("Moving from something to C") + } + +Multiple onTransition blocks may be given and all will be execution while processing a transition. This enables you to associate your Actions either with the initial state of a processing step, or with the transition into the final state of a processing step. + +Transitions occur "between states" conceptually, which means after any actions you have put into the event handling block; this is obvious since the next state is only defined by the value returned by the event handling logic. You do not need to worry about the exact order with respect to setting the internal state variable, as everything within the FSM actor is running single-threaded anyway. + +It is also possible to pass a function object accepting two states to onTransition, in case your state handling logic is implemented as a method: + +.. code-block:: scala + + onTransition(handler _) + + private def handler(from: State, to: State) { + ... + } + +State Data +---------- + +The FSM can also hold state data that is attached to every event. The state data can be of any type but to demonstrate let's look at a lock with a String as state data holding the entered unlock code. +First we need two states for the lock: + +.. code-block:: scala + + sealed trait LockState + case object Locked extends LockState + case object Open extends LockState + +Now we can create a lock FSM that takes LockState as a state and a String as state data: + +.. code-block:: scala + + import akka.actor.{FSM, Actor} + import FSM._ + import akka.util.duration._ + + class Lock(code: String) extends Actor with FSM[LockState, String] { + + val emptyCode = "" + + when(Locked) { + // receive a digit and the code that we have so far + case Event(digit: Char, soFar) => { + // add the digit to what we have + soFar + digit match { + // not enough digits yet so stay using the incomplete code as the new state data + case incomplete if incomplete.length < code.length => + stay using incomplete + // code matched the one from the lock so go to Open state and reset the state data + case `code` => + log.info("Unlocked") + goto(Open) using emptyCode forMax (1 seconds) + // wrong code, stay Locked and reset the state data + case wrong => + log.error("Wrong code " + wrong) + stay using emptyCode + } + } + } + + when(Open) { + // after the timeout, go back to Locked state + case Event(StateTimeout, _) => { + log.info("Locked") + goto(Locked) + } + } + + startWith(Locked, emptyCode) + } + +To use the Lock you can run a small program like this: + +.. code-block:: scala + + object Lock { + + def main(args: Array[String]) { + + val lock = Actor.actorOf(new Lock("1234")).start + + lock ! '1' + lock ! '2' + lock ! '3' + lock ! '4' + + Actor.registry.shutdownAll + exit + } + } + +Dining Hakkers +-------------- + +A bigger FSM example can be found in the sources. +`Dining Hakkers using FSM <@https://github.com/jboner/akka/blob/master/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala#L1>`_ +`Dining Hakkers using become <@https://github.com/jboner/akka/blob/master/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala#L1>`_ diff --git a/akka-docs/pending/futures-scala.rst b/akka-docs/pending/futures-scala.rst new file mode 100644 index 0000000000..040ff59884 --- /dev/null +++ b/akka-docs/pending/futures-scala.rst @@ -0,0 +1,197 @@ +Futures (Scala) +=============== + +Introduction +------------ + +In Akka, a `Future `_ is a data structure used to retrieve the result of some concurrent operation. This operation is usually performed by an `Actor `_ or by the Dispatcher `directly `_. This result can be accessed synchronously (blocking) or asynchronously (non-blocking). + +Use with Actors +--------------- + +There are generally two ways of getting a reply from an Actor: the first is by a sent message (`actor ! msg `_), which only works if the original sender was an Actor) and the second is through a Future. + +Using an Actor's '!!!' method to send a message will return a Future. To wait for and retreive the actual result the simplest method is: + +.. code-block:: scala + + val future = actor !!! msg + val result: Any = future.apply + // or more simply + val result: Any = future() + +This will cause the current thread to block and wait for the Actor to 'complete' the Future with it's reply. Due to the dynamic nature of Akka's Actors this result will be untyped and will default to 'Nothing'. The safest way to deal with this is to cast the result to an Any as is shown in the above example. You can also use the expected result type instead of Any, but if an unexpected type were to be returned you will get a ClassCastException. For more elegant ways to deal with this and to use the result without blocking refer to `Functional Futures `_. + +Use Directly +------------ + +A common use case within Akka is to have some computation performed concurrently without needing the extra utility of an Actor. If you find yourself creating a pool of Actors for the sole reason of performing a calculation in parallel, there is an easier (and faster) way: + +.. code-block:: scala + + import akka.dispatch.Future + + val future = Future { + "Hello" + "World" + } + val result = future() + +In the above code the block passed to Future will be executed by the default `Dispatcher `_, with the return value of the block used to complete the Future (in this case, the result would be the string: "HelloWorld"). Unlike a Future that is returned from an Actor, this Future is properly typed, and we also avoid the overhead of managing an Actor. + +Functional Futures +------------------ + +A recent addition to Akka's Future is several monadic methods that are very similar to the ones used by Scala's collections. These allow you to create 'pipelines' or 'streams' that the result will travel through. + +Future is a Monad +^^^^^^^^^^^^^^^^^ + +The first method for working with Future functionally is 'map'. This method takes a Function which performs some operation on the result of the Future, and returning a new result. The return value of the 'map' method is another Future that will contain the new result: + +.. code-block:: scala + + val f1 = Future { + "Hello" + "World" + } + + val f2 = f1 map { x => + x.length + } + + val result = f2() + +In this example we are joining two strings together within a Future. Instead of waiting for this to complete, we apply our Function that calculates the length of the string using the 'map' method. Now we have a second Future that will contain an Int. When our original Future completes, it will also apply our Function and complete the second Future with that result. When we finally await the result, it will contain the number 10. Our original Future still contains the string "HelloWorld" and is unaffected by the 'map'. + +Something to note when using these methods: if the Future is still being processed when one of these methods are called, it will be the completing thread that actually does the work. If the Future is already complete though, it will be run in our current thread. For example: + +.. code-block:: scala + + val f1 = Future { + Thread.sleep(1000) + "Hello" + "World" + } + + val f2 = f1 map { x => + x.length + } + + val result = fs() + +The original Future will take at least 1 second to execute due to sleep, which means it is still being processed at the time we call 'map'. The Function we provide gets stored within the Future and later executed by the dispatcher when the result is ready. + +If we do the opposite: + +.. code-block:: scala + + val f1 = Future { + "Hello" + "World" + } + + Thread.sleep(1000) + + val f2 = f1 map { x => + x.length + } + + val result = fs() + +Our little string has been processed long before our 1 second sleep has finished. Because of this, the dispatcher has moved onto other messages that need processing and can no longer calculate the length of the string for us, instead it gets calculated in the current thread just as if we weren't using a Future. + +Normally this works quite well for us as it means there is very little overhead to running a quick Function. If there is a possiblity of the Function taking a non-trivial amount of time to process it might be better to have this done concurrently, and for that we use 'flatMap': + +.. code-block:: scala + + val f1 = Future { + "Hello" + "World" + } + + val f2 = f1 flatMap {x => + Future(x.length) + } + + val result = fs() + +Now our second Future is executed concurrently as well. This technique can also be used to combine the results of several Futures into a single calculation, which will be better explained in the following sections. + +For Comprehensions +^^^^^^^^^^^^^^^^^^ + +Since Future has a 'map' and 'flatMap' method it can be easily used in a for comprehension: + +.. code-block:: scala + + val f = for { + a <- Future(10 / 2) // 10 / 2 = 5 + b <- Future(a + 1) // 5 + 1 = 6 + c <- Future(a - 1) // 5 - 1 = 4 + } yield b * c // 6 * 4 = 24 + + val result = f() + +Something to keep in mind when doing this is even though it looks like parts of the above example can run in parallel, each step of the for comprehension is run sequentially. This will happen on separate threads for each step but there isn't much benefit over running the calculations all within a single Future. The real benefit comes when the Futures are created first, and then combining them together. + +Composing Futures +^^^^^^^^^^^^^^^^^ + +The example for comprehension above is an example of composing Futures. A common use case for this is combining the replies of several Actors into a single calculation without resorting to calling 'await' to block for each result. For example: + +.. code-block:: scala + + val f1 = actor1 !!! msg1 + val f2 = actor2 !!! msg2 + + val f3 = for { + a: Int <- f1 + b: Int <- f2 + c: String <- actor3 !!! (a + b) + } yield c + + val result = f3() + +Here we have 2 actors processing a single message each. In the for comprehension we need to add the expected types in order to work with the results. Once the 2 results are available, they are being added together and sent to a third actor, which replies with a String, which we assign to 'result'. + +This is fine when dealing with a known amount of Actors, but can grow unwieldly if we have more then a handful. The 'sequence' and 'traverse' helper methods can make it easier to handle more complex use cases. Both of these methods are ways of turning a Traversable[Future[A]] into a Future[Traversable[A]]. For example: + +.. code-block:: scala + + // oddActor returns odd numbers sequentially from 1 + val listOfFutures: List[Future[Int]] = List.fill(100)(oddActor !!! GetNext) + + // now we have a Future[List[Int]] + val futureList = Futures.sequence(listOfFutures) + + // Find the sum of the odd numbers + val oddSum = futureList.map(_.sum).apply + +To better explain what happened in the example, Futures.sequence is taking the List[Future[Int]] and turning it into a Future[List[Int]]. We can then use 'map' to work with the List[Int] directly, and we find the sum of the List. + +The 'traverse' method is similar to 'sequence', but it takes a Traversable[A] and a Function T => Future[B] to return a Future[Traversable[B]]. For example, to use 'traverse' to sum the first 100 odd numbers: + +.. code-block:: scala + + val oddSum = Futures.traverse((1 to 100).toList)(x => Future(x * 2 - 1)).map(_.sum).apply + +This is the same result as this example: + +.. code-block:: scala + + val oddSum = Futures.sequence((1 to 100).toList.map(x => Future(x * 2 - 1))).map(_.sum).apply + +But it may be faster to use 'traverse' as it doesn't have to create an intermediate List[Future[Int]]. + +This is just a sample of what can be done, but to use more advanced techniques it is easier to take advantage of Scalaz, which Akka has support for in it's akka-scalaz module. + +Scalaz +^^^^^^ + +Akka also has a `Scalaz module `_ for a more complete support of programming in a functional style. + +Exceptions (TODO) +----------------- + +Handling exceptions. + +Fine Tuning (TODO) +------------------ + +Dispatchers and timeouts diff --git a/akka-docs/pending/getting-started.rst b/akka-docs/pending/getting-started.rst new file mode 100644 index 0000000000..51fd1d7957 --- /dev/null +++ b/akka-docs/pending/getting-started.rst @@ -0,0 +1,124 @@ +Getting Started +=============== + +There are several ways to download Akka. You can download the full distribution with microkernel, which includes all modules. You can download just the core distribution. Or you can use a build tool like Maven or SBT to download dependencies from the Akka Maven repository. + +A list of each of the Akka module JARs dependencies can be found `here `_. + +Using a release distribution +============================ + +Akka is split up into two different parts: +* Akka - The core modules. Reflects all the sections under 'Scala API' and 'Java API' in the navigation bar. +* Akka Modules - The microkernel and add-on modules. Reflects all the sections under 'Add-on modules' in the navigation bar. + +Download the release you need (Akka core or Akka Modules) from ``_ and unzip it. + +Microkernel +----------- + +The Akka Modules distribution includes the mircokernel. To run the microkernel: +* Set the AKKA_HOME environment variable to the root of the Akka distribution. +* Run ``java -jar akka-modules-1.0.jar``. This will boot up the microkernel and deploy all samples applications from './deploy' dir. + +For example (bash shell): + +:: + + cd akka-modules-1.0 + export AKKA_HOME=`pwd` + java -jar akka-modules-1.0.jar + +Now you can continue with reading the `tutorial `_ and try to build the tutorial sample project step by step. This can be a good starting point before diving into the reference documentation which can be navigated in the left sidebar. + +Using a build tool +================== + +Akka can be used with build tools that support Maven repositories. The Akka Maven repository can be found at ``_. + +Using Akka with Maven +--------------------- + +If you want to use Akka with Maven then you need to add this repository to your ``pom.xml``: + +.. code-block:: xml + + + Akka + Akka Maven2 Repository + http://akka.io/repository/ + + +Then you can add the Akka dependencies. For example, here is the dependency for Akka Actor 1.0: + +.. code-block:: xml + + + se.scalablesolutions.akka + akka-actor + 1.0 + + +Using Akka with SBT +------------------- + +Akka has an SBT plugin which makes it very easy to get started with Akka and SBT. + +The Scala version in your SBT project needs to match the version that Akka is built against. For 1.0 this is 2.8.1. + +To use the plugin, first add a plugin definition to your SBT project by creating project/plugins/Plugins.scala with: + +.. code-block:: scala + + import sbt._ + + class Plugins(info: ProjectInfo) extends PluginDefinition(info) { + val akkaRepo = "Akka Repo" at "http://akka.io/repository" + val akkaPlugin = "se.scalablesolutions.akka" % "akka-sbt-plugin" % "1.0" + } + +*Note: the plugin version matches the Akka version provided. The current release is 1.0.* + +Then mix the AkkaProject trait into your project definition. For example: + +.. code-block:: scala + + class MyProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject + +*Note: This adds akka-actor as a dependency by default.* + +If you also want to include other Akka modules there is a convenience method: ``akkaModule``. For example, you can add extra Akka modules by adding any of the following lines to your project class: + +.. code-block:: scala + + val akkaStm = akkaModule("stm") + val akkaTypedActor = akkaModule("typed-actor") + val akkaRemote = akkaModule("remote") + val akkaHttp = akkaModule("http") + val akkaAmqp = akkaModule("amqp") + val akkaCamel = akkaModule("camel") + val akkaCamelTyped = akkaModule("camel-typed") + val akkaSpring = akkaModule("spring") + val akkaJta = akkaModule("jta") + val akkaCassandra = akkaModule("persistence-cassandra") + val akkaMongo = akkaModule("persistence-mongo") + val akkaRedis = akkaModule("persistence-redis") + +Build from sources +================== + +Akka uses Git and is hosted at `Github `_. + +* Akka: clone the Akka repository from ``_ +* Akka Modules: clone the Akka Modules repository from ``_ + +Continue reading the page on `how to build and run Akka `_ + +Need help? +========== + +If you have questions you can get help on the `Akka Mailing List `_. + +You can also ask for `commercial support `_. + +Thanks for being a part of the Akka community. diff --git a/akka-docs/pending/guice-integration.rst b/akka-docs/pending/guice-integration.rst new file mode 100644 index 0000000000..44a77fd22c --- /dev/null +++ b/akka-docs/pending/guice-integration.rst @@ -0,0 +1,50 @@ +Guice Integration +================= + +Module stability: **STABLE** + +All Typed Actors supports dependency injection using `Guice `_ annotations (such as ‘@Inject’ etc.). +The ‘TypedActorManager’ class understands Guice and will do the wiring for you. + +External Guice modules +---------------------- + +You can also plug in external Guice modules and have not-actors wired up as part of the configuration. +Here is an example: + +.. code-block:: java + + import static akka.config.Supervision.*; + import static akka.config.SupervisorConfig.*; + + TypedActorConfigurator manager = new TypedActorConfigurator(); + + manager.configure( + new AllForOneStrategy(new Class[]{Exception.class}, 3, 1000), + new SuperviseTypedActor[] { + new SuperviseTypedActor( + Foo.class, + FooImpl.class, + temporary(), + 1000), + new SuperviseTypedActor( + Bar.class, + BarImpl.class, + permanent(), + 1000) + }) + .addExternalGuiceModule(new AbstractModule() { + protected void configure() { + bind(Ext.class).to(ExtImpl.class).in(Scopes.SINGLETON); + }}) + .configure() + .inject() + .supervise(); + +Retrieve the external Guice dependency +-------------------------------------- + +The external dependency can be retrieved like this: +``_ +Ext ext = manager.getExternalDependency(Ext.class); +``_ diff --git a/akka-docs/pending/http.rst b/akka-docs/pending/http.rst new file mode 100644 index 0000000000..51643ad6bc --- /dev/null +++ b/akka-docs/pending/http.rst @@ -0,0 +1,527 @@ +HTTP +==== + +Module stability: **SOLID** + += + +When using Akkas embedded servlet container: +-------------------------------------------- + +Akka supports the JSR for REST called JAX-RS (JSR-311). It allows you to create interaction with your actors through HTTP + REST + +You can deploy your REST services directly into the Akka kernel. All you have to do is to drop the JAR with your application containing the REST services into the ‘$AKKA_HOME/deploy’ directory and specify in your akka.conf what resource packages to scan for (more on that below) and optionally define a “boot class” (if you need to create any actors or do any config). WAR deployment is coming soon. + +Boot configuration class +------------------------ + +The boot class is needed for Akka to bootstrap the application and should contain the initial supervisor configuration of any actors in the module. + +The boot class should be a regular POJO with a default constructor in which the initial configuration is done. The boot class then needs to be defined in the ‘$AKKA_HOME/config/akka.conf’ config file like this: + +.. code-block:: ruby + + akka { + boot = ["sample.java.Boot", "sample.scala.Boot"] # FQN to the class doing initial actor + # supervisor bootstrap, should be defined in default constructor + ... + } + +After you've placed your service-jar into the $AKKA_HOME/deploy directory, you'll need to tell Akka where to look for your services, and you do that by specifying what packages you want Akka to scan for services, and that's done in akka.conf in the http-section: + +.. code-block:: ruby + + akka { + http { + ... + resource-packages = ["com.bar","com.foo.bar"] # List with all resource packages for your Jersey services + ... + } + +When deploying in another servlet container: +-------------------------------------------- + +If you deploy Akka in another JEE container, don't forget to create an Akka initialization and cleanup hook: + +.. code-block:: scala + + package com.my //<--- your own package + import akka.util.AkkaLoader + import akka.remote.BootableRemoteActorService + import akka.actor.BootableActorLoaderService + import javax.servlet.{ServletContextListener, ServletContextEvent} + + /** + * This class can be added to web.xml mappings as a listener to start and postStop Akka. + * + * ... + * + * com.my.Initializer + * + * ... + * + */ + class Initializer extends ServletContextListener { + lazy val loader = new AkkaLoader + def contextDestroyed(e: ServletContextEvent): Unit = loader.shutdown + def contextInitialized(e: ServletContextEvent): Unit = + loader.boot(true, new BootableActorLoaderService with BootableRemoteActorService) //<--- Important + // loader.boot(true, new BootableActorLoaderService {}) // If you don't need akka-remote + } + +Then you just declare it in your web.xml: + +.. code-block:: xml + + + ... + + your.package.Initializer + + ... + + +Also, you need to map the servlet that will handle your Jersey/JAX-RS calls, you use Jerseys ServletContainer servlet. + +.. code-block:: xml + + + ... + + Akka + com.sun.jersey.spi.container.servlet.ServletContainer + + + com.sun.jersey.config.property.resourceConfigClass + com.sun.jersey.api.core.PackagesResourceConfig + + + com.sun.jersey.config.property.packages + your.resource.package.here;and.another.here;and.so.on + + + + * + Akka + + ... + + +Adapting your own Akka Initializer for the Servlet Container +------------------------------------------------------------ + +If you want to use akka-camel or any other modules that have their own "Bootable"'s you'll need to write your own Initializer, which is _ultra_ simple, see below for an example on how to include Akka-camel. + +.. code-block:: scala + + package com.my //<--- your own package + import akka.remote.BootableRemoteActorService + import akka.actor.BootableActorLoaderService + import akka.camel.CamelService + import javax.servlet.{ServletContextListener, ServletContextEvent} + + /** + * This class can be added to web.xml mappings as a listener to start and postStop Akka. + * + * ... + * + * com.my.Initializer + * + * ... + * + */ + class Initializer extends ServletContextListener { + lazy val loader = new AkkaLoader + def contextDestroyed(e: ServletContextEvent): Unit = loader.shutdown + def contextInitialized(e: ServletContextEvent): Unit = + loader.boot(true, new BootableActorLoaderService with BootableRemoteActorService with CamelService) //<--- Important + } + +- + +Java API: Typed Actors +---------------------- + +Click `here `_ to look at a sample module for REST services with Actors in Java + +- + +Scala API: Actors +----------------- + +Click `here `_ to look at a sample module for REST services with Actors in Scala + +Using Akka with the Pinky REST/MVC framework +============================================ + +Pinky has a slick Akka integration. Read more `here `_ + +jetty-run in SBT +================ + +If you want to use jetty-run in SBT you need to exclude the version of Jetty that is bundled in akka-http: + +.. code-block:: scala + + override def ivyXML = + + + + + + +Mist - Lightweight Asynchronous HTTP +==================================== + +The *Mist* layer was developed to provide a direct connection between the servlet container and Akka actors with the goal of handling the incoming HTTP request as quickly as possible in an asynchronous manner. The motivation came from the simple desire to treat REST calls as completable futures, that is, effectively passing the request along an actor message chain to be resumed at the earliest possible time. The primary constraint was to not block any existing threads and secondarily, not create additional ones. Mist is very simple and works both with Jetty Continuations as well as with Servlet API 3.0 (tested using Jetty-8.0.0.M1). When the servlet handles a request, a message is created typed to represent the method (e.g. Get, Post, etc.), the request is suspended and the message is sent (fire-and-forget) to the *root endpoint* actor. That's it. There are no POJOs required to host the service endpoints and the request is treated as any other. The message can be resumed (completed) using a number of helper methods that set the proper HTTP response status code. + +Complete runnable example can be found here: ``_ + +Endpoints +--------- + +Endpoints are actors that handle request messages. Minimally there must be an instance of the *RootEndpoint* and then at least one more (to implement your services). + +Preparations +------------ + +In order to use Mist you have to register the MistServlet in *web.xml* or do the analogous for the embedded server if running in Akka Micrkernel: + +.. code-block:: xml + + + akkaMistServlet + akka.http.AkkaMistServlet + + + + + akkaMistServlet + /* + + +Then you also have to add the following dependencies to your SBT build definition: + +.. code-block:: scala + + val jettyWebapp = "org.eclipse.jetty" % "jetty-webapp" % "8.0.0.M2" % "test" + val javaxServlet30 = "org.mortbay.jetty" % "servlet-api" % "3.0.20100224" % "provided" + +Attention: You have to use SBT 0.7.5.RC0 or higher in order to be able to work with that Jetty version. + +An Example +---------- + +Startup +^^^^^^^ + +In this example, we'll use the built-in *RootEndpoint* class and implement our own service from that. Here the services are started in the boot loader and attached to the top level supervisor. + +.. code-block:: scala + + class Boot { + val factory = SupervisorFactory( + SupervisorConfig( + OneForOneStrategy(List(classOf[Exception]), 3, 100), + // + // in this particular case, just boot the built-in default root endpoint + // + Supervise( + actorOf[RootEndpoint], + Permanent) :: + Supervise( + actorOf[SimpleAkkaAsyncHttpService], + Permanent) + :: Nil)) + factory.newInstance.start + } + +**Defining the Endpoint** +The service is an actor that mixes in the *Endpoint* trait. Here the dispatcher is taken from the Akka configuration file which allows for custom tuning of these actors, though naturally, any dispatcher can be used. + +URI Handling +************ + +Rather than use traditional annotations to pair HTTP request and class methods, Mist uses hook and provide functions. This offers a great deal of flexibility in how a given endpoint responds to a URI. A hook function is simply a filter, returning a Boolean to indicate whether or not the endpoint will handle the URI. This can be as simple as a straight match or as fancy as you need. If a hook for a given URI returns true, the matching provide function is called to obtain an actor to which the message can be delivered. Notice in the example below, in one case, the same actor is returned and in the other, a new actor is created and returned. Note that URI hooking is non-exclusive and a message can be delivered to multiple actors (see next example). + +Plumbing +******** + +Hook and provider functions are attached to a parent endpoint, in this case the root, by sending it the **Endpoint.Attach** message. +Finally, bind the *handleHttpRequest* function of the *Endpoint* trait to the actor's *receive* function and we're done. + +.. code-block:: scala + + class SimpleAkkaAsyncHttpService extends Actor with Endpoint { + final val ServiceRoot = "/simple/" + final val ProvideSameActor = ServiceRoot + "same" + final val ProvideNewActor = ServiceRoot + "new" + + // + // use the configurable dispatcher + // + self.dispatcher = Endpoint.Dispatcher + + // + // there are different ways of doing this - in this case, we'll use a single hook function + // and discriminate in the provider; alternatively we can pair hooks & providers + // + def hook(uri: String): Boolean = ((uri == ProvideSameActor) || (uri == ProvideNewActor)) + def provide(uri: String): ActorRef = { + if (uri == ProvideSameActor) same + else actorOf[BoringActor].start + } + + // + // this is where you want attach your endpoint hooks + // + override def preStart = { + // + // we expect there to be one root and that it's already been started up + // obviously there are plenty of other ways to obtaining this actor + // the point is that we need to attach something (for starters anyway) + // to the root + // + val root = Actor.registry.actorsFor(classOf[RootEndpoint]).head + root ! Endpoint.Attach(hook, provide) + } + + // + // since this actor isn't doing anything else (i.e. not handling other messages) + // just assign the receive func like so... + // otherwise you could do something like: + // def myrecv = {...} + // def receive = myrecv orElse _recv + // + def receive = handleHttpRequest + + // + // this will be our "same" actor provided with ProvideSameActor endpoint is hit + // + lazy val same = actorOf[BoringActor].start + } + +Handling requests +^^^^^^^^^^^^^^^^^ + +Messages are handled just as any other that are received by your actor. The servlet requests and response are not hidden and can be accessed directly as shown below. + +.. code-block:: scala + + /** + * Define a service handler to respond to some HTTP requests + */ + class BoringActor extends Actor { + import java.util.Date + import javax.ws.rs.core.MediaType + + var gets = 0 + var posts = 0 + var lastget: Option[Date] = None + var lastpost: Option[Date] = None + + def receive = { + // handle a get request + case get: Get => + // the content type of the response. + // similar to @Produces annotation + get.response.setContentType(MediaType.TEXT_HTML) + + // + // "work" + // + gets += 1 + lastget = Some(new Date) + + // + // respond + // + val res = "

Gets: "+gets+" Posts: "+posts+"

Last Get: "+lastget.getOrElse("Never").toString+" Last Post: "+lastpost.getOrElse("Never").toString+"

" + get.OK(res) + + // handle a post request + case post:Post => + // the expected content type of the request + // similar to @Consumes + if (post.request.getContentType startsWith MediaType.APPLICATION_FORM_URLENCODED) { + // the content type of the response. + // similar to @Produces annotation + post.response.setContentType(MediaType.TEXT_HTML) + + // "work" + posts += 1 + lastpost = Some(new Date) + + // respond + val res = "

Gets: "+gets+" Posts: "+posts+"

Last Get: "+lastget.getOrElse("Never").toString+" Last Post: "+lastpost.getOrElse("Never").toString+"

" + post.OK(res) + } else { + post.UnsupportedMediaType("Content-Type request header missing or incorrect (was '" + post.request.getContentType + "' should be '" + MediaType.APPLICATION_FORM_URLENCODED + "')") + } + } + + case other: RequestMethod => + other.NotAllowed("Invalid method for this endpoint") + } + } + +**Timeouts** +Messages will expire according to the default timeout (specified in akka.conf). Individual messages can also be updated using the *timeout* method. One thing that may seem unexpected is that when an expired request returns to the caller, it will have a status code of OK (200). Mist will add an HTTP header to such responses to help clients, if applicable. By default, the header will be named "Async-Timeout" with a value of "expired" - both of which are configurable. + +Another Example - multiplexing handlers +--------------------------------------- + +As noted above, hook functions are non-exclusive. This means multiple actors can handle the same request if desired. In this next example, the hook functions are identical (yes, the same one could have been reused) and new instances of both A and B actors will be created to handle the Post. A third mediator is inserted to coordinate the results of these actions and respond to the caller. + +.. code-block:: scala + + package sample.mist + + import akka.actor._ + import akka.actor.Actor._ + import akka.http._ + + import javax.servlet.http.HttpServletResponse + + class InterestingService extends Actor with Endpoint { + final val ServiceRoot = "/interesting/" + final val Multi = ServiceRoot + "multi/" + // use the configurable dispatcher + self.dispatcher = Endpoint.Dispatcher + + // + // The "multi" endpoint shows forking off multiple actions per request + // It is triggered by POSTing to http://localhost:9998/interesting/multi/{foo} + // Try with/without a header named "Test-Token" + // Try with/without a form parameter named "Data" + def hookMultiActionA(uri: String): Boolean = uri startsWith Multi + def provideMultiActionA(uri: String): ActorRef = actorOf(new ActionAActor(complete)).start + + def hookMultiActionB(uri: String): Boolean = uri startsWith Multi + def provideMultiActionB(uri: String): ActorRef = actorOf(new ActionBActor(complete)).start + + // + // this is where you want attach your endpoint hooks + // + override def preStart = { + // + // we expect there to be one root and that it's already been started up + // obviously there are plenty of other ways to obtaining this actor + // the point is that we need to attach something (for starters anyway) + // to the root + // + val root = Actor.registry.actorsFor(classOf[RootEndpoint]).head + root ! Endpoint.Attach(hookMultiActionA, provideMultiActionA) + root ! Endpoint.Attach(hookMultiActionB, provideMultiActionB) + } + + // + // since this actor isn't doing anything else (i.e. not handling other messages) + // just assign the receive func like so... + // otherwise you could do something like: + // def myrecv = {...} + // def receive = myrecv orElse handleHttpRequest + // + def receive = handleHttpRequest + + // + // this guy completes requests after other actions have occured + // + lazy val complete = actorOf[ActionCompleteActor].start + } + + class ActionAActor(complete:ActorRef) extends Actor { + import javax.ws.rs.core.MediaType + + def receive = { + // handle a post request + case post: Post => + // the expected content type of the request + // similar to @Consumes + if (post.request.getContentType startsWith MediaType.APPLICATION_FORM_URLENCODED) { + // the content type of the response. + // similar to @Produces annotation + post.response.setContentType(MediaType.TEXT_HTML) + + // get the resource name + val name = post.request.getRequestURI.substring("/interesting/multi/".length) + if (name.length % 2 == 0) post.response.getWriter.write("

Action A verified request.

") + else post.response.getWriter.write("

Action A could not verify request.

") + + // notify the next actor to coordinate the response + complete ! post + } else post.UnsupportedMediaType("Content-Type request header missing or incorrect (was '" + post.request.getContentType + "' should be '" + MediaType.APPLICATION_FORM_URLENCODED + "')") + } + } + } + + class ActionBActor(complete:ActorRef) extends Actor { + import javax.ws.rs.core.MediaType + + def receive = { + // handle a post request + case post: Post => + // the expected content type of the request + // similar to @Consumes + if (post.request.getContentType startsWith MediaType.APPLICATION_FORM_URLENCODED) { + // pull some headers and form params + def default(any: Any): String = "" + + val token = post.getHeaderOrElse("Test-Token", default) + val data = post.getParameterOrElse("Data", default) + + val (resp, status) = (token, data) match { + case ("", _) => ("No token provided", HttpServletResponse.SC_FORBIDDEN) + case (_, "") => ("No data", HttpServletResponse.SC_ACCEPTED) + case _ => ("Data accepted", HttpServletResponse.SC_OK) + } + + // update the response body + post.response.getWriter.write(resp) + + // notify the next actor to coordinate the response + complete ! (post, status) + } else post.UnsupportedMediaType("Content-Type request header missing or incorrect (was '" + post.request.getContentType + "' should be '" + MediaType.APPLICATION_FORM_URLENCODED + "')") + } + + case other: RequestMethod => + other.NotAllowed("Invalid method for this endpoint") + } + } + + class ActionCompleteActor extends Actor { + import collection.mutable.HashMap + + val requests = HashMap.empty[Int, Int] + + def receive = { + case req: RequestMethod => + if (requests contains req.hashCode) complete(req) + else requests += (req.hashCode -> 0) + + case t: Tuple2[RequestMethod, Int] => + if (requests contains t._1.hashCode) complete(t._1) + else requests += (t._1.hashCode -> t._2) + } + + def complete(req: RequestMethod) = requests.remove(req.hashCode) match { + case Some(HttpServletResponse.SC_FORBIDDEN) => req.Forbidden("") + case Some(HttpServletResponse.SC_ACCEPTED) => req.Accepted("") + case Some(_) => req.OK("") + case _ => {} + } + } + +Examples +-------- + +Using the Akka Mist module with OAuth +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``_ + +Using the Akka Mist module with the Facebook Graph API and WebGL +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Example project using Akka Mist with the Facebook Graph API and WebGL +``_ diff --git a/akka-docs/pending/issue-tracking.rst b/akka-docs/pending/issue-tracking.rst new file mode 100644 index 0000000000..fcff2e2c94 --- /dev/null +++ b/akka-docs/pending/issue-tracking.rst @@ -0,0 +1,41 @@ +Issue Tracking +============== + +Akka is using Assembla as issue tracking system. + +Browsing +-------- + +You can find the Akka tickets here: ``_ +The roadmap for each milestone is here: ``_ + +Creating tickets +---------------- + +In order to create tickets you need to do the following: + +# Register here: ``_ +# Log in +# Create the ticket: ``_ + +Thanks a lot for reporting bugs and suggesting features. + +Failing test +------------ + +Please submit a failing test on the following format: + +``_ + +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers + +class Ticket001Spec extends WordSpec with MustMatchers { + + "An XXX" should { + "do YYY" in { + 1 must be (1) + } + } +} +``_ diff --git a/akka-docs/pending/language-bindings.rst b/akka-docs/pending/language-bindings.rst new file mode 100644 index 0000000000..0492108863 --- /dev/null +++ b/akka-docs/pending/language-bindings.rst @@ -0,0 +1,24 @@ +Other Language Bindings +======================= + +JRuby +===== + +High level concurrency using Akka actors and JRuby. + +``_ + +If you are using STM with JRuby then you need to unwrap the Multiverse control flow exception as follows: + +.. code-block:: ruby + + begin + ... atomic stuff + rescue NativeException => e + raise e.cause if e.cause.java_class.package.name.include? "org.multiverse" + end + +Groovy/Groovy++ +=============== + +``_ diff --git a/akka-docs/pending/licenses.rst b/akka-docs/pending/licenses.rst new file mode 100644 index 0000000000..557f37a779 --- /dev/null +++ b/akka-docs/pending/licenses.rst @@ -0,0 +1,197 @@ +Licenses +======== + +Akka License +============ + +:: + + This software is licensed under the Apache 2 license, quoted below. + + Copyright 2009-2011 Scalable Solutions AB + + Licensed under the Apache License, Version 2.0 (the "License"); you may not + use this file except in compliance with the License. You may obtain a copy of + the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + License for the specific language governing permissions and limitations under + the License. + +Akka Committer License Agreement +================================ + +All committers have signed this CLA + +:: + + Based on: http://www.apache.org/licenses/icla.txt + + Scalable Solutions AB + Individual Contributor License Agreement ("Agreement") V2.0 + http://www.scalablesolutions.se/licenses/ + + Thank you for your interest in Akka, a Scalable Solutions AB (the + "Company") Open Source project. In order to clarify the intellectual + property license granted with Contributions from any person or entity, + the Company must have a Contributor License Agreement ("CLA") on file + that has been signed by each Contributor, indicating agreement to the + license terms below. This license is for your protection as a + Contributor as well as the protection of the Company and its users; + it does not change your rights to use your own Contributions for any + other purpose. + + Full name: ______________________________________________________ + + Mailing Address: ________________________________________________ + + _________________________________________________________________ + + _________________________________________________________________ + + Country: ______________________________________________________ + + Telephone: ______________________________________________________ + + Facsimile: ______________________________________________________ + + E-Mail: ______________________________________________________ + + You accept and agree to the following terms and conditions for Your + present and future Contributions submitted to the Company. In + return, the Company shall not use Your Contributions in a way that + is contrary to the public benefit or inconsistent with its nonprofit + status and bylaws in effect at the time of the Contribution. Except + for the license granted herein to the Company and recipients of + software distributed by the Company, You reserve all right, title, + and interest in and to Your Contributions. + + 1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with the Company. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to the Company for inclusion + in, or documentation of, any of the products owned or managed by + the Company (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to the Company or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, the Company for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + + 2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to the Company and to + recipients of software distributed by the Company a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + + 3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to the Company and to + recipients of software distributed by the Company a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + + 4. You agree that all Contributions are and will be given entirely + voluntarily. Company will not be required to use, or to refrain + from using, any Contributions that You, will not, absent a + separate written agreement signed by Company, create any + confidentiality obligation of Company, and Company has not + undertaken any obligation to treat any Contributions or other + information You have given Company or will give Company in the + future as confidential or proprietary information. Furthermore, + except as otherwise provided in a separate subsequence written + agreement between You and Company, Company will be free to use, + disclose, reproduce, license or otherwise distribute, and exploit + the Contributions as it sees fit, entirely without obligation or + restriction of any kind on account of any proprietary or + intellectual property rights or otherwise. + + 5. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to the Company, or that your employer has + executed a separate Corporate CLA with the Company. + + 6. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + + 7. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + + 8. Should You wish to submit work that is not Your original creation, + You may submit it to the Company separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + + 9. You agree to notify the Company of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + + 9. The validity of the interpretation of this Agreements shall be + governed by, and constructed and enforced in accordance with, the + laws of Sweden, applicable to the agreements made there (excluding + the conflict of law rules). This Agreement embodies the entire + agreement and understanding of the parties hereto and supersedes + any and all prior agreements, arrangements and understandings + relating to the matters provided for herein. No alteration, waiver, + amendment changed or supplement hereto shall be binding more + effective unless the same as set forth in writing signed by both + parties. + + Please sign: __________________________________ Date: ________________ + += + +Licenses for Dependency Libraries +================================= + +Each dependency and its license can be seen in the project build file (the comment on the side of each dependency): +``_ diff --git a/akka-docs/pending/logging.rst b/akka-docs/pending/logging.rst new file mode 100644 index 0000000000..833f2f419b --- /dev/null +++ b/akka-docs/pending/logging.rst @@ -0,0 +1,4 @@ +Logging +======= + +Logging has been removed. See the `Event Handler `_. diff --git a/akka-docs/pending/migration-guide-0.10.x-1.0.x.rst b/akka-docs/pending/migration-guide-0.10.x-1.0.x.rst new file mode 100644 index 0000000000..300100941f --- /dev/null +++ b/akka-docs/pending/migration-guide-0.10.x-1.0.x.rst @@ -0,0 +1,432 @@ +Migration guide from 0.10.x to 1.0.x +==================================== + +---- + +Akka & Akka Modules separated into two different repositories and distributions +------------------------------------------------------------------------------- + +Akka is split up into two different parts: +* Akka - Reflects all the sections under 'Scala API' and 'Java API' in the navigation bar. +* Akka Modules - Reflects all the sections under 'Add-on modules' in the navigation bar. + +Download the release you need (Akka core or Akka Modules) from ``_ and unzip it. + +---- + +Changed Akka URI +---------------- + +http:*akkasource.org changed to http:*akka.io + +Reflects XSDs, Maven repositories, ScalaDoc etc. + +---- + +Removed 'se.scalablesolutions' prefix +------------------------------------- + +We have removed some boilerplate by shortening the Akka package from +**se.scalablesolutions.akka** to just **akka** so just do a search-replace in your project, +we apologize for the inconvenience, but we did it for our users. + +---- + +Akka-core is no more +-------------------- + +Akka-core has been split into akka-actor, akka-stm, akka-typed-actor & akka-remote this means that you need to update any deps you have on akka-core. + +---- + +Config +------ + +Turning on/off modules +^^^^^^^^^^^^^^^^^^^^^^ + +All the 'service = on' elements for turning modules on and off have been replaced by a top-level list of the enabled services. + +Services available for turning on/off are: +* "remote" +* "http" +* "camel" + +**All** services are **OFF** by default. Enable the ones you are using. + +.. code-block:: ruby + + akka { + enabled-modules = [] # Comma separated list of the enabled modules. Options: ["remote", "camel", "http"] + } + +Renames +^^^^^^^ + +* 'rest' section - has been renamed to 'http' to align with the module name 'akka-http'. +* 'storage' section - has been renamed to 'persistence' to align with the module name 'akka-persistence'. + +.. code-block:: ruby + + akka { + http { + .. + } + + persistence { + .. + } + } + +---- + +Important changes from RC2-RC3 +------------------------------ + +**akka.config.SupervisionSupervise** +def apply(actorRef: ActorRef, lifeCycle: LifeCycle, registerAsRemoteService: Boolean = false) +- boolean instead of remoteAddress, registers that actor with it's id as service name on the local server + +**akka.actor.Actors now is the API for Java to interact with Actors, Remoting and ActorRegistry:** + +import static akka.actor.Actors.*; +*actorOf()..* +remote().actorOf()... +*registry().actorsFor("foo")...* + +***akka.actor.Actor now is the API for Scala to interact with Actors, Remoting and ActorRegistry:*** + +*import akka.actor.Actor._* +actorOf()... +*remote.actorOf()...* +registry.actorsFor("foo") + +**object UntypedActor has been deleted and replaced with akka.actor.Actors/akka.actor.Actor (Java/Scala)** +UntypedActor.actorOf -> Actors.actorOf (Java) or Actor.actorOf (Scala) + +**object ActorRegistry has been deleted and replaced with akka.actor.Actors.registry()/akka.actor.Actor.registry (Java/Scala)** +ActorRegistry. -> Actors.registry(). (Java) or Actor.registry. (Scala) + +**object RemoteClient has been deleted and replaced with akka.actor.Actors.remote()/akka.actor.Actor.remote (Java/Scala)** +RemoteClient -> Actors.remote() (Java) or Actor.remote (Scala) + +**object RemoteServer has been deleted and replaced with akka.actor.Actors.remote()/akka.actor.Actor.remote (Java/Scala)** +RemoteServer - deleted -> Actors.remote() (Java) or Actor.remote (Scala) + +**classes RemoteActor, RemoteUntypedActor and RemoteUntypedConsumerActors has been deleted and replaced** +**with akka.actor.Actors.remote().actorOf(x, host port)/akka.actor.Actor.remote.actorOf(x, host, port)** +RemoteActor, RemoteUntypedActor - deleted, use: remote().actorOf(YourActor.class, host, port) (Java) or remote.actorOf[YourActor](host, port) + +**Remoted spring-actors now default to spring id as service-name, use "service-name" attribute on "remote"-tag to override** + +**Listeners for RemoteServer and RemoteClient** are now registered on Actors.remote().addListener (Java) or Actor.remote.addListener (Scala), +this means that all listeners get all remote events, both remote server evens and remote client events, **so adjust your code accordingly.** + +**ActorRef.startLinkRemote has been removed since one specified on creation wether the actor is client-managed or not.** + +Important change from RC3 to RC4 +-------------------------------- + +The Akka-Spring namespace has changed from akkasource.org and scalablesolutions.se to http:*akka.io/schema and http:*akka.io/akka-.xsd + +---- + +Module akka-actor +----------------- + +The Actor.init callback has been renamed to "preStart" to align with the general callback naming and is more clear about when it's called. + +The Actor.shutdown callback has been renamed to "postStop" to align with the general callback naming and is more clear about when it's called. + +The Actor.initTransactionalState callback has been removed, logic should be moved to preStart and be wrapped in an atomic block + +**se.scalablesolutions.akka.config.ScalaConfig** and **se.scalablesolutions.akka.config.JavaConfig** have been merged into **akka.config.Supervision** + +**RemoteAddress** has moved from **se.scalablesolutions.akka.config.ScalaConfig** to **akka.config** + +The ActorRef.lifeCycle has changed signature from Option[LifeCycle] to LifeCycle, this means you need to change code that looks like this: +**self.lifeCycle = Some(LifeCycle(Permanent))** to **self.lifeCycle = Permanent** + +The equivalent to **self.lifeCycle = None** is **self.lifeCycle = UndefinedLifeCycle** +**LifeCycle(Permanent)** becomes **Permanent** +**new LifeCycle(permanent())** becomes **permanent()** (need to do: import static se.scalablesolutions.akka.config.Supervision.*; first) + +**JavaConfig.Component** and **ScalaConfig.Component** have been consolidated and renamed as **Supervision.SuperviseTypedActor** + +**self.trapExit** has been moved into the FaultHandlingStrategy, and **ActorRef.faultHandler** has switched type from Option[FaultHandlingStrategy] +to FaultHandlingStrategy: + +|| **Scala** || +|| +``_ +import akka.config.Supervision._ + +self.faultHandler = OneForOneStrategy(List(classOf[Exception]), 3, 5000) + +``_ || +|| **Java** || +|| +``_ +import static akka.Supervision.*; + +getContext().setFaultHandler(new OneForOneStrategy(new Class[] { Exception.class },50,1000)) + +``_ || + +**RestartStrategy, AllForOne, OneForOne** have been replaced with **AllForOneStrategy** and **OneForOneStrategy** in **se.scalablesolutions.akka.config.Supervision** + +|| **Scala** || +|| +``_ +import akka.config.Supervision._ +SupervisorConfig( + OneForOneStrategy(List(classOf[Exception]), 3, 5000), + Supervise(pingpong1,Permanent) :: Nil +) + +``_ || +|| **Java** || +|| +``_ +import static akka.Supervision.*; + +new SupervisorConfig( + new OneForOneStrategy(new Class[] { Exception.class },50,1000), + new Server[] { new Supervise(pingpong1, permanent()) } +) + +``_ || + +We have removed the following factory methods: + +**Actor.actor { case foo => bar }** +**Actor.transactor { case foo => bar }** +**Actor.temporaryActor { case foo => bar }** +**Actor.init {} receive { case foo => bar }** + +They started the actor and no config was possible, it was inconsistent and irreparable. + +replace with your own factories, or: + +**actorOf( new Actor { def receive = { case foo => bar } } ).start** +**actorOf( new Actor { self.lifeCycle = Temporary; def receive = { case foo => bar } } ).start** + +ReceiveTimeout is now rescheduled after every message, before there was only an initial timeout. +To stop rescheduling of ReceiveTimeout, set **receiveTimeout = None** + +HotSwap +------- + +HotSwap does no longer use behavior stacking by default, but that is an option to both "become" and HotSwap. + +HotSwap now takes for Scala a Function from ActorRef to a Receive, the ActorRef passed in is the reference to self, so you can do self.reply() etc. + +---- + +Module akka-stm +--------------- + +The STM stuff is now in its own module. This means that there is no support for transactions or transactors in akka-actor. + +Local and global +^^^^^^^^^^^^^^^^ + +The **local/global** distinction has been dropped. This means that if the following general import was being used: + +.. code-block:: scala + + import akka.stm.local._ + +this is now just: + +.. code-block:: scala + + import akka.stm._ + +Coordinated is the new global +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There is a new explicit mechanism for coordinated transactions. See the `Scala Transactors `_ and `Java Transactors `_ documentation for more information. Coordinated transactions and transactors are found in the ``akka.transactor`` package now. The usage of transactors has changed. + +Agents +^^^^^^ + +Agent is now in the akka-stm module and has moved to the ``akka.agent`` package. The implementation has been reworked and is now closer to Clojure agents. There is not much difference in general usage, the main changes involve interaction with the STM. + +While updates to Agents are asynchronous, the state of an Agent is always immediately available for reading by any thread. Agents are integrated with the STM - any dispatches made in a transaction are held until that transaction commits, and are discarded if it is retried or aborted. There is a new ``sendOff`` method for long-running or blocking update functions. + +---- + +Module akka-camel +----------------- + +Access to the CamelService managed by CamelServiceManager has changed: + +* Method service renamed to mandatoryService (Scala) +* Method service now returns Option[CamelService] (Scala) +* Introduced method getMandatoryService() (Java) +* Introduced method getService() (Java) + +|| **Scala** || +|| +``_ +import se.scalablesolutions.akka.camel.CamelServiceManager._ +import se.scalablesolutions.akka.camel.CamelService + +val o: Option[CamelService] = service +val s: CamelService = mandatoryService + +``_ || +|| **Java** || +|| +``_ +import se.scalablesolutions.akka.camel.CamelService; +import se.scalablesolutions.akka.japi.Option; +import static se.scalablesolutions.akka.camel.CamelServiceManager.*; + +Option o = getService(); +CamelService s = getMandatoryService(); + +``_ || + +Access to the CamelContext and ProducerTemplate managed by CamelContextManager has changed: + +* Method context renamed to mandatoryContext (Scala) +* Method template renamed to mandatoryTemplate (Scala) +* Method service now returns Option[CamelContext] (Scala) +* Method template now returns Option[ProducerTemplate] (Scala) +* Introduced method getMandatoryContext() (Java) +* Introduced method getContext() (Java) +* Introduced method getMandatoryTemplate() (Java) +* Introduced method getTemplate() (Java) + +|| **Scala** || +|| +``_ +import org.apache.camel.CamelContext +import org.apache.camel.ProducerTemplate + +import se.scalablesolutions.akka.camel.CamelContextManager._ + +val co: Option[CamelContext] = context +val to: Option[ProducerTemplate] = template + +val c: CamelContext = mandatoryContext +val t: ProducerTemplate = mandatoryTemplate + +``_ || +|| **Java** || +|| +``_ +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; + +import se.scalablesolutions.akka.japi.Option; +import static se.scalablesolutions.akka.camel.CamelContextManager.*; + +Option co = getContext(); +Option to = getTemplate(); + +CamelContext c = getMandatoryContext(); +ProducerTemplate t = getMandatoryTemplate(); + +``_ || + +The following methods have been renamed on class se.scalablesolutions.akka.camel.Message: + +* bodyAs(Class) has been renamed to getBodyAs(Class) +* headerAs(String, Class) has been renamed to getHeaderAs(String, Class) + +The API for waiting for consumer endpoint activation and de-activation has been changed + +* CamelService.expectEndpointActivationCount has been removed and replaced by CamelService.awaitEndpointActivation +* CamelService.expectEndpointDeactivationCount has been removed and replaced by CamelService.awaitEndpointDeactivation + +|| **Scala** || +|| +``_ +import se.scalablesolutions.akka.actor.Actor +import se.scalablesolutions.akka.camel.CamelServiceManager._ + +val s = startCamelService +val actor = Actor.actorOf[SampleConsumer] + +// wait for 1 consumer being activated +s.awaitEndpointActivation(1) { + actor.start +} + +// wait for 1 consumer being de-activated +s.awaitEndpointDeactivation(1) { + actor.stop +} + +s.stop + +``_ || +|| **Java** || +|| +``_ +import java.util.concurrent.TimeUnit; +import se.scalablesolutions.akka.actor.ActorRef; +import se.scalablesolutions.akka.actor.Actors; +import se.scalablesolutions.akka.camel.CamelService; +import se.scalablesolutions.akka.japi.SideEffect; +import static se.scalablesolutions.akka.camel.CamelServiceManager.*; + +CamelService s = startCamelService(); +final ActorRef actor = Actors.actorOf(SampleUntypedConsumer.class); + +// wait for 1 consumer being activated +s.awaitEndpointActivation(1, new SideEffect() { + public void apply() { + actor.start(); + } +}); + +// wait for 1 consumer being de-activated +s.awaitEndpointDeactivation(1, new SideEffect() { + public void apply() { + actor.stop(); + } +}); + +s.stop(); + +``_ || + +- + +Module Akka-Http +---------------- + +Atmosphere support has been removed. If you were using akka.comet.AkkaServlet for Jersey support only, +you can switch that to: akka.http.AkkaRestServlet and it should work just like before. + +Atmosphere has been removed because we have a new async http support in the form of Akka Mist, a very thin bridge +between Servlet3.0/JettyContinuations and Actors, enabling Http-as-messages, read more about it here: +http://doc.akka.io/http#Mist%20-%20Lightweight%20Asynchronous%20HTTP + +If you really need Atmosphere support, you can add it yourself by following the steps listed at the start of: +http://doc.akka.io/comet + +Module akka-spring +------------------ + +The Akka XML schema URI has changed to http://akka.io/schema/akka + +``_ + + + + + + +``_ diff --git a/akka-docs/pending/migration-guide-0.7.x-0.8.x.rst b/akka-docs/pending/migration-guide-0.7.x-0.8.x.rst new file mode 100644 index 0000000000..5c45eb76c1 --- /dev/null +++ b/akka-docs/pending/migration-guide-0.7.x-0.8.x.rst @@ -0,0 +1,94 @@ +Migrate from 0.7.x to 0.8.x +=========================== + +This is a case-by-case migration guide from Akka 0.7.x (on Scala 2.7.7) to Akka 0.8.x (on Scala 2.8.x) +------------------------------------------------------------------------------------------------------ + +Cases: +====== + +Actor.send is removed and replaced in full with Actor.! +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + myActor send "test" + +becomes + +.. code-block:: scala + + myActor ! "test" + +Actor.! now has it's implicit sender defaulted to None +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + def !(message: Any)(implicit sender: Option[Actor] = None) + +"import Actor.Sender.Self" has been removed because it's not needed anymore +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Remove + +.. code-block:: scala + + import Actor.Sender.Self + +Actor.spawn now uses manifests instead of concrete class types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + val someActor = spawn(classOf[MyActor]) + +becomes + +.. code-block:: scala + + val someActor = spawn[MyActor] + +Actor.spawnRemote now uses manifests instead of concrete class types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + val someActor = spawnRemote(classOf[MyActor],"somehost",1337) + +becomes + +.. code-block:: scala + + val someActor = spawnRemote[MyActor]("somehost",1337) + +Actor.spawnLink now uses manifests instead of concrete class types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + val someActor = spawnLink(classOf[MyActor]) + +becomes + +.. code-block:: scala + + val someActor = spawnLink[MyActor] + +Actor.spawnLinkRemote now uses manifests instead of concrete class types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + val someActor = spawnLinkRemote(classOf[MyActor],"somehost",1337) + +becomes + +.. code-block:: scala + + val someActor = spawnLinkRemote[MyActor]("somehost",1337) + +**Transaction.atomic and friends are moved into Transaction.Local._ and Transaction.Global._** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We now make a difference between transaction management that are local within a thread and global across many threads (and actors). diff --git a/akka-docs/pending/migration-guide-0.8.x-0.9.x.rst b/akka-docs/pending/migration-guide-0.8.x-0.9.x.rst new file mode 100644 index 0000000000..81866e1993 --- /dev/null +++ b/akka-docs/pending/migration-guide-0.8.x-0.9.x.rst @@ -0,0 +1,169 @@ +**This document describes between the 0.8.x and the 0.9 release.** + +Background for the new ActorRef +=============================== + +In the work towards 0.9 release we have now done a major change to how Actors are created. In short we have separated identity and value, created an 'ActorRef' that holds the actual Actor instance. This allows us to do many great things such as for example: + +* Create serializable, immutable, network-aware Actor references that can be freely shared across the network. They "remember" their origin and will always work as expected. +* Not only kill and restart the same supervised Actor instance when it has crashed (as we do now), but dereference it, throw it away and make it eligible for garbage collection. +* etc. much more + +These work very much like the 'PID' (process id) in Erlang. + +These changes means that there is no difference in defining Actors. You still use the old Actor trait, all methods are there etc. But you can't just new this Actor up and send messages to it since all its public API methods are gone. They now reside in a new class; 'ActorRef' and use need to use instances of this class to interact with the Actor (sending messages etc.). + +Here is a short migration guide with the things that you have to change. It is a big conceptual change but in practice you don't have to change much. + +Migration Guide +=============== + +Creating Actors with default constructor +---------------------------------------- + +From: + +.. code-block:: scala + + val a = new MyActor + a ! msg + +To: + +.. code-block:: scala + + import Actor._ + val a = actorOf[MyActor] + a ! msg + +You can also start it in the same statement: + +.. code-block:: scala + + val a = actorOf[MyActor].start + +Creating Actors with non-default constructor +-------------------------------------------- + +From: + +.. code-block:: scala + + val a = new MyActor(..) + a ! msg + +To: + +.. code-block:: scala + + import Actor._ + val a = actorOf(new MyActor(..)) + a ! msg + +Use of 'self' ActorRef API +-------------------------- + +Where you have used 'this' to refer to the Actor from within itself now use 'self': + +.. code-block:: scala + + self ! MessageToMe + +Now the Actor trait only has the callbacks you can implement: +* receive +* postRestart/preRestart +* init/shutdown + +It has no state at all. + +All API has been moved to ActorRef. The Actor is given its ActorRef through the 'self' member variable. +Here you find functions like: +* !, !!, !!! and forward +* link, unlink, startLink, spawnLink etc +* makeTransactional, makeRemote etc. +* start, stop +* etc. + +Here you also find fields like +* dispatcher = ... +* id = ... +* lifeCycle = ... +* faultHandler = ... +* trapExit = ... +* etc. + +This means that to use them you have to prefix them with 'self', like this: + +.. code-block:: scala + + self ! Message + +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 = ... + spawnLink[OtherActor] + ... + } + +Serialization +============= + +If you want to serialize it yourself, here is how to do it: + +.. code-block:: scala + + val actorRef1 = actorOf[MyActor] + + val bytes = actorRef1.toBinary + + val actorRef2 = ActorRef.fromBinary(bytes) + +If you are also using Protobuf then you can use the methods that work with Protobuf's Messages directly. + +.. code-block:: scala + + val actorRef1 = actorOf[MyActor] + + val protobufMessage = actorRef1.toProtocol + + val actorRef2 = ActorRef.fromProtocol(protobufMessage) + + Camel +====== + +Some methods of the se.scalablesolutions.akka.camel.Message class have been deprecated in 0.9. These are + +.. code-block:: scala + + package se.scalablesolutions.akka.camel + + case class Message(...) { + // ... + @deprecated def bodyAs[T](clazz: Class[T]): T + @deprecated def setBodyAs[T](clazz: Class[T]): Message + // ... + } + +They will be removed in 1.0. Instead use + +.. code-block:: scala + + package se.scalablesolutions.akka.camel + + case class Message(...) { + // ... + def bodyAs[T](implicit m: Manifest[T]): T = + def setBodyAs[T](implicit m: Manifest[T]): Message + // ... + } + +Usage example: +``_ +val m = Message(1.4) +val b = m.bodyAs[String] +``_ diff --git a/akka-docs/pending/migration-guide-0.9.x-0.10.x.rst b/akka-docs/pending/migration-guide-0.9.x-0.10.x.rst new file mode 100644 index 0000000000..68ec0cb087 --- /dev/null +++ b/akka-docs/pending/migration-guide-0.9.x-0.10.x.rst @@ -0,0 +1,45 @@ +Migration Guide from Akka 0.9.x to Akka 0.10.x +============================================== + +Module akka-camel +----------------- + +The following list summarizes the breaking changes since Akka 0.9.1. + +* CamelService moved from package se.scalablesolutions.akka.camel.service one level up to se.scalablesolutions.akka.camel. +* CamelService.newInstance removed. For starting and stopping a CamelService, applications should use +** CamelServiceManager.startCamelService and +** CamelServiceManager.stopCamelService. +* Existing def receive = produce method definitions from Producer implementations must be removed (resolves compile error: method receive needs override modifier). +* The Producer.async method and the related Sync trait have been removed. This is now fully covered by Camel's `asynchronous routing engine `_. +* @consume annotation can not placed any longer on actors (i.e. on type-level), only on typed actor methods. Consumer actors must mixin the Consumer trait. +* @consume annotation moved to package se.scalablesolutions.akka.camel. + +Logging +------- + +We've switched to Logback (SLF4J compatible) for the logging, if you're having trouble seeing your log output you'll need to make sure that there's a logback.xml available on the classpath or you'll need to specify the location of the logback.xml file via the system property, ex: -Dlogback.configurationFile=/path/to/logback.xml + +Configuration +------------- + +* The configuration is now JSON-style (see below). +* Now you can define the time-unit to be used throughout the config file: + +.. code-block:: ruby + + akka { + version = "0.10" + time-unit = "seconds" # default timeout time unit for all timeout properties throughout the config + + actor { + timeout = 5 # default timeout for future based invocations + throughput = 5 # default throughput for ExecutorBasedEventDrivenDispatcher + } + ... + } + +RemoteClient events +------------------- + +All events now has a reference to the RemoteClient instance instead of 'hostname' and 'port'. This is more flexible. Enables simpler reconnecting etc. diff --git a/akka-docs/pending/migration-guides.rst b/akka-docs/pending/migration-guides.rst new file mode 100644 index 0000000000..4c44977d2f --- /dev/null +++ b/akka-docs/pending/migration-guides.rst @@ -0,0 +1,8 @@ +Here are migration guides for the latest releases +================================================= + +* `Migrate 0.7.x -> 0.8.x `_ +* `Migrate 0.8.x -> 0.9.x `_ +* `Migrate 0.9.x -> 0.10.x `_ +* `Migrate 0.10.x -> 1.0.x `_ +* `Migrate 1.0.x -> 1.1.x `_ diff --git a/akka-docs/pending/release-notes.rst b/akka-docs/pending/release-notes.rst new file mode 100644 index 0000000000..2000b5a1d6 --- /dev/null +++ b/akka-docs/pending/release-notes.rst @@ -0,0 +1,656 @@ +Release Notes +============== + +Changes listed in no particular order. + +Current Development 1.1-SNAPSHOT +================================ + +||~ =Type= ||~ =Changes= ||~ =By= || +|| **UPD** || improve FSM DSL: make onTransition syntax nicer || Roland Kuhn || + +Release 1.1-M1 +============== + +||~ =**Type** + ||~ +===== + +**Changes** + ||~ +===== + +**By**= || +|| **ADD** || #647 Extract an akka-camel-typed module out of akka-camel for optional typed actor support || Martin Krasser || +|| **ADD** || #654 Allow consumer actors to acknowledge in-only message exchanges || Martin Krasser || +|| **ADD** || #669 Support self.reply in preRestart and postStop after exception in receive || Martin Krasser || +|| **ADD** || #682 Support for fault-tolerant Producer actors || Martin Krasser || +|| **ADD** || Move TestKit to akka-testkit and add CallingThreadDispatcher || Roland Kuhn || +|| **ADD** || Remote Client message buffering transaction log for buffering messages failed to send due to network problems. Flushes the buffer on reconnect. || Jonas Bonér || +|| **ADD** || Added trait simulate network problems/errors to be used for remote actor testing || Jonas Bonér || +|| **ADD** || Add future and await methods to Agent || Peter Vlugter || +|| **ADD** || #586 Allow explicit reconnect for RemoteClient || Viktor Klang || +|| **ADD** || #587 Dead letter sink queue for messages sent through RemoteClient that didn't get sent due to connection failure || Viktor Klang || +|| **ADD** || #598 actor.id when using akka-spring should be the id of the spring bean || Viktor Klang || +|| **ADD** || #652 Reap expired futures from ActiveRemoteClientHandler || Viktor Klang || +|| **ADD** || #656 Squeeze more out of EBEDD? || Viktor Klang || +|| **ADD** || #715 EventHandler.error should be usable without Throwable || Viktor Klang || +|| **ADD** || #717 Add ExecutionHandler to NettyRemoteServer for more performance and scalability || Viktor Klang || +|| **ADD** || #497 Optimize remote sends done in local scope || Viktor Klang || +|| **ADD** || #633 Add support for Scalaz in akka-modules || Derek Williams || +|| **ADD** || #677 Add map, flatMap, foreach, and filter to Future || Derek Williams || +|| **ADD** || #661 Optimized Future's internals || Derek Williams || +|| **ADD** || #685 Optimize execution of Futures || Derek Williams || +|| **ADD** || #711 Make Future.completeWith work with an uncompleted Future || Derek Williams || +|| **UPD** || #667 Upgrade to Camel 2.7.0 || Martin Krasser || +|| **UPD** || Updated HawtDispatch to 1.1 || Hiram Chirino || +|| **UPD** || #688 Update Akka 1.1-SNAPSHOT to Scala 2.9.0-RC1 || Viktor Klang || +|| **UPD** || #718 Add HawtDispatcher to akka-modules || Viktor Klang || +|| **UPD** || #698 Deprecate client-managed actors || Viktor Klang || +|| **UPD** || #730 Update Akka and Akka Modules to SBT 0.7.6-RC0 || Viktor Klang || +|| **UPD** || #663 Update to latest scalatest || Derek Williams || +|| **FIX** || Misc cleanup, API changes and refactorings || Jonas Bonér || +|| **FIX** || #675 preStart() is called twice when creating new instance of TypedActor || Debasish Ghosh || +|| **FIX** || #704 Write docs for Java Serialization || Debasish Ghosh || +|| **FIX** || #645 Change Futures.awaitAll to not throw FutureTimeoutException but return a List[Option[Any]] || Viktor Klang || +|| **FIX** || #681 Clean exit using server-managed remote actor via client || Viktor Klang || +|| **FIX** || #720 Connection loss when sending to a dead remote actor || Viktor Klang || +|| **FIX** || #593 Move Jetty specific stuff (with deps) from akka-http to akka-kernel || Viktor Klang || +|| **FIX** || #638 ActiveRemoteClientHandler - Unexpected exception from downstream in remote client || Viktor Klang || +|| **FIX** || #655 Remote actors with non-uuid names doesnt work for req./reply-pattern || Viktor Klang || +|| **FIX** || #588 RemoteClient.shutdown does not remove client from Map with clients || Viktor Klang || +|| **FIX** || #672 Remoting breaks if mutual DNS lookup isn't possible || Viktor Klang || +|| **FIX** || #699 Remote typed actor per-session server won't start if called method has no result || Viktor Klang || +|| **FIX** || #702 Handle ReadTimeoutException in akka-remote || Viktor Klang || +|| **FIX** || #708 Fall back to Akka classloader if event-handler class cannot be found. || Viktor Klang || +|| **FIX** || #716 Split akka-http and clean-up dependencies || Viktor Klang || +|| **FIX** || #721 Inability to parse/load the Config should do a System.exit(-1) || Viktor Klang || +|| **FIX** || #722 Race condition in Actor hotswapping || Viktor Klang || +|| **FIX** || #723 MessageSerializer CNFE regression || Viktor Klang || +|| **FIX** || #680 Remote TypedActor behavior differs from local one when sending to generic interfaces || Viktor Klang || +|| **FIX** || #659 Calling await on a Future that is expired and uncompleted should throw an exception || Derek Williams || +|| **REM** || #626 Update and clean up dependencies || Viktor Klang || +|| **REM** || #623 Remove embedded-repo (Akka + Akka Modules) || Viktor Klang || +|| **REM** || #686 Remove SBinary || Viktor Klang || + +Release 1.0-RC6 +=============== + +||~ =**Type** + ||~ +===== + +**Changes** + ||~ +===== + +**By**= || +|| **FIX** || #628 Supervied TypedActors fails to restart || Viktor Klang || +|| **FIX** || #629 Stuck upon actor invocation || Viktor Klang || + +Release 1.0-RC5 +=============== + +||~ =**Type** + ||~ +===== + +**Changes** + ||~ +===== + +**By**= || +|| **FIX** || Source JARs published to 'src' instead of 'source' || Odd Moller || +|| **FIX** || #612 Conflict between Spring autostart=true for Consumer actors and || Martin Krasser || +|| **FIX** || #613 Change Akka XML schema URI to http://akka.io/schema/akka || Martin Krasser || +|| **FIX** || Spring XSD namespace changed from 'akkasource.org' to 'akka.io' || Viktor Klang || +|| **FIX** || Checking for remote secure cookie is disabled by default if no akka.conf is loaded || Viktor Klang || +|| **FIX** || Changed Casbah to ScalaToolsRepo for akka-sbt-plugin || Viktor Klang || +|| **FIX** || ActorRef.forward now doesn't require the sender to be set on the message || Viktor Klang || + +Release 1.0-RC3 +=============== + +||~ =**Type** + ||~ +===== + +**Changes** + ||~ +===== + +**By**= || +|| **ADD** || #568 Add autostart attribute to Spring actor configuration || Viktor Klang || +|| **ADD** || #586 Allow explicit reconnect for remote clients || Viktor Klang || +|| **ADD** || #587 Add possibility for dead letter queues for failed remote sends || Viktor Klang || +|| **ADD** || #497 Optimize remote send in local scope || Viktor Klang || +|| **ADD** || Improved Java Actor API: akka.actor.Actors || Viktor Klang || +|| **ADD** || Improved Scala Actor API: akka.actor.Actor || Viktor Klang || +|| **ADD** || #148 Create a testing framework for testing Actors || Roland Kuhn || +|| **ADD** || Support Replica Set/Replica Pair connection modes with MongoDB Persistence || Brendan McAdams || +|| **ADD** || User configurable Write Concern settings for MongoDB Persistence || Brendan McAdams || +|| **ADD** || Support for configuring MongoDB Persistence with MongoDB's URI Connection String || Brendan McAdams || +|| **ADD** || Support for Authentication with MongoDB Persistence || Brendan McAdams || +|| **FIX** || Misc bug fixes || Team || +|| **FIX** || #603 Race condition in Remote send || Viktor Klang || +|| **FIX** || #594 Log statement in RemoteClientHandler was wrongly formatted || Viktor Klang || +|| **FIX** || #580 Message uuids must be generated || Viktor Klang || +|| **FIX** || #583 Serialization classloader has a visibility issue || Viktor Klang || +|| **FIX** || #598 By default the bean ID should become the actor id for Spring actor configuration || Viktor Klang || +|| **FIX** || #577 RemoteClientHandler swallows certain exceptions || Viktor Klang || +|| **FIX** || #581 Fix edgecase where an exception could not be deserialized || Viktor Klang || +|| **FIX** || MongoDB write success wasn't being properly checked; fixed (integrated w/ new write concern features) || Brendan McAdams || +|| **UPD** || Improvements to FSM module akka.actor.FSM || Manie & Kuhn || +|| **UPD** || Changed Akka URI to http://akka.io. Reflects both XSDs, Maven repositories etc. || Jonas Bonér || +|| **REM** || #574 Remote RemoteClient, RemoteServer and RemoteNode || Viktor Klang || +|| **REM** || object UntypedActor, object ActorRegistry, class RemoteActor, class RemoteUntypedActor, class RemoteUntypedConsumerActor || Viktor Klang || + +Release 1.0-RC1 +=============== + +||~ =**Type** + ||~ +===== + +**Changes** + ||~ +===== + +**By**= || +|| **ADD** || #477 Added support for Remote Agents || Viktor Klang || +|| **ADD** || #460 Hotswap for Java API (UntypedActor) || Viktor Klang || +|| **ADD** || #471 Added support for TypedActors to return Java Option || Viktor Klang || +|| **ADD** || New design and API for more fluent and intuitive FSM module || Roland Kuhn || +|| **ADD** || Added secure cookie based remote node authentication || Jonas Bonér || +|| **ADD** || Untrusted safe mode for remote server || Jonas Bonér || +|| **ADD** || Refactored config file format - added list of enabled modules etc. || Jonas Bonér || +|| **ADD** || Docs for Dataflow Concurrency || Jonas Bonér || +|| **ADD** || Made remote message frame size configurable || Jonas Bonér || +|| **ADD** || #496 Detect when Remote Client disconnects || Jonas Bonér || +|| **ADD** || #472 Improve API to wait for endpoint activation/deactivation (`more `_ ...) || Martin Krasser || +|| **ADD** || #473 Allow consumer actors to customize their own routes (`more `_ ...) || Martin Krasser || +|| **ADD** || #504 Add session bound server managed remote actors || Paul Pach || +|| **ADD** || DSL for FSM || Irmo Manie || +|| **ADD** || Shared unit test for all dispatchers to enforce Actor Model || Viktor Klang || +|| **ADD** || #522 Make stacking optional for become and HotSwap || Viktor Klang || +|| **ADD** || #524 Make frame size configurable for client&server || Bonér & Klang || +|| **ADD** || #526 Add onComplete callback to Future || Viktor Klang || +|| **ADD** || #536 Document Channel-abstraction for later replies || Viktor Klang || +|| **ADD** || #540 Include self-reference as parameter to HotSwap || Viktor Klang || +|| **ADD** || #546 Include Garrick Evans' Akka-mist into master || Viktor Klang || +|| **ADD** || #438 Support remove operation in PersistentVector || Scott Clasen || +|| **ADD** || #229 Memcached protocol support for Persistence module || Scott Clasen || +|| **ADD** || Amazon SimpleDb support for Persistence module || Scott Clasen || +|| **FIX** || #518 refactor common storage bakend to use bulk puts/gets where possible || Scott Clasen || +|| **FIX** || #532 Prevent persistent datatypes with same uuid from corrupting a TX || Scott Clasen || +|| **FIX** || #464 ThreadPoolBuilder should be rewritten to be an immutable builder || Viktor Klang || +|| **FIX** || #449 Futures.awaitOne now uses onComplete listeners || Viktor Klang || +|| **FIX** || #486 Fixed memory leak caused by Configgy that prevented full unload || Viktor Klang || +|| **FIX** || #488 Fixed race condition in EBEDD restart || Viktor Klang || +|| **FIX** || #492 Fixed race condition in Scheduler || Viktor Klang || +|| **FIX** || #493 Switched to non-https repository for JBoss artifacts || Viktor Klang || +|| **FIX** || #481 Exception when creating an actor now behaves properly when supervised || Viktor Klang || +|| **FIX** || #498 Fixed no-op in supervision DSL || Viktor Klang || +|| **FIX** || #491 reply and reply_? now sets a sender reference || Viktor Klang || +|| **FIX** || #519 NotSerializableError when using Remote Typed Actors || Viktor Klang || +|| **FIX** || #523 Message.toString is called all the time for incomign messages, expensive || Viktor Klang || +|| **FIX** || #537 Make sure top folder is included in sources jar || Viktor Klang || +|| **FIX** || #529 Remove Scala version number from Akka artifact ids || Viktor Klang || +|| **FIX** || #533 Can't set LifeCycle from the Java API || Viktor Klang || +|| **FIX** || #542 Make Future-returning Remote Typed Actor methods use onComplete || Viktor Klang || +|| **FIX** || #479 Do not register listeners when CamelService is turned off by configuration || Martin Krasser || +|| **FIX** || Fixed bug with finding TypedActor by type in ActorRegistry || Jonas Bonér || +|| **FIX** || #515 race condition in FSM StateTimeout Handling || Irmo Manie || +|| **UPD** || Akka package from "se.scalablesolutions.akka" to "akka" || Viktor Klang || +|| **UPD** || Update Netty to 3.2.3.Final || Viktor Klang || +|| **UPD** || #458 Camel to 2.5.0 || Martin Krasser || +|| **UPD** || #458 Spring to 3.0.4.RELEASE || Martin Krasser || +|| **UPD** || #458 Jetty to 7.1.6.v20100715 || Martin Krasser || +|| **UPD** || Update to Scala 2.8.1 || Jonas Bonér || +|| **UPD** || Changed remote server default port to 2552 (AKKA) || Jonas Bonér || +|| **UPD** || Cleaned up and made remote protocol more effifient || Jonas Bonér || +|| **UPD** || #528 RedisPersistentRef should not throw in case of missing key || Debasish Ghosh || +|| **UPD** || #531 Fix RedisStorage add() method in Java API || Debasish Ghosh || +|| **UPD** || #513 Implement snapshot based persistence control in SortedSet || Debasish Ghosh || +|| **UPD** || #547 Update FSM docs || Irmo Manie || +|| **UPD** || #548 Update AMQP docs || Irmo Manie || +|| **REM** || Atmosphere integration, replace with Mist || Klang @ Evans || +|| **REM** || JGroups integration, doesn't play with cloud services :/ || Viktor Klang || + +Release 1.0-MILESTONE1 +====================== + +||~ =**Type** + ||~ +===== + +**Changes** + ||~ +===== + +**By**= || +|| **ADD** || Splitted akka-core up in akka-actor, akka-typed-actor & akka-remote || Jonas Bonér || +|| **ADD** || Added meta-data to network protocol || Jonas Bonér || +|| **ADD** || HotSwap and actor.become now uses a stack of PartialFunctions with API for pushing and popping the stack || Jonas Bonér || +|| **ADD** || #440 Create typed actors with constructor args || Michael Kober || +|| **ADD** || #322 Abstraction for unification of sender and senderFuture for later reply || Michael Kober || +|| **ADD** || #364 Serialization for TypedActor proxy reference || Michael Kober || +|| **ADD** || #423 Support configuration of Akka via Spring || Michael Kober || +|| **FIX** || #426 UUID wrong for remote proxy for server managed actor || Michael Kober || +|| **ADD** || #378 Support for server initiated remote TypedActor and UntypedActor in Spring config || Michael Kober || +||< **ADD** ||< #194 Support for server-managed typed actor ||< Michael Kober || +|| **ADD** || #447 Allow Camel service to be turned off by configuration || Martin Krasser || +|| **ADD** || #457 JavaAPI improvements for akka-camel (please read the `migration guide `_) || Martin Krasser || +|| **ADD** || #465 Dynamic message routing to actors (`more `_ ...) || Martin Krasser || +|| **FIX** || #410 Use log configuration from config directory || Martin Krasser || +|| **FIX** || #343 Some problems with persistent structures || Debasish Ghosh || +|| **FIX** || #430 Refactor / re-implement MongoDB adapter so that it conforms to the guidelines followed in Redis and Cassandra modules || Debasish Ghosh || +|| **FIX** || #436 ScalaJSON serialization does not map Int data types properly when used within a Map || Debasish Ghosh || +|| **ADD** || #230 Update redisclient to be Redis 2.0 compliant || Debasish Ghosh || +|| **FIX** || #435 Mailbox serialization does not retain messages || Debasish Ghosh || +|| **ADD** || #445 Integrate type class based serialization of sjson into Akka || Debasish Ghosh || +|| **FIX** || #480: Regression multibulk replies redis client || Debasish Ghosh || +|| **FIX** || #415 Publish now generate source and doc jars || Viktor Klang || +|| **FIX** || #420 REST endpoints should be able to be processed in parallel || Viktor Klang || +|| **FIX** || #422 Dispatcher config should work for ThreadPoolBuilder-based dispatchers || Viktor Klang || +|| **FIX** || #401 ActorRegistry should not leak memory || Viktor Klang || +|| **FIX** || #250 Performance optimization for ExecutorBasedEventDrivenDispatcher || Viktor Klang || +|| **FIX** || #419 Rename init and shutdown callbacks to preStart and postStop, and remove initTransactionalState || Viktor Klang || +|| **FIX** || #346 Make max no of restarts (and within) are now both optional || Viktor Klang || +|| **FIX** || #424 Actors self.supervisor not set by the time init() is called when started by startLink() || Viktor Klang || +|| **FIX** || #427 spawnLink and startLink now has the same dispatcher semantics || Viktor Klang || +|| **FIX** || #413 Actor shouldn't process more messages when waiting to be restarted (HawtDispatcher still does) || Viktor Klang || +|| **FIX** || !! and !!! now do now not block the actor when used in remote actor || Viktor Klang || +|| **FIX** || RemoteClient now reconnects properly || Viktor Klang || +|| **FIX** || Logger.warn now properly works with varargs || Viktor Klang || +|| **FIX** || #450 Removed ActorRef lifeCycle boilerplate: Some(LifeCycle(Permanent)) => Permanent || Viktor Klang || +|| **FIX** || Moved ActorRef.trapExit into ActorRef.faultHandler and removed Option-boilerplate from faultHandler || Viktor Klang || +|| **FIX** || ThreadBasedDispatcher cheaper for idling actors, also benefits from all that is ExecutorBasedEventDrivenDispatcher || Viktor Klang || +|| **FIX** || Fixing Futures.future, uses Actor.spawn under the hood, specify dispatcher to control where block is executed || Viktor Klang || +|| **FIX** || #469 Akka "dist" now uses a root folder to avoid loitering if unzipped in a folder || Viktor Klang || +|| **FIX** || Removed ScalaConfig, JavaConfig and rewrote Supervision configuration || Viktor Klang || +|| **UPD** || Jersey to 1.3 || Viktor Klang || +|| **UPD** || Atmosphere to 0.6.2 || Viktor Klang || +|| **UPD** || Netty to 3.2.2.Final || Viktor Klang || +|| **ADD** || Changed config file priority loading and added config modes. || Viktor Klang || +|| **ADD** || #411 Bumped Jetty to v 7 and migrated to it's eclipse packages || Viktor Klang || +|| **ADD** || #414 Migrate from Grizzly to Jetty for Akka Microkernel || Viktor Klang || +|| **ADD** || #261 Add Java API for 'routing' module || VIktor Klang || +|| **ADD** || #262 Add Java API for Agent || Viktor Klang || +|| **ADD** || #264 Add Java API for Dataflow || Viktor Klang || +|| **ADD** || Using JerseySimpleBroadcaster instead of JerseyBroadcaster in AkkaBroadcaster || Viktor Klang || +|| **ADD** || #433 Throughput deadline added for ExecutorBasedEventDrivenDispatcher || Viktor Klang || +|| **ADD** || Add possibility to set default cometSupport in akka.conf || Viktor Klang || +|| **ADD** || #451 Added possibility to use akka-http as a standalone REST server || Viktor Klang || +|| **ADD** || #446 Added support for Erlang-style receiveTimeout || Viktor Klang || +|| **ADD** || #462 Added support for suspend/resume of processing individual actors mailbox, should give clearer restart semantics || Viktor Klang || +|| **ADD** || #466 Actor.spawn now takes an implicit dispatcher to specify who should run the block || Viktor Klang || +|| **ADD** || #456 Added map to Future and Futures.awaitMap || Viktor Klang || +|| **REM** || #418 Remove Lift sample module and docs || Viktor Klang || +|| **REM** || Removed all Reactor-based dispatchers || Viktor Klang || +|| **REM** || Removed anonymous actor factories || Viktor Klang || +|| **ADD** || Voldemort support for akka-persistence || Scott Clasen || +|| **ADD** || HBase support for akka-persistence || David Greco || +|| **ADD** || CouchDB support for akka-persistence || Yung-Luen Lan & Kahlen || +|| **ADD** || #265 Java API for AMQP module || Irmo Manie || + +Release 0.10 - Aug 21 2010 +========================== + +``_ + +||~ =**Type** + ||~ +===== + +**Changes** + ||~ +===== + +**By**= || +||< **ADD** ||< Added new Actor type: UntypedActor for Java API ||< Jonas Bonér || +||< **ADD** ||< #26 Deep serialization of Actor including its mailbox ||< Jonas Bonér || +||< **ADD** ||< Rewritten network protocol. More efficient and cleaner. ||< Jonas Bonér || +||< **ADD** ||< Rewritten Java Active Object tests into Scala to be able to run the in SBT. ||< Jonas Bonér || +||< **ADD** ||< Added isDefinedAt method to Actor for checking if it can receive a certain message ||< Jonas Bonér || +||< **ADD** ||< Added caching of Active Object generated class bytes, huge perf improvement ||< Jonas Bonér || +||< **ADD** ||< Added RemoteClient Listener API ||< Jonas Bonér || +||< **ADD** ||< Added methods to retreive children from a Supervisor ||< Jonas Bonér || +||< **ADD** ||< Rewritten Supervisor to become more clear and "correct" ||< Jonas Bonér || +||< **ADD** ||< Added options to configure a blocking mailbox with custom capacity ||< Jonas Bonér || +||< **ADD** ||< Added RemoteClient reconnection time window configuration option ||< Jonas Bonér || +||< **ADD** ||< Added ActiveObjectContext with sender reference etc ||< Jonas Bonér || +||< **ADD** ||< #293 Changed config format to JSON-style ||< Jonas Bonér || +||< **ADD** ||< #302: Incorporate new ReceiveTimeout in Actor serialization ||< Jonas Bonér || +||< **ADD** ||< Added Java API docs and made it comparable with Scala API docs. 1-1 mirroring ||< Jonas Bonér || +||< **ADD** ||< Renamed Active Object to Typed Actor ||< Jonas Bonér || +||< **ADD** ||< Enhanced Typed Actor: remoting, "real" restart upon failure etc. ||< Jonas Bonér || +||< **ADD** ||< Typed Actor now inherits Actor and is a full citizen in the Actor world. ||< Jonas Bonér || +||< **ADD** ||< Added support for remotely shutting down a remote actor ||< Jonas Bonér || +||< **ADD** ||< #224 Add support for Camel in typed actors (`more `_ ...) ||< Martin Krasser || +||< **ADD** || #282 Producer trait should implement Actor.receive (`more `_ ...) || Martin Krasser || +||< **ADD** || #271 Support for bean scope prototype in akka-spring || Johan Rask || +||< **ADD** || Support for DI of values and bean references on target instance in akka-spring || Johan Rask || +||< **ADD** || #287 Method annotated with @postrestart in ActiveObject is not called during restart || Johan Rask || +|| **ADD** || Support for ApplicationContextAware in akka-spring || Johan Rask || +|| **ADD** || #199 Support shutdown hook in TypedActor || Martin Krasser || +|| **ADD** || #266 Access to typed actors from user-defined Camel routes (`more `_ ...) || Martin Krasser || +|| **ADD** || #268 Revise akka-camel documentation (`more `_ ...) || Martin Krasser || +|| **ADD** || #289 Support for Spring configuration element (`more `_ ...) || Martin Krasser || +|| **ADD** || #296 TypedActor lifecycle management || Martin Krasser || +|| **ADD** || #297 Shutdown routes to typed actors (`more `_ ...) || Martin Krasser || +|| **ADD** || #314 akka-spring to support typed actor lifecycle management (`more `_ ...) || Martin Krasser || +|| **ADD** || #315 akka-spring to support configuration of shutdown callback method (`more `_ ...) || Martin Krasser || +|| **ADD** || Fault-tolerant consumer actors and typed consumer actors (`more `_ ...) || Martin Krasser || +|| **ADD** || #320 Leverage Camel's non-blocking routing engine (`more `_ ...) || Martin Krasser || +|| **ADD** || #335 Producer trait should allow forwarding of results || Martin Krasser || +|| **ADD** || #339 Redesign of Producer trait (pre/post processing hooks, async in-out) (`more `_ ...) || Martin Krasser || +|| **ADD** || Non-blocking, asynchronous routing example for akka-camel (`more `_ ...) || Martin Krasser || +|| **ADD** || #333 Allow applications to wait for endpoints being activated (`more `_ ...) || Martin Krasser || +|| **ADD** || #356 Support @consume annotations on typed actor implementation class || Martin Krasser || +|| **ADD** || #357 Support untyped Java actors as endpoint consumer || Martin Krasser || +|| **ADD** || #366 CamelService should be a singleton || Martin Krasser || +|| **ADD** || #392 Support untyped Java actors as endpoint producer || Martin Krasser || +|| **ADD** || #393 Redesign CamelService singleton to be a CamelServiceManager (`more `_ ...) || Martin Krasser || +|| **ADD** || #295 Refactoring Actor serialization to type classes || Debasish Ghosh || +|| **ADD** || #317 Change documentation for Actor Serialization || Debasish Ghosh || +|| **ADD** || #388 Typeclass serialization of ActorRef/UntypedActor isn't Java friendly || Debasish Ghosh || +|| **ADD** || #292 Add scheduleOnce to Scheduler || Irmo Manie || +|| **ADD** || #308 Initial receive timeout on actor || Irmo Manie || +|| **ADD** || Redesign of AMQP module (`more `_ ...) || Irmo Manie || +|| **ADD** || Added "become(behavior: Option[Receive])" to Actor || Viktor Klang || +|| **ADD** || Added "find[T](f: PartialFunction[ActorRef,T]) : Option[T]" to ActorRegistry || Viktor Klang || +|| **ADD** || #369 Possibility to configure dispatchers in akka.conf || Viktor Klang || +|| **ADD** || #395 Create ability to add listeners to RemoteServer || Viktor Klang || +|| **ADD** || #225 Add possibility to use Scheduler from TypedActor || Viktor Klang || +|| **ADD** || #61 Integrate new persistent datastructures in Scala 2.8 || Peter Vlugter || +|| **ADD** || Expose more of what Multiverse can do || Peter Vlugter || +|| **ADD** || #205 STM transaction settings || Peter Vlugter || +|| **ADD** || #206 STM transaction deferred and compensating || Peter Vlugter || +|| **ADD** || #232 Expose blocking transactions || Peter Vlugter || +|| **ADD** || #249 Expose Multiverse Refs for primitives || Peter Vlugter || +|| **ADD** || #390 Expose transaction propagation level in multiverse || Peter Vlugter || +|| **ADD** || Package objects for importing local/global STM || Peter Vlugter || +|| **ADD** || Java API for the STM || Peter Vlugter || +|| **ADD** || #379 Create STM Atomic templates for Java API || Peter Vlugter || +|| **ADD** || #270 SBT plugin for Akka || Peter Vlugter || +|| **ADD** || #198 support for ThreadBasedDispatcher in Spring config || Michael Kober || +|| **ADD** || #377 support HawtDispatcher in Spring config || Michael Kober || +|| **ADD** || #376 support Spring config for untyped actors || Michael Kober || +|| **ADD** || #200 support WorkStealingDispatcher in Spring config || Michael Kober || +|| **UPD** || #336 RabbitMQ 1.8.1 || Irmo Manie || +|| **UPD** || #288 Netty to 3.2.1.Final || Viktor Klang || +|| **UPD** || Atmosphere to 0.6.1 || Viktor Klang || +|| **UPD** || Lift to 2.8.0-2.1-M1 || Viktor Klang || +|| **UPD** || Camel to 2.4.0 || Martin Krasser || +|| **UPD** || Spring to 3.0.3.RELEASE || Martin Krasser || +|| **UPD** || Multiverse to 0.6 || Peter Vlugter || +|| **FIX** || Fixed bug with stm not being enabled by default when no AKKA_HOME is set || Jonas Bonér || +|| **FIX** || Fixed bug in network manifest serialization || Jonas Bonér || +|| **FIX** || Fixed bug Remote Actors || Jonas Bonér || +|| **FIX** || Fixed memory leak in Active Objects || Jonas Bonér || +|| **FIX** || Fixed indeterministic deadlock in Transactor restart || Jonas Bonér || +|| **FIX** || #325 Fixed bug in STM with dead hanging CountDownCommitBarrier || Jonas Bonér || +|| **FIX** || #316: NoSuchElementException during ActiveObject restart || Jonas Bonér || +|| **FIX** || #256: Tests for ActiveObjectContext || Jonas Bonér || +|| **FIX** || Fixed bug in restart of Actors with 'Temporary' life-cycle || Jonas Bonér || +|| **FIX** || #280 Tests fail if there is no akka.conf set || Jonas Bonér || +|| **FIX** || #286 unwanted transitive dependencies from Geronimo project || Viktor Klang || +|| **FIX** || Atmosphere comet comment to use stream instead of writer || Viktor Klang || +|| **FIX** || #285 akka.conf is now used as defaults for Akka REST servlet init parameters || Viktor Klang || +|| **FIX** || #321 fixed performance regression in ActorRegistry || Viktor Klang || +|| **FIX** || #286 geronimo servlet 2.4 dep is no longer transitively loaded || Viktor Klang || +|| **FIX** || #334 partial lift sample rewrite to fix breakage || Viktor Klang || +|| **FIX** || Fixed a memory leak in ActorRegistry || Viktor Klang || +|| **FIX** || Fixed a race-condition in Cluster || Viktor Klang || +|| **FIX** || #355 Switched to Array instead of List on ActorRegistry return types || Viktor Klang || +|| **FIX** || #352 ActorRegistry.actorsFor(class) now checks isAssignableFrom || Viktor Klang || +|| **FIX** || Fixed a race condition in ActorRegistry.register || Viktor Klang || +|| **FIX** || #337 Switched from Configgy logging to SLF4J, better for OSGi || Viktor Klang || +|| **FIX** || #372 Scheduler now returns Futures to cancel tasks || Viktor Klang || +|| **FIX** || #306 JSON serialization between remote actors is not transparent || Debasish Ghosh || +|| **FIX** || #204 Reduce object creation in STM || Peter Vlugter || +|| **FIX** || #253 Extend Multiverse BasicRef rather than wrap ProgrammaticRef || Peter Vlugter || +|| **REM** || Removed pure POJO-style Typed Actor (old Active Object) || Jonas Bonér || +|| **REM** || Removed Lift as a dependency for Akka-http || Viktor Klang || +|| **REM** || #294 Remove reply and reply_? from Actor || Viktor Klang || +|| **REM** || Removed one field in Actor, should be a minor memory reduction for high actor quantities || Viktor Klang || +|| **FIX** || #301 DI does not work in akka-spring when specifying an interface || Johan Rask || +|| **FIX** || #328 +trapExit should pass through self with Exit to supervisor || Irmo Manie || +|| **FIX** || Fixed warning when deregistering listeners || Martin Krasser || +|| **FIX** || Added camel-jetty-2.4.0.1 to Akka's embedded-repo. +(fixes a concurrency bug in camel-jetty-2.4.0, to be officially released in Camel 2.5.0) || Martin Krasser || +|| **FIX** || #338 RedisStorageBackend fails when redis closes connection to idle client || Debasish Ghosh || +|| **FIX** || #340 RedisStorage Map.get does not throw exception when disconnected from redis but returns None || Debasish Ghosh || + +Release 0.9 - June 2th 2010 +=========================== + +||~ =**Type** + ||~ +===== + +**Changes** + ||~ +===== + +**By**= || +|| || || || +|| **ADD** || Serializable, immutable, network-aware ActorRefs || Jonas Bonér || +|| **ADD** || Optionally JTA-aware STM transactions || Jonas Bonér || +|| **ADD** || Rewritten supervisor management, making use of ActorRef, now really kills the Actor instance and replaces it || Jonas Bonér || +|| **ADD** || Allow linking and unlinking a declaratively configured Supervisor || Jonas Bonér || +|| **ADD** || Remote protocol rewritten to allow passing along sender reference in all situations || Jonas Bonér || +|| **ADD** || #37 API for JTA usage || Jonas Bonér || +|| **ADD** || Added user accessible 'sender' and 'senderFuture' references || Jonas Bonér || +|| **ADD** || Sender actor is now passed along for all message send functions (!, !!, !!!, forward) || Jonas Bonér || +|| **ADD** || Subscription API for listening to RemoteClient failures || Jonas Bonér || +|| **ADD** || Implemented link/unlink for ActiveObjects || Jan Kronquist / Michael Kober || +|| **ADD** || Added alter method to TransactionalRef + added appl(initValue) to Transactional Map/Vector/Ref || Peter Vlugter || +|| **ADD** || Load dependency JARs in JAR deloyed in kernel's ,/deploy dir || Jonas Bonér || +|| **ADD** || Allowing using Akka without specifying AKKA_HOME or path to akka.conf config file || Jonas Bonér || +|| **ADD** || Redisclient now supports PubSub || Debasish Ghosh || +|| **ADD** || Added a sample project under akka-samples for Redis PubSub using Akka actors || Debasish Ghosh || +|| **ADD** || Richer API for Actor.reply || Viktor Klang || +|| **ADD** || Added Listeners to Akka patterns || Viktor Klang || +|| **ADD** || #183 Deactivate endpoints of stopped consumer actors || Martin Krasser || +|| **ADD** || Camel `Message API improvements `_ || Martin Krasser || +|| **ADD** || #83 Send notification to parent supervisor if all actors supervised by supervisor has been permanently killed || Jonas Bonér || +|| **ADD** || #121 Make it possible to dynamically create supervisor hierarchies for Active Objects || Michael Kober || +|| **ADD** || #131 Subscription API for node joining & leaving cluster || Jonas Bonér || +|| **ADD** || #145 Register listener for errors in RemoteClient/RemoteServer || Jonas Bonér || +|| **ADD** || #146 Create an additional distribution with sources || Jonas Bonér || +|| **ADD** || #149 Support loading JARs from META-INF/lib in JARs put into the ./deploy directory || Jonas Bonér || +|| **ADD** || #166 Implement insertVectorStorageEntriesFor in CassandraStorageBackend || Jonas Bonér || +|| **ADD** || #168 Separate ID from Value in Actor; introduce ActorRef || Jonas Bonér || +|| **ADD** || #174 Create sample module for remote actors || Jonas Bonér || +|| **ADD** || #175 Add new sample module with Peter Vlugter's Ant demo || Jonas Bonér || +|| **ADD** || #177 Rewrite remote protocol to make use of new ActorRef || Jonas Bonér || +|| **ADD** || #180 Make use of ActorRef indirection for fault-tolerance management || Jonas Bonér || +|| **ADD** || #184 Upgrade to Netty 3.2.0.CR1 || Jonas Bonér || +|| **ADD** || #185 Rewrite Agent and Supervisor to work with new ActorRef || Jonas Bonér || +|| **ADD** || #188 Change the order of how the akka.conf is detected || Jonas Bonér || +|| **ADD** || #189 Reintroduce 'sender: Option[Actor]' ref in Actor || Jonas Bonér || +|| **ADD** || #203 Upgrade to Scala 2.8 RC2 || Jonas Bonér || +|| **ADD** || #222 Using Akka without AKKA_HOME or akka.conf || Jonas Bonér || +|| **ADD** || #234 Add support for injection and management of ActiveObjectContext with RTTI such as 'sender' and 'senderFuture' references etc. || Jonas Bonér || +|| **ADD** || #236 Upgrade SBinary to Scala 2.8 RC2 || Jonas Bonér || +|| **ADD** || #235 Problem with RedisStorage.getVector(..) data structure storage management || Jonas Bonér || +|| **ADD** || #239 Upgrade to Camel 2.3.0 || Martin Krasser || +|| **ADD** || #242 Upgraded to Scala 2.8 RC3 || Jonas Bonér || +|| **ADD** || #243 Upgraded to Protobuf 2.3.0 || Jonas Bonér || +|| **ADD** || Added option to specify class loader when de-serializing messages and RemoteActorRef in RemoteClient || Jonas Bonér || +|| **ADD** || #238 Upgrading to Cassandra 0.6.1 || Jonas Bonér || +|| **ADD** || Upgraded to Jersey 1.2 || Viktor Klang || +|| **ADD** || Upgraded Atmosphere to 0.6-SNAPSHOT, adding WebSocket support || Viktor Klang || +|| **FIX** || Simplified ActiveObject configuration || Michael Kober || +|| **FIX** || #237 Upgrade Mongo Java driver to 1.4 (the latest stable release) || Debasish Ghosh || +|| **FIX** || #165 Implemented updateVectorStorageEntryFor in Mongo persistence module || Debasish Ghosh || +|| **FIX** || #154: Allow ActiveObjects to use the default timeout in config file || Michael Kober || +|| **FIX** || Active Object methods with @inittransactionalstate should be invoked automatically || Michael Kober || +|| **FIX** || Nested supervisor hierarchy failure propagation bug fixed || Jonas Bonér || +|| **FIX** || Fixed bug on CommitBarrier transaction registration || Jonas Bonér || +|| **FIX** || Merged many modules to reduce total number of modules || Viktor Klang || +|| **FIX** || Future parameterized || Viktor Klang || +|| **FIX** || #191: Workstealing dispatcher didn't work with !! || Viktor Klang || +|| **FIX** || #202: Allow applications to disable stream-caching || Martin Krasser || +|| **FIX** || #119 Problem with Cassandra-backed Vector || Jonas Bonér || +|| **FIX** || #147 Problem replying to remote sender when message sent with ! || Jonas Bonér || +|| **FIX** || #171 initial value of Ref can become null if first transaction rolled back || Jonas Bonér || +|| **FIX** || #172 Fix "broken" Protobuf serialization API || Jonas Bonér || +|| **FIX** || #173 Problem with Vector::slice in CassandraStorage || Jonas Bonér || +|| **FIX** || #190 RemoteClient shutdown ends up in endless loop || Jonas Bonér || +|| **FIX** || #211 Problem with getting CommitBarrierOpenException when using Transaction.Global || Jonas Bonér || +|| **FIX** || #240 Supervised actors not started when starting supervisor || Jonas Bonér || +|| **FIX** || Fixed problem with Transaction.Local not committing to persistent storage || Jonas Bonér || +|| **FIX** || #215: Re-engineered the JAX-RS support || Viktor Klang || +|| **FIX** || Many many bug fixes || Team || +|| **REM** || Shoal cluster module || Viktor Klang || + +Release 0.8.1 - April 6th 2010 +============================== + +||~ =**Type** + ||~ +===== + +**Changes** + ||~ +===== + +**By**= || +|| || || || +|| **ADD** || Redis cluster support || Debasish Ghosh || +|| **ADD** || Reply to remote sender from message set with ! || Jonas Bonér || +|| **ADD** || Load-balancer which prefers actors with few messages in mailbox || Jan Van Besien || +|| **ADD** || Added developer mailing list: [akka-dev AT googlegroups DOT com] || Jonas Bonér || +|| **FIX** || Separated thread-local from thread-global transaction API || Jonas Bonér || +|| **FIX** || Fixed bug in using STM outside Actors || Jonas Bonér || +|| **FIX** || Fixed bug in anonymous actors || Jonas Bonér || +|| **FIX** || Moved web initializer to new akka-servlet module || Viktor Klang || + +Release 0.8 - March 31st 2010 +============================= + +||~ =**Type** + ||~ +===== + +**Changes** + ||~ +===== + +**By**= || +|| || || || +|| **ADD** || Scala 2.8 based || Viktor Klang || +|| **ADD** || Monadic API for Agents || Jonas Bonér || +|| **ADD** || Agents are transactional || Jonas Bonér || +|| **ADD** || Work-stealing dispatcher || Jan Van Besien || +|| **ADD** || Improved Spring integration || Michael Kober || +|| **FIX** || Various bugfixes || Team || +|| **FIX** || Improved distribution packaging || Jonas Bonér || +|| **REMOVE** || Actor.send function || Jonas Bonér || + +Release 0.7 - March 21st 2010 +============================= + +||~ =**Type** + ||~ +===== + +**Changes** + ||~ +===== + +**By**= || +|| || || || +|| **ADD** || Rewritten STM now works generically with fire-forget message flows || Jonas Bonér || +|| **ADD** || Apache Camel integration || Martin Krasser || +|| **ADD** || Spring integration || Michael Kober || +|| **ADD** || Server-managed Remote Actors || Jonas Bonér || +|| **ADD** || Clojure-style Agents || Viktor Klang || +|| **ADD** || Shoal cluster backend || Viktor Klang || +|| **ADD** || Redis-based transactional queue storage backend || Debasish Ghosh || +|| **ADD** || Redis-based transactional sorted set storage backend || Debasish Ghosh || +|| **ADD** || Redis-based atomic INC (index) operation || Debasish Ghosh || +|| **ADD** || Distributed Comet || Viktor Klang || +|| **ADD** || Project moved to SBT (simple-build-tool) || Peter Hausel || +|| **ADD** || Futures object with utility methods for Future's || Jonas Bonér || +|| **ADD** || !!! function that returns a Future || Jonas Bonér || +|| **ADD** || Richer ActorRegistry API || Jonas Bonér || +|| **FIX** || Improved event-based dispatcher performance with 40% || Jan Van Besien || +|| **FIX** || Improved remote client pipeline performance || Viktor Klang || +|| **FIX** || Support several Clusters on the same network || Viktor Klang || +|| **FIX** || Structural package refactoring || Jonas Bonér || +|| **FIX** || Various bugs fixed || Team || + +Release 0.6 - January 5th 2010 +============================== + +||~ =**Type** + ||~ +===== + +**Changes** + ||~ +===== + +**By**= || +|| || || || +|| **ADD** || Clustered Comet using Akka remote actors and clustered membership API || Viktor Klang || +|| **ADD** || Cluster membership API and implementation based on JGroups || Viktor Klang || +|| **ADD** || Security module for HTTP-based authentication and authorization || Viktor Klang || +|| **ADD** || Support for using Scala XML tags in RESTful Actors (scala-jersey) || Viktor Klang || +|| **ADD** || Support for Comet Actors using Atmosphere || Viktor Klang || +|| **ADD** || MongoDB as Akka storage backend || Debasish Ghosh || +|| **ADD** || Redis as Akka storage backend || Debasish Ghosh || +|| **ADD** || Transparent JSON serialization of Scala objects based on SJSON || Debasish Ghosh || +|| **ADD** || Kerberos/SPNEGO support for Security module || Eckhart Hertzler || +|| **ADD** || Implicit sender for remote actors: Remote actors are able to use reply to answer a request || Mikael Högqvist || +|| **ADD** || Support for using the Lift Web framework with Actors || Tim Perrett || +|| **ADD** || Added CassandraSession API (with socket pooling) wrapping Cassandra's Thrift API in Scala and Java APIs || Jonas Bonér || +|| **ADD** || Rewritten STM, now integrated with Multiverse STM || Jonas Bonér || +|| **ADD** || Added STM API for atomic {..} and run {..} orElse {..} || Jonas Bonér || +|| **ADD** || Added STM retry || Jonas Bonér || +|| **ADD** || AMQP integration; abstracted as actors in a supervisor hierarchy. Impl AMQP 0.9.1 || Jonas Bonér || +|| **ADD** || Complete rewrite of the persistence transaction management, now based on Unit of Work and Multiverse STM || Jonas Bonér || +|| **ADD** || Monadic API to TransactionalRef (use it in for-comprehension) || Jonas Bonér || +|| **ADD** || Lightweight actor syntax using one of the Actor.actor(..) methods. F.e: 'val a = actor { case _ => .. }' || Jonas Bonér || +|| **ADD** || Rewritten event-based dispatcher which improved perfomance by 10x, now substantially faster than event-driven Scala Actors || Jonas Bonér || +|| **ADD** || New Scala JSON parser based on sjson || Jonas Bonér || +|| **ADD** || Added zlib compression to remote actors || Jonas Bonér || +|| **ADD** || Added implicit sender reference for fire-forget ('!') message sends || Jonas Bonér || +|| **ADD** || Monadic API to TransactionalRef (use it in for-comprehension) || Jonas Bonér || +|| **ADD** || Smoother web app integration; just add akka.conf to the classpath (WEB-INF/classes), no need for AKKA_HOME or -Dakka.conf=.. || Jonas Bonér || +|| **ADD** || Modularization of distribution into a thin core (actors, remoting and STM) and the rest in submodules || Jonas Bonér || +|| **ADD** || Added 'forward' to Actor, forwards message but keeps original sender address || Jonas Bonér || +|| **ADD** || JSON serialization for Java objects (using Jackson) || Jonas Bonér || +|| **ADD** || JSON serialization for Scala objects (using SJSON) || Jonas Bonér || +|| **ADD** || Added implementation for remote actor reconnect upon failure || Jonas Bonér || +|| **ADD** || Protobuf serialization for Java and Scala objects || Jonas Bonér || +|| **ADD** || SBinary serialization for Scala objects || Jonas Bonér || +|| **ADD** || Protobuf as remote protocol || Jonas Bonér || +|| **ADD** || Updated Cassandra integration and CassandraSession API to v0.4 || Jonas Bonér || +|| **ADD** || CassandraStorage is now works with external Cassandra cluster || Jonas Bonér || +|| **ADD** || ActorRegistry for retrieving Actor instances by class name and by id || Jonas Bonér || +|| **ADD** || SchedulerActor for scheduling periodic tasks || Jonas Bonér || +|| **ADD** || Now start up kernel with 'java -jar dist/akka-0.6.jar' || Jonas Bonér || +|| **ADD** || Added Akka user mailing list: akka-user AT googlegroups DOT com]] || Jonas Bonér || +|| **ADD** || Improved and restructured documentation || Jonas Bonér || +|| **ADD** || New URL: http://akkasource.org || Jonas Bonér || +|| **ADD** || New and much improved docs || Jonas Bonér || +|| **ADD** || Enhanced trapping of failures: 'trapExit = List(classOf[..], classOf[..])' || Jonas Bonér || +|| **ADD** || Upgraded to Netty 3.2, Protobuf 2.2, ScalaTest 1.0, Jersey 1.1.3, Atmosphere 0.4.1, Cassandra 0.4.1, Configgy 1.4 || Jonas Bonér || +|| **FIX** || Lowered actor memory footprint; now an actor consumes ~600 bytes, which mean that you can create 6.5 million on 4 G RAM || Jonas Bonér || +|| **FIX** || Remote actors are now defined by their UUID (not class name) || Jonas Bonér || +|| **FIX** || Fixed dispatcher bugs || Jonas Bonér || +|| **FIX** || Cleaned up Maven scripts and distribution in general || Jonas Bonér || +|| **FIX** || Fixed many many bugs and minor issues || Jonas Bonér || +|| **FIX** || Fixed inconsistencies and uglyness in Actors API || Jonas Bonér || +|| **REMOVE** || Removed concurrent mode || Jonas Bonér || +|| **REMOVE** || Removed embedded Cassandra mode || Jonas Bonér || +|| **REMOVE** || Removed the !? method in Actor (synchronous message send, since it's evil. Use !! with time-out instead. || Jonas Bonér || +|| **REMOVE** || Removed startup scripts and lib dir || Jonas Bonér || +|| **REMOVE** || Removed the 'Transient' life-cycle scope since to close to 'Temporary' in semantics. || Jonas Bonér || +|| **REMOVE** || Removed 'Transient' Actors and restart timeout || Jonas Bonér || diff --git a/akka-docs/pending/remote-actors-java.rst b/akka-docs/pending/remote-actors-java.rst new file mode 100644 index 0000000000..0857ae41b7 --- /dev/null +++ b/akka-docs/pending/remote-actors-java.rst @@ -0,0 +1,617 @@ +Remote Actors (Java) +==================== + +Module stability: **SOLID** + +Akka supports starting UntypedActors and TypedActors on remote nodes using a very efficient and scalable NIO implementation built upon `JBoss Netty `_ and `Google Protocol Buffers `_ . + +The usage is completely transparent both in regards to sending messages and error handling and propagation as well as supervision, linking and restarts. You can send references to other Actors as part of the message. + +**WARNING**: For security reasons, do not run an Akka node with a Remote Actor port reachable by untrusted connections unless you have supplied a classloader that restricts access to the JVM. + +Managing the Remote Service +--------------------------- + +Starting remote service in user code as a library +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Here is how to start up the server and specify the hostname and port programatically: + +.. code-block:: java + + import static akka.actor.Actors.*; + + remote().start("localhost", 2552); + + // Specify the classloader to use to load the remote class (actor) + remote().start("localhost", 2552, classLoader); + +Here is how to start up the server and specify the hostname and port in the ‘akka.conf’ configuration file (see the section below for details): + +.. code-block:: java + + import static akka.actor.Actors.*; + + remote().start(); + + // Specify the classloader to use to load the remote class (actor) + remote().start(classLoader); + +Starting remote service as part of the stand-alone Kernel +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You simply need to make sure that the service is turned on in the external ‘akka.conf’ configuration file. + +.. code-block:: ruby + + akka { + remote { + server { + service = on + hostname = "localhost" + port = 2552 + connection-timeout = 1000 # in millis + } + } + } + +Stopping the server +^^^^^^^^^^^^^^^^^^^ + +.. code-block:: java + + import static akka.actor.Actors.*; + + remote().shutdown(); + +Connecting and shutting down a client connection explicitly +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Normally you should not have to start and stop the client connection explicitly since that is handled by Akka on a demand basis. But if you for some reason want to do that then you can do it like this: + +.. code-block:: java + + import static akka.actor.Actors.*; + import java.net.InetSocketAddress; + + remote().shutdownClientConnection(new InetSocketAddress("localhost", 6666)); //Returns true if successful, else false + remote().restartClientConnection(new InetSocketAddress("localhost", 6666)); //Returns true if successful, else false + +Client message frame size configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can define the max message frame size for the remote messages: + +.. code-block:: ruby + + akka { + remote { + client { + message-frame-size = 1048576 + } + } + } + +Client reconnect configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Client automatically performs reconnection upon connection failure. + +You can configure it like this: + +.. code-block:: ruby + + akka { + remote { + client { + reconnect-delay = 5000 # in millis (5 sec default) + read-timeout = 10000 # in millis (10 sec default) + reconnection-time-window = 600 # the maximum time window that a client should try to reconnect for + } + } + } + +The client will automatically trying to reconnect to the server if the connection is broken. By default it has a reconnection window of 10 minutes (600 seconds). + +If it has not been able to reconnect during this period of time then it is shut down and further attempts to use it will yield a 'RemoteClientException'. The 'RemoteClientException' contains the message as well as a reference to the address that is not yet connect in order for you to retrieve it an do an explicit connect if needed. + +You can also register a listener that will listen for example the 'RemoteClientStopped' event, retrieve the address that got disconnected and reconnect explicitly. + +See the section on client listener and events below for details. + +Remote Client message buffering and send retry on failure +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Remote Client implements message buffering on network failure. This feature has zero overhead (even turned on) in the successful scenario and a queue append operation in case of unsuccessful send. So it is really really fast. + +The default behavior is that the remote client will maintain a transaction log of all messages that it has failed to send due to network problems (not other problems like serialization errors etc.). The client will try to resend these messages upon first successful reconnect and the message ordering is maintained. This means that the remote client will swallow all exceptions due to network failure and instead queue remote messages in the transaction log. The failures will however be reported through the remote client life-cycle events as well as the regular Akka event handler. You can turn this behavior on and off in the configuration file. It gives 'at-least-once' semantics, use a message id/counter for discarding potential duplicates (or use idempotent messages). + +.. code-block:: ruby + + akka { + remote { + client { + buffering { + retry-message-send-on-failure = on + capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) + # If positive then a bounded mailbox is used and the capacity is set using the property + } + } + } + } + +If you choose a capacity higher than 0, then a bounded queue will be used and if the limit of the queue is reached then a 'RemoteClientMessageBufferException' will be thrown. + +You can also get an Array with all the messages that the remote client has failed to send. Since the remote client events passes you an instance of the RemoteClient you have an easy way to act upon failure and do something with these messages (while waiting for them to be retried). + +.. code-block:: java + + Object[] pending = Actors.remote().pendingMessages(); + +Running Remote Server in untrusted mode +--------------------------------------- + +You can run the remote server in untrusted mode. This means that the server will not allow any client-managed remote actors or any life-cycle messages and methods. This is useful if you want to let untrusted clients use server-managed actors in a safe way. This can optionally be combined with the secure cookie authentication mechanism described below as well as the SSL support for remote actor communication. + +If the client is trying to perform one of these unsafe actions then a 'java.lang.SecurityException' is thrown on the server as well as transferred to the client and thrown there as well. + +Here is how you turn it on: + +.. code-block:: ruby + + akka { + remote { + server { + untrusted-mode = on # the default is 'off' + } + } + } + +The messages that it prevents are all that extends 'LifeCycleMessage': +* case class HotSwap(..) +* case object RevertHotSwap +* case class Restart(..) +* case class Exit(..) +* case class Link(..) +* case class Unlink(..) +* case class UnlinkAndStop(..) +* case object ReceiveTimeout + +It also prevents the client from invoking any life-cycle and side-effecting methods, such as: +* start +* stop +* link +* unlink +* spawnLink +* etc. + +Using secure cookie for remote client authentication +---------------------------------------------------- + +Akka is using a similar scheme for remote client node authentication as Erlang; using secure cookies. In order to use this authentication mechanism you have to do two things: + +* Enable secure cookie authentication in the remote server +* Use the same secure cookie on all the trusted peer nodes + +Enabling secure cookie authentication +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The first one is done by enabling the secure cookie authentication in the remote server section in the configuration file: + +.. code-block:: ruby + + akka { + remote { + server { + require-cookie = on + } + } + +Now if you have try to connect to a server from a client then it will first try to authenticate the client by comparing the secure cookie for the two nodes. If they are the same then it allows the client to connect and use the server freely but if they are not the same then it will throw a 'java.lang.SecurityException' and not allow the client to connect. + +Generating and using the secure cookie +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The secure cookie can be any string value but in order to ensure that it is secure it is best to randomly generate it. This can be done by invoking the 'generate_config_with_secure_cookie.sh' script which resides in the '$AKKA_HOME/scripts' folder. This script will generate and print out a complete 'akka.conf' configuration file with the generated secure cookie defined that you can either use as-is or cut and paste the 'secure-cookie' snippet. Here is an example of its generated output: + +.. code-block:: ruby + + # This config imports the Akka reference configuration. + include "akka-reference.conf" + + # In this file you can override any option defined in the 'akka-reference.conf' file. + # Copy in all or parts of the 'akka-reference.conf' file and modify as you please. + + akka { + remote { + secure-cookie = "000E02050F0300040C050C0D060A040306090B0C" + } + } + +The simplest way to use it is to have it create your 'akka.conf' file like this: + +.. code-block:: ruby + + cd $AKKA_HOME + ./scripts/generate_config_with_secure_cookie.sh > ./config/akka.conf + +Now it is good to make sure that the configuration file is only accessible by the owner of the file. On Unix-style file system this can be done like this: + +.. code-block:: ruby + + chmod 400 ./config/akka.conf + +Running this script requires having 'scala' on the path (and will take a couple of seconds to run since it is using Scala and has to boot up the JVM to run). + +You can also generate the secure cookie by using the 'Crypt' object and its 'generateSecureCookie' method. + +.. code-block:: scala + + import akka.util.Crypt; + + String secureCookie = Crypt.generateSecureCookie(); + +The secure cookie is a cryptographically secure randomly generated byte array turned into a SHA-1 hash. + +Remote Actors +------------- + +Akka has two types of remote actors: + +* Client-initiated and managed. Here it is the client that creates the remote actor and "moves it" to the server. +* Server-initiated and managed. Here it is the server that creates the remote actor and the client can ask for a handle to this actor. + +They are good for different use-cases. The client-initiated are great when you want to monitor an actor on another node since it allows you to link to it and supervise it using the regular supervision semantics. They also make RPC completely transparent. The server-initiated, on the other hand, are great when you have a service running on the server that you want clients to connect to, and you want full control over the actor on the server side for security reasons etc. + +Client-managed Remote UntypedActor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +DEPRECATED AS OF 1.1 + +When you define an actors as being remote it is instantiated as on the remote host and your local actor becomes a proxy, it works as a handle to the remote actor. The real execution is always happening on the remote node. + +Here is an example: + +.. code-block:: java + + import akka.actor.UntypedActor; + import static akka.actor.Actors.*; + + class MyActor extends UntypedActor { + public void onReceive(Object message) throws Exception { + ... + } + } + + //How to make it client-managed: + remote().actorOf(MyActor.class,"192.68.23.769", 2552); + +An UntypedActor can also start remote child Actors through one of the “spawn/link” methods. These will start, link and make the UntypedActor remote atomically. + +.. code-block:: java + + ... + getContext().spawnRemote(MyActor.class, hostname, port); + getContext().spawnLinkRemote(MyActor.class, hostname, port, timeoutInMsForFutures); + ... + +Server-managed Remote UntypedActor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Server side setup +***************** + +The API for server managed remote actors is really simple. 2 methods only: + +.. code-block:: java + + class MyActor extends UntypedActor { + public void onReceive(Object message) throws Exception { + ... + } + } + Actors.remote().start("localhost", 2552).register("hello-service", Actors.actorOf(HelloWorldActor.class); + +Actors created like this are automatically started. + +You can also register an actor by its UUD rather than ID or handle. This is done by prefixing the handle with the "uuid:" protocol. + +.. code-block:: scala + + server.register("uuid:" + actor.uuid, actor); + + server.unregister("uuid:" + actor.uuid); + +Client side usage +***************** + +.. code-block:: java + + ActorRef actor = Actors.remote().actorFor("hello-service", "localhost", 2552); + actor.sendOneWay("Hello"); + +There are many variations on the 'remote()#actorFor' method. Here are some of them: + +.. code-block:: java + + ... = actorFor(className, hostname, port); + ... = actorFor(className, timeout, hostname, port); + ... = actorFor(uuid, className, hostname, port); + ... = actorFor(uuid, className, timeout, hostname, port); + ... // etc + +All of these also have variations where you can pass in an explicit 'ClassLoader' which can be used when deserializing messages sent from the remote actor. + +Client-managed Remote TypedActor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +DEPRECATED AS OF 1.1 + +Remote Typed Actors are created through the 'TypedActor.newRemoteInstance' factory method. + +.. code-block:: java + + MyPOJO remoteActor = (MyPOJO)TypedActor.newRemoteInstance(MyPOJO.class, MyPOJOImpl.class, , "localhost", 2552); + +And if you want to specify the timeout: + +.. code-block:: java + + MyPOJO remoteActor = (MyPOJO)TypedActor.newRemoteInstance(MyPOJO.class, MyPOJOImpl.class, timeout, "localhost", 2552); + +You can also define the Typed Actor to be a client-managed-remote service by adding the ‘RemoteAddress’ configuration element in the declarative supervisor configuration: + +.. code-block:: java + + new Component( + Foo.class, + FooImpl.class, + new LifeCycle(new Permanent(), 1000), + 1000, + new RemoteAddress("localhost", 2552)) + +Server-managed Remote TypedActor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +WARNING: Remote TypedActors do not work with overloaded methods on your TypedActor, refrain from using overloading. + +Server side setup +***************** + +The API for server managed remote typed actors is nearly the same as for untyped actor: + +.. code-block:: java + + import static akka.actor.Actors.*; + remote().start("localhost", 2552); + + RegistrationService typedActor = TypedActor.newInstance(RegistrationService.class, RegistrationServiceImpl.class, 2000); + remote().registerTypedActor("user-service", typedActor); + +Client side usage + +.. code-block:: java + + import static akka.actor.Actors.*; + RegistrationService actor = remote().typedActorFor(RegistrationService.class, "user-service", 5000L, "localhost", 2552); + actor.registerUser(...); + +There are variations on the 'remote()#typedActorFor' method. Here are some of them: + +.. code-block:: java + + ... = typedActorFor(interfaceClazz, serviceIdOrClassName, hostname, port); + ... = typedActorFor(interfaceClazz, serviceIdOrClassName, timeout, hostname, port); + ... = typedActorFor(interfaceClazz, serviceIdOrClassName, timeout, hostname, port, classLoader); + +Session bound server side setup +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Session bound server managed remote actors work by creating and starting a new actor for every client that connects. Actors are stopped automatically when the client disconnects. The client side is the same as regular server managed remote actors. Use the function registerPerSession instead of register. + +Session bound actors are useful if you need to keep state per session, e.g. username. They are also useful if you need to perform some cleanup when a client disconnects by overriding the postStop method as described `here `_ + +.. code-block:: java + + import static akka.actor.Actors.*; + class HelloWorldActor extends Actor { + ... + } + + remote().start("localhost", 2552); + + remote().registerPerSession("hello-service", new Creator[ActorRef]() { + public ActorRef create() { + return actorOf(HelloWorldActor.class); + } + }) + +Note that the second argument in registerPerSession is a Creator, it means that the create method will create a new ActorRef each invocation. +It will be called to create an actor every time a session is established. + +Client side usage +^^^^^^^^^^^^^^^^^ + +.. code-block:: java + + import static akka.actor.Actors.*; + ActorRef actor = remote().actorFor("hello-service", "localhost", 2552); + + Object result = actor.sendRequestReply("Hello"); + +There are many variations on the 'remote()#actorFor' method. Here are some of them: + +.. code-block:: java + + ... = actorFor(className, hostname, port); + ... = actorFor(className, timeout, hostname, port); + ... = actorFor(uuid, className, hostname, port); + ... = actorFor(uuid, className, timeout, hostname, port); + ... // etc + +Automatic remote 'sender' reference management +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Akka is automatically remote-enabling the sender Actor reference for you in order to allow the receiver to respond to the message using 'getContext().getSender().sendOneWay(msg);' or 'getContext().reply(msg);'. By default it is registering the sender reference in the remote server with the 'hostname' and 'port' from the akka.conf configuration file. The default is "localhost" and 2552 and if there is no remote server with this hostname and port then it creates and starts it. + +Identifying remote actors +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The 'id' field in the 'Actor' class is of importance since it is used as identifier for the remote actor. If you want to create a brand new actor every time you instantiate a remote actor then you have to set the 'id' field to a unique 'String' for each instance. If you want to reuse the same remote actor instance for each new remote actor (of the same class) you create then you don't have to do anything since the 'id' field by default is equal to the name of the actor class. + +Here is an example of overriding the 'id' field: + +.. code-block:: java + + import akka.util.UUID; + + class MyActor extends UntypedActor { + public MyActor() { + getContext().setId(UUID.newUuid().toString()); + } + + public void onReceive(Object message) throws Exception { + ... + } + } + +Data Compression Configuration +------------------------------ + +Akka uses compression to minimize the size of the data sent over the wire. Currently it only supports 'zlib' compression but more will come later. + +You can configure it like this: + +.. code-block:: ruby + + akka { + remote { + compression-scheme = "zlib" # Options: "zlib" (lzf to come), leave out for no compression + zlib-compression-level = 6 # Options: 0-9 (1 being fastest and 9 being the most compressed), default is 6 + + ... + } + } + +Subscribe to Remote Client events +--------------------------------- + +Akka has a subscription API for remote client events. You can register an Actor as a listener and this actor will have to be able to process these events: + +RemoteClientError { Throwable cause; RemoteClientModule client; InetSocketAddress remoteAddress; } +RemoteClientDisconnected { RemoteClientModule client; InetSocketAddress remoteAddress; } +RemoteClientConnected { RemoteClientModule client; InetSocketAddress remoteAddress; } +RemoteClientStarted { RemoteClientModule client; InetSocketAddress remoteAddress; } +RemoteClientShutdown { RemoteClientModule client; InetSocketAddress remoteAddress; } +RemoteClientWriteFailed { Object message; Throwable cause; RemoteClientModule client; InetSocketAddress remoteAddress; } + +So a simple listener actor can look like this: + +.. code-block:: java + + class Listener extends UntypedActor { + + public void onReceive(Object message) throws Exception { + if (message instanceof RemoteClientError) { + RemoteClientError event = (RemoteClientError)message; + Exception cause = event.getCause(); + ... + } else if (message instanceof RemoteClientConnected) { + RemoteClientConnected event = (RemoteClientConnected)message; + ... + } else if (message instanceof RemoteClientDisconnected) { + RemoteClientDisconnected event = (RemoteClientDisconnected)message; + ... + } else if (message instanceof RemoteClientStarted) { + RemoteClientStarted event = (RemoteClientStarted)message; + ... + } else if (message instanceof RemoteClientShutdown) { + RemoteClientShutdown event = (RemoteClientShutdown)message; + ... + } else if (message instanceof RemoteClientWriteFailed) { + RemoteClientWriteFailed event = (RemoteClientWriteFailed)message; + ... + } + } + } + +Registration and de-registration can be done like this: + +.. code-block:: java + + ActorRef listener = Actors.actorOf(Listener.class); + ... + Actors.remote().addListener(listener); + ... + Actors.remote().removeListener(listener); + +Subscribe to Remote Server events +--------------------------------- + +Akka has a subscription API for the server events. You can register an Actor as a listener and this actor will have to be able to process these events: + +RemoteServerStarted { RemoteServerModule server; } +RemoteServerShutdown { RemoteServerModule server; } +RemoteServerError { Throwable cause; RemoteServerModule server; } +RemoteServerClientConnected { RemoteServerModule server; Option clientAddress; } +RemoteServerClientDisconnected { RemoteServerModule server; Option clientAddress; } +RemoteServerClientClosed { RemoteServerModule server; Option clientAddress; } +RemoteServerWriteFailed { Object request; Throwable cause; RemoteServerModule server; Option clientAddress; } + +So a simple listener actor can look like this: + +.. code-block:: java + + class Listener extends UntypedActor { + + public void onReceive(Object message) throws Exception { + if (message instanceof RemoteServerError) { + RemoteServerError event = (RemoteServerError)message; + Exception cause = event.getCause(); + ... + } else if (message instanceof RemoteServerStarted) { + RemoteServerStarted event = (RemoteServerStarted)message; + ... + } else if (message instanceof RemoteServerShutdown) { + RemoteServerShutdown event = (RemoteServerShutdown)message; + ... + } else if (message instanceof RemoteServerClientConnected) { + RemoteServerClientConnected event = (RemoteServerClientConnected)message; + ... + } else if (message instanceof RemoteServerClientDisconnected) { + RemoteServerClientDisconnected event = (RemoteServerClientDisconnected)message; + ... + } else if (message instanceof RemoteServerClientClosed) { + RemoteServerClientClosed event = (RemoteServerClientClosed)message; + ... + } else if (message instanceof RemoteServerWriteFailed) { + RemoteServerWriteFailed event = (RemoteServerWriteFailed)message; + ... + } + } + } + +Registration and de-registration can be done like this: + +.. code-block:: java + + import static akka.actor.Actors.*; + + ActorRef listener = actorOf(Listener.class); + ... + remote().addListener(listener); + ... + remote().removeListener(listener); + +Message Serialization +--------------------- + +All messages that are sent to remote actors needs to be serialized to binary format to be able to travel over the wire to the remote node. This is done by letting your messages extend one of the traits in the 'akka.serialization.Serializable' object. If the messages don't implement any specific serialization trait then the runtime will try to use standard Java serialization. + +Read more about that in the `Serialization section `_. + +Code provisioning +----------------- + +Akka does currently not support automatic code provisioning but requires you to have the remote actor class files available on both the "client" the "server" nodes. +This is something that will be addressed soon. Until then, sorry for the inconvenience. diff --git a/akka-docs/pending/remote-actors-scala.rst b/akka-docs/pending/remote-actors-scala.rst new file mode 100644 index 0000000000..19be8d78f5 --- /dev/null +++ b/akka-docs/pending/remote-actors-scala.rst @@ -0,0 +1,722 @@ +Remote Actors (Scala) +===================== + +Module stability: **SOLID** + +Akka supports starting Actors and Typed Actors on remote nodes using a very efficient and scalable NIO implementation built upon `JBoss Netty `_ and `Google Protocol Buffers `_ . + +The usage is completely transparent both in regards to sending messages and error handling and propagation as well as supervision, linking and restarts. You can send references to other Actors as part of the message. + +You can find a runnable sample `here `_. + +Starting up the remote service +------------------------------ + +Starting remote service in user code as a library +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Here is how to start up the RemoteNode and specify the hostname and port programatically: + +.. code-block:: scala + + import akka.actor.Actor._ + + remote.start("localhost", 2552) + + // Specify the classloader to use to load the remote class (actor) + remote.start("localhost", 2552, classLoader) + +Here is how to start up the RemoteNode and specify the hostname and port in the 'akka.conf' configuration file (see the section below for details): + +.. code-block:: scala + + import akka.actor.Actor._ + + remote.start + + // Specify the classloader to use to load the remote class (actor) + remote.start(classLoader) + +Starting remote service as part of the stand-alone Kernel +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You simply need to make sure that the service is turned on in the external 'akka.conf' configuration file. + +.. code-block:: ruby + + akka { + remote { + server { + service = on + hostname = "localhost" + port = 2552 + connection-timeout = 1000 # in millis + } + } + } + +Stopping a RemoteNode or RemoteServer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you invoke 'shutdown' on the server then the connection will be closed. + +.. code-block:: scala + + import akka.actor.Actor._ + + remote.shutdown + +Connecting and shutting down a client explicitly +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Normally you should not have to start and stop the client connection explicitly since that is handled by Akka on a demand basis. But if you for some reason want to do that then you can do it like this: + +.. code-block:: scala + + import akka.actor.Actor._ + + remote.shutdownClientConnection(new InetSocketAddress("localhost", 6666)) //Returns true if successful, false otherwise + remote.restartClientConnection(new InetSocketAddress("localhost", 6666)) //Returns true if successful, false otherwise + +Remote Client message frame size configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can define the max message frame size for the remote messages: + +.. code-block:: ruby + + akka { + remote { + client { + message-frame-size = 1048576 + } + } + } + +Remote Client reconnect configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Remote Client automatically performs reconnection upon connection failure. + +You can configure it like this: + +.. code-block:: ruby + + akka { + remote { + client { + reconnect-delay = 5000 # in millis (5 sec default) + read-timeout = 10000 # in millis (10 sec default) + reconnection-time-window = 600 # the maximum time window that a client should try to reconnect for + } + } + } + +The RemoteClient is automatically trying to reconnect to the server if the connection is broken. By default it has a reconnection window of 10 minutes (600 seconds). + +If it has not been able to reconnect during this period of time then it is shut down and further attempts to use it will yield a 'RemoteClientException'. The 'RemoteClientException' contains the message as well as a reference to the RemoteClient that is not yet connect in order for you to retrieve it an do an explicit connect if needed. + +You can also register a listener that will listen for example the 'RemoteClientStopped' event, retrieve the 'RemoteClient' from it and reconnect explicitly. + +See the section on RemoteClient listener and events below for details. + +Remote Client message buffering and send retry on failure +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Remote Client implements message buffering on network failure. This feature has zero overhead (even turned on) in the successful scenario and a queue append operation in case of unsuccessful send. So it is really really fast. + +The default behavior is that the remote client will maintain a transaction log of all messages that it has failed to send due to network problems (not other problems like serialization errors etc.). The client will try to resend these messages upon first successful reconnect and the message ordering is maintained. This means that the remote client will swallow all exceptions due to network failure and instead queue remote messages in the transaction log. The failures will however be reported through the remote client life-cycle events as well as the regular Akka event handler. You can turn this behavior on and off in the configuration file. It gives 'at-least-once' semantics, use a message id/counter for discarding potential duplicates (or use idempotent messages). + +.. code-block:: ruby + + akka { + remote { + client { + buffering { + retry-message-send-on-failure = on + capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) + # If positive then a bounded mailbox is used and the capacity is set using the property + } + } + } + } + +If you choose a capacity higher than 0, then a bounded queue will be used and if the limit of the queue is reached then a 'RemoteClientMessageBufferException' will be thrown. + +You can also get an Array with all the messages that the remote client has failed to send. Since the remote client events passes you an instance of the RemoteClient you have an easy way to act upon failure and do something with these messages (while waiting for them to be retried). + +.. code-block:: scala + + val pending: Array[Any] = Actor.remote.pendingMessages + +Running Remote Server in untrusted mode +--------------------------------------- + +You can run the remote server in untrusted mode. This means that the server will not allow any client-managed remote actors or any life-cycle messages and methods. This is useful if you want to let untrusted clients use server-managed actors in a safe way. This can optionally be combined with the secure cookie authentication mechanism described below as well as the SSL support for remote actor communication. + +If the client is trying to perform one of these unsafe actions then a 'java.lang.SecurityException' is thrown on the server as well as transferred to the client and thrown there as well. + +Here is how you turn it on: + +.. code-block:: ruby + + akka { + remote { + server { + untrusted-mode = on # the default is 'off' + } + } + } + +The messages that it prevents are all that extends 'LifeCycleMessage': +* class HotSwap(..) +* class RevertHotSwap..) +* class Restart(..) +* class Exit(..) +* class Link(..) +* class Unlink(..) +* class UnlinkAndStop(..) +* class ReceiveTimeout..) + +It also prevents the client from invoking any life-cycle and side-effecting methods, such as: +* start +* stop +* link +* unlink +* spawnLink +* etc. + +Using secure cookie for remote client authentication +---------------------------------------------------- + +Akka is using a similar scheme for remote client node authentication as Erlang; using secure cookies. In order to use this authentication mechanism you have to do two things: + +* Enable secure cookie authentication in the remote server +* Use the same secure cookie on all the trusted peer nodes + +Enabling secure cookie authentication +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The first one is done by enabling the secure cookie authentication in the remote server section in the configuration file: + +.. code-block:: ruby + + akka { + remote { + server { + require-cookie = on + } + } + } + +Now if you have try to connect to a server with a client then it will first try to authenticate the client by comparing the secure cookie for the two nodes. If they are the same then it allows the client to connect and use the server freely but if they are not the same then it will throw a 'java.lang.SecurityException' and not allow the client to connect. + +Generating and using the secure cookie +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The secure cookie can be any string value but in order to ensure that it is secure it is best to randomly generate it. This can be done by invoking the 'generate_config_with_secure_cookie.sh' script which resides in the '$AKKA_HOME/scripts' folder. This script will generate and print out a complete 'akka.conf' configuration file with the generated secure cookie defined that you can either use as-is or cut and paste the 'secure-cookie' snippet. Here is an example of its generated output: + +.. code-block:: ruby + + # This config imports the Akka reference configuration. + include "akka-reference.conf" + + # In this file you can override any option defined in the 'akka-reference.conf' file. + # Copy in all or parts of the 'akka-reference.conf' file and modify as you please. + + akka { + remote { + secure-cookie = "000E02050F0300040C050C0D060A040306090B0C" + } + } + +The simplest way to use it is to have it create your 'akka.conf' file like this: + +.. code-block:: ruby + + cd $AKKA_HOME + ./scripts/generate_config_with_secure_cookie.sh > ./config/akka.conf + +Now it is good to make sure that the configuration file is only accessible by the owner of the file. On Unix-style file system this can be done like this: + +.. code-block:: ruby + + chmod 400 ./config/akka.conf + +Running this script requires having 'scala' on the path (and will take a couple of seconds to run since it is using Scala and has to boot up the JVM to run). + +You can also generate the secure cookie by using the 'Crypt' object and its 'generateSecureCookie' method. + +.. code-block:: scala + + import akka.util.Crypt + + val secureCookie = Crypt.generateSecureCookie + +The secure cookie is a cryptographically secure randomly generated byte array turned into a SHA-1 hash. + +Remote Actors +------------- + +Akka has two types of remote actors: + +* Client-initiated and managed. Here it is the client that creates the remote actor and "moves it" to the server. +* Server-initiated and managed. Here it is the server that creates the remote actor and the client can ask for a handle to this actor. + +They are good for different use-cases. The client-initiated are great when you want to monitor an actor on another node since it allows you to link to it and supervise it using the regular supervision semantics. They also make RPC completely transparent. The server-initiated, on the other hand, are great when you have a service running on the server that you want clients to connect to, and you want full control over the actor on the server side for security reasons etc. + +Client-managed Remote Actors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +DEPRECATED AS OF 1.1 + +When you define an actors as being remote it is instantiated as on the remote host and your local actor becomes a proxy, it works as a handle to the remote actor. The real execution is always happening on the remote node. + +Actors can be made remote by calling remote().actorOf[MyActor](host, port) + +Here is an example: + +.. code-block:: scala + + import akka.actor.Actor + + class MyActor extends RemoteActor() { + def receive = { + case "hello" => self.reply("world") + } + } + + val remote = Actor.remote().actorOf[MyActor]("192.68.23.769", 2552) + +An Actor can also start remote child Actors through one of the 'spawn/link' methods. These will start, link and make the Actor remote atomically. + +.. code-block:: scala + + ... + spawnRemote[MyActor](hostname, port) + spawnLinkRemote[MyActor](hostname, port) + ... + +Server-managed Remote Actors +---------------------------- + +Server side setup +^^^^^^^^^^^^^^^^^ + +The API for server managed remote actors is really simple. 2 methods only: + +.. code-block:: scala + + class HelloWorldActor extends Actor { + def receive = { + case "Hello" => self.reply("World") + } + } + + remote.start("localhost", 2552) //Start the server + remote.register("hello-service", actorOf[HelloWorldActor]) //Register the actor with the specified service id + +Actors created like this are automatically started. + +You can also register an actor by its UUD rather than ID or handle. This is done by prefixing the handle with the "uuid:" protocol. + +.. code-block:: scala + + remote.register("uuid:" + actor.uuid, actor) + + remote.unregister("uuid:" + actor.uuid) + +Session bound server side setup +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Session bound server managed remote actors work by creating and starting a new actor for every client that connects. Actors are stopped automatically when the client disconnects. The client side is the same as regular server managed remote actors. Use the function registerPerSession instead of register. + +Session bound actors are useful if you need to keep state per session, e.g. username. +They are also useful if you need to perform some cleanup when a client disconnects by overriding the postStop method as described `here `_ + +.. code-block:: scala + + class HelloWorldActor extends Actor { + def receive = { + case "Hello" => self.reply("World") + } + } + remote.start("localhost", 2552) + remote.registerPerSession("hello-service", actorOf[HelloWorldActor]) + +Note that the second argument in registerPerSession is an implicit function. It will be called to create an actor every time a session is established. + +Client side usage +^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + val actor = remote.actorFor("hello-service", "localhost", 2552) + val result = actor !! "Hello" + +There are many variations on the 'remote#actorFor' method. Here are some of them: + +.. code-block:: scala + + ... = actorFor(className, hostname, port) + ... = actorFor(className, timeout, hostname, port) + ... = actorFor(uuid, className, hostname, port) + ... = actorFor(uuid, className, timeout, hostname, port) + ... // etc + +All of these also have variations where you can pass in an explicit 'ClassLoader' which can be used when deserializing messages sent from the remote actor. + +Running sample +^^^^^^^^^^^^^^ + +Here is a complete running sample (also available `here `_): + +.. code-block:: scala + + import akka.actor.Actor + import akka.util.Logging + import Actor._ + + class HelloWorldActor extends Actor { + def receive = { + case "Hello" => self.reply("World") + } + } + + object ServerInitiatedRemoteActorServer { + + def run = { + remote.start("localhost", 2552) + remote.register("hello-service", actorOf[HelloWorldActor]) + } + + def main(args: Array[String]) = run + } + + object ServerInitiatedRemoteActorClient extends Logging { + + def run = { + val actor = remote.actorFor("hello-service", "localhost", 2552) + val result = actor !! "Hello" + log.info("Result from Remote Actor: %s", result) + } + + def main(args: Array[String]) = run + } + +Automatic remote 'sender' reference management +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The sender of a remote message will be reachable with a reply through the remote server on the node that the actor is residing, automatically. +Please note that firewalled clients won't work right now. [2011-01-05] + +Identifying remote actors +------------------------- + +The 'id' field in the 'Actor' class is of importance since it is used as identifier for the remote actor. If you want to create a brand new actor every time you instantiate a remote actor then you have to set the 'id' field to a unique 'String' for each instance. If you want to reuse the same remote actor instance for each new remote actor (of the same class) you create then you don't have to do anything since the 'id' field by default is equal to the name of the actor class. + +Here is an example of overriding the 'id' field: + +.. code-block:: scala + + import akka.util.UUID + + class MyActor extends Actor { + self.id = UUID.newUuid.toString + def receive = { + case "hello" => self.reply("world") + } + } + + val actor = remote.actorOf[MyActor]("192.68.23.769", 2552) + +Remote Typed Actors +------------------- + +Client-managed Remote Actors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +DEPRECATED AS OF 1.1 + +You can define the Typed Actor to be a remote service by adding the 'RemoteAddress' configuration element in the declarative supervisor configuration: + +.. code-block:: java + + new Component( + Foo.class, + new LifeCycle(new Permanent(), 1000), + 1000, + new RemoteAddress("localhost", 2552)) + +You can also define an Typed Actor to be remote programmatically when creating it explicitly: + +.. code-block:: java + + TypedActorFactory factory = new TypedActorFactory(); + + POJO pojo = (POJO) factory.newRemoteInstance(POJO.class, 1000, "localhost", 2552) + + ... // use pojo as usual + +Server-managed Remote Actors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +WARNING: Remote TypedActors do not work with overloaded methods on your TypedActor, refrain from using overloading. + +Server side setup +***************** + +The API for server managed remote typed actors is nearly the same as for untyped actor + +.. code-block:: scala + + class RegistrationServiceImpl extends TypedActor with RegistrationService { + def registerUser(user: User): Unit = { + ... // register user + } + } + + remote.start("localhost", 2552) + + val typedActor = TypedActor.newInstance(classOf[RegistrationService], classOf[RegistrationServiceImpl], 2000) + remote.registerTypedActor("user-service", typedActor) + +Actors created like this are automatically started. + +Session bound server side setup +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Session bound server managed remote actors work by creating and starting a new actor for every client that connects. Actors are stopped automatically when the client disconnects. The client side is the same as regular server managed remote actors. Use the function registerTypedPerSessionActor instead of registerTypedActor. + +Session bound actors are useful if you need to keep state per session, e.g. username. +They are also useful if you need to perform some cleanup when a client disconnects. + +.. code-block:: scala + + class RegistrationServiceImpl extends TypedActor with RegistrationService { + def registerUser(user: User): Unit = { + ... // register user + } + } + remote.start("localhost", 2552) + + remote.registerTypedPerSessionActor("user-service", + TypedActor.newInstance(classOf[RegistrationService], + classOf[RegistrationServiceImpl], 2000)) + +Note that the second argument in registerTypedPerSessionActor is an implicit function. It will be called to create an actor every time a session is established. + +Client side usage +***************** + +.. code-block:: scala + + val actor = remote.typedActorFor(classOf[RegistrationService], "user-service", 5000L, "localhost", 2552) + actor.registerUser(…) + +There are variations on the 'RemoteClient#typedActorFor' method. Here are some of them: + +.. code-block:: scala + + ... = typedActorFor(interfaceClazz, serviceIdOrClassName, hostname, port) + ... = typedActorFor(interfaceClazz, serviceIdOrClassName, timeout, hostname, port) + ... = typedActorFor(interfaceClazz, serviceIdOrClassName, timeout, hostname, port, classLoader) + +Data Compression Configuration +------------------------------ + +Akka uses compression to minimize the size of the data sent over the wire. Currently it only supports 'zlib' compression but more will come later. + +You can configure it like this: + +.. code-block:: ruby + + akka { + remote { + compression-scheme = "zlib" # Options: "zlib" (lzf to come), leave out for no compression + zlib-compression-level = 6 # Options: 0-9 (1 being fastest and 9 being the most compressed), default is 6 + } + } + +Code provisioning +----------------- + +Akka does currently not support automatic code provisioning but requires you to have the remote actor class files available on both the "client" the "server" nodes. +This is something that will be addressed soon. Until then, sorry for the inconvenience. + +Subscribe to Remote Client events +--------------------------------- + +Akka has a subscription API for the client event. You can register an Actor as a listener and this actor will have to be able to process these events: + +.. code-block:: scala + + sealed trait RemoteClientLifeCycleEvent + case class RemoteClientError( + @BeanProperty cause: Throwable, + @BeanProperty client: RemoteClientModule, + @BeanProperty remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent + + case class RemoteClientDisconnected( + @BeanProperty client: RemoteClientModule, + @BeanProperty remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent + + case class RemoteClientConnected( + @BeanProperty client: RemoteClientModule, + @BeanProperty remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent + + case class RemoteClientStarted( + @BeanProperty client: RemoteClientModule, + @BeanProperty remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent + + case class RemoteClientShutdown( + @BeanProperty client: RemoteClientModule, + @BeanProperty remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent + + case class RemoteClientWriteFailed( + @BeanProperty request: AnyRef, + @BeanProperty cause: Throwable, + @BeanProperty client: RemoteClientModule, + @BeanProperty remoteAddress: InetSocketAddress) extends RemoteClientLifeCycleEvent + +So a simple listener actor can look like this: + +.. code-block:: scala + + val listener = actorOf(new Actor { + def receive = { + case RemoteClientError(cause, client, address) => ... // act upon error + case RemoteClientDisconnected(client, address) => ... // act upon disconnection + case RemoteClientConnected(client, address) => ... // act upon connection + case RemoteClientStarted(client, address) => ... // act upon client shutdown + case RemoteClientShutdown(client, address) => ... // act upon client shutdown + case RemoteClientWriteFailed(request, cause, client, address) => ... // act upon write failure + case _ => //ignore other + } + }).start + +Registration and de-registration can be done like this: + +.. code-block:: scala + + remote.addListener(listener) + ... + remote.removeListener(listener) + +Subscribe to Remote Server events +--------------------------------- + +Akka has a subscription API for the 'RemoteServer'. You can register an Actor as a listener and this actor will have to be able to process these events: + +.. code-block:: scala + + sealed trait RemoteServerLifeCycleEvent + case class RemoteServerStarted( + @BeanProperty val server: RemoteServerModule) extends RemoteServerLifeCycleEvent + case class RemoteServerShutdown( + @BeanProperty val server: RemoteServerModule) extends RemoteServerLifeCycleEvent + case class RemoteServerError( + @BeanProperty val cause: Throwable, + @BeanProperty val server: RemoteServerModule) extends RemoteServerLifeCycleEvent + case class RemoteServerClientConnected( + @BeanProperty val server: RemoteServerModule, + @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent + case class RemoteServerClientDisconnected( + @BeanProperty val server: RemoteServerModule, + @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent + case class RemoteServerClientClosed( + @BeanProperty val server: RemoteServerModule, + @BeanProperty val clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent + case class RemoteServerWriteFailed( + @BeanProperty request: AnyRef, + @BeanProperty cause: Throwable, + @BeanProperty server: RemoteServerModule, + @BeanProperty clientAddress: Option[InetSocketAddress]) extends RemoteServerLifeCycleEvent + +So a simple listener actor can look like this: + +.. code-block:: scala + + val listener = actorOf(new Actor { + def receive = { + case RemoteServerStarted(server) => ... // act upon server start + case RemoteServerShutdown(server) => ... // act upon server shutdown + case RemoteServerError(cause, server) => ... // act upon server error + case RemoteServerClientConnected(server, clientAddress) => ... // act upon client connection + case RemoteServerClientDisconnected(server, clientAddress) => ... // act upon client disconnection + case RemoteServerClientClosed(server, clientAddress) => ... // act upon client connection close + case RemoteServerWriteFailed(request, casue, server, clientAddress) => ... // act upon server write failure + } + }).start + +Registration and de-registration can be done like this: + +.. code-block:: scala + + remote.addListener(listener) + ... + remote.removeListener(listener) + +Message Serialization +--------------------- + +All messages that are sent to remote actors needs to be serialized to binary format to be able to travel over the wire to the remote node. This is done by letting your messages extend one of the traits in the 'akka.serialization.Serializable' object. If the messages don't implement any specific serialization trait then the runtime will try to use standard Java serialization. + +Here are some examples, but full documentation can be found in the `Serialization section `_. + +Scala JSON +^^^^^^^^^^ + +.. code-block:: scala + + case class MyMessage(id: String, value: Tuple2[String, Int]) extends Serializable.ScalaJSON[MyMessage] + +Protobuf +^^^^^^^^ + +Protobuf message specification needs to be compiled with 'protoc' compiler. + +.. code-block:: scala + + message ProtobufPOJO { + required uint64 id = 1; + required string name = 2; + required bool status = 3; + } + +Using the generated message builder to send the message to a remote actor: + +.. code-block:: scala + + val result = actor !! ProtobufPOJO.newBuilder + .setId(11) + .setStatus(true) + .setName("Coltrane") + .build + +SBinary +^^^^^^^ + +``_ +case class User(firstNameLastName: Tuple2[String, String], email: String, age: Int) extends Serializable.SBinary[User] { + import sbinary.DefaultProtocol._ + + def this() = this(null, null, 0) + + implicit object UserFormat extends Format[User] { + def reads(in : Input) = User( + read[Tuple2[String, String]](in), + read[String](in), + read[Int](in)) + def writes(out: Output, value: User) = { + write[Tuple2[String, String]](out, value. firstNameLastName) + write[String](out, value.email) + write[Int](out, value.age) + } + } + + def fromBytes(bytes: Array[Byte]) = fromByteArray[User](bytes) + + def toBytes: Array[Byte] = toByteArray(this) +} +``_ diff --git a/akka-docs/pending/routing-java.rst b/akka-docs/pending/routing-java.rst new file mode 100644 index 0000000000..2c818af896 --- /dev/null +++ b/akka-docs/pending/routing-java.rst @@ -0,0 +1,93 @@ +Routing (Java) +============== + +**UntypedDispatcher** +--------------------- + +An UntypedDispatcher is an actor that routes incoming messages to outbound actors. + +.. code-block:: java + + import static akka.actor.Actors.*; + import akka.actor.*; + import akka.routing.*; + + //A Pinger is an UntypedActor that prints "Pinger: " + class Pinger extends UntypedActor { + public void onReceive(Object message) throws Exception { + System.out.println("Pinger: " + message); + } + } + + //A Ponger is an UntypedActor that prints "Ponger: " + class Ponger extends UntypedActor { + public void onReceive(Object message) throws Exception { + System.out.println("Ponger: " + message); + } + } + + public class MyDispatcher extends UntypedDispatcher { + private ActorRef pinger = actorOf(Pinger.class).start(); + private ActorRef ponger = actorOf(Ponger.class).start(); + + //Route Ping-messages to the pinger, and Pong-messages to the ponger + public ActorRef route(Object message) { + if("Ping".equals(message)) return pinger; + else if("Pong".equals(message)) return ponger; + else throw new IllegalArgumentException("I do not understand " + message); + } + } + + ActorRef dispatcher = actorOf(MyDispatcher.class).start(); + dispatcher.sendOneWay("Ping"); //Prints "Pinger: Ping" + dispatcher.sendOneWay("Pong"); //Prints "Ponger: Pong" + +**UntypedLoadBalancer** +----------------------- + +An UntypedLoadBalancer is an actor that forwards messages it receives to a boundless sequence of destination actors. + +.. code-block:: java + + import static akka.actor.Actors.*; + import akka.actor.*; + import akka.routing.*; + import static java.util.Arrays.asList; + + //A Pinger is an UntypedActor that prints "Pinger: " + class Pinger extends UntypedActor { + public void onReceive(Object message) throws Exception { + System.out.println("Pinger: " + message); + } + } + + //A Ponger is an UntypedActor that prints "Ponger: " + class Ponger extends UntypedActor { + public void onReceive(Object message) throws Exception { + System.out.println("Ponger: " + message); + } + } + + //Our load balancer, sends messages to a pinger, then a ponger, rinse and repeat. + public class MyLoadBalancer extends UntypedLoadBalancer { + private InfiniteIterator actors = new CyclicIterator(asList( + actorOf(Pinger.class).start(), + actorOf(Ponger.class).start() + )); + + public InfiniteIterator seq() { + return actors; + } + } + + ActorRef dispatcher = actorOf(MyLoadBalancer.class).start(); + dispatcher.sendOneWay("Pong"); //Prints "Pinger: Pong" + dispatcher.sendOneWay("Ping"); //Prints "Ponger: Ping" + dispatcher.sendOneWay("Ping"); //Prints "Pinger: Ping" + dispatcher.sendOneWay("Pong"); //Prints "Ponger: Pong + +You can also send a 'new Routing.Broadcast(msg)' message to the router to have it be broadcasted out to all the actors it represents. + +``_ +router.sendOneWay(new Routing.Broadcast(new PoisonPill())); +``_ diff --git a/akka-docs/pending/routing-scala.rst b/akka-docs/pending/routing-scala.rst new file mode 100644 index 0000000000..45a297575d --- /dev/null +++ b/akka-docs/pending/routing-scala.rst @@ -0,0 +1,263 @@ +**Routing / Patterns (Scala)** + +Akka-core includes some building blocks to build more complex message flow handlers, they are listed and explained below: + +Dispatcher +---------- + +A Dispatcher is an actor that routes incoming messages to outbound actors. + +To use it you can either create a Dispatcher through the **dispatcherActor()** factory method + +.. code-block:: scala + + import akka.actor.Actor._ + import akka.actor.Actor + import akka.routing.Routing._ + + //Our message types + case object Ping + case object Pong + + //Two actors, one named Pinger and one named Ponger + //The actor(pf) method creates an anonymous actor and starts it + val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start + val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start + + //A dispatcher that dispatches Ping messages to the pinger + //and Pong messages to the ponger + val d = dispatcherActor { + case Ping => pinger + case Pong => ponger + } + + d ! Ping //Prints "Pinger: Ping" + d ! Pong //Prints "Ponger: Pong" + +Or by mixing in akka.patterns.Dispatcher: + +.. code-block:: scala + + import akka.actor.Actor + import akka.actor.Actor._ + import akka.routing.Dispatcher + + //Our message types + case object Ping + case object Pong + + class MyDispatcher extends Actor with Dispatcher { + //Our pinger and ponger actors + val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start + val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start + //When we get a ping, we dispatch to the pinger + //When we get a pong, we dispatch to the ponger + def routes = { + case Ping => pinger + case Pong => ponger + } + } + + //Create an instance of our dispatcher, and start it + val d = actorOf[MyDispatcher].start + + d ! Ping //Prints "Pinger: Ping" + d ! Pong //Prints "Ponger: Pong" + +LoadBalancer +------------ + +A LoadBalancer is an actor that forwards messages it receives to a boundless sequence of destination actors. + +Example using the **loadBalancerActor()** factory method: + +.. code-block:: scala + + import akka.actor.Actor._ + import akka.actor.Actor + import akka.routing.Routing._ + import akka.routing.CyclicIterator + + //Our message types + case object Ping + case object Pong + + //Two actors, one named Pinger and one named Ponger + //The actor(pf) method creates an anonymous actor and starts it + + val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start + val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start + + //A load balancer that given a sequence of actors dispatches them accordingly + //a CyclicIterator works in a round-robin-fashion + + val d = loadBalancerActor( new CyclicIterator( List(pinger,ponger) ) ) + + d ! Pong //Prints "Pinger: Pong" + d ! Pong //Prints "Ponger: Pong" + d ! Ping //Prints "Pinger: Ping" + d ! Ping //Prints "Ponger: Ping" + +Or by mixing in akka.routing.LoadBalancer + +.. code-block:: scala + + import akka.actor._ + import akka.actor.Actor._ + import akka.routing.{ LoadBalancer, CyclicIterator } + + //Our message types + case object Ping + case object Pong + + //A load balancer that balances between a pinger and a ponger + class MyLoadBalancer extends Actor with LoadBalancer { + val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start + val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start + + val seq = new CyclicIterator[ActorRef](List(pinger,ponger)) + } + + //Create an instance of our loadbalancer, and start it + val d = actorOf[MyLoadBalancer].start + + d ! Pong //Prints "Pinger: Pong" + d ! Pong //Prints "Ponger: Pong" + d ! Ping //Prints "Pinger: Ping" + d ! Ping //Prints "Ponger: Ping" + +Also, instead of using the CyclicIterator, you can create your own message distribution algorithms, there’s already `one <@http://github.com/jboner/akka/blob/master/akka-core/src/main/scala/routing/Iterators.scala#L31>`_ that dispatches depending on target mailbox size, effectively dispatching to the one that’s got fewest messages to process right now. + +Example ``_ + +You can also send a 'Routing.Broadcast(msg)' message to the router to have it be broadcasted out to all the actors it represents. + +.. code-block:: scala + + router ! Routing.Broadcast(PoisonPill) + +Actor Pool +---------- + +An actor pool is similar to the load balancer is that it routes incoming messages to other actors. It has different semantics however when it comes to how those actors are managed and selected for dispatch. Therein lies the difference. The pool manages, from start to shutdown, the lifecycle of all delegated actors. The number of actors in a pool can be fixed or grow and shrink over time. Also, messages can be routed to more than one actor in the pool if so desired. This is a useful little feature for accounting for expected failure - especially with remoting - where you can invoke the same request of multiple actors and just take the first, best response. + +The actor pool is built around three concepts: capacity, filtering and selection. + +Selection +^^^^^^^^^ + +All pools require a *Selector* to be mixed-in. This trait controls how and how many actors in the pool will receive the incoming message. Define *selectionCount* to some positive number greater than one to route to multiple actors. Currently two are provided: +* `SmallestMailboxSelector `_ - Using the exact same logic as the iterator of the same name, the pooled actor with the fewest number of pending messages will be chosen. +* `RoundRobinSelector `_ - Performs a very simple index-based selection, wrapping around the end of the list, very much like the CyclicIterator does. + +* + +Partial Fills +************* + +When selecting more than one pooled actor, its possible that in order to fulfill the requested amount, the selection set must contain duplicates. By setting *partialFill* to **true**, you instruct the selector to return only unique actors from the pool. + +Capacity +^^^^^^^^ + +As you'd expect, capacity traits determine how the pool is funded with actors. There are two types of strategies that can be employed: +* `FixedCapacityStrategy `_ - When you mix this into your actor pool, you define a pool size and when the pool is started, it will have that number of actors within to which messages will be delegated. +* `BoundedCapacityStrategy `_ - When you mix this into your actor pool, you define upper and lower bounds, and when the pool is started, it will have the minimum number of actors in place to handle messages. You must also mix-in a Capacitor and a Filter when using this strategy (see below). + +The *BoundedCapacityStrategy* requires additional logic to function. Specifically it requires a *Capacitor* and a *Filter*. Capacitors are used to determine the pressure that the pool is under and provide a (usually) raw reading of this information. Currently we provide for the use of either mailbox backlog or active futures count as a means of evaluating pool pressure. Each expresses itself as a simple number - a reading of the number of actors either with mailbox sizes over a certain threshold or blocking a thread waiting on a future to complete or expire. + +Filtering +^^^^^^^^^ + +A *Filter* is a trait that modifies the raw pressure reading returned from a Capacitor such that it drives the adjustment of the pool capacity to a desired end. More simply, if we just used the pressure reading alone, we might only ever increase the size of the pool (to respond to overload) or we might only have a single mechanism for reducing the pool size when/if it became necessary. This behavior is fully under your control through the use of *Filters*. Let's take a look at some code to see how this works: + +.. code-block:: scala + + trait BoundedCapacitor + { + def lowerBound:Int + def upperBound:Int + + def capacity(delegates:Seq[ActorRef]):Int = + { + val current = delegates length + var delta = _eval(delegates) + val proposed = current + delta + + if (proposed < lowerBound) delta += (lowerBound - proposed) + else if (proposed > upperBound) delta -= (proposed - upperBound) + + delta + } + + protected def _eval(delegates:Seq[ActorRef]):Int + } + + trait CapacityStrategy + { + import ActorPool._ + + def pressure(delegates:Seq[ActorRef]):Int + def filter(pressure:Int, capacity:Int):Int + + protected def _eval(delegates:Seq[ActorRef]):Int = filter(pressure(delegates), delegates.size) + } + +Here we see how the filter function will have the chance to modify the pressure reading to influence the capacity change. You are free to implement filter() however you like. We provide a `Filter `_ trait that evaluates both a rampup and a backoff subfilter to determine how to use the pressure reading to alter the pool capacity. There are several subfilters available to use, though again you may create whatever makes the most sense for you pool: +* `BasicRampup `_ - When pressure exceeds current capacity, increase the number of actors in the pool by some factor (*rampupRate*) of the current pool size. +* `BasicBackoff `_ - When the pressure ratio falls under some predefined amount (*backoffThreshold*), decrease the number of actors in the pool by some factor of the current pool size. +* `RunningMeanBackoff `_ - This filter tracks the average pressure-to-capacity over the lifetime of the pool (or since the last time the filter was reset) and will begin to reduce capacity once this mean falls below some predefined amount. The number of actors that will be stopped is determined by some factor of the difference between the current capacity and pressure. The idea behind this filter is to reduce the likelihood of "thrashing" (removing then immediately creating...) pool actors by delaying the backoff until some quiescent stage of the pool. Put another way, use this subfilter to allow quick rampup to handle load and more subtle backoff as that decreases over time. + +Examples +^^^^^^^^ + +.. code-block:: scala + + class TestPool extends Actor with DefaultActorPool + with BoundedCapacityStrategy + with ActiveFuturesPressureCapacitor + with SmallestMailboxSelector + with BasicNoBackoffFilter + { + def factory = actorOf(new Actor {def receive = {case n:Int => + Thread.sleep(n) + counter.incrementAndGet + latch.countDown}}) + + def lowerBound = 2 + def upperBound = 4 + def rampupRate = 0.1 + def partialFill = true + def selectionCount = 1 + def instance = factory + def receive = _route + } + +.. code-block:: scala + + class TestPool extends Actor with DefaultActorPool + with BoundedCapacityStrategy + with MailboxPressureCapacitor + with SmallestMailboxSelector + with Filter + with RunningMeanBackoff + with BasicRampup + { + + def factory = actorOf(new Actor {def receive = {case n:Int => + Thread.sleep(n) + latch.countDown}}) + + def lowerBound = 1 + def upperBound = 5 + def pressureThreshold = 1 + def partialFill = true + def selectionCount = 1 + def rampupRate = 0.1 + def backoffRate = 0.50 + def backoffThreshold = 0.50 + def instance = factory + def receive = _route + } + +Taken from the unit test `spec `_. diff --git a/akka-docs/pending/scheduler.rst b/akka-docs/pending/scheduler.rst new file mode 100644 index 0000000000..ac0c7a3a50 --- /dev/null +++ b/akka-docs/pending/scheduler.rst @@ -0,0 +1,16 @@ +Scheduler +========= + +Module stability: **SOLID** + +Akka has a little scheduler written using actors. Can be convenient if you want to schedule some periodic task for maintenance or similar. + +It allows you to register a message that you want to be sent to a specific actor at a periodic interval. Here is an example: + +``_ +//Sends messageToBeSent to receiverActor after initialDelayBeforeSending and then after each delayBetweenMessages +Scheduler.schedule(receiverActor, messageToBeSent, initialDelayBeforeSending, delayBetweenMessages, timeUnit) + +//Sends messageToBeSent to receiverActor after delayUntilSend +Scheduler.scheduleOnce(receiverActor, messageToBeSent, delayUntilSend, timeUnit) +``_ diff --git a/akka-docs/pending/security.rst b/akka-docs/pending/security.rst new file mode 100644 index 0000000000..3600c21285 --- /dev/null +++ b/akka-docs/pending/security.rst @@ -0,0 +1,261 @@ +Security +======== + +Module stability: **IN PROGRESS** + +Akka supports security for access to RESTful Actors through `HTTP Authentication `_. The security is implemented as a jersey ResourceFilter which delegates the actual authentication to an authentication actor. + +Akka provides authentication via the following authentication schemes: +* `Basic Authentication `_ +* `Digest Authentication `_ +* `Kerberos SPNEGO Authentication `_ + +The authentication is performed by implementations of akka.security.AuthenticationActor. + +Akka provides a trait for each authentication scheme: +* BasicAuthenticationActor +* DigestAuthenticationActor +* SpnegoAuthenticationActor + +With Akka’s excellent support for distributed databases, it’s a one-liner to do a distributed authentication scheme. + +^ + +Setup +===== + +To secure your RESTful actors you need to perform the following steps: + +1. configure the resource filter factory 'akka.security.AkkaSecurityFilterFactory' in the 'akka.conf' like this: + +.. code-block:: ruby + + akka { + ... + rest { + filters="akka.security.AkkaSecurityFilterFactory" + } + ... + } + +2. Configure an implementation of an authentication actor in 'akka.conf': + +.. code-block:: ruby + + akka { + ... + rest { + filters= ... + authenticator = "akka.security.samples.BasicAuthenticationService" + } + ... + } + +3. Start your authentication actor in your 'Boot' class. The security package consists of the following parts: + +4. Secure your RESTful actors using class or resource level annotations: +* @DenyAll +* @RolesAllowed(listOfRoles) +* @PermitAll + +Security Samples +---------------- + +The akka-samples-security module contains a small sample application with sample implementations for each authentication scheme. +You can start the sample app using the jetty plugin: mvn jetty:run. + +The RESTful actor can then be accessed using your browser of choice under: +* permit access only to users having the “chef” role: ``_ +* public access: ``_ + +You can access the secured resource using any user for basic authentication (which is the default authenticator in the sample app). + +Digest authentication can be directly enabled in the sample app. Kerberos/SPNEGO authentication is a bit more involved an is described below. + +^ + +Kerberos/SPNEGO Authentication +------------------------------ + +Kerberos is a network authentication protocol, (see ``_). It provides strong authentication for client/server applications. +In a kerberos enabled environment a user will need to sign on only once. Subsequent authentication to applications is handled transparently by kerberos. + +Most prominently the kerberos protocol is used to authenticate users in a windows network. When deploying web applications to a corporate intranet an important feature will be to support the single sign on (SSO), which comes to make the application kerberos aware. + +How does it work (at least for REST actors)? +# When accessing a secured resource the server will check the request for the *Authorization* header as with basic or digest authentication. +# If it is not set, the server will respond with a challenge to “Negotiate”. The negotiation is in fact the NEGO part of the `SPNEGO `_ specification) +# The browser will then try to acquire a so called *service ticket* from a ticket granting service, i.e. the kerberos server +# The browser will send the *service ticket* to the web application encoded in the header value of the *Authorization*header +# The web application must validate the ticket based on a shared secret between the web application and the kerberos server. As a result the web application will know the name of the user + +To activate the kerberos/SPNEGO authentication for your REST actor you need to enable the kerberos/SPNGEOauthentication actor in the akka.conf like this: + +.. code-block:: ruby + + akka { + ... + rest { + filters= ... + authenticator = "akka.security.samples.SpnegoAuthenticationService" + } + ... + } + +Furthermore you must provide the SpnegoAuthenticator with the following information. +# Service principal name: the name of your web application in the kerberos servers user database. This name is always has the form “HTTP/{server}@{realm}” +# Path to the keytab file: this is a kind of certificate for your web application to acquire tickets from the kerberos server + +.. code-block:: ruby + + akka { + ... + rest { + filters= ... + authenticator = "akka.security.samples.SpnegoAuthenticationService" + kerberos { + servicePrincipal = "HTTP/{server}@{realm}" + keyTabLocation = "URL to keytab" + # kerberosDebug = "true" + } + } + ... + } + +^ + +How to setup kerberos on localhost for Ubuntu +--------------------------------------------- + +This is a short step by step description of howto set up a kerberos server on an ubuntu system. + +1. Install the Heimdal Kerberos Server and Client + +:: + + sudo apt-get install heimdal-clients heimdal-clients-x heimdal-kdc krb5-config + ... + +2. Set up your kerberos realm. In this example the realm is of course … EXAMPLE.COM + +:: + + eckart@dilbert:~$ sudo kadmin -l + kadmin> init EXAMPLE.COM + Realm max ticket life [unlimited]: + Realm max renewable ticket life [unlimited]: + kadmin> quit + +3. Tell your kerberos clients what your realm is and where to find the kerberos server (aka the Key Distribution Centre or KDC) + +Edit the kerberos config file: /etc/krb5.conf and configure … +…the default realm: + +:: + + [libdefaults] + default_realm = EXAMPLE.COM + +… where to find the KDC for your realm + +:: + + [realms] + EXAMPLE.COM = { + kdc = localhost + } + +…which hostnames or domains map to which realm (a kerberos realm is **not** a DNS domain): + +:: + + [domain_realm] + localhost = EXAMPLE.COM + +4. Add the principals +The user principal: + +:: + + eckart@dilbert:~$ sudo kadmin -l + kadmin> add zaphod + Max ticket life [1 day]: + Max renewable life [1 week]: + Principal expiration time [never]: + Password expiration time [never]: + Attributes []: + zaphod@EXAMPLE.COM's Password: + Verifying - zaphod@EXAMPLE.COM's Password: + kadmin> quit + +The service principal: + +:: + + eckart@dilbert:~$ sudo kadmin -l + kadmin> add HTTP/localhost@EXAMPLE.COM + Max ticket life [1 day]: + Max renewable life [1 week]: + Principal expiration time [never]: + Password expiration time [never]: + Attributes []: + HTTP/localhost@EXAMPLE.COM's Password: + Verifying - HTTP/localhost@EXAMPLE.COM's Password: + kadmin> quit + +We can now try to acquire initial tickets for the principals to see if everything worked. + +:: + + eckart@dilbert:~$ kinit zaphod + zaphod@EXAMPLE.COM's Password: + +If this method returns withour error we have a success. +We can additionally list the acquired tickets: + +:: + + eckart@dilbert:~$ klist + Credentials cache: FILE:/tmp/krb5cc_1000 + Principal: zaphod@EXAMPLE.COM + + Issued Expires Principal + Oct 24 21:51:59 Oct 25 06:51:59 krbtgt/EXAMPLE.COM@EXAMPLE.COM + +This seems correct. To remove the ticket cache simply type kdestroy. + +5. Create a keytab for your service principal + +:: + + eckart@dilbert:~$ ktutil -k http.keytab add -p HTTP/localhost@EXAMPLE.COM -V 1 -e aes256-cts-hmac-sha1-96 + Password: + Verifying - Password: + eckart@dilbert:~$ + +This command will create a keytab file for the service principal named “http.keytab” in the current directory. You can specify other encryption methods than ‘aes256-cts-hmac-sha1-96’, but this is the e default encryption method for the heimdal client, so there is no additional configuration needed. You can specify other encryption types in the krb5.conf. + +Note that you might need to install the unlimited strength policy files for java from here:``_ to use the aes256 encryption from your application. + +Again we can test if the keytab generation worked with the kinit command: + +:: + + eckart@dilbert:~$ kinit -t http.keytab HTTP/localhost@EXAMPLE.COM + eckart@dilbert:~$ klist + Credentials cache: FILE:/tmp/krb5cc_1000 + Principal: HTTP/localhost@EXAMPLE.COM + + Issued Expires Principal + Oct 24 21:59:20 Oct 25 06:59:20 krbtgt/EXAMPLE.COM@EXAMPLE.COM + +Now point the configuration of the key in 'akka.conf' to the correct location and set the correct service principal name. The web application should now startup and produce at least a 401 response with a header “WWW-Authenticate” = “Negotiate”. The last step is to configure the browser. + +6. Set up Firefox to use Kerberos/SPNEGO +This is done by typing 'about:config'. Filter the config entries for “network.neg” and set the config entries “network.negotiate-auth.delegation-uris” and “network.negotiate-auth.trusted-uris” to “localhost”. +and now … + +7. Access the RESTful Actor. + +8. Have fun +… but acquire an initial ticket for the user principal first: kinit zaphod diff --git a/akka-docs/pending/serialization-java.rst b/akka-docs/pending/serialization-java.rst new file mode 100644 index 0000000000..1206211b8d --- /dev/null +++ b/akka-docs/pending/serialization-java.rst @@ -0,0 +1,178 @@ +Serialization (Java) +==================== + +Akka serialization module has been documented extensively under the Scala API section. In this section we will point out the different APIs that are available in Akka for Java based serialization of ActorRefs. The Scala APIs of ActorSerialization has implicit Format objects that set up the type class based serialization. In the Java API, the Format objects need to be specified explicitly. + +Serialization of ActorRef +========================= + +The following are the Java APIs for serialization of local ActorRefs: + +.. code-block:: scala + + /** + * Module for local actor serialization. + */ + object ActorSerialization { + // wrapper for implicits to be used by Java + def fromBinaryJ[T <: Actor](bytes: Array[Byte], format: Format[T]): ActorRef = + fromBinary(bytes)(format) + + // wrapper for implicits to be used by Java + def toBinaryJ[T <: Actor](a: ActorRef, format: Format[T], srlMailBox: Boolean = true): Array[Byte] = + toBinary(a, srlMailBox)(format) + } + +The following steps describe the procedure for serializing an Actor and ActorRef. + +Serialization of a Stateless Actor +================================== + +Step 1: Define the Actor +------------------------ + +.. code-block:: scala + + public class SerializationTestActor extends UntypedActor { + public void onReceive(Object msg) { + getContext().replySafe("got it!"); + } + } + +Step 2: Define the typeclass instance for the actor +--------------------------------------------------- + +Note how the generated Java classes are accessed using the $class based naming convention of the Scala compiler. + +.. code-block:: scala + + class SerializationTestActorFormat implements StatelessActorFormat { + @Override + public SerializationTestActor fromBinary(byte[] bytes, SerializationTestActor act) { + return (SerializationTestActor) StatelessActorFormat$class.fromBinary(this, bytes, act); + } + + @Override + public byte[] toBinary(SerializationTestActor ac) { + return StatelessActorFormat$class.toBinary(this, ac); + } + } + +**Step 3: Serialize and de-serialize** + +The following JUnit snippet first creates an actor using the default constructor. The actor is, as we saw above a stateless one. Then it is serialized and de-serialized to get back the original actor. Being stateless, the de-serialized version behaves in the same way on a message as the original actor. + +.. code-block:: java + + @Test public void mustBeAbleToSerializeAfterCreateActorRefFromClass() { + ActorRef ref = Actors.actorOf(SerializationTestActor.class); + assertNotNull(ref); + ref.start(); + try { + Object result = ref.sendRequestReply("Hello"); + assertEquals("got it!", result); + } catch (ActorTimeoutException ex) { + fail("actor should not time out"); + } + + Format f = new SerializationTestActorFormat(); + byte[] bytes = toBinaryJ(ref, f, false); + ActorRef r = fromBinaryJ(bytes, f); + assertNotNull(r); + r.start(); + try { + Object result = r.sendRequestReply("Hello"); + assertEquals("got it!", result); + } catch (ActorTimeoutException ex) { + fail("actor should not time out"); + } + ref.stop(); + r.stop(); + } + +Serialization of a Stateful Actor +================================= + +Let's now have a look at how to serialize an actor that carries a state with it. Here the expectation is that the serialization of the actor will also persist the state information. And after de-serialization we will get back the state with which it was serialized. + +Step 1: Define the Actor +------------------------ + +Here we consider an actor defined in Scala. We will however serialize using the Java APIs. + +.. code-block:: scala + + class MyUntypedActor extends UntypedActor { + var count = 0 + def onReceive(message: Any): Unit = message match { + case m: String if m == "hello" => + count = count + 1 + getContext.replyUnsafe("world " + count) + case m: String => + count = count + 1 + getContext.replyUnsafe("hello " + m + " " + count) + case _ => + throw new Exception("invalid message type") + } + } + +Note the actor has a state in the form of an Integer. And every message that the actor receives, it replies with an addition to the integer member. + +Step 2: Define the instance of the typeclass +-------------------------------------------- + +.. code-block:: java + + class MyUntypedActorFormat implements Format { + @Override + public MyUntypedActor fromBinary(byte[] bytes, MyUntypedActor act) { + ProtobufProtocol.Counter p = + (ProtobufProtocol.Counter) new SerializerFactory().getProtobuf().fromBinary(bytes, ProtobufProtocol.Counter.class); + act.count_$eq(p.getCount()); + return act; + } + + @Override + public byte[] toBinary(MyUntypedActor ac) { + return ProtobufProtocol.Counter.newBuilder().setCount(ac.count()).build().toByteArray(); + } + } + +Note the usage of Protocol Buffers to serialize the state of the actor. + +Step 3: Serialize and de-serialize +---------------------------------- + +.. code-block:: java + + @Test public void mustBeAbleToSerializeAStatefulActor() { + ActorRef ref = Actors.actorOf(MyUntypedActor.class); + assertNotNull(ref); + ref.start(); + try { + Object result = ref.sendRequestReply("hello"); + assertEquals("world 1", result); + result = ref.sendRequestReply("hello"); + assertEquals("world 2", result); + } catch (ActorTimeoutException ex) { + fail("actor should not time out"); + } + + Format f = new MyUntypedActorFormat(); + byte[] bytes = toBinaryJ(ref, f, false); + ActorRef r = fromBinaryJ(bytes, f); + assertNotNull(r); + r.start(); + try { + Object result = r.sendRequestReply("hello"); + assertEquals("world 3", result); + result = r.sendRequestReply("hello"); + assertEquals("world 4", result); + } catch (ActorTimeoutException ex) { + fail("actor should not time out"); + } + ref.stop(); + r.stop(); + } + +Note how the de-serialized version starts with the state value with which it was earlier serialized. diff --git a/akka-docs/pending/serialization-scala.rst b/akka-docs/pending/serialization-scala.rst new file mode 100644 index 0000000000..b2aef526a8 --- /dev/null +++ b/akka-docs/pending/serialization-scala.rst @@ -0,0 +1,978 @@ +Serialization (Scala) +===================== + +Module stability: **SOLID** + +Serialization of ActorRef +========================= + +An Actor can be serialized in two different ways: + +* Serializable RemoteActorRef - Serialized to an immutable, network-aware Actor reference that can be freely shared across the network. They "remember" and stay mapped to their original Actor instance and host node, and will always work as expected. +* Serializable LocalActorRef - Serialized by doing a deep copy of both the ActorRef and the Actor instance itself. Can be used to physically move an Actor from one node to another and continue the execution there. + +Both of these can be sent as messages over the network and/or store them to disk, in a persistent storage backend etc. + +Actor serialization in Akka is implemented through a type class 'Format[T <: Actor]' which publishes the 'fromBinary' and 'toBinary' methods for serialization. Here's the complete definition of the type class: + +.. code-block:: scala + + /** + * Type class definition for Actor Serialization + */ + trait FromBinary[T <: Actor] { + def fromBinary(bytes: Array[Byte], act: T): T + } + + trait ToBinary[T <: Actor] { + def toBinary(t: T): Array[Byte] + } + + // client needs to implement Format[] for the respective actor + trait Format[T <: Actor] extends FromBinary[T] with ToBinary[T] + +**Deep serialization of an Actor and ActorRef** +----------------------------------------------- + +You can serialize the whole actor deeply, e.g. both the 'ActorRef' and then instance of its 'Actor'. This can be useful if you want to move an actor from one node to another, or if you want to store away an actor, with its state, into a database. + +Here is an example of how to serialize an Actor. + +Step 1: Define the actor +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + class MyActor extends Actor { + var count = 0 + + def receive = { + case "hello" => + count = count + 1 + self.reply("world " + count) + } + } + +Step 2: Implement the type class for the actor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + object BinaryFormatMyActor { + implicit object MyActorFormat extends Format[MyActor] { + def fromBinary(bytes: Array[Byte], act: MyActor) = { + val p = Serializer.Protobuf.fromBinary(bytes, Some(classOf[ProtobufProtocol.Counter])).asInstanceOf[ProtobufProtocol.Counter] + act.count = p.getCount + act + } + def toBinary(ac: MyActor) = + ProtobufProtocol.Counter.newBuilder.setCount(ac.count).build.toByteArray + } + } + } + +Step 3: Import the type class module definition and serialize / de-serialize +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + it("should be able to serialize and de-serialize a stateful actor") { + import akka.serialization.ActorSerialization._ + import BinaryFormatMyActor._ + + val actor1 = actorOf[MyActor].start + (actor1 !! "hello").getOrElse("_") should equal("world 1") + (actor1 !! "hello").getOrElse("_") should equal("world 2") + + val bytes = toBinary(actor1) + val actor2 = fromBinary(bytes) + actor2.start + (actor2 !! "hello").getOrElse("_") should equal("world 3") + } + +**Helper Type Class for Stateless Actors** + +If your actor is stateless, then you can use the helper trait that Akka provides to serialize / de-serialize. Here's the definition: + +.. code-block:: scala + + trait StatelessActorFormat[T <: Actor] extends Format[T] { + def fromBinary(bytes: Array[Byte], act: T) = act + def toBinary(ac: T) = Array.empty[Byte] + } + +Then you use it as follows: + +.. code-block:: scala + + class MyStatelessActor extends Actor { + def receive = { + case "hello" => + self.reply("world") + } + } + +Just create an object for the helper trait for your actor: + +.. code-block:: scala + + object BinaryFormatMyStatelessActor { + implicit object MyStatelessActorFormat extends StatelessActorFormat[MyStatelessActor] + } + +and use it for serialization: + +.. code-block:: scala + + it("should be able to serialize and de-serialize a stateless actor") { + import akka.serialization.ActorSerialization._ + import BinaryFormatMyStatelessActor._ + + val actor1 = actorOf[MyStatelessActor].start + (actor1 !! "hello").getOrElse("_") should equal("world") + (actor1 !! "hello").getOrElse("_") should equal("world") + + val bytes = toBinary(actor1) + val actor2 = fromBinary(bytes) + actor2.start + (actor2 !! "hello").getOrElse("_") should equal("world") + } + +**Helper Type Class for actors with external serializer** + +Use the trait 'SerializerBasedActorFormat' for specifying serializers. + +.. code-block:: scala + + trait SerializerBasedActorFormat[T <: Actor] extends Format[T] { + val serializer: Serializer + def fromBinary(bytes: Array[Byte], act: T) = serializer.fromBinary(bytes, Some(act.self.actorClass)).asInstanceOf[T] + def toBinary(ac: T) = serializer.toBinary(ac) + } + +For a Java serializable actor: + +.. code-block:: scala + + @serializable class MyJavaSerializableActor extends Actor { + var count = 0 + + def receive = { + case "hello" => + count = count + 1 + self.reply("world " + count) + } + } + +Create a module for the type class .. + +.. code-block:: scala + + object BinaryFormatMyJavaSerializableActor { + implicit object MyJavaSerializableActorFormat extends SerializerBasedActorFormat[MyJavaSerializableActor] { + val serializer = Serializer.Java + } + } + +and serialize / de-serialize .. + +.. code-block:: scala + + it("should be able to serialize and de-serialize a stateful actor with a given serializer") { + import akka.serialization.ActorSerialization._ + import BinaryFormatMyJavaSerializableActor._ + + val actor1 = actorOf[MyJavaSerializableActor].start + (actor1 !! "hello").getOrElse("_") should equal("world 1") + (actor1 !! "hello").getOrElse("_") should equal("world 2") + + val bytes = toBinary(actor1) + val actor2 = fromBinary(bytes) + actor2.start + (actor2 !! "hello").getOrElse("_") should equal("world 3") + } + +**Serialization of a RemoteActorRef** +------------------------------------- + +You can serialize an 'ActorRef' to an immutable, network-aware Actor reference that can be freely shared across the network, a reference that "remembers" and stay mapped to its original Actor instance and host node, and will always work as expected. + +The 'RemoteActorRef' serialization is based upon Protobuf (Google Protocol Buffers) and you don't need to do anything to use it, it works on any 'ActorRef' (as long as the actor has **not** implemented one of the 'SerializableActor' traits, since then deep serialization will happen). + +Currently Akka will **not** autodetect an 'ActorRef' as part of your message and serialize it for you automatically, so you have to do that manually or as part of your custom serialization mechanisms. + +Here is an example of how to serialize an Actor. + +.. code-block:: scala + + val actor1 = actorOf[MyActor] + + val bytes = toBinary(actor1) + +To deserialize the 'ActorRef' to a 'RemoteActorRef' you need to use the 'fromBinaryToRemoteActorRef(bytes: Array[Byte])' method on the 'ActorRef' companion object: + +.. code-block:: scala + + import RemoteActorSerialization._ + val actor2 = fromBinaryToRemoteActorRef(bytes) + +You can also pass in a class loader to load the 'ActorRef' class and dependencies from: + +.. code-block:: scala + + import RemoteActorSerialization._ + val actor2 = fromBinaryToRemoteActorRef(bytes, classLoader) + +Deep serialization of a TypedActor +---------------------------------- + +Serialization of typed actors works almost the same way as untyped actors. You can serialize the whole actor deeply, e.g. both the 'proxied ActorRef' and the instance of its 'TypedActor'. + +Here is the example from above implemented as a TypedActor. + +^ + +Step 1: Define the actor +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + trait MyTypedActor { + def requestReply(s: String) : String + def oneWay() : Unit + } + + class MyTypedActorImpl extends TypedActor with MyTypedActor { + var count = 0 + + override def requestReply(message: String) : String = { + count = count + 1 + "world " + count + } + } + +Step 2: Implement the type class for the actor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + class MyTypedActorFormat extends Format[MyTypedActorImpl] { + def fromBinary(bytes: Array[Byte], act: MyTypedActorImpl) = { + val p = Serializer.Protobuf.fromBinary(bytes, Some(classOf[ProtobufProtocol.Counter])).asInstanceOf[ProtobufProtocol.Counter] + act.count = p.getCount + act + } + def toBinary(ac: MyTypedActorImpl) = ProtobufProtocol.Counter.newBuilder.setCount(ac.count).build.toByteArray + } + +Step 3: Import the type class module definition and serialize / de-serialize +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + val typedActor1 = TypedActor.newInstance(classOf[MyTypedActor], classOf[MyTypedActorImpl], 1000) + + val f = new MyTypedActorFormat + val bytes = toBinaryJ(typedActor1, f) + + val typedActor2: MyTypedActor = fromBinaryJ(bytes, f) //type hint needed + typedActor2.requestReply("hello") + +- + +Serialization of a remote typed ActorRef +---------------------------------------- + +To deserialize the TypedActor to a 'RemoteTypedActorRef' (an aspectwerkz proxy to a RemoteActorRef) you need to use the 'fromBinaryToRemoteTypedActorRef(bytes: Array[Byte])' method on 'RemoteTypedActorSerialization' object: + +.. code-block:: scala + + import RemoteTypedActorSerialization._ + val typedActor = fromBinaryToRemoteTypedActorRef(bytes) + + // you can also pass in a class loader + val typedActor2 = fromBinaryToRemoteTypedActorRef(bytes, classLoader) + +Compression +=========== + +Akka has a helper class for doing compression of binary data. This can be useful for example when storing data in one of the backing storages. It currently supports LZF which is a very fast compression algorithm suited for runtime dynamic compression. + +Here is an example of how it can be used: + +.. code-block:: scala + + import akka.serialization.Compression + + val bytes: Array[Byte] = ... + val compressBytes = Compression.LZF.compress(bytes) + val uncompressBytes = Compression.LZF.uncompress(compressBytes) + +Using the Serializable trait and Serializer class for custom serialization +========================================================================== + +If you are sending messages to a remote Actor and these messages implement one of the predefined interfaces/traits in the 'akka.serialization.Serializable.*' object, then Akka will transparently detect which serialization format it should use as wire protocol and will automatically serialize and deserialize the message according to this protocol. + +Each serialization interface/trait in +* akka.serialization.Serializable.* +> has a matching serializer in +* akka.serialization.Serializer.* + +Note however that if you are using one of the Serializable interfaces then you don’t have to do anything else in regard to sending remote messages. + +The ones currently supported are (besides the default which is regular Java serialization): +* ScalaJON (Scala only) +* JavaJSON (Java but some Scala structures) +* SBinary (Scala only) +* Protobuf (Scala and Java) + +Apart from the above, Akka also supports Scala object serialization through `SJSON `_ that implements APIs similar to 'akka.serialization.Serializer.*'. See the section on SJSON below for details. + +Protobuf +-------- + +Akka supports using `Google Protocol Buffers `_ to serialize your objects. Protobuf is a very efficient network serialization protocol which is also used internally by Akka. The remote actors understand Protobuf messages so if you just send them as they are they will be correctly serialized and unserialized. + +Here is an example. + +Let's say you have this Protobuf message specification that you want to use as message between remote actors. First you need to compiled it with 'protoc' compiler. + +.. code-block:: scala + + message ProtobufPOJO { + required uint64 id = 1; + required string name = 2; + required bool status = 3; + } + +When you compile the spec you will among other things get a message builder. You then use this builder to create the messages to send over the wire: + +.. code-block:: scala + + val result = remoteActor !! ProtobufPOJO.newBuilder + .setId(11) + .setStatus(true) + .setName("Coltrane") + .build + +The remote Actor can then receive the Protobuf message typed as-is: + +.. code-block:: scala + + class MyRemoteActor extends Actor { + def receive = { + case pojo: ProtobufPOJO => + val id = pojo.getId + val status = pojo.getStatus + val name = pojo.getName + ... + } + } + +JSON: Scala +----------- + +Use the akka.serialization.Serialization.ScalaJSON base class with its toJSON method. Akka’s Scala JSON is based upon the SJSON library. + +For your POJOs to be able to serialize themselves you have to extend the ScalaJSON[] trait as follows. JSON serialization is based on a type class protocol which you need to define for your own abstraction. The instance of the type class is defined as an implicit object which is used for serialization and de-serialization. You also need to implement the methods in terms of the APIs which sjson publishes. + +.. code-block:: scala + + import akka.serialization.Serializer + import akka.serialization.Serializable.ScalaJSON + import scala.reflect.BeanInfo + + case class MyMessage(val id: String, val value: Tuple2[String, Int]) extends ScalaJSON[MyMessage] { + // type class instance + implicit val MyMessageFormat: sjson.json.Format[MyMessage] = + asProduct2("id", "value")(MyMessage)(MyMessage.unapply(_).get) + + def toJSON: String = JsValue.toJson(tojson(this)) + def toBytes: Array[Byte] = tobinary(this) + def fromBytes(bytes: Array[Byte]) = frombinary[MyMessage](bytes) + def fromJSON(js: String) = fromjson[MyMessage](Js(js)) + } + + // sample test case + it("should be able to serialize and de-serialize MyMessage") { + val s = MyMessage("Target", ("cooker", 120)) + s.fromBytes(s.toBytes) should equal(s) + s.fromJSON(s.toJSON) should equal(s) + } + +Use akka.serialization.Serializer.ScalaJSON to do generic JSON serialization, e.g. serialize object that does not extend ScalaJSON using the JSON serializer. Serialization using Serializer can be done in two ways :- + +1. Type class based serialization (recommended) +2. Reflection based serialization + +We will discuss both of these techniques in this section. For more details refer to the discussion in the next section SJSON: Scala. + +Serializer API using type classes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Here are the steps that you need to follow: + +1. Define your class + +.. code-block:: scala + + case class MyMessage(val id: String, val value: Tuple2[String, Int]) + +2. Define the type class instance + +.. code-block:: scala + + import DefaultProtocol._ + implicit val MyMessageFormat: sjson.json.Format[MyMessage] = + asProduct2("id", "value")(MyMessage)(MyMessage.unapply(_).get) + +3. Serialize + +.. code-block:: scala + + import akka.serialization.Serializer.ScalaJSON + + val o = MyMessage("dg", ("akka", 100)) + fromjson[MyMessage](tojson(o)) should equal(o) + frombinary[MyMessage](tobinary(o)) should equal(o) + +Serializer API using reflection +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can also use the Serializer abstraction to serialize using reflection based serialization API of sjson. But we recommend using the type class based one, because reflection based serialization has limitations due to type erasure. Here's an example of reflection based serialization: + +.. code-block:: scala + + import akka.serialization.Serializer + import scala.reflect.BeanInfo + + @BeanInfo case class Foo(name: String) { + def this() = this(null) // default constructor is necessary for deserialization + } + + val foo = new Foo("bar") + val json = Serializer.ScalaJSON.out(foo) + + val fooCopy = Serializer.ScalaJSON.in(json) // returns a JsObject as an AnyRef + + val fooCopy2 = Serializer.ScalaJSON.in(new String(json)) // can also take a string as input + + val fooCopy3 = Serializer.ScalaJSON.in[Foo](json).asInstanceOf[Foo] + +Classes without a @BeanInfo annotation cannot be serialized as JSON. +So if you see something like that: + +.. code-block:: scala + + scala> Serializer.ScalaJSON.out(bar) + Serializer.ScalaJSON.out(bar) + java.lang.UnsupportedOperationException: Class class Bar not supported for conversion + at sjson.json.JsBean$class.toJSON(JsBean.scala:210) + at sjson.json.Serializer$SJSON$.toJSON(Serializer.scala:107) + at sjson.json.Serializer$SJSON$class.out(Serializer.scala:37) + at sjson.json.Serializer$SJSON$.out(Serializer.scala:107) + at akka.serialization.Serializer$ScalaJSON... + +it means, that you haven't got a @BeanInfo annotation on your class. + +You may also see this exception when trying to serialize a case class with out an attribute like this: + +.. code-block:: scala + + @BeanInfo case class Empty() // cannot be serialized + + SJSON: Scala +------------- + +SJSON supports serialization of Scala objects into JSON. It implements support for built in Scala structures like List, Map or String as well as custom objects. SJSON is available as an Apache 2 licensed project on Github `here `_. + +Example: I have a Scala object as .. + +.. code-block:: scala + + val addr = Address("Market Street", "San Francisco", "956871") + +where Address is a custom class defined by the user. Using SJSON, I can store it as JSON and retrieve as plain old Scala object. Here’s the simple assertion that validates the invariant. Note that during de-serialziation, the class name is specified. Hence what it gives back is an instance of Address. + +.. code-block:: scala + + addr should equal( + serializer.in[Address](serializer.out(addr))) + +Note, that the class needs to have a default constructor. Otherwise the deserialization into the specified class will fail. + +There are situations, particularly when writing generic persistence libraries in Akka, when the exact class is not known during de-serialization. Using SJSON I can get it as AnyRef or Nothing .. + +.. code-block:: scala + + serializer.in[AnyRef](serializer.out(addr)) + +or just as .. + +.. code-block:: scala + + serializer.in(serializer.out(addr)) + +What you get back from is a JsValue, an abstraction of the JSON object model. For details of JsValueimplementation, refer to `dispatch-json `_ that SJSON uses as the underlying JSON parser implementation. Once I have the JsValue model, I can use use extractors to get back individual attributes .. + +.. code-block:: scala + + val a = serializer.in[AnyRef](serializer.out(addr)) + + // use extractors + val c = 'city ? str + val c(_city) = a + _city should equal("San Francisco") + + val s = 'street ? str + val s(_street) = a + _street should equal("Market Street") + + val z = 'zip ? str + val z(_zip) = a + _zip should equal("956871") + +Serialization of Embedded Objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + SJSON supports serialization of Scala objects that have other embedded objects. Suppose you have the following Scala classes .. Here Contact has an embedded Address Map .. + +.. code-block:: scala + + @BeanInfo + case class Contact(name: String, + @(JSONTypeHint @field)(value = classOf[Address]) + addresses: Map[String, Address]) { + + override def toString = "name = " + name + " addresses = " + + addresses.map(a => a._1 + ":" + a._2.toString).mkString(",") + } + + @BeanInfo + case class Address(street: String, city: String, zip: String) { + override def toString = "address = " + street + "/" + city + "/" + zip + } + +With SJSON, I can do the following: + +.. code-block:: scala + + val a1 = Address("Market Street", "San Francisco", "956871") + val a2 = Address("Monroe Street", "Denver", "80231") + val a3 = Address("North Street", "Atlanta", "987671") + + val c = Contact("Bob", Map("residence" -> a1, "office" -> a2, "club" -> a3)) + val co = serializer.out(c) + + // with class specified + c should equal(serializer.in[Contact](co)) + + // no class specified + val a = serializer.in[AnyRef](co) + + // extract name + val n = 'name ? str + val n(_name) = a + "Bob" should equal(_name) + + // extract addresses + val addrs = 'addresses ? obj + val addrs(_addresses) = a + + // extract residence from addresses + val res = 'residence ? obj + val res(_raddr) = _addresses + + // make an Address bean out of _raddr + val address = JsBean.fromJSON(_raddr, Some(classOf[Address])) + a1 should equal(address) + + object r { def ># [T](f: JsF[T]) = f(a.asInstanceOf[JsValue]) } + + // still better: chain 'em up + "Market Street" should equal( + (r ># { ('addresses ? obj) andThen ('residence ? obj) andThen ('street ? str) })) + +^ + +Changing property names during serialization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + @BeanInfo + case class Book(id: Number, + title: String, @(JSONProperty @getter)(value = "ISBN") isbn: String) { + + override def toString = "id = " + id + " title = " + title + " isbn = " + isbn + } + +When this will be serialized out, the property name will be changed. + +.. code-block:: scala + + val b = new Book(100, "A Beautiful Mind", "012-456372") + val jsBook = Js(JsBean.toJSON(b)) + val expected_book_map = Map( + JsString("id") -> JsNumber(100), + JsString("title") -> JsString("A Beautiful Mind"), + JsString("ISBN") -> JsString("012-456372") + ) + +^ + +Serialization with ignore properties +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When serializing objects, some of the properties can be ignored declaratively. Consider the following class declaration: + +.. code-block:: scala + + @BeanInfo + case class Journal(id: BigDecimal, + title: String, + author: String, + @(JSONProperty @getter)(ignore = true) issn: String) { + + override def toString = + "Journal: " + id + "/" + title + "/" + author + + (issn match { + case null => "" + case _ => "/" + issn + }) + } + +The annotation @JSONProperty can be used to selectively ignore fields. When I serialize a Journal object out and then back in, the content of issn field will be null. + +.. code-block:: scala + + it("should ignore issn field") { + val j = Journal(100, "IEEE Computer", "Alex Payne", "012-456372") + serializer.in[Journal](serializer.out(j)).asInstanceOf[Journal].issn should equal(null) + } + +Similarly, we can ignore properties of an object **only** if they are null and not ignore otherwise. Just specify the annotation @JSONProperty as @JSONProperty {val ignoreIfNull = true}. + +^ + +Serialization with Type Hints for Generic Data Members +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Consider the following Scala class: + +.. code-block:: scala + + @BeanInfo + case class Contact(name: String, + @(JSONTypeHint @field)(value = classOf[Address]) + addresses: Map[String, Address]) { + + override def toString = "name = " + name + " addresses = " + + addresses.map(a => a._1 + ":" + a._2.toString).mkString(",") + } + +Because of erasure, you need to add the type hint declaratively through the annotation @JSONTypeHint that +SJSON will pick up during serialization. No we can say: + +.. code-block:: scala + + val c = Contact("Bob", Map("residence" -> a1, "office" -> a2, "club" -> a3)) + val co = serializer.out(c) + + it("should give an instance of Contact") { + c should equal(serializer.in[Contact](co)) + } + +With optional generic data members, we need to provide the hint to SJSON through another annotation@OptionTypeHint. + +.. code-block:: scala + + @BeanInfo + case class ContactWithOptionalAddr(name: String, + @(JSONTypeHint @field)(value = classOf[Address]) + @(OptionTypeHint @field)(value = classOf[Map[_,_]]) + addresses: Option[Map[String, Address]]) { + + override def toString = "name = " + name + " " + + (addresses match { + case None => "" + case Some(ad) => " addresses = " + ad.map(a => a._1 + ":" + a._2.toString).mkString(",") + }) + } + +Serialization works ok with optional members annotated as above. + +.. code-block:: scala + + describe("Bean with optional bean member serialization") { + it("should serialize with Option defined") { + val c = new ContactWithOptionalAddr("Debasish Ghosh", + Some(Map("primary" -> new Address("10 Market Street", "San Francisco, CA", "94111"), + "secondary" -> new Address("3300 Tamarac Drive", "Denver, CO", "98301")))) + c should equal( + serializer.in[ContactWithOptionalAddr](serializer.out(c))) + } + } + +You can also specify a custom ClassLoader while using SJSON serializer: + +.. code-block:: scala + + object SJSON { + val classLoader = //.. specify a custom classloader + } + + import SJSON._ + serializer.out(..) + + //.. + +Fighting Type Erasure +^^^^^^^^^^^^^^^^^^^^^ + +Because of type erasure, it's not always possible to infer the correct type during de-serialization of objects. Consider the following example: + +.. code-block:: scala + + abstract class A + @BeanInfo case class B(param1: String) extends A + @BeanInfo case class C(param1: String, param2: String) extends A + + @BeanInfo case class D(@(JSONTypeHint @field)(value = classOf[A])param1: List[A]) + +and the serialization code like the following: + +.. code-block:: scala + + object TestSerialize{ + def main(args: Array[String]) { + val test1 = new D(List(B("hello1"))) + val json = sjson.json.Serializer.SJSON.out(test1) + val res = sjson.json.Serializer.SJSON.in[D](json) + val res1: D = res.asInstanceOf[D] + println(res1) + } + } + +Note that the type hint on class D says A, but the actual instances that have been put into the object before serialization is one of the derived classes (B). During de-serialization, we have no idea of what can be inside D. The serializer.in API will fail since all hint it has is for A, which is abstract. In such cases, we need to handle the de-serialization by using extractors over the underlying data structure that we use for storing JSON objects, which is JsValue. Here's an example: + +.. code-block:: scala + + val test1 = new D(List(B("hello1"))) + val json = serializer.out(test1) + + // create a JsValue from the string + val js = Js(new String(json)) + + // extract the named list argument + val m = (Symbol("param1") ? list) + val m(_m) = js + + // extract the string within + val s = (Symbol("param1") ? str) + + // form a list of B's + val result = _m.map{ e => + val s(_s) = e + B(_s) + } + + // form a D + println("result = " + D(result)) + +The above snippet de-serializes correctly using extractors defined on JsValue. For more details on JsValue and the extractors, please refer to `dispatch-json `_ . + +**NOTE**: Serialization with SJSON is based on bean introspection. In the current version of Scala (2.8.0.Beta1 and 2.7.7) there is a bug where bean introspection does not work properly for classes enclosed within another class. Please ensure that the beans are the top level classes in your application. They can be within objects though. A ticket has been filed in the Scala Tracker and also fixed in the trunk. Here's the `ticket `_ . + +Type class based Serialization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If type erasure hits you, reflection based serialization may not be the right option. In fact the last section shows some of the scenarios which may not be possible to handle using reflection based serialization of sjson. sjson also supports type class based serialization where you can provide a custom protocol for serialization as part of the type class implementation. + +Here's a sample session at the REPL which shows the default serialization protocol of sjson: + +.. code-block:: scala + + scala> import sjson.json._ + import sjson.json._ + + scala> import DefaultProtocol._ + import DefaultProtocol._ + + scala> val str = "debasish" + str: java.lang.String = debasish + + scala> import JsonSerialization._ + import JsonSerialization._ + + scala> tojson(str) + res0: dispatch.json.JsValue = "debasish" + + scala> fromjson[String](res0) + res1: String = debasish + +You can use serialization of generic data types using the default protocol as well: + +.. code-block:: scala + + scala> val list = List(10, 12, 14, 18) + list: List[Int] = List(10, 12, 14, 18) + + scala> tojson(list) + res2: dispatch.json.JsValue = [10, 12, 14, 18] + + scala> fromjson[List[Int]](res2) + res3: List[Int] = List(10, 12, 14, 18) + +You can also define your own custom protocol, which as to be an implementation of the following type class: + +.. code-block:: scala + + trait Writes[T] { + def writes(o: T): JsValue + } + + trait Reads[T] { + def reads(json: JsValue): T + } + + trait Format[T] extends Writes[T] with Reads[T] + +Consider a case class and a custom protocol to serialize it into JSON. Here's the type class implementation: + +.. code-block:: scala + + object Protocols { + case class Person(lastName: String, firstName: String, age: Int) + object PersonProtocol extends DefaultProtocol { + import dispatch.json._ + import JsonSerialization._ + + implicit object PersonFormat extends Format[Person] { + def reads(json: JsValue): Person = json match { + case JsObject(m) => + Person(fromjson[String](m(JsString("lastName"))), + fromjson[String](m(JsString("firstName"))), fromjson[Int](m(JsString("age")))) + case _ => throw new RuntimeException("JsObject expected") + } + + def writes(p: Person): JsValue = + JsObject(List( + (tojson("lastName").asInstanceOf[JsString], tojson(p.lastName)), + (tojson("firstName").asInstanceOf[JsString], tojson(p.firstName)), + (tojson("age").asInstanceOf[JsString], tojson(p.age)) )) + } + } + } + +and the serialization in action in the REPL: + +.. code-block:: scala + + scala> import sjson.json._ + import sjson.json._ + + scala> import Protocols._ + import Protocols._ + + scala> import PersonProtocol._ + import PersonProtocol._ + + scala> val p = Person("ghosh", "debasish", 20) + p: sjson.json.Protocols.Person = Person(ghosh,debasish,20) + + scala> import JsonSerialization._ + import JsonSerialization._ + + scala> tojson[Person](p) + res1: dispatch.json.JsValue = {"lastName" : "ghosh", "firstName" : "debasish", "age" : 20} + + scala> fromjson[Person](res1) + res2: sjson.json.Protocols.Person = Person(ghosh,debasish,20) + +There are other nifty ways to implement case class serialization using sjson. For more details, have a look at the `wiki `_ for sjson. + +**JSON: Java** + +Use the akka.serialization.Serialization.JavaJSON base class with its toJSONmethod. Akka’s Java JSON is based upon the Jackson library. + +For your POJOs to be able to serialize themselves you have to extend the JavaJSON trait. + +.. code-block:: java + + class MyMessage extends JavaJSON { + private String name = null; + public MyMessage(String name) { + this.name = name; + } + public String getName() { + return name; + } + } + + MyMessage message = new MyMessage("json"); + String json = message.toJSON(); + SerializerFactory factory = new SerializerFactory(); + MyMessage messageCopy = factory.getJavaJSON().in(json); + +Use the akka.serialization.SerializerFactory.getJavaJSON to do generic JSONserialization, e.g. serialize object that does not extend JavaJSON using the JSON serializer. + +.. code-block:: java + + Foo foo = new Foo(); + SerializerFactory factory = new SerializerFactory(); + String json = factory.getJavaJSON().out(foo); + Foo fooCopy = factory.getJavaJSON().in(json, Foo.class); + +- + +SBinary: Scala +-------------- + +To serialize Scala structures you can use SBinary serializer. SBinary can serialize all primitives and most default Scala datastructures; such as List, Tuple, Map, Set, BigInt etc. + +Here is an example of using the akka.serialization.Serializer.SBinary serializer to serialize standard Scala library objects. + +.. code-block:: scala + + import akka.serialization.Serializer + import sbinary.DefaultProtocol._ // you always need to import these implicits + val users = List(("user1", "passwd1"), ("user2", "passwd2"), ("user3", "passwd3")) + val bytes = Serializer.SBinary.out(users) + val usersCopy = Serializer.SBinary.in(bytes, Some(classOf[List[Tuple2[String,String]]])) + +If you need to serialize your own user-defined objects then you have to do three things: +# Define an empty constructor +# Mix in the Serializable.SBinary[T] trait, and implement its methods: +## fromBytes(bytes: Array[Byte])[T] +## toBytes: Array[Byte] +# Create an implicit sbinary.Format[T] object for your class. Which means that you have to define its two methods: +## reads(in: Input): T; in which you read in all the fields in your object, using read[FieldType](in)and recreate it. +## writes(out: Output, value: T): Unit; in which you write out all the fields in your object, usingwrite[FieldType](out, value.field). + +Here is an example: +``_ +case class User(val usernamePassword: Tuple2[String, String], val email: String, val age: Int) + extends Serializable.SBinary[User] { + import sbinary.DefaultProtocol._ + import sbinary.Operations._ + + def this() = this(null, null, 0) + + implicit object UserFormat extends Format[User] { + def reads(in : Input) = User( + read[Tuple2[String, String]](in), + read[String](in), + read[Int](in)) + def writes(out: Output, value: User) = { + write[Tuple2[String, String]](out, value.usernamePassword) + write[String](out, value.email) + write[Int](out, value.age) + } + } + + def fromBytes(bytes: Array[Byte]) = fromByteArray[User](bytes) + + def toBytes: Array[Byte] = toByteArray(this) +} +``_ diff --git a/akka-docs/pending/servlet.rst b/akka-docs/pending/servlet.rst new file mode 100644 index 0000000000..6859657a72 --- /dev/null +++ b/akka-docs/pending/servlet.rst @@ -0,0 +1,41 @@ +Akka Servlet +============ + += + +Module stability: **STABLE** + +Akka has a servlet; ‘se.scalablesolutions.akka.comet.AkkaServlet’ that can use to deploy your Akka-based application in an external Servlet container. All you need to do is to add the servlet to the ‘web.xml’, set ‘$AKKA_HOME’ to the root of the distribution (needs the ‘$AKKA_HOME/config/*’ files) and add the JARs in the ‘$AKKA_HOME/lib’ to your classpath (or put them in the ‘WEB-INF/lib’ directory in the WAR file). + +Also, you need to add the Akka initialize/cleanup listener in web.xml + +.. code-block:: xml + + + ... + + se.scalablesolutions.akka.servlet.Initializer + + ... + + +And to support REST actors and/or comet actors, you need to add the following servlet declaration: + +``_ + +... + + Akka + + se.scalablesolutions.akka.comet.AkkaServlet + + se.scalablesolutions.akka.rest.AkkaServlet + + + * + Akka + +... + + +``_ diff --git a/akka-docs/pending/slf4j.rst b/akka-docs/pending/slf4j.rst new file mode 100644 index 0000000000..6b7ea8affb --- /dev/null +++ b/akka-docs/pending/slf4j.rst @@ -0,0 +1,24 @@ +SLF4J +===== + +This module is available in the 'akka-slf4j.jar'. It has one single dependency; the slf4j-api jar. + +Logging trait +------------- + +You can use the 'akka.event.slf4j.Logging' trait to mix in logging behavior into your classes and use the 'log' Logger member variable. But the preferred way is to use the event handler (see below). + +Event Handler +------------- + +This module also includes an SLF4J Event Handler that works with Akka's standar Event Handler. You enabled it in the 'event-handlers' element in akka.conf. Here you can also define the log level. + +.. code-block:: ruby + + akka { + event-handlers = ["akka.event.slf4j.Slf4jEventHandler"] + event-handler-level = "DEBUG" + } + +Read more about how to use the event handler `here `_. + diff --git a/akka-docs/pending/sponsors.rst b/akka-docs/pending/sponsors.rst new file mode 100644 index 0000000000..65faac6794 --- /dev/null +++ b/akka-docs/pending/sponsors.rst @@ -0,0 +1,15 @@ +****Sponsors **** +======================================================= + +Scalable Solutions +================== + +Scalable Solutions AB is commercial entity behind Akka, providing support, consulting and training around Akka. +``_ + +YourKit +======= + +YourKit is kindly supporting open source projects with its full-featured Java Profiler. +YourKit, LLC is the creator of innovative and intelligent tools for profiling Java and .NET applications. +Take a look at YourKit’s leading software products: `YourKit Java Profiler `_ and `YourKit .NET Profiler `_ diff --git a/akka-docs/pending/stm-java.rst b/akka-docs/pending/stm-java.rst new file mode 100644 index 0000000000..7873e38a7a --- /dev/null +++ b/akka-docs/pending/stm-java.rst @@ -0,0 +1,522 @@ +Software Transactional Memory (Java) +==================================== + +Module stability: **SOLID** + +Overview of STM +=============== + +An `STM `_ turns the Java heap into a transactional data set with begin/commit/rollback semantics. Very much like a regular database. It implements the first three letters in ACID; ACI: +* (failure) Atomicity: all changes during the execution of a transaction make it, or none make it. This only counts for transactional datastructures. +* Consistency: a transaction gets a consistent of reality (in Akka you get the Oracle version of the SERIALIZED isolation level). +* Isolated: changes made by concurrent execution transactions are not visible to each other. + +Generally, the STM is not needed that often when working with Akka. Some use-cases (that we can think of) are: +# When you really need composable message flows across many actors updating their **internal local** state but need them to do that atomically in one big transaction. Might not often, but when you do need this then you are screwed without it. +# When you want to share a datastructure across actors. +# When you need to use the persistence modules. + +Akka’s STM implements the concept in `Clojure’s `_ STM view on state in general. Please take the time to read `this excellent document `_ and view `this presentation `_ by Rich Hickey (the genius behind Clojure), since it forms the basis of Akka’s view on STM and state in general. + +The STM is based on Transactional References (referred to as Refs). Refs are memory cells, holding an (arbitrary) immutable value, that implement CAS (Compare-And-Swap) semantics and are managed and enforced by the STM for coordinated changes across many Refs. They are implemented using the excellent `Multiverse STM `_. + +Working with immutable collections can sometimes give bad performance due to extensive copying. Scala provides so-called persistent datastructures which makes working with immutable collections fast. They are immutable but with constant time access and modification. The use of structural sharing and an insert or update does not ruin the old structure, hence “persistent”. Makes working with immutable composite types fast. The persistent datastructures currently consist of a Map and Vector. + +Simple example +============== + +Here is a simple example of an incremental counter using STM. This shows creating a ``Ref``, a transactional reference, and then modifying it within a transaction, which is delimited by an ``Atomic`` anonymous inner class. + +.. code-block:: java + + import akka.stm.*; + + final Ref ref = new Ref(0); + + public int counter() { + return new Atomic() { + public Integer atomically() { + int inc = ref.get() + 1; + ref.set(inc); + return inc; + } + }.execute(); + } + + counter(); + // -> 1 + + counter(); + // -> 2 + +---- + +Ref +=== + +Refs (transactional references) are mutable references to values and through the STM allow the safe sharing of mutable data. To ensure safety the value stored in a Ref should be immutable. The value referenced by a Ref can only be accessed or swapped within a transaction. Refs separate identity from value. + +Creating a Ref +-------------- + +You can create a Ref with or without an initial value. + +.. code-block:: java + + import akka.stm.*; + + // giving an initial value + final Ref ref = new Ref(0); + + // specifying a type but no initial value + final Ref ref = new Ref(); + +Accessing the value of a Ref +---------------------------- + +Use ``get`` to access the value of a Ref. Note that if no initial value has been given then the value is initially ``null``. + +.. code-block:: java + + import akka.stm.*; + + final Ref ref = new Ref(0); + + Integer value = new Atomic() { + public Integer atomically() { + return ref.get(); + } + }.execute(); + // -> value = 0 + +Changing the value of a Ref +--------------------------- + +To set a new value for a Ref you can use ``set`` (or equivalently ``swap``), which sets the new value and returns the old value. + +.. code-block:: java + + import akka.stm.*; + + final Ref ref = new Ref(0); + + new Atomic() { + public Object atomically() { + return ref.set(5); + } + }.execute(); + +---- + +Transactions +============ + +A transaction is delimited using an ``Atomic`` anonymous inner class. + +.. code-block:: java + + new Atomic() { + public Object atomically() { + // ... + } + }.execute(); + +All changes made to transactional objects are isolated from other changes, all make it or non make it (so failure atomicity) and are consistent. With the AkkaSTM you automatically have the Oracle version of the SERIALIZED isolation level, lower isolation is not possible. To make it fully serialized, set the writeskew property that checks if a writeskew problem is allowed to happen. + +Retries +------- + +A transaction is automatically retried when it runs into some read or write conflict, until the operation completes, an exception (throwable) is thrown or when there are too many retries. When a read or writeconflict is encountered, the transaction uses a bounded exponential backoff to prevent cause more contention and give other transactions some room to complete. + +If you are using non transactional resources in an atomic block, there could be problems because a transaction can be retried. If you are using print statements or logging, it could be that they are called more than once. So you need to be prepared to deal with this. One of the possible solutions is to work with a deferred or compensating task that is executed after the transaction aborts or commits. + +Unexpected retries +------------------ + +It can happen for the first few executions that you get a few failures of execution that lead to unexpected retries, even though there is not any read or writeconflict. The cause of this is that speculative transaction configuration/selection is used. There are transactions optimized for a single transactional object, for 1..n and for n to unlimited. So based on the execution of the transaction, the system learns; it begins with a cheap one and upgrades to more expensive ones. Once it has learned, it will reuse this knowledge. It can be activated/deactivated using the speculative property on the TransactionFactoryBuilder. In most cases it is best use the default value (enabled) so you get more out of performance. + +Coordinated transactions and Transactors +---------------------------------------- + +If you need coordinated transactions across actors or threads then see `Transactors `_. + +Configuring transactions +------------------------ + +It's possible to configure transactions. The ``Atomic`` class can take a ``TransactionFactory``, which can determine properties of the transaction. A default transaction factory is used if none is specified. You can create a ``TransactionFactory`` with a ``TransactionFactoryBuilder``. + +Configuring transactions with a ``TransactionFactory``: + +.. code-block:: java + + import akka.stm.*; + + TransactionFactory txFactory = new TransactionFactoryBuilder() + .setReadonly(true) + .build(); + + new Atomic(txFactory) { + public Object atomically() { + // read only transaction + return ...; + } + }.execute(); + +The following settings are possible on a TransactionFactory: +* familyName - Family name for transactions. Useful for debugging because the familyName is shown in exceptions, logging and in the future also will be used for profiling. +* readonly - Sets transaction as readonly. Readonly transactions are cheaper and can be used to prevent modification to transactional objects. +* maxRetries - The maximum number of times a transaction will retry. +* timeout - The maximum time a transaction will block for. +* trackReads - Whether all reads should be tracked. Needed for blocking operations. Readtracking makes a transaction more expensive, but makes subsequent reads cheaper and also lowers the chance of a readconflict. +* writeSkew - Whether writeskew is allowed. Disable with care. +* blockingAllowed - Whether explicit retries are allowed. +* interruptible - Whether a blocking transaction can be interrupted if it is blocked. +* speculative - Whether speculative configuration should be enabled. +* quickRelease - Whether locks should be released as quickly as possible (before whole commit). +* propagation - For controlling how nested transactions behave. +* traceLevel - Transaction trace level. + +You can also specify the default values for some of these options in akka.conf. Here they are with their default values: + +:: + + stm { + max-retries = 1000 + timeout = 10 + write-skew = true + blocking-allowed = false + interruptible = false + speculative = true + quick-release = true + propagation = requires + trace-level = none + } + +Transaction lifecycle listeners +------------------------------- + +It's possible to have code that will only run on the successful commit of a transaction, or when a transaction aborts. You can do this by adding ``deferred`` or ``compensating`` blocks to a transaction. + +.. code-block:: java + + import akka.stm.*; + import static akka.stm.StmUtils.deferred; + import static akka.stm.StmUtils.compensating; + + new Atomic() { + public Object atomically() { + deferred(new Runnable() { + public void run() { + // executes when transaction commits + } + }); + compensating(new Runnable() { + public void run() { + // executes when transaction aborts + } + }); + // ... + return something; + } + }.execute(); + +Blocking transactions +--------------------- + +You can block in a transaction until a condition is met by using an explicit ``retry``. To use ``retry`` you also need to configure the transaction to allow explicit retries. + +Here is an example of using ``retry`` to block until an account has enough money for a withdrawal. This is also an example of using actors and STM together. + +.. code-block:: java + + import akka.stm.*; + + public class Transfer { + public Ref from; + public Ref to; + public double amount; + + public Transfer(Ref from, Ref to, double amount) { + this.from = from; + this.to = to; + this.amount = amount; + } + } + +.. code-block:: java + + import akka.stm.*; + import static akka.stm.StmUtils.retry; + import akka.actor.*; + import akka.util.FiniteDuration; + import java.util.concurrent.TimeUnit; + + public class Transferer extends UntypedActor { + TransactionFactory txFactory = new TransactionFactoryBuilder() + .setBlockingAllowed(true) + .setTrackReads(true) + .setTimeout(new FiniteDuration(60, TimeUnit.SECONDS)) + .build(); + + public void onReceive(Object message) throws Exception { + if (message instanceof Transfer) { + Transfer transfer = (Transfer) message; + final Ref from = transfer.from; + final Ref to = transfer.to; + final double amount = transfer.amount; + new Atomic(txFactory) { + public Object atomically() { + if (from.get() < amount) { + System.out.println("Transferer: not enough money - retrying"); + retry(); + } + System.out.println("Transferer: transferring"); + from.set(from.get() - amount); + to.set(to.get() + amount); + return null; + } + }.execute(); + } + } + } + +.. code-block:: java + + import akka.stm.*; + import akka.actor.*; + + final Ref account1 = new Ref(100.0); + final Ref account2 = new Ref(100.0); + + ActorRef transferer = Actors.actorOf(Transferer.class).start(); + + transferer.sendOneWay(new Transfer(account1, account2, 500.0)); + // Transferer: not enough money - retrying + + new Atomic() { + public Object atomically() { + return account1.set(account1.get() + 2000); + } + }.execute(); + // Transferer: transferring + + Double acc1 = new Atomic() { + public Double atomically() { + return account1.get(); + } + }.execute(); + + Double acc2 = new Atomic() { + public Double atomically() { + return account2.get(); + } + }.execute(); + + System.out.println("Account 1: " + acc1); + // Account 1: 1600.0 + + System.out.println("Account 2: " + acc2); + // Account 2: 600.0 + + transferer.stop(); + +Alternative blocking transactions +--------------------------------- + +You can also have two alternative blocking transactions, one of which can succeed first, with ``EitherOrElse``. + +.. code-block:: java + + import akka.stm.*; + + public class Branch { + public Ref left; + public Ref right; + public int amount; + + public Branch(Ref left, Ref right, int amount) { + this.left = left; + this.right = right; + this.amount = amount; + } + } + +.. code-block:: java + + import akka.stm.*; + import static akka.stm.StmUtils.retry; + import akka.actor.*; + import akka.util.FiniteDuration; + import java.util.concurrent.TimeUnit; + + public class Brancher extends UntypedActor { + TransactionFactory txFactory = new TransactionFactoryBuilder() + .setBlockingAllowed(true) + .setTrackReads(true) + .setTimeout(new FiniteDuration(60, TimeUnit.SECONDS)) + .build(); + + public void onReceive(Object message) throws Exception { + if (message instanceof Branch) { + Branch branch = (Branch) message; + final Ref left = branch.left; + final Ref right = branch.right; + final double amount = branch.amount; + new Atomic(txFactory) { + public Integer atomically() { + return new EitherOrElse() { + public Integer either() { + if (left.get() < amount) { + System.out.println("not enough on left - retrying"); + retry(); + } + System.out.println("going left"); + return left.get(); + } + public Integer orElse() { + if (right.get() < amount) { + System.out.println("not enough on right - retrying"); + retry(); + } + System.out.println("going right"); + return right.get(); + } + }.execute(); + } + }.execute(); + } + } + } + +.. code-block:: java + + import akka.stm.*; + import akka.actor.*; + + final Ref left = new Ref(100); + final Ref right = new Ref(100); + + ActorRef brancher = Actors.actorOf(Brancher.class).start(); + + brancher.sendOneWay(new Branch(left, right, 500)); + // not enough on left - retrying + // not enough on right - retrying + + new Atomic() { + public Object atomically() { + return right.set(right.get() + 1000); + } + }.execute(); + // going right + + brancher.stop(); + +---- + +Transactional datastructures +============================ + +Akka provides two datastructures that are managed by the STM. +* TransactionalMap +* TransactionalVector + +TransactionalMap and TransactionalVector look like regular mutable datastructures, they even implement the standard Scala 'Map' and 'RandomAccessSeq' interfaces, but they are implemented using persistent datastructures and managed references under the hood. Therefore they are safe to use in a concurrent environment. Underlying TransactionalMap is HashMap, an immutable Map but with near constant time access and modification operations. Similarly TransactionalVector uses a persistent Vector. See the Persistent Datastructures section below for more details. + +Like managed references, TransactionalMap and TransactionalVector can only be modified inside the scope of an STM transaction. + +Here is an example of creating and accessing a TransactionalMap: + +.. code-block:: java + + import akka.stm.*; + + // assuming a User class + + final TransactionalMap users = new TransactionalMap(); + + // fill users map (in a transaction) + new Atomic() { + public Object atomically() { + users.put("bill", new User("bill")); + users.put("mary", new User("mary")); + users.put("john", new User("john")); + return null; + } + }.execute(); + + // access users map (in a transaction) + User user = new Atomic() { + public User atomically() { + return users.get("bill").get(); + } + }.execute(); + +Here is an example of creating and accessing a TransactionalVector: + +.. code-block:: java + + import akka.stm.*; + + // assuming an Address class + + final TransactionalVector
addresses = new TransactionalVector
(); + + // fill addresses vector (in a transaction) + new Atomic() { + public Object atomically() { + addresses.add(new Address("somewhere")); + addresses.add(new Address("somewhere else")); + return null; + } + }.execute(); + + // access addresses vector (in a transaction) + Address address = new Atomic
() { + public Address atomically() { + return addresses.get(0); + } + }.execute(); + +---- + +Persistent datastructures +========================= + +Akka's STM should only be used with immutable data. This can be costly if you have large datastructures and are using a naive copy-on-write. In order to make working with immutable datastructures fast enough Scala provides what are called Persistent Datastructures. There are currently two different ones: +* HashMap (`scaladoc `_) +* Vector (`scaladoc `_) + +They are immutable and each update creates a completely new version but they are using clever structural sharing in order to make them almost as fast, for both read and update, as regular mutable datastructures. + +This illustration is taken from Rich Hickey's presentation. Copyright Rich Hickey 2009. + +``_ + +---- + +JTA integration +=============== + +The STM has JTA (Java Transaction API) integration. This means that it will, if enabled, hook in to JTA and start a JTA transaction when the STM transaction is started. It will also rollback the STM transaction if the JTA transaction has failed and vice versa. This does not mean that the STM is made durable, if you need that you should use one of the `persistence modules `_. It simply means that the STM will participate and interact with and external JTA provider, for example send a message using JMS atomically within an STM transaction, or use Hibernate to persist STM managed data etc. + +Akka also has an API for using JTA explicitly. Read the `section on JTA `_ for details. + +You can enable JTA support in the 'stm' section in the config: + +:: + + stm { + jta-aware = off # 'on' means that if there JTA Transaction Manager available then the STM will + # begin (or join), commit or rollback the JTA transaction. Default is 'off'. + } + +You also have to configure which JTA provider to use etc in the 'jta' config section: + +``_ + jta { + provider = "from-jndi" # Options: "from-jndi" (means that Akka will try to detect a TransactionManager in the JNDI) + # "atomikos" (means that Akka will use the Atomikos based JTA impl in 'akka-jta', + # e.g. you need the akka-jta JARs on classpath). + timeout = 60 + } +``_ diff --git a/akka-docs/pending/stm-scala.rst b/akka-docs/pending/stm-scala.rst new file mode 100644 index 0000000000..2ee7f08f11 --- /dev/null +++ b/akka-docs/pending/stm-scala.rst @@ -0,0 +1,544 @@ +Software Transactional Memory (Scala) +===================================== + +Module stability: **SOLID** + +Overview of STM +=============== + +An `STM `_ turns the Java heap into a transactional data set with begin/commit/rollback semantics. Very much like a regular database. It implements the first three letters in ACID; ACI: +* Atomic +* Consistent +* Isolated + +Generally, the STM is not needed very often when working with Akka. Some use-cases (that we can think of) are: +# When you really need composable message flows across many actors updating their **internal local** state but need them to do that atomically in one big transaction. Might not often, but when you do need this then you are screwed without it. +# When you want to share a datastructure across actors. +# When you need to use the persistence modules. + +Akka’s STM implements the concept in `Clojure’s `_ STM view on state in general. Please take the time to read `this excellent document `_ and view `this presentation `_ by Rich Hickey (the genius behind Clojure), since it forms the basis of Akka’s view on STM and state in general. + +The STM is based on Transactional References (referred to as Refs). Refs are memory cells, holding an (arbitrary) immutable value, that implement CAS (Compare-And-Swap) semantics and are managed and enforced by the STM for coordinated changes across many Refs. They are implemented using the excellent `Multiverse STM `_. + +Working with immutable collections can sometimes give bad performance due to extensive copying. Scala provides so-called persistent datastructures which makes working with immutable collections fast. They are immutable but with constant time access and modification. The use of structural sharing and an insert or update does not ruin the old structure, hence “persistent”. Makes working with immutable composite types fast. The persistent datastructures currently consist of a Map and Vector. + +Simple example +============== + +Here is a simple example of an incremental counter using STM. This shows creating a ``Ref``, a transactional reference, and then modifying it within a transaction, which is delimited by ``atomic``. + +.. code-block:: scala + + import akka.stm._ + + val ref = Ref(0) + + def counter = atomic { + ref alter (_ + 1) + } + + counter + // -> 1 + + counter + // -> 2 + +---- + +Ref +=== + +Refs (transactional references) are mutable references to values and through the STM allow the safe sharing of mutable data. Refs separate identity from value. To ensure safety the value stored in a Ref should be immutable (they can of course contain refs themselves). The value referenced by a Ref can only be accessed or swapped within a transaction. If a transaction is not available, the call will be executed in its own transaction (the call will be atomic). This is a different approach than the Clojure Refs, where a missing transaction results in an error. + +Creating a Ref +-------------- + +You can create a Ref with or without an initial value. + +.. code-block:: scala + + import akka.stm._ + + // giving an initial value + val ref = Ref(0) + + // specifying a type but no initial value + val ref = Ref[Int] + +Accessing the value of a Ref +---------------------------- + +Use ``get`` to access the value of a Ref. Note that if no initial value has been given then the value is initially ``null``. + +.. code-block:: scala + + import akka.stm._ + + val ref = Ref(0) + + atomic { + ref.get + } + // -> 0 + +If there is a chance that the value of a Ref is null then you can use ``opt``, which will create an Option, either Some(value) or None, or you can provide a default value with ``getOrElse``. You can also check for null using ``isNull``. + +.. code-block:: scala + + import akka.stm._ + + val ref = Ref[Int] + + atomic { + ref.opt // -> None + ref.getOrElse(0) // -> 0 + ref.isNull // -> true + } + +Changing the value of a Ref +--------------------------- + +To set a new value for a Ref you can use ``set`` (or equivalently ``swap``), which sets the new value and returns the old value. + +.. code-block:: scala + + import akka.stm._ + + val ref = Ref(0) + + atomic { + ref.set(5) + } + // -> 0 + + atomic { + ref.get + } + // -> 5 + +You can also use ``alter`` which accepts a function that takes the old value and creates a new value of the same type. + +.. code-block:: scala + + import akka.stm._ + + val ref = Ref(0) + + atomic { + ref alter (_ + 5) + } + // -> 5 + + val inc = (i: Int) => i + 1 + + atomic { + ref alter inc + } + // -> 6 + +Refs in for-comprehensions +-------------------------- + +Ref is monadic and can be used in for-comprehensions. + +.. code-block:: scala + + import akka.stm._ + + val ref = Ref(1) + + atomic { + for (value <- ref) { + // do something with value + } + } + + val anotherRef = Ref(3) + + atomic { + for { + value1 <- ref + value2 <- anotherRef + } yield (value1 + value2) + } + // -> Ref(4) + + val emptyRef = Ref[Int] + + atomic { + for { + value1 <- ref + value2 <- emptyRef + } yield (value1 + value2) + } + // -> Ref[Int] + +---- + +Transactions +============ + +A transaction is delimited using ``atomic``. + +.. code-block:: scala + + atomic { + // ... + } + +Coordinated transactions and Transactors +---------------------------------------- + +If you need coordinated transactions across actors or threads then see `Transactors `_. + +Configuring transactions +------------------------ + +It's possible to configure transactions. The ``atomic`` method can take an implicit or explicit ``TransactionFactory``, which can determine properties of the transaction. A default transaction factory is used if none is specified explicitly or there is no implicit ``TransactionFactory`` in scope. + +Configuring transactions with an **implicit** ``TransactionFactory``: + +.. code-block:: scala + + import akka.stm._ + + implicit val txFactory = TransactionFactory(readonly = true) + + atomic { + // read only transaction + } + +Configuring transactions with an **explicit** ``TransactionFactory``: + +.. code-block:: scala + + import akka.stm._ + + val txFactory = TransactionFactory(readonly = true) + + atomic(txFactory) { + // read only transaction + } + +The following settings are possible on a TransactionFactory: +* familyName - Family name for transactions. Useful for debugging. +* readonly - Sets transaction as readonly. Readonly transactions are cheaper. +* maxRetries - The maximum number of times a transaction will retry. +* timeout - The maximum time a transaction will block for. +* trackReads - Whether all reads should be tracked. Needed for blocking operations. +* writeSkew - Whether writeskew is allowed. Disable with care. +* blockingAllowed - Whether explicit retries are allowed. +* interruptible - Whether a blocking transaction can be interrupted. +* speculative - Whether speculative configuration should be enabled. +* quickRelease - Whether locks should be released as quickly as possible (before whole commit). +* propagation - For controlling how nested transactions behave. +* traceLevel - Transaction trace level. + +You can also specify the default values for some of these options in akka.conf. Here they are with their default values: + +:: + + stm { + max-retries = 1000 + timeout = 10 + write-skew = true + blocking-allowed = false + interruptible = false + speculative = true + quick-release = true + propagation = requires + trace-level = none + } + +You can also determine at which level a transaction factory is shared or not shared, which affects the way in which the STM can optimise transactions. + +Here is a shared transaction factory for all instances of an actor. + +.. code-block:: scala + + import akka.actor._ + import akka.stm._ + + object MyActor { + implicit val txFactory = TransactionFactory(readonly = true) + } + + class MyActor extends Actor { + import MyActor.txFactory + + def receive = { + case message: String => + atomic { + // read only transaction + } + } + } + +Here's a similar example with an individual transaction factory for each instance of an actor. + +.. code-block:: scala + + import akka.actor._ + import akka.stm._ + + class MyActor extends Actor { + implicit val txFactory = TransactionFactory(readonly = true) + + def receive = { + case message: String => + atomic { + // read only transaction + } + } + } + +Transaction lifecycle listeners +------------------------------- + +It's possible to have code that will only run on the successful commit of a transaction, or when a transaction aborts. You can do this by adding ``deferred`` or ``compensating`` blocks to a transaction. + +.. code-block:: scala + + import akka.stm._ + + atomic { + deferred { + // executes when transaction commits + } + compensating { + // executes when transaction aborts + } + } + +Blocking transactions +--------------------- + +You can block in a transaction until a condition is met by using an explicit ``retry``. To use ``retry`` you also need to configure the transaction to allow explicit retries. + +Here is an example of using ``retry`` to block until an account has enough money for a withdrawal. This is also an example of using actors and STM together. + +.. code-block:: scala + + import akka.stm._ + import akka.actor._ + import akka.util.duration._ + import akka.util.Logging + + type Account = Ref[Double] + + case class Transfer(from: Account, to: Account, amount: Double) + + class Transferer extends Actor with Logging { + implicit val txFactory = TransactionFactory(blockingAllowed = true, trackReads = true, timeout = 60 seconds) + + def receive = { + case Transfer(from, to, amount) => + atomic { + if (from.get < amount) { + log.info("not enough money - retrying") + retry + } + log.info("transferring") + from alter (_ - amount) + to alter (_ + amount) + } + } + } + + val account1 = Ref(100.0) + val account2 = Ref(100.0) + + val transferer = Actor.actorOf(new Transferer).start + + transferer ! Transfer(account1, account2, 500.0) + // INFO Transferer: not enough money - retrying + + atomic { account1 alter (_ + 2000) } + // INFO Transferer: transferring + + atomic { account1.get } + // -> 1600.0 + + atomic { account2.get } + // -> 600.0 + + transferer.stop + +Alternative blocking transactions +--------------------------------- + +You can also have two alternative blocking transactions, one of which can succeed first, with ``either-orElse``. + +.. code-block:: scala + + import akka.stm._ + import akka.actor._ + import akka.util.duration._ + import akka.util.Logging + + case class Branch(left: Ref[Int], right: Ref[Int], amount: Int) + + class Brancher extends Actor with Logging { + implicit val txFactory = TransactionFactory(blockingAllowed = true, trackReads = true, timeout = 60 seconds) + + def receive = { + case Branch(left, right, amount) => + atomic { + either { + if (left.get < amount) { + log.info("not enough on left - retrying") + retry + } + log.info("going left") + } orElse { + if (right.get < amount) { + log.info("not enough on right - retrying") + retry + } + log.info("going right") + } + } + } + } + + val ref1 = Ref(0) + val ref2 = Ref(0) + + val brancher = Actor.actorOf(new Brancher).start + + brancher ! Branch(ref1, ref2, 1) + // INFO Brancher: not enough on left - retrying + // INFO Brancher: not enough on right - retrying + + atomic { ref2 alter (_ + 1) } + // INFO Brancher: not enough on left - retrying + // INFO Brancher: going right + + brancher.stop + +---- + +Transactional datastructures +============================ + +Akka provides two datastructures that are managed by the STM. +* TransactionalMap +* TransactionalVector + +TransactionalMap and TransactionalVector look like regular mutable datastructures, they even implement the standard Scala 'Map' and 'RandomAccessSeq' interfaces, but they are implemented using persistent datastructures and managed references under the hood. Therefore they are safe to use in a concurrent environment. Underlying TransactionalMap is HashMap, an immutable Map but with near constant time access and modification operations. Similarly TransactionalVector uses a persistent Vector. See the Persistent Datastructures section below for more details. + +Like managed references, TransactionalMap and TransactionalVector can only be modified inside the scope of an STM transaction. + +*IMPORTANT*: There have been some problems reported when using transactional datastructures with 'lazy' initialization. Avoid that. + +Here is how you create these transactional datastructures: + +.. code-block:: scala + + import akka.stm._ + + // assuming something like + case class User(name: String) + case class Address(location: String) + + // using initial values + val map = TransactionalMap("bill" -> User("bill")) + val vector = TransactionalVector(Address("somewhere")) + + // specifying types + val map = TransactionalMap[String, User] + val vector = TransactionalVector[Address] + +TransactionalMap and TransactionalVector wrap persistent datastructures with transactional references and provide a standard Scala interface. This makes them convenient to use. + +Here is an example of using a Ref and a HashMap directly: + +.. code-block:: scala + + import akka.stm._ + import scala.collection.immutable.HashMap + + case class User(name: String) + + val ref = Ref(HashMap[String, User]()) + + atomic { + val users = ref.get + val newUsers = users + ("bill" -> User("bill")) // creates a new HashMap + ref.swap(newUsers) + } + + atomic { + ref.get.apply("bill") + } + // -> User("bill") + +Here is the same example using TransactionalMap: + +.. code-block:: scala + + import akka.stm._ + + case class User(name: String) + + val users = TransactionalMap[String, User] + + atomic { + users += "bill" -> User("bill") + } + + atomic { + users("bill") + } + // -> User("bill") + +---- + +Persistent datastructures +========================= + +Akka's STM should only be used with immutable data. This can be costly if you have large datastructures and are using a naive copy-on-write. In order to make working with immutable datastructures fast enough Scala provides what are called Persistent Datastructures. There are currently two different ones: +* HashMap (`scaladoc `_) +* Vector (`scaladoc `_) + +They are immutable and each update creates a completely new version but they are using clever structural sharing in order to make them almost as fast, for both read and update, as regular mutable datastructures. + +This illustration is taken from Rich Hickey's presentation. Copyright Rich Hickey 2009. + +``_ + +---- + +JTA integration +=============== + +The STM has JTA (Java Transaction API) integration. This means that it will, if enabled, hook in to JTA and start a JTA transaction when the STM transaction is started. It will also rollback the STM transaction if the JTA transaction has failed and vice versa. This does not mean that the STM is made durable, if you need that you should use one of the `persistence modules `_. It simply means that the STM will participate and interact with and external JTA provider, for example send a message using JMS atomically within an STM transaction, or use Hibernate to persist STM managed data etc. + +Akka also has an API for using JTA explicitly. Read the `section on JTA `_ for details. + +You can enable JTA support in the 'stm' section in the config: + +:: + + stm { + jta-aware = off # 'on' means that if there JTA Transaction Manager available then the STM will + # begin (or join), commit or rollback the JTA transaction. Default is 'off'. + } + +You also have to configure which JTA provider to use etc in the 'jta' config section: + +:: + + jta { + provider = "from-jndi" # Options: "from-jndi" (means that Akka will try to detect a TransactionManager in the JNDI) + # "atomikos" (means that Akka will use the Atomikos based JTA impl in 'akka-jta', + # e.g. you need the akka-jta JARs on classpath). + timeout = 60 + } + +---- + +Ants simulation sample +====================== + +One fun and very enlightening visual demo of STM, actors and transactional references is the `Ant simulation sample `_. I encourage you to run it and read through the code since it's a good example of using actors with STM. diff --git a/akka-docs/pending/stm.rst b/akka-docs/pending/stm.rst new file mode 100644 index 0000000000..c84ca4e6bb --- /dev/null +++ b/akka-docs/pending/stm.rst @@ -0,0 +1,60 @@ +Akka STM + +The Akka Software Transactional Memory implementation + +**Read consistency** +^^^^^^^^^^^^^^^^^^^^ + +Read consistency is that all value + +**Read concistency and MVCC** +***************************** + +A lot of STM (like the Clojure STM) implementations are Multi Version Concurrency Control Based (MVCC) based (TL2 of david dice could be seen as MVCC). + +To provide read consistency, every ref is augmented with a version field (a long). There also is a logical clock (an AtomicLong for instance) that is incremented every time a transaction does a commit (there are some optimizations) and on all refs written, the version of the ref is updated to this new clock value. + +If a transaction begins, it reads the current version of the clock and makes sure that the version of the refs it reads, are equal or lower than the version of the transaction. If the transaction encounters a ref with a higher value, the transaction is aborted and retried. + +MVCC STM’s are relatively simple to write and have some very nice properties: +# readers don’t block writers +# writers don’t block readers +# persistent data-structures are very easy to write since a log can be added to each ref containing older versions of the data, + +The problem with MVCC however is that the central clock forms a contention point that makes independent transactional data-structures not linearly scalable. todo: give example of scalability with MVCC. + +So even if you have 2 Threads having their private transactional Ref (so there is no visible contention), underwater the transaction still are going to contend for the clock. + +**Read consistency and the Akka STM** +************************************* + +The AkkaSTM (that is build on top of the Multiverse 0.7 STM) and from Akka 1.1 it doesn’t use a MVCC based implementation because of the scalability limiting central clock. + +It uses 2 different mechanisms: +1) For very short transactions it does a full conflict scan every time a new ref is read. Doing a full conflict scan sounds expensive, but it only involves volatile reads. +2) For longer transactions it uses semi visible reads. Every time a read is done, the surplus of readers is incremented and stored in the ref. Once the transaction aborts or commits, the surplus is lowered again. If a transaction does an update, and sees that there is a surplus of readers, it increments a conflict counter. This conflict counter is checked every time a transaction reads a new ref. If it hasn’t changed, no full conflict scan is needed. If it has changed, a full conflict scan is required. If a conflict is detected, the transaction is aborted and retried. This technique is called a semi visible read (we don’t know which transactions are possibly going to encounter a conflict, but we do know if there is at least one possible conflict). + +There are 2 important optimizations to this design: +# Eager full conflict scan +# Read biases refs + +**Eager full conflict scan** +**************************** + +The reasons why short transactions always do a full conflict scan is that doing semi visible reads, relies doing more expensive synchronization operations (e.g. doing a cas to increase the surplus of readers, or doing a cas to decrease it). + +**Read biased vs update biased.** +********************************* + +The problem with semi visible reads is that certain structures (e.g. the root of a tree) can form a contention point (because of the arrives/departs) even though it mostly is read. To reduce contention, a ref can become read biased after a certain number of reads by transactions that use semi visible reads is done. Once it has become read biased, no arrives and departs are required any more, but once it the Ref is updated it will always increment the conflict counter because it doesn’t know if there are any conflicting readers. + +Visible reads, semi visible reads +Read tracking + +strict isolation +eager conflict detection +deferred write, no dirty read possible + +isolation level +optimistic +various levels of pessimistic behavior diff --git a/akka-docs/pending/team.rst b/akka-docs/pending/team.rst new file mode 100644 index 0000000000..cdc97244bd --- /dev/null +++ b/akka-docs/pending/team.rst @@ -0,0 +1,22 @@ +Team +===== + +|| **Name** || **Role** || **Email** || +|| Jonas Bonér || Founder, Despot, Committer || jonas AT jonasboner DOT com || +|| Viktor Klang || Bad cop, Committer || viktor DOT klang AT gmail DOT com || +|| Debasish Ghosh || Committer || dghosh AT acm DOT org || +|| Ross McDonald || Alumni || rossajmcd AT gmail DOT com || +|| Eckhart Hertzler || Alumni || || +|| Mikael Högqvist || Alumni || || +|| Tim Perrett || Alumni || || +|| Jeanfrancois Arcand || Alumni || jfarcand AT apache DOT org || +|| Martin Krasser || Committer || krasserm AT googlemail DOT com || +|| Jan Van Besien || Alumni || || +|| Michael Kober || Committer || || +|| Peter Vlugter || Committer || || +|| Peter Veentjer || Committer || || +|| Irmo Manie || Committer || || +|| Heiko Seeberger || Committer || || +|| Hiram Chirino || Committer || || +|| Scott Clasen || Committer || || +|| Roland Kuhn || Committer || || diff --git a/akka-docs/pending/test.rst b/akka-docs/pending/test.rst new file mode 100644 index 0000000000..c845cb36d2 --- /dev/null +++ b/akka-docs/pending/test.rst @@ -0,0 +1,55 @@ +Testing of Akka +=============== + +Introduction +============ + +Testing concurrent code using time-outs (like Thread.sleep(..)) is usually a bad idea since it is both slow and error-prone. There are some frameworks that can help, some are listed below. + +Testing Actor Interaction +========================= + +For Actor interaction, making sure certain message arrives in time etc. we recommend you use Akka's built-in `TestKit `_. If you want to roll your own, you will find helpful abstractions in the `java.util.concurrent` package, most notably `BlockingQueue` and `CountDownLatch`. + +Unit testing of Actors +====================== + +If you need to unit test your actors then the best way to do that would be to decouple it from the Actor by putting it in a regular class/trait, test that, and then mix in the Actor trait when you want to create actors. This is necessary since you can't instantiate an Actor class directly with 'new'. But note that you can't test Actor interaction with this, but only local Actor implementation. Here is an example: + +.. code-block:: scala + + // test this + class MyLogic { + def blabla: Unit = { + ... + } + } + + // run this + actorOf(new MyLogic with Actor { + def receive = { + case Bla => blabla + } + }) + +...or define a non-anonymous MyLogicActor class. + +Akka Expect +=========== + +Expect mimic for testing Akka actors. + +``_ + +Awaitility +========== + +Not a Akka specific testing framework but a nice DSL for testing asynchronous code. +Scala and Java API. + +``_ + +ScalaTest Conductor +=================== + +``_ diff --git a/akka-docs/pending/testkit-example.rst b/akka-docs/pending/testkit-example.rst new file mode 100644 index 0000000000..b270368183 --- /dev/null +++ b/akka-docs/pending/testkit-example.rst @@ -0,0 +1,138 @@ +Ray Roestenburg's example code from `his blog `_. +``_ +package unit.akka + +import org.scalatest.matchers.ShouldMatchers +import org.scalatest.{WordSpec, BeforeAndAfterAll} +import akka.actor.Actor._ +import akka.util.duration._ +import akka.util.TestKit +import java.util.concurrent.TimeUnit +import akka.actor.{ActorRef, Actor} +import util.Random + +/** + * a Test to show some TestKit examples + */ + +class TestKitUsageSpec extends WordSpec with BeforeAndAfterAll with ShouldMatchers with TestKit { + val echoRef = actorOf(new EchoActor).start + val forwardRef = actorOf(new ForwardingActor(testActor)).start + val filterRef = actorOf(new FilteringActor(testActor)).start + val randomHead = Random.nextInt(6) + val randomTail = Random.nextInt(10) + val headList = List().padTo(randomHead, "0") + val tailList = List().padTo(randomTail, "1") + val seqRef = actorOf(new SequencingActor(testActor, headList, tailList)).start + + override protected def afterAll(): scala.Unit = { + stopTestActor + echoRef.stop + forwardRef.stop + filterRef.stop + seqRef.stop + } + + "An EchoActor" should { + "Respond with the same message it receives" in { + within(100 millis) { + echoRef ! "test" + expectMsg("test") + } + } + } + "A ForwardingActor" should { + "Forward a message it receives" in { + within(100 millis) { + forwardRef ! "test" + expectMsg("test") + } + } + } + "A FilteringActor" should { + "Filter all messages, except expected messagetypes it receives" in { + var messages = List[String]() + within(100 millis) { + filterRef ! "test" + expectMsg("test") + filterRef ! 1 + expectNoMsg + filterRef ! "some" + filterRef ! "more" + filterRef ! 1 + filterRef ! "text" + filterRef ! 1 + + receiveWhile(500 millis) { + case msg: String => messages = msg :: messages + } + } + messages.length should be(3) + messages.reverse should be(List("some", "more", "text")) + } + } + "A SequencingActor" should { + "receive an interesting message at some point " in { + within(100 millis) { + seqRef ! "something" + ignoreMsg { + case msg: String => msg != "something" + } + expectMsg("something") + ignoreMsg { + case msg: String => msg == "1" + } + expectNoMsg + } + } + } +} + +/** + * An Actor that echoes everything you send to it + */ +class EchoActor extends Actor { + def receive = { + case msg => { + self.reply(msg) + } + } +} + +/** + * An Actor that forwards every message to a next Actor + */ +class ForwardingActor(next: ActorRef) extends Actor { + def receive = { + case msg => { + next ! msg + } + } +} + +/** + * An Actor that only forwards certain messages to a next Actor + */ +class FilteringActor(next: ActorRef) extends Actor { + def receive = { + case msg: String => { + next ! msg + } + case _ => None + } +} + +/** + * An actor that sends a sequence of messages with a random head list, an interesting value and a random tail list + * The idea is that you would like to test that the interesting value is received and that you cant be bothered with the rest + */ +class SequencingActor(next: ActorRef, head: List[String], tail: List[String]) extends Actor { + def receive = { + case msg => { + head map (next ! _) + next ! msg + tail map (next ! _) + } + } +} +``_ diff --git a/akka-docs/pending/testkit.rst b/akka-docs/pending/testkit.rst new file mode 100644 index 0000000000..d2d177948f --- /dev/null +++ b/akka-docs/pending/testkit.rst @@ -0,0 +1,49 @@ +Actor TestKit +============= + +Module Stability: **In Progress** + +Overview +-------- + +Testing actors comprises several aspects, which can have different weight according to the concrete project at hand: +* If you have a collection of actors which performs a certain function, you may want to apply defined stimuli and observe the delivery of the desired result messages to a test actor; in this case the ***TestKit*** trait will likely interest you. +* If you encounter undesired behaviour (exceptions, dead-locks) and want to nail down the cause, it might help to run the actors in question using the ***CallingThreadDispatcher***; this dispatcher is strictly less powerful than the general purpose ones, but its deterministic behaviour and complete message stack can help debugging, unless your setup depends on concurrent execution for correctness. +* For real unit tests of one actor body at a time, there soon will be a special ***TestActorRef*** which allows access to the innards and enables running without a dispatcher. + +TestKit +------- + +The TestKit is a trait which you can mix into your test class to setup a test harness consisting of an test actor, which is implicitly available as sender reference, methods for querying and asserting features of messages received by said actor, and finally methods which provide a DSL for timing assertions. + +Ray Roestenburg has written a great article on using the TestKit: ``_. Here is a short teaser: + +.. code-block:: scala + + class SomeSpec extends WordSpec with MustMatchers with TestKit { + + val worker = actorOf(...) + + "A Worker" must { + "send timely replies" in { + within (50 millis) { + worker ! "some work" + expectMsg("some result") + expectNoMsg + } + } + } + } + +His full example is also available `here `_. + +CallingThreadDispatcher +----------------------- + +This special purpose dispatcher was conceived to enable collection of the full stack trace accumulated during processing of a complete message chain. The idea is to run invocations always on the calling thread, except when the target actor is already running on the current thread; in that case it is necessary to queue the invocation and run it after the current invocation on that actor has finished processing. This design implies that any invocation which blocks waiting on some future action to be done by the current thread will dead-lock. Hence, the CallingThreadDispatcher offers strictly more possibilities to dead-lock than a standard dispatcher. + +One nice property is that this feature can help verify that your design is dead-lock free: if you run only on this dispatcher and utilitze only one thread, then a successful run implies that for the given set of inputs there cannot be a dead-lock. (This is unfortunately not a hard guarantee, as long as your actor behavior depends on the dispatcher used, e.g. you could sabotage it by explicitly dead-locking only if self.dispatcher != CallingThreadDispatcher.) + +TestActorRef (coming soon ...) +------------------------------ + diff --git a/akka-docs/pending/third-party-integrations.rst b/akka-docs/pending/third-party-integrations.rst new file mode 100644 index 0000000000..579c3123d0 --- /dev/null +++ b/akka-docs/pending/third-party-integrations.rst @@ -0,0 +1,21 @@ +Third-party Integrations +======================== + +The Play! Framework +=================== + +Dustin Whitney has done an Akka integration module for the `Play! framework `_. + +Detailed instructions here: ``_. + +There are three screencasts: +# Using Play! with Akka STM: ``_ +# Using Play! with Akka Actors: ``_ +# Using Play! with Akka Remote Actors: ``_ + +The Pinky REST/MVC Framework +============================ + +Peter Hausel has done an Akka integration module for the `Pinky framework `_. + +Read more here: ``_ diff --git a/akka-docs/pending/transactors-java.rst b/akka-docs/pending/transactors-java.rst new file mode 100644 index 0000000000..6547063703 --- /dev/null +++ b/akka-docs/pending/transactors-java.rst @@ -0,0 +1,265 @@ +**Transactors (Java)** +============================================================ + +Module stability: **SOLID** + +Why Transactors? +================ + +Actors are excellent for solving problems where you have many independent processes that can work in isolation and only interact with other Actors through message passing. This model fits many problems. But the actor model is unfortunately a terrible model for implementing truly shared state. E.g. when you need to have consensus and a stable view of state across many components. The classic example is the bank account where clients can deposit and withdraw, in which each operation needs to be atomic. For detailed discussion on the topic see `this JavaOne presentation `_. + +**STM** on the other hand is excellent for problems where you need consensus and a stable view of the state by providing compositional transactional shared state. Some of the really nice traits of STM are that transactions compose, and it raises the abstraction level from lock-based concurrency. + +Akka's Transactors combine Actors and STM to provide the best of the Actor model (concurrency and asynchronous event-based programming) and STM (compositional transactional shared state) by providing transactional, compositional, asynchronous, event-based message flows. + +If you need Durability then you should not use one of the in-memory data structures but one of the persistent ones. + +Generally, the STM is not needed very often when working with Akka. Some use-cases (that we can think of) are: +# When you really need composable message flows across many actors updating their **internal local** state but need them to do that atomically in one big transaction. Might not often, but when you do need this then you are screwed without it. +# When you want to share a datastructure across actors. +# When you need to use the persistence modules. + +Actors and STM +-------------- + +You can combine Actors and STM in several ways. An Actor may use STM internally so that particular changes are guaranteed to be atomic. Actors may also share transactional datastructures as the STM provides safe shared state across threads. + +It's also possible to coordinate transactions across Actors or threads so that either the transactions in a set all commit successfully or they all fail. This is the focus of Transactors and the explicit support for coordinated transactions in this section. + +---- + +Coordinated transactions +======================== + +Akka provides an explicit mechanism for coordinating transactions across actors. Under the hood it uses a ``CountDownCommitBarrier``, similar to a CountDownLatch. + +Here is an example of coordinating two simple counter UntypedActors so that they both increment together in coordinated transactions. If one of them was to fail to increment, the other would also fail. + +.. code-block:: java + + import akka.actor.ActorRef; + + public class Increment { + private ActorRef friend = null; + + public Increment() {} + + public Increment(ActorRef friend) { + this.friend = friend; + } + + public boolean hasFriend() { + return friend != null; + } + + public ActorRef getFriend() { + return friend; + } + } + +.. code-block:: java + + import akka.actor.ActorRef; + import akka.actor.UntypedActor; + import static akka.actor.Actors.*; + import akka.stm.Ref; + import akka.transactor.Atomically; + import akka.transactor.Coordinated; + + public class Counter extends UntypedActor { + private Ref count = new Ref(0); + + private void increment() { + count.set(count.get() + 1); + } + + public void onReceive(Object incoming) throws Exception { + if (incoming instanceof Coordinated) { + Coordinated coordinated = (Coordinated) incoming; + Object message = coordinated.getMessage(); + if (message instanceof Increment) { + Increment increment = (Increment) message; + if (increment.hasFriend()) { + increment.getFriend().sendOneWay(coordinated.coordinate(new Increment())); + } + coordinated.atomic(new Atomically() { + public void atomically() { + increment(); + } + }); + } + } else if (incoming instanceof String) { + String message = (String) incoming; + if (message.equals("GetCount")) { + getContext().replyUnsafe(count.get()); + } + } + } + } + +.. code-block:: java + + ActorRef counter1 = actorOf(Counter.class).start(); + ActorRef counter2 = actorOf(Counter.class).start(); + + counter1.sendOneWay(new Coordinated(new Increment(counter2))); + +To start a new coordinated transaction set that you will also participate in, just create a ``Coordinated`` object: + +.. code-block:: java + + Coordinated coordinated = new Coordinated(); + +To start a coordinated transaction that you won't participate in yourself you can create a ``Coordinated`` object with a message and send it directly to an actor. The recipient of the message will be the first member of the coordination set: + +.. code-block:: java + + actor.sendOneWay(new Coordinated(new Message())); + +To include another actor in the same coordinated transaction set that you've created or received, use the ``coordinate`` method on that object. This will increment the number of parties involved by one and create a new ``Coordinated`` object to be sent. + +.. code-block:: java + + actor.sendOneWay(coordinated.coordinate(new Message())); + +To enter the coordinated transaction use the atomic method of the coordinated object. This accepts either an ``akka.transactor.Atomically`` object, or an ``Atomic`` object the same as used normally in the STM (just don't execute it - the coordination will do that). + +.. code-block:: java + + coordinated.atomic(new Atomically() { + public void atomically() { + // do something in a transaction + } + }); + +The coordinated transaction will wait for the other transactions before committing. If any of the coordinated transactions fail then they all fail. + +---- + +UntypedTransactor +================= + +UntypedTransactors are untyped actors that provide a general pattern for coordinating transactions, using the explicit coordination described above. + +Here's an example of a simple untyped transactor that will join a coordinated transaction: + +.. code-block:: java + + import akka.transactor.UntypedTransactor; + + public class Counter extends UntypedTransactor { + Ref count = new Ref(0); + + public void atomically(Object message) { + if (message instanceof Increment) { + count.set(count.get() + 1); + } + } + } + +You could send this Counter transactor a ``Coordinated(Increment)`` message. If you were to send it just an ``Increment`` message it will create its own ``Coordinated`` (but in this particular case wouldn't be coordinating transactions with any other transactors). + +To coordinate with other transactors override the ``coordinate`` method. The ``coordinate`` method maps a message to a set of ``SendTo`` objects, pairs of ``ActorRef`` and a message. You can use the ``include`` and ``sendTo`` methods to easily coordinate with other transactors. + +Example of coordinating an increment, similar to the explicitly coordinated example: + +.. code-block:: java + + import akka.transactor.UntypedTransactor; + import akka.transactor.SendTo; + import akka.stm.Ref; + + import java.util.Set; + + public class Counter extends UntypedTransactor { + Ref count = new Ref(0); + + @Override public Set coordinate(Object message) { + if (message instanceof Increment) { + Increment increment = (Increment) message; + if (increment.hasFriend()) + return include(increment.getFriend(), new Increment()); + } + return nobody(); + } + + public void atomically(Object message) { + if (message instanceof Increment) { + count.set(count.get() + 1); + } + } + } + +To exeucte directly before or after the coordinated transaction, override the ``before`` and ``after`` methods. These methods also expect partial functions like the receive method. They do not execute within the transaction. + +To completely bypass coordinated transactions override the ``normally`` method. Any message matched by ``normally`` will not be matched by the other methods, and will not be involved in coordinated transactions. In this method you can implement normal actor behavior, or use the normal STM atomic for local transactions. + +---- + +Coordinating Typed Actors +========================= + +It's also possible to use coordinated transactions with typed actors. You can explicitly pass around ``Coordinated`` objects, or use built-in support with the ``@Coordinated`` annotation and the ``Coordination.coordinate`` method. + +To specify a method should use coordinated transactions add the ``@Coordinated`` annotation. **Note**: the ``@Coordinated`` annotation will only work with void (one-way) methods. + +.. code-block:: java + + public interface Counter { + @Coordinated public void increment(); + public Integer get(); + } + +To coordinate transactions use a ``coordinate`` block. This accepts either an ``akka.transactor.Atomically`` object, or an ``Atomic`` object liked used in the STM (but don't execute it). The first boolean parameter specifies whether or not to wait for the transactions to complete. + +.. code-block:: java + + Coordination.coordinate(true, new Atomically() { + public void atomically() { + counter1.increment(); + counter2.increment(); + } + }); + +Here's an example of using ``@Coordinated`` with a TypedActor to coordinate increments: + +.. code-block:: java + + import akka.transactor.annotation.Coordinated; + + public interface Counter { + @Coordinated public void increment(); + public Integer get(); + } + +.. code-block:: java + + import akka.actor.TypedActor; + import akka.stm.Ref; + + public class CounterImpl extends TypedActor implements Counter { + private Ref count = new Ref(0); + + public void increment() { + count.set(count.get() + 1); + } + + public Integer get() { + return count.get(); + } + } + +``_ +Counter counter1 = (Counter) TypedActor.newInstance(Counter.class, CounterImpl.class); +Counter counter2 = (Counter) TypedActor.newInstance(Counter.class, CounterImpl.class); + +Coordination.coordinate(true, new Atomically() { + public void atomically() { + counter1.increment(); + counter2.increment(); + } +}); + +TypedActor.stop(counter1); +TypedActor.stop(counter2); +``_ diff --git a/akka-docs/pending/transactors-scala.rst b/akka-docs/pending/transactors-scala.rst new file mode 100644 index 0000000000..c0e232f676 --- /dev/null +++ b/akka-docs/pending/transactors-scala.rst @@ -0,0 +1,244 @@ +**Transactors (Scala)** +============================================================= + +Module stability: **SOLID** + +Why Transactors? +================ + +Actors are excellent for solving problems where you have many independent processes that can work in isolation and only interact with other Actors through message passing. This model fits many problems. But the actor model is unfortunately a terrible model for implementing truly shared state. E.g. when you need to have consensus and a stable view of state across many components. The classic example is the bank account where clients can deposit and withdraw, in which each operation needs to be atomic. For detailed discussion on the topic see `this JavaOne presentation `_. + +**STM** on the other hand is excellent for problems where you need consensus and a stable view of the state by providing compositional transactional shared state. Some of the really nice traits of STM are that transactions compose, and it raises the abstraction level from lock-based concurrency. + +Akka's Transactors combine Actors and STM to provide the best of the Actor model (concurrency and asynchronous event-based programming) and STM (compositional transactional shared state) by providing transactional, compositional, asynchronous, event-based message flows. + +If you need Durability then you should not use one of the in-memory data structures but one of the persistent ones. + +Generally, the STM is not needed very often when working with Akka. Some use-cases (that we can think of) are: +# When you really need composable message flows across many actors updating their **internal local** state but need them to do that atomically in one big transaction. Might not often, but when you do need this then you are screwed without it. +# When you want to share a datastructure across actors. +# When you need to use the persistence modules. + +Actors and STM +-------------- + +You can combine Actors and STM in several ways. An Actor may use STM internally so that particular changes are guaranteed to be atomic. Actors may also share transactional datastructures as the STM provides safe shared state across threads. + +It's also possible to coordinate transactions across Actors or threads so that either the transactions in a set all commit successfully or they all fail. This is the focus of Transactors and the explicit support for coordinated transactions in this section. + +---- + +Coordinated transactions +======================== + +Akka provides an explicit mechanism for coordinating transactions across Actors. Under the hood it uses a ``CountDownCommitBarrier``, similar to a CountDownLatch. + +Here is an example of coordinating two simple counter Actors so that they both increment together in coordinated transactions. If one of them was to fail to increment, the other would also fail. + +.. code-block:: scala + + import akka.transactor.Coordinated + import akka.stm.Ref + import akka.actor.{Actor, ActorRef} + + case class Increment(friend: Option[ActorRef] = None) + case object GetCount + + class Counter extends Actor { + val count = Ref(0) + + def receive = { + case coordinated @ Coordinated(Increment(friend)) => { + friend foreach (_ ! coordinated(Increment())) + coordinated atomic { + count alter (_ + 1) + } + } + case GetCount => self.reply(count.get) + } + } + + val counter1 = Actor.actorOf[Counter].start + val counter2 = Actor.actorOf[Counter].start + + counter1 ! Coordinated(Increment(Some(counter2))) + + ... + + counter1 !! GetCount // Some(1) + + counter1.stop + counter2.stop + +To start a new coordinated transaction set that you will also participate in, just create a ``Coordinated`` object: + +.. code-block:: scala + + val coordinated = Coordinated() + +To start a coordinated transaction that you won't participate in yourself you can create a ``Coordinated`` object with a message and send it directly to an actor. The recipient of the message will be the first member of the coordination set: + +.. code-block:: scala + + actor ! Coordinated(Message) + +To receive a coordinated message in an actor simply match it in a case statement: + +.. code-block:: scala + + def receive = { + case coordinated @ Coordinated(Message) => ... + } + +To include another actor in the same coordinated transaction set that you've created or received, use the apply method on that object. This will increment the number of parties involved by one and create a new ``Coordinated`` object to be sent. + +.. code-block:: scala + + actor ! coordinated(Message) + +To enter the coordinated transaction use the atomic method of the coordinated object: + +.. code-block:: scala + + coordinated atomic { + // do something in transaction ... + } + +The coordinated transaction will wait for the other transactions before committing. If any of the coordinated transactions fail then they all fail. + +---- + +Transactor +========== + +Transactors are actors that provide a general pattern for coordinating transactions, using the explicit coordination described above. + +Here's an example of a simple transactor that will join a coordinated transaction: + +.. code-block:: scala + + import akka.transactor.Transactor + import akka.stm.Ref + + case object Increment + + class Counter extends Transactor { + val count = Ref(0) + + def atomically = { + case Increment => count alter (_ + 1) + } + } + +You could send this Counter transactor a ``Coordinated(Increment)`` message. If you were to send it just an ``Increment`` message it will create its own ``Coordinated`` (but in this particular case wouldn't be coordinating transactions with any other transactors). + +To coordinate with other transactors override the ``coordinate`` method. The ``coordinate`` method maps a message to a set of ``SendTo`` objects, pairs of ``ActorRef`` and a message. You can use the ``include`` and ``sendTo`` methods to easily coordinate with other transactors. The ``include`` method will send on the same message that was received to other transactors. The ``sendTo`` method allows you to specify both the actor to send to, and the message to send. + +Example of coordinating an increment: + +.. code-block:: scala + + import akka.transactor.Transactor + import akka.stm.Ref + + case object Increment + + class FriendlyCounter(friend: ActorRef) extends Transactor { + val count = Ref(0) + + override def coordinate = { + case Increment => include(friend) + } + + def atomically = { + case Increment => count alter (_ + 1) + } + } + +Using ``include`` to include more than one transactor: + +.. code-block:: scala + + override def coordinate = { + case Message => include(actor1, actor2, actor3) + } + +Using ``sendTo`` to coordinate transactions but pass-on a different message than the one that was received: + +.. code-block:: scala + + override def coordinate = { + case Message => sendTo(someActor -> SomeOtherMessage) + case SomeMessage => sendTo(actor1 -> Message1, actor2 -> Message2) + } + +To exeucte directly before or after the coordinated transaction, override the ``before`` and ``after`` methods. These methods also expect partial functions like the receive method. They do not execute within the transaction. + +To completely bypass coordinated transactions override the ``normally`` method. Any message matched by ``normally`` will not be matched by the other methods, and will not be involved in coordinated transactions. In this method you can implement normal actor behavior, or use the normal STM atomic for local transactions. + +---- + +Coordinating Typed Actors +========================= + +It's also possible to use coordinated transactions with typed actors. You can explicitly pass around ``Coordinated`` objects, or use built-in support with the ``@Coordinated`` annotation and the ``Coordination.coordinate`` method. + +To specify a method should use coordinated transactions add the ``@Coordinated`` annotation. **Note**: the ``@Coordinated`` annotation only works with methods that return Unit (one-way methods). + +.. code-block:: scala + + trait Counter { + @Coordinated def increment: Unit + def get: Int + } + +To coordinate transactions use a ``coordinate`` block: + +.. code-block:: scala + + coordinate { + counter1.increment + counter2.increment + } + +Here's an example of using ``@Coordinated`` with a TypedActor to coordinate increments. + +.. code-block:: scala + + import akka.actor.TypedActor + import akka.stm.Ref + import akka.transactor.annotation.Coordinated + import akka.transactor.Coordination._ + + trait Counter { + @Coordinated def increment: Unit + def get: Int + } + + class CounterImpl extends TypedActor with Counter { + val ref = Ref(0) + def increment = ref alter (_ + 1) + def get = ref.get + } + + ... + + val counter1 = TypedActor.newInstance(classOf[Counter], classOf[CounterImpl]) + val counter2 = TypedActor.newInstance(classOf[Counter], classOf[CounterImpl]) + + coordinate { + counter1.increment + counter2.increment + } + + TypedActor.stop(counter1) + TypedActor.stop(counter2) + +The ``coordinate`` block will wait for the transactions to complete. If you do not want to wait then you can specify this explicitly: + +``_ +coordinate(wait = false) { + counter1.increment + counter2.increment +} +``_ diff --git a/akka-docs/pending/tutorial-chat-server-java.rst b/akka-docs/pending/tutorial-chat-server-java.rst new file mode 100644 index 0000000000..4f0daaa0de --- /dev/null +++ b/akka-docs/pending/tutorial-chat-server-java.rst @@ -0,0 +1,7 @@ +Tutorial: write a scalable, fault-tolerant, persistent network chat server and client (Java) +============================================================================================ + +Here is a couple of ports of the Scala API chat sample application in the `Scala tutorial `_. + +``_ +``_ diff --git a/akka-docs/pending/tutorial-chat-server-scala.rst b/akka-docs/pending/tutorial-chat-server-scala.rst new file mode 100644 index 0000000000..db1a249b6e --- /dev/null +++ b/akka-docs/pending/tutorial-chat-server-scala.rst @@ -0,0 +1,515 @@ +Tutorial: write a scalable, fault-tolerant, persistent network chat server and client (Scala) +============================================================================================= + +Introduction +============ + +*The source code for this tutorial can be found [[https:*github.com/jboner/akka/blob/master/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala|here]].// + +Writing correct concurrent, fault-tolerant and scalable applications is too hard. Most of the time it's because we are using the wrong tools and the wrong level of abstraction. + +`Akka `_ is an attempt to change that. + +Akka uses the Actor Model together with Software Transactional Memory to raise the abstraction level and provide a better platform to build correct concurrent and scalable applications. + +For fault-tolerance Akka adopts the "Let it crash", also called "Embrace failure", model which has been used with great success in the telecom industry to build applications that self-heal, systems that never stop. + +Actors also provides the abstraction for transparent distribution and the basis for truly scalable and fault-tolerant applications. + +Akka is Open Source and available under the Apache 2 License. + +In this article we will introduce you to Akka and see how we can utilize it to build a highly concurrent, scalable and fault-tolerant network server. + +But first let's take a step back and discuss what Actors really are and what they are useful for. + +Actors +====== + +`The Actor Model `_ provides a higher level of abstraction for writing concurrent and distributed systems. It alleviates the developer from having to deal with explicit locking and thread management. It makes it easier to write correct concurrent and parallel systems. Actors are really nothing new, they were defined in the 1963 paper by Carl Hewitt and have been popularized by the Erlang language which emerged in the mid 80s. It has been used by for example at Ericsson with great success to build highly concurrent and extremely reliable (99.9999999 % availability - 31 ms/year downtime) telecom systems. + +Actors encapsulate state and behavior into a lightweight process/thread. In a sense they are like OO objects but with a major semantic difference; they *do not* share state with any other Actor. Each Actor has its own view of the world and can only have impact on other Actors by sending messages to them. Messages are sent asynchronously and non-blocking in a so-called "fire-and-forget" manner where the Actor sends off a message to some other Actor and then do not wait for a reply but goes off doing other things or are suspended by the runtime. Each Actor has a mailbox (ordered message queue) in which incoming messages are processed one by one. Since all processing is done asynchronously and Actors do not block and consume any resources while waiting for messages, Actors tend to give very good concurrency and scalability characteristics and are excellent for building event-based systems. + +Creating Actors +=============== + +Akka has both a `Scala API `_ and a `Java API `_. In this article we will only look at the Scala API since that is the most expressive one. The article assumes some basic Scala knowledge, but even if you don't know Scala I don't think it will not be too hard to follow along anyway. + +Akka has adopted the same style of writing Actors as Erlang in which each Actor has an explicit message handler which does pattern matching to match on the incoming messages. + +Actors can be created either by: +* Extending the 'Actor' class and implementing the 'receive' method. +* Create an anonymous Actor using one of the 'actor' methods. + +Here is a little example before we dive into a more interesting one. + +.. code-block:: scala + + class MyActor extends Actor { + def receive = { + case "test" => println("received test") + case _ => println("received unknown message") + } + } + + val myActor = Actor.actorOf[MyActor] + myActor.start + +From this call we get a handle to the 'Actor' called 'ActorRef', which we can use to interact with the Actor + +The 'actorOf' factory method can be imported like this: + +.. code-block:: scala + + import akka.actor.Actor.actorOf + + val a = actorOf[MyActor] + +From now on we will assume that it is imported like this and can use it directly. + +Akka Actors are extremely lightweight. Each Actor consume ~600 bytes, which means that you can create 6.5 million on 4 G RAM. + +Messages are sent using the '!' operator: + +.. code-block:: scala + + myActor ! "test" + +Sample application +================== + +We will try to write a simple chat/IM system. It is client-server based and uses remote Actors to implement remote clients. Even if it is not likely that you will ever write a chat system I think that it can be a useful exercise since it uses patterns and idioms found in many other use-cases and domains. + +We will use many of the features of Akka along the way. In particular; Actors, fault-tolerance using Actor supervision, remote Actors, Software Transactional Memory (STM) and persistence. + +But let's start by defining the messages that will flow in our system. + +Creating messages +================= + +It is very important that all messages that will be sent around in the system are immutable. The Actor model relies on the simple fact that no state is shared between Actors and the only way to guarantee that is to make sure we don't pass mutable state around as part of the messages. + +In Scala we have something called `case classes `_. These make excellent messages since they are both immutable and great to pattern match on. + +Let's now start by creating the messages that will flow in our system. + +.. code-block:: scala + + sealed trait Event + case class Login(user: String) extends Event + case class Logout(user: String) extends Event + case class GetChatLog(from: String) extends Event + case class ChatLog(log: List[String]) extends Event + case class ChatMessage(from: String, message: String) extends Event + +As you can see with these messages we can log in and out, send a chat message and ask for and get a reply with all the messages in the chat log so far. + +Client: Sending messages +======================== + +Our client wraps each message send in a function, making it a bit easier to use. Here we assume that we have a reference to the chat service so we can communicate with it by sending messages. Messages are sent with the '!' operator (pronounced "bang"). This sends a message of asynchronously and do not wait for a reply. + +Sometimes however, there is a need for sequential logic, sending a message and wait for the reply before doing anything else. In Akka we can achieve that using the '!!' ("bangbang") operator. When sending a message with '!!' we do not return immediately but wait for a reply using a `Future `_. A 'Future' is a promise that we will get a result later but with the difference from regular method dispatch that the OS thread we are running on is put to sleep while waiting and that we can set a time-out for how long we wait before bailing out, retrying or doing something else. The '!!' function returns a `scala.Option `_ which implements the `Null Object pattern `_. It has two subclasses; 'None' which means no result and 'Some(value)' which means that we got a reply. The 'Option' class has a lot of great methods to work with the case of not getting a defined result. F.e. as you can see below we are using the 'getOrElse' method which will try to return the result and if there is no result defined invoke the "...OrElse" statement. + +.. code-block:: scala + + class ChatClient(val name: String) { + val chat = Actor.remote.actorFor("chat:service", "localhost", 2552) + + def login = chat ! Login(name) + def logout = chat ! Logout(name) + def post(message: String) = chat ! ChatMessage(name, name + ": " + message) + def chatLog = (chat !! GetChatLog(name)).as[ChatLog].getOrElse(throw new Exception("Couldn't get the chat log from ChatServer")) + } + +As you can see, we are using the 'Actor.remote.actorFor' to lookup the chat server on the remote node. From this call we will get a handle to the remote instance and can use it as it is local. + +Session: Receiving messages +=========================== + +Now we are done with the client side and let's dig into the server code. We start by creating a user session. The session is an Actor and is defined by extending the 'Actor' trait. This trait has one abstract method that we have to define; 'receive' which implements the message handler for the Actor. + +In our example the session has state in the form of a 'List' with all the messages sent by the user during the session. In takes two parameters in its constructor; the user name and a reference to an Actor implementing the persistent message storage. For both of the messages it responds to, 'ChatMessage' and 'GetChatLog', it passes them on to the storage Actor. + +If you look closely (in the code below) you will see that when passing on the 'GetChatLog' message we are not using '!' but 'forward'. This is similar to '!' but with the important difference that it passes the original sender reference, in this case to the storage Actor. This means that the storage can use this reference to reply to the original sender (our client) directly. + +.. code-block:: scala + + class Session(user: String, storage: ActorRef) extends Actor { + private val loginTime = System.currentTimeMillis + private var userLog: List[String] = Nil + + EventHandler.info(this, "New session for user [%s] has been created at [%s]".format(user, loginTime)) + + def receive = { + case msg @ ChatMessage(from, message) => + userLog ::= message + storage ! msg + + case msg @ GetChatLog(_) => + storage forward msg + } + } + +Let it crash: Implementing fault-tolerance +========================================== + +Akka's `approach to fault-tolerance `_; the "let it crash" model, is implemented by linking Actors. It is very different to what Java and most non-concurrency oriented languages/frameworks have adopted. It’s a way of dealing with failure that is designed for concurrent and distributed systems. + +If we look at concurrency first. Now let’s assume we are using non-linked Actors. Throwing an exception in concurrent code, will just simply blow up the thread that currently executes the Actor. There is no way to find out that things went wrong (apart from see the stack trace in the log). There is nothing you can do about it. Here linked Actors provide a clean way of both getting notification of the error so you know what happened, as well as the Actor that crashed, so you can do something about it. + +Linking Actors allow you to create sets of Actors where you can be sure that either: +* All are dead +* All are alive + +This is very useful when you have hundreds of thousands of concurrent Actors. Some Actors might have implicit dependencies and together implement a service, computation, user session etc. for these being able to group them is very nice. + +Akka encourages non-defensive programming. Don’t try to prevent things from go wrong, because they will, whether you want it or not. Instead; expect failure as a natural state in the life-cycle of your app, crash early and let someone else (that sees the whole picture), deal with it. + +Now let’s look at distributed Actors. As you probably know, you can’t build a fault-tolerant system with just one single node, but you need at least two. Also, you (usually) need to know if one node is down and/or the service you are talking to on the other node is down. Here Actor supervision/linking is a critical tool for not only monitoring the health of remote services, but to actually manage the service, do something about the problem if the Actor or node is down. This could be restarting him on the same node or on another node. + +To sum things up, it is a very different way of thinking but a way that is very useful (if not critical) to building fault-tolerant highly concurrent and distributed applications. + +Supervisor hierarchies +====================== + +A supervisor is a regular Actor that is responsible for starting, stopping and monitoring its child Actors. The basic idea of a supervisor is that it should keep its child Actors alive by restarting them when necessary. This makes for a completely different view on how to write fault-tolerant servers. Instead of trying all things possible to prevent an error from happening, this approach embraces failure. It shifts the view to look at errors as something natural and something that will happen and instead of trying to prevent it; embrace it. Just "let it crash" and reset the service to a stable state through restart. + +Akka has two different restart strategies; All-For-One and One-For-One. + +* OneForOne: Restart only the component that has crashed. +* AllForOne: Restart all the components that the supervisor is managing, including the one that have crashed. + +The latter strategy should be used when you have a certain set of components that are coupled in some way that if one is crashing they all need to be reset to a stable state before continuing. + +Chat server: Supervision, Traits and more +========================================= + +There are two ways you can define an Actor to be a supervisor; declaratively and dynamically. In this example we use the dynamic approach. There are two things we have to do: + +* Define the fault handler by setting the 'faultHandler' member field to the strategy we want. +* Define the exceptions we want to "trap", e.g. which exceptions should be handled according to the fault handling strategy we have defined. This in done by setting the 'trapExit' member field to a 'List' with all exceptions we want to trap. + +The last thing we have to do to supervise Actors (in our example the storage Actor) is to 'link' the Actor. Invoking 'link(actor)' will create a link between the Actor passed as argument into 'link' and ourselves. This means that we will now get a notification if the linked Actor is crashing and if the cause of the crash, the exception, matches one of the exceptions in our 'trapExit' list then the crashed Actor is restarted according the the fault handling strategy defined in our 'faultHandler'. We also have the 'unlink(actor)' function which disconnects the linked Actor from the supervisor. + +In our example we are using a method called 'spawnLink(actor)' which creates, starts and links the Actor in an atomic operation. The linking and unlinking is done in 'preStart' and 'postStop' callback methods which are invoked by the runtime when the Actor is started and shut down (shutting down is done by invoking 'actor.stop'). In these methods we initialize our Actor, by starting and linking the storage Actor and clean up after ourselves by shutting down all the user session Actors and the storage Actor. + +That is it. Now we have implemented the supervising part of the fault-tolerance for the storage Actor. But before we dive into the 'ChatServer' code there are some more things worth mentioning about its implementation. + +It defines an abstract member field holding the 'ChatStorage' implementation the server wants to use. We do not define that in the 'ChatServer' directly since we want to decouple it from the actual storage implementation. + +The 'ChatServer' is a 'trait', which is Scala's version of mixins. A mixin can be seen as an interface with an implementation and is a very powerful tool in Object-Oriented design that makes it possible to design the system into small, reusable, highly cohesive, loosely coupled parts that can be composed into larger object and components structures. + +I'll try to show you how we can make use Scala's mixins to decouple the Actor implementation from the business logic of managing the user sessions, routing the chat messages and storing them in the persistent storage. Each of these separate parts of the server logic will be represented by its own trait; giving us four different isolated mixins; 'Actor', 'SessionManagement', 'ChatManagement' and 'ChatStorageFactory' This will give us as loosely coupled system with high cohesion and reusability. At the end of the article I'll show you how you can compose these mixins into a the complete runtime component we like. + +.. code-block:: scala + + /** + * Chat server. Manages sessions and redirects all other messages to the Session for the client. + */ + trait ChatServer extends Actor { + self.faultHandler = OneForOneStrategy(List(classOf[Exception]),5, 5000) + val storage: ActorRef + + EventHandler.info(this, "Chat server is starting up...") + + // actor message handler + def receive: Receive = sessionManagement orElse chatManagement + + // abstract methods to be defined somewhere else + protected def chatManagement: Receive + protected def sessionManagement: Receive + protected def shutdownSessions(): Unit + + override def postStop = { + EventHandler.info(this, "Chat server is shutting down...") + shutdownSessions + self.unlink(storage) + storage.stop + } + } + +If you look at the 'receive' message handler function you can see that we have defined it but instead of adding our logic there we are delegating to two different functions; 'sessionManagement' and 'chatManagement', chaining them with 'orElse'. These two functions are defined as abstract in our 'ChatServer' which means that they have to be provided by some another mixin or class when we instantiate our 'ChatServer'. Naturally we will put the 'sessionManagement' implementation in the 'SessionManagement' trait and the 'chatManagement' implementation in the 'ChatManagement' trait. First let's create the 'SessionManagement' trait. + +Chaining partial functions like this is a great way of composing functionality in Actors. You can for example put define one default message handle handling generic messages in the base Actor and then let deriving Actors extend that functionality by defining additional message handlers. There is a section on how that is done `here `_. + +Session management +================== + +The session management is defined in the 'SessionManagement' trait in which we implement the two abstract methods in the 'ChatServer'; 'sessionManagement' and 'shutdownSessions'. + +The 'SessionManagement' trait holds a 'HashMap' with all the session Actors mapped by user name as well as a reference to the storage (to be able to pass it in to each newly created 'Session'). + +The 'sessionManagement' function performs session management by responding to the 'Login' and 'Logout' messages. For each 'Login' message it creates a new 'Session' Actor, starts it and puts it in the 'sessions' Map and for each 'Logout' message it does the opposite; shuts down the user's session and removes it from the 'sessions' Map. + +The 'shutdownSessions' function simply shuts all the sessions Actors down. That completes the user session management. + +.. code-block:: scala + + /** + * Implements user session management. + *

+ * Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor. + */ + trait SessionManagement { this: Actor => + + val storage: ActorRef // needs someone to provide the ChatStorage + val sessions = new HashMap[String, ActorRef] + + protected def sessionManagement: Receive = { + case Login(username) => + EventHandler.info(this, "User [%s] has logged in".format(username)) + val session = actorOf(new Session(username, storage)) + session.start + sessions += (username -> session) + + case Logout(username) => + EventHandler.info(this, "User [%s] has logged out".format(username)) + val session = sessions(username) + session.stop + sessions -= username + } + + protected def shutdownSessions = + sessions.foreach { case (_, session) => session.stop } + } + +Chat message management +======================= + +Chat message management is implemented by the 'ChatManagement' trait. It has an abstract 'HashMap' session member field with all the sessions. Since it is abstract it needs to be mixed in with someone that can provide this reference. If this dependency is not resolved when composing the final component, you will get a compilation error. + +It implements the 'chatManagement' function which responds to two different messages; 'ChatMessage' and 'GetChatLog'. It simply gets the session for the user (the sender of the message) and routes the message to this session. Here we also use the 'forward' function to make sure the original sender reference is passed along to allow the end receiver to reply back directly. + +.. code-block:: scala + + /** + * Implements chat management, e.g. chat message dispatch. + *

+ * Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor. + */ + trait ChatManagement { this: Actor => + val sessions: HashMap[String, ActorRef] // needs someone to provide the Session map + + protected def chatManagement: Receive = { + case msg @ ChatMessage(from, _) => getSession(from).foreach(_ ! msg) + case msg @ GetChatLog(from) => getSession(from).foreach(_ forward msg) + } + + private def getSession(from: String) : Option[ActorRef] = { + if (sessions.contains(from)) + Some(sessions(from)) + else { + EventHandler.info(this, "Session expired for %s".format(from)) + None + } + } + } + +Using an Actor as a message broker, as in this example, is a very common pattern with many variations; load-balancing, master/worker, map/reduce, replication, logging etc. It becomes even more useful with remote Actors when we can use it to route messages to different nodes. + +STM and Transactors +=================== + +Actors are excellent for solving problems where you have many independent processes that can work in isolation and only interact with other Actors through message passing. This model fits many problems. But the Actor model is unfortunately a terrible model for implementing truly shared state. E.g. when you need to have consensus and a stable view of state across many components. The classic example is the bank account where clients can deposit and withdraw, in which each operation needs to be atomic. For detailed discussion on the topic see this `presentation `_. + +`Software Transactional Memory `_ (STM) on the other hand is excellent for problems where you need consensus and a stable view of the state by providing compositional transactional shared state. Some of the really nice traits of STM are that transactions compose and that it raises the abstraction level from lock-based concurrency. + +Akka has a `STM implementation `_ that is based on the same ideas as found in the `Clojure language `_; Managed References working with immutable data. + +Akka allows you to combine Actors and STM into what we call `Transactors `_ (short for Transactional Actors), these allow you to optionally combine Actors and STM provides IMHO the best of the Actor model (simple concurrency and asynchronous event-based programming) and STM (compositional transactional shared state) by providing transactional, compositional, asynchronous, event-based message flows. You don't need Transactors all the time but when you do need them then you *really need* them. + +Akka currently provides three different transactional abstractions; 'Map', 'Vector' and 'Ref'. They can be shared between multiple Actors and they are managed by the STM. You are not allowed to modify them outside a transaction, if you do so, an exception will be thrown. + +What you get is transactional memory in which multiple Actors are allowed to read and write to the same memory concurrently and if there is a clash between two transactions then both of them are aborted and retried. Aborting a transaction means that the memory is rolled back to the state it were in when the transaction was started. + +In database terms STM gives you 'ACI' semantics; 'Atomicity', 'Consistency' and 'Isolation'. The 'D' in 'ACID'; 'Durability', you can't get with an STM since it is in memory. This however is addressed by the persistence module in Akka. + +Persistence: Storing the chat log +================================= + +Akka modules provides the possibility of taking the transactional data structures we discussed above and making them persistent. It is an extension to the STM which guarantees that it has the same semantics. + +The `persistence module `_ has pluggable storage back-ends. + +They all implement persistent 'Map', 'Vector' and 'Ref'. Which can be created and retrieved by id through one of the storage modules. + +.. code-block:: scala + + val map = RedisStorage.newMap(id) + val vector = CassandraStorage.newVector(id) + val ref = MongoStorage.newRef(id) + +Chat storage: Backed with simple in-memory +========================================== + +To keep it simple we implement the persistent storage, with a in-memory Vector, i.e. it will not be persistent. We start by creating a 'ChatStorage' trait allowing us to have multiple different storage backend. For example one in-memory and one persistent. + +.. code-block:: scala + + /** + * Abstraction of chat storage holding the chat log. + */ + trait ChatStorage extends Actor + +Our 'MemoryChatStorage' extends the 'ChatStorage' trait. The only state it holds is the 'chatLog' which is a transactional 'Vector'. + +It responds to two different messages; 'ChatMessage' and 'GetChatLog'. The 'ChatMessage' message handler takes the 'message' attribute and appends it to the 'chatLog' vector. Here you can see that we are using the 'atomic { ... }' block to run the vector operation in a transaction. For this in-memory storage it is not important to use a transactional Vector, since it is not shared between actors, but it illustrates the concept. + +The 'GetChatLog' message handler retrieves all the messages in the chat log storage inside an atomic block, iterates over them using the 'map' combinator transforming them from 'Array[Byte] to 'String'. Then it invokes the 'reply(message)' function that will send the chat log to the original sender; the 'ChatClient'. + +You might rememeber that the 'ChatServer' was supervising the 'ChatStorage' actor. When we discussed that we showed you the supervising Actor's view. Now is the time for the supervised Actor's side of things. First, a supervised Actor need to define a life-cycle in which it declares if it should be seen as a: + +* 'Permanent': which means that the actor will always be restarted. +* 'Temporary': which means that the actor will not be restarted, but it will be shut down through the regular shutdown process so the 'postStop' callback function will called. + +We define the 'MemoryChatStorage' as 'Permanent' by setting the 'lifeCycle' member field to 'Permanent'. + +The idea with this crash early style of designing your system is that the services should just crash and then they should be restarted and reset into a stable state and continue from there. The definition of "stable state" is domain specific and up to the application developer to define. Akka provides two callback functions; 'preRestart' and 'postRestart' that are called right *before* and right *after* the Actor is restarted. Both of these functions take a 'Throwable', the reason for the crash, as argument. In our case we just need to implement the 'postRestart' hook and there re-initialize the 'chatLog' member field with a fresh 'Vector'. + +.. code-block:: scala + + /** + * Memory-backed chat storage implementation. + */ + class MemoryChatStorage extends ChatStorage { + self.lifeCycle = Permanent + + private var chatLog = TransactionalVector[Array[Byte]]() + + EventHandler.info(this, "Memory-based chat storage is starting up...") + + def receive = { + case msg @ ChatMessage(from, message) => + EventHandler.debug(this, "New chat message [%s]".format(message)) + atomic { chatLog + message.getBytes("UTF-8") } + + case GetChatLog(_) => + val messageList = atomic { chatLog.map(bytes => new String(bytes, "UTF-8")).toList } + self.reply(ChatLog(messageList)) + } + + override def postRestart(reason: Throwable) = chatLog = TransactionalVector() + } + +The last thing we need to do in terms of persistence is to create a 'MemoryChatStorageFactory' that will take care of instantiating and resolving the 'val storage: ChatStorage' field in the 'ChatServer' with a concrete implementation of our persistence Actor. + +.. code-block:: scala + + /** + * Creates and links a MemoryChatStorage. + */ + trait MemoryChatStorageFactory { this: Actor => + val storage = this.self.spawnLink[MemoryChatStorage] // starts and links ChatStorage + } + +Composing the full Chat Service +=============================== + +We have now created the full functionality for the chat server, all nicely decoupled into isolated and well-defined traits. Now let's bring all these traits together and compose the complete concrete 'ChatService'. + +.. code-block:: scala + + /** + * Class encapsulating the full Chat Service. + * Start service by invoking: + *

+   * val chatService = Actor.actorOf[ChatService].start
+   * 
+ */ + class ChatService extends + ChatServer with + SessionManagement with + ChatManagement with + MemoryChatStorageFactory { + override def preStart = { + remote.start("localhost", 2552); + remote.register("chat:service", self) //Register the actor with the specified service id + } + } + +Creating a remote server service +================================ + +As you can see in the section above, we are overriding the Actor's 'start' method and are starting up a remote server node by invoking 'remote.start("localhost", 2552)'. This starts up the remote node on address "localhost" and port 2552 which means that it accepts incoming messages on this address. Then we register the ChatService actor in the remote node by invoking 'remote.register("chat:service", self)'. This means that the ChatService will be available to other actors on this specific id, address and port. + +That's it. Were done. Now we have a, very simple, but scalable, fault-tolerant, event-driven, persistent chat server that can without problem serve a million concurrent users on a regular workstation. + +Let's use it. + +Sample client chat session +========================== + +Now let's create a simple test runner that logs in posts some messages and logs out. + +.. code-block:: scala + + /** + * Test runner emulating a chat session. + */ + object ClientRunner { + + def run = { + val client1 = new ChatClient("jonas") + client1.login + val client2 = new ChatClient("patrik") + client2.login + + client1.post("Hi there") + println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t")) + + client2.post("Hello") + println("CHAT LOG:\n\t" + client2.chatLog.log.mkString("\n\t")) + + client1.post("Hi again") + println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t")) + + client1.logout + client2.logout + } + } + +Sample code +=========== + +All this code is available as part of the Akka distribution. It resides in the './akka-samples/akka-sample-chat' module and have a 'README' file explaining how to run it. + +Or if you rather browse it `online `_. + +Run it +====== + +Download and build Akka + +# Check out Akka from ``_ +# Set 'AKKA_HOME' environment variable to the root of the Akka distribution. +# Open up a shell and step into the Akka distribution root folder. +# Build Akka by invoking: + +:: + + % sbt update + % sbt dist + +Run a sample chat session + +1. Fire up two shells. For each of them: +- Step down into to the root of the Akka distribution. +- Set 'export AKKA_HOME=. +- Run 'sbt console' to start up a REPL (interpreter). +2. In the first REPL you get execute: +- scala> import sample.chat._ +- scala> import akka.actor.Actor._ +- scala> val chatService = actorOf[ChatService].start +3. In the second REPL you get execute: +- scala> import sample.chat._ +- scala> ClientRunner.run +4. See the chat simulation run. +5. Run it again to see full speed after first initialization. +6. In the client REPL, or in a new REPL, you can also create your own client +- scala> import sample.chat._ +- scala> val myClient = new ChatClient("") +- scala> myClient.login +- scala> myClient.post("Can I join?") +- scala> println("CHAT LOG:\n\t" + myClient.chatLog.log.mkString("\n\t")) + +That's it. Have fun. diff --git a/akka-docs/pending/typed-actors-java.rst b/akka-docs/pending/typed-actors-java.rst new file mode 100644 index 0000000000..a1d0d3594c --- /dev/null +++ b/akka-docs/pending/typed-actors-java.rst @@ -0,0 +1,186 @@ +Typed Actors (Java) +=================== + +Module stability: **SOLID** + +The Typed Actors are implemented through `Typed Actors `_. It uses AOP through `AspectWerkz `_ to turn regular POJOs into asynchronous non-blocking Actors with semantics of the Actor Model. E.g. each message dispatch is turned into a message that is put on a queue to be processed by the Typed Actor sequentially one by one. + +If you are using the `Spring Framework `_ then take a look at Akka's `Spring integration `_. + +Creating Typed Actors +--------------------- + +**IMPORTANT:** The Typed Actors class must have access modifier 'public' and can't be a non-static inner class. + +Akka turns POJOs with interface and implementation into asynchronous (Typed) Actors. Akka is using `AspectWerkz’s Proxy `_ implementation, which is the `most performant `_ proxy implementation there exists. + +In order to create a Typed Actor you have to subclass the TypedActor base class. + +Here is an example. + +If you have a POJO with an interface implementation separation like this: + +.. code-block:: java + + interface RegistrationService { + void register(User user, Credentials cred) + User getUserFor(String username) + } + +.. code-block:: java + + public class RegistrationServiceImpl implements RegistrationService extends TypedActor { + public void register(User user, Credentials cred) { + ... // register user + } + + public User getUserFor(String username) { + ... // fetch user by username + return user; + } + } + +Then you can create an Typed Actor out of it by creating it through the 'TypedActor' factory like this: + +.. code-block:: java + + RegistrationService service = + (RegistrationService) TypedActor.newInstance(RegistrationService.class, RegistrationServiceImpl.class, 1000); + // The last parameter defines the timeout for Future calls + +**Creating Typed Actors with non-default constructor** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To create a typed actor that takes constructor arguments use a variant of 'newInstance' or 'newRemoteInstance' that takes an instance of a 'TypedActorFactory' in which you can create the TypedActor in any way you like. If you use this method then make sure that no one can get a reference to the actor instance. Touching actor state directly is bypassing the whole actor dispatching mechanism and create race conditions which can lead to corrupt data. + +Here is an example: + +.. code-block:: java + + Service service = TypedActor.newInstance(classOf[Service], new TypedActorFactory() { + public TypedActor create() { + return new ServiceWithConstructorArgsImpl("someString", 500L)); + }); + +Configuration factory class +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Using a configuration object: + +.. code-block:: java + + TypedActorConfiguration config = new TypedActorConfiguration() + .timeout(3000) + .makeTransactionRequired(); + + RegistrationService service = (RegistrationService) TypedActor.newInstance(RegistrationService.class, config); + +However, often you will not use these factory methods but declaratively define the Typed Actors as part of a supervisor hierarchy. More on that in the `Fault Tolerance `_ section. + +Sending messages +---------------- + +Messages are sent simply by invoking methods on the POJO, which is proxy to the "real" POJO now. The arguments to the method are bundled up atomically into an message and sent to the receiver (the actual POJO instance). + +One-way message send +^^^^^^^^^^^^^^^^^^^^ + +Methods that return void are turned into ‘fire-and-forget’ semantics by asynchronously firing off the message and return immediately. In the example above it would be the 'register' method, so if this method is invoked then it returns immediately: + +.. code-block:: java + + // method invocation returns immediately and method is invoke asynchronously using the Actor Model semantics + service.register(user, creds); + +Request-reply message send +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Methods that return something (e.g. non-void methods) are turned into ‘send-and-recieve-eventually’ semantics by asynchronously firing off the message and wait on the reply using a Future. + +.. code-block:: java + + // method invocation is asynchronously dispatched using the Actor Model semantics, + // but it blocks waiting on a Future to be resolved in the background + User user = service.getUser(username); + +Generally it is preferred to use fire-forget messages as much as possible since they will never block, e.g. consume a resource by waiting. But sometimes they are neat to use since they: +# Simulates standard Java method dispatch, which is more intuitive for most Java developers +# Are a neat to model request-reply +# Are useful when you need to do things in a defined order + +The same holds for the 'request-reply-with-future' described below. + +Request-reply-with-future message send +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Methods that return a 'akka.dispatch.Future' are turned into ‘send-and-recieve-with-future’ semantics by asynchronously firing off the message and returns immediately with a Future. You need to use the 'future(...)' method in the TypedActor base class to resolve the Future that the client code is waiting on. + +Here is an example: + +.. code-block:: java + + public class MathTypedActorImpl extends TypedActor implements MathTypedActor { + public Future square(int value) { + return future(value * value); + } + } + + MathTypedActor math = TypedActor.actorOf(MathTypedActor .class, MathTypedActorImpl.class); + + // This method will return immediately when called, caller should wait on the Future for the result + Future future = math.square(10); + future.await(); + Integer result = future.get(); + +Stopping Typed Actors +--------------------- + +Once Typed Actors have been created with one of the TypedActor.newInstance methods they need to be stopped with TypedActor.stop to free resources allocated by the created Typed Actor (this is not needed when the Typed Actor is `supervised `_). + +.. code-block:: java + + // Create Typed Actor + RegistrationService service = (RegistrationService) TypedActor.newInstance(RegistrationService.class); + + // ... + + // Free Typed Actor resources + TypedActor.stop(service); + +When the Typed Actor defines a `shutdown callback `_ method it will be invoked on TypedActor.stop. + +How to use the TypedActorContext for runtime information access +--------------------------------------------------------------- + +The 'akka.actor.TypedActorContext' class Holds 'runtime type information' (RTTI) for the Typed Actor. This context is a member field in the TypedActor base class and holds for example the current sender reference, the current sender future etc. + +Here is an example how you can use it to in a 'void' (e.g. fire-forget) method to implement request-reply by using the sender reference: + +.. code-block:: java + + class PingImpl implements Ping extends TypedActor { + + public void hit(int count) { + Pong pong = (Pong) context.getSender(); + pong.hit(count++); + } + } + +If the sender, sender future etc. is not available, then these methods will return 'null' so you should have a way of dealing with scenario. + +Messages and immutability +------------------------- + +**IMPORTANT**: Messages can be any kind of object but have to be immutable (there is a workaround, see next section). Java or Scala can’t enforce immutability (yet) so this has to be by convention. Primitives like String, int, Long are always immutable. Apart from these you have to create your own immutable objects to send as messages. If you pass on a reference to an instance that is mutable then this instance can be modified concurrently by two different Typed Actors and the Actor model is broken leaving you with NO guarantees and most likely corrupt data. + +Akka can help you in this regard. It allows you to turn on an option for serializing all messages, e.g. all parameters to the Typed Actor effectively making a deep clone/copy of the parameters. This will make sending mutable messages completely safe. This option is turned on in the ‘$AKKA_HOME/config/akka.conf’ config file like this: + +.. code-block:: ruby + + akka { + actor { + serialize-messages = on # does a deep clone of messages to ensure immutability + } + } + +This will make a deep clone (using Java serialization) of all parameters. diff --git a/akka-docs/pending/typed-actors-scala.rst b/akka-docs/pending/typed-actors-scala.rst new file mode 100644 index 0000000000..74a18527e9 --- /dev/null +++ b/akka-docs/pending/typed-actors-scala.rst @@ -0,0 +1,167 @@ +Typed Actors (Scala) +==================== + +Module stability: **SOLID** + +The Typed Actors are implemented through `Typed Actors `_. It uses AOP through `AspectWerkz `_ to turn regular POJOs into asynchronous non-blocking Actors with semantics of the Actor Model. E.g. each message dispatch is turned into a message that is put on a queue to be processed by the Typed Actor sequentially one by one. + +If you are using the `Spring Framework `_ then take a look at Akka's `Spring integration `_. + +Creating Typed Actors +--------------------- + +**IMPORTANT:** The Typed Actors class must have access modifier 'public' and can't be an inner class (unless it is an inner class in an 'object'). + +Akka turns POJOs with interface and implementation into asynchronous (Typed) Actors. Akka is using `AspectWerkz’s Proxy `_ implementation, which is the `most performant `_ proxy implementation there exists. + +In order to create a Typed Actor you have to subclass the TypedActor base class. + +Here is an example. + +If you have a POJO with an interface implementation separation like this: + +.. code-block:: scala + + trait RegistrationService { + def register(user: User, cred: Credentials): Unit + def getUserFor(username: String): User + } + +.. code-block:: scala + + public class RegistrationServiceImpl extends TypedActor with RegistrationService { + def register(user: User, cred: Credentials): Unit = { + ... // register user + } + + def getUserFor(username: String): User = { + ... // fetch user by username + user + } + } + +Then you can create an Typed Actor out of it by creating it through the 'TypedActor' factory like this: + +.. code-block:: scala + + val service = TypedActor.newInstance(classOf[RegistrationService], classOf[RegistrationServiceImpl], 1000) + // The last parameter defines the timeout for Future calls + +**Creating Typed Actors with non-default constructor** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To create a typed actor that takes constructor arguments use a variant of 'newInstance' or 'newRemoteInstance' that takes a call-by-name block in which you can create the Typed Actor in any way you like. + +Here is an example: + +.. code-block:: scala + + val service = TypedActor.newInstance(classOf[Service], new ServiceWithConstructorArgs("someString", 500L)) + +Configuration factory class +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Using a configuration object: + +.. code-block:: scala + + val config = new TypedActorConfiguration + .timeout(3000) + .makeTransactionRequired + + val service = TypedActor.newInstance(classOf[RegistrationService], classOf[RegistrationServiceImpl], config) + +However, often you will not use these factory methods but declaratively define the Typed Actors as part of a supervisor hierarchy. More on that in the `Fault Tolerance `_ section. + +Sending messages +---------------- + +Messages are sent simply by invoking methods on the POJO, which is proxy to the "real" POJO now. The arguments to the method are bundled up atomically into an message and sent to the receiver (the actual POJO instance). + +One-way message send +^^^^^^^^^^^^^^^^^^^^ + +Methods that return void are turned into ‘fire-and-forget’ semantics by asynchronously firing off the message and return immediately. In the example above it would be the 'register' method, so if this method is invoked then it returns immediately: + +.. code-block:: java + + // method invocation returns immediately and method is invoke asynchronously using the Actor Model semantics + service.register(user, creds) + +Request-reply message send +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Methods that return something (e.g. non-void methods) are turned into ‘send-and-recieve-eventually’ semantics by asynchronously firing off the message and wait on the reply using a Future. + +.. code-block:: scala + + // method invocation is asynchronously dispatched using the Actor Model semantics, + // but it blocks waiting on a Future to be resolved in the background + val user = service.getUser(username) + +Generally it is preferred to use fire-forget messages as much as possible since they will never block, e.g. consume a resource by waiting. But sometimes they are neat to use since they: +# Simulates standard Java method dispatch, which is more intuitive for most Java developers +# Are a neat to model request-reply +# Are useful when you need to do things in a defined order + +Request-reply-with-future message send +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Methods that return a 'akka.dispatch.Future' are turned into ‘send-and-recieve-with-future’ semantics by asynchronously firing off the message and returns immediately with a Future. You need to use the 'future(...)' method in the TypedActor base class to resolve the Future that the client code is waiting on. + +Here is an example: + +.. code-block:: scala + + class MathTypedActorImpl extends TypedActor with MathTypedActor { + def square(x: Int): Future[Integer] = future(x * x) + } + + // create the ping actor + val math = TypedActor.newInstance(classOf[MathTyped], classOf[MathTypedImpl]) + + // This method will return immediately when called, caller should wait on the Future for the result + val future = math.square(10) + future.await + val result: Int = future.get + +Stopping Typed Actors +--------------------- + +Once Typed Actors have been created with one of the TypedActor.newInstance methods they need to be stopped with TypedActor.stop to free resources allocated by the created Typed Actor (this is not needed when the Typed Actor is `supervised `_). + +.. code-block:: scala + + // Create Typed Actor + val service = TypedActor.newInstance(classOf[RegistrationService], classOf[RegistrationServiceImpl], 1000) + + // ... + + // Free Typed Actor resources + TypedActor.stop(service) + +When the Typed Actor defines a `shutdown callback `_ method it will be invoked on TypedActor.stop. + +How to use the TypedActorContext for runtime information access +--------------------------------------------------------------- + +The 'akka.actor.TypedActorContext' class Holds 'runtime type information' (RTTI) for the Typed Actor. This context is a member field in the TypedActor base class and holds for example the current sender reference, the current sender future etc. + +Here is an example how you can use it to in a 'void' (e.g. fire-forget) method to implement request-reply by using the sender reference: + +.. code-block:: scala + + class PingImpl extends TypedActor with Ping { + + def hit(count: Int) { + val pong = context.getSender.asInstanceOf[Pong] + pong.hit(count++) + } + } + +If the sender, sender future etc. is not available, then these methods will return 'null' so you should have a way of dealing with scenario. + +Messages and immutability +------------------------- + +**IMPORTANT**: Messages can be any kind of object but have to be immutable (there is a workaround, see next section). Java or Scala can’t enforce immutability (yet) so this has to be by convention. Primitives like String, int, Long are always immutable. Apart from these you have to create your own immutable objects to send as messages. If you pass on a reference to an instance that is mutable then this instance can be modified concurrently by two different Typed Actors and the Actor model is broken leaving you with NO guarantees and most likely corrupt data. diff --git a/akka-docs/pending/untyped-actors-java.rst b/akka-docs/pending/untyped-actors-java.rst new file mode 100644 index 0000000000..a5503391e4 --- /dev/null +++ b/akka-docs/pending/untyped-actors-java.rst @@ -0,0 +1,417 @@ +Actors (Java) +============= + += + +Module stability: **SOLID** + +The `Actor Model `_ provides a higher level of abstraction for writing concurrent and distributed systems. It alleviates the developer from having to deal with explicit locking and thread management, making it easier to write correct concurrent and parallel systems. Actors were defined in the 1973 paper by Carl Hewitt but have been popularized by the Erlang language, and used for example at Ericsson with great success to build highly concurrent and reliable telecom systems. + +Defining an Actor class +^^^^^^^^^^^^^^^^^^^^^^^ + +Actors in Java are created either by extending the 'UntypedActor' class and implementing the 'onReceive' method. This method takes the message as a parameter. + +Here is an example: + +.. code-block:: java + + public class SampleUntypedActor extends UntypedActor { + + public void onReceive(Object message) throws Exception { + if (message instanceof String) log.info("Received String message: %s", message); + else throw new IllegalArgumentException("Unknown message: " + message); + } + } + +The 'UntypedActor' class inherits from the 'akka.util.Logging' class which defines a logger in the 'log' field that you can use to log. The logging uses SLF4j backed by logback - for more information on how to configure the logger see `Logging `_. + +Creating Actors +^^^^^^^^^^^^^^^ + +Creating an Actor is done using the 'akka.actor.Actors.actorOf' factory method. This method returns a reference to the UntypedActor's ActorRef. This 'ActorRef' is an immutable serializable reference that you should use to communicate with the actor, send messages, link to it etc. This reference also functions as the context for the actor and holds run-time type information such as sender of the last message, + +.. code-block:: java + + ActorRef actor = Actors.actorOf(SampleUntypedActor.class); + myActor.start(); + +Normally you would want to import the 'actorOf' method like this: + +.. code-block:: java + + import static akka.actor.Actors.*; + ActorRef actor = actorOf(SampleUntypedActor.class); + +To avoid prefix it with 'Actors' every time you use it. + +You can also create & start the actor in one statement: + +.. code-block:: java + + ActorRef actor = actorOf(SampleUntypedActor.class).start(); + +The call to 'actorOf' returns an instance of 'ActorRef'. This is a handle to the 'UntypedActor' instance which you can use to interact with the Actor, like send messages to it etc. more on this shortly. The 'ActorRef' is immutble and has a one to one relationship with the Actor it represents. The 'ActorRef' is also serializable and network-aware. This means that you can serialize it, send it over the wire and use it on a remote host and it will still be representing the same Actor on the original node, across the network. + +Creating Actors with non-default constructor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If your UntypedActor has a constructor that takes parameters then you can't create it using 'actorOf(clazz)'. Instead you can use a variant of 'actorOf' that takes an instance of an 'UntypedActorFactory' in which you can create the Actor in any way you like. If you use this method then you to make sure that no one can get a reference to the actor instance. If they can get a reference it then they can touch state directly in bypass the whole actor dispatching mechanism and create race conditions which can lead to corrupt data. + +Here is an example: + +.. code-block:: java + + ActorRef actor = actorOf(new UntypedActorFactory() { + public UntypedActor create() { + return new MyUntypedActor("service:name", 5); + } + }); + +This way of creating the Actor is also great for integrating with Dependency Injection (DI) frameworks like Guice or Spring. + +UntypedActor context +-------------------- + +The UntypedActor base class contains almost no member fields or methods to invoke. It only has the 'onReceive(Object message)' method, which is defining the Actor's message handler, and some life-cycle callbacks that you can choose to implement: +## preStart +## postStop +## preRestart +## postRestart + +Most of the API is in the UnypedActorRef a reference for the actor. This reference is available in the 'getContext()' method in the UntypedActor (or you can use its alias, the 'context()' method, if you prefer. Here, for example, you find methods to reply to messages, send yourself messages, define timeouts, fault tolerance etc., start and stop etc. + +Identifying Actors +------------------ + +Each ActorRef has two methods: +* getContext().getUuid(); +* getContext().getId(); + +The difference is that the 'uuid' is generated by the runtime, guaranteed to be unique and can't be modified. While the 'id' can be set by the user (using 'getContext().setId(...)', and defaults to Actor class name. You can retrieve Actors by both UUID and ID using the 'ActorRegistry', see the section further down for details. + +Messages and immutability +------------------------- + +**IMPORTANT**: Messages can be any kind of object but have to be immutable. Akka can’t enforce immutability (yet) so this has to be by convention. + +Send messages +------------- + +Messages are sent to an Actor through one of the 'send' methods. +* 'sendOneWay' means “fire-and-forget”, e.g. send a message asynchronously and return immediately. +* 'sendRequestReply' means “send-and-reply-eventually”, e.g. send a message asynchronously and wait for a reply through a Future. Here you can specify a timeout. Using timeouts is very important. If no timeout is specified then the actor’s default timeout (set by the 'getContext().setTimeout(..)' method in the 'ActorRef') is used. This method throws an 'ActorTimeoutException' if the call timed out. +* 'sendRequestReplyFuture' sends a message asynchronously and returns a 'Future'. + +In all these methods you have the option of passing along your 'ActorRef' context variable. Make it a practive of doing so because it will allow the receiver actors to be able to respond to your message, since the sender reference is sent along with the message. + +Fire-forget +^^^^^^^^^^^ + +This is the preferred way of sending messages. No blocking waiting for a message. Give best concurrency and scalability characteristics. + +.. code-block:: java + + actor.sendOneWay("Hello"); + +Or with the sender reference passed along: + +.. code-block:: java + + actor.sendOneWay("Hello", getContext()); + +If invoked from within an Actor, then the sending actor reference will be implicitly passed along with the message and available to the receiving Actor in its 'getContext().getSender();' method. He can use this to reply to the original sender or use the 'getContext().reply(message);' method. + +If invoked from an instance that is **not** an Actor there will be no implicit sender passed along the message and you will get an 'IllegalStateException' if you call 'getContext().reply(..)'. + +Send-And-Receive-Eventually +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Using 'sendRequestReply' will send a message to the receiving Actor asynchronously but it will wait for a reply on a 'Future', blocking the sender Actor until either: + +* A reply is received, or +* The Future times out and an 'ActorTimeoutException' is thrown. + +You can pass an explicit time-out to the 'sendRequestReply' method and if none is specified then the default time-out defined in the sender Actor will be used. + +Here are some examples: + +.. code-block:: java + + UnypedActorRef actorRef = ... + + try { + Object result = actorRef.sendRequestReply("Hello", getContext(), 1000); + ... // handle reply + } catch(ActorTimeoutException e) { + ... // handle timeout + } + +Send-And-Receive-Future +^^^^^^^^^^^^^^^^^^^^^^^ + +Using 'sendRequestReplyFuture' will send a message to the receiving Actor asynchronously and will immediately return a 'Future'. + +.. code-block:: java + + Future future= actorRef.sendRequestReplyFuture("Hello", getContext(), 1000); + +The 'Future' interface looks like this: + +.. code-block:: java + + interface Future { + void await(); + void awaitBlocking(); + boolean isCompleted(); + boolean isExpired(); + long timeoutInNanos(); + Option result(); + Option exception(); + Future onComplete(Procedure> procedure); + } + +So the normal way of working with futures is something like this: + +.. code-block:: java + + Future future= actorRef.sendRequestReplyFuture("Hello", getContext(), 1000); + future.await(); + if (future.isCompleted()) { + Option resultOption = future.result(); + if (resultOption.isDefined()) { + Object result = resultOption.get(); + ... + } + ... // whatever + } + +The 'onComplete' callback can be used to register a callback to get a notification when the Future completes. Gives you a way to avoid blocking. + +We also have a utility class 'Futures' that have a couple of convenience methods: + +.. code-block:: java + + void awaitAll(Future[] futures); + Future awaitOne(Future[] futures) + +Forward message +^^^^^^^^^^^^^^^ + +You can forward a message from one actor to another. This means that the original sender address/reference is maintained even though the message is going through a 'mediator'. This can be useful when writing actors that work as routers, load-balancers, replicators etc. You need to pass along your ActorRef context variable as well. + +.. code-block:: java + + getContext().forward(message, getContext()); + +Receive messages +---------------- + +When an actor receives a message is passed into the 'onReceive' method, this is an abstract method on the 'UntypedActor' base class that needs to be defined. + +Here is an example: + +.. code-block:: java + + public class SampleUntypedActor extends UntypedActor { + + public void onReceive(Object message) throws Exception { + if (message instanceof String) log.info("Received String message: %s", message); + else throw new IllegalArgumentException("Unknown message: " + message); + } + } + +Reply to messages +----------------- + +Reply using the 'replySafe' and 'replyUnsafe' 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. + +.. code-block:: java + + public void onReceive(Object message) throws Exception { + 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()); + } + } + } + +In this case we will a reply back to the Actor that send 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. + +.. code-block:: java + + public void onReceive(Object message) throws Exception { + 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 + else ... // handle failure + } + } + } + +Reply using the sender reference +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If the sender reference (the sender's 'ActorRef') is passed into one ofe the 'send*' methods it will be implicitly passed along together with the message and will be available in the 'Option getSender()' method on the 'ActorRef. This means that you can use this field to send a message back to the sender. + +On this 'Option' you can invoke 'boolean isDefined()' or 'boolean isEmpty()' to check if the sender is available or not, and if it is call 'get()' to get the reference. It's important to know that 'getSender().get()' will throw an exception if there is no sender in scope. The same pattern holds for using the 'getSenderFuture()' in the section below. + +.. code-block:: java + + public void onReceive(Object message) throws Exception { + if (message instanceof String) { + String msg = (String)message; + if (msg.equals("Hello")) { + // Reply to original sender of message using the sender reference + // also passing along my own refererence (the context) + if (getContext().getSender().isDefined) + getContext().getSender().get().sendOneWay(msg + " from " + getContext().getUuid(), getContext()); + } + } + } + +Reply using the sender future +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If a message was sent with the 'sendRequestReply' or 'sendRequestReplyFuture' methods, which both implements request-reply semantics using Future's, then you either have the option of replying using the 'reply' method as above. This method will then resolve the Future. But you can also get a reference to the Future directly and resolve it yourself or if you would like to store it away to resolve it later, or pass it on to some other Actor to resolve it. + +The reference to the Future resides in the 'ActorRef' instance and can be retreived using 'Option getSenderFuture()'. + +CompletableFuture is a future with methods for 'completing the future: +* completeWithResult(..) +* completeWithException(..) + +Here is an example of how it can be used: + +.. code-block:: java + + public void onReceive(Object message) throws Exception { + if (message instanceof String) { + String msg = (String)message; + if (msg.equals("Hello") && getContext().getSenderFuture().isDefined()) { + // Reply to original sender of message using the sender future reference + getContext().getSenderFuture().get().completeWithResult(msg + " from " + getContext().getUuid()); + } + } + } + +Reply using the channel +^^^^^^^^^^^^^^^^^^^^^^^ + +If you want to have a handle to an object to whom you can reply to the message, you can use the Channel abstraction. +Simply call getContext().channel() and then you can forward that to others, store it away or otherwise until you want to reply, +which you do by Channel.sendOneWay(msg) + +.. code-block:: java + + public void onReceive(Object message) throws Exception { + if (message instanceof String) { + String msg = (String)message; + if (msg.equals("Hello") && getContext().getSenderFuture().isDefined()) { + // Reply to original sender of message using the channel + getContext().channel().sendOneWay(msg + " from " + getContext().getUuid()); + } + } + } + +Summary of reply semantics and options +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* getContext().reply(...) can be used to reply to an Actor or a Future. +* getContext().getSender() is a reference to the actor you can reply to, if it exists +* getContext().getSenderFuture() is a reference to the future you can reply to, if it exists +* getContext().channel() is a reference providing an abstraction to either self.sender or self.senderFuture if one is set, providing a single reference to store and reply to (the reference equivalent to the 'reply(...)' method). +* getContext().getSender() and getContext().getSenderFuture() will never be set at the same time, as there can only be one reference to accept a reply. + +Starting actors +--------------- + +Actors are started by invoking the ‘start’ method. + +.. code-block:: java + + ActorRef actor = actorOf(SampleUntypedActor.class); + myActor.start(); + +You can create and start the Actor in a oneliner like this: + +.. code-block:: java + + ActorRef actor = actorOf(SampleUntypedActor.class).start(); + +When you start the actor then it will automatically call the 'preStart' callback method on the 'UntypedActor'. This is an excellent place to add initialization code for the actor. + +.. code-block:: java + + @Override + void preStart() { + ... // initialization code + } + +Stopping actors +--------------- + +Actors are stopped by invoking the ‘stop’ method. + +.. code-block:: java + + actor.stop(); + +When stop is called then a call to the ‘postStop’ callback method will take place. The Actor can use this callback to implement shutdown behavior. + +.. code-block:: java + + @Override + void postStop() { + ... // clean up resources + } + +You can shut down all Actors in the system by invoking: + +.. code-block:: java + + Actors.registry().shutdownAll(); + +PoisonPill +---------- + +You can also send an actor the akka.actor.PoisonPill message, which will stop the actor when the message is processed. +If the sender is a Future, the Future will be completed with an akka.actor.ActorKilledException("PoisonPill") + +Use it like this: + +.. code-block:: java + + actor.sendOneWay(poisonPill()); + +Killing an Actor +---------------- + +You can kill an actor by sending a 'new Kill()' message. This will restart the actor through regular supervisor semantics. + +Use it like this: + +.. code-block:: java + + // kill the actor called 'victim' + victim.sendOneWay(kill()); + +Actor life-cycle +---------------- + +The actor has a well-defined non-circular life-cycle. + +``_ +NEW (newly created actor) - can't receive messages (yet) + => STARTED (when 'start' is invoked) - can receive messages + => SHUT DOWN (when 'exit' or 'stop' is invoked) - can't do anything +``_ diff --git a/akka-docs/pending/use-cases.rst b/akka-docs/pending/use-cases.rst new file mode 100644 index 0000000000..8647d0b17c --- /dev/null +++ b/akka-docs/pending/use-cases.rst @@ -0,0 +1,31 @@ +Examples of use-cases for Akka +============================== + +There is a great discussion on use-cases for Akka with some good write-ups by production users here: ``_ + +Here are some of the areas where Akka is being deployed into production +----------------------------------------------------------------------- + +# **Transaction processing (Online Gaming, Finance/Banking, Trading, Statistics, Betting, Social Media, Telecom)** +** Scale up, scale out, fault-tolerance / HA +# **Service backend (any industry, any app)** +** Service REST, SOAP, Cometd, WebSockets etc +** Act as message hub / integration layer +** Scale up, scale out, fault-tolerance / HA +# **Concurrency/parallelism (any app)** +** Correct +** Simple to work with and understand +** Just add the jars to your existing JVM project (use Scala, Java, Groovy or JRuby) +# **Simulation** +** Master/Worker, Compute Grid, MapReduce etc. +# **Batch processing (any industry)** +** Camel integration to hook up with batch data sources +** Actors divide and conquer the batch workloads +# **Communications Hub (Telecom, Web media, Mobile media)** +** Scale up, scale out, fault-tolerance / HA +# **Gaming and Betting (MOM, online gaming, betting)** +** Scale up, scale out, fault-tolerance / HA +# **Business Intelligence/Data Mining/general purpose crunching** +** Scale up, scale out, fault-tolerance / HA +# **Complex Event Stream Processing** +** Scale up, scale out, fault-tolerance / HA diff --git a/akka-docs/pending/web.rst b/akka-docs/pending/web.rst new file mode 100644 index 0000000000..7d09ede65c --- /dev/null +++ b/akka-docs/pending/web.rst @@ -0,0 +1,99 @@ +Web Framework Integrations +========================== + +Play Framework +============== + +Home page: ``_ +Akka Play plugin: ``_ +Read more here: ``_ + +Lift Web Framework +================== + +Home page: ``_ + +In order to use Akka with Lift you basically just have to do one thing, add the 'AkkaServlet' to your 'web.xml'. + +web.xml +------- + +.. code-block:: xml + + + + + AkkaServlet + akka.comet.AkkaServlet + + + AkkaServlet + /* + + + + + LiftFilter + Lift Filter + The Filter that intercepts lift calls + net.liftweb.http.LiftFilter + + + LiftFilter + /* + + + +Boot class +---------- + +Lift bootstrap happens in the Lift 'Boot' class. Here is a good place to add Akka specific initialization. For example add declarative supervisor configuration to wire up the initial Actors. +Here is a full example taken from the Akka sample code, found here ``_. + +If a request is processed by Liftweb filter, Akka will not process the request. To disable processing of a request by the Lift filter : +* append partial function to LiftRules.liftRequest and return *false* value to disable processing of matching request +* use LiftRules.passNotFoundToChain to chain the request to the Akka filter + +Example of Boot class source code : +``_ +class Boot { + def boot { + // where to search snippet + LiftRules.addToPackages("sample.lift") + + LiftRules.httpAuthProtectedResource.prepend { + case (ParsePath("liftpage" :: Nil, _, _, _)) => Full(AuthRole("admin")) + } + + LiftRules.authentication = HttpBasicAuthentication("lift") { + case ("someuser", "1234", req) => { + Log.info("You are now authenticated !") + userRoles(AuthRole("admin")) + true + } + } + + LiftRules.liftRequest.append { + case Req("liftcount" :: _, _, _) => false + case Req("persistentliftcount" :: _, _, _) => false + } + LiftRules.passNotFoundToChain = true + + // Akka supervisor configuration wiring up initial Actor services + val supervisor = Supervisor( + SupervisorConfig( + RestartStrategy(OneForOne, 3, 100, List(classOf[Exception])), + Supervise( + actorOf[SimpleService], + LifeCycle(Permanent)) :: + Supervise( + actorOf[PersistentSimpleService], + LifeCycle(Permanent)) :: + Nil)) + + // Build SiteMap + // val entries = Menu(Loc("Home", List("index"), "Home")) :: Nil + // LiftRules.setSiteMap(SiteMap(entries:_*)) + } +} +``_ From 84f6e4f795924a0683427d423d7986b84d66f7bb Mon Sep 17 00:00:00 2001 From: Heiko Seeberger Date: Sun, 10 Apr 2011 02:44:27 -0700 Subject: [PATCH 030/147] Text changes according to my review (https://groups.google.com/a/typesafe.com/group/everyone/browse_thread/thread/6661e205caf3434d?hl=de). --- akka-docs/manual/getting-started-first.rst | 37 ++++++++-------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst index 8d41572471..e3f6a78d2d 100644 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -15,7 +15,7 @@ Since they are so similar we will present them both in this tutorial. The sample application that we will create is using actors to calculate the value of Pi. Calculating Pi is a CPU intensive operation and we will utilize Akka Actors to write a concurrent solution that scales out to multi-core processors. This sample will be extended in future tutorials to use Akka Remote Actors to scale out on multiple machines in a cluster. -We will be using an algorithm that is what is called "embarrassingly parallel" which just means that each job is completely isolated and not coupled with any other job. Since this algorithm is so parallelizable it suits the actor model very well. +We will be using an algorithm that is called "embarrassingly parallel" which just means that each job is completely isolated and not coupled with any other job. Since this algorithm is so parallelizable it suits the actor model very well. Here is the formula for the algorithm we will use: @@ -144,7 +144,11 @@ To use the plugin, first add a plugin definition to your SBT project by creating Now we need to create a project definition using our Akka SBT plugin. We do that by creating a ``Project.scala`` file in the ``build`` directory containing:: - class TutorialOneProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject + import sbt._ + + class TutorialOneProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject { + val akkaRepo = "Akka Repo" at "http://akka.io/repository" + } The magic is in mixing in the ``AkkaProject`` trait. @@ -157,7 +161,7 @@ Not needed in this tutorial, but if you would like to use additional Akka module So, now we are all set. Just one final thing to do; make SBT download all dependencies it needs. That is done by invoking:: - $ sbt update + > update SBT itself needs a whole bunch of dependencies but our project will only need one; ``akka-actor-1.1.jar``. SBT downloads that as well. @@ -180,7 +184,7 @@ We start by creating a ``Pi.scala`` file and add these import statements at the If you are using SBT in this tutorial then create the file in the ``src/main/scala`` directory. -If you are using the command line tools then just create the file wherever you want, I will create it in a directory called ``tutorial`` the root of the Akka distribution, e.g. in ``$AKKA_HOME/tutorial/Pi.scala``. +If you are using the command line tools then just create the file wherever you want. I will create it in a directory called ``tutorial`` at the root of the Akka distribution, e.g. in ``$AKKA_HOME/tutorial/Pi.scala``. Creating the messages --------------------- @@ -189,9 +193,9 @@ The design we are aiming for is to have one ``Master`` actor initiating the comp With this in mind, let's now create the messages that we want to have flowing in the system. We need three different messages: -- ``Calculate`` -- starts the calculation -- ``Work`` -- contains the work assignment -- ``Result`` -- contains the result from the worker's calculation +- ``Calculate`` -- sent to the ``Master`` actor to start the calculation +- ``Work`` -- sent from the ``Master`` actor to the ``Worker`` actors containing the work assignment +- ``Result`` -- sent from the ``Worker`` actors to the ``Master`` actor containing the result from the worker's calculation Messages sent to actors should always be immutable to avoid sharing mutable state. In scala we have 'case classes' which make excellent messages. So let's start by creating three messages as case classes. We also create a common base trait for our messages (that we define as being ``sealed`` in order to prevent creating messages outside our control):: @@ -215,7 +219,7 @@ Now we can create the worker actor. This is done by mixing in the ``Actor`` tra } } -As you can see we have now created an ``Actor`` with a ``receive`` method that as a handler for the ``Work`` message. In this handler we invoke the ``calculatePiFor(..)`` method, wraps the result in a ``Result`` message and sends it back to the original sender using ``self.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 use. +As you can see we have now created an ``Actor`` with a ``receive`` 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 ``self.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 use. The only thing missing in our ``Worker`` actor is the implementation on the ``calculatePiFor(..)`` method. There are many ways we can implement this algorithm in Scala, in this introductory tutorial we have chosen an imperative style using a for comprehension and an accumulator:: @@ -479,26 +483,13 @@ If you have based the tutorial on SBT then you can run the application directly > compile ... -When this in done we can start up a Scala REPL (console/interpreter) directly inside SBT with our dependencies and classes on the classpath:: +When this in done we can run our application directly inside SBT:: - > console + > run ... - scala> - -In this REPL we can now evaluate Scala code. For example run our application:: - - scala> akka.tutorial.scala.first.Pi.calculate( - | nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) - AKKA_HOME is defined as [/Users/jboner/src/akka-stuff/akka-core], loading config from \ - [/Users/jboner/src/akka-stuff/akka-core/config/akka.conf]. - Pi estimate: 3.1435501812459323 Calculation time: 942 millis -See it complete the calculation and print out the result. When that is done we can exit the REPL:: - - > :quit - Yippee! It is working. Conclusion From 2ad80c34da709368a29c861c23a47976ac8997f7 Mon Sep 17 00:00:00 2001 From: Heiko Seeberger Date: Sun, 10 Apr 2011 12:18:09 +0200 Subject: [PATCH 031/147] Code changes according to my review (https://groups.google.com/a/typesafe.com/group/everyone/browse_thread/thread/6661e205caf3434d?hl=de) plus some more Scala style improvements. --- akka-docs/manual/getting-started-first.rst | 57 ++++++++++--------- .../src/main/scala/Pi.scala | 25 ++++---- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst index e3f6a78d2d..e5f7a4ba1f 100644 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -203,7 +203,7 @@ Messages sent to actors should always be immutable to avoid sharing mutable stat case object Calculate extends PiMessage - case class Work(arg: Int, nrOfElements: Int) extends PiMessage + case class Work(start: Int, nrOfElements: Int) extends PiMessage case class Result(value: Double) extends PiMessage @@ -214,8 +214,8 @@ Now we can create the worker actor. This is done by mixing in the ``Actor`` tra class Worker extends Actor { def receive = { - case Work(arg, nrOfElements) => - self reply Result(calculatePiFor(arg, nrOfElements)) // perform the work + case Work(start, nrOfElements) => + self reply Result(calculatePiFor(start, nrOfElements)) // perform the work } } @@ -223,9 +223,9 @@ As you can see we have now created an ``Actor`` with a ``receive`` method as a h The only thing missing in our ``Worker`` actor is the implementation on the ``calculatePiFor(..)`` method. There are many ways we can implement this algorithm in Scala, in this introductory tutorial we have chosen an imperative style using a for comprehension and an accumulator:: - def calculatePiFor(start: Int, elems: Int): Double = { + def calculatePiFor(start: Int, nrOfElements: Int): Double = { var acc = 0.0 - for (i <- start until (start + elems)) + for (i <- start until (start + nrOfElements)) acc += 4 * math.pow(-1, i) / (2 * i + 1) acc } @@ -268,14 +268,14 @@ Let's now write the master actor:: def receive = { ... } - override def preStart = start = System.currentTimeMillis + override def preStart { + start = now + } - override def postStop = { + override def postStop { // tell the world that the calculation is complete - println( - "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" - .format(pi, (System.currentTimeMillis - start))) - latch.countDown + println("\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis".format(pi, (now - start))) + latch.countDown() } } @@ -312,7 +312,7 @@ Now, let's capture this in code:: // handle result from the worker pi += value nrOfResults += 1 - if (nrOfResults == nrOfMessages) self.stop + if (nrOfResults == nrOfMessages) self.stop() } Bootstrap the calculation @@ -338,7 +338,7 @@ Now the only thing that is left to implement is the runner that should bootstrap master ! Calculate // wait for master to shut down - latch.await + latch.await() } } @@ -348,12 +348,12 @@ But before we package it up and run it, let's take a look at the full code now, package akka.tutorial.scala.first - import akka.actor.{Actor, ActorRef, PoisonPill} + import akka.actor.{Actor, PoisonPill} import Actor._ import akka.routing.{Routing, CyclicIterator} import Routing._ - import akka.dispatch.Dispatchers + import System.{currentTimeMillis => now} import java.util.concurrent.CountDownLatch object Pi extends App { @@ -365,7 +365,7 @@ But before we package it up and run it, let's take a look at the full code now, // ==================== sealed trait PiMessage case object Calculate extends PiMessage - case class Work(arg: Int, nrOfElements: Int) extends PiMessage + case class Work(start: Int, nrOfElements: Int) extends PiMessage case class Result(value: Double) extends PiMessage // ================== @@ -374,16 +374,16 @@ But before we package it up and run it, let's take a look at the full code now, class Worker extends Actor { // define the work - def calculatePiFor(start: Int, elems: Int): Double = { + def calculatePiFor(start: Int, nrOfElements: Int): Double = { var acc = 0.0 - for (i <- start until (start + elems)) + for (i <- start until (start + nrOfElements)) acc += 4 * math.pow(-1, i) / (2 * i + 1) acc } def receive = { - case Work(arg, nrOfElements) => - self reply Result(calculatePiFor(arg, nrOfElements)) // perform the work + case Work(start, nrOfElements) => + self reply Result(calculatePiFor(start, nrOfElements)) // perform the work } } @@ -407,6 +407,7 @@ But before we package it up and run it, let's take a look at the full code now, def receive = { case Calculate => // schedule work + //for (arg <- 0 until nrOfMessages) router ! Work(arg, nrOfElements) for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) // send a PoisonPill to all workers telling them to shut down themselves @@ -419,17 +420,17 @@ But before we package it up and run it, let's take a look at the full code now, // handle result from the worker pi += value nrOfResults += 1 - if (nrOfResults == nrOfMessages) self.stop + if (nrOfResults == nrOfMessages) self.stop() } - override def preStart = start = System.currentTimeMillis + override def preStart { + start = now + } - override def postStop = { + override def postStop { // tell the world that the calculation is complete - println( - "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" - .format(pi, (System.currentTimeMillis - start))) - latch.countDown + println("\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis".format(pi, (now - start))) + latch.countDown() } } @@ -448,7 +449,7 @@ But before we package it up and run it, let's take a look at the full code now, master ! Calculate // wait for master to shut down - latch.await + latch.await() } } diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index 568a4a092f..3107bc280a 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -4,11 +4,10 @@ package akka.tutorial.scala.first -import akka.actor.{Actor, ActorRef, PoisonPill} +import akka.actor.{Actor, PoisonPill} import Actor._ import akka.routing.{Routing, CyclicIterator} import Routing._ -import akka.dispatch.Dispatchers import System.{currentTimeMillis => now} import java.util.concurrent.CountDownLatch @@ -48,7 +47,7 @@ object Pi extends App { // ==================== sealed trait PiMessage case object Calculate extends PiMessage - case class Work(arg: Int, nrOfElements: Int) extends PiMessage + case class Work(start: Int, nrOfElements: Int) extends PiMessage case class Result(value: Double) extends PiMessage // ================== @@ -57,16 +56,16 @@ object Pi extends App { class Worker extends Actor { // define the work - def calculatePiFor(start: Int, elems: Int): Double = { + def calculatePiFor(start: Int, nrOfElements: Int): Double = { var acc = 0.0 - for (i <- start until (start + elems)) + for (i <- start until (start + nrOfElements)) acc += 4 * math.pow(-1, i) / (2 * i + 1) acc } def receive = { - case Work(arg, nrOfElements) => - self reply Result(calculatePiFor(arg, nrOfElements)) // perform the work + case Work(start, nrOfElements) => + self reply Result(calculatePiFor(start, nrOfElements)) // perform the work } } @@ -103,15 +102,17 @@ object Pi extends App { // handle result from the worker pi += value nrOfResults += 1 - if (nrOfResults == nrOfMessages) self.stop + if (nrOfResults == nrOfMessages) self.stop() } - override def preStart = start = now + override def preStart { + start = now + } - override def postStop = { + override def postStop { // tell the world that the calculation is complete println("\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis".format(pi, (now - start))) - latch.countDown + latch.countDown() } } @@ -130,6 +131,6 @@ object Pi extends App { master ! Calculate // wait for master to shut down - latch.await + latch.await() } } From b8097f3756bc426a39293a2aaf59e87024fac84a Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Sun, 10 Apr 2011 13:07:57 -0600 Subject: [PATCH 032/147] Documentation cleanup --- akka-docs/pending/articles.rst | 12 +-- akka-docs/pending/building-akka.rst | 30 +++--- akka-docs/pending/buildr.rst | 80 ++++++++-------- akka-docs/pending/cluster-membership.rst | 23 +++-- akka-docs/pending/dataflow-java.rst | 100 ++++++++++---------- akka-docs/pending/dataflow-scala.rst | 89 +++++++++-------- akka-docs/pending/developer-guidelines.rst | 10 +- akka-docs/pending/fault-tolerance-java.rst | 43 ++++----- akka-docs/pending/fault-tolerance-scala.rst | 41 ++++---- akka-docs/pending/futures-scala.rst | 16 ++-- 10 files changed, 219 insertions(+), 225 deletions(-) diff --git a/akka-docs/pending/articles.rst b/akka-docs/pending/articles.rst index e8b411f47d..91138b404c 100644 --- a/akka-docs/pending/articles.rst +++ b/akka-docs/pending/articles.rst @@ -2,7 +2,7 @@ Articles & Presentations ======================== Videos -====== +------ `Functional Programming eXchange - March 2011 `_ @@ -17,7 +17,7 @@ Videos `Akka talk at Scala Days - March 2010 `_ Articles -======== +-------- `Remote Actor Class Loading with Akka `_ @@ -88,13 +88,13 @@ Articles `Enterprise scala actors: introducing the Akka framework `_ Books -===== +----- `Akka and Camel `_ (appendix E of `Camel in Action `_) `Ett första steg i Scala `_ (Kapitel "Aktörer och Akka") (en. "A first step in Scala", chapter "Actors and Akka", book in Swedish) Presentations -============= +------------- `Slides from Akka talk at Scala Days 2010, good short intro to Akka `_ @@ -105,14 +105,14 @@ Presentations ``_ Podcasts -======== +-------- `Episode 16 – Scala and Akka an Interview with Jonas Boner `_ `Jonas Boner on the Akka framework, Scala, and highly scalable applications `_ Interviews -========== +---------- `JetBrains/DZone interview: Talking about Akka, Scala and life with Jonas Bonér `_ diff --git a/akka-docs/pending/building-akka.rst b/akka-docs/pending/building-akka.rst index aeeec7409d..31af34c687 100644 --- a/akka-docs/pending/building-akka.rst +++ b/akka-docs/pending/building-akka.rst @@ -4,7 +4,7 @@ Building Akka This page describes how to build and run Akka from the latest source code. Get the source code -=================== +------------------- Akka uses `Git `_ and is hosted at `Github `_. @@ -26,7 +26,7 @@ If you have already cloned the repositories previously then you can update the c git pull origin master SBT - Simple Build Tool -======================= +----------------------- Akka is using the excellent `SBT `_ build system. So the first thing you have to do is to download and install SBT. You can read more about how to do that `here `_ . @@ -37,7 +37,7 @@ The Akka SBT build file is ``project/build/AkkaProject.scala`` with some propert ---- Building Akka -============= +------------- First make sure that you are in the akka code directory: @@ -46,7 +46,7 @@ First make sure that you are in the akka code directory: cd akka Fetching dependencies ---------------------- +^^^^^^^^^^^^^^^^^^^^^ SBT does not fetch dependencies automatically. You need to manually do this with the ``update`` command: @@ -59,7 +59,7 @@ Once finished, all the dependencies for Akka will be in the ``lib_managed`` dire *Note: you only need to run {{update}} the first time you are building the code, or when the dependencies have changed.* Building --------- +^^^^^^^^ To compile all the Akka core modules use the ``compile`` command: @@ -76,7 +76,7 @@ You can run all tests with the ``test`` command: If compiling and testing are successful then you have everything working for the latest Akka development version. Publish to local Ivy repository -------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you want to deploy the artifacts to your local Ivy repository (for example, to use from an SBT project) use the ``publish-local`` command: @@ -85,7 +85,7 @@ If you want to deploy the artifacts to your local Ivy repository (for example, t sbt publish-local Publish to local Maven repository ---------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you want to deploy the artifacts to your local Maven repository use: @@ -94,7 +94,7 @@ If you want to deploy the artifacts to your local Maven repository use: sbt publish-local publish SBT interactive mode --------------------- +^^^^^^^^^^^^^^^^^^^^ Note that in the examples above we are calling ``sbt compile`` and ``sbt test`` and so on. SBT also has an interactive mode. If you just run ``sbt`` you enter the interactive SBT prompt and can enter the commands directly. This saves starting up a new JVM instance for each command and can be much faster and more convenient. @@ -118,7 +118,7 @@ For example, building Akka as above is more commonly done like this: ... SBT batch mode --------------- +^^^^^^^^^^^^^^ It's also possible to combine commands in a single call. For example, updating, testing, and publishing Akka to the local Ivy repository can be done with: @@ -129,7 +129,7 @@ It's also possible to combine commands in a single call. For example, updating, ---- Building Akka Modules -===================== +--------------------- To build Akka Modules first build and publish Akka to your local Ivy repository as described above. Or using: @@ -146,7 +146,7 @@ Then you can build Akka Modules using the same steps as building Akka. First upd sbt update publish-local Microkernel distribution ------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^ To build the Akka Modules microkernel (the same as the Akka Modules distribution download) use the ``dist`` command: @@ -170,10 +170,10 @@ The microkernel will boot up and install the sample applications that reside in ---- Scripts -======= +------- Linux/Unix init script ----------------------- +^^^^^^^^^^^^^^^^^^^^^^ Here is a Linux/Unix init script that can be very useful: @@ -182,7 +182,7 @@ Here is a Linux/Unix init script that can be very useful: Copy and modify as needed. Simple startup shell script ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ This little script might help a bit. Just make sure you have the Akka distribution in the '$AKKA_HOME/dist' directory and then invoke this script to start up the kernel. The distribution is created in the './dist' dir for you if you invoke 'sbt dist'. @@ -193,7 +193,7 @@ Copy and modify as needed. ---- Dependencies -============ +------------ If you are managing dependencies by hand you can find out what all the compile dependencies are for each module by looking in the ``lib_managed/compile`` directories. For example, you can run this to create a listing of dependencies (providing you have the source code and have run ``sbt update``): diff --git a/akka-docs/pending/buildr.rst b/akka-docs/pending/buildr.rst index 85fb9bcd19..a684463270 100644 --- a/akka-docs/pending/buildr.rst +++ b/akka-docs/pending/buildr.rst @@ -3,53 +3,53 @@ Using Akka in a Buildr project This is an example on how to use Akka in a project based on Buildr -``_ -require 'buildr/scala' +.. code-block:: ruby -VERSION_NUMBER = "0.6" -GROUP = "se.scalablesolutions.akka" + require 'buildr/scala' -repositories.remote << "http://www.ibiblio.org/maven2/" -repositories.remote << "http://www.lag.net/repo" -repositories.remote << "http://multiverse.googlecode.com/svn/maven-repository/releases" + VERSION_NUMBER = "0.6" + GROUP = "se.scalablesolutions.akka" -AKKA = group('akka-core', 'akka-comet', 'akka-util','akka-kernel', 'akka-rest', 'akka-util-java', - 'akka-security','akka-persistence-common', 'akka-persistence-redis', - 'akka-amqp', - :under=> 'se.scalablesolutions.akka', - :version => '0.6') -ASPECTJ = "org.codehaus.aspectwerkz:aspectwerkz-nodeps-jdk5:jar:2.1" -SBINARY = "sbinary:sbinary:jar:0.3" -COMMONS_IO = "commons-io:commons-io:jar:1.4" -CONFIGGY = "net.lag:configgy:jar:1.4.7" -JACKSON = group('jackson-core-asl', 'jackson-mapper-asl', - :under=> 'org.codehaus.jackson', - :version => '1.2.1') -MULTIVERSE = "org.multiverse:multiverse-alpha:jar:jar-with-dependencies:0.3" -NETTY = "org.jboss.netty:netty:jar:3.2.0.ALPHA2" -PROTOBUF = "com.google.protobuf:protobuf-java:jar:2.2.0" -REDIS = "com.redis:redisclient:jar:1.0.1" -SJSON = "sjson.json:sjson:jar:0.3" + repositories.remote << "http://www.ibiblio.org/maven2/" + repositories.remote << "http://www.lag.net/repo" + repositories.remote << "http://multiverse.googlecode.com/svn/maven-repository/releases" -Project.local_task "run" + AKKA = group('akka-core', 'akka-comet', 'akka-util','akka-kernel', 'akka-rest', 'akka-util-java', + 'akka-security','akka-persistence-common', 'akka-persistence-redis', + 'akka-amqp', + :under=> 'se.scalablesolutions.akka', + :version => '0.6') + ASPECTJ = "org.codehaus.aspectwerkz:aspectwerkz-nodeps-jdk5:jar:2.1" + SBINARY = "sbinary:sbinary:jar:0.3" + COMMONS_IO = "commons-io:commons-io:jar:1.4" + CONFIGGY = "net.lag:configgy:jar:1.4.7" + JACKSON = group('jackson-core-asl', 'jackson-mapper-asl', + :under=> 'org.codehaus.jackson', + :version => '1.2.1') + MULTIVERSE = "org.multiverse:multiverse-alpha:jar:jar-with-dependencies:0.3" + NETTY = "org.jboss.netty:netty:jar:3.2.0.ALPHA2" + PROTOBUF = "com.google.protobuf:protobuf-java:jar:2.2.0" + REDIS = "com.redis:redisclient:jar:1.0.1" + SJSON = "sjson.json:sjson:jar:0.3" -desc "Akka Chat Sample Module" -define "akka-sample-chat" do - project.version = VERSION_NUMBER - project.group = GROUP + Project.local_task "run" - compile.with AKKA, CONFIGGY + desc "Akka Chat Sample Module" + define "akka-sample-chat" do + project.version = VERSION_NUMBER + project.group = GROUP - p artifact(MULTIVERSE).to_s + compile.with AKKA, CONFIGGY - package(:jar) + p artifact(MULTIVERSE).to_s - task "run" do - Java.java "scala.tools.nsc.MainGenericRunner", - :classpath => [ compile.dependencies, compile.target, - ASPECTJ, COMMONS_IO, JACKSON, NETTY, MULTIVERSE, PROTOBUF, REDIS, - SBINARY, SJSON], - :java_args => ["-server"] + package(:jar) + + task "run" do + Java.java "scala.tools.nsc.MainGenericRunner", + :classpath => [ compile.dependencies, compile.target, + ASPECTJ, COMMONS_IO, JACKSON, NETTY, MULTIVERSE, PROTOBUF, REDIS, + SBINARY, SJSON], + :java_args => ["-server"] + end end -end -``_ diff --git a/akka-docs/pending/cluster-membership.rst b/akka-docs/pending/cluster-membership.rst index 9c7d75194a..6aa70e8bce 100644 --- a/akka-docs/pending/cluster-membership.rst +++ b/akka-docs/pending/cluster-membership.rst @@ -6,7 +6,7 @@ Module stability: **IN PROGRESS** Akka supports a Cluster Membership through a `JGroups `_ based implementation. JGroups is is a `P2P `_ clustering API Configuration -============= +------------- The cluster is configured in 'akka.conf' by adding the Fully Qualified Name (FQN) of the actor class and serializer: @@ -18,15 +18,15 @@ The cluster is configured in 'akka.conf' by adding the Fully Qualified Name (FQN name = "default" # The name of the cluster serializer = "akka.serialization.Serializer$Java" # FQN of the serializer class } - } + } How to join the cluster -======================= +----------------------- The node joins the cluster when the 'RemoteNode' and/or 'RemoteServer' servers are started. Cluster API -=========== +----------- Interaction with the cluster is done through the 'akka.remote.Cluster' object. @@ -80,11 +80,10 @@ Here is an example: Here is another example: -``_ -Cluster.lookup({ - case remoteAddress @ RemoteAddress(_,_) => remoteAddress -}) match { - case Some(remoteAddress) => spawnAllRemoteActors(remoteAddress) - case None => handleNoRemoteNodeFound -} -``_ +.. code-block:: scala + Cluster.lookup({ + case remoteAddress @ RemoteAddress(_,_) => remoteAddress + }) match { + case Some(remoteAddress) => spawnAllRemoteActors(remoteAddress) + case None => handleNoRemoteNodeFound + } diff --git a/akka-docs/pending/dataflow-java.rst b/akka-docs/pending/dataflow-java.rst index 69d7cec42e..a5f1929431 100644 --- a/akka-docs/pending/dataflow-java.rst +++ b/akka-docs/pending/dataflow-java.rst @@ -2,10 +2,9 @@ Dataflow Concurrency (Java) =========================== Introduction -============ +------------ -IMPORTANT: As of Akka 1.1, Akka Future, CompletableFuture and DefaultCompletableFuture have all the functionality of DataFlowVariables, they also support non-blocking composition and advanced features like fold and reduce, Akka DataFlowVariable is therefor deprecated and will probably resurface in the following release as a DSL on top of Futures. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +**IMPORTANT: As of Akka 1.1, Akka Future, CompletableFuture and DefaultCompletableFuture have all the functionality of DataFlowVariables, they also support non-blocking composition and advanced features like fold and reduce, Akka DataFlowVariable is therefor deprecated and will probably resurface in the following release as a DSL on top of Futures.** Akka implements `Oz-style dataflow concurrency `_ through dataflow (single assignment) variables and lightweight (event-based) processes/threads. @@ -80,12 +79,12 @@ You can also set the thread to a reference to be able to control its life-cycle: t.sendOneWay(new Exit()); // shut down the thread Examples -======== +-------- Most of these examples are taken from the `Oz wikipedia page `_ Simple DataFlowVariable example -------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This example is from Oz wikipedia page: http://en.wikipedia.org/wiki/Oz_(programming_language). Sort of the "Hello World" of dataflow concurrency. @@ -132,60 +131,59 @@ Example in Akka: }); Example on life-cycle management of DataFlowVariables ------------------------------------------------------ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Shows how to shutdown dataflow variables and bind threads to values to be able to interact with them (exit etc.). Example in Akka: -``_ -import static akka.dataflow.DataFlow.*; -import akka.japi.Effect; +.. code-block:: java + import static akka.dataflow.DataFlow.*; + import akka.japi.Effect; -// create four 'int' data flow variables -DataFlowVariable x = new DataFlowVariable(); -DataFlowVariable y = new DataFlowVariable(); -DataFlowVariable z = new DataFlowVariable(); -DataFlowVariable v = new DataFlowVariable(); + // create four 'int' data flow variables + DataFlowVariable x = new DataFlowVariable(); + DataFlowVariable y = new DataFlowVariable(); + DataFlowVariable z = new DataFlowVariable(); + DataFlowVariable v = new DataFlowVariable(); -ActorRef main = thread(new Effect() { - public void apply() { - System.out.println("Thread 'main'") - if (x.get() > y.get()) { - z.set(x); - System.out.println("'z' set to 'x': " + z.get()); - } else { - z.set(y); - System.out.println("'z' set to 'y': " + z.get()); + ActorRef main = thread(new Effect() { + public void apply() { + System.out.println("Thread 'main'") + if (x.get() > y.get()) { + z.set(x); + System.out.println("'z' set to 'x': " + z.get()); + } else { + z.set(y); + System.out.println("'z' set to 'y': " + z.get()); + } + + // main completed, shut down the data flow variables + x.shutdown(); + y.shutdown(); + z.shutdown(); + v.shutdown(); } + }); - // main completed, shut down the data flow variables - x.shutdown(); - y.shutdown(); - z.shutdown(); - v.shutdown(); - } -}); + ActorRef setY = thread(new Effect() { + public void apply() { + System.out.println("Thread 'setY', sleeping..."); + Thread.sleep(5000); + y.set(2); + System.out.println("'y' set to: " + y.get()); + } + }); -ActorRef setY = thread(new Effect() { - public void apply() { - System.out.println("Thread 'setY', sleeping..."); - Thread.sleep(5000); - y.set(2); - System.out.println("'y' set to: " + y.get()); - } -}); + ActorRef setV = thread(new Effect() { + public void apply() { + System.out.println("Thread 'setV'"); + y.set(2); + System.out.println("'v' set to y: " + v.get()); + } + }); -ActorRef setV = thread(new Effect() { - public void apply() { - System.out.println("Thread 'setV'"); - y.set(2); - System.out.println("'v' set to y: " + v.get()); - } -}); - -// shut down the threads -main.sendOneWay(new Exit()); -setY.sendOneWay(new Exit()); -setV.sendOneWay(new Exit()); -``_ + // shut down the threads + main.sendOneWay(new Exit()); + setY.sendOneWay(new Exit()); + setV.sendOneWay(new Exit()); diff --git a/akka-docs/pending/dataflow-scala.rst b/akka-docs/pending/dataflow-scala.rst index 135a1f535f..c935537cae 100644 --- a/akka-docs/pending/dataflow-scala.rst +++ b/akka-docs/pending/dataflow-scala.rst @@ -2,10 +2,9 @@ Dataflow Concurrency (Scala) ============================ Description -=========== +----------- -IMPORTANT: As of Akka 1.1, Akka Future, CompletableFuture and DefaultCompletableFuture have all the functionality of DataFlowVariables, they also support non-blocking composition and advanced features like fold and reduce, Akka DataFlowVariable is therefor deprecated and will probably resurface in the following release as a DSL on top of Futures. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +**IMPORTANT: As of Akka 1.1, Akka Future, CompletableFuture and DefaultCompletableFuture have all the functionality of DataFlowVariables, they also support non-blocking composition and advanced features like fold and reduce, Akka DataFlowVariable is therefor deprecated and will probably resurface in the following release as a DSL on top of Futures.** Akka implements `Oz-style dataflow concurrency `_ through dataflow (single assignment) variables and lightweight (event-based) processes/threads. @@ -14,6 +13,7 @@ Dataflow concurrency is deterministic. This means that it will always behave the The best way to learn how to program with dataflow variables is to read the fantastic book `Concepts, Techniques, and Models of Computer Programming `_. By Peter Van Roy and Seif Haridi. The documentation is not as complete as it should be, something we will improve shortly. For now, besides above listed resources on dataflow concurrency, I recommend you to read the documentation for the GPars implementation, which is heavily influenced by the Akka implementation: + * ``_ * ``_ @@ -68,7 +68,7 @@ You can also set the thread to a reference to be able to control its life-cycle: t ! 'exit // shut down the thread Examples -======== +-------- Most of these examples are taken from the `Oz wikipedia page `_ @@ -96,7 +96,7 @@ Note: Do not try to run the Oz version, it is only there for reference. 3. Have fun. Simple DataFlowVariable example -------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This example is from Oz wikipedia page: http://en.wikipedia.org/wiki/Oz_(programming_language). Sort of the "Hello World" of dataflow concurrency. @@ -128,7 +128,7 @@ Example in Akka: thread { y << 2 } Example of using DataFlowVariable with recursion ------------------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Using DataFlowVariable and recursion to calculate sum. @@ -178,56 +178,55 @@ Example in Akka: thread { println("List of sums: " + y()) } Example on life-cycle management of DataFlowVariables ------------------------------------------------------ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Shows how to shutdown dataflow variables and bind threads to values to be able to interact with them (exit etc.). Example in Akka: -``_ -import akka.dataflow.DataFlow._ +.. code-block:: scala + import akka.dataflow.DataFlow._ -// create four 'Int' data flow variables -val x, y, z, v = new DataFlowVariable[Int] + // create four 'Int' data flow variables + val x, y, z, v = new DataFlowVariable[Int] -val main = thread { - println("Thread 'main'") + val main = thread { + println("Thread 'main'") - x << 1 - println("'x' set to: " + x()) + x << 1 + println("'x' set to: " + x()) - println("Waiting for 'y' to be set...") + println("Waiting for 'y' to be set...") - if (x() > y()) { - z << x - println("'z' set to 'x': " + z()) - } else { - z << y - println("'z' set to 'y': " + z()) + if (x() > y()) { + z << x + println("'z' set to 'x': " + z()) + } else { + z << y + println("'z' set to 'y': " + z()) + } + + // main completed, shut down the data flow variables + x.shutdown + y.shutdown + z.shutdown + v.shutdown } - // main completed, shut down the data flow variables - x.shutdown - y.shutdown - z.shutdown - v.shutdown -} + val setY = thread { + println("Thread 'setY', sleeping...") + Thread.sleep(5000) + y << 2 + println("'y' set to: " + y()) + } -val setY = thread { - println("Thread 'setY', sleeping...") - Thread.sleep(5000) - y << 2 - println("'y' set to: " + y()) -} + val setV = thread { + println("Thread 'setV'") + v << y + println("'v' set to 'y': " + v()) + } -val setV = thread { - println("Thread 'setV'") - v << y - println("'v' set to 'y': " + v()) -} - -// shut down the threads -main ! 'exit -setY ! 'exit -setV ! 'exit -``_ + // shut down the threads + main ! 'exit + setY ! 'exit + setV ! 'exit diff --git a/akka-docs/pending/developer-guidelines.rst b/akka-docs/pending/developer-guidelines.rst index f1fcbc8c96..bf2e9dad26 100644 --- a/akka-docs/pending/developer-guidelines.rst +++ b/akka-docs/pending/developer-guidelines.rst @@ -2,7 +2,7 @@ Developer Guidelines ==================== Code Style -========== +---------- The Akka code style follows `this document `_ . @@ -12,20 +12,22 @@ Here is a code style settings file for IntelliJ IDEA. Please follow the code style. Look at the code around you and mimic. Testing -======= +------- All code that is checked in should have tests. All testing is done with ScalaTest and ScalaCheck. + * Name tests as *Test.scala if they do not depend on any external stuff. That keeps surefire happy. * Name tests as *Spec.scala if they have external dependencies. + There is a testing standard that should be followed: `Ticket001Spec <@https://github.com/jboner/akka/blob/master/akka-actor/src/test/scala/akka/ticket/Ticket001Spec.scala>`_ Actor TestKit -------------- +^^^^^^^^^^^^^ There is a useful test kit for testing actors: `akka.util.TestKit <@https://github.com/jboner/akka/tree/master/akka-actor/src/main/scala/akka/util/TestKit.scala>`_. It enables assertions concerning replies received and their timing, there is more documentation in the ``_ module. NetworkFailureTest ------------------- +^^^^^^^^^^^^^^^^^^ You can use the 'NetworkFailureTest' trait to test network failure. See the 'RemoteErrorHandlingNetworkTest' test. Your tests needs to end with 'NetworkTest'. They are disabled by default. To run them you need to enable a flag. diff --git a/akka-docs/pending/fault-tolerance-java.rst b/akka-docs/pending/fault-tolerance-java.rst index 11e03c5aef..5c6510bcbd 100644 --- a/akka-docs/pending/fault-tolerance-java.rst +++ b/akka-docs/pending/fault-tolerance-java.rst @@ -6,7 +6,7 @@ Module stability: **SOLID** The "let it crash" approach to fault/error handling, implemented by linking actors, is very different to what Java and most non-concurrency oriented languages/frameworks have adopted. It’s a way of dealing with failure that is designed for concurrent and distributed systems. Concurrency -^^^^^^^^^^^ +----------- Throwing an exception in concurrent code (let’s assume we are using non-linked actors), will just simply blow up the thread that currently executes the actor. @@ -24,14 +24,14 @@ This is very useful when you have thousands of concurrent actors. Some actors mi It encourages non-defensive programming. Don’t try to prevent things from go wrong, because they will, whether you want it or not. Instead; expect failure as a natural state in the life-cycle of your app, crash early and let someone else (that sees the whole picture), deal with it. Distributed actors -^^^^^^^^^^^^^^^^^^ +------------------ You can’t build a fault-tolerant system with just one single box - you need at least two. Also, you (usually) need to know if one box is down and/or the service you are talking to on the other box is down. Here actor supervision/linking is a critical tool for not only monitoring the health of remote services, but to actually manage the service, do something about the problem if the actor or node is down. Such as restarting actors on the same node or on another node. In short, it is a very different way of thinking, but a way that is very useful (if not critical) to building fault-tolerant highly concurrent and distributed applications, which is as valid if you are writing applications for the JVM or the Erlang VM (the origin of the idea of "let-it-crash" and actor supervision). Supervision -=========== +----------- Supervisor hierarchies originate from `Erlang’s OTP framework `_. @@ -45,20 +45,17 @@ OneForOne The OneForOne fault handler will restart only the component that has crashed. ``_ -^ - AllForOne ^^^^^^^^^ The AllForOne fault handler will restart all the components that the supervisor is managing, including the one that have crashed. This strategy should be used when you have a certain set of components that are coupled in some way that if one is crashing they all need to be reset to a stable state before continuing. ``_ -^ - Restart callbacks ^^^^^^^^^^^^^^^^^ There are two different callbacks that an UntypedActor or TypedActor can hook in to: + * Pre restart * Post restart @@ -68,8 +65,10 @@ Defining a supervisor's restart strategy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Both the Typed Actor supervisor configuration and the Actor supervisor configuration take a ‘FaultHandlingStrategy’ instance which defines the fault management. The different strategies are: + * AllForOne * OneForOne + These have the semantics outlined in the section above. Here is an example of how to define a restart strategy: @@ -86,6 +85,7 @@ Defining actor life-cycle ^^^^^^^^^^^^^^^^^^^^^^^^^ The other common configuration element is the ‘LifeCycle’ which defines the life-cycle. The supervised actor can define one of two different life-cycle configurations: + * Permanent: which means that the actor will always be restarted. * Temporary: which means that the actor will **not** be restarted, but it will be shut down through the regular shutdown process so the 'postStop' callback function will called. @@ -223,10 +223,11 @@ If a linked Actor is failing and throws an exception then an ‘new Exit(deadAct The supervising Actor also needs to define a fault handler that defines the restart strategy the Actor should accommodate when it traps an ‘Exit’ message. This is done by setting the ‘setFaultHandler’ method. The different options are: + * AllForOneStrategy(trapExit, maxNrOfRetries, withinTimeRange) -** trapExit is an Array of classes inheriting from Throwable, they signal which types of exceptions this actor will handle + * trapExit is an Array of classes inheriting from Throwable, they signal which types of exceptions this actor will handle * OneForOneStrategy(trapExit, maxNrOfRetries, withinTimeRange) -** trapExit is an Array of classes inheriting from Throwable, they signal which types of exceptions this actor will handle + * trapExit is an Array of classes inheriting from Throwable, they signal which types of exceptions this actor will handle Here is an example: @@ -334,6 +335,7 @@ If you remember, when you define the 'RestartStrategy' you also defined maximum Now, what happens if this limit is reached? What will happen is that the failing actor will send a system message to its supervisor called 'MaximumNumberOfRestartsWithinTimeRangeReached' with the following these properties: + * victim: ActorRef * maxNrOfRetries: int * withinTimeRange: int @@ -369,8 +371,6 @@ You will also get this log warning similar to this: If you don't define a message handler for this message then you don't get an error but the message is simply not sent to the supervisor. Instead you will get a log warning. -- - Supervising Typed Actors ------------------------ @@ -409,8 +409,6 @@ Then you can retrieve the Typed Actor as follows: Foo foo = (Foo) manager.getInstance(Foo.class); -^ - Restart callbacks ^^^^^^^^^^^^^^^^^ @@ -450,17 +448,16 @@ If the parent TypedActor (supervisor) wants to be able to do handle failing chil For convenience there is an overloaded link that takes trapExit and faultHandler for the supervisor as arguments. Here is an example: -``_ -import static akka.actor.TypedActor.*; -import static akka.config.Supervision.*; +.. code-block:: java + import static akka.actor.TypedActor.*; + import static akka.config.Supervision.*; -foo = newInstance(Foo.class, FooImpl.class, 1000); -bar = newInstance(Bar.class, BarImpl.class, 1000); + foo = newInstance(Foo.class, FooImpl.class, 1000); + bar = newInstance(Bar.class, BarImpl.class, 1000); -link(foo, bar, new AllForOneStrategy(new Class[]{IOException.class}, 3, 2000)); + link(foo, bar, new AllForOneStrategy(new Class[]{IOException.class}, 3, 2000)); -// alternative: chaining -bar = faultHandler(foo, new AllForOneStrategy(new Class[]{IOException.class}, 3, 2000)).newInstance(Bar.class, 1000); + // alternative: chaining + bar = faultHandler(foo, new AllForOneStrategy(new Class[]{IOException.class}, 3, 2000)).newInstance(Bar.class, 1000); -link(foo, bar); -``_ + link(foo, bar); diff --git a/akka-docs/pending/fault-tolerance-scala.rst b/akka-docs/pending/fault-tolerance-scala.rst index 790bf984d5..4e1cae6aa2 100644 --- a/akka-docs/pending/fault-tolerance-scala.rst +++ b/akka-docs/pending/fault-tolerance-scala.rst @@ -6,7 +6,7 @@ Module stability: **SOLID** The "let it crash" approach to fault/error handling, implemented by linking actors, is very different to what Java and most non-concurrency oriented languages/frameworks have adopted. It's a way of dealing with failure that is designed for concurrent and distributed systems. Concurrency -^^^^^^^^^^^ +----------- Throwing an exception in concurrent code (let's assume we are using non-linked actors), will just simply blow up the thread that currently executes the actor. @@ -16,6 +16,7 @@ Throwing an exception in concurrent code (let's assume we are using non-linked a Here actors provide a clean way of getting notification of the error and do something about it. Linking actors also allow you to create sets of actors where you can be sure that either: + # All are dead # None are dead @@ -24,14 +25,14 @@ This is very useful when you have thousands of concurrent actors. Some actors mi It encourages non-defensive programming. Don't try to prevent things from go wrong, because they will, whether you want it or not. Instead; expect failure as a natural state in the life-cycle of your app, crash early and let someone else (that sees the whole picture), deal with it. Distributed actors -^^^^^^^^^^^^^^^^^^ +------------------ You can't build a fault-tolerant system with just one single box - you need at least two. Also, you (usually) need to know if one box is down and/or the service you are talking to on the other box is down. Here actor supervision/linking is a critical tool for not only monitoring the health of remote services, but to actually manage the service, do something about the problem if the actor or node is down. Such as restarting actors on the same node or on another node. In short, it is a very different way of thinking, but a way that is very useful (if not critical) to building fault-tolerant highly concurrent and distributed applications, which is as valid if you are writing applications for the JVM or the Erlang VM (the origin of the idea of "let-it-crash" and actor supervision). Supervision -=========== +----------- Supervisor hierarchies originate from `Erlang's OTP framework `_. @@ -45,20 +46,17 @@ OneForOne The OneForOne fault handler will restart only the component that has crashed. ``_ -^ - AllForOne ^^^^^^^^^ The AllForOne fault handler will restart all the components that the supervisor is managing, including the one that have crashed. This strategy should be used when you have a certain set of components that are coupled in some way that if one is crashing they all need to be reset to a stable state before continuing. ``_ -^ - Restart callbacks ^^^^^^^^^^^^^^^^^ There are two different callbacks that the Typed Actor and Actor can hook in to: + * Pre restart * Post restart @@ -68,8 +66,10 @@ Defining a supervisor's restart strategy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Both the Typed Actor supervisor configuration and the Actor supervisor configuration take a 'FaultHandlingStrategy' instance which defines the fault management. The different strategies are: + * AllForOne * OneForOne + These have the semantics outlined in the section above. Here is an example of how to define a restart strategy: @@ -86,6 +86,7 @@ Defining actor life-cycle ^^^^^^^^^^^^^^^^^^^^^^^^^ The other common configuration element is the "LifeCycle' which defines the life-cycle. The supervised actor can define one of two different life-cycle configurations: + * Permanent: which means that the actor will always be restarted. * Temporary: which means that the actor will **not** be restarted, but it will be shut down through the regular shutdown process so the 'postStop' callback function will called. @@ -216,10 +217,11 @@ The supervising Actor also needs to define a fault handler that defines the rest protected var faultHandler: FaultHandlingStrategy The different options are: + * AllForOneStrategy(trapExit, maxNrOfRetries, withinTimeRange) -** trapExit is a List or Array of classes inheriting from Throwable, they signal which types of exceptions this actor will handle + * trapExit is a List or Array of classes inheriting from Throwable, they signal which types of exceptions this actor will handle * OneForOneStrategy(trapExit, maxNrOfRetries, withinTimeRange) -** trapExit is a List or Array of classes inheriting from Throwable, they signal which types of exceptions this actor will handle + * trapExit is a List or Array of classes inheriting from Throwable, they signal which types of exceptions this actor will handle Here is an example: @@ -346,8 +348,6 @@ You will also get this log warning similar to this: If you don't define a message handler for this message then you don't get an error but the message is simply not sent to the supervisor. Instead you will get a log warning. -- - Supervising Typed Actors ------------------------ @@ -407,17 +407,16 @@ If the parent TypedActor (supervisor) wants to be able to do handle failing chil For convenience there is an overloaded link that takes trapExit and faultHandler for the supervisor as arguments. Here is an example: -``_ -import akka.actor.TypedActor._ +.. code-block:: scala + import akka.actor.TypedActor._ -val foo = newInstance(classOf[Foo], 1000) -val bar = newInstance(classOf[Bar], 1000) + val foo = newInstance(classOf[Foo], 1000) + val bar = newInstance(classOf[Bar], 1000) -link(foo, bar, new AllForOneStrategy(Array(classOf[IOException]), 3, 2000)) + link(foo, bar, new AllForOneStrategy(Array(classOf[IOException]), 3, 2000)) -// alternative: chaining -bar = faultHandler(foo, new AllForOneStrategy(Array(classOf[IOException]), 3, 2000)) - .newInstance(Bar.class, 1000) + // alternative: chaining + bar = faultHandler(foo, new AllForOneStrategy(Array(classOf[IOException]), 3, 2000)) + .newInstance(Bar.class, 1000) -link(foo, bar -``_ + link(foo, bar diff --git a/akka-docs/pending/futures-scala.rst b/akka-docs/pending/futures-scala.rst index 040ff59884..5cbfc08cea 100644 --- a/akka-docs/pending/futures-scala.rst +++ b/akka-docs/pending/futures-scala.rst @@ -4,14 +4,14 @@ Futures (Scala) Introduction ------------ -In Akka, a `Future `_ is a data structure used to retrieve the result of some concurrent operation. This operation is usually performed by an `Actor `_ or by the Dispatcher `directly `_. This result can be accessed synchronously (blocking) or asynchronously (non-blocking). +In Akka, a `Future `_ is a data structure used to retrieve the result of some concurrent operation. This operation is usually performed by an ``Actor`` or by the ``Dispatcher`` directly. This result can be accessed synchronously (blocking) or asynchronously (non-blocking). Use with Actors --------------- -There are generally two ways of getting a reply from an Actor: the first is by a sent message (`actor ! msg `_), which only works if the original sender was an Actor) and the second is through a Future. +There are generally two ways of getting a reply from an ``Actor``: the first is by a sent message (``actor ! msg``), which only works if the original sender was an ``Actor``) and the second is through a ``Future``. -Using an Actor's '!!!' method to send a message will return a Future. To wait for and retreive the actual result the simplest method is: +Using an ``Actor``\'s ``!!!`` method to send a message will return a Future. To wait for and retreive the actual result the simplest method is: .. code-block:: scala @@ -20,12 +20,12 @@ Using an Actor's '!!!' method to send a message will return a Future. To wait fo // or more simply val result: Any = future() -This will cause the current thread to block and wait for the Actor to 'complete' the Future with it's reply. Due to the dynamic nature of Akka's Actors this result will be untyped and will default to 'Nothing'. The safest way to deal with this is to cast the result to an Any as is shown in the above example. You can also use the expected result type instead of Any, but if an unexpected type were to be returned you will get a ClassCastException. For more elegant ways to deal with this and to use the result without blocking refer to `Functional Futures `_. +This will cause the current thread to block and wait for the ``Actor`` to 'complete' the ``Future`` with it's reply. Due to the dynamic nature of Akka's ``Actor``\s this result will be untyped and will default to ``Nothing``. The safest way to deal with this is to cast the result to an ``Any`` as is shown in the above example. You can also use the expected result type instead of ``Any``, but if an unexpected type were to be returned you will get a ``ClassCastException``. For more elegant ways to deal with this and to use the result without blocking refer to `Functional Futures`_. Use Directly ------------ -A common use case within Akka is to have some computation performed concurrently without needing the extra utility of an Actor. If you find yourself creating a pool of Actors for the sole reason of performing a calculation in parallel, there is an easier (and faster) way: +A common use case within Akka is to have some computation performed concurrently without needing the extra utility of an ``Actor``. If you find yourself creating a pool of ``Actor``\s for the sole reason of performing a calculation in parallel, there is an easier (and faster) way: .. code-block:: scala @@ -36,17 +36,17 @@ A common use case within Akka is to have some computation performed concurrently } val result = future() -In the above code the block passed to Future will be executed by the default `Dispatcher `_, with the return value of the block used to complete the Future (in this case, the result would be the string: "HelloWorld"). Unlike a Future that is returned from an Actor, this Future is properly typed, and we also avoid the overhead of managing an Actor. +In the above code the block passed to ``Future`` will be executed by the default ``Dispatcher``, with the return value of the block used to complete the ``Future`` (in this case, the result would be the string: "HelloWorld"). Unlike a ``Future`` that is returned from an ``Actor``, this ``Future`` is properly typed, and we also avoid the overhead of managing an ``Actor``. Functional Futures ------------------ -A recent addition to Akka's Future is several monadic methods that are very similar to the ones used by Scala's collections. These allow you to create 'pipelines' or 'streams' that the result will travel through. +A recent addition to Akka's ``Future`` is several monadic methods that are very similar to the ones used by Scala's collections. These allow you to create 'pipelines' or 'streams' that the result will travel through. Future is a Monad ^^^^^^^^^^^^^^^^^ -The first method for working with Future functionally is 'map'. This method takes a Function which performs some operation on the result of the Future, and returning a new result. The return value of the 'map' method is another Future that will contain the new result: +The first method for working with ``Future`` functionally is ``map``. This method takes a ``Function`` which performs some operation on the result of the ``Future``, and returning a new result. The return value of the ``map`` method is another ``Future`` that will contain the new result: .. code-block:: scala From 6537c7562590e3fb9a72426ccc58268c55e3f8db Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 11 Apr 2011 11:56:19 +0200 Subject: [PATCH 033/147] improved documentation of actor registry --- akka-docs/pending/actor-registry-java.rst | 43 ++++++++++++++++------ akka-docs/pending/actor-registry-scala.rst | 30 ++++++++++++--- akka-docs/pending/actors-scala.rst | 4 +- akka-docs/pending/untyped-actors-java.rst | 6 ++- 4 files changed, 63 insertions(+), 20 deletions(-) diff --git a/akka-docs/pending/actor-registry-java.rst b/akka-docs/pending/actor-registry-java.rst index 713ad8bba6..85a36b5174 100644 --- a/akka-docs/pending/actor-registry-java.rst +++ b/akka-docs/pending/actor-registry-java.rst @@ -7,8 +7,8 @@ ActorRegistry: Finding Actors ----------------------------- Actors can be looked up using the 'akka.actor.Actors.registry()' object. Through this registry you can look up actors by: -* uuid string – this uses the ‘uuid’ field in the Actor class, returns all actor instances with that uuid -* id string – this uses the ‘id’ field in the Actor class, which can be set by the user (default is the class name), returns instances of a specific Actor +* uuid com.eaio.uuid.UUID – this uses the ‘uuid’ field in the Actor class, returns the actor reference for the actor with specified uuid, if one exists, otherwise None +* id string – this uses the ‘id’ field in the Actor class, which can be set by the user (default is the class name), returns all actor references to actors with specified id * parameterized type - returns a 'ActorRef[]' with all actors that are a subtype of this specific type * specific actor class - returns a 'ActorRef[]' with all actors of this exact class @@ -19,10 +19,10 @@ Here is a summary of the API for finding actors: .. code-block:: java import static akka.actor.Actors.*; - Option actor = registry().actorFor(String uuid); - Actor[] actors = registry().actors(); - Actor[] otherActors = registry().actorsFor(String id); - Actor[] moreActors = registry().actorsFor(Class clazz); + Option actor = registry().actorFor(uuid); + ActorRef[] actors = registry().actors(); + ActorRef[] otherActors = registry().actorsFor(id); + ActorRef[] moreActors = registry().actorsFor(clazz); You can shut down all Actors in the system by invoking: @@ -34,18 +34,39 @@ If you want to know when a new Actor is added or to or removed from the registry .. code-block:: java - void addListener(ActorRef listener) - void removeListener(ActorRef listener) + void addListener(ActorRef listener); + void removeListener(ActorRef listener); The messages sent to this Actor are: .. code-block:: java - class ActorRegistered { + public class ActorRegistered { ActorRef actor(); } - class ActorUnregistered { + public class ActorUnregistered { ActorRef actor(); } -So your listener Actor needs to be able to handle these two messages. +So your listener Actor needs to be able to handle these two messages. Example: + +.. code-block:: java +public class RegistryListener extends UntypedActor { + public void onReceive(Object message) throws Exception { + if (message instanceof ActorRegistered) { + ActorRegistered event = (ActorRegistered) message; + EventHandler.info(this, String.format("Actor registered: %s - %s", + event.actor().actorClassName(), event.actor().getUuid())); + } else if (message instanceof ActorUnregistered) { + // ... + } + } +} +.. code-block:: java +The above actor can be added as listener of registry events: +.. code-block:: java +import static akka.actor.Actors.*; + + ActorRef listener = actorOf(RegistryListener.class).start(); + registry().addListener(listener); +.. code-block:: java diff --git a/akka-docs/pending/actor-registry-scala.rst b/akka-docs/pending/actor-registry-scala.rst index 9741383147..3931ec0115 100644 --- a/akka-docs/pending/actor-registry-scala.rst +++ b/akka-docs/pending/actor-registry-scala.rst @@ -7,8 +7,8 @@ ActorRegistry: Finding Actors ----------------------------- Actors can be looked up by using the **akka.actor.Actor.registry: akka.actor.ActorRegistry**. Lookups for actors through this registry can be done by: -* uuid string – this uses the ‘**uuid**’ field in the Actor class, returns all actor instances with the specified uuid -* id string – this uses the ‘**id**’ field in the Actor class, which can be set by the user (default is the class name), returns instances of a specific Actor +* uuid akka.actor.Uuid – this uses the ‘**uuid**’ field in the Actor class, returns the actor reference for the actor with specified uuid, if one exists, otherwise None +* id string – this uses the ‘**id**’ field in the Actor class, which can be set by the user (default is the class name), returns all actor references to actors with specified id * specific actor class - returns an '**Array[Actor]**' with all actors of this exact class * parameterized type - returns an '**Array[Actor]**' with all actors that are a subtype of this specific type @@ -19,14 +19,14 @@ Here is a summary of the API for finding actors: .. code-block:: scala def actors: Array[ActorRef] - def actorFor(uuid: String): Option[ActorRef] + def actorFor(uuid: akka.actor.Uuid): Option[ActorRef] def actorsFor(id : String): Array[ActorRef] def actorsFor[T <: Actor](implicit manifest: Manifest[T]): Array[ActorRef] def actorsFor[T <: Actor](clazz: Class[T]): Array[ActorRef] // finding typed actors def typedActors: Array[AnyRef] - def typedActorFor(uuid: Uuid): Option[AnyRef] + def typedActorFor(uuid: akka.actor.Uuid): Option[AnyRef] def typedActorsFor(id: String): Array[AnyRef] def typedActorsFor[T <: AnyRef](implicit manifest: Manifest[T]): Array[AnyRef] def typedActorsFor[T <: AnyRef](clazz: Class[T]): Array[AnyRef] @@ -75,4 +75,24 @@ The messages sent to this Actor are: case class ActorRegistered(actor: ActorRef) case class ActorUnregistered(actor: ActorRef) -So your listener Actor needs to be able to handle these two messages. +So your listener Actor needs to be able to handle these two messages. Example: + +.. code-block:: java +class RegistryListener extends Actor { + def receive = { + case event: ActorRegistered => + EventHandler.info(this, "Actor registered: %s - %s".format( + event.actor.actorClassName, event.actor.uuid)) + case event: ActorUnregistered => + // ... + } +} +.. code-block:: java +The above actor can be added as listener of registry events: +.. code-block:: java +import akka.actor._ +import akka.actor.Actor._ + + val listener = actorOf[RegistryListener].start + registry.addListener(listener) +.. code-block:: java diff --git a/akka-docs/pending/actors-scala.rst b/akka-docs/pending/actors-scala.rst index 360e7607c9..58220055a4 100644 --- a/akka-docs/pending/actors-scala.rst +++ b/akka-docs/pending/actors-scala.rst @@ -29,8 +29,8 @@ Here is an example: class MyActor extends Actor { def receive = { - case "test" => log.info("received test") - case _ => log.info("received unknown message") + case "test" => EventHandler.info(this, "received test") + case _ => EventHandler.info(this, "received unknown message") } } diff --git a/akka-docs/pending/untyped-actors-java.rst b/akka-docs/pending/untyped-actors-java.rst index a5503391e4..6c2a665929 100644 --- a/akka-docs/pending/untyped-actors-java.rst +++ b/akka-docs/pending/untyped-actors-java.rst @@ -19,8 +19,10 @@ Here is an example: public class SampleUntypedActor extends UntypedActor { public void onReceive(Object message) throws Exception { - if (message instanceof String) log.info("Received String message: %s", message); - else throw new IllegalArgumentException("Unknown message: " + message); + if (message instanceof String) + EventHandler.info(this, String.format("Received String message: %s", message)); + else + throw new IllegalArgumentException("Unknown message: " + message); } } From 1e28baaada27d44b63bc51b9907657d4213e7d2d Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 11 Apr 2011 12:06:09 +0200 Subject: [PATCH 034/147] included imports also --- akka-docs/pending/actor-registry-java.rst | 5 +++++ akka-docs/pending/actor-registry-scala.rst | 14 ++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/akka-docs/pending/actor-registry-java.rst b/akka-docs/pending/actor-registry-java.rst index 85a36b5174..005d9eb4bb 100644 --- a/akka-docs/pending/actor-registry-java.rst +++ b/akka-docs/pending/actor-registry-java.rst @@ -51,6 +51,11 @@ The messages sent to this Actor are: So your listener Actor needs to be able to handle these two messages. Example: .. code-block:: java +import akka.actor.ActorRegistered; +import akka.actor.ActorUnregistered; +import akka.actor.UntypedActor; +import akka.event.EventHandler; + public class RegistryListener extends UntypedActor { public void onReceive(Object message) throws Exception { if (message instanceof ActorRegistered) { diff --git a/akka-docs/pending/actor-registry-scala.rst b/akka-docs/pending/actor-registry-scala.rst index 3931ec0115..83a77689d4 100644 --- a/akka-docs/pending/actor-registry-scala.rst +++ b/akka-docs/pending/actor-registry-scala.rst @@ -77,7 +77,13 @@ The messages sent to this Actor are: So your listener Actor needs to be able to handle these two messages. Example: -.. code-block:: java +.. code-block:: scala +import akka.actor.Actor +import akka.actor.ActorRegistered; +import akka.actor.ActorUnregistered; +import akka.actor.UntypedActor; +import akka.event.EventHandler; + class RegistryListener extends Actor { def receive = { case event: ActorRegistered => @@ -87,12 +93,12 @@ class RegistryListener extends Actor { // ... } } -.. code-block:: java +.. code-block:: scala The above actor can be added as listener of registry events: -.. code-block:: java +.. code-block:: scala import akka.actor._ import akka.actor.Actor._ val listener = actorOf[RegistryListener].start registry.addListener(listener) -.. code-block:: java +.. code-block:: scala From f753e2aef1975f7ed932024ab842b77326836adb Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 11 Apr 2011 12:15:08 +0200 Subject: [PATCH 035/147] typo --- akka-docs/pending/slf4j.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-docs/pending/slf4j.rst b/akka-docs/pending/slf4j.rst index 6b7ea8affb..99e9256cde 100644 --- a/akka-docs/pending/slf4j.rst +++ b/akka-docs/pending/slf4j.rst @@ -11,7 +11,7 @@ You can use the 'akka.event.slf4j.Logging' trait to mix in logging behavior into Event Handler ------------- -This module also includes an SLF4J Event Handler that works with Akka's standar Event Handler. You enabled it in the 'event-handlers' element in akka.conf. Here you can also define the log level. +This module also includes an SLF4J Event Handler that works with Akka's standard Event Handler. You enabled it in the 'event-handlers' element in akka.conf. Here you can also define the log level. .. code-block:: ruby From 909e90d019d4f93048b0c9902aca69e875638f9e Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 11 Apr 2011 12:51:47 +0200 Subject: [PATCH 036/147] removed Logging trait --- akka-docs/pending/slf4j.rst | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/akka-docs/pending/slf4j.rst b/akka-docs/pending/slf4j.rst index 99e9256cde..780030a543 100644 --- a/akka-docs/pending/slf4j.rst +++ b/akka-docs/pending/slf4j.rst @@ -3,15 +3,10 @@ SLF4J This module is available in the 'akka-slf4j.jar'. It has one single dependency; the slf4j-api jar. -Logging trait -------------- - -You can use the 'akka.event.slf4j.Logging' trait to mix in logging behavior into your classes and use the 'log' Logger member variable. But the preferred way is to use the event handler (see below). - Event Handler ------------- -This module also includes an SLF4J Event Handler that works with Akka's standard Event Handler. You enabled it in the 'event-handlers' element in akka.conf. Here you can also define the log level. +This module includes a SLF4J Event Handler that works with Akka's standard Event Handler. You enabled it in the 'event-handlers' element in akka.conf. Here you can also define the log level. .. code-block:: ruby From 2142ca13098b60808a9e5cd0555a2a00fb1f925b Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 11 Apr 2011 12:52:12 +0200 Subject: [PATCH 037/147] fixed wrong code block syntax --- akka-docs/pending/actor-registry-java.rst | 30 ++++++++++---------- akka-docs/pending/actor-registry-scala.rst | 32 +++++++++++----------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/akka-docs/pending/actor-registry-java.rst b/akka-docs/pending/actor-registry-java.rst index 005d9eb4bb..925b51d012 100644 --- a/akka-docs/pending/actor-registry-java.rst +++ b/akka-docs/pending/actor-registry-java.rst @@ -51,27 +51,27 @@ The messages sent to this Actor are: So your listener Actor needs to be able to handle these two messages. Example: .. code-block:: java -import akka.actor.ActorRegistered; -import akka.actor.ActorUnregistered; -import akka.actor.UntypedActor; -import akka.event.EventHandler; + import akka.actor.ActorRegistered; + import akka.actor.ActorUnregistered; + import akka.actor.UntypedActor; + import akka.event.EventHandler; -public class RegistryListener extends UntypedActor { - public void onReceive(Object message) throws Exception { - if (message instanceof ActorRegistered) { - ActorRegistered event = (ActorRegistered) message; - EventHandler.info(this, String.format("Actor registered: %s - %s", + public class RegistryListener extends UntypedActor { + public void onReceive(Object message) throws Exception { + if (message instanceof ActorRegistered) { + ActorRegistered event = (ActorRegistered) message; + EventHandler.info(this, String.format("Actor registered: %s - %s", event.actor().actorClassName(), event.actor().getUuid())); - } else if (message instanceof ActorUnregistered) { - // ... + } else if (message instanceof ActorUnregistered) { + // ... + } } } -} -.. code-block:: java + The above actor can be added as listener of registry events: .. code-block:: java -import static akka.actor.Actors.*; + import static akka.actor.Actors.*; ActorRef listener = actorOf(RegistryListener.class).start(); registry().addListener(listener); -.. code-block:: java + diff --git a/akka-docs/pending/actor-registry-scala.rst b/akka-docs/pending/actor-registry-scala.rst index 83a77689d4..70defb918c 100644 --- a/akka-docs/pending/actor-registry-scala.rst +++ b/akka-docs/pending/actor-registry-scala.rst @@ -78,27 +78,27 @@ The messages sent to this Actor are: So your listener Actor needs to be able to handle these two messages. Example: .. code-block:: scala -import akka.actor.Actor -import akka.actor.ActorRegistered; -import akka.actor.ActorUnregistered; -import akka.actor.UntypedActor; -import akka.event.EventHandler; + import akka.actor.Actor + import akka.actor.ActorRegistered; + import akka.actor.ActorUnregistered; + import akka.actor.UntypedActor; + import akka.event.EventHandler; -class RegistryListener extends Actor { - def receive = { - case event: ActorRegistered => - EventHandler.info(this, "Actor registered: %s - %s".format( + class RegistryListener extends Actor { + def receive = { + case event: ActorRegistered => + EventHandler.info(this, "Actor registered: %s - %s".format( event.actor.actorClassName, event.actor.uuid)) - case event: ActorUnregistered => - // ... + case event: ActorUnregistered => + // ... + } } -} -.. code-block:: scala + The above actor can be added as listener of registry events: .. code-block:: scala -import akka.actor._ -import akka.actor.Actor._ + import akka.actor._ + import akka.actor.Actor._ val listener = actorOf[RegistryListener].start registry.addListener(listener) -.. code-block:: scala + From 7df734320ee3aa69a541e18b237cc1890ac9a53f Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Mon, 11 Apr 2011 06:28:53 -0600 Subject: [PATCH 038/147] cleanup docs --- akka-docs/pending/actor-registry-java.rst | 35 +++++++++--------- akka-docs/pending/actor-registry-scala.rst | 41 ++++++++++++---------- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/akka-docs/pending/actor-registry-java.rst b/akka-docs/pending/actor-registry-java.rst index 005d9eb4bb..88eca2a88d 100644 --- a/akka-docs/pending/actor-registry-java.rst +++ b/akka-docs/pending/actor-registry-java.rst @@ -7,6 +7,7 @@ ActorRegistry: Finding Actors ----------------------------- Actors can be looked up using the 'akka.actor.Actors.registry()' object. Through this registry you can look up actors by: + * uuid com.eaio.uuid.UUID – this uses the ‘uuid’ field in the Actor class, returns the actor reference for the actor with specified uuid, if one exists, otherwise None * id string – this uses the ‘id’ field in the Actor class, which can be set by the user (default is the class name), returns all actor references to actors with specified id * parameterized type - returns a 'ActorRef[]' with all actors that are a subtype of this specific type @@ -51,27 +52,29 @@ The messages sent to this Actor are: So your listener Actor needs to be able to handle these two messages. Example: .. code-block:: java -import akka.actor.ActorRegistered; -import akka.actor.ActorUnregistered; -import akka.actor.UntypedActor; -import akka.event.EventHandler; -public class RegistryListener extends UntypedActor { - public void onReceive(Object message) throws Exception { - if (message instanceof ActorRegistered) { - ActorRegistered event = (ActorRegistered) message; - EventHandler.info(this, String.format("Actor registered: %s - %s", - event.actor().actorClassName(), event.actor().getUuid())); - } else if (message instanceof ActorUnregistered) { - // ... + import akka.actor.ActorRegistered; + import akka.actor.ActorUnregistered; + import akka.actor.UntypedActor; + import akka.event.EventHandler; + + public class RegistryListener extends UntypedActor { + public void onReceive(Object message) throws Exception { + if (message instanceof ActorRegistered) { + ActorRegistered event = (ActorRegistered) message; + EventHandler.info(this, String.format("Actor registered: %s - %s", + event.actor().actorClassName(), event.actor().getUuid())); + } else if (message instanceof ActorUnregistered) { + // ... + } } } -} -.. code-block:: java + The above actor can be added as listener of registry events: + .. code-block:: java -import static akka.actor.Actors.*; + + import static akka.actor.Actors.*; ActorRef listener = actorOf(RegistryListener.class).start(); registry().addListener(listener); -.. code-block:: java diff --git a/akka-docs/pending/actor-registry-scala.rst b/akka-docs/pending/actor-registry-scala.rst index 83a77689d4..d135c6e5b8 100644 --- a/akka-docs/pending/actor-registry-scala.rst +++ b/akka-docs/pending/actor-registry-scala.rst @@ -7,6 +7,7 @@ ActorRegistry: Finding Actors ----------------------------- Actors can be looked up by using the **akka.actor.Actor.registry: akka.actor.ActorRegistry**. Lookups for actors through this registry can be done by: + * uuid akka.actor.Uuid – this uses the ‘**uuid**’ field in the Actor class, returns the actor reference for the actor with specified uuid, if one exists, otherwise None * id string – this uses the ‘**id**’ field in the Actor class, which can be set by the user (default is the class name), returns all actor references to actors with specified id * specific actor class - returns an '**Array[Actor]**' with all actors of this exact class @@ -78,27 +79,29 @@ The messages sent to this Actor are: So your listener Actor needs to be able to handle these two messages. Example: .. code-block:: scala -import akka.actor.Actor -import akka.actor.ActorRegistered; -import akka.actor.ActorUnregistered; -import akka.actor.UntypedActor; -import akka.event.EventHandler; -class RegistryListener extends Actor { - def receive = { - case event: ActorRegistered => - EventHandler.info(this, "Actor registered: %s - %s".format( + import akka.actor.Actor + import akka.actor.ActorRegistered; + import akka.actor.ActorUnregistered; + import akka.actor.UntypedActor; + import akka.event.EventHandler; + + class RegistryListener extends Actor { + def receive = { + case event: ActorRegistered => + EventHandler.info(this, "Actor registered: %s - %s".format( event.actor.actorClassName, event.actor.uuid)) - case event: ActorUnregistered => - // ... + case event: ActorUnregistered => + // ... + } } -} -.. code-block:: scala -The above actor can be added as listener of registry events: -.. code-block:: scala -import akka.actor._ -import akka.actor.Actor._ - val listener = actorOf[RegistryListener].start - registry.addListener(listener) +The above actor can be added as listener of registry events: + .. code-block:: scala + + import akka.actor._ + import akka.actor.Actor._ + + val listener = actorOf[RegistryListener].start + registry.addListener(listener) From 6641b30a8fa7fa53cfcfae9d25f1664239c33969 Mon Sep 17 00:00:00 2001 From: Heiko Seeberger Date: Mon, 11 Apr 2011 14:31:27 +0200 Subject: [PATCH 039/147] Another minor coding style correction in akka-tutorial. --- akka-docs/manual/getting-started-first.rst | 16 ++++++++-------- .../akka-tutorial-first/src/main/scala/Pi.scala | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst index e5f7a4ba1f..2f3f9e518f 100644 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -236,10 +236,10 @@ Creating the master The master actor is a little bit more involved. In its constructor we need to create the workers (the ``Worker`` actors) and start them. We will also wrap them in a load-balancing router to make it easier to spread out the work evenly between the workers. Let's do that first:: // create the workers - val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start) + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) // wrap them with a load-balancing router - val router = Routing.loadBalancerActor(CyclicIterator(workers)).start + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() As you can see we are using the ``actorOf`` factory method to create actors, this method returns as an ``ActorRef`` which is a reference to our newly created actor. This method is available in the ``Actor`` object but is usually imported:: @@ -261,10 +261,10 @@ Let's now write the master actor:: var start: Long = _ // create the workers - val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start) + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) // wrap them with a load-balancing router - val router = Routing.loadBalancerActor(CyclicIterator(workers)).start + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() def receive = { ... } @@ -332,7 +332,7 @@ Now the only thing that is left to implement is the runner that should bootstrap val latch = new CountDownLatch(1) // create the master - val master = actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start + val master = actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() // start the calculation master ! Calculate @@ -398,10 +398,10 @@ But before we package it up and run it, let's take a look at the full code now, var start: Long = _ // create the workers - val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start) + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) // wrap them with a load-balancing router - val router = Routing.loadBalancerActor(CyclicIterator(workers)).start + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() // message handler def receive = { @@ -443,7 +443,7 @@ But before we package it up and run it, let's take a look at the full code now, val latch = new CountDownLatch(1) // create the master - val master = actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start + val master = actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() // start the calculation master ! Calculate diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index 3107bc280a..c31f8ee2f6 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -80,10 +80,10 @@ object Pi extends App { var start: Long = _ // create the workers - val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start) + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) // wrap them with a load-balancing router - val router = Routing.loadBalancerActor(CyclicIterator(workers)).start + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() // message handler def receive = { @@ -125,7 +125,7 @@ object Pi extends App { val latch = new CountDownLatch(1) // create the master - val master = actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start + val master = actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() // start the calculation master ! Calculate From bb8824296ae9c93576d5cf1618df9d97df80871c Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 11 Apr 2011 14:41:17 +0200 Subject: [PATCH 040/147] improved actors doc --- akka-docs/pending/actors-scala.rst | 22 +++++++++++++--------- akka-docs/pending/untyped-actors-java.rst | 6 ++---- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/akka-docs/pending/actors-scala.rst b/akka-docs/pending/actors-scala.rst index 58220055a4..fa24e91db3 100644 --- a/akka-docs/pending/actors-scala.rst +++ b/akka-docs/pending/actors-scala.rst @@ -198,7 +198,7 @@ An Actor has to implement the ‘receive’ method to receive messages: protected def receive: PartialFunction[Any, Unit] -Note: Akka has an alias to the 'PartialFunction[Any, Unit]' type called 'Receive', so you can use this type instead for clarity. But most often you don't need to spell it out. +Note: Akka has an alias to the 'PartialFunction[Any, Unit]' type called 'Receive' (akka.actor.Actor.Receive), so you can use this type instead for clarity. But most often you don't need to spell it out. This method should return a PartialFunction, e.g. a ‘match/case’ clause in which the message can be matched against the different case clauses using Scala pattern matching. Here is an example: @@ -545,24 +545,28 @@ In generic base Actor: .. code-block:: scala + import akka.actor.Actor.Receive + abstract class GenericActor extends Actor { - // to be defined in subclassing actor - def specificMessageHandler: PartialFunction[Any, Unit] - + def specificMessageHandler: Receive + // generic message handler - def genericMessageHandler = { - ... // generic message handler + def genericMessageHandler: Receive = { + case event => printf("generic: %s\n", event) } - + def receive = specificMessageHandler orElse genericMessageHandler } In subclassing Actor: + ``_ class SpecificActor extends GenericActor { def specificMessageHandler = { - ... // specific message handler + case event: MyMsg => printf("specific: %s\n", event.subject) } } -``_ + +case class MyMsg(subject: String) +``_ \ No newline at end of file diff --git a/akka-docs/pending/untyped-actors-java.rst b/akka-docs/pending/untyped-actors-java.rst index 6c2a665929..be6e35f2cd 100644 --- a/akka-docs/pending/untyped-actors-java.rst +++ b/akka-docs/pending/untyped-actors-java.rst @@ -26,8 +26,6 @@ Here is an example: } } -The 'UntypedActor' class inherits from the 'akka.util.Logging' class which defines a logger in the 'log' field that you can use to log. The logging uses SLF4j backed by logback - for more information on how to configure the logger see `Logging `_. - Creating Actors ^^^^^^^^^^^^^^^ @@ -35,7 +33,7 @@ Creating an Actor is done using the 'akka.actor.Actors.actorOf' factory method. .. code-block:: java - ActorRef actor = Actors.actorOf(SampleUntypedActor.class); + ActorRef myActor = Actors.actorOf(SampleUntypedActor.class); myActor.start(); Normally you would want to import the 'actorOf' method like this: @@ -51,7 +49,7 @@ You can also create & start the actor in one statement: .. code-block:: java - ActorRef actor = actorOf(SampleUntypedActor.class).start(); + ActorRef myActor = actorOf(SampleUntypedActor.class).start(); The call to 'actorOf' returns an instance of 'ActorRef'. This is a handle to the 'UntypedActor' instance which you can use to interact with the Actor, like send messages to it etc. more on this shortly. The 'ActorRef' is immutble and has a one to one relationship with the Actor it represents. The 'ActorRef' is also serializable and network-aware. This means that you can serialize it, send it over the wire and use it on a remote host and it will still be representing the same Actor on the original node, across the network. From 4aba589eee3e7e95b650f78a31b9aa574ce8bf2d Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 11 Apr 2011 15:12:32 +0200 Subject: [PATCH 041/147] cleanup --- akka-docs/pending/actor-registry-java.rst | 4 ++-- akka-docs/pending/actor-registry-scala.rst | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/akka-docs/pending/actor-registry-java.rst b/akka-docs/pending/actor-registry-java.rst index 925b51d012..ef815d857a 100644 --- a/akka-docs/pending/actor-registry-java.rst +++ b/akka-docs/pending/actor-registry-java.rst @@ -7,6 +7,7 @@ ActorRegistry: Finding Actors ----------------------------- Actors can be looked up using the 'akka.actor.Actors.registry()' object. Through this registry you can look up actors by: + * uuid com.eaio.uuid.UUID – this uses the ‘uuid’ field in the Actor class, returns the actor reference for the actor with specified uuid, if one exists, otherwise None * id string – this uses the ‘id’ field in the Actor class, which can be set by the user (default is the class name), returns all actor references to actors with specified id * parameterized type - returns a 'ActorRef[]' with all actors that are a subtype of this specific type @@ -73,5 +74,4 @@ The above actor can be added as listener of registry events: import static akka.actor.Actors.*; ActorRef listener = actorOf(RegistryListener.class).start(); - registry().addListener(listener); - + registry().addListener(listener); \ No newline at end of file diff --git a/akka-docs/pending/actor-registry-scala.rst b/akka-docs/pending/actor-registry-scala.rst index 70defb918c..a87dcc3039 100644 --- a/akka-docs/pending/actor-registry-scala.rst +++ b/akka-docs/pending/actor-registry-scala.rst @@ -7,6 +7,7 @@ ActorRegistry: Finding Actors ----------------------------- Actors can be looked up by using the **akka.actor.Actor.registry: akka.actor.ActorRegistry**. Lookups for actors through this registry can be done by: + * uuid akka.actor.Uuid – this uses the ‘**uuid**’ field in the Actor class, returns the actor reference for the actor with specified uuid, if one exists, otherwise None * id string – this uses the ‘**id**’ field in the Actor class, which can be set by the user (default is the class name), returns all actor references to actors with specified id * specific actor class - returns an '**Array[Actor]**' with all actors of this exact class @@ -78,6 +79,7 @@ The messages sent to this Actor are: So your listener Actor needs to be able to handle these two messages. Example: .. code-block:: scala + import akka.actor.Actor import akka.actor.ActorRegistered; import akka.actor.ActorUnregistered; @@ -95,10 +97,12 @@ So your listener Actor needs to be able to handle these two messages. Example: } The above actor can be added as listener of registry events: + .. code-block:: scala + import akka.actor._ import akka.actor.Actor._ - val listener = actorOf[RegistryListener].start - registry.addListener(listener) + val listener = actorOf[RegistryListener].start + registry.addListener(listener) From f878ee4c5d854c6ce1a4fce51c38f6d7588de4a0 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 11 Apr 2011 15:27:45 +0200 Subject: [PATCH 042/147] minor improvements --- akka-docs/pending/actors-scala.rst | 2 +- akka-docs/pending/untyped-actors-java.rst | 27 +++++++++++------------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/akka-docs/pending/actors-scala.rst b/akka-docs/pending/actors-scala.rst index fa24e91db3..f07dbb0ca4 100644 --- a/akka-docs/pending/actors-scala.rst +++ b/akka-docs/pending/actors-scala.rst @@ -263,7 +263,7 @@ If you want to send a message back to the original sender of the message you jus val result = process(request) self.reply(result) -In this case the 'result' will be send back to the Actor that send the 'request'. +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. diff --git a/akka-docs/pending/untyped-actors-java.rst b/akka-docs/pending/untyped-actors-java.rst index be6e35f2cd..760b5fd324 100644 --- a/akka-docs/pending/untyped-actors-java.rst +++ b/akka-docs/pending/untyped-actors-java.rst @@ -41,7 +41,7 @@ Normally you would want to import the 'actorOf' method like this: .. code-block:: java import static akka.actor.Actors.*; - ActorRef actor = actorOf(SampleUntypedActor.class); + ActorRef myActor = actorOf(SampleUntypedActor.class); To avoid prefix it with 'Actors' every time you use it. @@ -154,7 +154,7 @@ Using 'sendRequestReplyFuture' will send a message to the receiving Actor asynch .. code-block:: java - Future future= actorRef.sendRequestReplyFuture("Hello", getContext(), 1000); + Future future = actorRef.sendRequestReplyFuture("Hello", getContext(), 1000); The 'Future' interface looks like this: @@ -175,7 +175,7 @@ So the normal way of working with futures is something like this: .. code-block:: java - Future future= actorRef.sendRequestReplyFuture("Hello", getContext(), 1000); + Future future = actorRef.sendRequestReplyFuture("Hello", getContext(), 1000); future.await(); if (future.isCompleted()) { Option resultOption = future.result(); @@ -188,13 +188,6 @@ So the normal way of working with futures is something like this: The 'onComplete' callback can be used to register a callback to get a notification when the Future completes. Gives you a way to avoid blocking. -We also have a utility class 'Futures' that have a couple of convenience methods: - -.. code-block:: java - - void awaitAll(Future[] futures); - Future awaitOne(Future[] futures) - Forward message ^^^^^^^^^^^^^^^ @@ -207,7 +200,7 @@ You can forward a message from one actor to another. This means that the origina Receive messages ---------------- -When an actor receives a message is passed into the 'onReceive' method, this is an abstract method on the 'UntypedActor' base class that needs to be defined. +When an actor receives a message it is passed into the 'onReceive' method, this is an abstract method on the 'UntypedActor' base class that needs to be defined. Here is an example: @@ -216,8 +209,10 @@ Here is an example: public class SampleUntypedActor extends UntypedActor { public void onReceive(Object message) throws Exception { - if (message instanceof String) log.info("Received String message: %s", message); - else throw new IllegalArgumentException("Unknown message: " + message); + if (message instanceof String) + EventHandler.info(this, String.format("Received String message: %s", message)); + else + throw new IllegalArgumentException("Unknown message: " + message); } } @@ -241,7 +236,7 @@ If you want to send a message back to the original sender of the message you jus } } -In this case we will a reply back to the Actor that send the message. +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. @@ -391,6 +386,8 @@ Use it like this: .. code-block:: java + import static akka.actor.Actors.*; + actor.sendOneWay(poisonPill()); Killing an Actor @@ -402,6 +399,8 @@ Use it like this: .. code-block:: java + import static akka.actor.Actors.*; + // kill the actor called 'victim' victim.sendOneWay(kill()); From ccd36c5ac97f103d7dd0275ec665520822f905f7 Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Mon, 11 Apr 2011 10:36:15 -0400 Subject: [PATCH 043/147] Pedantic language tweaks to the first getting started chapter. None of these tweaks are "rewrites" nor should they have semantic effect, in this patch just fixing small stuff and making it read a bit more nicely. --- akka-docs/manual/getting-started-first.rst | 82 +++++++++++----------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst index 2f3f9e518f..ee8618b110 100644 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -4,14 +4,14 @@ Getting Started Tutorial: First Chapter Introduction ------------ -Welcome to the first tutorial on how to get started with Akka and Scala. We assume that you already know what Akka and Scala is and will now focus on the steps necessary to start your first project. +Welcome to the first tutorial on how to get started with Akka and Scala. We assume that you already know what Akka and Scala are and will now focus on the steps necessary to start your first project. There are two variations of this first tutorial: - creating a standalone project and run it from the command line - creating a SBT (Simple Build Tool) project and running it from within SBT -Since they are so similar we will present them both in this tutorial. +Since they are so similar we will present them both. The sample application that we will create is using actors to calculate the value of Pi. Calculating Pi is a CPU intensive operation and we will utilize Akka Actors to write a concurrent solution that scales out to multi-core processors. This sample will be extended in future tutorials to use Akka Remote Actors to scale out on multiple machines in a cluster. @@ -21,7 +21,7 @@ Here is the formula for the algorithm we will use: .. image:: pi-formula.png -In this particular algorithm the master splits the series into chunks which are sent out to each worker actor to be processed, when each worker has processed its chunk it sends a result back to the master which aggregates to total result. +In this particular algorithm the master splits the series into chunks which are sent out to each worker actor to be processed. When each worker has processed its chunk it sends a result back to the master which aggregates the total result. Tutorial source code -------------------- @@ -31,23 +31,23 @@ If you want don't want to type in the code and/or set up an SBT project then you Prerequisites ------------- -This tutorial assumes that you have Jave 1.6 or later installed on you machine and ``java`` on your ``PATH``. You also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a decent text editor or IDE to type in the Scala code in. +This tutorial assumes that you have Jave 1.6 or later installed on you machine and ``java`` on your ``PATH``. You also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a decent text editor or IDE to type in the Scala code. Downloading and installing Akka ------------------------------- -If you want to be able to build and run the tutorial sample from the command line then you have to download Akka. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. +To build and run the tutorial sample from the command line, you have to download Akka. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. Let's get the ``akka-1.1`` distribution of Akka core (not Akka Modules) from `http://akka.io/downloads `_. Once you have downloaded the distribution unzip it in the folder you would like to have Akka installed in, in my case I choose to install it in ``/Users/jboner/tools/``, simply by unzipping it to this directory. -You need to do one more thing in order to install Akka properly and that is to set the ``AKKA_HOME`` environment variable to the root of the distribution. In my case I'm opening up a shell and navigating down to the distribution and setting the ``AKKA_HOME`` variable:: +You need to do one more thing in order to install Akka properly: set the ``AKKA_HOME`` environment variable to the root of the distribution. In my case I'm opening up a shell, navigating down to the distribution, and setting the ``AKKA_HOME`` variable:: $ cd /Users/jboner/tools/akka-1.1 $ export AKKA_HOME=`pwd` $ echo $AKKA_HOME /Users/jboner/tools/akka-1.1 -If we now take a look at what we have in this distribution, looks like this:: +The distribution looks like this:: $ ls -l total 16944 @@ -59,11 +59,11 @@ If we now take a look at what we have in this distribution, looks like this:: -rwxr-xr-x 1 jboner staff 8674105 Apr 6 11:15 scala-library.jar drwxr-xr-x 4 jboner staff 136 Apr 6 11:16 scripts -- In the ``dist`` directory we have all the Akka JARs, including sources and docs. -- In the ``lib_managed/compile`` directory we have all the Akka's dependency JARs. -- In the ``deploy`` directory we have all the sample JARs. +- In the ``dist`` directory we have the Akka JARs, including sources and docs. +- In the ``lib_managed/compile`` directory we have Akka's dependency JARs. +- In the ``deploy`` directory we have the sample JARs. - In the ``scripts`` directory we have scripts for running Akka. -- Finallly the ``scala-library.jar`` is the JAR for the latest Scala distribution that Akka depends on. +- Finally ``scala-library.jar`` is the JAR for the latest Scala distribution that Akka depends on. The only JAR we will need for this tutorial (apart from the ``scala-library.jar`` JAR) is the ``akka-actor-1.1.jar`` JAR in the ``dist`` directory. This is a self-contained JAR with zero dependencies and contains everything we need to write a system using Actors. @@ -77,7 +77,7 @@ Akka is very modular and has many JARs for containing different features. The co - ``akka-slf4j-1.1.jar`` -- SLF4J Event Handler Listener - ``akka-testkit-1.1.jar`` -- Toolkit for testing Actors -We also have Akka Modules containing add-on modules for the core of Akka. You can download the Akka Modules distribution from TODO. It contains Akka core as well. We will not be needing any modules there today but for your information the module JARs are these: +We also have Akka Modules containing add-on modules outside the core of Akka. You can download the Akka Modules distribution from TODO. It contains Akka core as well. We will not be needing any modules there today, but for your information the module JARs are these: - ``akka-kernel-1.1.jar`` -- Akka microkernel for running a bare-bones mini application server (embeds Jetty etc.) - ``akka-amqp-1.1.jar`` -- AMQP integration @@ -90,27 +90,27 @@ We also have Akka Modules containing add-on modules for the core of Akka. You ca Downloading and installing Scala -------------------------------- -If you want to be able to build and run the tutorial sample from the command line then you have to install the Scala distribution. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. +To build and run the tutorial sample from the command line, you have to install the Scala distribution. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. -Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0 final release. If you pick the ``tgz`` or ``zip`` distributions then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. +Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0 final release. If you pick the ``tgz`` or ``zip`` distribution then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. You also need to make sure that the ``scala-2.9.0-final/bin`` (if that is the directory where you installed Scala) is on your ``PATH``:: $ export PATH=$PATH:scala-2.9.0-final/bin -Now you can test you installation by invoking and see the printout:: +You can test your installation by invoking scala:: $ scala -version Scala code runner version 2.9.0.final -- Copyright 2002-2011, LAMP/EPFL Looks like we are all good. Finally let's create a source file ``Pi.scala`` for the tutorial and put it in the root of the Akka distribution in the ``tutorial`` directory (you have to create it first). -Some tools requires you to set the ``SCALA_HOME`` environment variable to the root of the Scala distribution, however Akka does not require that. +Some tools require you to set the ``SCALA_HOME`` environment variable to the root of the Scala distribution, however Akka does not require that. Downloading and installing SBT ------------------------------ -SBT, short for 'Simple Build Tool' is an excellent build system written in Scala. You are using Scala to write the build scripts which gives you a lot of power. It has a plugin architecture with many plugins available, something that we will take advantage of soon. SBT is the preferred way of building software in Scala. If you want to use SBT for this tutorial then follow the following instructions, if not you can skip this section and the next. +SBT, short for 'Simple Build Tool' is an excellent build system written in Scala. It uses Scala to write the build scripts which gives you a lot of power. It has a plugin architecture with many plugins available, something that we will take advantage of soon. SBT is the preferred way of building software in Scala. If you want to use SBT for this tutorial then follow the following instructions, if not you can skip this section and the next. To install SBT and create a project for this tutorial it is easiest to follow the instructions on `this page `_. The preferred SBT version to install is ``0.7.6``. @@ -131,7 +131,7 @@ If you have not already done so, now is the time to create an SBT project for ou Scala version [2.9.0]: sbt version [0.7.6]: -Now we have the basis for an SBT project. Akka has an SBT Plugin that makes it very easy to use Akka is an SBT-based project so let's use that. +Now we have the basis for an SBT project. Akka has an SBT Plugin making it very easy to use Akka is an SBT-based project so let's use that. To use the plugin, first add a plugin definition to your SBT project by creating a ``Plugins.scala`` file in the ``project/plugins`` directory containing:: @@ -152,14 +152,14 @@ Now we need to create a project definition using our Akka SBT plugin. We do that The magic is in mixing in the ``AkkaProject`` trait. -Not needed in this tutorial, but if you would like to use additional Akka modules than ``akka-actor`` then you can add these as "module configurations" in the project file. Here is an example adding ``akka-remote`` and ``akka-stm``:: +Not needed in this tutorial, but if you would like to use additional Akka modules beyond ``akka-actor``, you can add these as "module configurations" in the project file. Here is an example adding ``akka-remote`` and ``akka-stm``:: class AkkaSampleProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject { val akkaSTM = akkaModule("stm") val akkaRemote = akkaModule("remote") } -So, now we are all set. Just one final thing to do; make SBT download all dependencies it needs. That is done by invoking:: +So, now we are all set. Just one final thing to do; make SBT download the dependencies it needs. That is done by invoking:: > update @@ -168,9 +168,9 @@ SBT itself needs a whole bunch of dependencies but our project will only need on Start writing the code ---------------------- -Now it's about time that we start hacking. +Now it's about time to start hacking. -We start by creating a ``Pi.scala`` file and add these import statements at the top of the file:: +We start by creating a ``Pi.scala`` file and adding these import statements at the top of the file:: package akka.tutorial.scala.first @@ -184,12 +184,12 @@ We start by creating a ``Pi.scala`` file and add these import statements at the If you are using SBT in this tutorial then create the file in the ``src/main/scala`` directory. -If you are using the command line tools then just create the file wherever you want. I will create it in a directory called ``tutorial`` at the root of the Akka distribution, e.g. in ``$AKKA_HOME/tutorial/Pi.scala``. +If you are using the command line tools then create the file wherever you want. I will create it in a directory called ``tutorial`` at the root of the Akka distribution, e.g. in ``$AKKA_HOME/tutorial/Pi.scala``. Creating the messages --------------------- -The design we are aiming for is to have one ``Master`` actor initiating the computation, creating a set of ``Worker`` actors. Then it splits up the work into discrete chunks, sends out these work chunks to the different workers in a round-robin fashion. The master then waits until all the workers have completed all the work and sent back the result for aggregation. When computation is completed the master prints out the result, shuts down all workers an then himself. +The design we are aiming for is to have one ``Master`` actor initiating the computation, creating a set of ``Worker`` actors. Then it splits up the work into discrete chunks, and sends these chunks to the different workers in a round-robin fashion. The master waits until all the workers have completed their work and sent back results for aggregation. When computation is completed the master prints out the result, shuts down all workers and then itself. With this in mind, let's now create the messages that we want to have flowing in the system. We need three different messages: @@ -219,9 +219,9 @@ Now we can create the worker actor. This is done by mixing in the ``Actor`` tra } } -As you can see we have now created an ``Actor`` with a ``receive`` 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 ``self.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 use. +As you can see we have now created an ``Actor`` with a ``receive`` 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 ``self.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. There are many ways we can implement this algorithm in Scala, in this introductory tutorial we have chosen an imperative style using a for comprehension and an accumulator:: +The only thing missing in our ``Worker`` actor is the implementation on the ``calculatePiFor(..)`` method. While there are many ways we can implement this algorithm in Scala, in this introductory tutorial we have chosen an imperative style using a for comprehension and an accumulator:: def calculatePiFor(start: Int, nrOfElements: Int): Double = { var acc = 0.0 @@ -245,13 +245,13 @@ As you can see we are using the ``actorOf`` factory method to create actors, thi import akka.actor.Actor._ -Now we have a router that is representing all our workers in a single abstraction. If you paid attention to the code above to see that we were using the ``nrOfWorkers`` variable. This variable and others we have to pass to the ``Master`` actor in its constructor. So now let's create the master actor. We had to pass in three integer variables needed: +Now we have a router that is representing all our workers in a single abstraction. If you paid attention to the code above, you saw that we were using the ``nrOfWorkers`` variable. This variable and others we have to pass to the ``Master`` actor in its constructor. So now let's create the master actor. We have to pass in three integer variables: - ``nrOfWorkers`` -- defining how many workers we should start up -- ``nrOfMessages`` -- defining how many number chunks should send out to the workers +- ``nrOfMessages`` -- defining how many number chunks to send out to the workers - ``nrOfElements`` -- defining how big the number chunks sent to each worker should be -Let's now write the master actor:: +Here is the master actor:: class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) extends Actor { @@ -279,9 +279,9 @@ Let's now write the master actor:: } } -Couple of things are worth explaining further. +A couple of things are worth explaining further. -First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for doing plumbing (in this specific tutorial), to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch but other abstractions and functions like ``Channel``, ``Future`` and ``!!!`` to achive the same thing in a non-blocking way. But for simplicity let's stick to a ``CountDownLatch`` for now. +First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing (in this specific tutorial), to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch but other abstractions and functions like ``Channel``, ``Future`` and ``!!!`` to achive the same thing in a non-blocking way. But for simplicity let's stick to a ``CountDownLatch`` for now. Second, we are adding a couple of life-cycle callback methods; ``preStart`` and ``postStop``. In the ``preStart`` callback we are recording the time when the actor is started and in the ``postStop`` callback we are printing out the result (the approximation of Pi) and the time it took to calculate it. In this call we also invoke ``latch.countDown`` to tell the outside world that we are done. @@ -290,11 +290,11 @@ But we are not done yet. We are missing the message handler for the ``Master`` a - ``Calculate`` -- which should start the calculation - ``Result`` -- which should aggregate the different results -The ``Calculate`` handler is sending out work to all the ``Worker`` actors and after doing that it also sends a ``Broadcast(PoisonPill)`` message to the router, which will send out the ``PoisonPill`` message to all the actors it is representing (in our case all the ``Worker`` actors). The ``PoisonPill`` is a special kind of message that tells the receiver to shut himself down using the normal shutdown; ``self.stop``. Then we also send a ``PoisonPill`` to the router itself (since it's also an actor that we want to shut down). +The ``Calculate`` handler is sending out work to all the ``Worker`` actors and after doing that it also sends a ``Broadcast(PoisonPill)`` message to the router, which will send out the ``PoisonPill`` message to all the actors it is representing (in our case all the ``Worker`` actors). ``PoisonPill`` is a special kind of message that tells the receiver to shut itself down using the normal shutdown method; ``self.stop``. We also send a ``PoisonPill`` to the router itself (since it's also an actor that we want to shut down). -The ``Result`` handler is simpler, here we just get the value from the ``Result`` message and aggregate it to our ``pi`` member variable. We also keep track of how many results we have received back and if it matches the number of tasks sent out the ``Master`` actor considers itself done and shuts himself down. +The ``Result`` handler is simpler, here we get the value from the ``Result`` message and aggregate it to our ``pi`` member variable. We also keep track of how many results we have received back, and if that matches the number of tasks sent out, the ``Master`` actor considers itself done and shuts down. -Now, let's capture this in code:: +Let's capture this in code:: // message handler def receive = { @@ -318,7 +318,9 @@ Now, let's capture this in code:: Bootstrap the calculation ------------------------- -Now the only thing that is left to implement is the runner that should bootstrap and run his calculation for us. We do that by creating an object that we call ``Pi``, here we can extend the ``App`` trait in Scala which means that we will be able to run this as an application directly from the command line. The ``Pi`` object is a perfect container module for our actors and messages, so let's put them all there. We also create a method ``calculate`` in which we start up the ``Master`` actor and waits for it to finish:: +Now the only thing that is left to implement is the runner that should bootstrap and run the calculation for us. We do that by creating an object that we call ``Pi``, here we can extend the ``App`` trait in Scala, which means that we will be able to run this as an application directly from the command line. + +The ``Pi`` object is a perfect container module for our actors and messages, so let's put them all there. We also create a method ``calculate`` in which we start up the ``Master`` actor and wait for it to finish:: object Pi extends App { @@ -456,13 +458,13 @@ But before we package it up and run it, let's take a look at the full code now, Run it as a command line application ------------------------------------ -If you have not typed (or copied) in the code for the tutorial in the ``$AKKA_HOME/tutorial/Pi.scala`` then now is the time. When that is done open up a shell and step in to the Akka distribution (``cd $AKKA_HOME``). +If you have not typed in (or copied) the code for the tutorial as ``$AKKA_HOME/tutorial/Pi.scala`` then now is the time. When that's done open up a shell and step in to the Akka distribution (``cd $AKKA_HOME``). First we need to compile the source file. That is done with Scala's compiler ``scalac``. Our application depends on the ``akka-actor-1.1.jar`` JAR file, so let's add that to the compiler classpath when we compile the source:: $ scalac -cp dist/akka-actor-1.1.jar tutorial/Pi.scala -When we have compiled the source file we are ready to run the application. This is done with ``java`` but yet again we need to add the ``akka-actor-1.1.jar`` JAR file to the classpath, this time we also need to add the Scala runtime library ``scala-library.jar`` and the classes we compiled ourselves to the classpath:: +When we have compiled the source file we are ready to run the application. This is done with ``java`` but yet again we need to add the ``akka-actor-1.1.jar`` JAR file to the classpath, and this time we also need to add the Scala runtime library ``scala-library.jar`` and the classes we compiled ourselves:: $ java -cp dist/akka-actor-1.1.jar:scala-library.jar:tutorial akka.tutorial.scala.first.Pi AKKA_HOME is defined as [/Users/jboner/src/akka-stuff/akka-core], loading config from \ @@ -476,7 +478,7 @@ Yippee! It is working. Run it inside SBT ----------------- -If you have based the tutorial on SBT then you can run the application directly inside SBT. First you need to compile the project:: +If you used SBT, then you can run the application directly inside SBT. First you need to compile the project:: $ sbt > update @@ -496,8 +498,8 @@ Yippee! It is working. Conclusion ---------- -Now we have learned how to create our first Akka project utilizing Akka's actors to speed up a computation intensive problem by scaling out on multi-core processors (also known as scaling up). We have also learned how to compile and run an Akka project utilizing either the tools on the command line or the SBT build system. +We have learned how to create our first Akka project using Akka's actors to speed up a computation-intensive problem by scaling out on multi-core processors (also known as scaling up). We have also learned to compile and run an Akka project using either the tools on the command line or the SBT build system. -Now we are ready to take on more advanced problems. In the next tutorial we will build upon this one, refactor it into more idiomatic Akka and Scala code and introduce a few new concepts and abstractions. Whenever you feel ready, join me in the `Getting Started Tutorial: Second Chapter `_. +Now we are ready to take on more advanced problems. In the next tutorial we will build on this one, refactor it into more idiomatic Akka and Scala code, and introduce a few new concepts and abstractions. Whenever you feel ready, join me in the `Getting Started Tutorial: Second Chapter `_. Happy hakking. From 9c08ca7b3d4a92a3b518faff7e49404edd8c714a Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 11 Apr 2011 16:50:22 +0200 Subject: [PATCH 044/147] fixed bugs in typed actors doc --- akka-docs/pending/typed-actors-java.rst | 17 +++++++++++------ akka-docs/pending/typed-actors-scala.rst | 12 ++++++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/akka-docs/pending/typed-actors-java.rst b/akka-docs/pending/typed-actors-java.rst index a1d0d3594c..2322698ed1 100644 --- a/akka-docs/pending/typed-actors-java.rst +++ b/akka-docs/pending/typed-actors-java.rst @@ -23,13 +23,15 @@ If you have a POJO with an interface implementation separation like this: .. code-block:: java interface RegistrationService { - void register(User user, Credentials cred) - User getUserFor(String username) + void register(User user, Credentials cred); + User getUserFor(String username); } .. code-block:: java - public class RegistrationServiceImpl implements RegistrationService extends TypedActor { + import akka.actor.TypedActor; + + public class RegistrationServiceImpl extends TypedActor implements RegistrationService { public void register(User user, Credentials cred) { ... // register user } @@ -69,9 +71,12 @@ Using a configuration object: .. code-block:: java + import static java.util.concurrent.TimeUnit.MILLISECONDS; + import akka.actor.TypedActorConfiguration; + import akka.util.FiniteDuration; + TypedActorConfiguration config = new TypedActorConfiguration() - .timeout(3000) - .makeTransactionRequired(); + .timeout(new FiniteDuration(3000, MILLISECONDS)); RegistrationService service = (RegistrationService) TypedActor.newInstance(RegistrationService.class, config); @@ -161,7 +166,7 @@ Here is an example how you can use it to in a 'void' (e.g. fire-forget) method t class PingImpl implements Ping extends TypedActor { public void hit(int count) { - Pong pong = (Pong) context.getSender(); + Pong pong = (Pong) getContext().getSender(); pong.hit(count++); } } diff --git a/akka-docs/pending/typed-actors-scala.rst b/akka-docs/pending/typed-actors-scala.rst index 74a18527e9..3d03cc93b1 100644 --- a/akka-docs/pending/typed-actors-scala.rst +++ b/akka-docs/pending/typed-actors-scala.rst @@ -10,7 +10,7 @@ If you are using the `Spring Framework `_ then take a l Creating Typed Actors --------------------- -**IMPORTANT:** The Typed Actors class must have access modifier 'public' and can't be an inner class (unless it is an inner class in an 'object'). +**IMPORTANT:** The Typed Actors class must have access modifier 'public' (which is default) and can't be an inner class (unless it is an inner class in an 'object'). Akka turns POJOs with interface and implementation into asynchronous (Typed) Actors. Akka is using `AspectWerkz’s Proxy `_ implementation, which is the `most performant `_ proxy implementation there exists. @@ -22,6 +22,8 @@ If you have a POJO with an interface implementation separation like this: .. code-block:: scala + import akka.actor.TypedActor + trait RegistrationService { def register(user: User, cred: Credentials): Unit def getUserFor(username: String): User @@ -64,10 +66,12 @@ Configuration factory class Using a configuration object: .. code-block:: scala + import akka.actor.TypedActorConfiguration + import akka.util.Duration + import akka.util.duration._ - val config = new TypedActorConfiguration - .timeout(3000) - .makeTransactionRequired + val config = TypedActorConfiguration() + .timeout(3000 millis) val service = TypedActor.newInstance(classOf[RegistrationService], classOf[RegistrationServiceImpl], config) From 3770a32216622bb2ac3d8a44d82c739d28c8f21b Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 11 Apr 2011 17:08:10 +0200 Subject: [PATCH 045/147] Fixing 2 wrong types in PriorityEBEDD and added tests for the message processing ordering --- .../akka/dispatch/MailboxConfigSpec.scala | 6 +-- .../dispatch/PriorityDispatcherSpec.scala | 51 +++++++++++++++++++ .../ExecutorBasedEventDrivenDispatcher.scala | 43 +++++++++++++++- 3 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/MailboxConfigSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/MailboxConfigSpec.scala index 7a469868a4..5e08708037 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/MailboxConfigSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/MailboxConfigSpec.scala @@ -173,11 +173,7 @@ class DefaultMailboxSpec extends MailboxSpec { } class PriorityMailboxSpec extends MailboxSpec { - val comparator = new java.util.Comparator[MessageInvocation] { - def compare(a: MessageInvocation, b: MessageInvocation): Int = { - a.## - b.## - } - } + val comparator = PriorityGenerator(_.##) lazy val name = "The priority mailbox implementation" def factory = { case UnboundedMailbox(blockDequeue) => diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala new file mode 100644 index 0000000000..383cf63f48 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala @@ -0,0 +1,51 @@ +package akka.dispatch + +import akka.actor.Actor._ +import akka.actor.Actor +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers +import java.util.concurrent.CountDownLatch + +class PriorityDispatcherSpec extends WordSpec with MustMatchers { + + "A PriorityExecutorBasedEventDrivenDispatcher" must { + "Order it's messages according to the specified comparator using an unbounded mailbox" in { + testOrdering(UnboundedMailbox(false)) + } + + "Order it's messages according to the specified comparator using a bounded mailbox" in { + testOrdering(BoundedMailbox(false,1000)) + } + } + + def testOrdering(mboxType: MailboxType) { + val dispatcher = new PriorityExecutorBasedEventDrivenDispatcher("Test", + PriorityGenerator({ + case i: Int => i //Reverse order + case 'Result => Int.MaxValue + }: Any => Int), + throughput = 1, + mailboxType = mboxType + ) + + val actor = actorOf(new Actor { + self.dispatcher = dispatcher + var acc: List[Int] = Nil + + def receive = { + case i: Int => acc = i :: acc + case 'Result => self reply_? acc + } + }).start + + dispatcher.suspend(actor) //Make sure the actor isn't treating any messages, let it buffer the incoming messages + + val msgs = (1 to 100).toList + for(m <- msgs) actor ! m + + dispatcher.resume(actor) //Signal the actor to start treating it's message backlog + + actor.!!![List[Int]]('Result).await.result.get must be === (msgs.reverse) + } + +} diff --git a/akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala b/akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala index 4fec527642..019923b4b4 100644 --- a/akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala +++ b/akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala @@ -229,9 +229,39 @@ trait ExecutableMailbox extends Runnable { self: MessageQueue => } } +object PriorityGenerator { + /** + * Creates a PriorityGenerator that uses the supplied function as priority generator + */ + def apply(priorityFunction: Any => Int): PriorityGenerator = new PriorityGenerator { + def gen(message: Any): Int = priorityFunction(message) + } + + /** + * Java API + * Creates a PriorityGenerator that uses the supplied function as priority generator + */ + def apply(priorityFunction: akka.japi.Function[Any, Int]): PriorityGenerator = new PriorityGenerator { + def gen(message: Any): Int = priorityFunction(message) + } +} + +/** + * A PriorityGenerator is a convenience API to create a Comparator that orders the messages of a + * PriorityExecutorBasedEventDrivenDispatcher + */ +abstract class PriorityGenerator extends java.util.Comparator[MessageInvocation] { + def gen(message: Any): Int + + final def compare(thisMessage: MessageInvocation, thatMessage: MessageInvocation): Int = + gen(thisMessage.message) - gen(thatMessage.message) +} + /** * A version of ExecutorBasedEventDrivenDispatcher that gives all actors registered to it a priority mailbox, * prioritized according to the supplied comparator. + * + * The dispatcher will process the messages with the _lowest_ priority first. */ class PriorityExecutorBasedEventDrivenDispatcher( name: String, @@ -242,10 +272,10 @@ class PriorityExecutorBasedEventDrivenDispatcher( config: ThreadPoolConfig = ThreadPoolConfig() ) extends ExecutorBasedEventDrivenDispatcher(name, throughput, throughputDeadlineTime, mailboxType, config) with PriorityMailbox { - def this(name: String, comparator: java.util.Comparator[MessageInvocation], throughput: Int, throughputDeadlineTime: Int, mailboxType: UnboundedMailbox) = + def this(name: String, comparator: java.util.Comparator[MessageInvocation], throughput: Int, throughputDeadlineTime: Int, mailboxType: MailboxType) = this(name, comparator, throughput, throughputDeadlineTime, mailboxType,ThreadPoolConfig()) // Needed for Java API usage - def this(name: String, comparator: java.util.Comparator[MessageInvocation], throughput: Int, mailboxType: UnboundedMailbox) = + def this(name: String, comparator: java.util.Comparator[MessageInvocation], throughput: Int, mailboxType: MailboxType) = this(name, comparator, throughput, Dispatchers.THROUGHPUT_DEADLINE_TIME_MILLIS, mailboxType) // Needed for Java API usage def this(name: String, comparator: java.util.Comparator[MessageInvocation], throughput: Int) = @@ -258,6 +288,15 @@ class PriorityExecutorBasedEventDrivenDispatcher( this(name, comparator, Dispatchers.THROUGHPUT, Dispatchers.THROUGHPUT_DEADLINE_TIME_MILLIS, Dispatchers.MAILBOX_TYPE) // Needed for Java API usage } + +/** + * Can be used to give an ExecutorBasedEventDrivenDispatcher's actors priority-enabled mailboxes + * + * Usage: + * new ExecutorBasedEventDrivenDispatcher(...) with PriorityMailbox { + * val comparator = ...comparator that determines mailbox priority ordering... + * } + */ trait PriorityMailbox { self: ExecutorBasedEventDrivenDispatcher => def comparator: java.util.Comparator[MessageInvocation] From d5e012ad143499c7f7c00c904a27593d5641debf Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 12 Apr 2011 12:52:20 +1200 Subject: [PATCH 046/147] Update docs logo and version --- akka-docs/_static/logo.png | Bin 8112 -> 6767 bytes akka-docs/conf.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-docs/_static/logo.png b/akka-docs/_static/logo.png index f9574199c0c3ed86409979fdd178740b72ffbb7f..2c36c66a36d62ba21608ec04770c4ddf8d8ccfa9 100644 GIT binary patch delta 4090 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 delta 5446 zcmaEFvcX=lGr-TCmrII^fq{Y7)59f*fq_wnfq@}_gN=cKVeu=E>Wzx?xavi2d%8G= zRNQ(qx4I_u>eS==AM$?U`Vi`Nt*jRiuDC!7^t%4g5rY2E&L&(589 zCVc%}bMCoX`uxq`Eq~AXYn{Dz@3mX5y!E`i5kq=z8`wtZFa*|7V!XN-fO)re@k%iK5!@m8W9vI>{tWIpi}!y(CC9efz^j?|M<=X-d)acp3(lBXTIuzpTEBQJdjJ!DKIN=*uM9G{ms=W1`>&t z7J?BRG0cCsG}y$nE5GC<-IWz|{^gM{>DtT0DbxOQR$RNepD|)x{rUjTH(AwlLC$jY z@^f7^>96Du_XP0`e&18OUpJ;|8Lqujdtb@8y57pIUY^~qYsK^S1Fm+Pj3s#1_H0Go1&@P7~&YpnD(<&EnV}w_`ux*91na|=R_VzZ;)=7Tq1wqvtem%%Z;t? z7~F1Wry6XrJ0!JwlDGNPg18HRzaD5mSGc*6?a03=EN`c-n8#H0V&8k_+l{wFvu3l} ztUaE`a7|@J{de96yAPai^uN72)nJR>p*@j9y4*lJ^8G=xw4vX8UI#K z_bR;G;BR^9>b#cgHvXLjng&LAC`<|s-JTqYVksGlm&#CA(E4pV) zUtYFL%Q7d#Xhzo$Q(cyQe0QG9ShIX+k}Ox+Tp?%28Poa0{p#y&VXy1te=HNaP?Vc& zxPgD>&)vbE1)Y;_g@xX2syA@i-g&?=K2lv}*$0~io8MLYbH4lj>%UflRDt*h9;4ek zuEm)*FWkVnTI9t7q2n(*)(bndPV%!XSTA&PZ-*)3jz^0#-Lk?lO@ zihA7372CeXzj)u=QCOGXl)XQA=?d=J6w?MDpG1Lm1tB+?wQkm~Sn{)HWjmX5qEMLs z0-+b{|NjqBRTsCOTbn!eLQHeT-u?$x$LqJeoVGsvWM1dLxSk(jd*?s6wVlyi@!ZPo zEGxDJ7Blg@vs<(a|!CQS65{=QE;M6F*j_w8iYVE(si(lsR`;{|NZ4-Evv{Ew(U2$cPy z@z95*{@d{t@p9WHaW+OyXizv*uutgx%#@^mj||v2KUWGAPP{Sw@V>}~8<+F0)m4f8 zV6Xnm(^hg~>czbhKYE2)+RGdDnYJ1}*K@Kt%dY%w8S4Rdhfv{l&)10@|K^y)^}6Bx zg&W;=(^t4UmCs|Gcj5OwxsTqbS*?`hzOhe?JXKY_mshPmk@c~*-KJYBCSBF$JilLZ z=1l1|Sxp@iYR$jM=hwQ+R(Vom*+!D6eqrV@(pTM&HjGoQGH- z!oT_JCOy?qVJouxVD!lF|E@ZQdu~RKZ#XReyB#t78k2tC_5{!K=>p}G>P^4RlJ42S zvRrwamVos&`Jj3CLpk%!lBzRvZ!BN+&`8CG!L@s)ugQu1zUrH@dy!^3DHE zdEyFvtEX{ZP0&(&#I`}AaM~ZWN0%nBJ)W5GB0Wkovhqg5jd_P+EbawuJ+Hj_UdP6N zdZC*2@&)Ir{_bt^@vCZHcva|{z>)j)tHd9P)|Gb^_q(M%d8U||@@~uV15PSud*%HV zH?Qwkp7-Z}&zgImU61@Lb3b6Y_r#eS+Z>xt^R|Vp7TOj*>1CVFt$>*y_B8%FD!-OF zlkHK&F3;C@?w^`}lqcouJfpw=U8g#ISKM|mdP0tn25+Q#(UI2m^H zbzVk&Z;Pq)#@RM=o7VU_HBF3nl2P3rbzQ!R`4{7h)|5><98bO~JK}%#aB;_7?K6ts z1&?gA@UqvEo#@%Sdcp^V=7pgGVdkMv*Ez;8E?aI<-kjH#aYZRlF?NDN&h$0cl*(4S zTbj5}@0L1Z`^aMIwSE05nR-XUB_-T9Awu2MOIkQ=k6zl{4|CfDaoSG5$=HZ1Y z=WaeQ|5f*YIFQ$IQjQ#{ncG3FHBNhbJD{gckU{sVvqL{SsFTToS81gJF59uDRy7^rcmoK z;b4G4OR41}o+{xF5+CZXg}SSJ3$jS`VspOM@>Q*1+bSWp=7RwjT0-Y;nwO*^wr+V| z?Mamh+D@A~@*Kk!-1K^UW>LGet-x+S&PnV4)*kVgo4>M1K*;fe^Y8j+yho-c=8Kh< zIy9a7t?*9c?}SKq{Xffjjf8(T=Din~pK&okBjS)(Q)0*Y3#=!m>@?R{bfIC*!|jqA z7gwC#CBN=qP}hy;c295bcfR`Yluhh$p9k@R>i**Am9tIuO`lMq?^1MQe%F#WFI~SZ zl2j1Zh!$La>G{MP=S(Ax1vxV+&909)bkO6_PEn6-XNo7vyqhaLCwfWz-^3F|k)J2} zu*9p^ckkM@!?a<|o`d$mOOHJ}cGIg*mid&MkUaMdv5ynhU;8&_!Qb2JjFAh^zfJxn zvs}4y=e$c+JZ)mSiLs4mWZ2g7R|uSZxFtw+g_X)%&v^H@Qc?x)7>*n)RN3^%Z=cYs zt@YQlxt-PbUMrvV)9RP7t+TkROl*jgjCfGo>a3@yf9c!{6y4aq&6?@Qj{Y0B6!Z9= zD;9S0-!w4X;&RAJK2l|ov%}5y6SJB^UDyrzUBcYtWCO}p^7VbJ404we+HyI7;k<^I zSCvB3nrRB2f6R_e_%^lUw3m2%WVj0BgX)gYD-YIRND4Xh?Zl@~vQDSuos#Z6GSbTl ziS~{4_?c>Tg6HFm1u4QUR&Ss88kWAfp8mbNga4M!)KffdV$KoM72_@?Rw%j7ejVz| zd3Xv((iwBEgENhE$_k2(SZ1G8`RLuW;`#GsdkiI_SQ2|XHEy%+nYHPz#v!ZTKN2nb(t^8#s|4oTom&{=PFk2 z_ZAXYfX?cH0Gu z^b`Ki9^SiMxMJZ=SJR&}H@=zNY9i$>m)U&z`^5@HZu=^QzO8Ka&Ig6V+!v*1@YZcxA0}Dc@p#m}l{3=o)uaiZ z)Hh|6UrF5ee&T`su2b&!rTnh<>HZ~>a$EG|onId(e42V-uVQMy;>*6~J&)u0C!ShV zp!6})gZrR*@596G?V?kei@&~K%JTbH<&6F_HDL^wg-m9D`|-?eX5Yf1`~S4|e%~-* z)7uW?FBP`iE^Of9oIZP7*40&AIh?EIc8EnDdB9{;Hb2*cWjRHuZdaXxaUx=ve(DGeZwvRTj_9(8wVc7M{Uv~#AU7h8A$m$YD_B!VrXQaWI??*)H*v*vl6OlL<}dm#_~p|4#oxcxOuN1z@GU!gP0Itp za`}{>YZX_W`kDT$@7naYf+-g}cCqC0ITme+clz_@;lwT@_g5b{>Q{$s-?HtXW~=*XM!$gjCG$|P6{T!>(kdRi+*CKk=r8=2oiee+r{iI0Am8u7omqpcn?A{vRJyqk9Kv-v;L6`8P&?_H{Ew5<2o49hjYUoWj%kxH@ zjGLmBRb?#ODCE9Z#y{^(<59b<*Hph zNAUdUIfDC?g*@&GajrfyF=mfrtZ$mwTBV~2v(_`0?g@@JJHA_JV*IHYt6E;ITpOy% zv3gcY+n1#(-+Sk*{(3zlKGf>3Rp|TFeY5I{>X%;ob$gcNXFYSN1kVERo?V8qo(Zb0 zlYd$8@w-o%E2?zrnbhgdIPn6njxQ!Mkt!-Y(GzA~uvD3CsI%zlugIUAr$c$YJ>EGz zuKl@ORb}Fe$FW;a{g(^1xIW9IYQdHC#ivERWRm8(B$sz+O=Za5@^_QWme6N69^Bcs zOf{5qvOrk9y^464gxTu@8JQ98TaN!-TDhw))KLG`hK9e3lb1Z6&rqMqx=s6h?LxDJ z4Of3HnY`pl@WkfF+Fz9mf?7(R{X6DabhPiwjhK}ECP(H3OkE^Dc}u9M)1IlG@ANqn zWkr*fKZ;HFSZ#H*_Ty~nQkUmm_T7KZaOa7SA5&L-s)%+Ds_re;Kl{`_R7zp)ufWIF+g9d=nm@HGp6I^USB#}N#^>ij zn>}xSZ)xu1*L>f)gZ0nWnd_DH-n|!Q{B74^seb+E5x$@qt81%MLi{!~-VWt%W=q|v zymyMmLYI|lnSI}LLKB-e*IRV|);(AFf9ekp%{Nip%hsyIpPlP+d+8?5n;n~{=bSry z`k&xMNv>NH=Rdvu_{7fY1N*)wpL;p?XsL_s@w=4@@3v_88#bI);z(HOZ1vRT_S8vo z?DdCuF(Bp(<7X@9`qW4fWiL zb?f5A{$93Cm5!U@G)3fxTccO_yuBJ3tGg6+^q*~T@?2u!q~%q-=e6C?pw_NXllNG z?e#_HgHe+(r<|V1WeXS8u7o|0Pb+mCP!_Mx-_YNs*tgtliCXB4ie z)6%)b>o1?bs%QA5`SEW5^he9h7U`~! zKUJd=JBq@3 zlkW80a5B&Mz3yV2*2TJt`a09R>Jm+CHj^qzb0rI+gEDJ5Db&t>mr>nxffreIsSd7}D6 zy$!p1KNC+y{Zq%n}FddF3^^KG9S6)yC~V;?2X$+upZ-w)*jB*R7=^#osTI zxa}_TP1)GL{{`=nr6E6=9%goLUpZxZ*{+Iuu@7rGUOtkM(s#Kic5>gZnG=fBj;{!J zlwjH8Qc?Hwb<&l>kf>c@{kBuz{M}HdD6C`8_V2X+Ipb$%4kuX5IdQG<%l^{Wy%H7e zFAi8ca;}k>S(z1ZGK2e3#SYe!Pd@DOihb>|@6kD%wXdymSMQ#5N9uulinwoGciiQO z(8$}0lDzfPlp7E4X1;Q5b))puGksTI-|t9?->cu9UEzFtevi)btNu$@UH{VkHK56= zOsVmC@dVL!H-_e~^M0-Ny3yY=`Munc)Aj6+K8Gzn|ET}TarScwC(fw}t0YSDusJI( z>Mdq=SqEiw5j@b?vw??Ax_P0&$L)*^+67ZtnO97|!oa}5;OXk;vd$@? F2>^X3T&Ms5 diff --git a/akka-docs/conf.py b/akka-docs/conf.py index c95735d40f..82f40cf18b 100644 --- a/akka-docs/conf.py +++ b/akka-docs/conf.py @@ -16,8 +16,8 @@ exclude_patterns = ['_build'] project = u'Akka' copyright = u'2009-2011, Scalable Solutions AB' -version = '1.0' -release = '1.0' +version = '1.1' +release = '1.1' pygments_style = 'akka' highlight_language = 'scala' From 171c8c8bc246a378429be996f774761143263e8e Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 12 Apr 2011 13:14:36 +1200 Subject: [PATCH 047/147] Exclude pending docs --- akka-docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-docs/conf.py b/akka-docs/conf.py index 82f40cf18b..4ff27f40bb 100644 --- a/akka-docs/conf.py +++ b/akka-docs/conf.py @@ -12,7 +12,7 @@ extensions = ['sphinx.ext.todo'] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' -exclude_patterns = ['_build'] +exclude_patterns = ['_build', 'pending'] project = u'Akka' copyright = u'2009-2011, Scalable Solutions AB' From 4ccf4f869f2eb4cd96728383ef8e2a6729afefa6 Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Mon, 11 Apr 2011 21:12:44 -0600 Subject: [PATCH 048/147] Made a pass through Actor docs --- akka-docs/pending/actors-scala.rst | 181 +++++++++++++++-------------- 1 file changed, 91 insertions(+), 90 deletions(-) diff --git a/akka-docs/pending/actors-scala.rst b/akka-docs/pending/actors-scala.rst index f07dbb0ca4..f756420de8 100644 --- a/akka-docs/pending/actors-scala.rst +++ b/akka-docs/pending/actors-scala.rst @@ -1,27 +1,26 @@ Actors (Scala) ============== -= - Module stability: **SOLID** The `Actor Model `_ provides a higher level of abstraction for writing concurrent and distributed systems. It alleviates the developer from having to deal with explicit locking and thread management, making it easier to write correct concurrent and parallel systems. Actors were defined in the 1973 paper by Carl Hewitt but have been popularized by the Erlang language, and used for example at Ericsson with great success to build highly concurrent and reliable telecom systems. The API of Akka’s Actors is similar to Scala Actors which has borrowed some of its syntax from Erlang. -The Akka 0.9 release introduced a new concept; ActorRef, which requires some refactoring. If you are new to Akka just read along, but if you have used Akka 0.6.x, 0.7.x and 0.8.x then you might be helped by the `0.8.x => 0.9.x migration guide `_ +The Akka 0.9 release introduced a new concept; ActorRef, which requires some refactoring. If you are new to Akka just read along, but if you have used Akka 0.6.x, 0.7.x and 0.8.x then you might be helped by the :doc:`0.8.x => 0.9.x migration guide ` Creating Actors --------------- Actors can be created either by: + * Extending the Actor class and implementing the receive method. * Create an anonymous actor using one of the actor methods. Defining an Actor class ^^^^^^^^^^^^^^^^^^^^^^^ -Actor classes are implemented by extending the Actor class and implementing the 'receive' method. The 'receive' method should definie a series of case statements (which has the type 'PartialFunction[Any, Unit]') that defines which messages your Actor can handle, using standard Scala pattern matching, along with the implementation of how the messages should be processed. +Actor classes are implemented by extending the Actor class and implementing the ``receive`` method. The ``receive`` method should define a series of case statements (which has the type ``PartialFunction[Any, Unit]``) that defines which messages your Actor can handle, using standard Scala pattern matching, along with the implementation of how the messages should be processed. Here is an example: @@ -34,9 +33,7 @@ Here is an example: } } -Please note that the Akka Actor 'receive' message loop is exhaustive, which is different compared to Erlang and Scala Actors. This means that you need to provide a pattern match for all messages that it can accept and if you want to be able to handle unknown messages then you need to have a default case as in the example above. - -The 'Actor' trait mixes in the 'akka.util.Logging' trait which defines a logger in the 'log' field that you can use to log. This logger is configured in the 'akka.conf' configuration file (and is based on the Configgy library which is using Java Logging). +Please note that the Akka Actor ``receive`` message loop is exhaustive, which is different compared to Erlang and Scala Actors. This means that you need to provide a pattern match for all messages that it can accept and if you want to be able to handle unknown messages then you need to have a default case as in the example above. Creating Actors ^^^^^^^^^^^^^^^ @@ -46,7 +43,7 @@ Creating Actors val myActor = Actor.actorOf[MyActor] myActor.start -Normally you would want to import the 'actorOf' method like this: +Normally you would want to import the ``actorOf`` method like this: .. code-block:: scala @@ -54,7 +51,7 @@ Normally you would want to import the 'actorOf' method like this: val myActor = actorOf[MyActor] -To avoid prefixing it with 'Actor' every time you use it. +To avoid prefixing it with ``Actor`` every time you use it. You can also start it in the same statement: @@ -62,12 +59,12 @@ You can also start it in the same statement: val myActor = actorOf[MyActor].start -The call to 'actorOf' returns an instance of 'ActorRef'. This is a handle to the 'Actor' instance which you can use to interact with the Actor, like send messages to it etc. more on this shortly. The 'ActorRef' is immutable and has a one to one relationship with the Actor it represents. The 'ActorRef' is also serializable and network-aware. This means that you can serialize it, send it over the wire and use it on a remote host and it will still be representing the same Actor on the original node, across the network. +The call to ``actorOf`` returns an instance of ``ActorRef``. This is a handle to the ``Actor`` instance which you can use to interact with the ``Actor``. The ``ActorRef`` is immutable and has a one to one relationship with the Actor it represents. The ``ActorRef`` is also serializable and network-aware. This means that you can serialize it, send it over the wire and use it on a remote host and it will still be representing the same Actor on the original node, across the network. Creating Actors with non-default constructor ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If your Actor has a constructor that takes parameters then you can't create it using 'actorOf[TYPE]'. Instead you can use a variant of 'actorOf' that takes a call-by-name block in which you can create the Actor in any way you like. +If your Actor has a constructor that takes parameters then you can't create it using ``actorOf[TYPE]``. Instead you can use a variant of ``actorOf`` that takes a call-by-name block in which you can create the Actor in any way you like. Here is an example: @@ -90,10 +87,11 @@ Identifying Actors ------------------ Each Actor has two fields: -* self.uuid -* self.id -The difference is that the 'uuid' is generated by the runtime, guaranteed to be unique and can't be modified. While the 'id' is modifiable by the user, and defaults to the Actor class name. You can retrieve Actors by both UUID and ID using the 'ActorRegistry', see the section further down for details. +* ``self.uuid`` +* ``self.id`` + +The difference is that the ``uuid`` is generated by the runtime, guaranteed to be unique and can't be modified. While the ``id`` is modifiable by the user, and defaults to the Actor class name. You can retrieve Actors by both UUID and ID using the ``ActorRegistry``, see the section further down for details. Messages and immutability ------------------------- @@ -110,21 +108,22 @@ Here is an example: // create a new case class message val message = Register(user) -Other good messages types are 'scala.Tuple2', 'scala.List', 'scala.Map' which are all immutable and great for pattern matching. +Other good messages types are ``scala.Tuple2``, ``scala.List``, ``scala.Map`` which are all immutable and great for pattern matching. Send messages ------------- Messages are sent to an Actor through one of the “bang” methods. -* ! means “fire-and-forget”, e.g. send a message asynchronously and return immediately. -* !! means “send-and-reply-eventually”, e.g. send a message asynchronously and wait for a reply through aFuture. Here you can specify a timeout. Using timeouts is very important. If no timeout is specified then the actor’s default timeout (set by the this.timeout variable in the actor) is used. This method returns an 'Option[Any]' which will be either 'Some(result)' if returning successfully or None if the call timed out. -* !!! sends a message asynchronously and returns a 'Future'. -You can check if an Actor can handle a specific message by invoking the 'isDefinedAt' method: +* ! means “fire-and-forget”, e.g. send a message asynchronously and return immediately. +* !! means “send-and-reply-eventually”, e.g. send a message asynchronously and wait for a reply through aFuture. Here you can specify a timeout. Using timeouts is very important. If no timeout is specified then the actor’s default timeout (set by the this.timeout variable in the actor) is used. This method returns an ``Option[Any]`` which will be either ``Some(result)`` if returning successfully or None if the call timed out. +* !!! sends a message asynchronously and returns a ``Future``. + +You can check if an Actor can handle a specific message by invoking the ``isDefinedAt`` method: .. code-block:: scala - if (actor.isDefinedAt(message) actor ! message + if (actor.isDefinedAt(message)) actor ! message else ... Fire-forget @@ -136,21 +135,21 @@ This is the preferred way of sending messages. No blocking waiting for a message actor ! "Hello" -If invoked from within an Actor, then the sending actor reference will be implicitly passed along with the message and available to the receiving Actor in its 'sender: Option[AnyRef]' member field. He can use this to reply to the original sender or use the 'reply(message: Any)' method. +If invoked from within an Actor, then the sending actor reference will be implicitly passed along with the message and available to the receiving Actor in its ``sender: Option[AnyRef]`` member field. He can use this to reply to the original sender or use the ``reply(message: Any)`` method. -If invoked from an instance that is **not** an Actor there will be no implicit sender passed along the message and you will get an IllegalStateException if you call 'self.reply(..)'. +If invoked from an instance that is **not** an Actor there will be no implicit sender passed along the message and you will get an IllegalStateException if you call ``self.reply(..)``. Send-And-Receive-Eventually ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Using '!!' will send a message to the receiving Actor asynchronously but it will wait for a reply on a 'Future', blocking the sender Actor until either: +Using ``!!`` will send a message to the receiving Actor asynchronously but it will wait for a reply on a ``Future``, blocking the sender Actor until either: * A reply is received, or * The Future times out -You can pass an explicit time-out to the '!!' method and if none is specified then the default time-out defined in the sender Actor will be used. +You can pass an explicit time-out to the ``!!`` method and if none is specified then the default time-out defined in the sender Actor will be used. -The '!!' method returns an 'Option[Any]' which will be either 'Some(result)' if returning successfully, or None if the call timed out. +The ``!!`` method returns an ``Option[Any]`` which will be either ``Some(result)`` if returning successfully, or ``None`` if the call timed out. Here are some examples: .. code-block:: scala @@ -172,7 +171,7 @@ Here are some examples: Send-And-Receive-Future ^^^^^^^^^^^^^^^^^^^^^^^ -Using '!!!' will send a message to the receiving Actor asynchronously and will return a 'Future': +Using ``!!!`` will send a message to the receiving Actor asynchronously and will return a 'Future': .. code-block:: scala @@ -192,15 +191,15 @@ You can forward a message from one actor to another. This means that the origina Receive messages ---------------- -An Actor has to implement the ‘receive’ method to receive messages: +An Actor has to implement the ``receive`` method to receive messages: .. code-block:: scala protected def receive: PartialFunction[Any, Unit] -Note: Akka has an alias to the 'PartialFunction[Any, Unit]' type called 'Receive' (akka.actor.Actor.Receive), so you can use this type instead for clarity. But most often you don't need to spell it out. +Note: Akka has an alias to the ``PartialFunction[Any, Unit]`` type called ``Receive`` (``akka.actor.Actor.Receive``), so you can use this type instead for clarity. But most often you don't need to spell it out. -This method should return a PartialFunction, e.g. a ‘match/case’ clause in which the message can be matched against the different case clauses using Scala pattern matching. Here is an example: +This method should return a ``PartialFunction``, e.g. a ‘match/case’ clause in which the message can be matched against the different case clauses using Scala pattern matching. Here is an example: .. code-block:: scala @@ -218,22 +217,24 @@ 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 (apart from the 'log' field from the mixed in 'Logging' trait): +#. ``receive`` message handler +#. life-cycle callbacks: + + #. preStart + #. postStop + #. preRestart + #. postRestart + +The ``Actor`` trait has one single member field (apart from the ``log`` field from the mixed in ``Logging`` trait): .. 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. +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: +However, for convenience you can import these functions and fields like below, which will allow you do drop the ``self`` prefix: .. code-block:: scala @@ -245,17 +246,17 @@ However, for convenience you can import these functions and fields like below, w ... } -But in this documentation we will always prefix the calls with 'self' for clarity. +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. +Let's start by looking how we can reply to messages in a convenient way using this ``ActorRef`` API. Reply to messages ----------------- -Reply using the reply and reply_? methods -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Reply using the reply 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 'reply(..)' method. +If you want to send a message back to the original sender of the message you just received then you can use the ``reply(..)`` method. .. code-block:: scala @@ -263,9 +264,9 @@ If you want to send a message back to the original sender of the message you jus val result = process(request) self.reply(result) -In this case the 'result' will be send back to the Actor that sent the 'request'. +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 ``reply_?`` method which returns ``true`` if reply was sent, and ``false`` if unable to determine what to reply to. .. code-block:: scala @@ -277,7 +278,7 @@ The 'reply' method throws an 'IllegalStateException' if unable to determine what Reply using the sender reference ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If the sender is an Actor then its reference will be implicitly passed along together with the message and will end up in the 'sender: Option[ActorRef]' member field in the 'ActorRef. This means that you can use this field to send a message back to the sender. +If the sender is an Actor then its reference will be implicitly passed along together with the message and will end up in the ``sender: Option[ActorRef]`` member field in the ``ActorRef``. This means that you can use this field to send a message back to the sender. .. code-block:: scala @@ -286,7 +287,7 @@ If the sender is an Actor then its reference will be implicitly passed along tog val result = process(request) self.sender.get ! result -It's important to know that 'sender.get' will throw an exception if the 'sender' is not defined, e.g. the 'Option' is 'None'. You can check if it is defined by invoking the 'sender.isDefined' method, but a more elegant solution is to use 'foreach' which will only be executed if the sender is defined in the 'sender' member 'Option' field. If it is not, then the operation in the 'foreach' method is ignored. +It's important to know that ``sender.get`` will throw an exception if the ``sender`` is not defined, e.g. the ``Option`` is ``None``. You can check if it is defined by invoking the ``sender.isDefined`` method, but a more elegant solution is to use ``foreach`` which will only be executed if the sender is defined in the ``sender`` member ``Option`` field. If it is not, then the operation in the ``foreach`` method is ignored. .. code-block:: scala @@ -295,14 +296,14 @@ It's important to know that 'sender.get' will throw an exception if the 'sender' val result = process(request) self.sender.foreach(_ ! result) -The same pattern holds for using the 'senderFuture' in the section below. +The same pattern holds for using the ``senderFuture`` in the section below. Reply using the sender future ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If a message was sent with the '!!' or '!!!' methods, which both implements request-reply semantics using Future's, then you either have the option of replying using the 'reply' method as above. This method will then resolve the Future. But you can also get a reference to the Future directly and resolve it yourself or if you would like to store it away to resolve it later, or pass it on to some other Actor to resolve it. +If a message was sent with the ``!!`` or ``!!!`` methods, which both implements request-reply semantics using Future's, then you either have the option of replying using the ``reply`` method as above. This method will then resolve the Future. But you can also get a reference to the Future directly and resolve it yourself or if you would like to store it away to resolve it later, or pass it on to some other Actor to resolve it. -The reference to the Future resides in the 'senderFuture: Option[CompletableFuture[]]' member field in the 'ActorRef' class. +The reference to the Future resides in the ``senderFuture: Option[CompletableFuture[_]]`` member field in the ``ActorRef`` class. Here is an example of how it can be used: @@ -320,8 +321,8 @@ Here is an example of how it can be used: Reply using the channel ^^^^^^^^^^^^^^^^^^^^^^^ -If you want to have a handle to an object to whom you can reply to the message, you can use the Channel abstraction. -Simply call self.channel and then you can forward that to others, store it away or otherwise until you want to reply, which you do by 'Channel ! response': +If you want to have a handle to an object to whom you can reply to the message, you can use the ``Channel`` abstraction. +Simply call ``self.channel`` and then you can forward that to others, store it away or otherwise until you want to reply, which you do by ``Channel ! response``: .. code-block:: scala @@ -337,16 +338,16 @@ Simply call self.channel and then you can forward that to others, store it away Summary of reply semantics and options ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* self.reply(...) can be used to reply to an Actor or a Future. -* self.sender is a reference to the actor you can reply to, if it exists -* self.senderFuture is a reference to the future you can reply to, if it exists -* self.channel is a reference providing an abstraction to either self.sender or self.senderFuture if one is set, providing a single reference to store and reply to (the reference equivalent to the 'reply(...)' method). -* self.sender and self.senderFuture will never be set at the same time, as there can only be one reference to accept a reply. +* ``self.reply(...)`` can be used to reply to an ``Actor`` or a ``Future``. +* ``self.sender`` is a reference to the ``Actor`` you can reply to, if it exists +* ``self.senderFuture`` is a reference to the ``Future`` you can reply to, if it exists +* ``self.channel`` is a reference providing an abstraction to either ``self.sender`` or ``self.senderFuture`` if one is set, providing a single reference to store and reply to (the reference equivalent to the ``reply(...)`` method). +* ``self.sender`` and ``self.senderFuture`` will never be set at the same time, as there can only be one reference to accept a reply. Initial receive timeout ----------------------- -A timeout mechanism can be used to receive a message when no initial message is received within a certain time. To receive this timeout you have to set the 'receiveTimeout' property and declare a case handing the ReceiveTimeout object. +A timeout mechanism can be used to receive a message when no initial message is received within a certain time. To receive this timeout you have to set the ``receiveTimeout`` property and declare a case handing the ReceiveTimeout object. .. code-block:: scala @@ -359,42 +360,42 @@ A timeout mechanism can be used to receive a message when no initial message is throw new RuntimeException("received timeout") } -This mechanism also work for hotswapped receive functions. Every time a 'HotSwap' is sent, the receive timeout is reset and rescheduled. +This mechanism also work for hotswapped receive functions. Every time a ``HotSwap`` is sent, the receive timeout is reset and rescheduled. Starting actors --------------- -Actors are started by invoking the ‘start’ method. +Actors are started by invoking the ``start`` method. .. code-block:: scala val actor = actorOf[MyActor] actor.start -You can create and start the Actor in a oneliner like this: +You can create and start the ``Actor`` in a oneliner like this: .. code-block:: scala val actor = actorOf[MyActor].start -When you start the actor then it will automatically call the 'def preStart' callback method on the 'Actor' trait. This is an excellent place to add initialization code for the actor. +When you start the ``Actor`` then it will automatically call the ``def preStart`` callback method on the ``Actor`` trait. This is an excellent place to add initialization code for the actor. .. code-block:: scala override def preStart = { - ... // initialization code + ... // initialization code } Stopping actors --------------- -Actors are stopped by invoking the ‘stop’ method. +Actors are stopped by invoking the ``stop`` method. .. code-block:: scala actor.stop -When stop is called then a calll to the ‘def postStop’ callback method will take place. The Actor can use this callback to implement shutdown behavior. +When stop is called then a call to the ``def postStop`` callback method will take place. The ``Actor`` can use this callback to implement shutdown behavior. .. code-block:: scala @@ -408,14 +409,13 @@ You can shut down all Actors in the system by invoking: Actor.registry.shutdownAll -- PoisonPill ---------- -You can also send an actor the 'akka.actor.PoisonPill' message, which will stop the actor when the message is processed. +You can also send an actor the ``akka.actor.PoisonPill`` message, which will stop the actor when the message is processed. -If the sender is a 'Future' (e.g. the message is sent with '!!' or '!!!'), the 'Future' will be completed with an 'akka.actor.ActorKilledException("PoisonPill")'. +If the sender is a ``Future`` (e.g. the message is sent with ``!!`` or ``!!!``), the ``Future`` will be completed with an ``akka.actor.ActorKilledException("PoisonPill")``. HotSwap ------- @@ -424,12 +424,13 @@ Upgrade ^^^^^^^ Akka supports hotswapping the Actor’s message loop (e.g. its implementation) at runtime. There are two ways you can do that: -* Send a 'HotSwap' message to the Actor -* Invoke the 'become' method from within the Actor. -Both of these takes a 'ActorRef => PartialFunction[Any, Unit]' that implements the new message handler. The hotswapped code is kept in a Stack which can be pushed and popped. +* Send a ``HotSwap`` message to the Actor. +* Invoke the ``become`` method from within the Actor. -To hotswap the Actor body using the 'HotSwap' message: +Both of these takes a ``ActorRef => PartialFunction[Any, Unit]`` that implements the new message handler. The hotswapped code is kept in a Stack which can be pushed and popped. + +To hotswap the Actor body using the ``HotSwap`` message: .. code-block:: scala @@ -437,9 +438,9 @@ To hotswap the Actor body using the 'HotSwap' message: case message => self.reply("hotswapped body") }) -Using the 'HotSwap' message for hotswapping has its limitations. You can not replace it with any code that uses the Actor's 'self' reference. If you need to do that the the 'become' method is better. +Using the ``HotSwap`` message for hotswapping has its limitations. You can not replace it with any code that uses the Actor's ``self`` reference. If you need to do that the the ``become`` method is better. -To hotswap the Actor using 'become': +To hotswap the Actor using ``become``: .. code-block:: scala @@ -458,9 +459,9 @@ To hotswap the Actor using 'become': case "bar" => become(happy) } -The 'become' method is useful for many different things, but a particular nice example of it is in example where it is used to implement a Finite State Machine (FSM): `Dining Hakkers `_ +The ``become`` method is useful for many different things, but a particular nice example of it is in example where it is used to implement a Finite State Machine (FSM): `Dining Hakkers `_ -Here is another little cute example of 'become' and 'unbecome' in action: +Here is another little cute example of ``become`` and ``unbecome`` in action: .. code-block:: scala @@ -494,18 +495,18 @@ Downgrade Since the hotswapped code is pushed to a Stack you can downgrade the code as well. There are two ways you can do that: -* Send the Actor a RevertHotswap message -* Invoke the 'unbecome' method from within the Actor. +* Send the Actor a ``RevertHotswap`` message +* Invoke the ``unbecome`` method from within the Actor. -Both of these will pop the Stack and replace the Actor's implementation with the PartialFunction[Any, Unit] that is at the top of the Stack. +Both of these will pop the Stack and replace the Actor's implementation with the ``PartialFunction[Any, Unit]`` that is at the top of the Stack. -Revert the Actor body using the 'RevertHotSwap' message: +Revert the Actor body using the ``RevertHotSwap`` message: .. code-block:: scala actor ! RevertHotSwap -Revert the Actor body using the 'unbecome' method: +Revert the Actor body using the ``unbecome`` method: .. code-block:: scala @@ -516,7 +517,7 @@ Revert the Actor body using the 'unbecome' method: Killing an Actor ---------------- -You can kill an actor by sending a 'Kill' message. This will restart the actor through regular supervisor semantics. +You can kill an actor by sending a ``Kill`` message. This will restart the actor through regular supervisor semantics. Use it like this: @@ -539,7 +540,7 @@ The actor has a well-defined non-circular life-cycle. Extending Actors using PartialFunction chaining ----------------------------------------------- -A bit advanced but very useful way of defining a base message handler and then extend that, either through inheritance or delegation, is to use 'PartialFunction' 'orElse' chaining. +A bit advanced but very useful way of defining a base message handler and then extend that, either through inheritance or delegation, is to use ``PartialFunction.orElse`` chaining. In generic base Actor: @@ -561,12 +562,12 @@ In generic base Actor: In subclassing Actor: -``_ -class SpecificActor extends GenericActor { - def specificMessageHandler = { - case event: MyMsg => printf("specific: %s\n", event.subject) +.. code-block:: scala + + class SpecificActor extends GenericActor { + def specificMessageHandler = { + case event: MyMsg => printf("specific: %s\n", event.subject) + } } -} -case class MyMsg(subject: String) -``_ \ No newline at end of file + case class MyMsg(subject: String) From a81ec07bd8cea0d8541843d1b75e1fd48e4aca54 Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Mon, 11 Apr 2011 21:15:21 -0600 Subject: [PATCH 049/147] Minor fixes to Agent docs --- akka-docs/pending/agents-scala.rst | 44 +++++++++++++++--------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/akka-docs/pending/agents-scala.rst b/akka-docs/pending/agents-scala.rst index cd4c486fec..9adb9e9f81 100644 --- a/akka-docs/pending/agents-scala.rst +++ b/akka-docs/pending/agents-scala.rst @@ -9,7 +9,7 @@ Agents provide asynchronous change of individual locations. Agents are bound to While updates to Agents are asynchronous, the state of an Agent is always immediately available for reading by any thread (using ``get`` or ``apply``) without any messages. -Agents are reactive. The update actions of all Agents get interleaved amongst threads in a thread pool. At any point in time, at most one ''send'' action for each Agent is being executed. Actions dispatched to an agent from another thread will occur in the order they were sent, potentially interleaved with actions dispatched to the same agent from other sources. +Agents are reactive. The update actions of all Agents get interleaved amongst threads in a thread pool. At any point in time, at most one ``send`` action for each Agent is being executed. Actions dispatched to an agent from another thread will occur in the order they were sent, potentially interleaved with actions dispatched to the same agent from other sources. If an Agent is used within an enclosing transaction, then it will participate in that transaction. Agents are integrated with the STM - any dispatches made in a transaction are held until that transaction commits, and are discarded if it is retried or aborted. @@ -69,7 +69,7 @@ Reading an Agent's current value does not involve any message passing and happen Awaiting an Agent's value ------------------------- -It is also possible to read the value after all currently queued ``send``s have completed. You can do this with ``await``: +It is also possible to read the value after all currently queued ``send``\s have completed. You can do this with ``await``: .. code-block:: scala @@ -95,27 +95,27 @@ Agents are also monadic, allowing you to compose operations using for-comprehens Example of a monadic usage: -``_ -val agent1 = Agent(3) -val agent2 = Agent(5) +.. code-block:: scala -// uses foreach -for (value <- agent1) { - result = value + 1 -} + val agent1 = Agent(3) + val agent2 = Agent(5) -// uses map -val agent3 = - for (value <- agent1) yield value + 1 + // uses foreach + for (value <- agent1) { + result = value + 1 + } -// uses flatMap -val agent4 = for { - value1 <- agent1 - value2 <- agent2 -} yield value1 + value2 + // uses map + val agent3 = + for (value <- agent1) yield value + 1 -agent1.close -agent2.close -agent3.close -agent4.close -``_ + // uses flatMap + val agent4 = for { + value1 <- agent1 + value2 <- agent2 + } yield value1 + value2 + + agent1.close + agent2.close + agent3.close + agent4.close From 939680899137159b77536dcb4b923ae63db8e207 Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Mon, 11 Apr 2011 21:23:53 -0600 Subject: [PATCH 050/147] Documentation fixes --- akka-docs/pending/companies-using-akka.rst | 35 +++++++++++----------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/akka-docs/pending/companies-using-akka.rst b/akka-docs/pending/companies-using-akka.rst index 7186ae3deb..3832adab1b 100644 --- a/akka-docs/pending/companies-using-akka.rst +++ b/akka-docs/pending/companies-using-akka.rst @@ -2,7 +2,7 @@ Companies and Open Source projects using Akka ============================================= Production Users -================ +**************** These are some of the production Akka users that are able to talk about their use publicly. @@ -52,12 +52,12 @@ Tapad *Take for instance the process backing a page view with ads served by a real-time ad exchange auction (somewhat simplified):* -*1. A user opens a site (or app) which has ads in it.* -*2. As the page / app loads, the ad serving components fires off a request to the ad exchange (this might just be due to an image tag on the page).* -*3. The ad exchange enriches the request with any information about the current user (tracking cookies are often employed for this) and and display context information (“news article about parenting”, “blog about food” etc).* -*4. The ad exchange forwards the enriched request to all bidders registered with the ad exchange.* -*5. The bidders consider the provided user information and responds with what price they are willing to pay for this particular ad slot.* -*6. The ad exchange picks the highest bidder and ensures that the winning bidder’s ad is shown to to user.* +1. *A user opens a site (or app) which has ads in it.* +2. *As the page / app loads, the ad serving components fires off a request to the ad exchange (this might just be due to an image tag on the page).* +3. *The ad exchange enriches the request with any information about the current user (tracking cookies are often employed for this) and and display context information (“news article about parenting”, “blog about food” etc).* +4. *The ad exchange forwards the enriched request to all bidders registered with the ad exchange.* +5. *The bidders consider the provided user information and responds with what price they are willing to pay for this particular ad slot.* +6. *The ad exchange picks the highest bidder and ensures that the winning bidder’s ad is shown to to user.* *Any latency in this process directly influences user experience latency, so this has to happen really fast. All-in-all, the total time should not exceed about 100ms and most ad exchanges allow bidders to spend about 60ms (including network time) to return their bids. That leaves the ad exchange with less than 40ms to facilitate the auction. At Tapad, this happens billions of times per month / tens of thousands of times per second.* @@ -105,11 +105,9 @@ Says.US LShift ------ -*"Diffa is an open source data analysis tool that automatically establishes data differences between two or more real-time systems.* - -*Diffa will help you compare local or distributed systems for data consistency, without having to stop them running or implement manual cross-system comparisons. The interface provides you with simple visual summary of any consistency breaks and tools to investigate the issues.* - -*Diffa is the ideal tool to use to investigate where or when inconsistencies are occuring, or simply to provide confidence that your systems are running in perfect sync. It can be used operationally as an early warning system, in deployment for release verification, or in development with other enterprise diagnosis tools to help troubleshoot faults."* +* *"Diffa is an open source data analysis tool that automatically establishes data differences between two or more real-time systems.* +* Diffa will help you compare local or distributed systems for data consistency, without having to stop them running or implement manual cross-system comparisons. The interface provides you with simple visual summary of any consistency breaks and tools to investigate the issues.* +* Diffa is the ideal tool to use to investigate where or when inconsistencies are occuring, or simply to provide confidence that your systems are running in perfect sync. It can be used operationally as an early warning system, in deployment for release verification, or in development with other enterprise diagnosis tools to help troubleshoot faults."* ``_ @@ -128,7 +126,7 @@ Rocket Pack Platform ``_ Open Source Projects using Akka -=============================== +******************************* Redis client ------------ @@ -162,10 +160,11 @@ This project provides an org.apache.wicket.pageStore.IDataStore implementation t Spray ----- -//"spray is a lightweight Scala framework for building RESTful web services on top of Akka actors and Akka Mist. It sports the following main features: -* Completely asynchronous, non-blocking, actor-based request processing for efficiently handling very high numbers of concurrent connections -* Powerful, flexible and extensible internal Scala DSL for declaratively defining your web service behavior -* Immutable model of the HTTP protocol, decoupled from the underlying servlet container -* Full testability of your REST services, without the need to fire up containers or actors"// +*"spray is a lightweight Scala framework for building RESTful web services on top of Akka actors and Akka Mist. It sports the following main features:* + +* *Completely asynchronous, non-blocking, actor-based request processing for efficiently handling very high numbers of concurrent connections* +* *Powerful, flexible and extensible internal Scala DSL for declaratively defining your web service behavior* +* *Immutable model of the HTTP protocol, decoupled from the underlying servlet container* +* *Full testability of your REST services, without the need to fire up containers or actors"* ``_ From 70a5bb0d41de731dc2e812a4c9e969a10bdcb1d9 Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Mon, 11 Apr 2011 21:26:13 -0600 Subject: [PATCH 051/147] Fix section headings --- akka-docs/pending/getting-started.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/akka-docs/pending/getting-started.rst b/akka-docs/pending/getting-started.rst index 51fd1d7957..8f86f5cfca 100644 --- a/akka-docs/pending/getting-started.rst +++ b/akka-docs/pending/getting-started.rst @@ -6,18 +6,20 @@ There are several ways to download Akka. You can download the full distribution A list of each of the Akka module JARs dependencies can be found `here `_. Using a release distribution -============================ +---------------------------- Akka is split up into two different parts: + * Akka - The core modules. Reflects all the sections under 'Scala API' and 'Java API' in the navigation bar. * Akka Modules - The microkernel and add-on modules. Reflects all the sections under 'Add-on modules' in the navigation bar. Download the release you need (Akka core or Akka Modules) from ``_ and unzip it. Microkernel ------------ +^^^^^^^^^^^ The Akka Modules distribution includes the mircokernel. To run the microkernel: + * Set the AKKA_HOME environment variable to the root of the Akka distribution. * Run ``java -jar akka-modules-1.0.jar``. This will boot up the microkernel and deploy all samples applications from './deploy' dir. @@ -32,12 +34,12 @@ For example (bash shell): Now you can continue with reading the `tutorial `_ and try to build the tutorial sample project step by step. This can be a good starting point before diving into the reference documentation which can be navigated in the left sidebar. Using a build tool -================== +------------------ Akka can be used with build tools that support Maven repositories. The Akka Maven repository can be found at ``_. Using Akka with Maven ---------------------- +^^^^^^^^^^^^^^^^^^^^^ If you want to use Akka with Maven then you need to add this repository to your ``pom.xml``: @@ -60,7 +62,7 @@ Then you can add the Akka dependencies. For example, here is the dependency for Using Akka with SBT -------------------- +^^^^^^^^^^^^^^^^^^^ Akka has an SBT plugin which makes it very easy to get started with Akka and SBT. @@ -105,7 +107,7 @@ If you also want to include other Akka modules there is a convenience method: `` val akkaRedis = akkaModule("persistence-redis") Build from sources -================== +------------------ Akka uses Git and is hosted at `Github `_. @@ -115,7 +117,7 @@ Akka uses Git and is hosted at `Github `_. Continue reading the page on `how to build and run Akka `_ Need help? -========== +---------- If you have questions you can get help on the `Akka Mailing List `_. From 2f7ff68b138e2cc004142fba2214c6413f1c4907 Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Mon, 11 Apr 2011 21:29:56 -0600 Subject: [PATCH 052/147] Fix section headings --- akka-docs/pending/Home.rst | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/akka-docs/pending/Home.rst b/akka-docs/pending/Home.rst index 33bb0a08c6..73c9f31172 100644 --- a/akka-docs/pending/Home.rst +++ b/akka-docs/pending/Home.rst @@ -3,9 +3,7 @@ Akka **Simpler Scalability, Fault-Tolerance, Concurrency & Remoting through Actors** ----- We believe that writing correct concurrent, fault-tolerant and scalable applications is too hard. Most of the time it's because we are using the wrong tools and the wrong level of abstraction. Akka is here to change that. Using the Actor Model together with Software Transactional Memory we raise the abstraction level and provide a better platform to build correct concurrent and scalable applications. For fault-tolerance we adopt the "Let it crash" / "Embrace failure" model which have been used with great success in the telecom industry to build applications that self-heals, systems that never stop. Actors also provides the abstraction for transparent distribution and the basis for truly scalable and fault-tolerant applications. Akka is Open Source and available under the Apache 2 License. ----- Akka is split up into two different parts: * Akka - Reflects all the sections under 'Scala API' and 'Java API' in the navigation bar. @@ -14,10 +12,10 @@ Akka is split up into two different parts: Download from ``_ News: Akka 1.0 final is released -================================ +-------------------------------- -1.0 documentation: -================== +1.0 documentation +----------------- This documentation covers the latest release ready code in 'master' branch in the repository. If you want the documentation for the 1.0 release you can find it `here `_. @@ -30,9 +28,9 @@ You can watch the recording of the `Akka talk at JFokus in Feb 2011 `_, which gives you: -** Simple and high-level abstractions for concurrency and parallelism. -** Asynchronous, non-blocking and highly performant event-driven programming model. -** Very lightweight event-driven processes (create ~6.5 million actors on 4 G RAM). + * Simple and high-level abstractions for concurrency and parallelism. + * Asynchronous, non-blocking and highly performant event-driven programming model. + * Very lightweight event-driven processes (create ~6.5 million actors on 4 G RAM). * `Failure management `_ through supervisor hierarchies with `let-it-crash `_ semantics. Excellent for writing highly fault-tolerant systems that never stop, systems that self-heal. * `Software Transactional Memory `_ (STM). (Distributed transactions coming soon). * `Transactors `_: combine actors and STM into transactional actors. Allows you to compose atomic message flows with automatic retry and rollback. From c3e494208cfe01b4e2035e9e8be0f42df06ac842 Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Mon, 11 Apr 2011 21:37:03 -0600 Subject: [PATCH 053/147] Fixing section headings --- akka-docs/pending/http.rst | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/akka-docs/pending/http.rst b/akka-docs/pending/http.rst index 51643ad6bc..3f4399327b 100644 --- a/akka-docs/pending/http.rst +++ b/akka-docs/pending/http.rst @@ -3,8 +3,6 @@ HTTP Module stability: **SOLID** -= - When using Akkas embedded servlet container: -------------------------------------------- @@ -137,27 +135,23 @@ If you want to use akka-camel or any other modules that have their own "Bootable loader.boot(true, new BootableActorLoaderService with BootableRemoteActorService with CamelService) //<--- Important } -- - Java API: Typed Actors ---------------------- -Click `here `_ to look at a sample module for REST services with Actors in Java - -- +`Sample module for REST services with Actors in Java `_ Scala API: Actors ----------------- -Click `here `_ to look at a sample module for REST services with Actors in Scala +`Sample module for REST services with Actors in Scala `_ Using Akka with the Pinky REST/MVC framework -============================================ +-------------------------------------------- Pinky has a slick Akka integration. Read more `here `_ jetty-run in SBT -================ +---------------- If you want to use jetty-run in SBT you need to exclude the version of Jetty that is bundled in akka-http: @@ -171,19 +165,19 @@ If you want to use jetty-run in SBT you need to exclude the version of Jetty tha Mist - Lightweight Asynchronous HTTP -==================================== +------------------------------------ The *Mist* layer was developed to provide a direct connection between the servlet container and Akka actors with the goal of handling the incoming HTTP request as quickly as possible in an asynchronous manner. The motivation came from the simple desire to treat REST calls as completable futures, that is, effectively passing the request along an actor message chain to be resumed at the earliest possible time. The primary constraint was to not block any existing threads and secondarily, not create additional ones. Mist is very simple and works both with Jetty Continuations as well as with Servlet API 3.0 (tested using Jetty-8.0.0.M1). When the servlet handles a request, a message is created typed to represent the method (e.g. Get, Post, etc.), the request is suspended and the message is sent (fire-and-forget) to the *root endpoint* actor. That's it. There are no POJOs required to host the service endpoints and the request is treated as any other. The message can be resumed (completed) using a number of helper methods that set the proper HTTP response status code. Complete runnable example can be found here: ``_ Endpoints ---------- +^^^^^^^^^ Endpoints are actors that handle request messages. Minimally there must be an instance of the *RootEndpoint* and then at least one more (to implement your services). Preparations ------------- +^^^^^^^^^^^^ In order to use Mist you have to register the MistServlet in *web.xml* or do the analogous for the embedded server if running in Akka Micrkernel: @@ -210,10 +204,10 @@ Then you also have to add the following dependencies to your SBT build definitio Attention: You have to use SBT 0.7.5.RC0 or higher in order to be able to work with that Jetty version. An Example ----------- +^^^^^^^^^^ Startup -^^^^^^^ +******* In this example, we'll use the built-in *RootEndpoint* class and implement our own service from that. Here the services are started in the boot loader and attached to the top level supervisor. @@ -302,7 +296,7 @@ Finally, bind the *handleHttpRequest* function of the *Endpoint* trait to the ac } Handling requests -^^^^^^^^^^^^^^^^^ +***************** Messages are handled just as any other that are received by your actor. The servlet requests and response are not hidden and can be accessed directly as shown below. @@ -369,7 +363,7 @@ Messages are handled just as any other that are received by your actor. The serv Messages will expire according to the default timeout (specified in akka.conf). Individual messages can also be updated using the *timeout* method. One thing that may seem unexpected is that when an expired request returns to the caller, it will have a status code of OK (200). Mist will add an HTTP header to such responses to help clients, if applicable. By default, the header will be named "Async-Timeout" with a value of "expired" - both of which are configurable. Another Example - multiplexing handlers ---------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As noted above, hook functions are non-exclusive. This means multiple actors can handle the same request if desired. In this next example, the hook functions are identical (yes, the same one could have been reused) and new instances of both A and B actors will be created to handle the Post. A third mediator is inserted to coordinate the results of these actions and respond to the caller. @@ -513,15 +507,15 @@ As noted above, hook functions are non-exclusive. This means multiple actors can } Examples --------- +^^^^^^^^ Using the Akka Mist module with OAuth -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +************************************* ``_ Using the Akka Mist module with the Facebook Graph API and WebGL -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +**************************************************************** Example project using Akka Mist with the Facebook Graph API and WebGL ``_ From 39bd77ed90f4bf33fb3731077e83ee9111c990e2 Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Mon, 11 Apr 2011 21:44:00 -0600 Subject: [PATCH 054/147] Fixing section headings --- akka-docs/pending/language-bindings.rst | 4 ++-- akka-docs/pending/licenses.rst | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/akka-docs/pending/language-bindings.rst b/akka-docs/pending/language-bindings.rst index 0492108863..2f44df50e2 100644 --- a/akka-docs/pending/language-bindings.rst +++ b/akka-docs/pending/language-bindings.rst @@ -2,7 +2,7 @@ Other Language Bindings ======================= JRuby -===== +----- High level concurrency using Akka actors and JRuby. @@ -19,6 +19,6 @@ If you are using STM with JRuby then you need to unwrap the Multiverse control f end Groovy/Groovy++ -=============== +--------------- ``_ diff --git a/akka-docs/pending/licenses.rst b/akka-docs/pending/licenses.rst index 557f37a779..02a3a10cec 100644 --- a/akka-docs/pending/licenses.rst +++ b/akka-docs/pending/licenses.rst @@ -1,8 +1,10 @@ Licenses ======== +.. highlight:: text + Akka License -============ +------------ :: @@ -23,7 +25,7 @@ Akka License the License. Akka Committer License Agreement -================================ +-------------------------------- All committers have signed this CLA @@ -188,10 +190,8 @@ All committers have signed this CLA Please sign: __________________________________ Date: ________________ -= - Licenses for Dependency Libraries -================================= +--------------------------------- Each dependency and its license can be seen in the project build file (the comment on the side of each dependency): ``_ From 8b3051226ae61172d82d0c7f21bf5e825e99eda3 Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Mon, 11 Apr 2011 21:55:08 -0600 Subject: [PATCH 055/147] Fixing section headings --- .../pending/tutorial-chat-server-scala.rst | 92 +++++++++++-------- 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/akka-docs/pending/tutorial-chat-server-scala.rst b/akka-docs/pending/tutorial-chat-server-scala.rst index db1a249b6e..99159e35f2 100644 --- a/akka-docs/pending/tutorial-chat-server-scala.rst +++ b/akka-docs/pending/tutorial-chat-server-scala.rst @@ -2,9 +2,9 @@ Tutorial: write a scalable, fault-tolerant, persistent network chat server and c ============================================================================================= Introduction -============ +------------ -*The source code for this tutorial can be found [[https:*github.com/jboner/akka/blob/master/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala|here]].// +`Tutorial source code `_. Writing correct concurrent, fault-tolerant and scalable applications is too hard. Most of the time it's because we are using the wrong tools and the wrong level of abstraction. @@ -23,14 +23,14 @@ In this article we will introduce you to Akka and see how we can utilize it to b But first let's take a step back and discuss what Actors really are and what they are useful for. Actors -====== +------ `The Actor Model `_ provides a higher level of abstraction for writing concurrent and distributed systems. It alleviates the developer from having to deal with explicit locking and thread management. It makes it easier to write correct concurrent and parallel systems. Actors are really nothing new, they were defined in the 1963 paper by Carl Hewitt and have been popularized by the Erlang language which emerged in the mid 80s. It has been used by for example at Ericsson with great success to build highly concurrent and extremely reliable (99.9999999 % availability - 31 ms/year downtime) telecom systems. Actors encapsulate state and behavior into a lightweight process/thread. In a sense they are like OO objects but with a major semantic difference; they *do not* share state with any other Actor. Each Actor has its own view of the world and can only have impact on other Actors by sending messages to them. Messages are sent asynchronously and non-blocking in a so-called "fire-and-forget" manner where the Actor sends off a message to some other Actor and then do not wait for a reply but goes off doing other things or are suspended by the runtime. Each Actor has a mailbox (ordered message queue) in which incoming messages are processed one by one. Since all processing is done asynchronously and Actors do not block and consume any resources while waiting for messages, Actors tend to give very good concurrency and scalability characteristics and are excellent for building event-based systems. Creating Actors -=============== +--------------- Akka has both a `Scala API `_ and a `Java API `_. In this article we will only look at the Scala API since that is the most expressive one. The article assumes some basic Scala knowledge, but even if you don't know Scala I don't think it will not be too hard to follow along anyway. @@ -75,7 +75,7 @@ Messages are sent using the '!' operator: myActor ! "test" Sample application -================== +------------------ We will try to write a simple chat/IM system. It is client-server based and uses remote Actors to implement remote clients. Even if it is not likely that you will ever write a chat system I think that it can be a useful exercise since it uses patterns and idioms found in many other use-cases and domains. @@ -84,7 +84,7 @@ We will use many of the features of Akka along the way. In particular; Actors, f But let's start by defining the messages that will flow in our system. Creating messages -================= +----------------- It is very important that all messages that will be sent around in the system are immutable. The Actor model relies on the simple fact that no state is shared between Actors and the only way to guarantee that is to make sure we don't pass mutable state around as part of the messages. @@ -104,7 +104,7 @@ Let's now start by creating the messages that will flow in our system. As you can see with these messages we can log in and out, send a chat message and ask for and get a reply with all the messages in the chat log so far. Client: Sending messages -======================== +------------------------ Our client wraps each message send in a function, making it a bit easier to use. Here we assume that we have a reference to the chat service so we can communicate with it by sending messages. Messages are sent with the '!' operator (pronounced "bang"). This sends a message of asynchronously and do not wait for a reply. @@ -124,7 +124,7 @@ Sometimes however, there is a need for sequential logic, sending a message and w As you can see, we are using the 'Actor.remote.actorFor' to lookup the chat server on the remote node. From this call we will get a handle to the remote instance and can use it as it is local. Session: Receiving messages -=========================== +--------------------------- Now we are done with the client side and let's dig into the server code. We start by creating a user session. The session is an Actor and is defined by extending the 'Actor' trait. This trait has one abstract method that we have to define; 'receive' which implements the message handler for the Actor. @@ -151,13 +151,14 @@ If you look closely (in the code below) you will see that when passing on the 'G } Let it crash: Implementing fault-tolerance -========================================== +------------------------------------------ Akka's `approach to fault-tolerance `_; the "let it crash" model, is implemented by linking Actors. It is very different to what Java and most non-concurrency oriented languages/frameworks have adopted. It’s a way of dealing with failure that is designed for concurrent and distributed systems. If we look at concurrency first. Now let’s assume we are using non-linked Actors. Throwing an exception in concurrent code, will just simply blow up the thread that currently executes the Actor. There is no way to find out that things went wrong (apart from see the stack trace in the log). There is nothing you can do about it. Here linked Actors provide a clean way of both getting notification of the error so you know what happened, as well as the Actor that crashed, so you can do something about it. Linking Actors allow you to create sets of Actors where you can be sure that either: + * All are dead * All are alive @@ -170,7 +171,7 @@ Now let’s look at distributed Actors. As you probably know, you can’t build To sum things up, it is a very different way of thinking but a way that is very useful (if not critical) to building fault-tolerant highly concurrent and distributed applications. Supervisor hierarchies -====================== +---------------------- A supervisor is a regular Actor that is responsible for starting, stopping and monitoring its child Actors. The basic idea of a supervisor is that it should keep its child Actors alive by restarting them when necessary. This makes for a completely different view on how to write fault-tolerant servers. Instead of trying all things possible to prevent an error from happening, this approach embraces failure. It shifts the view to look at errors as something natural and something that will happen and instead of trying to prevent it; embrace it. Just "let it crash" and reset the service to a stable state through restart. @@ -182,7 +183,7 @@ Akka has two different restart strategies; All-For-One and One-For-One. The latter strategy should be used when you have a certain set of components that are coupled in some way that if one is crashing they all need to be reset to a stable state before continuing. Chat server: Supervision, Traits and more -========================================= +----------------------------------------- There are two ways you can define an Actor to be a supervisor; declaratively and dynamically. In this example we use the dynamic approach. There are two things we have to do: @@ -233,7 +234,7 @@ If you look at the 'receive' message handler function you can see that we have d Chaining partial functions like this is a great way of composing functionality in Actors. You can for example put define one default message handle handling generic messages in the base Actor and then let deriving Actors extend that functionality by defining additional message handlers. There is a section on how that is done `here `_. Session management -================== +------------------ The session management is defined in the 'SessionManagement' trait in which we implement the two abstract methods in the 'ChatServer'; 'sessionManagement' and 'shutdownSessions'. @@ -274,7 +275,7 @@ The 'shutdownSessions' function simply shuts all the sessions Actors down. That } Chat message management -======================= +----------------------- Chat message management is implemented by the 'ChatManagement' trait. It has an abstract 'HashMap' session member field with all the sessions. Since it is abstract it needs to be mixed in with someone that can provide this reference. If this dependency is not resolved when composing the final component, you will get a compilation error. @@ -308,7 +309,7 @@ It implements the 'chatManagement' function which responds to two different mess Using an Actor as a message broker, as in this example, is a very common pattern with many variations; load-balancing, master/worker, map/reduce, replication, logging etc. It becomes even more useful with remote Actors when we can use it to route messages to different nodes. STM and Transactors -=================== +------------------- Actors are excellent for solving problems where you have many independent processes that can work in isolation and only interact with other Actors through message passing. This model fits many problems. But the Actor model is unfortunately a terrible model for implementing truly shared state. E.g. when you need to have consensus and a stable view of state across many components. The classic example is the bank account where clients can deposit and withdraw, in which each operation needs to be atomic. For detailed discussion on the topic see this `presentation `_. @@ -325,7 +326,7 @@ What you get is transactional memory in which multiple Actors are allowed to rea In database terms STM gives you 'ACI' semantics; 'Atomicity', 'Consistency' and 'Isolation'. The 'D' in 'ACID'; 'Durability', you can't get with an STM since it is in memory. This however is addressed by the persistence module in Akka. Persistence: Storing the chat log -================================= +--------------------------------- Akka modules provides the possibility of taking the transactional data structures we discussed above and making them persistent. It is an extension to the STM which guarantees that it has the same semantics. @@ -340,7 +341,7 @@ They all implement persistent 'Map', 'Vector' and 'Ref'. Which can be created an val ref = MongoStorage.newRef(id) Chat storage: Backed with simple in-memory -========================================== +------------------------------------------ To keep it simple we implement the persistent storage, with a in-memory Vector, i.e. it will not be persistent. We start by creating a 'ChatStorage' trait allowing us to have multiple different storage backend. For example one in-memory and one persistent. @@ -403,7 +404,7 @@ The last thing we need to do in terms of persistence is to create a 'MemoryChatS } Composing the full Chat Service -=============================== +------------------------------- We have now created the full functionality for the chat server, all nicely decoupled into isolated and well-defined traits. Now let's bring all these traits together and compose the complete concrete 'ChatService'. @@ -428,7 +429,7 @@ We have now created the full functionality for the chat server, all nicely decou } Creating a remote server service -================================ +-------------------------------- As you can see in the section above, we are overriding the Actor's 'start' method and are starting up a remote server node by invoking 'remote.start("localhost", 2552)'. This starts up the remote node on address "localhost" and port 2552 which means that it accepts incoming messages on this address. Then we register the ChatService actor in the remote node by invoking 'remote.register("chat:service", self)'. This means that the ChatService will be available to other actors on this specific id, address and port. @@ -437,7 +438,7 @@ That's it. Were done. Now we have a, very simple, but scalable, fault-tolerant, Let's use it. Sample client chat session -========================== +-------------------------- Now let's create a simple test runner that logs in posts some messages and logs out. @@ -469,21 +470,21 @@ Now let's create a simple test runner that logs in posts some messages and logs } Sample code -=========== +----------- All this code is available as part of the Akka distribution. It resides in the './akka-samples/akka-sample-chat' module and have a 'README' file explaining how to run it. Or if you rather browse it `online `_. Run it -====== +------ Download and build Akka -# Check out Akka from ``_ -# Set 'AKKA_HOME' environment variable to the root of the Akka distribution. -# Open up a shell and step into the Akka distribution root folder. -# Build Akka by invoking: +#. Check out Akka from ``_ +#. Set 'AKKA_HOME' environment variable to the root of the Akka distribution. +#. Open up a shell and step into the Akka distribution root folder. +#. Build Akka by invoking: :: @@ -493,23 +494,38 @@ Download and build Akka Run a sample chat session 1. Fire up two shells. For each of them: -- Step down into to the root of the Akka distribution. -- Set 'export AKKA_HOME=. -- Run 'sbt console' to start up a REPL (interpreter). + + - Step down into to the root of the Akka distribution. + - Set 'export AKKA_HOME=. + - Run 'sbt console' to start up a REPL (interpreter). + 2. In the first REPL you get execute: -- scala> import sample.chat._ -- scala> import akka.actor.Actor._ -- scala> val chatService = actorOf[ChatService].start + +.. code-block:: scala + + import sample.chat._ + import akka.actor.Actor._ + val chatService = actorOf[ChatService].start + 3. In the second REPL you get execute: -- scala> import sample.chat._ -- scala> ClientRunner.run + +.. code-block:: scala + + import sample.chat._ + ClientRunner.run + 4. See the chat simulation run. + 5. Run it again to see full speed after first initialization. + 6. In the client REPL, or in a new REPL, you can also create your own client -- scala> import sample.chat._ -- scala> val myClient = new ChatClient("") -- scala> myClient.login -- scala> myClient.post("Can I join?") -- scala> println("CHAT LOG:\n\t" + myClient.chatLog.log.mkString("\n\t")) + +.. code-block:: scala + + import sample.chat._ + val myClient = new ChatClient("") + myClient.login + myClient.post("Can I join?") + println("CHAT LOG:\n\t" + myClient.chatLog.log.mkString("\n\t")) That's it. Have fun. From c308e888562fc74a9e0101bd3fde587af220f91d Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 12 Apr 2011 17:32:59 +1200 Subject: [PATCH 056/147] Remove project definitions in tutorials They get picked up by sbt when running publish, confusing the versions. Projects are defined in the main akka project. --- akka-tutorials/akka-tutorial-first/project/build.properties | 5 ----- .../akka-tutorial-first/project/plugins/Plugins.scala | 6 ------ .../akka-tutorial-second/project/build.properties | 5 ----- .../akka-tutorial-second/project/plugins/Plugins.scala | 6 ------ 4 files changed, 22 deletions(-) delete mode 100644 akka-tutorials/akka-tutorial-first/project/build.properties delete mode 100644 akka-tutorials/akka-tutorial-first/project/plugins/Plugins.scala delete mode 100644 akka-tutorials/akka-tutorial-second/project/build.properties delete mode 100644 akka-tutorials/akka-tutorial-second/project/plugins/Plugins.scala diff --git a/akka-tutorials/akka-tutorial-first/project/build.properties b/akka-tutorials/akka-tutorial-first/project/build.properties deleted file mode 100644 index f1a5103baa..0000000000 --- a/akka-tutorials/akka-tutorial-first/project/build.properties +++ /dev/null @@ -1,5 +0,0 @@ -project.organization=se.scalablesolutions.akka -project.name=Akka Tutorial 1 SBT -project.version=1.1-SNAPSHOT -build.scala.versions=2.9.0.RC1 -sbt.version=0.7.6.RC0 diff --git a/akka-tutorials/akka-tutorial-first/project/plugins/Plugins.scala b/akka-tutorials/akka-tutorial-first/project/plugins/Plugins.scala deleted file mode 100644 index 74a3d6b705..0000000000 --- a/akka-tutorials/akka-tutorial-first/project/plugins/Plugins.scala +++ /dev/null @@ -1,6 +0,0 @@ -import sbt._ - -class Plugins(info: ProjectInfo) extends PluginDefinition(info) { - val akkaRepo = "Akka Repo" at "http://akka.io/repository" - val akkaPlugin = "se.scalablesolutions.akka" % "akka-sbt-plugin" % "1.1-SNAPSHOT" -} diff --git a/akka-tutorials/akka-tutorial-second/project/build.properties b/akka-tutorials/akka-tutorial-second/project/build.properties deleted file mode 100644 index f1a5103baa..0000000000 --- a/akka-tutorials/akka-tutorial-second/project/build.properties +++ /dev/null @@ -1,5 +0,0 @@ -project.organization=se.scalablesolutions.akka -project.name=Akka Tutorial 1 SBT -project.version=1.1-SNAPSHOT -build.scala.versions=2.9.0.RC1 -sbt.version=0.7.6.RC0 diff --git a/akka-tutorials/akka-tutorial-second/project/plugins/Plugins.scala b/akka-tutorials/akka-tutorial-second/project/plugins/Plugins.scala deleted file mode 100644 index 74a3d6b705..0000000000 --- a/akka-tutorials/akka-tutorial-second/project/plugins/Plugins.scala +++ /dev/null @@ -1,6 +0,0 @@ -import sbt._ - -class Plugins(info: ProjectInfo) extends PluginDefinition(info) { - val akkaRepo = "Akka Repo" at "http://akka.io/repository" - val akkaPlugin = "se.scalablesolutions.akka" % "akka-sbt-plugin" % "1.1-SNAPSHOT" -} From 8572c6336871fed5b618d10ab1aa68258a0ea13f Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 11 Apr 2011 22:20:35 +0200 Subject: [PATCH 057/147] fixed warning --- .../src/test/java/akka/config/SupervisionConfig.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/akka-actor-tests/src/test/java/akka/config/SupervisionConfig.java b/akka-actor-tests/src/test/java/akka/config/SupervisionConfig.java index fd71c86bf1..97605a4a79 100644 --- a/akka-actor-tests/src/test/java/akka/config/SupervisionConfig.java +++ b/akka-actor-tests/src/test/java/akka/config/SupervisionConfig.java @@ -9,13 +9,15 @@ import java.util.List; import static akka.config.Supervision.*; public class SupervisionConfig { - /*Just some sample code to demonstrate the declarative supervision configuration for Java */ + /*Just some sample code to demonstrate the declarative supervision configuration for Java */ + @SuppressWarnings("unchecked") public SupervisorConfig createSupervisorConfig(List toSupervise) { ArrayList targets = new ArrayList(toSupervise.size()); for(ActorRef ref : toSupervise) { targets.add(new Supervise(ref, permanent(), true)); } - return new SupervisorConfig(new AllForOneStrategy(new Class[] { Exception.class },50,1000), targets.toArray(new Server[0])); + + return new SupervisorConfig(new AllForOneStrategy(new Class[] { Exception.class }, 50, 1000), targets.toArray(new Server[targets.size()])); } } From 97d4fc8e18f7271a90e9a9ee6cf37fdb864c0cac Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 12 Apr 2011 19:25:47 +1200 Subject: [PATCH 058/147] Adjust sleep in supervisor tree spec --- .../akka/actor/supervisor/SupervisorTreeSpec.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 cb694d7408..40203e9137 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 @@ -6,14 +6,16 @@ package akka.actor import org.scalatest.WordSpec import org.scalatest.matchers.MustMatchers +import akka.util.duration._ +import akka.testing.Testing.sleepFor import akka.dispatch.Dispatchers import akka.config.Supervision.{SupervisorConfig, OneForOneStrategy, Supervise, Permanent} import Actor._ -class SupervisorTreeSpec extends WordSpec with MustMatchers { - +class SupervisorTreeSpec extends WordSpec with MustMatchers { var log = "" case object Die + class Chainer(myId: String, a: Option[ActorRef] = None) extends Actor { self.id = myId self.lifeCycle = Permanent @@ -29,7 +31,7 @@ class SupervisorTreeSpec extends WordSpec with MustMatchers { } } - "In a 3 levels deep supervisor tree (linked in the constructor) we" should { + "In a 3 levels deep supervisor tree (linked in the constructor) we" must { "be able to kill the middle actor and see itself and its child restarted" in { log = "INIT" @@ -39,7 +41,7 @@ class SupervisorTreeSpec extends WordSpec with MustMatchers { val headActor = actorOf(new Chainer("headActor", Some(middleActor))).start middleActor ! Die - Thread.sleep(100) + sleepFor(500 millis) log must equal ("INITmiddleActorlastActor") } } From 087191f19f01fe49895ccff16d28a82ae6139404 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 12 Apr 2011 09:55:32 +0200 Subject: [PATCH 059/147] Added parens to start --- .../ActorFireForgetRequestReplySpec.scala | 10 ++-- .../scala/akka/actor/actor/ActorRefSpec.scala | 20 +++---- .../test/scala/akka/actor/actor/Bench.scala | 4 +- .../scala/akka/actor/actor/FSMActorSpec.scala | 6 +-- .../akka/actor/actor/FSMTimingSpec.scala | 2 +- .../akka/actor/actor/ForwardActorSpec.scala | 10 ++-- .../scala/akka/actor/actor/HotSwapSpec.scala | 8 +-- .../akka/actor/actor/ReceiveTimeoutSpec.scala | 10 ++-- .../supervisor/RestartStrategySpec.scala | 10 ++-- .../supervisor/SupervisorHierarchySpec.scala | 4 +- .../actor/supervisor/SupervisorMiscSpec.scala | 8 +-- .../actor/supervisor/SupervisorSpec.scala | 20 +++---- .../actor/supervisor/SupervisorTreeSpec.scala | 6 +-- .../akka/actor/supervisor/Ticket669Spec.scala | 4 +- .../scala/akka/dispatch/ActorModelSpec.scala | 16 +++--- ...rBasedEventDrivenDispatcherActorSpec.scala | 16 +++--- ...BasedEventDrivenDispatcherActorsSpec.scala | 4 +- ...ventDrivenWorkStealingDispatcherSpec.scala | 12 ++--- .../test/scala/akka/dispatch/FutureSpec.scala | 54 +++++++++---------- .../akka/dispatch/MailboxConfigSpec.scala | 2 +- .../dispatch/PriorityDispatcherSpec.scala | 2 +- .../akka/dispatch/ThreadBasedActorSpec.scala | 8 +-- .../scala/akka/misc/ActorRegistrySpec.scala | 46 ++++++++-------- .../test/scala/akka/misc/SchedulerSpec.scala | 8 +-- .../test/scala/akka/routing/RoutingSpec.scala | 42 +++++++-------- .../CallingThreadDispatcherModelSpec.scala | 4 +- .../scala/akka/ticket/Ticket703Spec.scala | 2 +- .../src/main/scala/akka/actor/Actor.scala | 16 +++--- .../src/main/scala/akka/actor/ActorRef.scala | 24 ++++----- .../main/scala/akka/actor/Supervisor.scala | 4 +- .../main/scala/akka/actor/UntypedActor.scala | 2 +- .../main/scala/akka/dataflow/DataFlow.scala | 8 +-- .../main/scala/akka/event/EventHandler.scala | 2 +- .../remoteinterface/RemoteInterface.scala | 14 ++--- .../src/main/scala/akka/routing/Routing.scala | 6 +-- .../scala/akka/util/ListenerManagement.scala | 2 +- akka-docs/pending/actor-registry-scala.rst | 2 +- akka-docs/pending/actors-scala.rst | 12 ++--- akka-docs/pending/fault-tolerance-scala.rst | 2 +- akka-docs/pending/fsm-scala.rst | 2 +- akka-docs/pending/http.rst | 10 ++-- akka-docs/pending/remote-actors-scala.rst | 6 +-- akka-docs/pending/routing-scala.rst | 20 +++---- akka-docs/pending/serialization-scala.rst | 12 ++--- akka-docs/pending/stm-scala.rst | 4 +- akka-docs/pending/testkit-example.rst | 8 +-- akka-docs/pending/transactors-scala.rst | 4 +- .../pending/tutorial-chat-server-scala.rst | 8 +-- akka-http/src/test/scala/SecuritySpec.scala | 2 +- .../remote/BootableRemoteActorService.scala | 2 +- .../remote/netty/NettyRemoteSupport.scala | 6 +-- .../test/scala/remote/AkkaRemoteTest.scala | 2 +- .../ClientInitiatedRemoteActorSpec.scala | 16 +++--- .../remote/OptimizedLocalScopedSpec.scala | 2 +- .../RemoteErrorHandlingNetworkTest.scala | 2 +- .../scala/remote/RemoteSupervisorSpec.scala | 22 ++++---- .../ServerInitiatedRemoteActorSample.scala | 2 +- .../ServerInitiatedRemoteActorSpec.scala | 4 +- .../remote/UnOptimizedLocalScopedSpec.scala | 2 +- .../SerializableTypeClassActorSpec.scala | 28 +++++----- .../scala/serialization/Ticket435Spec.scala | 6 +-- .../UntypedActorSerializationSpec.scala | 12 ++--- .../src/main/scala/Ants.scala | 4 +- akka-samples/akka-sample-chat/README | 2 +- .../src/main/scala/ChatServer.scala | 8 +-- .../main/scala/DiningHakkersOnBecome.scala | 4 +- .../src/main/scala/DiningHakkersOnFsm.scala | 4 +- .../ClientManagedRemoteActorSample.scala | 2 +- .../src/main/scala/akka/agent/Agent.scala | 4 +- .../transactor/CoordinatedIncrementSpec.scala | 4 +- .../scala/transactor/FickleFriendsSpec.scala | 4 +- .../scala/transactor/TransactorSpec.scala | 6 +-- .../src/main/scala/akka/testkit/TestKit.scala | 4 +- .../src/main/scala/Pi.scala | 6 +-- .../main/scala/akka/actor/TypedActor.scala | 6 +-- .../config/TypedActorGuiceConfigurator.scala | 2 +- .../typed-actor/TypedActorLifecycleSpec.scala | 2 +- .../actor/typed-actor/TypedActorSpec.scala | 6 +-- 78 files changed, 341 insertions(+), 341 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/ActorFireForgetRequestReplySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/ActorFireForgetRequestReplySpec.scala index ea31d57287..f76c328573 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/ActorFireForgetRequestReplySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/ActorFireForgetRequestReplySpec.scala @@ -70,23 +70,23 @@ class ActorFireForgetRequestReplySpec extends WordSpec with MustMatchers with Be "An Actor" must { "reply to bang message using reply" in { - val replyActor = actorOf[ReplyActor].start - val senderActor = actorOf(new SenderActor(replyActor)).start + val replyActor = actorOf[ReplyActor].start() + val senderActor = actorOf(new SenderActor(replyActor)).start() senderActor ! "Init" state.finished.await state.s must be ("Reply") } "reply to bang message using implicit sender" in { - val replyActor = actorOf[ReplyActor].start - val senderActor = actorOf(new SenderActor(replyActor)).start + val replyActor = actorOf[ReplyActor].start() + val senderActor = actorOf(new SenderActor(replyActor)).start() senderActor ! "InitImplicit" state.finished.await state.s must be ("ReplyImplicit") } "should shutdown crashed temporary actor" in { - val actor = actorOf[CrashingTemporaryActor].start + val actor = actorOf[CrashingTemporaryActor].start() actor.isRunning must be (true) actor ! "Die" state.finished.await 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 c70142f743..cb8036ee24 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 @@ -25,11 +25,11 @@ object ActorRefSpec { def receive = { case "complexRequest" => { replyTo = self.channel - val worker = Actor.actorOf[WorkerActor].start + val worker = Actor.actorOf[WorkerActor].start() worker ! "work" } case "complexRequest2" => - val worker = Actor.actorOf[WorkerActor].start + val worker = Actor.actorOf[WorkerActor].start() worker ! self.channel case "workDone" => replyTo ! "complexReply" case "simpleRequest" => self.reply("simpleReply") @@ -85,16 +85,16 @@ class ActorRefSpec extends WordSpec with MustMatchers { val a = Actor.actorOf(new Actor { val nested = new Actor { def receive = { case _ => } } def receive = { case _ => } - }).start + }).start() fail("shouldn't get here") } } "support nested actorOfs" in { val a = Actor.actorOf(new Actor { - val nested = Actor.actorOf(new Actor { def receive = { case _ => } }).start + val nested = Actor.actorOf(new Actor { def receive = { case _ => } }).start() def receive = { case _ => self reply nested } - }).start + }).start() val nested = (a !! "any").get.asInstanceOf[ActorRef] a must not be null @@ -103,8 +103,8 @@ class ActorRefSpec extends WordSpec with MustMatchers { } "support reply via channel" in { - val serverRef = Actor.actorOf[ReplyActor].start - val clientRef = Actor.actorOf(new SenderActor(serverRef)).start + val serverRef = Actor.actorOf[ReplyActor].start() + val clientRef = Actor.actorOf(new SenderActor(serverRef)).start() clientRef ! "complex" clientRef ! "simple" @@ -134,7 +134,7 @@ class ActorRefSpec extends WordSpec with MustMatchers { case null => self reply_? "null" } } - ).start + ).start() val ffive: Future[String] = ref !!! 5 val fnull: Future[String] = ref !!! null @@ -163,12 +163,12 @@ class ActorRefSpec extends WordSpec with MustMatchers { override def preRestart(reason: Throwable) = latch.countDown override def postRestart(reason: Throwable) = latch.countDown } - ).start + ).start() self link ref protected def receive = { case "sendKill" => ref ! Kill } - }).start + }).start() boss ! "sendKill" latch.await(5, TimeUnit.SECONDS) must be === true diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/Bench.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/Bench.scala index f043f5c92e..df12fc390d 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/Bench.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/Bench.scala @@ -28,7 +28,7 @@ object Chameneos { class Chameneo(var mall: ActorRef, var colour: Colour, cid: Int) extends Actor { var meetings = 0 - self.start + self.start() mall ! Meet(self, colour) def receive = { @@ -110,7 +110,7 @@ object Chameneos { def run { // System.setProperty("akka.config", "akka.conf") Chameneos.start = System.currentTimeMillis - actorOf(new Mall(1000000, 4)).start + actorOf(new Mall(1000000, 4)).start() Thread.sleep(10000) println("Elapsed: " + (end - start)) } diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/FSMActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/FSMActorSpec.scala index 31d09c8ebf..9489c1e64f 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/FSMActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/FSMActorSpec.scala @@ -104,12 +104,12 @@ class FSMActorSpec extends WordSpec with MustMatchers { "unlock the lock" in { // lock that locked after being open for 1 sec - val lock = Actor.actorOf(new Lock("33221", 1 second)).start + val lock = Actor.actorOf(new Lock("33221", 1 second)).start() val transitionTester = Actor.actorOf(new Actor { def receive = { case Transition(_, _, _) => transitionCallBackLatch.open case CurrentState(_, Locked) => initialStateLatch.open - }}).start + }}).start() lock ! SubscribeTransitionCallBack(transitionTester) initialStateLatch.await @@ -137,7 +137,7 @@ class FSMActorSpec extends WordSpec with MustMatchers { case "world" => answerLatch.open case Bye => lock ! "bye" } - }).start + }).start() tester ! Hello answerLatch.await diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTimingSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTimingSpec.scala index 2ea9525a3d..606ac280b7 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTimingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTimingSpec.scala @@ -11,7 +11,7 @@ class FSMTimingSpec extends WordSpec with MustMatchers with TestKit { import FSMTimingSpec._ import FSM._ - val fsm = Actor.actorOf(new StateMachine(testActor)).start + val fsm = Actor.actorOf(new StateMachine(testActor)).start() fsm ! SubscribeTransitionCallBack(testActor) expectMsg(200 millis, CurrentState(fsm, Initial)) diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/ForwardActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/ForwardActorSpec.scala index b52b7c6b14..ad965f838d 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/ForwardActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/ForwardActorSpec.scala @@ -32,7 +32,7 @@ object ForwardActorSpec { class ForwardActor extends Actor { val receiverActor = actorOf[ReceiverActor] - receiverActor.start + receiverActor.start() def receive = { case "SendBang" => receiverActor.forward("SendBang") case "SendBangBang" => receiverActor.forward("SendBangBang") @@ -41,7 +41,7 @@ object ForwardActorSpec { class BangSenderActor extends Actor { val forwardActor = actorOf[ForwardActor] - forwardActor.start + forwardActor.start() forwardActor ! "SendBang" def receive = { case _ => {} @@ -51,7 +51,7 @@ object ForwardActorSpec { class BangBangSenderActor extends Actor { val latch = TestLatch() val forwardActor = actorOf[ForwardActor] - forwardActor.start + forwardActor.start() (forwardActor !! "SendBangBang") match { case Some(_) => latch.countDown case None => {} @@ -72,7 +72,7 @@ class ForwardActorSpec extends WordSpec with MustMatchers { .forwardActor.actor.asInstanceOf[ForwardActor] .receiverActor.actor.asInstanceOf[ReceiverActor] .latch - senderActor.start + senderActor.start() latch.await ForwardState.sender must not be (null) senderActor.toString must be (ForwardState.sender.get.toString) @@ -80,7 +80,7 @@ class ForwardActorSpec extends WordSpec with MustMatchers { "forward actor reference when invoking forward on bang bang" in { val senderActor = actorOf[BangBangSenderActor] - senderActor.start + senderActor.start() val latch = senderActor.actor.asInstanceOf[BangBangSenderActor].latch latch.await } diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/HotSwapSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/HotSwapSpec.scala index e985d383b6..eb020eebac 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/HotSwapSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/HotSwapSpec.scala @@ -21,7 +21,7 @@ class HotSwapSpec extends WordSpec with MustMatchers { @volatile var _log = "" val a = actorOf( new Actor { def receive = { case _ => _log += "default" } - }).start + }).start() a ! HotSwap( self => { case _ => _log += "swapped" @@ -46,7 +46,7 @@ class HotSwapSpec extends WordSpec with MustMatchers { barrier.await }) } - }).start + }).start() a ! "init" barrier.await @@ -69,7 +69,7 @@ class HotSwapSpec extends WordSpec with MustMatchers { _log += "init" barrier.await } - }).start + }).start() a ! "init" barrier.await @@ -123,7 +123,7 @@ class HotSwapSpec extends WordSpec with MustMatchers { }) barrier.await } - }).start + }).start() a ! "init" barrier.await diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/ReceiveTimeoutSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/ReceiveTimeoutSpec.scala index c48fbd6f9d..10952265a2 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/ReceiveTimeoutSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/ReceiveTimeoutSpec.scala @@ -28,7 +28,7 @@ class ReceiveTimeoutSpec extends WordSpec with MustMatchers { protected def receive = { case ReceiveTimeout => timeoutLatch.open } - }).start + }).start() timeoutLatch.await timeoutActor.stop @@ -43,7 +43,7 @@ class ReceiveTimeoutSpec extends WordSpec with MustMatchers { protected def receive = { case ReceiveTimeout => timeoutLatch.open } - }).start + }).start() timeoutLatch.await @@ -68,7 +68,7 @@ class ReceiveTimeoutSpec extends WordSpec with MustMatchers { case Tick => () case ReceiveTimeout => timeoutLatch.open } - }).start + }).start() timeoutActor ! Tick @@ -91,7 +91,7 @@ class ReceiveTimeoutSpec extends WordSpec with MustMatchers { timeoutLatch.open self.receiveTimeout = None } - }).start + }).start() timeoutActor ! Tick @@ -107,7 +107,7 @@ class ReceiveTimeoutSpec extends WordSpec with MustMatchers { protected def receive = { case ReceiveTimeout => timeoutLatch.open } - }).start + }).start() timeoutLatch.awaitTimeout(1 second) // timeout expected timeoutActor.stop diff --git a/akka-actor-tests/src/test/scala/akka/actor/supervisor/RestartStrategySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/supervisor/RestartStrategySpec.scala index 741cd7a49e..59dd191bc6 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/supervisor/RestartStrategySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/supervisor/RestartStrategySpec.scala @@ -25,7 +25,7 @@ class RestartStrategySpec extends JUnitSuite { val boss = actorOf(new Actor{ self.faultHandler = OneForOneStrategy(List(classOf[Throwable]), Some(2), Some(1000)) protected def receive = { case _ => () } - }).start + }).start() val restartLatch = new StandardLatch val secondRestartLatch = new StandardLatch @@ -80,7 +80,7 @@ class RestartStrategySpec extends JUnitSuite { val boss = actorOf(new Actor{ self.faultHandler = OneForOneStrategy(List(classOf[Throwable]), None, None) protected def receive = { case _ => () } - }).start + }).start() val countDownLatch = new CountDownLatch(100) @@ -107,7 +107,7 @@ class RestartStrategySpec extends JUnitSuite { val boss = actorOf(new Actor{ self.faultHandler = OneForOneStrategy(List(classOf[Throwable]), Some(2), Some(500)) protected def receive = { case _ => () } - }).start + }).start() val restartLatch = new StandardLatch val secondRestartLatch = new StandardLatch @@ -168,7 +168,7 @@ class RestartStrategySpec extends JUnitSuite { val boss = actorOf(new Actor{ self.faultHandler = OneForOneStrategy(List(classOf[Throwable]), Some(2), None) protected def receive = { case _ => () } - }).start + }).start() val restartLatch = new StandardLatch val secondRestartLatch = new StandardLatch @@ -230,7 +230,7 @@ class RestartStrategySpec extends JUnitSuite { protected def receive = { case m:MaximumNumberOfRestartsWithinTimeRangeReached => maxNoOfRestartsLatch.open } - }).start + }).start() val slave = actorOf(new Actor{ diff --git a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala index 0fee4b77b5..4a0581477e 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala @@ -40,7 +40,7 @@ class SupervisorHierarchySpec extends JUnitSuite { self.faultHandler = OneForOneStrategy(List(classOf[Throwable]), 5, 1000) protected def receive = { case _ => () } - }).start + }).start() val manager = actorOf(new CountDownActor(countDown)) boss.startLink(manager) @@ -67,7 +67,7 @@ class SupervisorHierarchySpec extends JUnitSuite { case MaximumNumberOfRestartsWithinTimeRangeReached(_, _, _, _) => countDown.countDown } - }).start + }).start() boss.startLink(crasher) crasher ! Exit(crasher, new FireWorkerException("Fire the worker!")) diff --git a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorMiscSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorMiscSpec.scala index 78547b4d19..0db5cc9db4 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorMiscSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorMiscSpec.scala @@ -23,7 +23,7 @@ class SupervisorMiscSpec extends WordSpec with MustMatchers { case "kill" => throw new Exception("killed") case _ => println("received unknown message") } - }).start + }).start() val actor2 = Actor.actorOf(new Actor { self.dispatcher = Dispatchers.newThreadBasedDispatcher(self) @@ -33,7 +33,7 @@ class SupervisorMiscSpec extends WordSpec with MustMatchers { case "kill" => throw new Exception("killed") case _ => println("received unknown message") } - }).start + }).start() val actor3 = Actor.actorOf(new Actor { self.dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher("test").build @@ -43,7 +43,7 @@ class SupervisorMiscSpec extends WordSpec with MustMatchers { case "kill" => throw new Exception("killed") case _ => println("received unknown message") } - }).start + }).start() val actor4 = Actor.actorOf(new Actor { self.dispatcher = Dispatchers.newThreadBasedDispatcher(self) @@ -53,7 +53,7 @@ class SupervisorMiscSpec extends WordSpec with MustMatchers { case "kill" => throw new Exception("killed") case _ => println("received unknown message") } - }).start + }).start() val sup = Supervisor( SupervisorConfig( 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 131cdeee8f..253570f576 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 @@ -72,7 +72,7 @@ object SupervisorSpec { // ===================================================== def temporaryActorAllForOne = { - val temporaryActor = actorOf[TemporaryActor].start + val temporaryActor = actorOf[TemporaryActor].start() val supervisor = Supervisor( SupervisorConfig( @@ -86,7 +86,7 @@ object SupervisorSpec { } def singleActorAllForOne = { - val pingpong = actorOf[PingPongActor].start + val pingpong = actorOf[PingPongActor].start() val supervisor = Supervisor( SupervisorConfig( @@ -100,7 +100,7 @@ object SupervisorSpec { } def singleActorOneForOne = { - val pingpong = actorOf[PingPongActor].start + val pingpong = actorOf[PingPongActor].start() val supervisor = Supervisor( SupervisorConfig( @@ -114,9 +114,9 @@ object SupervisorSpec { } def multipleActorsAllForOne = { - val pingpong1 = actorOf[PingPongActor].start - val pingpong2 = actorOf[PingPongActor].start - val pingpong3 = actorOf[PingPongActor].start + val pingpong1 = actorOf[PingPongActor].start() + val pingpong2 = actorOf[PingPongActor].start() + val pingpong3 = actorOf[PingPongActor].start() val supervisor = Supervisor( SupervisorConfig( @@ -138,9 +138,9 @@ object SupervisorSpec { } def multipleActorsOneForOne = { - val pingpong1 = actorOf[PingPongActor].start - val pingpong2 = actorOf[PingPongActor].start - val pingpong3 = actorOf[PingPongActor].start + val pingpong1 = actorOf[PingPongActor].start() + val pingpong2 = actorOf[PingPongActor].start() + val pingpong3 = actorOf[PingPongActor].start() val supervisor = Supervisor( SupervisorConfig( @@ -209,7 +209,7 @@ class SupervisorSpec extends WordSpec with MustMatchers with BeforeAndAfterEach "A supervisor" must { "not restart programmatically linked temporary actor" in { - val master = actorOf[Master].start + val master = actorOf[Master].start() intercept[RuntimeException] { master !! (Die, TimeoutMillis) 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 40203e9137..d298b2c930 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 @@ -36,9 +36,9 @@ class SupervisorTreeSpec extends WordSpec with MustMatchers { "be able to kill the middle actor and see itself and its child restarted" in { log = "INIT" - val lastActor = actorOf(new Chainer("lastActor")).start - val middleActor = actorOf(new Chainer("middleActor", Some(lastActor))).start - val headActor = actorOf(new Chainer("headActor", Some(middleActor))).start + val lastActor = actorOf(new Chainer("lastActor")).start() + val middleActor = actorOf(new Chainer("middleActor", Some(lastActor))).start() + val headActor = actorOf(new Chainer("headActor", Some(middleActor))).start() middleActor ! Die sleepFor(500 millis) 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 54c8179152..6b43201c3f 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 @@ -19,7 +19,7 @@ class Ticket669Spec extends WordSpec with MustMatchers with BeforeAndAfterAll { "A supervised actor with lifecycle PERMANENT" should { "be able to reply on failure during preRestart" in { val latch = new CountDownLatch(1) - val sender = Actor.actorOf(new Sender(latch)).start + val sender = Actor.actorOf(new Sender(latch)).start() val supervised = Actor.actorOf[Supervised] val supervisor = Supervisor(SupervisorConfig( @@ -33,7 +33,7 @@ class Ticket669Spec extends WordSpec with MustMatchers with BeforeAndAfterAll { "be able to reply on failure during postStop" in { val latch = new CountDownLatch(1) - val sender = Actor.actorOf(new Sender(latch)).start + val sender = Actor.actorOf(new Sender(latch)).start() val supervised = Actor.actorOf[Supervised] val supervisor = Supervisor(SupervisorConfig( 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 e875ac87e0..37688631c8 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala @@ -202,7 +202,7 @@ abstract class ActorModelSpec extends JUnitSuite { implicit val dispatcher = newInterceptedDispatcher val a = newTestActor assertDispatcher(dispatcher)(starts = 0, stops = 0) - a.start + a.start() assertDispatcher(dispatcher)(starts = 1, stops = 0) a.stop await(dispatcher.stops.get == 1)(withinMs = dispatcher.timeoutMs * 5) @@ -222,7 +222,7 @@ abstract class ActorModelSpec extends JUnitSuite { implicit val dispatcher = newInterceptedDispatcher val a = newTestActor val start,oneAtATime = new CountDownLatch(1) - a.start + a.start() a ! CountDown(start) assertCountDown(start, Testing.testTime(3000), "Should process first message within 3 seconds") @@ -242,7 +242,7 @@ abstract class ActorModelSpec extends JUnitSuite { implicit val dispatcher = newInterceptedDispatcher val a = newTestActor val counter = new CountDownLatch(200) - a.start + a.start() def start = spawn { for (i <- 1 to 20) { a ! WaitAck(1, counter) } } for (i <- 1 to 10) { start } @@ -254,13 +254,13 @@ abstract class ActorModelSpec extends JUnitSuite { def spawn(f : => Unit) = { val thread = new Thread { override def run { f } } - thread.start + thread.start() thread } @Test def dispatcherShouldProcessMessagesInParallel: Unit = { implicit val dispatcher = newInterceptedDispatcher - val a, b = newTestActor.start + val a, b = newTestActor.start() val aStart,aStop,bParallel = new CountDownLatch(1) a ! Meet(aStart,aStop) @@ -278,7 +278,7 @@ abstract class ActorModelSpec extends JUnitSuite { @Test def dispatcherShouldSuspendAndResumeAFailingNonSupervisedPermanentActor { implicit val dispatcher = newInterceptedDispatcher - val a = newTestActor.start + val a = newTestActor.start() val done = new CountDownLatch(1) a ! Restart a ! CountDown(done) @@ -290,7 +290,7 @@ abstract class ActorModelSpec extends JUnitSuite { @Test def dispatcherShouldNotProcessMessagesForASuspendedActor { implicit val dispatcher = newInterceptedDispatcher - val a = newTestActor.start + val a = newTestActor.start() val done = new CountDownLatch(1) dispatcher.suspend(a) a ! CountDown(done) @@ -313,7 +313,7 @@ abstract class ActorModelSpec extends JUnitSuite { def flood(num: Int) { val cachedMessage = CountDownNStop(new CountDownLatch(num)) (1 to num) foreach { - _ => newTestActor.start ! cachedMessage + _ => newTestActor.start() ! cachedMessage } assertCountDown(cachedMessage.latch, Testing.testTime(10000), "Should process " + num + " countdowns") } diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala index e9b34c17d3..10a3bbc67f 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala @@ -35,28 +35,28 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { private val unit = TimeUnit.MILLISECONDS @Test def shouldSendOneWay = { - val actor = actorOf[OneWayTestActor].start + val actor = actorOf[OneWayTestActor].start() val result = actor ! "OneWay" assert(OneWayTestActor.oneWay.await(1, TimeUnit.SECONDS)) actor.stop } @Test def shouldSendReplySync = { - val actor = actorOf[TestActor].start + val actor = actorOf[TestActor].start() val result = (actor !! ("Hello", 10000)).as[String] assert("World" === result.get) actor.stop } @Test def shouldSendReplyAsync = { - val actor = actorOf[TestActor].start + val actor = actorOf[TestActor].start() val result = actor !! "Hello" assert("World" === result.get.asInstanceOf[String]) actor.stop } @Test def shouldSendReceiveException = { - val actor = actorOf[TestActor].start + val actor = actorOf[TestActor].start() try { actor !! "Failure" fail("Should have thrown an exception") @@ -80,7 +80,7 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { new Actor { self.dispatcher = throughputDispatcher def receive = { case "sabotage" => works.set(false) } - }).start + }).start() val slowOne = actorOf( new Actor { @@ -89,7 +89,7 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { case "hogexecutor" => start.await case "ping" => if (works.get) latch.countDown } - }).start + }).start() slowOne ! "hogexecutor" (1 to 100) foreach { _ => slowOne ! "ping"} @@ -116,7 +116,7 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { new Actor { self.dispatcher = throughputDispatcher def receive = { case "ping" => if(works.get) latch.countDown; self.stop } - }).start + }).start() val slowOne = actorOf( new Actor { @@ -125,7 +125,7 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { case "hogexecutor" => ready.countDown; start.await case "ping" => works.set(false); self.stop } - }).start + }).start() slowOne ! "hogexecutor" slowOne ! "ping" diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala index 66a02e0d33..e797319a83 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala @@ -37,8 +37,8 @@ class ExecutorBasedEventDrivenDispatcherActorsSpec extends JUnitSuite with MustM @Test def slowActorShouldntBlockFastActor { val sFinished = new CountDownLatch(50) val fFinished = new CountDownLatch(10) - val s = actorOf(new SlowActor(sFinished)).start - val f = actorOf(new FastActor(fFinished)).start + val s = actorOf(new SlowActor(sFinished)).start() + val f = actorOf(new FastActor(fFinished)).start() // send a lot of stuff to s for (i <- 1 to 50) { diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala index 2085ed66a0..25780cdaf9 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala @@ -58,8 +58,8 @@ class ExecutorBasedEventDrivenWorkStealingDispatcherSpec extends JUnitSuite with @Test def fastActorShouldStealWorkFromSlowActor { val finishedCounter = new CountDownLatch(110) - val slow = actorOf(new DelayableActor("slow", 50, finishedCounter)).start - val fast = actorOf(new DelayableActor("fast", 10, finishedCounter)).start + val slow = actorOf(new DelayableActor("slow", 50, finishedCounter)).start() + val fast = actorOf(new DelayableActor("fast", 10, finishedCounter)).start() var sentToFast = 0 @@ -98,9 +98,9 @@ class ExecutorBasedEventDrivenWorkStealingDispatcherSpec extends JUnitSuite with val first = actorOf[FirstActor] val second = actorOf[SecondActor] - first.start + first.start() intercept[IllegalActorStateException] { - second.start + second.start() } } @@ -108,9 +108,9 @@ class ExecutorBasedEventDrivenWorkStealingDispatcherSpec extends JUnitSuite with val parent = actorOf[ParentActor] val child = actorOf[ChildActor] - parent.start + parent.start() intercept[IllegalActorStateException] { - child.start + child.start() } } } 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 a946713c3d..359f658709 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala @@ -38,7 +38,7 @@ class FutureSpec extends JUnitSuite { @Test def shouldActorReplyResultThroughExplicitFuture { val actor = actorOf[TestActor] - actor.start + actor.start() val future = actor !!! "Hello" future.await assert(future.result.isDefined) @@ -48,7 +48,7 @@ class FutureSpec extends JUnitSuite { @Test def shouldActorReplyExceptionThroughExplicitFuture { val actor = actorOf[TestActor] - actor.start + actor.start() val future = actor !!! "Failure" future.await assert(future.exception.isDefined) @@ -57,8 +57,8 @@ class FutureSpec extends JUnitSuite { } @Test def shouldFutureCompose { - val actor1 = actorOf[TestActor].start - val actor2 = actorOf(new Actor { def receive = { case s: String => self reply s.toUpperCase } } ).start + val actor1 = actorOf[TestActor].start() + val actor2 = actorOf(new Actor { def receive = { case s: String => self reply s.toUpperCase } } ).start() val future1 = actor1 !!! "Hello" flatMap ((s: String) => actor2 !!! s) val future2 = actor1 !!! "Hello" flatMap (actor2 !!! (_: String)) val future3 = actor1 !!! "Hello" flatMap (actor2 !!! (_: Int)) @@ -70,8 +70,8 @@ class FutureSpec extends JUnitSuite { } @Test def shouldFutureComposePatternMatch { - val actor1 = actorOf[TestActor].start - val actor2 = actorOf(new Actor { def receive = { case s: String => self reply s.toUpperCase } } ).start + val actor1 = actorOf[TestActor].start() + val actor2 = actorOf(new Actor { def receive = { case s: String => self reply s.toUpperCase } } ).start() val future1 = actor1 !!! "Hello" collect { case (s: String) => s } flatMap (actor2 !!! _) val future2 = actor1 !!! "Hello" collect { case (n: Int) => n } flatMap (actor2 !!! _) assert(Some(Right("WORLD")) === future1.await.value) @@ -86,7 +86,7 @@ class FutureSpec extends JUnitSuite { case s: String => self reply s.length case i: Int => self reply (i * 2).toString } - }).start + }).start() val future0 = actor !!! "Hello" @@ -115,7 +115,7 @@ class FutureSpec extends JUnitSuite { case Req(s: String) => self reply Res(s.length) case Req(i: Int) => self reply Res((i * 2).toString) } - }).start + }).start() val future1 = for { a <- actor !!! Req("Hello") collect { case Res(x: Int) => x } @@ -135,8 +135,8 @@ class FutureSpec extends JUnitSuite { } @Test def shouldFutureAwaitEitherLeft = { - val actor1 = actorOf[TestActor].start - val actor2 = actorOf[TestActor].start + val actor1 = actorOf[TestActor].start() + val actor2 = actorOf[TestActor].start() val future1 = actor1 !!! "Hello" val future2 = actor2 !!! "NoReply" val result = Futures.awaitEither(future1, future2) @@ -147,8 +147,8 @@ class FutureSpec extends JUnitSuite { } @Test def shouldFutureAwaitEitherRight = { - val actor1 = actorOf[TestActor].start - val actor2 = actorOf[TestActor].start + val actor1 = actorOf[TestActor].start() + val actor2 = actorOf[TestActor].start() val future1 = actor1 !!! "NoReply" val future2 = actor2 !!! "Hello" val result = Futures.awaitEither(future1, future2) @@ -159,8 +159,8 @@ class FutureSpec extends JUnitSuite { } @Test def shouldFutureAwaitOneLeft = { - val actor1 = actorOf[TestActor].start - val actor2 = actorOf[TestActor].start + val actor1 = actorOf[TestActor].start() + val actor2 = actorOf[TestActor].start() val future1 = actor1 !!! "NoReply" val future2 = actor2 !!! "Hello" val result = Futures.awaitOne(List(future1, future2)) @@ -171,8 +171,8 @@ class FutureSpec extends JUnitSuite { } @Test def shouldFutureAwaitOneRight = { - val actor1 = actorOf[TestActor].start - val actor2 = actorOf[TestActor].start + val actor1 = actorOf[TestActor].start() + val actor2 = actorOf[TestActor].start() val future1 = actor1 !!! "Hello" val future2 = actor2 !!! "NoReply" val result = Futures.awaitOne(List(future1, future2)) @@ -183,8 +183,8 @@ class FutureSpec extends JUnitSuite { } @Test def shouldFutureAwaitAll = { - val actor1 = actorOf[TestActor].start - val actor2 = actorOf[TestActor].start + val actor1 = actorOf[TestActor].start() + val actor2 = actorOf[TestActor].start() val future1 = actor1 !!! "Hello" val future2 = actor2 !!! "Hello" Futures.awaitAll(List(future1, future2)) @@ -202,7 +202,7 @@ class FutureSpec extends JUnitSuite { @Test def shouldFuturesAwaitMapHandleNonEmptySequence { val latches = (1 to 3) map (_ => new StandardLatch) - val actors = latches map (latch => actorOf(new TestDelayActor(latch)).start) + val actors = latches map (latch => actorOf(new TestDelayActor(latch)).start()) val futures = actors map (actor => (actor.!!![String]("Hello"))) latches foreach { _.open } @@ -213,7 +213,7 @@ class FutureSpec extends JUnitSuite { val actors = (1 to 10).toList map { _ => actorOf(new Actor { def receive = { case (add: Int, wait: Int) => Thread.sleep(wait); self reply_? add } - }).start + }).start() } def futures = actors.zipWithIndex map { case (actor: ActorRef, idx: Int) => actor.!!![Int]((idx, idx * 200 )) } assert(Futures.fold(0)(futures)(_ + _).awaitBlocking.result.get === 45) @@ -223,7 +223,7 @@ class FutureSpec extends JUnitSuite { val actors = (1 to 10).toList map { _ => actorOf(new Actor { def receive = { case (add: Int, wait: Int) => Thread.sleep(wait); self reply_? add } - }).start + }).start() } def futures = actors.zipWithIndex map { case (actor: ActorRef, idx: Int) => actor.!!![Int]((idx, idx * 200 )) } assert(futures.foldLeft(Future(0))((fr, fa) => for (r <- fr; a <- fa) yield (r + a)).awaitBlocking.result.get === 45) @@ -238,7 +238,7 @@ class FutureSpec extends JUnitSuite { if (add == 6) throw new IllegalArgumentException("shouldFoldResultsWithException: expected") self reply_? add } - }).start + }).start() } def futures = actors.zipWithIndex map { case (actor: ActorRef, idx: Int) => actor.!!![Int]((idx, idx * 100 )) } assert(Futures.fold(0)(futures)(_ + _).awaitBlocking.exception.get.getMessage === "shouldFoldResultsWithException: expected") @@ -252,7 +252,7 @@ class FutureSpec extends JUnitSuite { val actors = (1 to 10).toList map { _ => actorOf(new Actor { def receive = { case (add: Int, wait: Int) => Thread.sleep(wait); self reply_? add } - }).start + }).start() } def futures = actors.zipWithIndex map { case (actor: ActorRef, idx: Int) => actor.!!![Int]((idx, idx * 200 )) } assert(Futures.reduce(futures)(_ + _).awaitBlocking.result.get === 45) @@ -267,7 +267,7 @@ class FutureSpec extends JUnitSuite { if (add == 6) throw new IllegalArgumentException("shouldFoldResultsWithException: expected") self reply_? add } - }).start + }).start() } def futures = actors.zipWithIndex map { case (actor: ActorRef, idx: Int) => actor.!!![Int]((idx, idx * 100 )) } assert(Futures.reduce(futures)(_ + _).awaitBlocking.exception.get.getMessage === "shouldFoldResultsWithException: expected") @@ -283,7 +283,7 @@ class FutureSpec extends JUnitSuite { val actors = (1 to 10).toList map { _ => actorOf(new Actor { def receive = { case (add: Int, wait: Boolean, latch: StandardLatch) => if (wait) latch.await; self reply_? add } - }).start + }).start() } def futures = actors.zipWithIndex map { case (actor: ActorRef, idx: Int) => actor.!!![Int]((idx, idx >= 5, latch)) } @@ -299,7 +299,7 @@ class FutureSpec extends JUnitSuite { @Test def receiveShouldExecuteOnComplete { val latch = new StandardLatch - val actor = actorOf[TestActor].start + val actor = actorOf[TestActor].start() actor !!! "Hello" receive { case "World" => latch.open } assert(latch.tryAwait(5, TimeUnit.SECONDS)) actor.stop @@ -313,7 +313,7 @@ class FutureSpec extends JUnitSuite { self reply counter counter += 2 } - }).start + }).start() val oddFutures: List[Future[Int]] = List.fill(100)(oddActor !!! 'GetNext) assert(Futures.sequence(oddFutures).get.sum === 10000) diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/MailboxConfigSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/MailboxConfigSpec.scala index 5e08708037..9ddbfdc332 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/MailboxConfigSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/MailboxConfigSpec.scala @@ -95,7 +95,7 @@ abstract class MailboxSpec extends case e: Throwable => result.completeWithException(e) } }) - t.start + t.start() result } 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 383cf63f48..f256715b8c 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/PriorityDispatcherSpec.scala @@ -36,7 +36,7 @@ class PriorityDispatcherSpec extends WordSpec with MustMatchers { case i: Int => acc = i :: acc case 'Result => self reply_? acc } - }).start + }).start() dispatcher.suspend(actor) //Make sure the actor isn't treating any messages, let it buffer the incoming messages diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala index eee135ebab..6201c7951e 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala @@ -33,28 +33,28 @@ class ThreadBasedActorSpec extends JUnitSuite { def receive = { case "OneWay" => oneWay.countDown } - }).start + }).start() val result = actor ! "OneWay" assert(oneWay.await(1, TimeUnit.SECONDS)) actor.stop } @Test def shouldSendReplySync = { - val actor = actorOf[TestActor].start + val actor = actorOf[TestActor].start() val result = (actor !! ("Hello", 10000)).as[String] assert("World" === result.get) actor.stop } @Test def shouldSendReplyAsync = { - val actor = actorOf[TestActor].start + val actor = actorOf[TestActor].start() val result = actor !! "Hello" assert("World" === result.get.asInstanceOf[String]) actor.stop } @Test def shouldSendReceiveException = { - val actor = actorOf[TestActor].start + val actor = actorOf[TestActor].start() try { actor !! "Failure" fail("Should have thrown an exception") diff --git a/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala b/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala index 09a23dbc5c..2eb4159411 100644 --- a/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala @@ -35,7 +35,7 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldGetActorByIdFromActorRegistry { Actor.registry.shutdownAll val actor = actorOf[TestActor] - actor.start + actor.start() val actors = Actor.registry.actorsFor("MyID") assert(actors.size === 1) assert(actors.head.actor.isInstanceOf[TestActor]) @@ -47,7 +47,7 @@ class ActorRegistrySpec extends JUnitSuite { Actor.registry.shutdownAll val actor = actorOf[TestActor] val uuid = actor.uuid - actor.start + actor.start() val actorOrNone = Actor.registry.actorFor(uuid) assert(actorOrNone.isDefined) assert(actorOrNone.get.uuid === uuid) @@ -57,7 +57,7 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldGetActorByClassFromActorRegistry { Actor.registry.shutdownAll val actor = actorOf[TestActor] - actor.start + actor.start() val actors = Actor.registry.actorsFor(classOf[TestActor]) assert(actors.size === 1) assert(actors.head.actor.isInstanceOf[TestActor]) @@ -68,7 +68,7 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldGetActorByManifestFromActorRegistry { Actor.registry.shutdownAll val actor = actorOf[TestActor] - actor.start + actor.start() val actors = Actor.registry.actorsFor[TestActor] assert(actors.size === 1) assert(actors.head.actor.isInstanceOf[TestActor]) @@ -79,7 +79,7 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldFindThingsFromActorRegistry { Actor.registry.shutdownAll val actor = actorOf[TestActor] - actor.start + actor.start() val found = Actor.registry.find({ case a: ActorRef if a.actor.isInstanceOf[TestActor] => a }) assert(found.isDefined) assert(found.get.actor.isInstanceOf[TestActor]) @@ -90,9 +90,9 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldGetActorsByIdFromActorRegistry { Actor.registry.shutdownAll val actor1 = actorOf[TestActor] - actor1.start + actor1.start() val actor2 = actorOf[TestActor] - actor2.start + actor2.start() val actors = Actor.registry.actorsFor("MyID") assert(actors.size === 2) assert(actors.head.actor.isInstanceOf[TestActor]) @@ -106,9 +106,9 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldGetActorsByClassFromActorRegistry { Actor.registry.shutdownAll val actor1 = actorOf[TestActor] - actor1.start + actor1.start() val actor2 = actorOf[TestActor] - actor2.start + actor2.start() val actors = Actor.registry.actorsFor(classOf[TestActor]) assert(actors.size === 2) assert(actors.head.actor.isInstanceOf[TestActor]) @@ -122,9 +122,9 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldGetActorsByManifestFromActorRegistry { Actor.registry.shutdownAll val actor1 = actorOf[TestActor] - actor1.start + actor1.start() val actor2 = actorOf[TestActor] - actor2.start + actor2.start() val actors = Actor.registry.actorsFor[TestActor] assert(actors.size === 2) assert(actors.head.actor.isInstanceOf[TestActor]) @@ -139,9 +139,9 @@ class ActorRegistrySpec extends JUnitSuite { Actor.registry.shutdownAll val actor1 = actorOf[TestActor] - actor1.start + actor1.start() val actor2 = actorOf[TestActor2] - actor2.start + actor2.start() val actorsForAcotrTestActor = Actor.registry.actorsFor[TestActor] assert(actorsForAcotrTestActor.size === 1) @@ -166,9 +166,9 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldGetAllActorsFromActorRegistry { Actor.registry.shutdownAll val actor1 = actorOf[TestActor] - actor1.start + actor1.start() val actor2 = actorOf[TestActor] - actor2.start + actor2.start() val actors = Actor.registry.actors assert(actors.size === 2) assert(actors.head.actor.isInstanceOf[TestActor]) @@ -182,9 +182,9 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldGetResponseByAllActorsInActorRegistryWhenInvokingForeach { Actor.registry.shutdownAll val actor1 = actorOf[TestActor] - actor1.start + actor1.start() val actor2 = actorOf[TestActor] - actor2.start + actor2.start() record = "" Actor.registry.foreach(actor => actor !! "ping") assert(record === "pongpong") @@ -195,9 +195,9 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldShutdownAllActorsInActorRegistry { Actor.registry.shutdownAll val actor1 = actorOf[TestActor] - actor1.start + actor1.start() val actor2 = actorOf[TestActor] - actor2.start + actor2.start() Actor.registry.shutdownAll assert(Actor.registry.actors.size === 0) } @@ -205,9 +205,9 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldRemoveUnregisterActorInActorRegistry { Actor.registry.shutdownAll val actor1 = actorOf[TestActor] - actor1.start + actor1.start() val actor2 = actorOf[TestActor] - actor2.start + actor2.start() assert(Actor.registry.actors.size === 2) Actor.registry.unregister(actor1) assert(Actor.registry.actors.size === 1) @@ -227,10 +227,10 @@ class ActorRegistrySpec extends JUnitSuite { val barrier = new CyclicBarrier(3) def mkThread(actors: Iterable[ActorRef]) = new Thread { - this.start + this.start() override def run { barrier.await - actors foreach { _.start } + actors foreach { _.start() } latch.countDown } } diff --git a/akka-actor-tests/src/test/scala/akka/misc/SchedulerSpec.scala b/akka-actor-tests/src/test/scala/akka/misc/SchedulerSpec.scala index 79b09d49d1..a81dec43f2 100644 --- a/akka-actor-tests/src/test/scala/akka/misc/SchedulerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/misc/SchedulerSpec.scala @@ -22,7 +22,7 @@ class SchedulerSpec extends JUnitSuite { val countDownLatch = new CountDownLatch(3) val tickActor = actorOf(new Actor { def receive = { case Tick => countDownLatch.countDown } - }).start + }).start() // run every 50 millisec Scheduler.schedule(tickActor, Tick, 0, 50, TimeUnit.MILLISECONDS) @@ -42,7 +42,7 @@ class SchedulerSpec extends JUnitSuite { val countDownLatch = new CountDownLatch(3) val tickActor = actorOf(new Actor { def receive = { case Tick => countDownLatch.countDown } - }).start + }).start() // run every 50 millisec Scheduler.scheduleOnce(tickActor, Tick, 50, TimeUnit.MILLISECONDS) Scheduler.scheduleOnce( () => countDownLatch.countDown, 50, TimeUnit.MILLISECONDS) @@ -61,7 +61,7 @@ class SchedulerSpec extends JUnitSuite { val ticks = new CountDownLatch(1000) val actor = actorOf(new Actor { def receive = { case Ping => ticks.countDown } - }).start + }).start() val numActors = Actor.registry.actors.length (1 to 1000).foreach( _ => Scheduler.scheduleOnce(actor,Ping,1,TimeUnit.MILLISECONDS) ) assert(ticks.await(10,TimeUnit.SECONDS)) @@ -77,7 +77,7 @@ class SchedulerSpec extends JUnitSuite { val actor = actorOf(new Actor { def receive = { case Ping => ticks.countDown } - }).start + }).start() (1 to 10).foreach { i => val future = Scheduler.scheduleOnce(actor,Ping,1,TimeUnit.SECONDS) 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 975ef1ae52..550def5312 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala @@ -25,18 +25,18 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers case `testMsg1` => self.reply(3) case `testMsg2` => self.reply(7) } - } ).start + } ).start() val t2 = actorOf( new Actor() { def receive = { case `testMsg3` => self.reply(11) } - }).start + }).start() val d = dispatcherActor { case `testMsg1`|`testMsg2` => t1 case `testMsg3` => t2 - }.start + }.start() val result = for { a <- (d !! (testMsg1, 5000)).as[Int] @@ -53,8 +53,8 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers @Test def testLogger = { val msgs = new java.util.concurrent.ConcurrentSkipListSet[Any] val latch = new CountDownLatch(2) - val t1 = actorOf(new Actor { def receive = { case _ => } }).start - val l = loggerActor(t1,(x) => { msgs.add(x); latch.countDown }).start + val t1 = actorOf(new Actor { def receive = { case _ => } }).start() + val l = loggerActor(t1,(x) => { msgs.add(x); latch.countDown }).start() val foo : Any = "foo" val bar : Any = "bar" l ! foo @@ -76,7 +76,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers t1ProcessedCount.incrementAndGet latch.countDown } - }).start + }).start() val t2ProcessedCount = new AtomicInteger(0) val t2 = actorOf(new Actor { @@ -84,7 +84,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers case x => t2ProcessedCount.incrementAndGet latch.countDown } - }).start + }).start() val d = loadBalancerActor(new SmallestMailboxFirstIterator(t1 :: t2 :: Nil)) for (i <- 1 to 500) d ! i val done = latch.await(10,TimeUnit.SECONDS) @@ -102,7 +102,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers case "foo" => gossip("bar") } }) - i.start + i.start() def newListener = actorOf(new Actor { def receive = { @@ -111,7 +111,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers latch.countDown case "foo" => foreachListener.countDown } - }).start + }).start() val a1 = newListener val a2 = newListener @@ -142,28 +142,28 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers case `testMsg1` => self.reply(3) case `testMsg2` => self.reply(7) } - } ).start + } ).start() val t2 = actorOf( new Actor() { def receive = { case `testMsg1` => self.reply(3) case `testMsg2` => self.reply(7) } - } ).start + } ).start() val t3 = actorOf( new Actor() { def receive = { case `testMsg1` => self.reply(3) case `testMsg2` => self.reply(7) } - } ).start + } ).start() val t4 = actorOf( new Actor() { def receive = { case `testMsg1` => self.reply(3) case `testMsg2` => self.reply(7) } - } ).start + } ).start() val d1 = loadBalancerActor(new SmallestMailboxFirstIterator(t1 :: t2 :: Nil)) val d2 = loadBalancerActor(new CyclicIterator[ActorRef](t3 :: t4 :: Nil)) @@ -213,9 +213,9 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers def receive = { case "success" => successes.countDown } - }).start) + }).start()) - val pool = actorOf(new TestPool).start + val pool = actorOf(new TestPool).start() pool ! "a" pool ! "b" @@ -253,7 +253,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers } } }) - }).start + }).start() try { (for(count <- 1 to 500) yield actorPool.!!![String]("Test", 20000)) foreach { @@ -299,7 +299,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers // // first message should create the minimum number of delgates // - val pool = actorOf(new TestPool).start + val pool = actorOf(new TestPool).start() pool ! 1 (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be (2) @@ -370,7 +370,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers def receive = _route } - val pool = actorOf(new TestPool).start + val pool = actorOf(new TestPool).start() var loops = 0 def loop(t:Int) = { @@ -433,7 +433,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers def receive = _route } - val pool1 = actorOf(new TestPool1).start + val pool1 = actorOf(new TestPool1).start() pool1 ! "a" pool1 ! "b" var done = latch.await(1,TimeUnit.SECONDS) @@ -465,7 +465,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers latch = new CountDownLatch(2) delegates clear - val pool2 = actorOf(new TestPool2).start + val pool2 = actorOf(new TestPool2).start() pool2 ! "a" pool2 ! "b" done = latch.await(1, TimeUnit.SECONDS) @@ -514,7 +514,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers // // put some pressure on the pool // - val pool = actorOf(new TestPool).start + val pool = actorOf(new TestPool).start() for (m <- 0 to 10) pool ! 250 Thread.sleep(5) val z = (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size diff --git a/akka-actor-tests/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala b/akka-actor-tests/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala index 22e16abdd9..2aade68fed 100644 --- a/akka-actor-tests/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala @@ -14,9 +14,9 @@ class CallingThreadDispatcherModelSpec extends ActorModelSpec { def flood(num: Int) { val cachedMessage = CountDownNStop(new CountDownLatch(num)) - val keeper = newTestActor.start + val keeper = newTestActor.start() (1 to num) foreach { - _ => newTestActor.start ! cachedMessage + _ => newTestActor.start() ! cachedMessage } keeper.stop assertCountDown(cachedMessage.latch,10000, "Should process " + num + " countdowns") 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 48dddfe634..3648d0ab45 100644 --- a/akka-actor-tests/src/test/scala/akka/ticket/Ticket703Spec.scala +++ b/akka-actor-tests/src/test/scala/akka/ticket/Ticket703Spec.scala @@ -27,7 +27,7 @@ class Ticket703Spec extends WordSpec with MustMatchers { self.reply_?("Response") } }) - }).start + }).start() (actorPool.!!![String]("Ping", 7000)).await.result must be === Some("Response") } } diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index db06a04418..a34753141d 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -132,13 +132,13 @@ object Actor extends ListenerManagement { *
    *   import Actor._
    *   val actor = actorOf[MyActor]
-   *   actor.start
+   *   actor.start()
    *   actor ! message
    *   actor.stop
    * 
* You can create and start the actor in one statement like this: *
-   *   val actor = actorOf[MyActor].start
+   *   val actor = actorOf[MyActor].start()
    * 
*/ def actorOf[T <: Actor : Manifest]: ActorRef = actorOf(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) @@ -148,13 +148,13 @@ object Actor extends ListenerManagement { *
    *   import Actor._
    *   val actor = actorOf(classOf[MyActor])
-   *   actor.start
+   *   actor.start()
    *   actor ! message
    *   actor.stop
    * 
* You can create and start the actor in one statement like this: *
-   *   val actor = actorOf(classOf[MyActor]).start
+   *   val actor = actorOf(classOf[MyActor]).start()
    * 
*/ def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(() => { @@ -176,13 +176,13 @@ object Actor extends ListenerManagement { *
    *   import Actor._
    *   val actor = actorOf(new MyActor)
-   *   actor.start
+   *   actor.start()
    *   actor ! message
    *   actor.stop
    * 
* You can create and start the actor in one statement like this: *
-   *   val actor = actorOf(new MyActor).start
+   *   val actor = actorOf(new MyActor).start()
    * 
*/ def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory, None) @@ -219,7 +219,7 @@ object Actor extends ListenerManagement { def receive = { case Spawn => try { body } finally { self.stop } } - }).start ! Spawn + }).start() ! Spawn } /** * Implicitly converts the given Option[Any] to a AnyOptionAsTypedOption which offers the method as[T] @@ -366,7 +366,7 @@ trait Actor { /** * User overridable callback. *

- * Is called when an Actor is started by invoking 'actor.start'. + * Is called when an Actor is started by invoking 'actor.start()'. */ def preStart {} diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index cb28a07407..88a00eaa03 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -60,14 +60,14 @@ abstract class Channel[T] { * import Actor._ * * val actor = actorOf[MyActor] - * actor.start + * actor.start() * actor ! message * actor.stop * * * You can also create and start actors like this: *

- *   val actor = actorOf[MyActor].start
+ *   val actor = actorOf[MyActor].start()
  * 
* * Here is an example on how to create an actor with a non-default constructor. @@ -75,7 +75,7 @@ abstract class Channel[T] { * import Actor._ * * val actor = actorOf(new MyActor(...)) - * actor.start + * actor.start() * actor ! message * actor.stop * @@ -761,7 +761,7 @@ class LocalActorRef private[akka] ( */ def startLink(actorRef: ActorRef): Unit = guard.withGuard { link(actorRef) - actorRef.start + actorRef.start() } /** @@ -770,7 +770,7 @@ class LocalActorRef private[akka] ( * To be invoked from within the actor itself. */ def spawn(clazz: Class[_ <: Actor]): ActorRef = - Actor.actorOf(clazz).start + Actor.actorOf(clazz).start() /** * Atomically create (from actor class), start and make an actor remote. @@ -781,7 +781,7 @@ class LocalActorRef private[akka] ( ensureRemotingEnabled val ref = Actor.remote.actorOf(clazz, hostname, port) ref.timeout = timeout - ref.start + ref.start() } /** @@ -792,7 +792,7 @@ class LocalActorRef private[akka] ( def spawnLink(clazz: Class[_ <: Actor]): ActorRef = { val actor = spawn(clazz) link(actor) - actor.start + actor.start() actor } @@ -806,7 +806,7 @@ class LocalActorRef private[akka] ( val actor = Actor.remote.actorOf(clazz, hostname, port) actor.timeout = timeout link(actor) - actor.start + actor.start() actor } @@ -1296,7 +1296,7 @@ trait ScalaActorRef extends ActorRefShared { ref: ActorRef => def !(message: Any)(implicit sender: Option[ActorRef] = None): Unit = { if (isRunning) postMessageToMailbox(message, sender) else throw new ActorInitializationException( - "Actor has not been started, you need to invoke 'actor.start' before using it") + "Actor has not been started, you need to invoke 'actor.start()' before using it") } /** @@ -1327,7 +1327,7 @@ trait ScalaActorRef extends ActorRefShared { ref: ActorRef => } future.resultOrException } else throw new ActorInitializationException( - "Actor has not been started, you need to invoke 'actor.start' before using it") + "Actor has not been started, you need to invoke 'actor.start()' before using it") } /** @@ -1342,7 +1342,7 @@ trait ScalaActorRef extends ActorRefShared { ref: ActorRef => def !!![T](message: Any, timeout: Long = this.timeout)(implicit sender: Option[ActorRef] = None): Future[T] = { if (isRunning) postMessageToMailboxAndCreateFutureResultWithTimeout[T](message, timeout, sender, None) else throw new ActorInitializationException( - "Actor has not been started, you need to invoke 'actor.start' before using it") + "Actor has not been started, you need to invoke 'actor.start()' before using it") } /** @@ -1356,7 +1356,7 @@ trait ScalaActorRef extends ActorRefShared { ref: ActorRef => postMessageToMailboxAndCreateFutureResultWithTimeout(message, timeout, sender.get.sender, sender.get.senderFuture) else postMessageToMailbox(message, sender.get.sender) - } else throw new ActorInitializationException("Actor has not been started, you need to invoke 'actor.start' before using it") + } else throw new ActorInitializationException("Actor has not been started, you need to invoke 'actor.start()' before using it") } /** diff --git a/akka-actor/src/main/scala/akka/actor/Supervisor.scala b/akka-actor/src/main/scala/akka/actor/Supervisor.scala index bb08bcdf80..e3f59d3777 100644 --- a/akka-actor/src/main/scala/akka/actor/Supervisor.scala +++ b/akka-actor/src/main/scala/akka/actor/Supervisor.scala @@ -106,7 +106,7 @@ sealed class Supervisor(handler: FaultHandlingStrategy) { private val _childActors = new ConcurrentHashMap[String, List[ActorRef]] private val _childSupervisors = new CopyOnWriteArrayList[Supervisor] - private[akka] val supervisor = actorOf(new SupervisorActor(handler)).start + private[akka] val supervisor = actorOf(new SupervisorActor(handler)).start() def uuid = supervisor.uuid @@ -131,7 +131,7 @@ sealed class Supervisor(handler: FaultHandlingStrategy) { servers.map(server => server match { case Supervise(actorRef, lifeCycle, registerAsRemoteService) => - actorRef.start + actorRef.start() val className = actorRef.actor.getClass.getName val currentActors = { val list = _childActors.get(className) diff --git a/akka-actor/src/main/scala/akka/actor/UntypedActor.scala b/akka-actor/src/main/scala/akka/actor/UntypedActor.scala index a8cea099f6..35e3746145 100644 --- a/akka-actor/src/main/scala/akka/actor/UntypedActor.scala +++ b/akka-actor/src/main/scala/akka/actor/UntypedActor.scala @@ -86,7 +86,7 @@ abstract class UntypedActor extends Actor { /** * User overridable callback. *

- * Is called when an Actor is started by invoking 'actor.start'. + * Is called when an Actor is started by invoking 'actor.start()'. */ override def preStart {} diff --git a/akka-actor/src/main/scala/akka/dataflow/DataFlow.scala b/akka-actor/src/main/scala/akka/dataflow/DataFlow.scala index 72fbbaaeb2..0eb28a4da2 100644 --- a/akka-actor/src/main/scala/akka/dataflow/DataFlow.scala +++ b/akka-actor/src/main/scala/akka/dataflow/DataFlow.scala @@ -40,14 +40,14 @@ object DataFlow { * Executes the supplied function in another thread. */ def thread[A <: AnyRef, R <: AnyRef](body: A => R) = - actorOf(new ReactiveEventBasedThread(body)).start + actorOf(new ReactiveEventBasedThread(body)).start() /** * JavaAPI. * Executes the supplied Function in another thread. */ def thread[A <: AnyRef, R <: AnyRef](body: Function[A,R]) = - actorOf(new ReactiveEventBasedThread(body.apply)).start + actorOf(new ReactiveEventBasedThread(body.apply)).start() private class ReactiveEventBasedThread[A <: AnyRef, T <: AnyRef](body: A => T) extends Actor { @@ -101,7 +101,7 @@ object DataFlow { } } - private[this] val in = actorOf(new In(this)).start + private[this] val in = actorOf(new In(this)).start() /** * Sets the value of this variable (if unset) with the value of the supplied variable. @@ -143,7 +143,7 @@ object DataFlow { */ def apply(): T = { value.get getOrElse { - val out = actorOf(new Out(this)).start + val out = actorOf(new Out(this)).start() val result = try { blockedReaders offer out diff --git a/akka-actor/src/main/scala/akka/event/EventHandler.scala b/akka-actor/src/main/scala/akka/event/EventHandler.scala index 87b6462750..9d53b57787 100644 --- a/akka-actor/src/main/scala/akka/event/EventHandler.scala +++ b/akka-actor/src/main/scala/akka/event/EventHandler.scala @@ -208,7 +208,7 @@ object EventHandler extends ListenerManagement { defaultListeners foreach { listenerName => try { ReflectiveAccess.getClassFor[Actor](listenerName) map { clazz => - addListener(Actor.actorOf(clazz).start) + addListener(Actor.actorOf(clazz).start()) } } catch { case e: Exception => diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index e9e4168995..157280dc9d 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -137,7 +137,7 @@ case class CannotInstantiateRemoteExceptionDueToRemoteProtocolParsingErrorExcept abstract class RemoteSupport extends ListenerManagement with RemoteServerModule with RemoteClientModule { lazy val eventHandler: ActorRef = { - val handler = Actor.actorOf[RemoteEventHandler].start + val handler = Actor.actorOf[RemoteEventHandler].start() // add the remote client and server listener that pipes the events to the event handler system addListener(handler) handler @@ -157,13 +157,13 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule *

    *   import Actor._
    *   val actor = actorOf(classOf[MyActor],"www.akka.io", 2552)
-   *   actor.start
+   *   actor.start()
    *   actor ! message
    *   actor.stop
    * 
* You can create and start the actor in one statement like this: *
-   *   val actor = actorOf(classOf[MyActor],"www.akka.io", 2552).start
+   *   val actor = actorOf(classOf[MyActor],"www.akka.io", 2552).start()
    * 
*/ @deprecated("Will be removed after 1.1") @@ -176,13 +176,13 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule *
    *   import Actor._
    *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552)
-   *   actor.start
+   *   actor.start()
    *   actor ! message
    *   actor.stop
    * 
* You can create and start the actor in one statement like this: *
-   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start
+   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start()
    * 
*/ @deprecated("Will be removed after 1.1") @@ -204,13 +204,13 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule *
    *   import Actor._
    *   val actor = actorOf[MyActor]("www.akka.io",2552)
-   *   actor.start
+   *   actor.start()
    *   actor ! message
    *   actor.stop
    * 
* You can create and start the actor in one statement like this: *
-   *   val actor = actorOf[MyActor]("www.akka.io",2552).start
+   *   val actor = actorOf[MyActor]("www.akka.io",2552).start()
    * 
*/ @deprecated("Will be removed after 1.1") diff --git a/akka-actor/src/main/scala/akka/routing/Routing.scala b/akka-actor/src/main/scala/akka/routing/Routing.scala index 2e041a4e35..d31653a2fb 100644 --- a/akka-actor/src/main/scala/akka/routing/Routing.scala +++ b/akka-actor/src/main/scala/akka/routing/Routing.scala @@ -37,7 +37,7 @@ object Routing { def loadBalancerActor(actors: => InfiniteIterator[ActorRef]): ActorRef = actorOf(new Actor with LoadBalancer { val seq = actors - }).start + }).start() /** * Creates a Dispatcher given a routing and a message-transforming function. @@ -46,14 +46,14 @@ object Routing { actorOf(new Actor with Dispatcher { override def transform(msg: Any) = msgTransformer(msg) def routes = routing - }).start + }).start() /** * Creates a Dispatcher given a routing. */ def dispatcherActor(routing: PF[Any, ActorRef]): ActorRef = actorOf(new Actor with Dispatcher { def routes = routing - }).start + }).start() /** * Creates an actor that pipes all incoming messages to diff --git a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala index 916fac9c6a..09224a7029 100644 --- a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala +++ b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala @@ -27,7 +27,7 @@ trait ListenerManagement { * The listener is started by this method if manageLifeCycleOfListeners yields true. */ def addListener(listener: ActorRef) { - if (manageLifeCycleOfListeners) listener.start + if (manageLifeCycleOfListeners) listener.start() listeners add listener } diff --git a/akka-docs/pending/actor-registry-scala.rst b/akka-docs/pending/actor-registry-scala.rst index d135c6e5b8..2f98c6d8a9 100644 --- a/akka-docs/pending/actor-registry-scala.rst +++ b/akka-docs/pending/actor-registry-scala.rst @@ -103,5 +103,5 @@ The above actor can be added as listener of registry events: import akka.actor._ import akka.actor.Actor._ - val listener = actorOf[RegistryListener].start + val listener = actorOf[RegistryListener].start() registry.addListener(listener) diff --git a/akka-docs/pending/actors-scala.rst b/akka-docs/pending/actors-scala.rst index f756420de8..17af9dc168 100644 --- a/akka-docs/pending/actors-scala.rst +++ b/akka-docs/pending/actors-scala.rst @@ -41,7 +41,7 @@ Creating Actors .. code-block:: scala val myActor = Actor.actorOf[MyActor] - myActor.start + myActor.start() Normally you would want to import the ``actorOf`` method like this: @@ -57,7 +57,7 @@ You can also start it in the same statement: .. code-block:: scala - val myActor = actorOf[MyActor].start + val myActor = actorOf[MyActor].start() The call to ``actorOf`` returns an instance of ``ActorRef``. This is a handle to the ``Actor`` instance which you can use to interact with the ``Actor``. The ``ActorRef`` is immutable and has a one to one relationship with the Actor it represents. The ``ActorRef`` is also serializable and network-aware. This means that you can serialize it, send it over the wire and use it on a remote host and it will still be representing the same Actor on the original node, across the network. @@ -70,7 +70,7 @@ Here is an example: .. code-block:: scala - val a = actorOf(new MyActor(..)).start // allows passing in arguments into the MyActor constructor + val a = actorOf(new MyActor(..)).start() // allows passing in arguments into the MyActor constructor Running a block of code asynchronously ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -370,13 +370,13 @@ Actors are started by invoking the ``start`` method. .. code-block:: scala val actor = actorOf[MyActor] - actor.start + actor.start() You can create and start the ``Actor`` in a oneliner like this: .. code-block:: scala - val actor = actorOf[MyActor].start + val actor = actorOf[MyActor].start() When you start the ``Actor`` then it will automatically call the ``def preStart`` callback method on the ``Actor`` trait. This is an excellent place to add initialization code for the actor. @@ -478,7 +478,7 @@ Here is another little cute example of ``become`` and ``unbecome`` in action: } } - val swap = actorOf[Swapper].start + val swap = actorOf[Swapper].start() swap ! Swap // prints Hi swap ! Swap // prints Ho diff --git a/akka-docs/pending/fault-tolerance-scala.rst b/akka-docs/pending/fault-tolerance-scala.rst index 4e1cae6aa2..279e69b849 100644 --- a/akka-docs/pending/fault-tolerance-scala.rst +++ b/akka-docs/pending/fault-tolerance-scala.rst @@ -336,7 +336,7 @@ Here is an example: victimActorRef, maxNrOfRetries, withinTimeRange, lastExceptionCausingRestart) => ... // handle the error situation } - }).start + }).start() You will also get this log warning similar to this: diff --git a/akka-docs/pending/fsm-scala.rst b/akka-docs/pending/fsm-scala.rst index d9f3690b24..7b2d5affa2 100644 --- a/akka-docs/pending/fsm-scala.rst +++ b/akka-docs/pending/fsm-scala.rst @@ -198,7 +198,7 @@ To use the Lock you can run a small program like this: def main(args: Array[String]) { - val lock = Actor.actorOf(new Lock("1234")).start + val lock = Actor.actorOf(new Lock("1234")).start() lock ! '1' lock ! '2' diff --git a/akka-docs/pending/http.rst b/akka-docs/pending/http.rst index 3f4399327b..739f443c1d 100644 --- a/akka-docs/pending/http.rst +++ b/akka-docs/pending/http.rst @@ -263,7 +263,7 @@ Finally, bind the *handleHttpRequest* function of the *Endpoint* trait to the ac def hook(uri: String): Boolean = ((uri == ProvideSameActor) || (uri == ProvideNewActor)) def provide(uri: String): ActorRef = { if (uri == ProvideSameActor) same - else actorOf[BoringActor].start + else actorOf[BoringActor].start() } // @@ -292,7 +292,7 @@ Finally, bind the *handleHttpRequest* function of the *Endpoint* trait to the ac // // this will be our "same" actor provided with ProvideSameActor endpoint is hit // - lazy val same = actorOf[BoringActor].start + lazy val same = actorOf[BoringActor].start() } Handling requests @@ -389,10 +389,10 @@ As noted above, hook functions are non-exclusive. This means multiple actors can // Try with/without a header named "Test-Token" // Try with/without a form parameter named "Data" def hookMultiActionA(uri: String): Boolean = uri startsWith Multi - def provideMultiActionA(uri: String): ActorRef = actorOf(new ActionAActor(complete)).start + def provideMultiActionA(uri: String): ActorRef = actorOf(new ActionAActor(complete)).start() def hookMultiActionB(uri: String): Boolean = uri startsWith Multi - def provideMultiActionB(uri: String): ActorRef = actorOf(new ActionBActor(complete)).start + def provideMultiActionB(uri: String): ActorRef = actorOf(new ActionBActor(complete)).start() // // this is where you want attach your endpoint hooks @@ -421,7 +421,7 @@ As noted above, hook functions are non-exclusive. This means multiple actors can // // this guy completes requests after other actions have occured // - lazy val complete = actorOf[ActionCompleteActor].start + lazy val complete = actorOf[ActionCompleteActor].start() } class ActionAActor(complete:ActorRef) extends Actor { diff --git a/akka-docs/pending/remote-actors-scala.rst b/akka-docs/pending/remote-actors-scala.rst index 19be8d78f5..5c594c5d56 100644 --- a/akka-docs/pending/remote-actors-scala.rst +++ b/akka-docs/pending/remote-actors-scala.rst @@ -32,7 +32,7 @@ Here is how to start up the RemoteNode and specify the hostname and port in the import akka.actor.Actor._ - remote.start + remote.start() // Specify the classloader to use to load the remote class (actor) remote.start(classLoader) @@ -593,7 +593,7 @@ So a simple listener actor can look like this: case RemoteClientWriteFailed(request, cause, client, address) => ... // act upon write failure case _ => //ignore other } - }).start + }).start() Registration and de-registration can be done like this: @@ -647,7 +647,7 @@ So a simple listener actor can look like this: case RemoteServerClientClosed(server, clientAddress) => ... // act upon client connection close case RemoteServerWriteFailed(request, casue, server, clientAddress) => ... // act upon server write failure } - }).start + }).start() Registration and de-registration can be done like this: diff --git a/akka-docs/pending/routing-scala.rst b/akka-docs/pending/routing-scala.rst index 45a297575d..2923b8240d 100644 --- a/akka-docs/pending/routing-scala.rst +++ b/akka-docs/pending/routing-scala.rst @@ -21,8 +21,8 @@ To use it you can either create a Dispatcher through the **dispatcherActor()** f //Two actors, one named Pinger and one named Ponger //The actor(pf) method creates an anonymous actor and starts it - val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start - val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start + val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start() + val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start() //A dispatcher that dispatches Ping messages to the pinger //and Pong messages to the ponger @@ -48,8 +48,8 @@ Or by mixing in akka.patterns.Dispatcher: class MyDispatcher extends Actor with Dispatcher { //Our pinger and ponger actors - val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start - val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start + val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start() + val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start() //When we get a ping, we dispatch to the pinger //When we get a pong, we dispatch to the ponger def routes = { @@ -59,7 +59,7 @@ Or by mixing in akka.patterns.Dispatcher: } //Create an instance of our dispatcher, and start it - val d = actorOf[MyDispatcher].start + val d = actorOf[MyDispatcher].start() d ! Ping //Prints "Pinger: Ping" d ! Pong //Prints "Ponger: Pong" @@ -85,8 +85,8 @@ Example using the **loadBalancerActor()** factory method: //Two actors, one named Pinger and one named Ponger //The actor(pf) method creates an anonymous actor and starts it - val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start - val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start + val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start() + val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start() //A load balancer that given a sequence of actors dispatches them accordingly //a CyclicIterator works in a round-robin-fashion @@ -112,14 +112,14 @@ Or by mixing in akka.routing.LoadBalancer //A load balancer that balances between a pinger and a ponger class MyLoadBalancer extends Actor with LoadBalancer { - val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start - val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start + val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start() + val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start() val seq = new CyclicIterator[ActorRef](List(pinger,ponger)) } //Create an instance of our loadbalancer, and start it - val d = actorOf[MyLoadBalancer].start + val d = actorOf[MyLoadBalancer].start() d ! Pong //Prints "Pinger: Pong" d ! Pong //Prints "Ponger: Pong" diff --git a/akka-docs/pending/serialization-scala.rst b/akka-docs/pending/serialization-scala.rst index b2aef526a8..93b738a176 100644 --- a/akka-docs/pending/serialization-scala.rst +++ b/akka-docs/pending/serialization-scala.rst @@ -80,13 +80,13 @@ Step 3: Import the type class module definition and serialize / de-serialize import akka.serialization.ActorSerialization._ import BinaryFormatMyActor._ - val actor1 = actorOf[MyActor].start + val actor1 = actorOf[MyActor].start() (actor1 !! "hello").getOrElse("_") should equal("world 1") (actor1 !! "hello").getOrElse("_") should equal("world 2") val bytes = toBinary(actor1) val actor2 = fromBinary(bytes) - actor2.start + actor2.start() (actor2 !! "hello").getOrElse("_") should equal("world 3") } @@ -128,13 +128,13 @@ and use it for serialization: import akka.serialization.ActorSerialization._ import BinaryFormatMyStatelessActor._ - val actor1 = actorOf[MyStatelessActor].start + val actor1 = actorOf[MyStatelessActor].start() (actor1 !! "hello").getOrElse("_") should equal("world") (actor1 !! "hello").getOrElse("_") should equal("world") val bytes = toBinary(actor1) val actor2 = fromBinary(bytes) - actor2.start + actor2.start() (actor2 !! "hello").getOrElse("_") should equal("world") } @@ -182,13 +182,13 @@ and serialize / de-serialize .. import akka.serialization.ActorSerialization._ import BinaryFormatMyJavaSerializableActor._ - val actor1 = actorOf[MyJavaSerializableActor].start + val actor1 = actorOf[MyJavaSerializableActor].start() (actor1 !! "hello").getOrElse("_") should equal("world 1") (actor1 !! "hello").getOrElse("_") should equal("world 2") val bytes = toBinary(actor1) val actor2 = fromBinary(bytes) - actor2.start + actor2.start() (actor2 !! "hello").getOrElse("_") should equal("world 3") } diff --git a/akka-docs/pending/stm-scala.rst b/akka-docs/pending/stm-scala.rst index 2ee7f08f11..e6ab56a92c 100644 --- a/akka-docs/pending/stm-scala.rst +++ b/akka-docs/pending/stm-scala.rst @@ -348,7 +348,7 @@ Here is an example of using ``retry`` to block until an account has enough money val account1 = Ref(100.0) val account2 = Ref(100.0) - val transferer = Actor.actorOf(new Transferer).start + val transferer = Actor.actorOf(new Transferer).start() transferer ! Transfer(account1, account2, 500.0) // INFO Transferer: not enough money - retrying @@ -404,7 +404,7 @@ You can also have two alternative blocking transactions, one of which can succee val ref1 = Ref(0) val ref2 = Ref(0) - val brancher = Actor.actorOf(new Brancher).start + val brancher = Actor.actorOf(new Brancher).start() brancher ! Branch(ref1, ref2, 1) // INFO Brancher: not enough on left - retrying diff --git a/akka-docs/pending/testkit-example.rst b/akka-docs/pending/testkit-example.rst index b270368183..96d851787e 100644 --- a/akka-docs/pending/testkit-example.rst +++ b/akka-docs/pending/testkit-example.rst @@ -16,14 +16,14 @@ import util.Random */ class TestKitUsageSpec extends WordSpec with BeforeAndAfterAll with ShouldMatchers with TestKit { - val echoRef = actorOf(new EchoActor).start - val forwardRef = actorOf(new ForwardingActor(testActor)).start - val filterRef = actorOf(new FilteringActor(testActor)).start + val echoRef = actorOf(new EchoActor).start() + val forwardRef = actorOf(new ForwardingActor(testActor)).start() + val filterRef = actorOf(new FilteringActor(testActor)).start() val randomHead = Random.nextInt(6) val randomTail = Random.nextInt(10) val headList = List().padTo(randomHead, "0") val tailList = List().padTo(randomTail, "1") - val seqRef = actorOf(new SequencingActor(testActor, headList, tailList)).start + val seqRef = actorOf(new SequencingActor(testActor, headList, tailList)).start() override protected def afterAll(): scala.Unit = { stopTestActor diff --git a/akka-docs/pending/transactors-scala.rst b/akka-docs/pending/transactors-scala.rst index c0e232f676..ae7853266b 100644 --- a/akka-docs/pending/transactors-scala.rst +++ b/akka-docs/pending/transactors-scala.rst @@ -58,8 +58,8 @@ Here is an example of coordinating two simple counter Actors so that they both i } } - val counter1 = Actor.actorOf[Counter].start - val counter2 = Actor.actorOf[Counter].start + val counter1 = Actor.actorOf[Counter].start() + val counter2 = Actor.actorOf[Counter].start() counter1 ! Coordinated(Increment(Some(counter2))) diff --git a/akka-docs/pending/tutorial-chat-server-scala.rst b/akka-docs/pending/tutorial-chat-server-scala.rst index 99159e35f2..ce0a1764c2 100644 --- a/akka-docs/pending/tutorial-chat-server-scala.rst +++ b/akka-docs/pending/tutorial-chat-server-scala.rst @@ -52,7 +52,7 @@ Here is a little example before we dive into a more interesting one. } val myActor = Actor.actorOf[MyActor] - myActor.start + myActor.start() From this call we get a handle to the 'Actor' called 'ActorRef', which we can use to interact with the Actor @@ -260,7 +260,7 @@ The 'shutdownSessions' function simply shuts all the sessions Actors down. That case Login(username) => EventHandler.info(this, "User [%s] has logged in".format(username)) val session = actorOf(new Session(username, storage)) - session.start + session.start() sessions += (username -> session) case Logout(username) => @@ -414,7 +414,7 @@ We have now created the full functionality for the chat server, all nicely decou * Class encapsulating the full Chat Service. * Start service by invoking: *
-   * val chatService = Actor.actorOf[ChatService].start
+   * val chatService = Actor.actorOf[ChatService].start()
    * 
*/ class ChatService extends @@ -505,7 +505,7 @@ Run a sample chat session import sample.chat._ import akka.actor.Actor._ - val chatService = actorOf[ChatService].start + val chatService = actorOf[ChatService].start() 3. In the second REPL you get execute: diff --git a/akka-http/src/test/scala/SecuritySpec.scala b/akka-http/src/test/scala/SecuritySpec.scala index d67d7e3bb7..4b7c7767be 100644 --- a/akka-http/src/test/scala/SecuritySpec.scala +++ b/akka-http/src/test/scala/SecuritySpec.scala @@ -34,7 +34,7 @@ class BasicAuthenticatorSpec extends junit.framework.TestCase import BasicAuthenticatorSpec._ val authenticator = actorOf[BasicAuthenticator] - authenticator.start + authenticator.start() @Test def testChallenge = { val req = mock[ContainerRequest] diff --git a/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala b/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala index bd586ce939..8139b35d0b 100644 --- a/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala +++ b/akka-remote/src/main/scala/akka/remote/BootableRemoteActorService.scala @@ -20,7 +20,7 @@ trait BootableRemoteActorService extends Bootable { def run = Actor.remote.start(self.applicationLoader.getOrElse(null)) //Use config host/port }, "Akka Remote Service") - def startRemoteService = remoteServerThread.start + def startRemoteService = remoteServerThread.start() abstract override def onLoad = { if (ReflectiveAccess.isRemotingEnabled && RemoteServerSettings.isRemotingEnabled) { diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index 29c2ba595a..a2a0f898ac 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -729,7 +729,7 @@ trait NettyRemoteServerModule extends RemoteServerModule { self: RemoteModule => private def register[Key](id: Key, actorRef: ActorRef, registry: ConcurrentHashMap[Key, ActorRef]) { if (_isRunning.isOn) { registry.put(id, actorRef) //TODO change to putIfAbsent - if (!actorRef.isRunning) actorRef.start + if (!actorRef.isRunning) actorRef.start() } } @@ -1124,7 +1124,7 @@ class RemoteServerHandler( val actorRef = factory() actorRef.uuid = parseUuid(uuid) //FIXME is this sensible? sessionActors.get(channel).put(id, actorRef) - actorRef.start //Start it where's it's created + actorRef.start() //Start it where's it's created } case sessionActor => sessionActor } @@ -1148,7 +1148,7 @@ class RemoteServerHandler( actorRef.id = id actorRef.timeout = timeout server.actorsByUuid.put(actorRef.uuid.toString, actorRef) // register by uuid - actorRef.start //Start it where it's created + actorRef.start() //Start it where it's created } catch { case e: Throwable => EventHandler.error(e, this, e.getMessage) diff --git a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala index 4538489191..897a14ca70 100644 --- a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala +++ b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala @@ -58,7 +58,7 @@ class AkkaRemoteTest extends /* Utilities */ - def replyHandler(latch: CountDownLatch, expect: String) = Some(Actor.actorOf(new ReplyHandlerActor(latch, expect)).start) + def replyHandler(latch: CountDownLatch, expect: String) = Some(Actor.actorOf(new ReplyHandlerActor(latch, expect)).start()) } trait NetworkFailureTest { self: WordSpec => diff --git a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala index 65693636b4..8e21f9daee 100644 --- a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala @@ -75,7 +75,7 @@ class MyActorCustomConstructor extends Actor { class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { "ClientInitiatedRemoteActor" should { "shouldSendOneWay" in { - val clientManaged = remote.actorOf[RemoteActorSpecActorUnidirectional](host,port).start + val clientManaged = remote.actorOf[RemoteActorSpecActorUnidirectional](host,port).start() clientManaged must not be null clientManaged.getClass must be (classOf[LocalActorRef]) clientManaged ! "OneWay" @@ -85,8 +85,8 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { "shouldSendOneWayAndReceiveReply" in { val latch = new CountDownLatch(1) - val actor = remote.actorOf[SendOneWayAndReplyReceiverActor](host,port).start - implicit val sender = Some(actorOf(new CountDownActor(latch)).start) + val actor = remote.actorOf[SendOneWayAndReplyReceiverActor](host,port).start() + implicit val sender = Some(actorOf(new CountDownActor(latch)).start()) actor ! "Hello" @@ -94,14 +94,14 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { } "shouldSendBangBangMessageAndReceiveReply" in { - val actor = remote.actorOf[RemoteActorSpecActorBidirectional](host,port).start + val actor = remote.actorOf[RemoteActorSpecActorBidirectional](host,port).start() val result = actor !! ("Hello", 10000) "World" must equal (result.get.asInstanceOf[String]) actor.stop } "shouldSendBangBangMessageAndReceiveReplyConcurrently" in { - val actors = (1 to 10).map(num => { remote.actorOf[RemoteActorSpecActorBidirectional](host,port).start }).toList + val actors = (1 to 10).map(num => { remote.actorOf[RemoteActorSpecActorBidirectional](host,port).start() }).toList actors.map(_ !!! ("Hello", 10000)) foreach { future => "World" must equal (future.await.result.asInstanceOf[Option[String]].get) } @@ -109,8 +109,8 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { } "shouldRegisterActorByUuid" in { - val actor1 = remote.actorOf[MyActorCustomConstructor](host, port).start - val actor2 = remote.actorOf[MyActorCustomConstructor](host, port).start + val actor1 = remote.actorOf[MyActorCustomConstructor](host, port).start() + val actor2 = remote.actorOf[MyActorCustomConstructor](host, port).start() actor1 ! "incrPrefix" @@ -128,7 +128,7 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { "shouldSendAndReceiveRemoteException" in { - val actor = remote.actorOf[RemoteActorSpecActorBidirectional](host, port).start + val actor = remote.actorOf[RemoteActorSpecActorBidirectional](host, port).start() try { implicit val timeout = 500000000L val f = (actor !!! "Failure").await.resultOrException diff --git a/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala b/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala index f6e0c1806f..d5aeccefa9 100644 --- a/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala +++ b/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala @@ -14,7 +14,7 @@ class OptimizedLocalScopedSpec extends AkkaRemoteTest { "An enabled optimized local scoped remote" should { "Fetch local actor ref when scope is local" in { - val fooActor = Actor.actorOf[TestActor].start + val fooActor = Actor.actorOf[TestActor].start() remote.register("foo", fooActor) remote.actorFor("foo", host, port) must be (fooActor) diff --git a/akka-remote/src/test/scala/remote/RemoteErrorHandlingNetworkTest.scala b/akka-remote/src/test/scala/remote/RemoteErrorHandlingNetworkTest.scala index dc4559a46b..2dc7ad65f5 100644 --- a/akka-remote/src/test/scala/remote/RemoteErrorHandlingNetworkTest.scala +++ b/akka-remote/src/test/scala/remote/RemoteErrorHandlingNetworkTest.scala @@ -109,7 +109,7 @@ class RemoteErrorHandlingNetworkTest extends AkkaRemoteTest with NetworkFailureT val actor = remote.actorFor( "akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorBidirectional", timeout, host, port) val latch = new CountDownLatch(1) - val sender = actorOf( new RemoteActorSpecActorAsyncSender(latch) ).start + val sender = actorOf( new RemoteActorSpecActorAsyncSender(latch) ).start() sender ! Send(actor) latch.await(1, TimeUnit.SECONDS) must be (true) } diff --git a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala index a0b6440287..e5ff681dbc 100644 --- a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala +++ b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala @@ -226,7 +226,7 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { // Then create a concrete container in which we mix in support for the specific // implementation of the Actors we want to use. - pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start + pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start() val factory = SupervisorFactory( SupervisorConfig( @@ -240,7 +240,7 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { } def getSingleActorOneForOneSupervisor: Supervisor = { - pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start + pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start() val factory = SupervisorFactory( SupervisorConfig( @@ -253,9 +253,9 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { } def getMultipleActorsAllForOneConf: Supervisor = { - pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start - pingpong2 = remote.actorOf[RemotePingPong2Actor](host,port).start - pingpong3 = remote.actorOf[RemotePingPong3Actor](host,port).start + pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start() + pingpong2 = remote.actorOf[RemotePingPong2Actor](host,port).start() + pingpong3 = remote.actorOf[RemotePingPong3Actor](host,port).start() val factory = SupervisorFactory( SupervisorConfig( @@ -276,9 +276,9 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { } def getMultipleActorsOneForOneConf: Supervisor = { - pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start - pingpong2 = remote.actorOf[RemotePingPong2Actor](host,port).start - pingpong3 = remote.actorOf[RemotePingPong3Actor](host,port).start + pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start() + pingpong2 = remote.actorOf[RemotePingPong2Actor](host,port).start() + pingpong3 = remote.actorOf[RemotePingPong3Actor](host,port).start() val factory = SupervisorFactory( SupervisorConfig( @@ -299,9 +299,9 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { } def getNestedSupervisorsAllForOneConf: Supervisor = { - pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start - pingpong2 = remote.actorOf[RemotePingPong2Actor](host,port).start - pingpong3 = remote.actorOf[RemotePingPong3Actor](host,port).start + pingpong1 = remote.actorOf[RemotePingPong1Actor](host,port).start() + pingpong2 = remote.actorOf[RemotePingPong2Actor](host,port).start() + pingpong3 = remote.actorOf[RemotePingPong3Actor](host,port).start() val factory = SupervisorFactory( SupervisorConfig( diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSample.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSample.scala index 6b11f73f10..cae866e6e2 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSample.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSample.scala @@ -25,7 +25,7 @@ Have fun. *************************************/ class HelloWorldActor extends Actor { - self.start + self.start() def receive = { case "Hello" => self.reply("World") diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala index 88a5ec8ec3..45e31a5e59 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala @@ -73,7 +73,7 @@ class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { val actor = remote.actorFor( "akka.actor.remote.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorBidirectional", timeout,host, port) val latch = new CountDownLatch(1) - val sender = actorOf( new RemoteActorSpecActorAsyncSender(latch) ).start + val sender = actorOf( new RemoteActorSpecActorAsyncSender(latch) ).start() sender ! Send(actor) latch.await(1, TimeUnit.SECONDS) must be (true) } @@ -163,7 +163,7 @@ class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { val actor1 = actorOf[RemoteActorSpecActorUnidirectional] remote.register("foo", actor1) val latch = new CountDownLatch(1) - val actor2 = actorOf(new Actor { def receive = { case "Pong" => latch.countDown } }).start + val actor2 = actorOf(new Actor { def receive = { case "Pong" => latch.countDown } }).start() val remoteActor = remote.actorFor("foo", host, port) remoteActor.!("Ping")(Some(actor2)) diff --git a/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala b/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala index 001a66eae0..6c6efd9f97 100644 --- a/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala +++ b/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala @@ -13,7 +13,7 @@ class UnOptimizedLocalScopedSpec extends AkkaRemoteTest { "An enabled optimized local scoped remote" should { "Fetch remote actor ref when scope is local" in { - val fooActor = Actor.actorOf[TestActor].start + val fooActor = Actor.actorOf[TestActor].start() remote.register("foo", fooActor) remote.actorFor("foo", host, port) must not be (fooActor) diff --git a/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala b/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala index 7aa0cede07..f37a47e843 100644 --- a/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala +++ b/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala @@ -66,52 +66,52 @@ class SerializableTypeClassActorSpec extends it("should be able to serialize and de-serialize a stateful actor") { import BinaryFormatMyActor._ - val actor1 = actorOf[MyActor].start + val actor1 = actorOf[MyActor].start() (actor1 !! "hello").getOrElse("_") should equal("world 1") (actor1 !! "hello").getOrElse("_") should equal("world 2") val bytes = toBinary(actor1) val actor2 = fromBinary(bytes) - actor2.start + actor2.start() (actor2 !! "hello").getOrElse("_") should equal("world 3") } it("should be able to serialize and de-serialize a stateful actor with compound state") { import BinaryFormatMyActorWithDualCounter._ - val actor1 = actorOf[MyActorWithDualCounter].start + val actor1 = actorOf[MyActorWithDualCounter].start() (actor1 !! "hello").getOrElse("_") should equal("world 1 1") (actor1 !! "hello").getOrElse("_") should equal("world 2 2") val bytes = toBinary(actor1) val actor2 = fromBinary(bytes) - actor2.start + actor2.start() (actor2 !! "hello").getOrElse("_") should equal("world 3 3") } it("should be able to serialize and de-serialize a stateless actor") { import BinaryFormatMyStatelessActor._ - val actor1 = actorOf[MyStatelessActor].start + val actor1 = actorOf[MyStatelessActor].start() (actor1 !! "hello").getOrElse("_") should equal("world") (actor1 !! "hello").getOrElse("_") should equal("world") val bytes = toBinary(actor1) val actor2 = fromBinary(bytes) - actor2.start + actor2.start() (actor2 !! "hello").getOrElse("_") should equal("world") } it("should be able to serialize and de-serialize a stateful actor with a given serializer") { import BinaryFormatMyJavaSerializableActor._ - val actor1 = actorOf[MyJavaSerializableActor].start + val actor1 = actorOf[MyJavaSerializableActor].start() (actor1 !! "hello").getOrElse("_") should equal("world 1") (actor1 !! "hello").getOrElse("_") should equal("world 2") val bytes = toBinary(actor1) val actor2 = fromBinary(bytes) - actor2.start + actor2.start() (actor2 !! "hello").getOrElse("_") should equal("world 3") actor2.receiveTimeout should equal (Some(1000)) @@ -122,7 +122,7 @@ class SerializableTypeClassActorSpec extends it("should be able to serialize and deserialize a MyStatelessActorWithMessagesInMailbox") { import BinaryFormatMyStatelessActorWithMessagesInMailbox._ - val actor1 = actorOf[MyStatelessActorWithMessagesInMailbox].start + val actor1 = actorOf[MyStatelessActorWithMessagesInMailbox].start() (actor1 ! "hello") (actor1 ! "hello") (actor1 ! "hello") @@ -147,7 +147,7 @@ class SerializableTypeClassActorSpec extends it("should be able to serialize and de-serialize an Actor hotswapped with 'become'") { import BinaryFormatMyActor._ - val actor1 = actorOf[MyActor].start + val actor1 = actorOf[MyActor].start() (actor1 !! "hello").getOrElse("_") should equal("world 1") (actor1 !! "hello").getOrElse("_") should equal("world 2") actor1 ! "swap" @@ -155,7 +155,7 @@ class SerializableTypeClassActorSpec extends val bytes = toBinary(actor1) val actor2 = fromBinary(bytes) - actor2.start + actor2.start() (actor1 !! "hello").getOrElse("_") should equal("swapped") @@ -166,7 +166,7 @@ class SerializableTypeClassActorSpec extends it("should be able to serialize and de-serialize an hotswapped actor") { import BinaryFormatMyActor._ - val actor1 = actorOf[MyActor].start + val actor1 = actorOf[MyActor].start() (actor1 !! "hello").getOrElse("_") should equal("world 1") (actor1 !! "hello").getOrElse("_") should equal("world 2") actor1 ! HotSwap { @@ -177,7 +177,7 @@ class SerializableTypeClassActorSpec extends val bytes = toBinary(actor1) val actor2 = fromBinary(bytes) - actor2.start + actor2.start() (actor1 !! "hello").getOrElse("_") should equal("swapped") @@ -190,7 +190,7 @@ class SerializableTypeClassActorSpec extends it("should serialize and de-serialize") { import BinaryFormatMyActorWithSerializableMessages._ - val actor1 = actorOf[MyActorWithSerializableMessages].start + val actor1 = actorOf[MyActorWithSerializableMessages].start() (actor1 ! MyMessage("hello1", ("akka", 100))) (actor1 ! MyMessage("hello2", ("akka", 200))) (actor1 ! MyMessage("hello3", ("akka", 300))) diff --git a/akka-remote/src/test/scala/serialization/Ticket435Spec.scala b/akka-remote/src/test/scala/serialization/Ticket435Spec.scala index a6193d9914..1697367b33 100644 --- a/akka-remote/src/test/scala/serialization/Ticket435Spec.scala +++ b/akka-remote/src/test/scala/serialization/Ticket435Spec.scala @@ -39,7 +39,7 @@ class Ticket435Spec extends it("should be able to serialize and deserialize a stateless actor with messages in mailbox") { import BinaryFormatMyStatelessActorWithMessagesInMailbox._ - val actor1 = actorOf[MyStatelessActorWithMessagesInMailbox].start + val actor1 = actorOf[MyStatelessActorWithMessagesInMailbox].start() (actor1 ! "hello") (actor1 ! "hello") (actor1 ! "hello") @@ -65,7 +65,7 @@ class Ticket435Spec extends it("should serialize the mailbox optionally") { import BinaryFormatMyStatelessActorWithMessagesInMailbox._ - val actor1 = actorOf[MyStatelessActorWithMessagesInMailbox].start + val actor1 = actorOf[MyStatelessActorWithMessagesInMailbox].start() (actor1 ! "hello") (actor1 ! "hello") (actor1 ! "hello") @@ -87,7 +87,7 @@ class Ticket435Spec extends it("should be able to serialize and deserialize a stateful actor with messages in mailbox") { import BinaryFormatMyStatefulActor._ - val actor1 = actorOf[MyStatefulActor].start + val actor1 = actorOf[MyStatefulActor].start() (actor1 ! "hi") (actor1 ! "hi") (actor1 ! "hi") diff --git a/akka-remote/src/test/scala/serialization/UntypedActorSerializationSpec.scala b/akka-remote/src/test/scala/serialization/UntypedActorSerializationSpec.scala index b9752d2d55..e33de43571 100644 --- a/akka-remote/src/test/scala/serialization/UntypedActorSerializationSpec.scala +++ b/akka-remote/src/test/scala/serialization/UntypedActorSerializationSpec.scala @@ -43,37 +43,37 @@ class UntypedActorSerializationSpec extends describe("Serializable untyped actor") { it("should be able to serialize and de-serialize a stateful untyped actor") { - val actor1 = Actors.actorOf(classOf[MyUntypedActor]).start + val actor1 = Actors.actorOf(classOf[MyUntypedActor]).start() actor1.sendRequestReply("hello") should equal("world 1") actor1.sendRequestReply("debasish") should equal("hello debasish 2") val f = new MyUntypedActorFormat val bytes = toBinaryJ(actor1, f) val actor2 = fromBinaryJ(bytes, f) - actor2.start + actor2.start() actor2.sendRequestReply("hello") should equal("world 3") } it("should be able to serialize and de-serialize a stateful actor with compound state") { - val actor1 = actorOf[MyUntypedActorWithDualCounter].start + val actor1 = actorOf[MyUntypedActorWithDualCounter].start() actor1.sendRequestReply("hello") should equal("world 1 1") actor1.sendRequestReply("hello") should equal("world 2 2") val f = new MyUntypedActorWithDualCounterFormat val bytes = toBinaryJ(actor1, f) val actor2 = fromBinaryJ(bytes, f) - actor2.start + actor2.start() actor2.sendRequestReply("hello") should equal("world 3 3") } it("should be able to serialize and de-serialize a stateless actor") { - val actor1 = actorOf[MyUntypedStatelessActor].start + val actor1 = actorOf[MyUntypedStatelessActor].start() actor1.sendRequestReply("hello") should equal("world") actor1.sendRequestReply("hello") should equal("world") val bytes = toBinaryJ(actor1, MyUntypedStatelessActorFormat) val actor2 = fromBinaryJ(bytes, MyUntypedStatelessActorFormat) - actor2.start + actor2.start() actor2.sendRequestReply("hello") should equal("world") } } diff --git a/akka-samples/akka-sample-ants/src/main/scala/Ants.scala b/akka-samples/akka-sample-ants/src/main/scala/Ants.scala index 1eb38d6f0f..ebcf1d0a79 100644 --- a/akka-samples/akka-sample-ants/src/main/scala/Ants.scala +++ b/akka-samples/akka-sample-ants/src/main/scala/Ants.scala @@ -65,7 +65,7 @@ object World { val homeOff = Dim / 4 lazy val places = Vector.fill(Dim, Dim)(new Place) lazy val ants = setup - lazy val evaporator = actorOf[Evaporator].start + lazy val evaporator = actorOf[Evaporator].start() private val snapshotFactory = TransactionFactory(readonly = true, familyName = "snapshot") @@ -81,7 +81,7 @@ object World { for (x <- homeRange; y <- homeRange) yield { place(x, y).makeHome place(x, y) enter Ant(randomInt(8)) - actorOf(new AntActor(x, y)).start + actorOf(new AntActor(x, y)).start() } } diff --git a/akka-samples/akka-sample-chat/README b/akka-samples/akka-sample-chat/README index 2c812593fa..3606097d6f 100644 --- a/akka-samples/akka-sample-chat/README +++ b/akka-samples/akka-sample-chat/README @@ -9,7 +9,7 @@ How to run the sample: 2. In the first REPL you get execute: - scala> import sample.chat._ - scala> import akka.actor.Actor._ - - scala> val chatService = actorOf[ChatService].start + - scala> val chatService = actorOf[ChatService].start() 3. In the second REPL you get execute: - scala> import sample.chat._ - scala> ClientRunner.run diff --git a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala index 46e0283874..e8c8073636 100644 --- a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala +++ b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala @@ -24,7 +24,7 @@ 2. In the first REPL you get execute: - scala> import sample.chat._ - scala> import akka.actor.Actor._ - - scala> val chatService = actorOf[ChatService].start + - scala> val chatService = actorOf[ChatService].start() 3. In the second REPL you get execute: - scala> import sample.chat._ - scala> ClientRunner.run @@ -125,7 +125,7 @@ case Login(username) => EventHandler.info(this, "User [%s] has logged in".format(username)) val session = actorOf(new Session(username, storage)) - session.start + session.start() sessions += (username -> session) case Logout(username) => @@ -198,7 +198,7 @@ * Class encapsulating the full Chat Service. * Start service by invoking: *
-   * val chatService = Actor.actorOf[ChatService].start
+   * val chatService = Actor.actorOf[ChatService].start()
    * 
*/ class ChatService extends @@ -220,7 +220,7 @@ def main(args: Array[String]): Unit = ServerRunner.run def run = { - actorOf[ChatService].start + actorOf[ChatService].start() } } diff --git a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala index 5a2c580e5e..6c836b290a 100644 --- a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala +++ b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala @@ -127,11 +127,11 @@ class Hakker(name: String,left: ActorRef, right: ActorRef) extends Actor { object DiningHakkers { def run { //Create 5 chopsticks - val chopsticks = for(i <- 1 to 5) yield actorOf(new Chopstick("Chopstick "+i)).start + val chopsticks = for(i <- 1 to 5) yield actorOf(new Chopstick("Chopstick "+i)).start() //Create 5 awesome hakkers and assign them their left and right chopstick val hakkers = for { (name,i) <- List("Ghosh","Bonér","Klang","Krasser","Manie").zipWithIndex - } yield actorOf(new Hakker(name,chopsticks(i),chopsticks((i+1) % 5))).start + } yield actorOf(new Hakker(name,chopsticks(i),chopsticks((i+1) % 5))).start() //Signal all hakkers that they should start thinking, and watch the show hakkers.foreach(_ ! Think) diff --git a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala index 3273136690..e63dbc3dfa 100644 --- a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala +++ b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala @@ -168,11 +168,11 @@ object DiningHakkersOnFsm { def run = { // Create 5 chopsticks - val chopsticks = for (i <- 1 to 5) yield actorOf(new Chopstick("Chopstick " + i)).start + val chopsticks = for (i <- 1 to 5) yield actorOf(new Chopstick("Chopstick " + i)).start() // Create 5 awesome fsm hakkers and assign them their left and right chopstick val hakkers = for{ (name, i) <- List("Ghosh", "Bonér", "Klang", "Krasser", "Manie").zipWithIndex - } yield actorOf(new FSMHakker(name, chopsticks(i), chopsticks((i + 1) % 5))).start + } yield actorOf(new FSMHakker(name, chopsticks(i), chopsticks((i + 1) % 5))).start() hakkers.foreach(_ ! Think) } diff --git a/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala b/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala index 3477ff5783..42450b0b39 100644 --- a/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala +++ b/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala @@ -26,7 +26,7 @@ object ClientManagedRemoteActorServer { object ClientManagedRemoteActorClient { def run = { - val actor = remote.actorOf[RemoteHelloWorldActor]("localhost",2552).start + val actor = remote.actorOf[RemoteHelloWorldActor]("localhost",2552).start() val result = actor !! "Hello" } diff --git a/akka-stm/src/main/scala/akka/agent/Agent.scala b/akka-stm/src/main/scala/akka/agent/Agent.scala index 2332f28b13..ad8d426860 100644 --- a/akka-stm/src/main/scala/akka/agent/Agent.scala +++ b/akka-stm/src/main/scala/akka/agent/Agent.scala @@ -94,7 +94,7 @@ object Agent { */ class Agent[T](initialValue: T) { private[akka] val ref = Ref(initialValue) - private[akka] val updater = Actor.actorOf(new AgentUpdater(this)).start + private[akka] val updater = Actor.actorOf(new AgentUpdater(this)).start() /** * Read the internal state of the agent. @@ -135,7 +135,7 @@ class Agent[T](initialValue: T) { */ def sendOff(f: T => T): Unit = send((value: T) => { suspend - val threadBased = Actor.actorOf(new ThreadBasedAgentUpdater(this)).start + val threadBased = Actor.actorOf(new ThreadBasedAgentUpdater(this)).start() threadBased ! Update(f) value }) diff --git a/akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala b/akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala index 367ef5ac5f..77ae01354a 100644 --- a/akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala +++ b/akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala @@ -51,9 +51,9 @@ class CoordinatedIncrementSpec extends WordSpec with MustMatchers { val timeout = 5 seconds def createActors = { - def createCounter(i: Int) = Actor.actorOf(new Counter("counter" + i)).start + def createCounter(i: Int) = Actor.actorOf(new Counter("counter" + i)).start() val counters = (1 to numCounters) map createCounter - val failer = Actor.actorOf(new Failer).start + val failer = Actor.actorOf(new Failer).start() (counters, failer) } diff --git a/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala b/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala index b6f8405e08..bdba458261 100644 --- a/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala +++ b/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala @@ -97,9 +97,9 @@ class FickleFriendsSpec extends WordSpec with MustMatchers { val numCounters = 2 def createActors = { - def createCounter(i: Int) = Actor.actorOf(new FickleCounter("counter" + i)).start + def createCounter(i: Int) = Actor.actorOf(new FickleCounter("counter" + i)).start() val counters = (1 to numCounters) map createCounter - val coordinator = Actor.actorOf(new Coordinator("coordinator")).start + val coordinator = Actor.actorOf(new Coordinator("coordinator")).start() (counters, coordinator) } diff --git a/akka-stm/src/test/scala/transactor/TransactorSpec.scala b/akka-stm/src/test/scala/transactor/TransactorSpec.scala index 212abff1d4..919692d82b 100644 --- a/akka-stm/src/test/scala/transactor/TransactorSpec.scala +++ b/akka-stm/src/test/scala/transactor/TransactorSpec.scala @@ -79,9 +79,9 @@ class TransactorSpec extends WordSpec with MustMatchers { val timeout = 5 seconds def createTransactors = { - def createCounter(i: Int) = Actor.actorOf(new Counter("counter" + i)).start + def createCounter(i: Int) = Actor.actorOf(new Counter("counter" + i)).start() val counters = (1 to numCounters) map createCounter - val failer = Actor.actorOf(new Failer).start + val failer = Actor.actorOf(new Failer).start() (counters, failer) } @@ -113,7 +113,7 @@ class TransactorSpec extends WordSpec with MustMatchers { "Transactor" should { "be usable without overriding normally" in { - val transactor = Actor.actorOf(new Setter).start + val transactor = Actor.actorOf(new Setter).start() val ref = Ref(0) val latch = new CountDownLatch(1) transactor ! Set(ref, 5, latch) diff --git a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala index a2d26ac4a8..6b9406767b 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala @@ -46,7 +46,7 @@ class TestActor(queue : BlockingDeque[AnyRef]) extends Actor with FSM[Int, TestA * *
  * class Test extends TestKit {
- *     val test = actorOf[SomeActor].start
+ *     val test = actorOf[SomeActor].start()
  *
  *     within (1 second) {
  *       test ! SomeWork
@@ -77,7 +77,7 @@ trait TestKit {
    * ActorRef of the test actor. Access is provided to enable e.g.
    * registration as message target.
    */
-  protected val testActor = actorOf(new TestActor(queue)).start
+  protected val testActor = actorOf(new TestActor(queue)).start()
 
   /**
    * Implicit sender reference so that replies are possible for messages sent
diff --git a/akka-tutorials/akka-tutorial-second/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-second/src/main/scala/Pi.scala
index d183566b74..0f9da10976 100644
--- a/akka-tutorials/akka-tutorial-second/src/main/scala/Pi.scala
+++ b/akka-tutorials/akka-tutorial-second/src/main/scala/Pi.scala
@@ -78,10 +78,10 @@ object Pi extends App {
     var nrOfResults: Int = _
 
     // create the workers
-    val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start)
+    val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start())
 
     // wrap them with a load-balancing router
-    val router = Routing.loadBalancerActor(CyclicIterator(workers)).start
+    val router = Routing.loadBalancerActor(CyclicIterator(workers)).start()
 
     // phase 1, can accept a Calculate message
     def scatter: Receive = {
@@ -124,7 +124,7 @@ object Pi extends App {
   // ==================
   def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) {
     // create the master
-    val master = actorOf(new Master(nrOfWorkers, nrOfElements, nrOfMessages)).start
+    val master = actorOf(new Master(nrOfWorkers, nrOfElements, nrOfMessages)).start()
 
     //start the calculation
     val start = now
diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala
index e21cdc395d..1f1c840162 100644
--- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala
+++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala
@@ -158,7 +158,7 @@ abstract class TypedActor extends Actor with Proxyable {
   /**
    * User overridable callback.
    * 

- * Is called when an Actor is started by invoking 'actor.start'. + * Is called when an Actor is started by invoking 'actor.start()'. */ override def preStart {} @@ -638,7 +638,7 @@ object TypedActor { } AspectInitRegistry.register(proxy, AspectInit(intfClass, typedActor, actorRef, remoteAddress, actorRef.timeout)) - actorRef.start + actorRef.start() proxy.asInstanceOf[T] } @@ -726,7 +726,7 @@ object TypedActor { actorRef.timeout = timeout if (remoteAddress.isDefined) actorRef.makeRemote(remoteAddress.get) AspectInitRegistry.register(proxy, AspectInit(targetClass, proxy, actorRef, remoteAddress, timeout)) - actorRef.start + actorRef.start() proxy.asInstanceOf[T] } */ diff --git a/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala b/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala index f2f2a7a1fc..ae19601351 100644 --- a/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala +++ b/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala @@ -125,7 +125,7 @@ private[akka] class TypedActorGuiceConfigurator extends TypedActorConfiguratorBa proxy, AspectInit(interfaceClass, typedActor, actorRef, remoteAddress, timeout)) typedActor.initialize(proxy) - actorRef.start + actorRef.start() supervised ::= Supervise(actorRef, component.lifeCycle) diff --git a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorLifecycleSpec.scala b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorLifecycleSpec.scala index a6e17b5767..78cd4535c9 100644 --- a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorLifecycleSpec.scala +++ b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorLifecycleSpec.scala @@ -120,7 +120,7 @@ class TypedActorLifecycleSpec extends Spec with ShouldMatchers with BeforeAndAft second.fail fail("shouldn't get here") } catch { - case r: ActorInitializationException if r.getMessage == "Actor has not been started, you need to invoke 'actor.start' before using it" => //expected + case r: ActorInitializationException if r.getMessage == "Actor has not been started, you need to invoke 'actor.start()' before using it" => //expected } } finally { conf.stop diff --git a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala index f871f98841..883ca6ac3a 100644 --- a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala +++ b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala @@ -131,7 +131,7 @@ class TypedActorSpec extends assert(typedActors.contains(pojo)) // creating untyped actor with same custom id - val actorRef = Actor.actorOf[MyActor].start + val actorRef = Actor.actorOf[MyActor].start() val typedActors2 = Actor.registry.typedActorsFor("my-custom-id") assert(typedActors2.length === 1) assert(typedActors2.contains(pojo)) @@ -166,7 +166,7 @@ class TypedActorSpec extends } it("should support foreach for typed actors") { - val actorRef = Actor.actorOf[MyActor].start + val actorRef = Actor.actorOf[MyActor].start() assert(Actor.registry.actors.size === 3) assert(Actor.registry.typedActors.size === 2) Actor.registry.foreachTypedActor(TypedActor.stop(_)) @@ -175,7 +175,7 @@ class TypedActorSpec extends } it("should shutdown all typed and untyped actors") { - val actorRef = Actor.actorOf[MyActor].start + val actorRef = Actor.actorOf[MyActor].start() assert(Actor.registry.actors.size === 3) assert(Actor.registry.typedActors.size === 2) Actor.registry.shutdownAll() From 3c8e375f30883a4e3a4aefa3e47cf6420903fdaf Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 12 Apr 2011 10:53:56 +0200 Subject: [PATCH 060/147] Added parens to stop --- .../scala/akka/actor/actor/ActorRefSpec.scala | 6 +-- .../test/scala/akka/actor/actor/Bench.scala | 2 +- .../akka/actor/actor/ReceiveTimeoutSpec.scala | 10 ++--- .../scala/akka/dispatch/ActorModelSpec.scala | 16 ++++---- ...rBasedEventDrivenDispatcherActorSpec.scala | 16 ++++---- ...BasedEventDrivenDispatcherActorsSpec.scala | 4 +- ...ventDrivenWorkStealingDispatcherSpec.scala | 4 +- .../test/scala/akka/dispatch/FutureSpec.scala | 40 +++++++++---------- .../akka/dispatch/ThreadBasedActorSpec.scala | 8 ++-- .../scala/akka/misc/ActorRegistrySpec.scala | 34 ++++++++-------- .../test/scala/akka/routing/RoutingSpec.scala | 14 +++---- .../CallingThreadDispatcherModelSpec.scala | 2 +- .../src/main/scala/akka/actor/Actor.scala | 14 +++---- .../src/main/scala/akka/actor/ActorRef.scala | 10 ++--- .../main/scala/akka/actor/ActorRegistry.scala | 4 +- .../src/main/scala/akka/actor/FSM.scala | 2 +- .../main/scala/akka/actor/Supervisor.scala | 4 +- .../main/scala/akka/actor/UntypedActor.scala | 2 +- .../main/scala/akka/dataflow/DataFlow.scala | 6 +-- .../scala/akka/dispatch/MessageHandling.scala | 2 +- .../remoteinterface/RemoteInterface.scala | 8 ++-- .../scala/akka/util/ListenerManagement.scala | 2 +- akka-docs/manual/getting-started-first.rst | 2 +- akka-docs/pending/actors-scala.rst | 2 +- akka-docs/pending/stm-scala.rst | 4 +- akka-docs/pending/testkit-example.rst | 8 ++-- akka-docs/pending/transactors-scala.rst | 4 +- .../pending/tutorial-chat-server-scala.rst | 8 ++-- .../remote/netty/NettyRemoteSupport.scala | 4 +- .../ClientInitiatedRemoteActorSpec.scala | 12 +++--- .../SerializableTypeClassActorSpec.scala | 4 +- .../src/main/scala/ChatServer.scala | 6 +-- .../src/main/scala/akka/agent/Agent.scala | 6 +-- .../transactor/CoordinatedIncrementSpec.scala | 8 ++-- .../scala/transactor/FickleFriendsSpec.scala | 4 +- .../scala/transactor/TransactorSpec.scala | 10 ++--- .../src/main/scala/akka/testkit/TestKit.scala | 2 +- .../src/main/scala/Pi.scala | 2 +- .../main/scala/akka/actor/TypedActor.scala | 4 +- .../actor/typed-actor/TypedActorSpec.scala | 2 +- 40 files changed, 151 insertions(+), 151 deletions(-) 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 cb8036ee24..3976becb14 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 @@ -41,7 +41,7 @@ object ActorRefSpec { case "work" => { work self.reply("workDone") - self.stop + self.stop() } case replyTo: Channel[Any] => { work @@ -122,8 +122,8 @@ class ActorRefSpec extends WordSpec with MustMatchers { latch.await - clientRef.stop - serverRef.stop + clientRef.stop() + serverRef.stop() } "stop when sent a poison pill" in { diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/Bench.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/Bench.scala index df12fc390d..f018de635c 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/Bench.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/Bench.scala @@ -88,7 +88,7 @@ object Chameneos { sumMeetings += i if (numFaded == numChameneos) { Chameneos.end = System.currentTimeMillis - self.stop + self.stop() } case msg @ Meet(a, c) => diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/ReceiveTimeoutSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/ReceiveTimeoutSpec.scala index 10952265a2..00298e10c0 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/ReceiveTimeoutSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/ReceiveTimeoutSpec.scala @@ -31,7 +31,7 @@ class ReceiveTimeoutSpec extends WordSpec with MustMatchers { }).start() timeoutLatch.await - timeoutActor.stop + timeoutActor.stop() } "get timeout when swapped" in { @@ -54,7 +54,7 @@ class ReceiveTimeoutSpec extends WordSpec with MustMatchers { }) swappedLatch.await - timeoutActor.stop + timeoutActor.stop() } "reschedule timeout after regular receive" in { @@ -73,7 +73,7 @@ class ReceiveTimeoutSpec extends WordSpec with MustMatchers { timeoutActor ! Tick timeoutLatch.await - timeoutActor.stop + timeoutActor.stop() } "be able to turn off timeout if desired" in { @@ -97,7 +97,7 @@ class ReceiveTimeoutSpec extends WordSpec with MustMatchers { timeoutLatch.await count.get must be (1) - timeoutActor.stop + timeoutActor.stop() } "not receive timeout message when not specified" in { @@ -110,7 +110,7 @@ class ReceiveTimeoutSpec extends WordSpec with MustMatchers { }).start() timeoutLatch.awaitTimeout(1 second) // timeout expected - timeoutActor.stop + timeoutActor.stop() } "have ReceiveTimeout eq to Actors ReceiveTimeout" in { 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 37688631c8..075a369cf1 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala @@ -60,7 +60,7 @@ object ActorModelSpec { 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() - case CountDownNStop(l)=> ack; l.countDown; self.stop; busy.switchOff() + case CountDownNStop(l)=> ack; l.countDown; self.stop(); busy.switchOff() case Restart => ack; busy.switchOff(); throw new Exception("Restart requested") } } @@ -204,7 +204,7 @@ abstract class ActorModelSpec extends JUnitSuite { assertDispatcher(dispatcher)(starts = 0, stops = 0) a.start() assertDispatcher(dispatcher)(starts = 1, stops = 0) - a.stop + a.stop() await(dispatcher.stops.get == 1)(withinMs = dispatcher.timeoutMs * 5) assertDispatcher(dispatcher)(starts = 1, stops = 1) assertRef(a,dispatcher)( @@ -234,7 +234,7 @@ abstract class ActorModelSpec extends JUnitSuite { assertCountDown(oneAtATime, Testing.testTime(1500) ,"Processed message when allowed") assertRefDefaultZero(a)(registers = 1, msgsReceived = 3, msgsProcessed = 3) - a.stop + a.stop() assertRefDefaultZero(a)(registers = 1, unregisters = 1, msgsReceived = 3, msgsProcessed = 3) } @@ -249,7 +249,7 @@ abstract class ActorModelSpec extends JUnitSuite { assertCountDown(counter, Testing.testTime(3000), "Should process 200 messages") assertRefDefaultZero(a)(registers = 1, msgsReceived = 200, msgsProcessed = 200) - a.stop + a.stop() } def spawn(f : => Unit) = { @@ -270,8 +270,8 @@ abstract class ActorModelSpec extends JUnitSuite { assertCountDown(bParallel, Testing.testTime(3000), "Should process other actors in parallel") aStop.countDown() - a.stop - b.stop + a.stop() + b.stop() assertRefDefaultZero(a)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1) assertRefDefaultZero(b)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1) } @@ -283,7 +283,7 @@ abstract class ActorModelSpec extends JUnitSuite { a ! Restart a ! CountDown(done) assertCountDown(done, Testing.testTime(3000), "Should be suspended+resumed and done with next message within 3 seconds") - a.stop + a.stop() assertRefDefaultZero(a)(registers = 1,unregisters = 1, msgsReceived = 2, msgsProcessed = 2, suspensions = 1, resumes = 1) } @@ -302,7 +302,7 @@ abstract class ActorModelSpec extends JUnitSuite { assertRefDefaultZero(a)(registers = 1, msgsReceived = 1, msgsProcessed = 1, suspensions = 1, resumes = 1) - a.stop + a.stop() assertRefDefaultZero(a)(registers = 1,unregisters = 1, msgsReceived = 1, msgsProcessed = 1, suspensions = 1, resumes = 1) } diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala index 10a3bbc67f..c7c8a5748d 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala @@ -38,21 +38,21 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { val actor = actorOf[OneWayTestActor].start() val result = actor ! "OneWay" assert(OneWayTestActor.oneWay.await(1, TimeUnit.SECONDS)) - actor.stop + actor.stop() } @Test def shouldSendReplySync = { val actor = actorOf[TestActor].start() val result = (actor !! ("Hello", 10000)).as[String] assert("World" === result.get) - actor.stop + actor.stop() } @Test def shouldSendReplyAsync = { val actor = actorOf[TestActor].start() val result = actor !! "Hello" assert("World" === result.get.asInstanceOf[String]) - actor.stop + actor.stop() } @Test def shouldSendReceiveException = { @@ -64,7 +64,7 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { case e => assert("Expected exception; to test fault-tolerance" === e.getMessage()) } - actor.stop + actor.stop() } @Test def shouldRespectThroughput { @@ -96,8 +96,8 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { fastOne ! "sabotage" start.countDown val result = latch.await(3,TimeUnit.SECONDS) - fastOne.stop - slowOne.stop + fastOne.stop() + slowOne.stop() assert(result === true) } @@ -115,7 +115,7 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { val fastOne = actorOf( new Actor { self.dispatcher = throughputDispatcher - def receive = { case "ping" => if(works.get) latch.countDown; self.stop } + def receive = { case "ping" => if(works.get) latch.countDown; self.stop() } }).start() val slowOne = actorOf( @@ -123,7 +123,7 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { self.dispatcher = throughputDispatcher def receive = { case "hogexecutor" => ready.countDown; start.await - case "ping" => works.set(false); self.stop + case "ping" => works.set(false); self.stop() } }).start() diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala index e797319a83..261da14814 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala @@ -55,7 +55,7 @@ class ExecutorBasedEventDrivenDispatcherActorsSpec extends JUnitSuite with MustM assert(sFinished.getCount > 0) sFinished.await assert(sFinished.getCount === 0) - f.stop - s.stop + f.stop() + s.stop() } } diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala index 25780cdaf9..941eaf6cc6 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala @@ -90,8 +90,8 @@ class ExecutorBasedEventDrivenWorkStealingDispatcherSpec extends JUnitSuite with fast.actor.asInstanceOf[DelayableActor].invocationCount must be > sentToFast fast.actor.asInstanceOf[DelayableActor].invocationCount must be > (slow.actor.asInstanceOf[DelayableActor].invocationCount) - slow.stop - fast.stop + slow.stop() + fast.stop() } @Test def canNotUseActorsOfDifferentTypesInSameDispatcher(): Unit = { 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 359f658709..6fc96bb6d2 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala @@ -43,7 +43,7 @@ class FutureSpec extends JUnitSuite { future.await assert(future.result.isDefined) assert("World" === future.result.get) - actor.stop + actor.stop() } @Test def shouldActorReplyExceptionThroughExplicitFuture { @@ -53,7 +53,7 @@ class FutureSpec extends JUnitSuite { future.await assert(future.exception.isDefined) assert("Expected exception; to test fault-tolerance" === future.exception.get.getMessage) - actor.stop + actor.stop() } @Test def shouldFutureCompose { @@ -65,8 +65,8 @@ class FutureSpec extends JUnitSuite { assert(Some(Right("WORLD")) === future1.await.value) assert(Some(Right("WORLD")) === future2.await.value) intercept[ClassCastException] { future3.await.resultOrException } - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldFutureComposePatternMatch { @@ -76,8 +76,8 @@ class FutureSpec extends JUnitSuite { val future2 = actor1 !!! "Hello" collect { case (n: Int) => n } flatMap (actor2 !!! _) assert(Some(Right("WORLD")) === future1.await.value) intercept[MatchError] { future2.await.resultOrException } - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldFutureForComprehension { @@ -104,7 +104,7 @@ class FutureSpec extends JUnitSuite { assert(Some(Right("10-14")) === future1.await.value) intercept[ClassCastException] { future2.await.resultOrException } - actor.stop + actor.stop() } @Test def shouldFutureForComprehensionPatternMatch { @@ -131,7 +131,7 @@ class FutureSpec extends JUnitSuite { assert(Some(Right("10-14")) === future1.await.value) intercept[MatchError] { future2.await.resultOrException } - actor.stop + actor.stop() } @Test def shouldFutureAwaitEitherLeft = { @@ -142,8 +142,8 @@ class FutureSpec extends JUnitSuite { val result = Futures.awaitEither(future1, future2) assert(result.isDefined) assert("World" === result.get) - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldFutureAwaitEitherRight = { @@ -154,8 +154,8 @@ class FutureSpec extends JUnitSuite { val result = Futures.awaitEither(future1, future2) assert(result.isDefined) assert("World" === result.get) - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldFutureAwaitOneLeft = { @@ -166,8 +166,8 @@ class FutureSpec extends JUnitSuite { val result = Futures.awaitOne(List(future1, future2)) assert(result.result.isDefined) assert("World" === result.result.get) - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldFutureAwaitOneRight = { @@ -178,8 +178,8 @@ class FutureSpec extends JUnitSuite { val result = Futures.awaitOne(List(future1, future2)) assert(result.result.isDefined) assert("World" === result.result.get) - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldFutureAwaitAll = { @@ -192,8 +192,8 @@ class FutureSpec extends JUnitSuite { assert("World" === future1.result.get) assert(future2.result.isDefined) assert("World" === future2.result.get) - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldFuturesAwaitMapHandleEmptySequence { @@ -302,7 +302,7 @@ class FutureSpec extends JUnitSuite { val actor = actorOf[TestActor].start() actor !!! "Hello" receive { case "World" => latch.open } assert(latch.tryAwait(5, TimeUnit.SECONDS)) - actor.stop + actor.stop() } @Test def shouldTraverseFutures { @@ -317,7 +317,7 @@ class FutureSpec extends JUnitSuite { val oddFutures: List[Future[Int]] = List.fill(100)(oddActor !!! 'GetNext) assert(Futures.sequence(oddFutures).get.sum === 10000) - oddActor.stop + oddActor.stop() val list = (1 to 100).toList assert(Futures.traverse(list)(x => Future(x * 2 - 1)).get.sum === 10000) diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala index 6201c7951e..3de303974f 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala @@ -36,21 +36,21 @@ class ThreadBasedActorSpec extends JUnitSuite { }).start() val result = actor ! "OneWay" assert(oneWay.await(1, TimeUnit.SECONDS)) - actor.stop + actor.stop() } @Test def shouldSendReplySync = { val actor = actorOf[TestActor].start() val result = (actor !! ("Hello", 10000)).as[String] assert("World" === result.get) - actor.stop + actor.stop() } @Test def shouldSendReplyAsync = { val actor = actorOf[TestActor].start() val result = actor !! "Hello" assert("World" === result.get.asInstanceOf[String]) - actor.stop + actor.stop() } @Test def shouldSendReceiveException = { @@ -62,6 +62,6 @@ class ThreadBasedActorSpec extends JUnitSuite { case e => assert("Expected exception; to test fault-tolerance" === e.getMessage()) } - actor.stop + actor.stop() } } diff --git a/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala b/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala index 2eb4159411..bd3220b3d8 100644 --- a/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala @@ -40,7 +40,7 @@ class ActorRegistrySpec extends JUnitSuite { assert(actors.size === 1) assert(actors.head.actor.isInstanceOf[TestActor]) assert(actors.head.id === "MyID") - actor.stop + actor.stop() } @Test def shouldGetActorByUUIDFromActorRegistry { @@ -51,7 +51,7 @@ class ActorRegistrySpec extends JUnitSuite { val actorOrNone = Actor.registry.actorFor(uuid) assert(actorOrNone.isDefined) assert(actorOrNone.get.uuid === uuid) - actor.stop + actor.stop() } @Test def shouldGetActorByClassFromActorRegistry { @@ -62,7 +62,7 @@ class ActorRegistrySpec extends JUnitSuite { assert(actors.size === 1) assert(actors.head.actor.isInstanceOf[TestActor]) assert(actors.head.id === "MyID") - actor.stop + actor.stop() } @Test def shouldGetActorByManifestFromActorRegistry { @@ -73,7 +73,7 @@ class ActorRegistrySpec extends JUnitSuite { assert(actors.size === 1) assert(actors.head.actor.isInstanceOf[TestActor]) assert(actors.head.id === "MyID") - actor.stop + actor.stop() } @Test def shouldFindThingsFromActorRegistry { @@ -84,7 +84,7 @@ class ActorRegistrySpec extends JUnitSuite { assert(found.isDefined) assert(found.get.actor.isInstanceOf[TestActor]) assert(found.get.id === "MyID") - actor.stop + actor.stop() } @Test def shouldGetActorsByIdFromActorRegistry { @@ -99,8 +99,8 @@ class ActorRegistrySpec extends JUnitSuite { assert(actors.head.id === "MyID") assert(actors.last.actor.isInstanceOf[TestActor]) assert(actors.last.id === "MyID") - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldGetActorsByClassFromActorRegistry { @@ -115,8 +115,8 @@ class ActorRegistrySpec extends JUnitSuite { assert(actors.head.id === "MyID") assert(actors.last.actor.isInstanceOf[TestActor]) assert(actors.last.id === "MyID") - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldGetActorsByManifestFromActorRegistry { @@ -131,8 +131,8 @@ class ActorRegistrySpec extends JUnitSuite { assert(actors.head.id === "MyID") assert(actors.last.actor.isInstanceOf[TestActor]) assert(actors.last.id === "MyID") - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldGetActorsByMessageFromActorRegistry { @@ -159,8 +159,8 @@ class ActorRegistrySpec extends JUnitSuite { val actorsForMessagePing = Actor.registry.actorsFor[Actor]("ping") assert(actorsForMessagePing.size === 2) - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldGetAllActorsFromActorRegistry { @@ -175,8 +175,8 @@ class ActorRegistrySpec extends JUnitSuite { assert(actors.head.id === "MyID") assert(actors.last.actor.isInstanceOf[TestActor]) assert(actors.last.id === "MyID") - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldGetResponseByAllActorsInActorRegistryWhenInvokingForeach { @@ -188,8 +188,8 @@ class ActorRegistrySpec extends JUnitSuite { record = "" Actor.registry.foreach(actor => actor !! "ping") assert(record === "pongpong") - actor1.stop - actor2.stop + actor1.stop() + actor2.stop() } @Test def shouldShutdownAllActorsInActorRegistry { 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 550def5312..a8a30e6ece 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala @@ -47,7 +47,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers result.isDefined must be (true) result.get must be(21) - for(a <- List(t1,t2,d)) a.stop + for(a <- List(t1,t2,d)) a.stop() } @Test def testLogger = { @@ -62,8 +62,8 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers val done = latch.await(5,TimeUnit.SECONDS) done must be (true) msgs must ( have size (2) and contain (foo) and contain (bar) ) - t1.stop - l.stop + t1.stop() + l.stop() } @Test def testSmallestMailboxFirstDispatcher = { @@ -90,7 +90,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers val done = latch.await(10,TimeUnit.SECONDS) done must be (true) t1ProcessedCount.get must be < (t2ProcessedCount.get) // because t1 is much slower and thus has a bigger mailbox all the time - for(a <- List(t1,t2,d)) a.stop + for(a <- List(t1,t2,d)) a.stop() } @Test def testListener = { @@ -129,7 +129,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers num.get must be (2) val withListeners = foreachListener.await(5,TimeUnit.SECONDS) withListeners must be (true) - for(a <- List(i,a1,a2,a3)) a.stop + for(a <- List(i,a1,a2,a3)) a.stop() } @Test def testIsDefinedAt = { @@ -177,7 +177,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers d2.isDefinedAt(testMsg1) must be (true) d2.isDefinedAt(testMsg3) must be (false) - for(a <- List(t1,t2,d1,d2)) a.stop + for(a <- List(t1,t2,d1,d2)) a.stop() } // Actor Pool Capacity Tests @@ -260,7 +260,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers _.await.resultOrException.get must be ("Response") } } finally { - actorPool.stop + actorPool.stop() } } diff --git a/akka-actor-tests/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala b/akka-actor-tests/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala index 2aade68fed..97480cb6d3 100644 --- a/akka-actor-tests/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/testkit/CallingThreadDispatcherModelSpec.scala @@ -18,7 +18,7 @@ class CallingThreadDispatcherModelSpec extends ActorModelSpec { (1 to num) foreach { _ => newTestActor.start() ! cachedMessage } - keeper.stop + keeper.stop() assertCountDown(cachedMessage.latch,10000, "Should process " + num + " countdowns") } for(run <- 1 to 3) { diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index a34753141d..6adbdffe2e 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -134,7 +134,7 @@ object Actor extends ListenerManagement { * val actor = actorOf[MyActor] * actor.start() * actor ! message - * actor.stop + * actor.stop() *

* You can create and start the actor in one statement like this: *
@@ -150,7 +150,7 @@ object Actor extends ListenerManagement {
    *   val actor = actorOf(classOf[MyActor])
    *   actor.start()
    *   actor ! message
-   *   actor.stop
+   *   actor.stop()
    * 
* You can create and start the actor in one statement like this: *
@@ -178,7 +178,7 @@ object Actor extends ListenerManagement {
    *   val actor = actorOf(new MyActor)
    *   actor.start()
    *   actor ! message
-   *   actor.stop
+   *   actor.stop()
    * 
* You can create and start the actor in one statement like this: *
@@ -217,7 +217,7 @@ object Actor extends ListenerManagement {
     actorOf(new Actor() {
       self.dispatcher = dispatcher
       def receive = {
-        case Spawn => try { body } finally { self.stop }
+        case Spawn => try { body } finally { self.stop() }
       }
     }).start() ! Spawn
   }
@@ -373,7 +373,7 @@ trait Actor {
   /**
    * User overridable callback.
    * 

- * Is called when 'actor.stop' is invoked. + * Is called when 'actor.stop()' is invoked. */ def postStop {} @@ -458,12 +458,12 @@ trait Actor { case Exit(dead, reason) => self.handleTrapExit(dead, reason) case Link(child) => self.link(child) case Unlink(child) => self.unlink(child) - case UnlinkAndStop(child) => self.unlink(child); child.stop + case UnlinkAndStop(child) => self.unlink(child); child.stop() case Restart(reason) => throw reason case Kill => throw new ActorKilledException("Kill") case PoisonPill => val f = self.senderFuture - self.stop + self.stop() if (f.isDefined) f.get.completeWithException(new ActorKilledException("PoisonPill")) } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 88a00eaa03..cab8ebbf27 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -62,7 +62,7 @@ abstract class Channel[T] { * val actor = actorOf[MyActor] * actor.start() * actor ! message - * actor.stop + * actor.stop() *

* * You can also create and start actors like this: @@ -77,7 +77,7 @@ abstract class Channel[T] { * val actor = actorOf(new MyActor(...)) * actor.start() * actor ! message - * actor.stop + * actor.stop() * * * @author Jonas Bonér @@ -891,7 +891,7 @@ class LocalActorRef private[akka] ( case _ => if (_supervisor.isDefined) notifySupervisorWithMessage(Exit(this, reason)) - else dead.stop + else dead.stop() } } @@ -1029,7 +1029,7 @@ class LocalActorRef private[akka] ( } private def shutDownTemporaryActor(temporaryActor: ActorRef) { - temporaryActor.stop + temporaryActor.stop() _linkedActors.remove(temporaryActor.uuid) // remove the temporary actor // if last temporary actor is gone, then unlink me from supervisor if (_linkedActors.isEmpty) notifySupervisorWithMessage(UnlinkAndStop(this)) @@ -1061,7 +1061,7 @@ class LocalActorRef private[akka] ( { val i = _linkedActors.values.iterator while (i.hasNext) { - i.next.stop + i.next.stop() i.remove } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala index 03bef32d25..27057ce8a9 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRegistry.scala @@ -261,9 +261,9 @@ private[actor] final class ActorRegistry private[actor] () extends ListenerManag val actorRef = elements.nextElement val proxy = typedActorFor(actorRef) if (proxy.isDefined) TypedActorModule.typedActorObjectInstance.get.stop(proxy.get) - else actorRef.stop + else actorRef.stop() } - } else foreach(_.stop) + } else foreach(_.stop()) if (Remote.isEnabled) { Actor.remote.clear //TODO: REVISIT: Should this be here? } diff --git a/akka-actor/src/main/scala/akka/actor/FSM.scala b/akka-actor/src/main/scala/akka/actor/FSM.scala index f0e22909d5..37752b1373 100644 --- a/akka-actor/src/main/scala/akka/actor/FSM.scala +++ b/akka-actor/src/main/scala/akka/actor/FSM.scala @@ -433,7 +433,7 @@ trait FSM[S, D] { private def terminate(reason: Reason) = { terminateEvent.apply(StopEvent(reason, currentState.stateName, currentState.stateData)) - self.stop + self.stop() } case class Event[D](event: Any, stateData: D) diff --git a/akka-actor/src/main/scala/akka/actor/Supervisor.scala b/akka-actor/src/main/scala/akka/actor/Supervisor.scala index e3f59d3777..22abafaccc 100644 --- a/akka-actor/src/main/scala/akka/actor/Supervisor.scala +++ b/akka-actor/src/main/scala/akka/actor/Supervisor.scala @@ -114,7 +114,7 @@ sealed class Supervisor(handler: FaultHandlingStrategy) { this } - def shutdown(): Unit = supervisor.stop + def shutdown(): Unit = supervisor.stop() def link(child: ActorRef) = supervisor.link(child) @@ -163,7 +163,7 @@ final class SupervisorActor private[akka] (handler: FaultHandlingStrategy) exten val i = self.linkedActors.values.iterator while(i.hasNext) { val ref = i.next - ref.stop + ref.stop() self.unlink(ref) } } diff --git a/akka-actor/src/main/scala/akka/actor/UntypedActor.scala b/akka-actor/src/main/scala/akka/actor/UntypedActor.scala index 35e3746145..77500d4059 100644 --- a/akka-actor/src/main/scala/akka/actor/UntypedActor.scala +++ b/akka-actor/src/main/scala/akka/actor/UntypedActor.scala @@ -93,7 +93,7 @@ abstract class UntypedActor extends Actor { /** * User overridable callback. *

- * Is called when 'actor.stop' is invoked. + * Is called when 'actor.stop()' is invoked. */ override def postStop {} diff --git a/akka-actor/src/main/scala/akka/dataflow/DataFlow.scala b/akka-actor/src/main/scala/akka/dataflow/DataFlow.scala index 0eb28a4da2..7ac900333d 100644 --- a/akka-actor/src/main/scala/akka/dataflow/DataFlow.scala +++ b/akka-actor/src/main/scala/akka/dataflow/DataFlow.scala @@ -52,7 +52,7 @@ object DataFlow { private class ReactiveEventBasedThread[A <: AnyRef, T <: AnyRef](body: A => T) extends Actor { def receive = { - case Exit => self.stop + case Exit => self.stop() case message => self.reply(body(message.asInstanceOf[A])) } } @@ -84,7 +84,7 @@ object DataFlow { dataFlow.blockedReaders.poll ! s } else throw new DataFlowVariableException( "Attempt to change data flow variable (from [" + dataFlow.value.get + "] to [" + v + "])") - case Exit => self.stop + case Exit => self.stop() } } @@ -97,7 +97,7 @@ object DataFlow { case None => readerFuture = self.senderFuture } case Set(v:T) => readerFuture.map(_ completeWithResult v) - case Exit => self.stop + case Exit => self.stop() } } diff --git a/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala b/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala index a4387fadf1..d9129d422d 100644 --- a/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala +++ b/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala @@ -143,7 +143,7 @@ trait MessageDispatcher { while (i.hasNext()) { val uuid = i.next() Actor.registry.actorFor(uuid) match { - case Some(actor) => actor.stop + case Some(actor) => actor.stop() case None => {} } } diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index 157280dc9d..081b622f39 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -144,7 +144,7 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule } def shutdown { - eventHandler.stop + eventHandler.stop() removeListener(eventHandler) this.shutdownClientModule this.shutdownServerModule @@ -159,7 +159,7 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule * val actor = actorOf(classOf[MyActor],"www.akka.io", 2552) * actor.start() * actor ! message - * actor.stop + * actor.stop() * * You can create and start the actor in one statement like this: *

@@ -178,7 +178,7 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule
    *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552)
    *   actor.start()
    *   actor ! message
-   *   actor.stop
+   *   actor.stop()
    * 
* You can create and start the actor in one statement like this: *
@@ -206,7 +206,7 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule
    *   val actor = actorOf[MyActor]("www.akka.io",2552)
    *   actor.start()
    *   actor ! message
-   *   actor.stop
+   *   actor.stop()
    * 
* You can create and start the actor in one statement like this: *
diff --git a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala
index 09224a7029..777c048d70 100644
--- a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala
+++ b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala
@@ -37,7 +37,7 @@ trait ListenerManagement {
    */
   def removeListener(listener: ActorRef) {
     listeners remove listener
-    if (manageLifeCycleOfListeners) listener.stop
+    if (manageLifeCycleOfListeners) listener.stop()
   }
 
   /*
diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst
index 2f3f9e518f..e0f6be1954 100644
--- a/akka-docs/manual/getting-started-first.rst
+++ b/akka-docs/manual/getting-started-first.rst
@@ -290,7 +290,7 @@ But we are not done yet. We are missing the message handler for the ``Master`` a
 - ``Calculate`` -- which should start the calculation
 - ``Result`` -- which should aggregate the different results
 
-The ``Calculate`` handler is sending out work to all the ``Worker`` actors and after doing that it also sends a ``Broadcast(PoisonPill)`` message to the router, which will send out the ``PoisonPill`` message to all the actors it is representing (in our case all the ``Worker`` actors). The ``PoisonPill`` is a special kind of message that tells the receiver to shut himself down using the normal shutdown; ``self.stop``. Then we also send a ``PoisonPill`` to the router itself (since it's also an actor that we want to shut down).
+The ``Calculate`` handler is sending out work to all the ``Worker`` actors and after doing that it also sends a ``Broadcast(PoisonPill)`` message to the router, which will send out the ``PoisonPill`` message to all the actors it is representing (in our case all the ``Worker`` actors). The ``PoisonPill`` is a special kind of message that tells the receiver to shut himself down using the normal shutdown; ``self.stop()``. Then we also send a ``PoisonPill`` to the router itself (since it's also an actor that we want to shut down).
 
 The ``Result`` handler is simpler, here we just get the value  from the ``Result`` message and aggregate it to our ``pi`` member variable. We also keep track of how many results we have received back and if it matches the number of tasks sent out the ``Master`` actor considers itself done and shuts himself down.
 
diff --git a/akka-docs/pending/actors-scala.rst b/akka-docs/pending/actors-scala.rst
index 17af9dc168..3999d364b0 100644
--- a/akka-docs/pending/actors-scala.rst
+++ b/akka-docs/pending/actors-scala.rst
@@ -393,7 +393,7 @@ Actors are stopped by invoking the ``stop`` method.
 
 .. code-block:: scala
 
-  actor.stop
+  actor.stop()
 
 When stop is called then a call to the ``def postStop`` callback method will take place. The ``Actor`` can use this callback to implement shutdown behavior.
 
diff --git a/akka-docs/pending/stm-scala.rst b/akka-docs/pending/stm-scala.rst
index e6ab56a92c..0e1249fc48 100644
--- a/akka-docs/pending/stm-scala.rst
+++ b/akka-docs/pending/stm-scala.rst
@@ -362,7 +362,7 @@ Here is an example of using ``retry`` to block until an account has enough money
   atomic { account2.get }
   // -> 600.0
 
-  transferer.stop
+  transferer.stop()
 
 Alternative blocking transactions
 ---------------------------------
@@ -414,7 +414,7 @@ You can also have two alternative blocking transactions, one of which can succee
   // INFO Brancher: not enough on left - retrying
   // INFO Brancher: going right
 
-  brancher.stop
+  brancher.stop()
 
 ----
 
diff --git a/akka-docs/pending/testkit-example.rst b/akka-docs/pending/testkit-example.rst
index 96d851787e..611ba4dea6 100644
--- a/akka-docs/pending/testkit-example.rst
+++ b/akka-docs/pending/testkit-example.rst
@@ -27,10 +27,10 @@ class TestKitUsageSpec extends WordSpec with BeforeAndAfterAll with ShouldMatche
 
   override protected def afterAll(): scala.Unit = {
     stopTestActor
-    echoRef.stop
-    forwardRef.stop
-    filterRef.stop
-    seqRef.stop
+    echoRef.stop()
+    forwardRef.stop()
+    filterRef.stop()
+    seqRef.stop()
   }
 
   "An EchoActor" should {
diff --git a/akka-docs/pending/transactors-scala.rst b/akka-docs/pending/transactors-scala.rst
index ae7853266b..454fffb6a6 100644
--- a/akka-docs/pending/transactors-scala.rst
+++ b/akka-docs/pending/transactors-scala.rst
@@ -67,8 +67,8 @@ Here is an example of coordinating two simple counter Actors so that they both i
 
   counter1 !! GetCount // Some(1)
 
-  counter1.stop
-  counter2.stop
+  counter1.stop()
+  counter2.stop()
 
 To start a new coordinated transaction set that you will also participate in, just create a ``Coordinated`` object:
 
diff --git a/akka-docs/pending/tutorial-chat-server-scala.rst b/akka-docs/pending/tutorial-chat-server-scala.rst
index ce0a1764c2..9d35abddd9 100644
--- a/akka-docs/pending/tutorial-chat-server-scala.rst
+++ b/akka-docs/pending/tutorial-chat-server-scala.rst
@@ -192,7 +192,7 @@ There are two ways you can define an Actor to be a supervisor; declaratively and
 
 The last thing we have to do to supervise Actors (in our example the storage Actor) is to 'link' the Actor. Invoking 'link(actor)' will create a link between the Actor passed as argument into 'link' and ourselves. This means that we will now get a notification if the linked Actor is crashing and if the cause of the crash, the exception, matches one of the exceptions in our 'trapExit' list then the crashed Actor is restarted according the the fault handling strategy defined in our 'faultHandler'. We also have the 'unlink(actor)' function which disconnects the linked Actor from the supervisor.
 
-In our example we are using a method called 'spawnLink(actor)' which creates, starts and links the Actor in an atomic operation. The linking and unlinking is done in 'preStart' and 'postStop' callback methods which are invoked by the runtime when the Actor is started and shut down (shutting down is done by invoking 'actor.stop'). In these methods we initialize our Actor, by starting and linking the storage Actor and clean up after ourselves by shutting down all the user session Actors and the storage Actor.
+In our example we are using a method called 'spawnLink(actor)' which creates, starts and links the Actor in an atomic operation. The linking and unlinking is done in 'preStart' and 'postStop' callback methods which are invoked by the runtime when the Actor is started and shut down (shutting down is done by invoking 'actor.stop()'). In these methods we initialize our Actor, by starting and linking the storage Actor and clean up after ourselves by shutting down all the user session Actors and the storage Actor.
 
 That is it. Now we have implemented the supervising part of the fault-tolerance for the storage Actor. But before we dive into the 'ChatServer' code there are some more things worth mentioning about its implementation.
 
@@ -225,7 +225,7 @@ I'll try to show you how we can make use Scala's mixins to decouple the Actor im
       EventHandler.info(this, "Chat server is shutting down...")
       shutdownSessions
       self.unlink(storage)
-      storage.stop
+      storage.stop()
     }
   }
 
@@ -266,12 +266,12 @@ The 'shutdownSessions' function simply shuts all the sessions Actors down. That
       case Logout(username) =>
         EventHandler.info(this, "User [%s] has logged out".format(username))
         val session = sessions(username)
-        session.stop
+        session.stop()
         sessions -= username
     }
 
     protected def shutdownSessions =
-      sessions.foreach { case (_, session) => session.stop }
+      sessions.foreach { case (_, session) => session.stop() }
   }
 
 Chat message management
diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala
index a2a0f898ac..64e26cfdf5 100644
--- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala
+++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala
@@ -405,7 +405,7 @@ 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 {
     notifyListeners(RemoteClientShutdown(module, remoteAddress))
-    timer.stop
+    timer.stop()
     timer = null
     openChannels.close.awaitUninterruptibly
     openChannels = null
@@ -973,7 +973,7 @@ class RemoteServerHandler(
     message match { // first match on system messages
       case RemoteActorSystemMessage.Stop =>
         if (UNTRUSTED_MODE) throw new SecurityException("Remote server is operating is untrusted mode, can not stop the actor")
-        else actorRef.stop
+        else actorRef.stop()
       case _: LifeCycleMessage if (UNTRUSTED_MODE) =>
         throw new SecurityException("Remote server is operating is untrusted mode, can not pass on a LifeCycleMessage to the remote actor")
 
diff --git a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala
index 8e21f9daee..15520fe4cb 100644
--- a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala
+++ b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala
@@ -80,7 +80,7 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest {
       clientManaged.getClass must be (classOf[LocalActorRef])
       clientManaged ! "OneWay"
       RemoteActorSpecActorUnidirectional.latch.await(1, TimeUnit.SECONDS) must be (true)
-      clientManaged.stop
+      clientManaged.stop()
     }
 
     "shouldSendOneWayAndReceiveReply" in {
@@ -97,7 +97,7 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest {
       val actor = remote.actorOf[RemoteActorSpecActorBidirectional](host,port).start()
       val result = actor !! ("Hello", 10000)
       "World" must equal (result.get.asInstanceOf[String])
-      actor.stop
+      actor.stop()
     }
 
     "shouldSendBangBangMessageAndReceiveReplyConcurrently" in {
@@ -105,7 +105,7 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest {
       actors.map(_ !!! ("Hello", 10000)) foreach { future =>
         "World" must equal (future.await.result.asInstanceOf[Option[String]].get)
       }
-      actors.foreach(_.stop)
+      actors.foreach(_.stop())
     }
 
     "shouldRegisterActorByUuid" in {
@@ -122,8 +122,8 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest {
 
       (actor2 !! "test").get must equal ("default-test")
 
-      actor1.stop
-      actor2.stop
+      actor1.stop()
+      actor2.stop()
     }
 
     "shouldSendAndReceiveRemoteException" in {
@@ -136,7 +136,7 @@ class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest {
       } catch {
         case e: ExpectedRemoteProblem =>
       }
-      actor.stop
+      actor.stop()
     }
   }
 }
diff --git a/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala b/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala
index f37a47e843..39584726f9 100644
--- a/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala
+++ b/akka-remote/src/test/scala/serialization/SerializableTypeClassActorSpec.scala
@@ -115,8 +115,8 @@ class SerializableTypeClassActorSpec extends
       (actor2 !! "hello").getOrElse("_") should equal("world 3")
 
       actor2.receiveTimeout should equal (Some(1000))
-      actor1.stop
-      actor2.stop
+      actor1.stop()
+      actor2.stop()
     }
 
     it("should be able to serialize and deserialize a MyStatelessActorWithMessagesInMailbox") {
diff --git a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala
index e8c8073636..a19ed26da0 100644
--- a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala
+++ b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala
@@ -131,12 +131,12 @@
       case Logout(username) =>
         EventHandler.info(this, "User [%s] has logged out".format(username))
         val session = sessions(username)
-        session.stop
+        session.stop()
         sessions -= username
     }
 
     protected def shutdownSessions =
-      sessions.foreach { case (_, session) => session.stop }
+      sessions.foreach { case (_, session) => session.stop() }
   }
 
   /**
@@ -190,7 +190,7 @@
       EventHandler.info(this, "Chat server is shutting down...")
       shutdownSessions
       self.unlink(storage)
-      storage.stop
+      storage.stop()
     }
   }
 
diff --git a/akka-stm/src/main/scala/akka/agent/Agent.scala b/akka-stm/src/main/scala/akka/agent/Agent.scala
index ad8d426860..80db8bff21 100644
--- a/akka-stm/src/main/scala/akka/agent/Agent.scala
+++ b/akka-stm/src/main/scala/akka/agent/Agent.scala
@@ -182,7 +182,7 @@ class Agent[T](initialValue: T) {
    * Closes the agents and makes it eligable for garbage collection.
    * A closed agent cannot accept any `send` actions.
    */
-  def close() = updater.stop
+  def close() = updater.stop()
 
   // ---------------------------------------------
   // Support for Java API Functions and Procedures
@@ -250,8 +250,8 @@ class ThreadBasedAgentUpdater[T](agent: Agent[T]) extends Actor {
     case update: Update[T] => {
       atomic(txFactory) { agent.ref alter update.function }
       agent.resume
-      self.stop
+      self.stop()
     }
-    case _ => self.stop
+    case _ => self.stop()
   }
 }
diff --git a/akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala b/akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala
index 77ae01354a..116baa7da7 100644
--- a/akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala
+++ b/akka-stm/src/test/scala/transactor/CoordinatedIncrementSpec.scala
@@ -66,8 +66,8 @@ class CoordinatedIncrementSpec extends WordSpec with MustMatchers {
       for (counter <- counters) {
         (counter !! GetCount).get must be === 1
       }
-      counters foreach (_.stop)
-      failer.stop
+      counters foreach (_.stop())
+      failer.stop()
     }
 
     "increment no counters with a failing transaction" in {
@@ -78,8 +78,8 @@ class CoordinatedIncrementSpec extends WordSpec with MustMatchers {
       for (counter <- counters) {
         (counter !! GetCount).get must be === 0
       }
-      counters foreach (_.stop)
-      failer.stop
+      counters foreach (_.stop())
+      failer.stop()
     }
   }
 }
diff --git a/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala b/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala
index bdba458261..ab56ab84c9 100644
--- a/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala
+++ b/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala
@@ -113,8 +113,8 @@ class FickleFriendsSpec extends WordSpec with MustMatchers {
       for (counter <- counters) {
         (counter !! GetCount).get must be === 1
       }
-      counters foreach (_.stop)
-      coordinator.stop
+      counters foreach (_.stop())
+      coordinator.stop()
     }
   }
 }
diff --git a/akka-stm/src/test/scala/transactor/TransactorSpec.scala b/akka-stm/src/test/scala/transactor/TransactorSpec.scala
index 919692d82b..8faf8a4d43 100644
--- a/akka-stm/src/test/scala/transactor/TransactorSpec.scala
+++ b/akka-stm/src/test/scala/transactor/TransactorSpec.scala
@@ -94,8 +94,8 @@ class TransactorSpec extends WordSpec with MustMatchers {
       for (counter <- counters) {
         (counter !! GetCount).get must be === 1
       }
-      counters foreach (_.stop)
-      failer.stop
+      counters foreach (_.stop())
+      failer.stop()
     }
 
     "increment no counters with a failing transaction" in {
@@ -106,8 +106,8 @@ class TransactorSpec extends WordSpec with MustMatchers {
       for (counter <- counters) {
         (counter !! GetCount).get must be === 0
       }
-      counters foreach (_.stop)
-      failer.stop
+      counters foreach (_.stop())
+      failer.stop()
     }
   }
 
@@ -120,7 +120,7 @@ class TransactorSpec extends WordSpec with MustMatchers {
       latch.await(timeout.length, timeout.unit)
       val value = atomic { ref.get }
       value must be === 5
-      transactor.stop
+      transactor.stop()
     }
   }
 }
diff --git a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala
index 6b9406767b..397635c628 100644
--- a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala
+++ b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala
@@ -98,7 +98,7 @@ trait TestKit {
    * Stop test actor. Should be done at the end of the test unless relying on
    * test actor timeout.
    */
-  def stopTestActor { testActor.stop }
+  def stopTestActor { testActor.stop() }
 
   /**
    * Set test actor timeout. By default, the test actor shuts itself down
diff --git a/akka-tutorials/akka-tutorial-second/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-second/src/main/scala/Pi.scala
index 0f9da10976..e7e10f56ef 100644
--- a/akka-tutorials/akka-tutorial-second/src/main/scala/Pi.scala
+++ b/akka-tutorials/akka-tutorial-second/src/main/scala/Pi.scala
@@ -103,7 +103,7 @@ object Pi extends App {
           // send the pi result back to the guy who started the calculation
           recipient ! pi
           // shut ourselves down, we're done
-          self.stop
+          self.stop()
         }
     }
 
diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala
index 1f1c840162..c25758b2e6 100644
--- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala
+++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala
@@ -165,7 +165,7 @@ abstract class TypedActor extends Actor with Proxyable {
   /**
    * User overridable callback.
    * 

- * Is called when 'actor.stop' is invoked. + * Is called when 'actor.stop()' is invoked. */ override def postStop {} @@ -1050,7 +1050,7 @@ private[akka] object AspectInitRegistry extends ListenerManagement { val init = if (proxy ne null) initializations.remove(proxy) else null if (init ne null) { notifyListeners(AspectInitUnregistered(proxy, init)) - init.actorRef.stop + init.actorRef.stop() } init } diff --git a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala index 883ca6ac3a..2032c71e81 100644 --- a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala +++ b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala @@ -135,7 +135,7 @@ class TypedActorSpec extends val typedActors2 = Actor.registry.typedActorsFor("my-custom-id") assert(typedActors2.length === 1) assert(typedActors2.contains(pojo)) - actorRef.stop + actorRef.stop() } it("should support to filter typed actors") { From 3c903ca0771b41aec1af9d46a077b6c289ea0976 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 12 Apr 2011 10:59:45 +0200 Subject: [PATCH 061/147] Added parens to shutdownAll --- .../akka/actor/supervisor/Ticket669Spec.scala | 2 +- .../scala/akka/dataflow/DataFlowSpec.scala | 4 +-- .../scala/akka/misc/ActorRegistrySpec.scala | 30 +++++++++---------- .../test/scala/akka/misc/SchedulerSpec.scala | 2 +- .../actor/BootableActorLoaderService.scala | 2 +- akka-docs/pending/actor-registry-scala.rst | 2 +- akka-docs/pending/actors-scala.rst | 2 +- akka-docs/pending/fsm-scala.rst | 2 +- .../test/scala/remote/AkkaRemoteTest.scala | 2 +- .../actor/typed-actor/Issue675Spec.scala | 2 +- .../typed-actor/TypedActorRegistrySpec.scala | 20 ++++++------- .../actor/typed-actor/TypedActorSpec.scala | 2 +- 12 files changed, 36 insertions(+), 36 deletions(-) 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 6b43201c3f..09ad6860da 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 @@ -14,7 +14,7 @@ import org.scalatest.matchers.MustMatchers class Ticket669Spec extends WordSpec with MustMatchers with BeforeAndAfterAll { import Ticket669Spec._ - override def afterAll = Actor.registry.shutdownAll + override def afterAll = Actor.registry.shutdownAll() "A supervised actor with lifecycle PERMANENT" should { "be able to reply on failure during preRestart" in { diff --git a/akka-actor-tests/src/test/scala/akka/dataflow/DataFlowSpec.scala b/akka-actor-tests/src/test/scala/akka/dataflow/DataFlowSpec.scala index f5a107f511..65e259edfb 100644 --- a/akka-actor-tests/src/test/scala/akka/dataflow/DataFlowSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dataflow/DataFlowSpec.scala @@ -72,7 +72,7 @@ class DataFlowTest extends Spec with ShouldMatchers with BeforeAndAfterAll { /* it("should be able to join streams") { import DataFlow._ - Actor.registry.shutdownAll + Actor.registry.shutdownAll() def ints(n: Int, max: Int, stream: DataFlowStream[Int]): Unit = if (n != max) { stream <<< n @@ -139,7 +139,7 @@ class DataFlowTest extends Spec with ShouldMatchers with BeforeAndAfterAll { /* it("should be able to conditionally set variables") { import DataFlow._ - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val latch = new CountDownLatch(1) val x, y, z, v = new DataFlowVariable[Int] diff --git a/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala b/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala index bd3220b3d8..7b01efd7dd 100644 --- a/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala @@ -33,7 +33,7 @@ class ActorRegistrySpec extends JUnitSuite { import ActorRegistrySpec._ @Test def shouldGetActorByIdFromActorRegistry { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor = actorOf[TestActor] actor.start() val actors = Actor.registry.actorsFor("MyID") @@ -44,7 +44,7 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetActorByUUIDFromActorRegistry { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor = actorOf[TestActor] val uuid = actor.uuid actor.start() @@ -55,7 +55,7 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetActorByClassFromActorRegistry { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor = actorOf[TestActor] actor.start() val actors = Actor.registry.actorsFor(classOf[TestActor]) @@ -66,7 +66,7 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetActorByManifestFromActorRegistry { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor = actorOf[TestActor] actor.start() val actors = Actor.registry.actorsFor[TestActor] @@ -77,7 +77,7 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldFindThingsFromActorRegistry { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor = actorOf[TestActor] actor.start() val found = Actor.registry.find({ case a: ActorRef if a.actor.isInstanceOf[TestActor] => a }) @@ -88,7 +88,7 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetActorsByIdFromActorRegistry { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor1 = actorOf[TestActor] actor1.start() val actor2 = actorOf[TestActor] @@ -104,7 +104,7 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetActorsByClassFromActorRegistry { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor1 = actorOf[TestActor] actor1.start() val actor2 = actorOf[TestActor] @@ -120,7 +120,7 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetActorsByManifestFromActorRegistry { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor1 = actorOf[TestActor] actor1.start() val actor2 = actorOf[TestActor] @@ -137,7 +137,7 @@ class ActorRegistrySpec extends JUnitSuite { @Test def shouldGetActorsByMessageFromActorRegistry { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor1 = actorOf[TestActor] actor1.start() val actor2 = actorOf[TestActor2] @@ -164,7 +164,7 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetAllActorsFromActorRegistry { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor1 = actorOf[TestActor] actor1.start() val actor2 = actorOf[TestActor] @@ -180,7 +180,7 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldGetResponseByAllActorsInActorRegistryWhenInvokingForeach { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor1 = actorOf[TestActor] actor1.start() val actor2 = actorOf[TestActor] @@ -193,17 +193,17 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldShutdownAllActorsInActorRegistry { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor1 = actorOf[TestActor] actor1.start() val actor2 = actorOf[TestActor] actor2.start() - Actor.registry.shutdownAll + Actor.registry.shutdownAll() assert(Actor.registry.actors.size === 0) } @Test def shouldRemoveUnregisterActorInActorRegistry { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val actor1 = actorOf[TestActor] actor1.start() val actor2 = actorOf[TestActor] @@ -216,7 +216,7 @@ class ActorRegistrySpec extends JUnitSuite { } @Test def shouldBeAbleToRegisterActorsConcurrently { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() def mkTestActors = for(i <- (1 to 10).toList;j <- 1 to 3000) yield actorOf( new Actor { self.id = i.toString diff --git a/akka-actor-tests/src/test/scala/akka/misc/SchedulerSpec.scala b/akka-actor-tests/src/test/scala/akka/misc/SchedulerSpec.scala index a81dec43f2..dc527d8c17 100644 --- a/akka-actor-tests/src/test/scala/akka/misc/SchedulerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/misc/SchedulerSpec.scala @@ -12,7 +12,7 @@ class SchedulerSpec extends JUnitSuite { def withCleanEndState(action: => Unit) { action Scheduler.restart - Actor.registry.shutdownAll + Actor.registry.shutdownAll() } diff --git a/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala b/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala index 4b96f9ab5d..48c1127f84 100644 --- a/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala +++ b/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala @@ -56,6 +56,6 @@ trait BootableActorLoaderService extends Bootable { abstract override def onUnload = { super.onUnload - Actor.registry.shutdownAll + Actor.registry.shutdownAll() } } diff --git a/akka-docs/pending/actor-registry-scala.rst b/akka-docs/pending/actor-registry-scala.rst index 2f98c6d8a9..5f57434501 100644 --- a/akka-docs/pending/actor-registry-scala.rst +++ b/akka-docs/pending/actor-registry-scala.rst @@ -60,7 +60,7 @@ The ActorRegistry also has a 'shutdownAll' and 'foreach' methods: def foreach(f: (ActorRef) => Unit) def foreachTypedActor(f: (AnyRef) => Unit) - def shutdownAll + def shutdownAll() If you need to know when a new Actor is added or removed from the registry, you can use the subscription API. You can register an Actor that should be notified when an event happens in the ActorRegistry: diff --git a/akka-docs/pending/actors-scala.rst b/akka-docs/pending/actors-scala.rst index 3999d364b0..75e5b91f75 100644 --- a/akka-docs/pending/actors-scala.rst +++ b/akka-docs/pending/actors-scala.rst @@ -407,7 +407,7 @@ You can shut down all Actors in the system by invoking: .. code-block:: scala - Actor.registry.shutdownAll + Actor.registry.shutdownAll() PoisonPill diff --git a/akka-docs/pending/fsm-scala.rst b/akka-docs/pending/fsm-scala.rst index 7b2d5affa2..9471b39165 100644 --- a/akka-docs/pending/fsm-scala.rst +++ b/akka-docs/pending/fsm-scala.rst @@ -205,7 +205,7 @@ To use the Lock you can run a small program like this: lock ! '3' lock ! '4' - Actor.registry.shutdownAll + Actor.registry.shutdownAll() exit } } diff --git a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala index 897a14ca70..0cdd850283 100644 --- a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala +++ b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala @@ -52,7 +52,7 @@ class AkkaRemoteTest extends override def afterEach() { remote.shutdown - Actor.registry.shutdownAll + Actor.registry.shutdownAll() super.afterEach } diff --git a/akka-typed-actor/src/test/scala/actor/typed-actor/Issue675Spec.scala b/akka-typed-actor/src/test/scala/actor/typed-actor/Issue675Spec.scala index e978b61c45..a30bef028d 100644 --- a/akka-typed-actor/src/test/scala/actor/typed-actor/Issue675Spec.scala +++ b/akka-typed-actor/src/test/scala/actor/typed-actor/Issue675Spec.scala @@ -38,7 +38,7 @@ class Issue675Spec extends BeforeAndAfterEach { override def afterEach() { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() } describe("TypedActor preStart method") { diff --git a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorRegistrySpec.scala b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorRegistrySpec.scala index 0a031026ef..a83dccc18d 100644 --- a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorRegistrySpec.scala +++ b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorRegistrySpec.scala @@ -17,34 +17,34 @@ class TypedActorRegistrySpec extends WordSpec with MustMatchers { "Typed Actor" should { "be able to be retreived from the registry by class" in { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val my = TypedActor.newInstance[My](classOf[My], classOf[MyImpl], 3000) val actors = Actor.registry.typedActorsFor(classOf[My]) actors.length must be (1) - Actor.registry.shutdownAll + Actor.registry.shutdownAll() } "be able to be retreived from the registry by manifest" in { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val my = TypedActor.newInstance[My](classOf[My], classOf[MyImpl], 3000) val option = Actor.registry.typedActorFor[My] option must not be (null) option.isDefined must be (true) - Actor.registry.shutdownAll + Actor.registry.shutdownAll() } "be able to be retreived from the registry by class two times" in { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val my = TypedActor.newInstance[My](classOf[My], classOf[MyImpl], 3000) val actors1 = Actor.registry.typedActorsFor(classOf[My]) actors1.length must be (1) val actors2 = Actor.registry.typedActorsFor(classOf[My]) actors2.length must be (1) - Actor.registry.shutdownAll + Actor.registry.shutdownAll() } "be able to be retreived from the registry by manifest two times" in { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val my = TypedActor.newInstance[My](classOf[My], classOf[MyImpl], 3000) val option1 = Actor.registry.typedActorFor[My] option1 must not be (null) @@ -52,11 +52,11 @@ class TypedActorRegistrySpec extends WordSpec with MustMatchers { val option2 = Actor.registry.typedActorFor[My] option2 must not be (null) option2.isDefined must be (true) - Actor.registry.shutdownAll + Actor.registry.shutdownAll() } "be able to be retreived from the registry by manifest two times (even when created in supervisor)" in { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() val manager = new TypedActorConfigurator manager.configure( OneForOneStrategy(classOf[Exception] :: Nil, 3, 1000), @@ -69,7 +69,7 @@ class TypedActorRegistrySpec extends WordSpec with MustMatchers { val option2 = Actor.registry.typedActorFor[My] option2 must not be (null) option2.isDefined must be (true) - Actor.registry.shutdownAll + Actor.registry.shutdownAll() } } } diff --git a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala index 2032c71e81..6391b2bc51 100644 --- a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala +++ b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorSpec.scala @@ -68,7 +68,7 @@ class TypedActorSpec extends } override def afterEach() { - Actor.registry.shutdownAll + Actor.registry.shutdownAll() } describe("TypedActor") { From 1f9d54dcedf047ab9fb7ebfac6604d723b4b2ecc Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 12 Apr 2011 11:03:36 +0200 Subject: [PATCH 062/147] Added parens to unbecome --- .../src/test/scala/akka/actor/actor/HotSwapSpec.scala | 2 +- akka-actor/src/main/scala/akka/actor/Actor.scala | 4 ++-- akka-docs/pending/actors-scala.rst | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/HotSwapSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/HotSwapSpec.scala index eb020eebac..6cb1da93e8 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/HotSwapSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/HotSwapSpec.scala @@ -119,7 +119,7 @@ class HotSwapSpec extends WordSpec with MustMatchers { _log += "swapped" barrier.await case "revert" => - unbecome + unbecome() }) barrier.await } diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index 6adbdffe2e..ee3c48374f 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -422,7 +422,7 @@ trait Actor { * If "discardOld" is true, an unbecome will be issued prior to pushing the new behavior to the stack */ def become(behavior: Receive, discardOld: Boolean = true) { - if (discardOld) unbecome + if (discardOld) unbecome() self.hotswap = self.hotswap.push(behavior) } @@ -454,7 +454,7 @@ trait Actor { private final def autoReceiveMessage(msg: AutoReceivedMessage): Unit = msg match { case HotSwap(code, discardOld) => become(code(self), discardOld) - case RevertHotSwap => unbecome + case RevertHotSwap => unbecome() case Exit(dead, reason) => self.handleTrapExit(dead, reason) case Link(child) => self.link(child) case Unlink(child) => self.unlink(child) diff --git a/akka-docs/pending/actors-scala.rst b/akka-docs/pending/actors-scala.rst index 75e5b91f75..fc456ba71e 100644 --- a/akka-docs/pending/actors-scala.rst +++ b/akka-docs/pending/actors-scala.rst @@ -473,7 +473,7 @@ Here is another little cute example of ``become`` and ``unbecome`` in action: become { case Swap => println("Ho") - unbecome // resets the latest 'become' (just for fun) + unbecome() // resets the latest 'become' (just for fun) } } } @@ -511,7 +511,7 @@ Revert the Actor body using the ``unbecome`` method: .. code-block:: scala def receive: Receive = { - case "revert" => unbecome + case "revert" => unbecome() } Killing an Actor From 178171bbef185b702098c0b97c93773768a19037 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 12 Apr 2011 11:35:10 +0200 Subject: [PATCH 063/147] Added parens to latch countDown --- .../main/scala/akka/testing/TestLatch.scala | 6 ++--- .../scala/akka/actor/actor/ActorRefSpec.scala | 8 +++---- .../akka/actor/actor/ForwardActorSpec.scala | 4 ++-- .../supervisor/RestartStrategySpec.scala | 8 +++---- .../supervisor/SupervisorHierarchySpec.scala | 4 ++-- .../actor/supervisor/SupervisorMiscSpec.scala | 8 +++---- .../akka/actor/supervisor/Ticket669Spec.scala | 4 ++-- .../scala/akka/dataflow/DataFlowSpec.scala | 10 ++++---- .../scala/akka/dispatch/ActorModelSpec.scala | 4 ++-- ...rBasedEventDrivenDispatcherActorSpec.scala | 12 +++++----- ...BasedEventDrivenDispatcherActorsSpec.scala | 4 ++-- ...ventDrivenWorkStealingDispatcherSpec.scala | 2 +- .../akka/dispatch/ThreadBasedActorSpec.scala | 2 +- .../scala/akka/misc/ActorRegistrySpec.scala | 2 +- .../test/scala/akka/misc/SchedulerSpec.scala | 14 +++++------ .../test/scala/akka/routing/RoutingSpec.scala | 24 +++++++++---------- .../src/main/scala/akka/actor/ActorRef.scala | 2 +- akka-docs/pending/routing-scala.rst | 4 ++-- .../test/scala/remote/AkkaRemoteTest.scala | 2 +- .../ClientInitiatedRemoteActorSpec.scala | 6 ++--- .../RemoteErrorHandlingNetworkTest.scala | 2 +- .../ServerInitiatedRemoteActorSpec.scala | 6 ++--- .../src/test/scala/ticket/Ticket506Spec.scala | 2 +- akka-stm/src/test/scala/agent/AgentSpec.scala | 4 ++-- .../scala/transactor/FickleFriendsSpec.scala | 2 +- .../scala/transactor/TransactorSpec.scala | 6 ++--- .../TypedActorUtilFunctionsSpec.scala | 2 +- 27 files changed, 77 insertions(+), 77 deletions(-) diff --git a/akka-actor-tests/src/main/scala/akka/testing/TestLatch.scala b/akka-actor-tests/src/main/scala/akka/testing/TestLatch.scala index b975145963..aeefa04dd7 100644 --- a/akka-actor-tests/src/main/scala/akka/testing/TestLatch.scala +++ b/akka-actor-tests/src/main/scala/akka/testing/TestLatch.scala @@ -27,9 +27,9 @@ object TestLatch { class TestLatch(count: Int = 1) { private var latch = new CountDownLatch(count) - def countDown = latch.countDown + def countDown() = latch.countDown() - def open = countDown + def open() = countDown() def await(): Boolean = await(TestLatch.DefaultTimeout) @@ -50,6 +50,6 @@ class TestLatch(count: Int = 1) { opened } - def reset = latch = new CountDownLatch(count) + def reset() = latch = new CountDownLatch(count) } 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 3976becb14..6c4374809e 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 @@ -61,10 +61,10 @@ object ActorRefSpec { case "complex2" => replyActor ! "complexRequest2" case "simple" => replyActor ! "simpleRequest" case "complexReply" => { - latch.countDown + latch.countDown() } case "simpleReply" => { - latch.countDown + latch.countDown() } } } @@ -160,8 +160,8 @@ class ActorRefSpec extends WordSpec with MustMatchers { val ref = Actor.actorOf( new Actor { def receive = { case _ => } - override def preRestart(reason: Throwable) = latch.countDown - override def postRestart(reason: Throwable) = latch.countDown + override def preRestart(reason: Throwable) = latch.countDown() + override def postRestart(reason: Throwable) = latch.countDown() } ).start() diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/ForwardActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/ForwardActorSpec.scala index ad965f838d..29d9fc5e10 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/ForwardActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/ForwardActorSpec.scala @@ -23,7 +23,7 @@ object ForwardActorSpec { def receive = { case "SendBang" => { ForwardState.sender = self.sender - latch.countDown + latch.countDown() } case "SendBangBang" => self.reply("SendBangBang") } @@ -53,7 +53,7 @@ object ForwardActorSpec { val forwardActor = actorOf[ForwardActor] forwardActor.start() (forwardActor !! "SendBangBang") match { - case Some(_) => latch.countDown + case Some(_) => latch.countDown() case None => {} } def receive = { diff --git a/akka-actor-tests/src/test/scala/akka/actor/supervisor/RestartStrategySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/supervisor/RestartStrategySpec.scala index 59dd191bc6..f2a3103d08 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/supervisor/RestartStrategySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/supervisor/RestartStrategySpec.scala @@ -36,7 +36,7 @@ class RestartStrategySpec extends JUnitSuite { val slave = actorOf(new Actor{ protected def receive = { - case Ping => countDownLatch.countDown + case Ping => countDownLatch.countDown() case Crash => throw new Exception("Crashing...") } override def postRestart(reason: Throwable) = { @@ -91,7 +91,7 @@ class RestartStrategySpec extends JUnitSuite { } override def postRestart(reason: Throwable) = { - countDownLatch.countDown + countDownLatch.countDown() } }) @@ -179,7 +179,7 @@ class RestartStrategySpec extends JUnitSuite { val slave = actorOf(new Actor{ protected def receive = { - case Ping => countDownLatch.countDown + case Ping => countDownLatch.countDown() case Crash => throw new Exception("Crashing...") } override def postRestart(reason: Throwable) = { @@ -235,7 +235,7 @@ class RestartStrategySpec extends JUnitSuite { val slave = actorOf(new Actor{ protected def receive = { - case Ping => countDownLatch.countDown + case Ping => countDownLatch.countDown() case Crash => throw new Exception("Crashing...") } diff --git a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala index 4a0581477e..529d2ef208 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorHierarchySpec.scala @@ -17,7 +17,7 @@ object SupervisorHierarchySpec { class CountDownActor(countDown: CountDownLatch) extends Actor { protected def receive = { case _ => () } - override def postRestart(reason: Throwable) = countDown.countDown + override def postRestart(reason: Throwable) = countDown.countDown() } class CrasherActor extends Actor { @@ -65,7 +65,7 @@ class SupervisorHierarchySpec extends JUnitSuite { self.faultHandler = OneForOneStrategy(List(classOf[Throwable]), 1, 5000) protected def receive = { case MaximumNumberOfRestartsWithinTimeRangeReached(_, _, _, _) => - countDown.countDown + countDown.countDown() } }).start() boss.startLink(crasher) diff --git a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorMiscSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorMiscSpec.scala index 0db5cc9db4..5a812c3f8a 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorMiscSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/supervisor/SupervisorMiscSpec.scala @@ -17,7 +17,7 @@ class SupervisorMiscSpec extends WordSpec with MustMatchers { val actor1 = Actor.actorOf(new Actor { self.dispatcher = Dispatchers.newThreadBasedDispatcher(self) - override def postRestart(cause: Throwable) {countDownLatch.countDown} + override def postRestart(cause: Throwable) {countDownLatch.countDown()} protected def receive = { case "kill" => throw new Exception("killed") @@ -27,7 +27,7 @@ class SupervisorMiscSpec extends WordSpec with MustMatchers { val actor2 = Actor.actorOf(new Actor { self.dispatcher = Dispatchers.newThreadBasedDispatcher(self) - override def postRestart(cause: Throwable) {countDownLatch.countDown} + override def postRestart(cause: Throwable) {countDownLatch.countDown()} protected def receive = { case "kill" => throw new Exception("killed") @@ -37,7 +37,7 @@ class SupervisorMiscSpec extends WordSpec with MustMatchers { val actor3 = Actor.actorOf(new Actor { self.dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher("test").build - override def postRestart(cause: Throwable) {countDownLatch.countDown} + override def postRestart(cause: Throwable) {countDownLatch.countDown()} protected def receive = { case "kill" => throw new Exception("killed") @@ -47,7 +47,7 @@ class SupervisorMiscSpec extends WordSpec with MustMatchers { val actor4 = Actor.actorOf(new Actor { self.dispatcher = Dispatchers.newThreadBasedDispatcher(self) - override def postRestart(cause: Throwable) {countDownLatch.countDown} + override def postRestart(cause: Throwable) {countDownLatch.countDown()} protected def receive = { case "kill" => throw new Exception("killed") 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 09ad6860da..206d06d1c4 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 @@ -50,8 +50,8 @@ class Ticket669Spec extends WordSpec with MustMatchers with BeforeAndAfterAll { object Ticket669Spec { class Sender(latch: CountDownLatch) extends Actor { def receive = { - case "failure1" => latch.countDown - case "failure2" => latch.countDown + case "failure1" => latch.countDown() + case "failure2" => latch.countDown() case _ => { } } } diff --git a/akka-actor-tests/src/test/scala/akka/dataflow/DataFlowSpec.scala b/akka-actor-tests/src/test/scala/akka/dataflow/DataFlowSpec.scala index 65e259edfb..e0e0a09e6b 100644 --- a/akka-actor-tests/src/test/scala/akka/dataflow/DataFlowSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dataflow/DataFlowSpec.scala @@ -29,7 +29,7 @@ class DataFlowTest extends Spec with ShouldMatchers with BeforeAndAfterAll { thread { z << x() + y() result.set(z()) - latch.countDown + latch.countDown() } thread { x << 40 } thread { y << 2 } @@ -62,7 +62,7 @@ class DataFlowTest extends Spec with ShouldMatchers with BeforeAndAfterAll { thread { z << y() result.set(z()) - latch.countDown + latch.countDown() } latch.await(10,TimeUnit.SECONDS) should equal (true) @@ -93,7 +93,7 @@ class DataFlowTest extends Spec with ShouldMatchers with BeforeAndAfterAll { val t2 = thread { Thread.sleep(1000) result.set(producer.map(x => x * x).foldLeft(0)(_ + _)) - latch.countDown + latch.countDown() } latch.await(3,TimeUnit.SECONDS) should equal (true) @@ -123,7 +123,7 @@ class DataFlowTest extends Spec with ShouldMatchers with BeforeAndAfterAll { val x = stream() if(result.addAndGet(x) == 166666500) - latch.countDown + latch.countDown() recurseSum(stream) } @@ -147,7 +147,7 @@ class DataFlowTest extends Spec with ShouldMatchers with BeforeAndAfterAll { val main = thread { x << 1 z << Math.max(x(),y()) - latch.countDown + latch.countDown() } val setY = thread { 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 075a369cf1..4e60ffcc96 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ActorModelSpec.scala @@ -54,13 +54,13 @@ object ActorModelSpec { case Await(latch) => ack; latch.await(); busy.switchOff() case Meet(sign, wait) => ack; sign.countDown(); wait.await(); busy.switchOff() case Wait(time) => ack; Thread.sleep(time); busy.switchOff() - case WaitAck(time, l) => ack; Thread.sleep(time); l.countDown; 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 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() - case CountDownNStop(l)=> ack; l.countDown; self.stop(); busy.switchOff() + case CountDownNStop(l)=> ack; l.countDown(); self.stop(); busy.switchOff() case Restart => ack; busy.switchOff(); throw new Exception("Restart requested") } } diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala index c7c8a5748d..8020c5acde 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorSpec.scala @@ -25,7 +25,7 @@ object ExecutorBasedEventDrivenDispatcherActorSpec { class OneWayTestActor extends Actor { self.dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher(self.uuid.toString).build def receive = { - case "OneWay" => OneWayTestActor.oneWay.countDown + case "OneWay" => OneWayTestActor.oneWay.countDown() } } } @@ -87,14 +87,14 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { self.dispatcher = throughputDispatcher def receive = { case "hogexecutor" => start.await - case "ping" => if (works.get) latch.countDown + case "ping" => if (works.get) latch.countDown() } }).start() slowOne ! "hogexecutor" (1 to 100) foreach { _ => slowOne ! "ping"} fastOne ! "sabotage" - start.countDown + start.countDown() val result = latch.await(3,TimeUnit.SECONDS) fastOne.stop() slowOne.stop() @@ -115,14 +115,14 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { val fastOne = actorOf( new Actor { self.dispatcher = throughputDispatcher - def receive = { case "ping" => if(works.get) latch.countDown; self.stop() } + def receive = { case "ping" => if(works.get) latch.countDown(); self.stop() } }).start() val slowOne = actorOf( new Actor { self.dispatcher = throughputDispatcher def receive = { - case "hogexecutor" => ready.countDown; start.await + case "hogexecutor" => ready.countDown(); start.await case "ping" => works.set(false); self.stop() } }).start() @@ -132,7 +132,7 @@ class ExecutorBasedEventDrivenDispatcherActorSpec extends JUnitSuite { fastOne ! "ping" assert(ready.await(2,TimeUnit.SECONDS) === true) Thread.sleep(deadlineMs+10) // wait just a bit more than the deadline - start.countDown + start.countDown() assert(latch.await(2,TimeUnit.SECONDS) === true) } } diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala index 261da14814..dfdaf9794d 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala @@ -19,7 +19,7 @@ class ExecutorBasedEventDrivenDispatcherActorsSpec extends JUnitSuite with MustM def receive = { case x: Int => { Thread.sleep(50) // slow actor - finishedCounter.countDown + finishedCounter.countDown() } } } @@ -29,7 +29,7 @@ class ExecutorBasedEventDrivenDispatcherActorsSpec extends JUnitSuite with MustM def receive = { case x: Int => { - finishedCounter.countDown + finishedCounter.countDown() } } } diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala index 941eaf6cc6..833a027c85 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala @@ -25,7 +25,7 @@ object ExecutorBasedEventDrivenWorkStealingDispatcherSpec { case x: Int => { Thread.sleep(delay) invocationCount += 1 - finishedCounter.countDown + finishedCounter.countDown() } } } diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala index 3de303974f..c6d6e2cb46 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ThreadBasedActorSpec.scala @@ -31,7 +31,7 @@ class ThreadBasedActorSpec extends JUnitSuite { val actor = actorOf(new Actor { self.dispatcher = Dispatchers.newThreadBasedDispatcher(self) def receive = { - case "OneWay" => oneWay.countDown + case "OneWay" => oneWay.countDown() } }).start() val result = actor ! "OneWay" diff --git a/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala b/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala index 7b01efd7dd..13fb72f046 100644 --- a/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/misc/ActorRegistrySpec.scala @@ -231,7 +231,7 @@ class ActorRegistrySpec extends JUnitSuite { override def run { barrier.await actors foreach { _.start() } - latch.countDown + latch.countDown() } } val a1,a2,a3 = mkTestActors diff --git a/akka-actor-tests/src/test/scala/akka/misc/SchedulerSpec.scala b/akka-actor-tests/src/test/scala/akka/misc/SchedulerSpec.scala index dc527d8c17..3afb9096fc 100644 --- a/akka-actor-tests/src/test/scala/akka/misc/SchedulerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/misc/SchedulerSpec.scala @@ -21,7 +21,7 @@ class SchedulerSpec extends JUnitSuite { case object Tick val countDownLatch = new CountDownLatch(3) val tickActor = actorOf(new Actor { - def receive = { case Tick => countDownLatch.countDown } + def receive = { case Tick => countDownLatch.countDown() } }).start() // run every 50 millisec Scheduler.schedule(tickActor, Tick, 0, 50, TimeUnit.MILLISECONDS) @@ -31,7 +31,7 @@ class SchedulerSpec extends JUnitSuite { val countDownLatch2 = new CountDownLatch(3) - Scheduler.schedule( () => countDownLatch2.countDown, 0, 50, TimeUnit.MILLISECONDS) + Scheduler.schedule( () => countDownLatch2.countDown(), 0, 50, TimeUnit.MILLISECONDS) // after max 1 second it should be executed at least the 3 times already assert(countDownLatch2.await(1, TimeUnit.SECONDS)) @@ -41,11 +41,11 @@ class SchedulerSpec extends JUnitSuite { case object Tick val countDownLatch = new CountDownLatch(3) val tickActor = actorOf(new Actor { - def receive = { case Tick => countDownLatch.countDown } + def receive = { case Tick => countDownLatch.countDown() } }).start() // run every 50 millisec Scheduler.scheduleOnce(tickActor, Tick, 50, TimeUnit.MILLISECONDS) - Scheduler.scheduleOnce( () => countDownLatch.countDown, 50, TimeUnit.MILLISECONDS) + Scheduler.scheduleOnce( () => countDownLatch.countDown(), 50, TimeUnit.MILLISECONDS) // after 1 second the wait should fail assert(countDownLatch.await(1, TimeUnit.SECONDS) == false) @@ -60,7 +60,7 @@ class SchedulerSpec extends JUnitSuite { object Ping val ticks = new CountDownLatch(1000) val actor = actorOf(new Actor { - def receive = { case Ping => ticks.countDown } + def receive = { case Ping => ticks.countDown() } }).start() val numActors = Actor.registry.actors.length (1 to 1000).foreach( _ => Scheduler.scheduleOnce(actor,Ping,1,TimeUnit.MILLISECONDS) ) @@ -76,7 +76,7 @@ class SchedulerSpec extends JUnitSuite { val ticks = new CountDownLatch(1) val actor = actorOf(new Actor { - def receive = { case Ping => ticks.countDown } + def receive = { case Ping => ticks.countDown() } }).start() (1 to 10).foreach { i => @@ -101,7 +101,7 @@ class SchedulerSpec extends JUnitSuite { self.lifeCycle = Permanent def receive = { - case Ping => pingLatch.countDown + case Ping => pingLatch.countDown() case Crash => throw new Exception("CRASH") } 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 a8a30e6ece..d79bd0651e 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala @@ -54,7 +54,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers val msgs = new java.util.concurrent.ConcurrentSkipListSet[Any] val latch = new CountDownLatch(2) val t1 = actorOf(new Actor { def receive = { case _ => } }).start() - val l = loggerActor(t1,(x) => { msgs.add(x); latch.countDown }).start() + val l = loggerActor(t1,(x) => { msgs.add(x); latch.countDown() }).start() val foo : Any = "foo" val bar : Any = "bar" l ! foo @@ -74,7 +74,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers case x => Thread.sleep(50) // slow actor t1ProcessedCount.incrementAndGet - latch.countDown + latch.countDown() } }).start() @@ -82,7 +82,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers val t2 = actorOf(new Actor { def receive = { case x => t2ProcessedCount.incrementAndGet - latch.countDown + latch.countDown() } }).start() val d = loadBalancerActor(new SmallestMailboxFirstIterator(t1 :: t2 :: Nil)) @@ -108,8 +108,8 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers def receive = { case "bar" => num.incrementAndGet - latch.countDown - case "foo" => foreachListener.countDown + latch.countDown() + case "foo" => foreachListener.countDown() } }).start() @@ -196,7 +196,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers def receive = { case _ => counter.incrementAndGet - latch.countDown + latch.countDown() self reply_? "success" } }) @@ -211,7 +211,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers val successes = new CountDownLatch(2) implicit val successCounterActor = Some(actorOf(new Actor { def receive = { - case "success" => successes.countDown + case "success" => successes.countDown() } }).start()) @@ -283,7 +283,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers case n:Int => Thread.sleep(n) counter.incrementAndGet - latch.countDown + latch.countDown() } }) @@ -356,7 +356,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers case n:Int => Thread.sleep(n) counter.incrementAndGet - latch.countDown + latch.countDown() } }) @@ -421,7 +421,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers def receive = { case _ => delegates put(self.uuid.toString, "") - latch.countDown + latch.countDown() } }) @@ -450,7 +450,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers def receive = { case _ => delegates put(self.uuid.toString, "") - latch.countDown + latch.countDown() } }) @@ -494,7 +494,7 @@ class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers def receive = { case n:Int => Thread.sleep(n) - latch.countDown + latch.countDown() } }) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index cab8ebbf27..71f916b6e8 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -964,7 +964,7 @@ class LocalActorRef private[akka] ( case _ => // either permanent or none where default is permanent val success = try { - performRestart + performRestart() true } catch { case e => diff --git a/akka-docs/pending/routing-scala.rst b/akka-docs/pending/routing-scala.rst index 2923b8240d..4cb825219e 100644 --- a/akka-docs/pending/routing-scala.rst +++ b/akka-docs/pending/routing-scala.rst @@ -222,7 +222,7 @@ Examples def factory = actorOf(new Actor {def receive = {case n:Int => Thread.sleep(n) counter.incrementAndGet - latch.countDown}}) + latch.countDown()}}) def lowerBound = 2 def upperBound = 4 @@ -246,7 +246,7 @@ Examples def factory = actorOf(new Actor {def receive = {case n:Int => Thread.sleep(n) - latch.countDown}}) + latch.countDown()}}) def lowerBound = 1 def upperBound = 5 diff --git a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala index 0cdd850283..9b2b299d25 100644 --- a/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala +++ b/akka-remote/src/test/scala/remote/AkkaRemoteTest.scala @@ -12,7 +12,7 @@ import java.util.concurrent.atomic.AtomicBoolean object AkkaRemoteTest { class ReplyHandlerActor(latch: CountDownLatch, expect: String) extends Actor { def receive = { - case x: String if x == expect => latch.countDown + case x: String if x == expect => latch.countDown() } } } diff --git a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala index 15520fe4cb..af5aaffcc3 100644 --- a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala @@ -21,7 +21,7 @@ class RemoteActorSpecActorUnidirectional extends Actor { def receive = { case "OneWay" => - RemoteActorSpecActorUnidirectional.latch.countDown + RemoteActorSpecActorUnidirectional.latch.countDown() } } @@ -42,7 +42,7 @@ class SendOneWayAndReplyReceiverActor extends Actor { class CountDownActor(latch: CountDownLatch) extends Actor { def receive = { - case "World" => latch.countDown + case "World" => latch.countDown() } } /* @@ -59,7 +59,7 @@ class SendOneWayAndReplySenderActor extends Actor { def receive = { case msg: AnyRef => state = Some(msg) - SendOneWayAndReplySenderActor.latch.countDown + SendOneWayAndReplySenderActor.latch.countDown() } }*/ diff --git a/akka-remote/src/test/scala/remote/RemoteErrorHandlingNetworkTest.scala b/akka-remote/src/test/scala/remote/RemoteErrorHandlingNetworkTest.scala index 2dc7ad65f5..6b882310e7 100644 --- a/akka-remote/src/test/scala/remote/RemoteErrorHandlingNetworkTest.scala +++ b/akka-remote/src/test/scala/remote/RemoteErrorHandlingNetworkTest.scala @@ -45,7 +45,7 @@ object RemoteErrorHandlingNetworkTest { def receive = { case Send(actor: ActorRef) => actor ! "Hello" - case "World" => latch.countDown + case "World" => latch.countDown() } } } diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala index 45e31a5e59..b8f4eb2748 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala @@ -42,7 +42,7 @@ object ServerInitiatedRemoteActorSpec { def receive = { case Send(actor: ActorRef) => actor ! "Hello" - case "World" => latch.countDown + case "World" => latch.countDown() } } } @@ -163,7 +163,7 @@ class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { val actor1 = actorOf[RemoteActorSpecActorUnidirectional] remote.register("foo", actor1) val latch = new CountDownLatch(1) - val actor2 = actorOf(new Actor { def receive = { case "Pong" => latch.countDown } }).start() + val actor2 = actorOf(new Actor { def receive = { case "Pong" => latch.countDown() } }).start() val remoteActor = remote.actorFor("foo", host, port) remoteActor.!("Ping")(Some(actor2)) @@ -191,7 +191,7 @@ class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { if (latch.await(200, TimeUnit.MILLISECONDS)) error("Test didn't complete within 100 cycles") else - latch.countDown + latch.countDown() } val decrementers = Actor.registry.actorsFor[Decrementer] diff --git a/akka-remote/src/test/scala/ticket/Ticket506Spec.scala b/akka-remote/src/test/scala/ticket/Ticket506Spec.scala index cd58c4a6bd..edd65fe47f 100644 --- a/akka-remote/src/test/scala/ticket/Ticket506Spec.scala +++ b/akka-remote/src/test/scala/ticket/Ticket506Spec.scala @@ -16,7 +16,7 @@ class ActorRefService(latch: CountDownLatch) extends Actor { case RecvActorRef(bytes) => val ref = RemoteActorSerialization.fromBinaryToRemoteActorRef(bytes) ref ! "hello" - case "hello" => latch.countDown + case "hello" => latch.countDown() } } diff --git a/akka-stm/src/test/scala/agent/AgentSpec.scala b/akka-stm/src/test/scala/agent/AgentSpec.scala index 6a9c36dbe0..ed07dea6bd 100644 --- a/akka-stm/src/test/scala/agent/AgentSpec.scala +++ b/akka-stm/src/test/scala/agent/AgentSpec.scala @@ -12,7 +12,7 @@ import java.util.concurrent.CountDownLatch class CountDownFunction[A](num: Int = 1) extends Function1[A, A] { val latch = new CountDownLatch(num) - def apply(a: A) = { latch.countDown; a } + def apply(a: A) = { latch.countDown(); a } def await(timeout: Duration) = latch.await(timeout.length, timeout.unit) } @@ -61,7 +61,7 @@ class AgentSpec extends WordSpec with MustMatchers { } agent send f1 val read = agent() - readLatch.countDown + readLatch.countDown() agent send countDown countDown.await(5 seconds) diff --git a/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala b/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala index ab56ab84c9..2c953fb36a 100644 --- a/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala +++ b/akka-stm/src/test/scala/transactor/FickleFriendsSpec.scala @@ -42,7 +42,7 @@ object FickleFriends { increment deferred { success = true - latch.countDown + latch.countDown() } } } catch { diff --git a/akka-stm/src/test/scala/transactor/TransactorSpec.scala b/akka-stm/src/test/scala/transactor/TransactorSpec.scala index 8faf8a4d43..f44b6f58fa 100644 --- a/akka-stm/src/test/scala/transactor/TransactorSpec.scala +++ b/akka-stm/src/test/scala/transactor/TransactorSpec.scala @@ -37,8 +37,8 @@ object TransactorIncrement { def atomically = { case Increment(friends, latch) => { increment - deferred { latch.countDown } - compensating { latch.countDown } + deferred { latch.countDown() } + compensating { latch.countDown() } } } @@ -65,7 +65,7 @@ object SimpleTransactor { def atomically = { case Set(ref, value, latch) => { ref.set(value) - latch.countDown + latch.countDown() } } } diff --git a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorUtilFunctionsSpec.scala b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorUtilFunctionsSpec.scala index d2243e92e6..87e2078389 100644 --- a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorUtilFunctionsSpec.scala +++ b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorUtilFunctionsSpec.scala @@ -14,7 +14,7 @@ class ActorObjectUtilFunctionsSpec extends junit.framework.TestCase with Suite w val latch = new CountDownLatch(1) spawn { - latch.countDown + latch.countDown() } val done = latch.await(10,TimeUnit.SECONDS) From 03e5944bbe185e7c327a9a5916f5ed2928d0827d Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 12 Apr 2011 12:36:16 +0200 Subject: [PATCH 064/147] changed wrong time unit --- akka-docs/pending/remote-actors-java.rst | 4 ++-- akka-docs/pending/remote-actors-scala.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/akka-docs/pending/remote-actors-java.rst b/akka-docs/pending/remote-actors-java.rst index 0857ae41b7..549f0a3744 100644 --- a/akka-docs/pending/remote-actors-java.rst +++ b/akka-docs/pending/remote-actors-java.rst @@ -104,8 +104,8 @@ You can configure it like this: akka { remote { client { - reconnect-delay = 5000 # in millis (5 sec default) - read-timeout = 10000 # in millis (10 sec default) + reconnect-delay = 5 # in millis (5 sec default) + read-timeout = 10 # in millis (10 sec default) reconnection-time-window = 600 # the maximum time window that a client should try to reconnect for } } diff --git a/akka-docs/pending/remote-actors-scala.rst b/akka-docs/pending/remote-actors-scala.rst index 5c594c5d56..b5471d8863 100644 --- a/akka-docs/pending/remote-actors-scala.rst +++ b/akka-docs/pending/remote-actors-scala.rst @@ -105,8 +105,8 @@ You can configure it like this: akka { remote { client { - reconnect-delay = 5000 # in millis (5 sec default) - read-timeout = 10000 # in millis (10 sec default) + reconnect-delay = 5 # in millis (5 sec default) + read-timeout = 10 # in millis (10 sec default) reconnection-time-window = 600 # the maximum time window that a client should try to reconnect for } } From e49d675aed33eb703aeaf6d2922e0d9d78d09589 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 12 Apr 2011 13:49:11 +0200 Subject: [PATCH 065/147] changed wrong time unit --- akka-docs/pending/remote-actors-java.rst | 4 ++-- akka-docs/pending/remote-actors-scala.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/akka-docs/pending/remote-actors-java.rst b/akka-docs/pending/remote-actors-java.rst index 549f0a3744..0e654fc698 100644 --- a/akka-docs/pending/remote-actors-java.rst +++ b/akka-docs/pending/remote-actors-java.rst @@ -104,8 +104,8 @@ You can configure it like this: akka { remote { client { - reconnect-delay = 5 # in millis (5 sec default) - read-timeout = 10 # in millis (10 sec default) + reconnect-delay = 5 # in seconds (5 sec default) + read-timeout = 10 # in seconds (10 sec default) reconnection-time-window = 600 # the maximum time window that a client should try to reconnect for } } diff --git a/akka-docs/pending/remote-actors-scala.rst b/akka-docs/pending/remote-actors-scala.rst index b5471d8863..fef71e69e5 100644 --- a/akka-docs/pending/remote-actors-scala.rst +++ b/akka-docs/pending/remote-actors-scala.rst @@ -105,8 +105,8 @@ You can configure it like this: akka { remote { client { - reconnect-delay = 5 # in millis (5 sec default) - read-timeout = 10 # in millis (10 sec default) + reconnect-delay = 5 # in seconds (5 sec default) + read-timeout = 10 # in seconds (10 sec default) reconnection-time-window = 600 # the maximum time window that a client should try to reconnect for } } From a38e0cacdb18a81f487c53872d9439160f6ed951 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 12 Apr 2011 15:40:09 +0200 Subject: [PATCH 066/147] Refining the PriorityGenerator API and also adding PriorityDispatcher to the docs --- .../ExecutorBasedEventDrivenDispatcher.scala | 8 --- .../scala/akka/dispatch/MessageHandling.scala | 3 + akka-docs/pending/dispatchers-java.rst | 61 +++++++++++++++++++ akka-docs/pending/dispatchers-scala.rst | 60 ++++++++++++++++++ 4 files changed, 124 insertions(+), 8 deletions(-) diff --git a/akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala b/akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala index 019923b4b4..261a4c8170 100644 --- a/akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala +++ b/akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala @@ -236,14 +236,6 @@ object PriorityGenerator { def apply(priorityFunction: Any => Int): PriorityGenerator = new PriorityGenerator { def gen(message: Any): Int = priorityFunction(message) } - - /** - * Java API - * Creates a PriorityGenerator that uses the supplied function as priority generator - */ - def apply(priorityFunction: akka.japi.Function[Any, Int]): PriorityGenerator = new PriorityGenerator { - def gen(message: Any): Int = priorityFunction(message) - } } /** diff --git a/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala b/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala index d9129d422d..374b3b4b45 100644 --- a/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala +++ b/akka-actor/src/main/scala/akka/dispatch/MessageHandling.scala @@ -215,6 +215,9 @@ trait MessageDispatcher { * Trait to be used for hooking in new dispatchers into Dispatchers.fromConfig */ abstract class MessageDispatcherConfigurator { + /** + * Returns an instance of MessageDispatcher given a Configuration + */ def configure(config: Configuration): MessageDispatcher def mailboxType(config: Configuration): MailboxType = { diff --git a/akka-docs/pending/dispatchers-java.rst b/akka-docs/pending/dispatchers-java.rst index 5882f4d426..3aa1a34f13 100644 --- a/akka-docs/pending/dispatchers-java.rst +++ b/akka-docs/pending/dispatchers-java.rst @@ -47,6 +47,7 @@ There are six different types of message dispatchers: * Thread-based * Event-based +* Priority event-based * Work-stealing event-based * HawtDispatch-based event-driven @@ -127,6 +128,66 @@ If you don't define a the 'throughput' option in the configuration file then the Browse the `ScalaDoc `_ or look at the code for all the options available. +Priority event-based +^^^^^^^^^^^ + +Sometimes it's useful to be able to specify priority order of messages, that is done by using PriorityExecutorBasedEventDrivenDispatcher and supply +a java.util.Comparator[MessageInvocation] or use a akka.dispatch.PriorityGenerator (recommended): + +Creating a PriorityExecutorBasedEventDrivenDispatcher using PriorityGenerator in Java: + +.. code-block:: java + + package some.package; + + import akka.actor.*; + import akka.dispatch.*; + + public class Main { + // A simple Actor that just prints the messages it processes + public static class MyActor extends UntypedActor { + public void onReceive(Object message) throws Exception { + System.out.println(message); + } + } + + public static void main(String[] args) { + // Create a new PriorityGenerator, lower prio means more important + PriorityGenerator gen = new PriorityGenerator() { + public int gen(Object message) { + if (message == "highpriority") return 0; // "highpriority" messages should be treated first if possible + else if (message == "lowpriority") return 100; // "lowpriority" messages should be treated last if possible + else return 50; // We default to 50 + } + }; + // We create an instance of the actor that will print out the messages it processes + ActorRef ref = Actors.actorOf(MyActor.class); + // We create a new Priority dispatcher and seed it with the priority generator + ref.setDispatcher(new PriorityExecutorBasedEventDrivenDispatcher("foo", gen)); + + ref.start(); // Start the actor + ref.getDispatcher().suspend(ref); // Suspening the actor so it doesn't start to treat the messages before we have enqueued all of them :-) + ref.sendOneWay("lowpriority"); + ref.sendOneWay("lowpriority"); + ref.sendOneWay("highpriority"); + ref.sendOneWay("pigdog"); + ref.sendOneWay("pigdog2"); + ref.sendOneWay("pigdog3"); + ref.sendOneWay("highpriority"); + ref.getDispatcher().resume(ref); // Resuming the actor so it will start treating its messages + } + } + +Prints: + +highpriority +highpriority +pigdog +pigdog2 +pigdog3 +lowpriority +lowpriority + Work-stealing event-based ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/akka-docs/pending/dispatchers-scala.rst b/akka-docs/pending/dispatchers-scala.rst index 76ca982c65..9b67b7b58e 100644 --- a/akka-docs/pending/dispatchers-scala.rst +++ b/akka-docs/pending/dispatchers-scala.rst @@ -45,6 +45,7 @@ There are six different types of message dispatchers: * Thread-based * Event-based +* Priority event-based * Work-stealing * HawtDispatch-based event-driven @@ -103,6 +104,65 @@ If you don't define a the 'throughput' option in the configuration file then the Browse the `ScalaDoc `_ or look at the code for all the options available. +Priority event-based +^^^^^^^^^^^ + +Sometimes it's useful to be able to specify priority order of messages, that is done by using PriorityExecutorBasedEventDrivenDispatcher and supply +a java.util.Comparator[MessageInvocation] or use a akka.dispatch.PriorityGenerator (recommended): + +Creating a PriorityExecutorBasedEventDrivenDispatcher using PriorityGenerator in Java: + +.. code-block:: scala + + import akka.dispatch._ + + import akka.actor._ + + val gen = PriorityGenerator { // Create a new PriorityGenerator, lower prio means more important + case 'highpriority => 0 // 'highpriority messages should be treated first if possible + case 'lowpriority => 100 // 'lowpriority messages should be treated last if possible + case otherwise => 50 // We default to 50 + } + + val a = Actor.actorOf( // We create a new Actor that just prints out what it processes + new Actor { + def receive = { + case x => println(x) + } + }) + + // We create a new Priority dispatcher and seed it with the priority generator + a.dispatcher = new PriorityExecutorBasedEventDrivenDispatcher("foo", gen) + a.start // Start the Actor + + a.dispatcher.suspend(a) // Suspening the actor so it doesn't start to treat the messages before we have enqueued all of them :-) + + a ! 'lowpriority + + a ! 'lowpriority + + a ! 'highpriority + + a ! 'pigdog + + a ! 'pigdog2 + + a ! 'pigdog3 + + a ! 'highpriority + + a.dispatcher.resume(a) // Resuming the actor so it will start treating its messages + +Prints: + +'highpriority +'highpriority +'pigdog +'pigdog2 +'pigdog3 +'lowpriority +'lowpriority + Work-stealing event-based ^^^^^^^^^^^^^^^^^^^^^^^^^ From e667ff6805ab55c7b050fcafc383d49a56c0dd9e Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 12 Apr 2011 16:34:16 +0200 Subject: [PATCH 067/147] Quick fix for #773 --- akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala index c25758b2e6..800385545b 100644 --- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala @@ -360,6 +360,9 @@ final class TypedActorContext(private[akka] val actorRef: ActorRef) { def isUnstarted: Boolean = actorRef.isUnstarted def isBeingRestarted: Boolean = actorRef.isBeingRestarted + def getSelfAs[T <: AnyRef](): T = TypedActor.proxyFor(actorRef).get.asInstanceOf[T] + def getSelf(): AnyRef = getSelfAs[AnyRef] + /** * Returns the current sender reference. * Scala style getter. From c6474e6c57056f8de433e183928ef97cb3c296bf Mon Sep 17 00:00:00 2001 From: Debasish Ghosh Date: Wed, 13 Apr 2011 00:45:19 +0530 Subject: [PATCH 068/147] updated sjson to version 0.11 --- project/build/AkkaProject.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index 75dc11e74a..fd2900fbcd 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -156,8 +156,8 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val protobuf = "com.google.protobuf" % "protobuf-java" % "2.3.0" % "compile" //New BSD - lazy val sjson = "net.debasishg" % "sjson_2.9.0.RC1" % "0.10" % "compile" //ApacheV2 - lazy val sjson_test = "net.debasishg" % "sjson_2.9.0.RC1" % "0.10" % "test" //ApacheV2 + lazy val sjson = "net.debasishg" % "sjson_2.9.0.RC1" % "0.11" % "compile" //ApacheV2 + lazy val sjson_test = "net.debasishg" % "sjson_2.9.0.RC1" % "0.11" % "test" //ApacheV2 lazy val slf4j = "org.slf4j" % "slf4j-api" % "1.6.0" lazy val logback = "ch.qos.logback" % "logback-classic" % "0.9.24" From 414122bf6cddd806325da2f259fcdc397cffda51 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Wed, 13 Apr 2011 10:23:01 +1200 Subject: [PATCH 069/147] Set actor-tests as test dependency only of typed-actor --- project/build/AkkaProject.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index fd2900fbcd..72598969ba 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -299,6 +299,9 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { // testing val junit = Dependencies.junit val scalatest = Dependencies.scalatest + + override def deliverProjectDependencies = + super.deliverProjectDependencies.toList - akka_actor_tests.projectID ++ Seq(akka_actor_tests.projectID % "test") } // ------------------------------------------------------------------------------------------------------------------- From 41ef2843b17eb8f94e41bc29c66f0c6e3560ce97 Mon Sep 17 00:00:00 2001 From: ticktock Date: Wed, 13 Apr 2011 21:09:25 -0700 Subject: [PATCH 070/147] add the ability to configure a handler for MaximumNumberOfRestartsWithinTimeRangeReached to declarative Supervision --- .../main/scala/akka/actor/Supervisor.scala | 20 +++++++++---------- .../scala/akka/config/SupervisionConfig.scala | 6 ++++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/Supervisor.scala b/akka-actor/src/main/scala/akka/actor/Supervisor.scala index 22abafaccc..388444ea3b 100644 --- a/akka-actor/src/main/scala/akka/actor/Supervisor.scala +++ b/akka-actor/src/main/scala/akka/actor/Supervisor.scala @@ -4,7 +4,6 @@ package akka.actor -import akka.config.Supervision._ import akka.AkkaException import akka.util._ import ReflectiveAccess._ @@ -81,7 +80,7 @@ case class SupervisorFactory(val config: SupervisorConfig) { def newInstance: Supervisor = newInstanceFor(config) def newInstanceFor(config: SupervisorConfig): Supervisor = { - val supervisor = new Supervisor(config.restartStrategy) + val supervisor = new Supervisor(config.restartStrategy, config.maxRestartsHandler) supervisor.configure(config) supervisor.start supervisor @@ -100,13 +99,13 @@ case class SupervisorFactory(val config: SupervisorConfig) { * * @author Jonas Bonér */ -sealed class Supervisor(handler: FaultHandlingStrategy) { +sealed class Supervisor(handler: FaultHandlingStrategy, maxRestartsHandler: MaximumNumberOfRestartsWithinTimeRangeReached => Unit) { import Supervisor._ private val _childActors = new ConcurrentHashMap[String, List[ActorRef]] private val _childSupervisors = new CopyOnWriteArrayList[Supervisor] - private[akka] val supervisor = actorOf(new SupervisorActor(handler)).start() + private[akka] val supervisor = actorOf(new SupervisorActor(handler,maxRestartsHandler)).start() def uuid = supervisor.uuid @@ -127,7 +126,8 @@ sealed class Supervisor(handler: FaultHandlingStrategy) { _childActors.values.toArray.toList.asInstanceOf[List[Supervisor]] def configure(config: SupervisorConfig): Unit = config match { - case SupervisorConfig(_, servers) => + case SupervisorConfig(_, servers, _) => + servers.map(server => server match { case Supervise(actorRef, lifeCycle, registerAsRemoteService) => @@ -143,7 +143,7 @@ sealed class Supervisor(handler: FaultHandlingStrategy) { supervisor.link(actorRef) if (registerAsRemoteService) Actor.remote.register(actorRef) - case supervisorConfig @ SupervisorConfig(_, _) => // recursive supervisor configuration + case supervisorConfig @ SupervisorConfig(_, _,_) => // recursive supervisor configuration val childSupervisor = Supervisor(supervisorConfig) supervisor.link(childSupervisor.supervisor) _childSupervisors.add(childSupervisor) @@ -156,9 +156,10 @@ sealed class Supervisor(handler: FaultHandlingStrategy) { * * @author Jonas Bonér */ -final class SupervisorActor private[akka] (handler: FaultHandlingStrategy) extends Actor { +final class SupervisorActor private[akka] (handler: FaultHandlingStrategy, maxRestartsHandler: MaximumNumberOfRestartsWithinTimeRangeReached => Unit) extends Actor { self.faultHandler = handler + override def postStop(): Unit = { val i = self.linkedActors.values.iterator while(i.hasNext) { @@ -169,11 +170,8 @@ final class SupervisorActor private[akka] (handler: FaultHandlingStrategy) exten } def receive = { - // FIXME add a way to respond to MaximumNumberOfRestartsWithinTimeRangeReached in declaratively configured Supervisor - case MaximumNumberOfRestartsWithinTimeRangeReached( - victim, maxNrOfRetries, withinTimeRange, lastExceptionCausingRestart) => + case max@MaximumNumberOfRestartsWithinTimeRangeReached(_,_,_,_) => maxRestartsHandler(max) case unknown => throw new SupervisorException( "SupervisorActor can not respond to messages.\n\tUnknown message [" + unknown + "]") } } - diff --git a/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala b/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala index 9f63c64bc1..da06c7aa44 100644 --- a/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala +++ b/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala @@ -4,8 +4,9 @@ package akka.config -import akka.actor.{ActorRef} import akka.dispatch.MessageDispatcher +import akka.actor.{MaximumNumberOfRestartsWithinTimeRangeReached, ActorRef} +import akka.japi.Procedure case class RemoteAddress(val hostname: String, val port: Int) @@ -21,9 +22,10 @@ object Supervision { sealed abstract class LifeCycle extends ConfigElement sealed abstract class FaultHandlingStrategy(val trapExit: List[Class[_ <: Throwable]]) extends ConfigElement - case class SupervisorConfig(restartStrategy: FaultHandlingStrategy, worker: List[Server]) extends Server { + case class SupervisorConfig(restartStrategy: FaultHandlingStrategy, worker: List[Server], maxRestartsHandler: MaximumNumberOfRestartsWithinTimeRangeReached => Unit = {max=>()}) extends Server { //Java API def this(restartStrategy: FaultHandlingStrategy, worker: Array[Server]) = this(restartStrategy,worker.toList) + def this(restartStrategy: FaultHandlingStrategy, worker: Array[Server], restartHandler:Procedure[MaximumNumberOfRestartsWithinTimeRangeReached]) = this(restartStrategy,worker.toList, {max=>restartHandler.apply(max)}) } class Supervise(val actorRef: ActorRef, val lifeCycle: LifeCycle, val registerAsRemoteService: Boolean = false) extends Server { From b96eca58686235e7d7376b9e9742b33338886650 Mon Sep 17 00:00:00 2001 From: ticktock Date: Thu, 14 Apr 2011 18:59:51 -0700 Subject: [PATCH 071/147] change the type of the handler function, and go down the rabbit hole a bit. Add a Procedure2[T1,T2] to the Java API, and add JavaEventHandler that gives access from java to the EventHandler, add docs for configuring the handler in declarative Supervision for Scala and Java. --- .../java/akka/event/JavaEventHandler.java | 35 +++++++++++++++++++ .../main/scala/akka/actor/Supervisor.scala | 6 ++-- .../scala/akka/config/SupervisionConfig.scala | 6 ++-- .../src/main/scala/akka/japi/JavaAPI.scala | 6 ++++ akka-docs/pending/fault-tolerance-java.rst | 30 ++++++++++++++++ akka-docs/pending/fault-tolerance-scala.rst | 23 ++++++++++++ 6 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 akka-actor/src/main/java/akka/event/JavaEventHandler.java diff --git a/akka-actor/src/main/java/akka/event/JavaEventHandler.java b/akka-actor/src/main/java/akka/event/JavaEventHandler.java new file mode 100644 index 0000000000..7e6e2d4143 --- /dev/null +++ b/akka-actor/src/main/java/akka/event/JavaEventHandler.java @@ -0,0 +1,35 @@ +package akka.event; + + +import akka.actor.ActorRef; + +/** + * Java API for Akka EventHandler + */ + +public class JavaEventHandler { + + + public static void notify(Object message){ + EventHandler$.MODULE$.notify(message); + } + + public static void debug(ActorRef instance, Object message){ + EventHandler$.MODULE$.debug(instance, message); + } + + public static void info(ActorRef instance, Object message){ + EventHandler$.MODULE$.info(instance,message); + } + + public static void warning(ActorRef instance, Object message){ + EventHandler$.MODULE$.warning(instance,message); + } + + public static void error(ActorRef instance, Object message){ + EventHandler$.MODULE$.debug(instance,message); + } + +} + + diff --git a/akka-actor/src/main/scala/akka/actor/Supervisor.scala b/akka-actor/src/main/scala/akka/actor/Supervisor.scala index 388444ea3b..e32b515ae5 100644 --- a/akka-actor/src/main/scala/akka/actor/Supervisor.scala +++ b/akka-actor/src/main/scala/akka/actor/Supervisor.scala @@ -99,7 +99,7 @@ case class SupervisorFactory(val config: SupervisorConfig) { * * @author Jonas Bonér */ -sealed class Supervisor(handler: FaultHandlingStrategy, maxRestartsHandler: MaximumNumberOfRestartsWithinTimeRangeReached => Unit) { +sealed class Supervisor(handler: FaultHandlingStrategy, maxRestartsHandler: (ActorRef, MaximumNumberOfRestartsWithinTimeRangeReached) => Unit) { import Supervisor._ private val _childActors = new ConcurrentHashMap[String, List[ActorRef]] @@ -156,7 +156,7 @@ sealed class Supervisor(handler: FaultHandlingStrategy, maxRestartsHandler: Maxi * * @author Jonas Bonér */ -final class SupervisorActor private[akka] (handler: FaultHandlingStrategy, maxRestartsHandler: MaximumNumberOfRestartsWithinTimeRangeReached => Unit) extends Actor { +final class SupervisorActor private[akka] (handler: FaultHandlingStrategy, maxRestartsHandler: (ActorRef,MaximumNumberOfRestartsWithinTimeRangeReached) => Unit) extends Actor { self.faultHandler = handler @@ -170,7 +170,7 @@ final class SupervisorActor private[akka] (handler: FaultHandlingStrategy, maxRe } def receive = { - case max@MaximumNumberOfRestartsWithinTimeRangeReached(_,_,_,_) => maxRestartsHandler(max) + case max@MaximumNumberOfRestartsWithinTimeRangeReached(_,_,_,_) => maxRestartsHandler(self, max) case unknown => throw new SupervisorException( "SupervisorActor can not respond to messages.\n\tUnknown message [" + unknown + "]") } diff --git a/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala b/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala index da06c7aa44..6b66f3415d 100644 --- a/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala +++ b/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala @@ -6,7 +6,7 @@ package akka.config import akka.dispatch.MessageDispatcher import akka.actor.{MaximumNumberOfRestartsWithinTimeRangeReached, ActorRef} -import akka.japi.Procedure +import akka.japi.{Procedure2, Procedure} case class RemoteAddress(val hostname: String, val port: Int) @@ -22,10 +22,10 @@ object Supervision { sealed abstract class LifeCycle extends ConfigElement sealed abstract class FaultHandlingStrategy(val trapExit: List[Class[_ <: Throwable]]) extends ConfigElement - case class SupervisorConfig(restartStrategy: FaultHandlingStrategy, worker: List[Server], maxRestartsHandler: MaximumNumberOfRestartsWithinTimeRangeReached => Unit = {max=>()}) extends Server { + case class SupervisorConfig(restartStrategy: FaultHandlingStrategy, worker: List[Server], maxRestartsHandler: (ActorRef,MaximumNumberOfRestartsWithinTimeRangeReached)=> Unit = {(aRef,max)=>()}) extends Server { //Java API def this(restartStrategy: FaultHandlingStrategy, worker: Array[Server]) = this(restartStrategy,worker.toList) - def this(restartStrategy: FaultHandlingStrategy, worker: Array[Server], restartHandler:Procedure[MaximumNumberOfRestartsWithinTimeRangeReached]) = this(restartStrategy,worker.toList, {max=>restartHandler.apply(max)}) + def this(restartStrategy: FaultHandlingStrategy, worker: Array[Server], restartHandler:Procedure2[ActorRef,MaximumNumberOfRestartsWithinTimeRangeReached]) = this(restartStrategy,worker.toList, {(aRef,max)=>restartHandler.apply(aRef,max)}) } class Supervise(val actorRef: ActorRef, val lifeCycle: LifeCycle, val registerAsRemoteService: Boolean = false) extends Server { diff --git a/akka-actor/src/main/scala/akka/japi/JavaAPI.scala b/akka-actor/src/main/scala/akka/japi/JavaAPI.scala index 20cd33b311..5d7bbf0a94 100644 --- a/akka-actor/src/main/scala/akka/japi/JavaAPI.scala +++ b/akka-actor/src/main/scala/akka/japi/JavaAPI.scala @@ -20,6 +20,12 @@ trait Procedure[T] { def apply(param: T): Unit } +/** A Procedure is like a Function, but it doesn't produce a return value + */ +trait Procedure2[T1,T2] { + def apply(param: T1, param2:T2): Unit +} + /** * An executable piece of code that takes no parameters and doesn't return any value. */ diff --git a/akka-docs/pending/fault-tolerance-java.rst b/akka-docs/pending/fault-tolerance-java.rst index 5c6510bcbd..18cbb63e9e 100644 --- a/akka-docs/pending/fault-tolerance-java.rst +++ b/akka-docs/pending/fault-tolerance-java.rst @@ -125,6 +125,36 @@ The Actor’s supervision can be declaratively defined by creating a ‘Supervis Supervisors created like this are implicitly instantiated and started. +To cofigure a handler function for when the actor underlying the supervisor recieves a MaximumNumberOfRestartsWithinTimeRangeReached message, you can specify + a Procedure2 when creating the SupervisorConfig. This handler will be called with the ActorRef of the supervisor and the +MaximumNumberOfRestartsWithinTimeRangeReached message. + +.. code-block:: java + + import static akka.config.Supervision.*; + import static akka.actor.Actors.*; + import akka.event.JavaEventHandler; + + Procedure2 handler = new Procedure2() { + public void apply(ActorRef ref, MaximumNumberOfRestartsWithinTimeRangeReached max) { + JavaEventHandler.error(ref, max); + } + }; + + Supervisor supervisor = new Supervisor( + new SupervisorConfig( + new AllForOneStrategy(new Class[]{Exception.class}, 3, 5000), + new Supervise[] { + new Supervise( + actorOf(MyActor1.class), + permanent()), + Supervise( + actorOf(MyActor2.class), + permanent()) + },handler)); + + + You can link and unlink actors from a declaratively defined supervisor using the 'link' and 'unlink' methods: .. code-block:: java diff --git a/akka-docs/pending/fault-tolerance-scala.rst b/akka-docs/pending/fault-tolerance-scala.rst index 279e69b849..5e02cf232a 100644 --- a/akka-docs/pending/fault-tolerance-scala.rst +++ b/akka-docs/pending/fault-tolerance-scala.rst @@ -121,6 +121,29 @@ The Actor's supervision can be declaratively defined by creating a "Supervisor' Supervisors created like this are implicitly instantiated and started. +To cofigure a handler function for when the actor underlying the supervisor recieves a MaximumNumberOfRestartsWithinTimeRangeReached message, you can specify a function of type +(ActorRef, MaximumNumberOfRestartsWithinTimeRangeReached) => Unit when creating the SupervisorConfig. This handler will be called with the ActorRef of the supervisor and the +MaximumNumberOfRestartsWithinTimeRangeReached message. + + +.. code-block:: scala + + val handler = { + (supervisor:ActorRef,max:MaximumNumberOfRestartsWithinTimeRangeReached) => EventHandler.notify(supervisor,max) + } + + val supervisor = Supervisor( + SupervisorConfig( + AllForOneStrategy(List(classOf[Exception]), 3, 1000), + Supervise( + actorOf[MyActor1], + Permanent) :: + Supervise( + actorOf[MyActor2], + Permanent) :: + Nil), handler) + + You can link and unlink actors from a declaratively defined supervisor using the 'link' and 'unlink' methods: .. code-block:: scala From ff711a4253df4c1239e08e4f72058c97048ecb69 Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Fri, 15 Apr 2011 13:09:53 -0600 Subject: [PATCH 072/147] Add Java API versions of Future.{traverse, sequence}, closes #786 --- .../java/akka/dispatch/JavaFutureTests.java | 30 ++++++- .../test/scala/akka/dispatch/FutureSpec.scala | 4 +- .../src/main/scala/akka/dispatch/Future.scala | 78 +++++++++++++++---- akka-docs/pending/futures-scala.rst | 8 +- 4 files changed, 93 insertions(+), 27 deletions(-) diff --git a/akka-actor-tests/src/test/java/akka/dispatch/JavaFutureTests.java b/akka-actor-tests/src/test/java/akka/dispatch/JavaFutureTests.java index d35946ce60..cdec7f5631 100644 --- a/akka-actor-tests/src/test/java/akka/dispatch/JavaFutureTests.java +++ b/akka-actor-tests/src/test/java/akka/dispatch/JavaFutureTests.java @@ -3,28 +3,50 @@ package akka.dispatch; import org.junit.Test; import static org.junit.Assert.*; import java.util.concurrent.Callable; +import java.util.LinkedList; import akka.japi.Function; import akka.japi.Procedure; import scala.Some; import scala.Right; import static akka.dispatch.Futures.future; +import static akka.dispatch.Futures.traverse; +import static akka.dispatch.Futures.sequence; -@SuppressWarnings("unchecked") public class JavaFutureTests { +public class JavaFutureTests { @Test public void mustBeAbleToMapAFuture() { - Future f1 = future(new Callable() { + Future f1 = future(new Callable() { public String call() { return "Hello"; } }); - Future f2 = f1.map(new Function() { + Future f2 = f1.map(new Function() { public String apply(String s) { return s + " World"; } }); - assertEquals(new Some(new Right("Hello World")), f2.await().value()); + assertEquals("Hello World", f2.get()); + } + + // TODO: Improve this test, perhaps with an Actor + @Test public void mustSequenceAFutureList() { + LinkedList> listFutures = new LinkedList>(); + LinkedList listExpected = new LinkedList(); + + for (int i = 0; i < 10; i++) { + listExpected.add("test"); + listFutures.add(future(new Callable() { + public String call() { + return "test"; + } + })); + } + + Future> futureList = sequence(listFutures); + + assertEquals(futureList.get(), listExpected); } } 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 6fc96bb6d2..e12294a70d 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala @@ -316,11 +316,11 @@ class FutureSpec extends JUnitSuite { }).start() val oddFutures: List[Future[Int]] = List.fill(100)(oddActor !!! 'GetNext) - assert(Futures.sequence(oddFutures).get.sum === 10000) + assert(Future.sequence(oddFutures).get.sum === 10000) oddActor.stop() val list = (1 to 100).toList - assert(Futures.traverse(list)(x => Future(x * 2 - 1)).get.sum === 10000) + assert(Future.traverse(list)(x => Future(x * 2 - 1)).get.sum === 10000) } @Test def shouldHandleThrowables { diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index 1f86613b47..c69ca82bad 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -14,6 +14,8 @@ import java.util.concurrent.locks.ReentrantLock import java.util.concurrent. {ConcurrentLinkedQueue, TimeUnit, Callable} import java.util.concurrent.TimeUnit.{NANOSECONDS => NANOS, MILLISECONDS => MILLIS} import java.util.concurrent.atomic. {AtomicBoolean, AtomicInteger} +import java.lang.{Iterable => JIterable} +import java.util.{LinkedList => JLinkedList} import annotation.tailrec class FutureTimeoutException(message: String) extends AkkaException(message) @@ -152,29 +154,47 @@ object Futures { def reduce[T <: AnyRef, R >: T](futures: java.lang.Iterable[Future[T]], timeout: Long, fun: akka.japi.Function2[R, T, T]): Future[R] = reduce(scala.collection.JavaConversions.iterableAsScalaIterable(futures), timeout)(fun.apply _) - import scala.collection.mutable.Builder - import scala.collection.generic.CanBuildFrom - /** - * Simple version of Futures.traverse. Transforms a Traversable[Future[A]] into a Future[Traversable[A]]. + * Java API. + * Simple version of Futures.traverse. Transforms a java.lang.Iterable[Future[A]] into a Future[java.util.LinkedList[A]]. * Useful for reducing many Futures into a single Future. */ - def sequence[A, M[_] <: Traversable[_]](in: M[Future[A]], timeout: Long = Actor.TIMEOUT)(implicit cbf: CanBuildFrom[M[Future[A]], A, M[A]]): Future[M[A]] = - in.foldLeft(new DefaultCompletableFuture[Builder[A, M[A]]](timeout).completeWithResult(cbf(in)): Future[Builder[A, M[A]]])((fr, fa) => for (r <- fr; a <- fa.asInstanceOf[Future[A]]) yield (r += a)).map(_.result) + def sequence[A](in: JIterable[Future[A]], timeout: Long): Future[JLinkedList[A]] = + scala.collection.JavaConversions.iterableAsScalaIterable(in).foldLeft(Future(new JLinkedList[A]()))((fr, fa) => + for (r <- fr; a <- fa) yield { + r add a + r + }) /** - * Transforms a Traversable[A] into a Future[Traversable[B]] using the provided Function A => Future[B]. - * This is useful for performing a parallel map. For example, to apply a function to all items of a list - * in parallel: - *

-   * val myFutureList = Futures.traverse(myList)(x => Future(myFunc(x)))
-   * 
+ * Java API. + * Simple version of Futures.traverse. Transforms a java.lang.Iterable[Future[A]] into a Future[java.util.LinkedList[A]]. + * Useful for reducing many Futures into a single Future. */ - def traverse[A, B, M[_] <: Traversable[_]](in: M[A], timeout: Long = Actor.TIMEOUT)(fn: A => Future[B])(implicit cbf: CanBuildFrom[M[A], B, M[B]]): Future[M[B]] = - in.foldLeft(new DefaultCompletableFuture[Builder[B, M[B]]](timeout).completeWithResult(cbf(in)): Future[Builder[B, M[B]]]) { (fr, a) => - val fb = fn(a.asInstanceOf[A]) - for (r <- fr; b <-fb) yield (r += b) - }.map(_.result) + def sequence[A](in: JIterable[Future[A]]): Future[JLinkedList[A]] = sequence(in, Actor.TIMEOUT) + + /** + * Java API. + * Transforms a java.lang.Iterable[A] into a Future[java.util.LinkedList[B]] using the provided Function A => Future[B]. + * This is useful for performing a parallel map. For example, to apply a function to all items of a list + * in parallel. + */ + def traverse[A, B](in: JIterable[A], timeout: Long, fn: JFunc[A,Future[B]]): Future[JLinkedList[B]] = + scala.collection.JavaConversions.iterableAsScalaIterable(in).foldLeft(Future(new JLinkedList[B]())){(fr, a) => + val fb = fn(a) + for (r <- fr; b <- fb) yield { + r add b + r + } + } + + /** + * Java API. + * Transforms a java.lang.Iterable[A] into a Future[java.util.LinkedList[B]] using the provided Function A => Future[B]. + * This is useful for performing a parallel map. For example, to apply a function to all items of a list + * in parallel. + */ + def traverse[A, B](in: JIterable[A], fn: JFunc[A,Future[B]]): Future[JLinkedList[B]] = traverse(in, Actor.TIMEOUT, fn) // ===================================== // Deprecations @@ -217,6 +237,30 @@ object Future { dispatcher.dispatchFuture(FutureInvocation(f.asInstanceOf[CompletableFuture[Any]], () => body)) f } + + import scala.collection.mutable.Builder + import scala.collection.generic.CanBuildFrom + + /** + * Simple version of Futures.traverse. Transforms a Traversable[Future[A]] into a Future[Traversable[A]]. + * Useful for reducing many Futures into a single Future. + */ + def sequence[A, M[_] <: Traversable[_]](in: M[Future[A]], timeout: Long = Actor.TIMEOUT)(implicit cbf: CanBuildFrom[M[Future[A]], A, M[A]]): Future[M[A]] = + in.foldLeft(new DefaultCompletableFuture[Builder[A, M[A]]](timeout).completeWithResult(cbf(in)): Future[Builder[A, M[A]]])((fr, fa) => for (r <- fr; a <- fa.asInstanceOf[Future[A]]) yield (r += a)).map(_.result) + + /** + * Transforms a Traversable[A] into a Future[Traversable[B]] using the provided Function A => Future[B]. + * This is useful for performing a parallel map. For example, to apply a function to all items of a list + * in parallel: + *
+   * val myFutureList = Futures.traverse(myList)(x => Future(myFunc(x)))
+   * 
+ */ + def traverse[A, B, M[_] <: Traversable[_]](in: M[A], timeout: Long = Actor.TIMEOUT)(fn: A => Future[B])(implicit cbf: CanBuildFrom[M[A], B, M[B]]): Future[M[B]] = + in.foldLeft(new DefaultCompletableFuture[Builder[B, M[B]]](timeout).completeWithResult(cbf(in)): Future[Builder[B, M[B]]]) { (fr, a) => + val fb = fn(a.asInstanceOf[A]) + for (r <- fr; b <-fb) yield (r += b) + }.map(_.result) } sealed trait Future[+T] { diff --git a/akka-docs/pending/futures-scala.rst b/akka-docs/pending/futures-scala.rst index 5cbfc08cea..3426ce0ff6 100644 --- a/akka-docs/pending/futures-scala.rst +++ b/akka-docs/pending/futures-scala.rst @@ -158,24 +158,24 @@ This is fine when dealing with a known amount of Actors, but can grow unwieldly val listOfFutures: List[Future[Int]] = List.fill(100)(oddActor !!! GetNext) // now we have a Future[List[Int]] - val futureList = Futures.sequence(listOfFutures) + val futureList = Future.sequence(listOfFutures) // Find the sum of the odd numbers val oddSum = futureList.map(_.sum).apply -To better explain what happened in the example, Futures.sequence is taking the List[Future[Int]] and turning it into a Future[List[Int]]. We can then use 'map' to work with the List[Int] directly, and we find the sum of the List. +To better explain what happened in the example, Future.sequence is taking the List[Future[Int]] and turning it into a Future[List[Int]]. We can then use 'map' to work with the List[Int] directly, and we find the sum of the List. The 'traverse' method is similar to 'sequence', but it takes a Traversable[A] and a Function T => Future[B] to return a Future[Traversable[B]]. For example, to use 'traverse' to sum the first 100 odd numbers: .. code-block:: scala - val oddSum = Futures.traverse((1 to 100).toList)(x => Future(x * 2 - 1)).map(_.sum).apply + val oddSum = Future.traverse((1 to 100).toList)(x => Future(x * 2 - 1)).map(_.sum).apply This is the same result as this example: .. code-block:: scala - val oddSum = Futures.sequence((1 to 100).toList.map(x => Future(x * 2 - 1))).map(_.sum).apply + val oddSum = Future.sequence((1 to 100).toList.map(x => Future(x * 2 - 1))).map(_.sum).apply But it may be faster to use 'traverse' as it doesn't have to create an intermediate List[Future[Int]]. From 9c539e96fe04604fecbbc99e13bdb8b969803081 Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Sat, 16 Apr 2011 22:20:04 +0200 Subject: [PATCH 073/147] add TestActorRef - add adaptation of ActorRefSpec test suite - add some more specific tests --- .../testkit/CallingThreadDispatcher.scala | 9 +- .../scala/akka/testkit/TestActorRef.scala | 71 +++++ .../scala/akka/testkit/TestActorRefSpec.scala | 245 ++++++++++++++++++ project/build/AkkaProject.scala | 4 +- 4 files changed, 326 insertions(+), 3 deletions(-) create mode 100644 akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala create mode 100644 akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala diff --git a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala index e6fd8ebbce..971ee2e89f 100644 --- a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala +++ b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala @@ -28,6 +28,11 @@ import scala.annotation.tailrec */ object CallingThreadDispatcher { + + lazy val global = new CallingThreadDispatcher + + // PRIVATE DATA + private var queues = Map[CallingThreadMailbox, Set[WeakReference[NestingQueue]]]() // we have to forget about long-gone threads sometime @@ -35,7 +40,7 @@ object CallingThreadDispatcher { queues = queues mapValues (_ filter (_.get ne null)) filter (!_._2.isEmpty) } - def registerQueue(mbox : CallingThreadMailbox, q : NestingQueue) : Unit = synchronized { + private[akka] def registerQueue(mbox : CallingThreadMailbox, q : NestingQueue) : Unit = synchronized { if (queues contains mbox) { val newSet = queues(mbox) + new WeakReference(q) queues += mbox -> newSet @@ -50,7 +55,7 @@ object CallingThreadDispatcher { * given mailbox. When this method returns, the queue will be entered * (active). */ - def gatherFromAllInactiveQueues(mbox : CallingThreadMailbox, own : NestingQueue) : Unit = synchronized { + private[akka] def gatherFromAllInactiveQueues(mbox : CallingThreadMailbox, own : NestingQueue) : Unit = synchronized { if (!own.isActive) own.enter if (queues contains mbox) { for { diff --git a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala new file mode 100644 index 0000000000..b3c197cf7a --- /dev/null +++ b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala @@ -0,0 +1,71 @@ +package akka.testkit + +import akka.actor._ +import akka.util.ReflectiveAccess +import akka.event.EventHandler + +/** + * This special ActorRef is exclusively for use during unit testing in a single-threaded environment. Therefore, it + * overrides the dispatcher to CallingThreadDispatcher and sets the receiveTimeout to None. Otherwise, + * it acts just like a normal ActorRef. You may retrieve a reference to the underlying actor to test internal logic. + * + * + * @author Roland Kuhn + * @since 1.1 + */ +class TestActorRef[T <: Actor](factory: () => T) extends LocalActorRef(factory, None) { + + dispatcher = CallingThreadDispatcher.global + receiveTimeout = None + + /** + * Retrieve reference to the underlying actor, where the static type matches the factory used inside the + * constructor. Beware that this reference is discarded by the ActorRef upon restarting the actor (should this + * reference be linked to a supervisor). The old Actor may of course still be used in post-mortem assertions. + */ + def underlyingActor : T = actor.asInstanceOf[T] + + /** + * Override to return the more specific static type. + */ + override def start() = { + super.start() + this + } + + override def toString = "TestActor[" + id + ":" + uuid + "]" + + override def equals(other : Any) = + other.isInstanceOf[TestActorRef[_]] && + other.asInstanceOf[TestActorRef[_]].uuid == uuid + + /** + * Override to check whether the new supervisor is running on the CallingThreadDispatcher, + * as it should be. This can of course be tricked by linking before setting the dispatcher before starting the + * supervisor, but then you just asked for trouble. + */ + override def supervisor_=(a : Option[ActorRef]) { + for (ref <- a) { + if (!ref.dispatcher.isInstanceOf[CallingThreadDispatcher]) + EventHandler.warning(this, "supervisor "+ref+" does not use CallingThreadDispatcher") + } + super.supervisor_=(a) + } + +} + +object TestActorRef { + + def apply[T <: Actor](factory: => T) = new TestActorRef(() => factory) + + def apply[T <: Actor : Manifest] : TestActorRef[T] = new TestActorRef[T] ({ () => + import ReflectiveAccess.{ createInstance, noParams, noArgs } + createInstance[T](manifest[T].erasure, noParams, noArgs).getOrElse( + 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)'.")) + }) + +} \ No newline at end of file diff --git a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala new file mode 100644 index 0000000000..1d79100b71 --- /dev/null +++ b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala @@ -0,0 +1,245 @@ +package akka.testkit + +import org.scalatest.matchers.MustMatchers +import org.scalatest.{BeforeAndAfterEach, WordSpec} +import akka.actor._ +import akka.config.Supervision.OneForOneStrategy +import akka.event.EventHandler +import akka.dispatch.Future + +/** + * Test whether TestActorRef behaves as an ActorRef should, besides its own spec. + * + * @author Roland Kuhn + */ + +object TestActorRefSpec { + + var counter = 4 + val thread = Thread.currentThread + var otherthread : Thread = null + + trait TActor extends Actor { + def receive = new Receive { + val recv = receiveT + def isDefinedAt(o : Any) = recv.isDefinedAt(o) + def apply(o : Any) { + if (Thread.currentThread ne thread) + otherthread = Thread.currentThread + recv(o) + } + } + def receiveT : Receive + } + + class ReplyActor extends TActor { + var replyTo: Channel[Any] = null + + def receiveT = { + case "complexRequest" => { + replyTo = self.channel + val worker = TestActorRef[WorkerActor].start() + worker ! "work" + } + case "complexRequest2" => + val worker = TestActorRef[WorkerActor].start() + worker ! self.channel + case "workDone" => replyTo ! "complexReply" + case "simpleRequest" => self.reply("simpleReply") + } + } + + class WorkerActor() extends TActor { + def receiveT = { + case "work" => { + self.reply("workDone") + self.stop() + } + case replyTo: Channel[Any] => { + replyTo ! "complexReply" + } + } + } + + class SenderActor(replyActor: ActorRef) extends TActor { + + def receiveT = { + case "complex" => replyActor ! "complexRequest" + case "complex2" => replyActor ! "complexRequest2" + case "simple" => replyActor ! "simpleRequest" + case "complexReply" => { + counter -= 1 + } + case "simpleReply" => { + counter -= 1 + } + } + } + + class Logger extends Actor { + import EventHandler._ + var count = 0 + var msg : String = _ + def receive = { + case Warning(_, m : String) => count += 1; msg = m + } + } + +} + +class TestActorRefSpec extends WordSpec with MustMatchers with BeforeAndAfterEach { + + import TestActorRefSpec._ + + override def beforeEach { + otherthread = null + } + + private def assertThread { + otherthread must (be (null) or equal (thread)) + } + + "A TestActorRef must be an ActorRef, hence it" must { + + "support nested Actor creation" when { + + "used with TestActorRef" in { + val a = TestActorRef(new Actor { + val nested = TestActorRef(new Actor { def receive = { case _ => } }).start() + def receive = { case _ => self reply nested } + }).start() + a must not be (null) + val nested = (a !! "any").get.asInstanceOf[ActorRef] + nested must not be (null) + a must not be theSameInstanceAs (nested) + } + + "used with ActorRef" in { + val a = TestActorRef(new Actor { + val nested = Actor.actorOf(new Actor { def receive = { case _ => } }).start() + def receive = { case _ => self reply nested } + }).start() + a must not be (null) + val nested = (a !! "any").get.asInstanceOf[ActorRef] + nested must not be (null) + a must not be theSameInstanceAs (nested) + } + + } + + "support reply via channel" in { + val serverRef = TestActorRef[ReplyActor].start() + val clientRef = TestActorRef(new SenderActor(serverRef)).start() + + counter = 4 + + clientRef ! "complex" + clientRef ! "simple" + clientRef ! "simple" + clientRef ! "simple" + + counter must be (0) + + counter = 4 + + clientRef ! "complex2" + clientRef ! "simple" + clientRef ! "simple" + clientRef ! "simple" + + counter must be (0) + + assertThread + } + + "stop when sent a poison pill" in { + val a = TestActorRef[WorkerActor].start() + intercept[ActorKilledException] { + a !! PoisonPill + } + a must not be ('running) + a must be ('shutdown) + assertThread + } + + "restart when Kill:ed" in { + counter = 2 + + val boss = TestActorRef(new TActor { + 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 postRestart(reason: Throwable) { counter -= 1 } + }).start() + self link ref + def receiveT = { case "sendKill" => ref ! Kill } + }).start() + + val l = stopLog() + boss ! "sendKill" + startLog(l) + + counter must be (0) + assertThread + } + + "support futures" in { + val a = TestActorRef[WorkerActor].start() + val f : Future[String] = a !!! "work" + f must be ('completed) + f.get must equal ("workDone") + } + + } + + "A TestActorRef" must { + + "allow access to internals" in { + val ref = TestActorRef(new TActor { + var s : String = _ + def receiveT = { + case x : String => s = x + } + }).start() + ref ! "hallo" + val actor = ref.underlyingActor + actor.s must equal ("hallo") + } + + "set receiveTimeout to None" in { + val a = TestActorRef[WorkerActor] + a.receiveTimeout must be (None) + } + + "set CallingThreadDispatcher" in { + val a = TestActorRef[WorkerActor] + a.dispatcher.getClass must be (classOf[CallingThreadDispatcher]) + } + + "warn about scheduled supervisor" in { + val boss = Actor.actorOf(new Actor { def receive = { case _ => } }).start() + val ref = TestActorRef[WorkerActor].start() + + val log = TestActorRef[Logger] + EventHandler.addListener(log) + boss link ref + val la = log.underlyingActor + la.count must be (1) + la.msg must (include ("supervisor") and include ("CallingThreadDispatcher")) + EventHandler.removeListener(log) + } + + } + + private def stopLog() = { + val l = Actor.registry.actorsFor[EventHandler.DefaultListener] + l foreach (EventHandler.removeListener(_)) + l + } + + private def startLog(l : Array[ActorRef]) { + l foreach {a => EventHandler.addListener(Actor.actorOf[EventHandler.DefaultListener])} + } + +} \ No newline at end of file diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index 72598969ba..7666cc7543 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -422,7 +422,9 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { // akka-testkit subproject // ------------------------------------------------------------------------------------------------------------------- - class AkkaTestkitProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) + class AkkaTestkitProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { + val scalatest = Dependencies.scalatest + } // ------------------------------------------------------------------------------------------------------------------- // akka-actor-tests subproject From 8756a5a6c656eb9736d77d93b1d415c7911d5571 Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Sun, 17 Apr 2011 12:39:40 +0200 Subject: [PATCH 074/147] properly handle infinite timeouts in FSM - now you can use Duration.Inf to override an existing state timeout with "no timeout"; previously this resulted in an exception. --- .../scala/akka/actor/actor/FSMTimingSpec.scala | 17 +++++++++++++++++ akka-actor/src/main/scala/akka/actor/FSM.scala | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTimingSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTimingSpec.scala index 606ac280b7..158bd3f0ee 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTimingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTimingSpec.scala @@ -4,6 +4,7 @@ import org.scalatest.WordSpec import org.scalatest.matchers.MustMatchers import akka.testkit.TestKit +import akka.util.Duration import akka.util.duration._ @@ -29,6 +30,18 @@ class FSMTimingSpec extends WordSpec with MustMatchers with TestKit { } } + "allow StateTimeout override" in { + within (500 millis) { + fsm ! TestStateTimeoutOverride + expectNoMsg + } + within (50 millis) { + fsm ! Cancel + expectMsg(Cancel) + expectMsg(Transition(fsm, TestStateTimeout, Initial)) + } + } + "receive single-shot timer" in { within (50 millis, 150 millis) { fsm ! TestSingleTimer @@ -81,6 +94,7 @@ object FSMTimingSpec { trait State case object Initial extends State case object TestStateTimeout extends State + case object TestStateTimeoutOverride extends State case object TestSingleTimer extends State case object TestRepeatedTimer extends State case object TestUnhandled extends State @@ -102,10 +116,13 @@ object FSMTimingSpec { case Ev(TestRepeatedTimer) => setTimer("tester", Tick, 100 millis, true) goto(TestRepeatedTimer) using 4 + case Ev(TestStateTimeoutOverride) => + goto(TestStateTimeout) forMax (Duration.Inf) case Ev(x : FSMTimingSpec.State) => goto(x) } when(TestStateTimeout, stateTimeout = 100 millis) { case Ev(StateTimeout) => goto(Initial) + case Ev(Cancel) => goto(Initial) replying (Cancel) } when(TestSingleTimer) { case Ev(Tick) => diff --git a/akka-actor/src/main/scala/akka/actor/FSM.scala b/akka-actor/src/main/scala/akka/actor/FSM.scala index 37752b1373..8918e14584 100644 --- a/akka-actor/src/main/scala/akka/actor/FSM.scala +++ b/akka-actor/src/main/scala/akka/actor/FSM.scala @@ -425,7 +425,7 @@ trait FSM[S, D] { val timeout = if (currentState.timeout.isDefined) currentState.timeout else stateTimeouts(currentState.stateName) if (timeout.isDefined) { val t = timeout.get - if (t.length >= 0) { + if (t.finite_? && t.length >= 0) { timeoutFuture = Some(Scheduler.scheduleOnce(self, TimeoutMarker(generation), t.length, t.unit)) } } From 34c16266ed0b9ea64e9d338c8b90a48d7893d258 Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Sun, 17 Apr 2011 14:37:43 +0200 Subject: [PATCH 075/147] make transition notifier more robust - FSM does not throw exception anymore when registering not running listener - FSM automatically removes stopped listeners from callback list --- .../akka/actor/actor/FSMTransitionSpec.scala | 90 +++++++++++++++++++ .../src/main/scala/akka/actor/FSM.scala | 21 ++++- 2 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 akka-actor-tests/src/test/scala/akka/actor/actor/FSMTransitionSpec.scala 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 new file mode 100644 index 0000000000..fd0485a116 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/FSMTransitionSpec.scala @@ -0,0 +1,90 @@ +package akka.actor + +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers + +import akka.testing._ +import akka.testkit._ +import akka.util.duration._ +import akka.config.Supervision._ + +import FSM._ + +object FSMTransitionSpec { + + class Supervisor extends Actor { + self.faultHandler = OneForOneStrategy(List(classOf[Throwable]), None, None) + def receive = { case _ => } + } + + class MyFSM(target : ActorRef) extends Actor with FSM[Int, Unit] { + startWith(0, Unit) + when(0) { + case Ev("tick") => goto(1) + } + when(1) { + case Ev("tick") => goto(0) + } + whenUnhandled { + case Ev("reply") => stay replying "reply" + } + initialize + override def preRestart(reason : Throwable) { target ! "restarted" } + } + + class Forwarder(target : ActorRef) extends Actor { + def receive = { case x => target ! x } + } + +} + +class FSMTransitionSpec extends WordSpec with MustMatchers with TestKit { + + import FSMTransitionSpec._ + + "A FSM transition notifier" must { + + "notify listeners" in { + val fsm = Actor.actorOf(new MyFSM(testActor)).start() + within(1 second) { + fsm ! SubscribeTransitionCallBack(testActor) + expectMsg(CurrentState(fsm, 0)) + fsm ! "tick" + expectMsg(Transition(fsm, 0, 1)) + fsm ! "tick" + expectMsg(Transition(fsm, 1, 0)) + } + } + + "not fail when listener goes away" in { + val forward = Actor.actorOf(new Forwarder(testActor)).start() + val fsm = Actor.actorOf(new MyFSM(testActor)).start() + val sup = Actor.actorOf[Supervisor].start() + sup link fsm + within(300 millis) { + fsm ! SubscribeTransitionCallBack(forward) + expectMsg(CurrentState(fsm, 0)) + forward.stop() + fsm ! "tick" + expectNoMsg + } + } + + "not fail when listener is invalid" in { + val forward = Actor.actorOf(new Forwarder(testActor)) + val fsm = Actor.actorOf(new MyFSM(testActor)).start() + val sup = Actor.actorOf[Supervisor].start() + sup link fsm + within(300 millis) { + fsm ! SubscribeTransitionCallBack(forward) + fsm ! "reply" + expectMsg("reply") + forward.start() + fsm ! SubscribeTransitionCallBack(forward) + expectMsg(CurrentState(fsm, 0)) + } + } + + } + +} diff --git a/akka-actor/src/main/scala/akka/actor/FSM.scala b/akka-actor/src/main/scala/akka/actor/FSM.scala index 8918e14584..f363253fe8 100644 --- a/akka-actor/src/main/scala/akka/actor/FSM.scala +++ b/akka-actor/src/main/scala/akka/actor/FSM.scala @@ -4,6 +4,7 @@ package akka.actor import akka.util._ +import akka.event.EventHandler import scala.collection.mutable import java.util.concurrent.ScheduledFuture @@ -376,8 +377,13 @@ trait FSM[S, D] { } case SubscribeTransitionCallBack(actorRef) => // send current state back as reference point - actorRef ! CurrentState(self, currentState.stateName) - transitionCallBackList ::= actorRef + try { + actorRef ! CurrentState(self, currentState.stateName) + transitionCallBackList ::= actorRef + } catch { + case e : ActorInitializationException => + EventHandler.warning(this, "trying to register not running listener") + } case UnsubscribeTransitionCallBack(actorRef) => transitionCallBackList = transitionCallBackList.filterNot(_ == actorRef) case value => { @@ -413,7 +419,16 @@ trait FSM[S, D] { handleTransition(currentState.stateName, nextState.stateName) if (!transitionCallBackList.isEmpty) { val transition = Transition(self, currentState.stateName, nextState.stateName) - transitionCallBackList.foreach(_ ! transition) + transitionCallBackList = transitionCallBackList flatMap { cb => + try { + cb ! transition + Some(cb) + } catch { + case e : ActorInitializationException => + EventHandler.warning(this, "registered transition listener went away") + None + } + } } } applyState(nextState) From e952569c7a714c1cdd61a23bcc3ff6c4c36cc0cb Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Sun, 17 Apr 2011 16:37:53 +0200 Subject: [PATCH 076/147] rewrite FSM docs in reST - also set add_function_parentheses to False (that is really annoying) - also set show_authors to True (that is a matter of taste) --- akka-docs/conf.py | 4 +- akka-docs/index.rst | 2 +- akka-docs/manual/fsm-scala.rst | 432 ++++++++++++++++++++++++++++++++ akka-docs/pending/fsm-scala.rst | 218 ---------------- 4 files changed, 436 insertions(+), 220 deletions(-) create mode 100644 akka-docs/manual/fsm-scala.rst delete mode 100644 akka-docs/pending/fsm-scala.rst diff --git a/akka-docs/conf.py b/akka-docs/conf.py index 4ff27f40bb..50c73d18d1 100644 --- a/akka-docs/conf.py +++ b/akka-docs/conf.py @@ -12,7 +12,7 @@ extensions = ['sphinx.ext.todo'] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' -exclude_patterns = ['_build', 'pending'] +exclude_patterns = ['_build'] project = u'Akka' copyright = u'2009-2011, Scalable Solutions AB' @@ -21,6 +21,8 @@ release = '1.1' pygments_style = 'akka' highlight_language = 'scala' +add_function_parentheses = False +show_authors = True # -- Options for HTML output --------------------------------------------------- diff --git a/akka-docs/index.rst b/akka-docs/index.rst index c7b2486170..9771b0044c 100644 --- a/akka-docs/index.rst +++ b/akka-docs/index.rst @@ -27,7 +27,7 @@ Contents pending/fault-tolerance-java pending/fault-tolerance-scala pending/Feature Stability Matrix - pending/fsm-scala + manual/fsm-scala pending/futures-scala pending/getting-started pending/guice-integration diff --git a/akka-docs/manual/fsm-scala.rst b/akka-docs/manual/fsm-scala.rst new file mode 100644 index 0000000000..fa9b353dee --- /dev/null +++ b/akka-docs/manual/fsm-scala.rst @@ -0,0 +1,432 @@ +FSM +=== + +.. sidebar:: Contents + + .. contents:: :local: + +.. module:: FSM + :platform: Scala + :synopsis: Finite State Machine DSL on top of Actors +.. moduleauthor:: Irmo Manie, Roland Kuhn +.. versionadded:: 1.0 + +Module stability: **STABLE** + +Overview +++++++++ + +The FSM (Finite State Machine) is available as a mixin for the akka Actor and +is best described in the `Erlang design principles +`_ + +A FSM can be described as a set of relations of the form: + + **State(S) x Event(E) -> Actions (A), State(S')** + +These relations are interpreted as meaning: + + *If we are in state S and the event E occurs, we should perform the actions A and make a transition to the state S'.* + +A Simple Example +++++++++++++++++ + +To demonstrate the usage of states we start with a simple FSM without state +data. The state can be of any type so for this example we create the states A, +B and C. + +.. code-block:: scala + + sealed trait ExampleState + case object A extends ExampleState + case object B extends ExampleState + case object C extends ExampleState + +Now lets create an object representing the FSM and defining the behaviour. + +.. code-block:: scala + + import akka.actor.{Actor, FSM} + import akka.event.EventHandler + import FSM._ + import akka.util.duration._ + + case object Move + + class ABC extends Actor with FSM[ExampleState, Unit] { + + startWith(A, Unit) + + when(A) { + case Ev(Move) => + EventHandler.info(this, "Go to B and move on after 5 seconds") + goto(B) forMax (5 seconds) + } + + when(B) { + case Ev(StateTimeout) => + EventHandler.info(this, "Moving to C") + goto(C) + } + + when(C) { + case Ev(Move) => + EventHandler.info(this, "Stopping") + stop + } + + initialize // this checks validity of the initial state and sets up timeout if needed + } + +Each state is described by one or more :func:`when(state)` blocks; if more than +one is given for the same state, they are tried in the order given until the +first is found which matches the incoming event. Events are matched using +either :func:`Ev(msg)` (if no state data are to be extracted) or +:func:`Event(msg, data)`, see below. The statements for each case are the +actions to be taken, where the final expression must describe the transition +into the next state. This can either be :func:`stay` when no transition is +needed or :func:`goto(target)` for changing into the target state. The +transition may be annotated with additional properties, where this example +includes a state timeout of 5 seconds after the transition into state B: +:func:`forMax(duration)` arranges for a :obj:`StateTimeout` message to be +scheduled, unless some other message is received first. The construction of the +FSM is finished by calling the :func:`initialize` method as last part of the +ABC constructor. + +State Data +++++++++++ + +The FSM can also hold state data associated with the internal state of the +state machine. The state data can be of any type but to demonstrate let's look +at a lock with a :class:`String` as state data holding the entered unlock code. +First we need two states for the lock: + +.. code-block:: scala + + sealed trait LockState + case object Locked extends LockState + case object Open extends LockState + +Now we can create a lock FSM that takes :class:`LockState` as a state and a +:class:`String` as state data: + +.. code-block:: scala + + class Lock(code: String) extends Actor with FSM[LockState, String] { + + val emptyCode = "" + + startWith(Locked, emptyCode) + + when(Locked) { + // receive a digit and the code that we have so far + case Event(digit: Char, soFar) => { + // add the digit to what we have + soFar + digit match { + case incomplete if incomplete.length < code.length => + // not enough digits yet so stay using the incomplete code as the new state data + stay using incomplete + case `code` => + // code matched the one from the lock so go to Open state and reset the state data + goto(Open) using emptyCode forMax (1 seconds) + case wrong => + // wrong code, stay Locked and reset the state data + stay using emptyCode + } + } + } + + when(Open) { + case Ev(StateTimeout, _) => { + // after the timeout, go back to Locked state + goto(Locked) + } + } + + initialize + } + +This very simple example shows how the complete state of the FSM is encoded in +the :obj:`(State, Data)` pair and only explicitly updated during transitions. +This encapsulation is what makes state machines a powerful abstraction, e.g. +for handling socket states in a network server application. + +Reference ++++++++++ + +This section describes the DSL in a more formal way, refer to `Examples`_ for more sample material. + +The FSM Trait and Object +------------------------ + +The :class:`FSM` trait may only be mixed into an :class:`Actor`. Instead of +extending :class:`Actor`, the self type approach was chosen in order to make it +obvious that an actor is actually created. Importing all members of the +:obj:`FSM` object is recommended to receive useful implicits and directly +access the symbols like :obj:`StateTimeout`. This import is usually placed +inside the state machine definition: + +.. code-block:: scala + + class MyFSM extends Actor with FSM[State, Data] { + import FSM._ + + ... + + } + +The :class:`FSM` trait takes two type parameters: + + #. the supertype of all state names, usually a sealed trait with case objects + extending it, + #. the type of the state data which are tracked by the :class:`FSM` module + itself. + +.. _fsm-philosophy: + +.. note:: + + The state data together with the state name describe the internal state of + the state machine; if you stick to this scheme and do not add mutable fields + to the FSM class you have the advantage of making all changes of the + internal state explicit in a few well-known places. + +Defining States +--------------- + +A state is defined by one or more invocations of the method + + :func:`when([, stateTimeout = ])(stateFunction)`. + +The given name must be an object which is type-compatible with the first type +parameter given to the :class:`FSM` trait. This object is used as a hash key, +so you must ensure that it properly implements :meth:`equals` and +:meth:`hashCode`; in particular it must not be mutable. The easiest fit for +these requirements are case objects. + +If the :meth:`stateTimeout` parameter is given, then all transitions into this +state, including staying, receive this timeout by default. Initiating the +transition with an explicit timeout may be used to override this default, see +`Initiating Transitions`_ for more information. + +The :meth:`stateFunction` argument is a :class:`PartialFunction[Event, State]`, +which is conveniently given using the partial function literal syntax as +demonstrated below: + +.. code-block:: scala + + when(Idle) { + case Ev(Start(msg)) => // convenience extractor when state data not needed + goto(Timer) using (msg, self.channel) + } + + when(Timer, stateTimeout = 12 seconds) { + case Event(StateTimeout, (msg, channel)) => + channel ! msg + goto(Idle) + } + +The :class:`Event(msg, data)` case class may be used directly in the pattern as +shown in state Idle, or you may use the extractor :obj:`Ev(msg)` when the state +data are not needed. + +Defining the Initial State +-------------------------- + +Each FSM needs a starting point, which is declared using + + :func:`startWith(state, data[, timeout])` + +The optionally given timeout argument overrides any specification given for the +desired initial state. If you want to cancel a default timeout, use +:obj:`Duration.Inf`. + +Unhandled Events +---------------- + +If a state doesn't handle a received event a warning is logged. If you want to +do something else in this case you can specify that with +:func:`whenUnhandled(stateFunction)`: + +.. code-block:: scala + + whenUnhandled { + case Event(x : X, data) => + EventHandler.info(this, "Received unhandled event: " + x) + stay + case Ev(msg) => + EventHandler.warn(this, "Received unknown event: " + x) + goto(Error) + } + +**IMPORTANT**: This handler is not stacked, meaning that each invocation of +:func:`whenUnhandled` replaces the previously installed handler. + +Initiating Transitions +---------------------- + +The result of any :obj:`stateFunction` must be a definition of the next state +unless terminating the FSM, which is described in `Termination`_. The state +definition can either be the current state, as described by the :func:`stay` +directive, or it is a different state as given by :func:`goto(state)`. The +resulting object allows further qualification by way of the modifiers described +in the following: + +:meth:`forMax(duration)` + This modifier sets a state timeout on the next state. This means that a timer + is started which upon expiry sends a :obj:`StateTimeout` message to the FSM. + This timer is canceled upon reception of any other message in the meantime; + you can rely on the fact that the :obj:`StateTimeout` message will not be + processed after an intervening message. + + This modifier can also be used to override any default timeout which is + specified for the target state. If you want to cancel the default timeout, + use :obj:`Duration.Inf`. + +:meth:`using(data)` + This modifier replaces the old state data with the new data given. If you + follow the advice :ref:`above `, this is the only place where + internal state data are ever modified. + +:meth:`replying(msg)` + This modifier sends a reply to the currently processed message and otherwise + does not modify the state transition. + +All modifier can be chained to achieve a nice and concise description: + +.. code-block:: scala + + when(State) { + case Ev(msg) => + goto(Processing) using (msg) forMax (5 seconds) replying (WillDo) + } + +The parentheses are not actually needed in all cases, but they visually +distinguish between modifiers and their arguments and therefore make the code +even more pleasant to read for foreigners. + +Monitoring Transitions +---------------------- + +Transitions occur "between states" conceptually, which means after any actions +you have put into the event handling block; this is obvious since the next +state is only defined by the value returned by the event handling logic. You do +not need to worry about the exact order with respect to setting the internal +state variable, as everything within the FSM actor is running single-threaded +anyway. + +Internal Monitoring +******************* + +Up to this point, the FSM DSL has been centered on states and events. The dual +view is to describe it as a series of transitions. This is enabled by the +method + + :func:`onTransition(handler)` + +which associates actions with a transition instead of with a state and event. +The handler is a partial function which takes a pair of states as input; no +resulting state is needed as it is not possible to modify the transition in +progress. + +.. code-block:: scala + + onTransition { + case Idle -> Active => setTimer("timeout") + case Active -> _ => cancelTimer("timeout") + case x -> Idle => EventHandler.info("entering Idle from "+x) + } + +The convenience extractor :obj:`->` enables decomposition of the pair of states +with a clear visual reminder of the transition's direction. As usual in pattern +matches, an underscore may be used for irrelevant parts; alternatively you +could bind the unconstrained state to a variable, e.g. for logging as shown in +the last case. + +It is also possible to pass a function object accepting two states to +:func:`onTransition`, in case your transition handling logic is implemented as +a method: + +.. code-block:: scala + + onTransition(handler _) + + private def handler(from: State, to: State) { + ... + } + +The handlers registered with this method are stacked, so you can intersperse +:func:`onTransition` blocks with :func:`when` blocks as suits your design. It +should be noted, however, that *all handlers will be invoked for each +transition*, not only the first matching one. This is designed specifically so +you can put all transition handling for a certain aspect into one place without +having to worry about earlier declarations shadowing later ones; the actions +are still executed in declaration order, though. + +.. note:: + + This kind of internal monitoring may be used to structure your FSM according + to transitions, so that for example the cancellation of a timer upon leaving + a certain state cannot be forgot when adding new target states. + +External Monitoring +******************* + +External actors may be registered to be notified of state transitions by +sending a message :class:`SubscribeTransitionCallBack(actorRef)`. The named +actor will be sent a :class:`CurrentState(self, stateName)` message immediately +and will receive :class:`Transition(actorRef, oldState, newState)` messages +whenever a new state is reached. External monitors may be unregistered by +sending :class:`UnsubscribeTransitionCallBack(actorRef)` to the FSM actor. + +Registering a not-running listener generates a warning and fails gracefully. +Stopping a listener without unregistering will remove the listener from the +subscription list upon the next transition. + +Termination +----------- + +The FSM is stopped by specifying the result state as + + :func:`stop([reason[, data]])` + +The reason must be one of :obj:`Normal` (which is the default), :obj:`Shutdown` +or :obj:`Failure(reason)`, and the second argument may be given to change the +state data which is available during termination handling. + +.. note:: + + It should be noted that :func:`stop` does not abort the actions and stop the + FSM immediately. The stop action must be returned from the event handler in + the same way as a state transition. + +.. code-block:: scala + + when(A) { + case Ev(Stop) => + doCleanup() + stop() + } + +You can use :func:`onTermination(handler)` to specify custom code that is +executed when the FSM is stopped. The handler is a partial function which takes +a :class:`StopEvent(reason, stateName, stateData)` as argument: + +.. code-block:: scala + + onTermination { + case StopEvent(Normal, s, d) => ... + case StopEvent(Shutdown, _, _) => ... + case StopEvent(Failure(cause), s, d) => ... + } + +As for the :func:`whenUnhandled` case, this handler is not stacked, so each +invocation of :func:`onTermination` replaces the previously installed handler. + +Examples +++++++++ + +A bigger FSM example can be found in the sources: + + * `Dining Hakkers using FSM `_ + * `Dining Hakkers using become `_ diff --git a/akka-docs/pending/fsm-scala.rst b/akka-docs/pending/fsm-scala.rst deleted file mode 100644 index 9471b39165..0000000000 --- a/akka-docs/pending/fsm-scala.rst +++ /dev/null @@ -1,218 +0,0 @@ -FSM -=== - -Module stability: **STABLE** - -The FSM (Finite State Machine) is available as a mixin for the akka Actor and is best described in the `Erlang design principals <@http://www.erlang.org/documentation/doc-4.8.2/doc/design_principles/fsm.html>`_ - -A FSM can be described as a set of relations of the form: -> **State(S) x Event(E) -> Actions (A), State(S')** - -These relations are interpreted as meaning: -> *If we are in state S and the event E occurs, we should perform the actions A and make a transition to the state S'.* - -State Definitions ------------------ - -To demonstrate the usage of states we start with a simple state only FSM without state data. The state can be of any type so for this example we create the states A, B and C. - -.. code-block:: scala - - sealed trait ExampleState - case object A extends ExampleState - case object B extends ExampleState - case object C extends ExampleState - -Now lets create an object to influence the FSM and define the states and their behaviour. - -.. code-block:: scala - - import akka.actor.{Actor, FSM} - import FSM._ - import akka.util.duration._ - - case object Move - - class ABC extends Actor with FSM[ExampleState,Unit] { - - startWith(A, Unit) - - when(A) { - case Event(Move, _) => - log.info("Go to B and move on after 5 seconds") - goto(B) forMax (5 seconds) - } - - when(B) { - case Event(StateTimeout, _) => - log.info("Moving to C") - goto(C) - } - - when(C) { - case Event(Move, _) => - log.info("Stopping") - stop - } - - initialize // this checks validity of the initial state and sets up timeout if needed - } - -So we use 'when' to specify a state and define what needs to happen when we receive an event. We use 'goto' to go to another state. We use 'forMax' to tell for how long we maximum want to stay in that state before we receive a timeout notification. We use 'stop' to stop the FSM. And we use 'startWith' to specify which state to start with. The call to 'initialize' should be the last action done in the actor constructor. - -If we want to stay in the current state we can use (I'm hoping you can guess this by now) 'stay'. That can also be combined with the 'forMax' - -.. code-block:: scala - - when(C) { - case Event(unknown, _) => - stay forMax (2 seconds) - } - -The timeout can also be associated with the state itself, the choice depends on whether most of the transitions to the state require the same value for the timeout: - -.. code-block:: scala - - when(A) { - case Ev(Start(msg)) => // convenience extractor when state data not needed - goto(Timer) using msg - } - - when(B, stateTimeout = 12 seconds) { - case Event(StateTimeout, msg) => - target ! msg - case Ev(DifferentPause(dur : Duration)) => - stay forMax dur // overrides default state timeout for this single transition - } - -Unhandled Events ----------------- - -If a state doesn't handle a received event a warning is logged. If you want to do something with this events you can specify that with 'whenUnhandled' - -.. code-block:: scala - - whenUnhandled { - case Event(x, _) => log.info("Received unhandled event: " + x) - } - -Termination ------------ - -You can use 'onTermination' to specify custom code that is executed when the FSM is stopped. A reason is passed to tell how the FSM was stopped. - -.. code-block:: scala - - onTermination { - case Normal => log.info("Stopped normal") - case Shutdown => log.info("Stopped because of shutdown") - case Failure(cause) => log.error("Stopped because of failure: " + cause) - } - -State Transitions ------------------ - -When state transitions to another state we might want to know about this and take action. To specify this we can use 'onTransition' to capture the transitions. - -.. code-block:: scala - - onTransition { - case A -> B => log.info("Moving from A to B") - case _ -> C => log.info("Moving from something to C") - } - -Multiple onTransition blocks may be given and all will be execution while processing a transition. This enables you to associate your Actions either with the initial state of a processing step, or with the transition into the final state of a processing step. - -Transitions occur "between states" conceptually, which means after any actions you have put into the event handling block; this is obvious since the next state is only defined by the value returned by the event handling logic. You do not need to worry about the exact order with respect to setting the internal state variable, as everything within the FSM actor is running single-threaded anyway. - -It is also possible to pass a function object accepting two states to onTransition, in case your state handling logic is implemented as a method: - -.. code-block:: scala - - onTransition(handler _) - - private def handler(from: State, to: State) { - ... - } - -State Data ----------- - -The FSM can also hold state data that is attached to every event. The state data can be of any type but to demonstrate let's look at a lock with a String as state data holding the entered unlock code. -First we need two states for the lock: - -.. code-block:: scala - - sealed trait LockState - case object Locked extends LockState - case object Open extends LockState - -Now we can create a lock FSM that takes LockState as a state and a String as state data: - -.. code-block:: scala - - import akka.actor.{FSM, Actor} - import FSM._ - import akka.util.duration._ - - class Lock(code: String) extends Actor with FSM[LockState, String] { - - val emptyCode = "" - - when(Locked) { - // receive a digit and the code that we have so far - case Event(digit: Char, soFar) => { - // add the digit to what we have - soFar + digit match { - // not enough digits yet so stay using the incomplete code as the new state data - case incomplete if incomplete.length < code.length => - stay using incomplete - // code matched the one from the lock so go to Open state and reset the state data - case `code` => - log.info("Unlocked") - goto(Open) using emptyCode forMax (1 seconds) - // wrong code, stay Locked and reset the state data - case wrong => - log.error("Wrong code " + wrong) - stay using emptyCode - } - } - } - - when(Open) { - // after the timeout, go back to Locked state - case Event(StateTimeout, _) => { - log.info("Locked") - goto(Locked) - } - } - - startWith(Locked, emptyCode) - } - -To use the Lock you can run a small program like this: - -.. code-block:: scala - - object Lock { - - def main(args: Array[String]) { - - val lock = Actor.actorOf(new Lock("1234")).start() - - lock ! '1' - lock ! '2' - lock ! '3' - lock ! '4' - - Actor.registry.shutdownAll() - exit - } - } - -Dining Hakkers --------------- - -A bigger FSM example can be found in the sources. -`Dining Hakkers using FSM <@https://github.com/jboner/akka/blob/master/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala#L1>`_ -`Dining Hakkers using become <@https://github.com/jboner/akka/blob/master/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala#L1>`_ From 779c5436dfb167594f48f1b5b0db210573b3e787 Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Sun, 17 Apr 2011 16:50:43 +0200 Subject: [PATCH 077/147] exclude pending/ again from sphinx build --- akka-docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-docs/conf.py b/akka-docs/conf.py index 50c73d18d1..91c7bab0ba 100644 --- a/akka-docs/conf.py +++ b/akka-docs/conf.py @@ -12,7 +12,7 @@ extensions = ['sphinx.ext.todo'] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' -exclude_patterns = ['_build'] +exclude_patterns = ['_build', 'pending'] project = u'Akka' copyright = u'2009-2011, Scalable Solutions AB' From bd18e7e88fa8b83c2cff23dc43ed0212c2f0edf4 Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Sun, 17 Apr 2011 20:18:32 +0200 Subject: [PATCH 078/147] use ListenerManagement in FSM remove quite some duplication, and make ListenerManagement more robust (catch ActorInitializationException; there was a race condition in notifyListeners) --- .../src/main/scala/akka/actor/FSM.scala | 57 ++++++++++++------- .../scala/akka/util/ListenerManagement.scala | 16 ++++-- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/FSM.scala b/akka-actor/src/main/scala/akka/actor/FSM.scala index f363253fe8..815ab1076c 100644 --- a/akka-actor/src/main/scala/akka/actor/FSM.scala +++ b/akka-actor/src/main/scala/akka/actor/FSM.scala @@ -138,7 +138,7 @@ object FSM { * timerActive_? ("tock") *
*/ -trait FSM[S, D] { +trait FSM[S, D] extends ListenerManagement { this: Actor => import FSM._ @@ -147,7 +147,9 @@ trait FSM[S, D] { type Timeout = Option[Duration] type TransitionHandler = PartialFunction[(S, S), Unit] - /* DSL */ + /****************************************** + * DSL + ******************************************/ /** * Insert a new StateFunction at the end of the processing chain for the @@ -324,15 +326,25 @@ trait FSM[S, D] { makeTransition(currentState) } - /**FSM State data and default handlers */ + /****************************************************************** + * PRIVATE IMPLEMENTATION DETAILS + ******************************************************************/ + + /* + * FSM State data and current timeout handling + */ private var currentState: State = _ private var timeoutFuture: Option[ScheduledFuture[AnyRef]] = None private var generation: Long = 0L - private var transitionCallBackList: List[ActorRef] = Nil - + /* + * Timer handling + */ private val timers = mutable.Map[String, Timer]() + /* + * State definitions + */ private val stateFunctions = mutable.Map[S, StateFunction]() private val stateTimeouts = mutable.Map[S, Timeout]() @@ -346,23 +358,38 @@ trait FSM[S, D] { } } + /* + * unhandled event handler + */ private val handleEventDefault: StateFunction = { case Event(value, stateData) => stay } private var handleEvent: StateFunction = handleEventDefault + /* + * termination handling + */ private var terminateEvent: PartialFunction[StopEvent[S,D], Unit] = { case StopEvent(Failure(cause), _, _) => case StopEvent(reason, _, _) => } + /* + * transition handling + */ private var transitionEvent: List[TransitionHandler] = Nil private def handleTransition(prev : S, next : S) { val tuple = (prev, next) for (te <- transitionEvent) { if (te.isDefinedAt(tuple)) te(tuple) } } + // ListenerManagement shall not start() or stop() listener actors + override protected val manageLifeCycleOfListeners = false + + /********************************************* + * Main actor receive() method + *********************************************/ override final protected def receive: Receive = { case TimeoutMarker(gen) => if (generation == gen) { @@ -376,16 +403,16 @@ trait FSM[S, D] { } } case SubscribeTransitionCallBack(actorRef) => - // send current state back as reference point + addListener(actorRef) + // send current state back as reference point try { actorRef ! CurrentState(self, currentState.stateName) - transitionCallBackList ::= actorRef } catch { case e : ActorInitializationException => EventHandler.warning(this, "trying to register not running listener") } case UnsubscribeTransitionCallBack(actorRef) => - transitionCallBackList = transitionCallBackList.filterNot(_ == actorRef) + removeListener(actorRef) case value => { if (timeoutFuture.isDefined) { timeoutFuture.get.cancel(true) @@ -417,19 +444,7 @@ trait FSM[S, D] { } else { if (currentState.stateName != nextState.stateName) { handleTransition(currentState.stateName, nextState.stateName) - if (!transitionCallBackList.isEmpty) { - val transition = Transition(self, currentState.stateName, nextState.stateName) - transitionCallBackList = transitionCallBackList flatMap { cb => - try { - cb ! transition - Some(cb) - } catch { - case e : ActorInitializationException => - EventHandler.warning(this, "registered transition listener went away") - None - } - } - } + notifyListeners(Transition(self, currentState.stateName, nextState.stateName)) } applyState(nextState) } diff --git a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala index 777c048d70..349d51255d 100644 --- a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala +++ b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala @@ -5,8 +5,7 @@ package akka.util import java.util.concurrent.ConcurrentSkipListSet - -import akka.actor.ActorRef +import akka.actor.{ActorInitializationException, ActorRef} /** * A manager for listener actors. Intended for mixin by observables. @@ -46,7 +45,8 @@ trait ListenerManagement { def hasListeners: Boolean = !listeners.isEmpty /** - * Checks if a specfic listener is registered. + * Checks if a specfic listener is registered. ActorInitializationException leads to removal of listener if that + * one isShutdown. */ def hasListener(listener: ActorRef): Boolean = listeners.contains(listener) @@ -56,13 +56,19 @@ trait ListenerManagement { val iterator = listeners.iterator while (iterator.hasNext) { val listener = iterator.next - if (listener.isRunning) listener ! msg + if (listener.isShutdown) iterator.remove() + else try { + listener ! msg + } catch { + case e : ActorInitializationException => + if (listener.isShutdown) iterator.remove() + } } } } /** - * Execute f with each listener as argument. + * Execute f with each listener as argument. ActorInitializationException is not handled. */ protected[akka] def foreachListener(f: (ActorRef) => Unit) { val iterator = listeners.iterator From 144f83c00412387e9d069881cd3f7b3336459261 Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Sun, 17 Apr 2011 20:32:07 +0200 Subject: [PATCH 079/147] document FSM timer handling --- akka-docs/manual/fsm-scala.rst | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/akka-docs/manual/fsm-scala.rst b/akka-docs/manual/fsm-scala.rst index fa9b353dee..3cd3c5bee0 100644 --- a/akka-docs/manual/fsm-scala.rst +++ b/akka-docs/manual/fsm-scala.rst @@ -207,7 +207,9 @@ these requirements are case objects. If the :meth:`stateTimeout` parameter is given, then all transitions into this state, including staying, receive this timeout by default. Initiating the transition with an explicit timeout may be used to override this default, see -`Initiating Transitions`_ for more information. +`Initiating Transitions`_ for more information. The state timeout of any state +may be changed during action processing with :func:`setStateTimeout(state, +duration)`. This enables runtime configuration e.g. via external message. The :meth:`stateFunction` argument is a :class:`PartialFunction[Event, State]`, which is conveniently given using the partial function literal syntax as @@ -383,6 +385,30 @@ Registering a not-running listener generates a warning and fails gracefully. Stopping a listener without unregistering will remove the listener from the subscription list upon the next transition. +Timers +------ + +Besides state timeouts, FSM manages timers identified by :class:`String` names. +You may set a timer using + + :func:`setTimer(name, msg, interval, repeat)` + +where :obj:`msg` is the message object which will be sent after the duration +:obj:`interval` has elapsed. If :obj:`repeat` is :obj:`true`, then the timer is +scheduled at fixed rate given by the :obj:`interval` parameter. Timers may be +canceled using + + :func:`cancelTimer(name)` + +which is guaranteed to work immediately, meaning that the scheduled message +will not be processed after this call even if the timer already fired and +queued it. The status of any timer may be inquired with + + :func:`timerActive_?(name)` + +These named timers complement state timeouts because they are not affected by +intervening reception of other messages. + Termination ----------- From d2d0ccf0ecede1946c75b1cd8650aa4a506ab154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Sun, 17 Apr 2011 23:18:04 -0700 Subject: [PATCH 080/147] minor edit --- project/build/AkkaProject.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index 72598969ba..319883e1a6 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -440,7 +440,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { // ------------------------------------------------------------------------------------------------------------------- class AkkaSlf4jProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { - val sjson = Dependencies.slf4j + val slf4j = Dependencies.slf4j } // ------------------------------------------------------------------------------------------------------------------- From d1737db53e92dac7f7586eaff912e2f5997da1a0 Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 18 Apr 2011 09:26:23 +0200 Subject: [PATCH 081/147] add Timeout vs. Duration to FSM docs --- akka-docs/manual/fsm-scala.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/akka-docs/manual/fsm-scala.rst b/akka-docs/manual/fsm-scala.rst index 3cd3c5bee0..a23fd9edf4 100644 --- a/akka-docs/manual/fsm-scala.rst +++ b/akka-docs/manual/fsm-scala.rst @@ -191,6 +191,32 @@ The :class:`FSM` trait takes two type parameters: to the FSM class you have the advantage of making all changes of the internal state explicit in a few well-known places. +Defining Timeouts +----------------- + +The :class:`FSM` module uses :class:`akka.util.Duration` for all timing +configuration, which includes a mini-DSL: + +.. code-block:: scala + + import akka.util.duration._ // notice the small d + + val fivesec = 5.seconds + val threemillis = 3.millis + val diff = fivesec - threemillis + +.. note:: + + You may leave out the dot if the expression is clearly delimited (e.g. + within parentheses or in an argument list), but it is recommended to use it + if the time unit is the last token on a line, otherwise semi-colon inference + might go wrong, depending on what starts the next line. + +Several methods, like :func:`when()` and :func:`startWith()` take a +:class:`FSM.Timeout`, which is an alias for :class:`Option[Duration]`. There is +an implicit conversion available in the :obj:`FSM` object which makes this +transparent, just import it into your FSM body. + Defining States --------------- From 0c623e6e12a60a8c197467d32037703cfb032df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Mon, 18 Apr 2011 00:39:48 -0700 Subject: [PATCH 082/147] Added a couple of articles --- akka-docs/pending/articles.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/akka-docs/pending/articles.rst b/akka-docs/pending/articles.rst index 91138b404c..2b2e4b1b8b 100644 --- a/akka-docs/pending/articles.rst +++ b/akka-docs/pending/articles.rst @@ -19,6 +19,10 @@ Videos Articles -------- +`Actor-Based Continuations with Akka and Swarm `_ + +`Mimicking Twitter Using an Akka-Based Event-Driven Architecture `_ + `Remote Actor Class Loading with Akka `_ `Akka Producer Actors: New Features and Best Practices `_ From 7db48c2453aa7a80236c94a65e939303cfe0cafa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Mon, 18 Apr 2011 14:33:03 +0200 Subject: [PATCH 083/147] Added some more detailed impl explanation --- akka-docs/manual/getting-started-first.rst | 37 +++++++++++++++---- .../src/main/scala/Pi.scala | 1 - 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst index ee8618b110..ba25c5bc63 100644 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -142,7 +142,7 @@ To use the plugin, first add a plugin definition to your SBT project by creating val akkaPlugin = "se.scalablesolutions.akka" % "akka-sbt-plugin" % "1.1" } -Now we need to create a project definition using our Akka SBT plugin. We do that by creating a ``Project.scala`` file in the ``build`` directory containing:: +Now we need to create a project definition using our Akka SBT plugin. We do that by creating a ``project/build/Project.scala`` file containing:: import sbt._ @@ -174,7 +174,7 @@ We start by creating a ``Pi.scala`` file and adding these import statements at t package akka.tutorial.scala.first - import akka.actor.{Actor, ActorRef, PoisonPill} + import akka.actor.{Actor, PoisonPill} import Actor._ import akka.routing.{Routing, CyclicIterator} import Routing._ @@ -334,7 +334,8 @@ The ``Pi`` object is a perfect container module for our actors and messages, so val latch = new CountDownLatch(1) // create the master - val master = actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() + val master = actorOf( + new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() // start the calculation master ! Calculate @@ -355,7 +356,6 @@ But before we package it up and run it, let's take a look at the full code now, import akka.routing.{Routing, CyclicIterator} import Routing._ - import System.{currentTimeMillis => now} import java.util.concurrent.CountDownLatch object Pi extends App { @@ -392,7 +392,8 @@ But before we package it up and run it, let's take a look at the full code now, // ================== // ===== Master ===== // ================== - class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) + class Master( + nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) extends Actor { var pi: Double = _ @@ -431,7 +432,9 @@ But before we package it up and run it, let's take a look at the full code now, override def postStop { // tell the world that the calculation is complete - println("\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis".format(pi, (now - start))) + println( + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" + .format(pi, (now - start))) latch.countDown() } } @@ -445,7 +448,8 @@ But before we package it up and run it, let's take a look at the full code now, val latch = new CountDownLatch(1) // create the master - val master = actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() + val master = actorOf( + new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() // start the calculation master ! Calculate @@ -454,7 +458,7 @@ But before we package it up and run it, let's take a look at the full code now, latch.await() } } - + Run it as a command line application ------------------------------------ @@ -475,6 +479,8 @@ When we have compiled the source file we are ready to run the application. This Yippee! It is working. +If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. + Run it inside SBT ----------------- @@ -495,6 +501,21 @@ When this in done we can run our application directly inside SBT:: Yippee! It is working. +If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. + +The implementation in more detail +--------------------------------- + +To create our actors we used a method called ``actorOf`` in the ``Actor`` object. We used it in two different ways, one of them taking a actor type and the other one an instance of an actor. The former one (``actorOf[Worker]``) is used when the actor class has a no-argument constructor while the second one (``actorOf(new Master(..))``) is used when the actor class has a constructor that takes arguments. This is the only way to create an instance of an Actor and the ``actorOf`` method ensures this. The latter version is using call-by-name and lazily creates the actor within the scope of the ``actorOf`` method. The ``actorOf`` method instantiates the actor and returns, not an instance to the actor, but an instance to an ``ActorRef``. This reference is the handle through which you communicate with the actor. It is immutable, serializable and location-aware meaning that it "remembers" its original actor even if it is sent to other nodes across the network and can be seen as the equivalent to the Erlang actor's PID. + +The actor's life-cycle is: + +- Created -- ``Actor.actorOf[MyActor]`` -- can **not** receive messages +- Started -- ``actorRef.start()`` -- can receive messages +- Stopped -- ``actorRef.stop()`` -- can **not** receive messages + +Once the actor has been stopped it is dead and can not be started again. + Conclusion ---------- diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index c31f8ee2f6..a1844cdc45 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -9,7 +9,6 @@ import Actor._ import akka.routing.{Routing, CyclicIterator} import Routing._ -import System.{currentTimeMillis => now} import java.util.concurrent.CountDownLatch /** From f6dd2fe0380d6df1f9e9d8d059d736340a5a931e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Mon, 18 Apr 2011 14:56:32 +0200 Subject: [PATCH 084/147] changed all versions in the getting started guide to current versions --- akka-docs/manual/getting-started-first.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst index ba25c5bc63..b6069067e3 100644 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -92,16 +92,16 @@ Downloading and installing Scala To build and run the tutorial sample from the command line, you have to install the Scala distribution. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. -Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0 final release. If you pick the ``tgz`` or ``zip`` distribution then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. +Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0.RC1 release. If you pick the ``tgz`` or ``zip`` distribution then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. -You also need to make sure that the ``scala-2.9.0-final/bin`` (if that is the directory where you installed Scala) is on your ``PATH``:: +You also need to make sure that the ``scala-2.9.0.RC1/bin`` (if that is the directory where you installed Scala) is on your ``PATH``:: - $ export PATH=$PATH:scala-2.9.0-final/bin + $ export PATH=$PATH:scala-2.9.0.RC1/bin You can test your installation by invoking scala:: $ scala -version - Scala code runner version 2.9.0.final -- Copyright 2002-2011, LAMP/EPFL + Scala code runner version 2.9.0.RC1 -- Copyright 2002-2011, LAMP/EPFL Looks like we are all good. Finally let's create a source file ``Pi.scala`` for the tutorial and put it in the root of the Akka distribution in the ``tutorial`` directory (you have to create it first). @@ -112,7 +112,10 @@ Downloading and installing SBT SBT, short for 'Simple Build Tool' is an excellent build system written in Scala. It uses Scala to write the build scripts which gives you a lot of power. It has a plugin architecture with many plugins available, something that we will take advantage of soon. SBT is the preferred way of building software in Scala. If you want to use SBT for this tutorial then follow the following instructions, if not you can skip this section and the next. -To install SBT and create a project for this tutorial it is easiest to follow the instructions on `this page `_. The preferred SBT version to install is ``0.7.6``. +First browse to the +`SBT download page`_ and download the ``0.7.6.RC0`` distribution. + +To install SBT and create a project for this tutorial it is easiest to follow the instructions on `this page `_. If you have created an SBT project then step into the newly created SBT project, create a source file ``Pi.scala`` for the tutorial sample and put it in the ``src/main/scala`` directory. @@ -128,8 +131,8 @@ If you have not already done so, now is the time to create an SBT project for ou Name: Tutorial 1 Organization: Hakkers Inc Version [1.0]: - Scala version [2.9.0]: - sbt version [0.7.6]: + Scala version [2.9.0.RC1]: + sbt version [0.7.6.RC0]: Now we have the basis for an SBT project. Akka has an SBT Plugin making it very easy to use Akka is an SBT-based project so let's use that. From 8d99fb3132e13b1441d7b9673f4eae8a7f17a18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Mon, 18 Apr 2011 14:56:32 +0200 Subject: [PATCH 085/147] changed all versions in the getting started guide to current versions --- akka-docs/manual/getting-started-first.rst | 27 ++++++++++++---------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst index ba25c5bc63..b62cf8a808 100644 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -92,16 +92,16 @@ Downloading and installing Scala To build and run the tutorial sample from the command line, you have to install the Scala distribution. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. -Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0 final release. If you pick the ``tgz`` or ``zip`` distribution then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. +Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0.RC1 release. If you pick the ``tgz`` or ``zip`` distribution then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. -You also need to make sure that the ``scala-2.9.0-final/bin`` (if that is the directory where you installed Scala) is on your ``PATH``:: +You also need to make sure that the ``scala-2.9.0.RC1/bin`` (if that is the directory where you installed Scala) is on your ``PATH``:: - $ export PATH=$PATH:scala-2.9.0-final/bin + $ export PATH=$PATH:scala-2.9.0.RC1/bin You can test your installation by invoking scala:: $ scala -version - Scala code runner version 2.9.0.final -- Copyright 2002-2011, LAMP/EPFL + Scala code runner version 2.9.0.RC1 -- Copyright 2002-2011, LAMP/EPFL Looks like we are all good. Finally let's create a source file ``Pi.scala`` for the tutorial and put it in the root of the Akka distribution in the ``tutorial`` directory (you have to create it first). @@ -110,9 +110,12 @@ Some tools require you to set the ``SCALA_HOME`` environment variable to the roo Downloading and installing SBT ------------------------------ -SBT, short for 'Simple Build Tool' is an excellent build system written in Scala. It uses Scala to write the build scripts which gives you a lot of power. It has a plugin architecture with many plugins available, something that we will take advantage of soon. SBT is the preferred way of building software in Scala. If you want to use SBT for this tutorial then follow the following instructions, if not you can skip this section and the next. +SBT, short for 'Simple Build Tool' is an excellent build system written in Scala. It uses Scala to write the build scripts which gives you a lot of power. It has a plugin architecture with many plugins available, something that we will take advantage of soon. SBT is the preferred way of building software in Scala and is probably the easiest way of getting through this tutorial. If you want to use SBT for this tutorial then follow the following instructions, if not you can skip this section and the next. -To install SBT and create a project for this tutorial it is easiest to follow the instructions on `this page `_. The preferred SBT version to install is ``0.7.6``. +First browse to the +`SBT download page`_ and download the ``0.7.6.RC0`` distribution. + +To install SBT and create a project for this tutorial it is easiest to follow the instructions on `this page `_. If you have created an SBT project then step into the newly created SBT project, create a source file ``Pi.scala`` for the tutorial sample and put it in the ``src/main/scala`` directory. @@ -128,8 +131,8 @@ If you have not already done so, now is the time to create an SBT project for ou Name: Tutorial 1 Organization: Hakkers Inc Version [1.0]: - Scala version [2.9.0]: - sbt version [0.7.6]: + Scala version [2.9.0.RC1]: + sbt version [0.7.6.RC0]: Now we have the basis for an SBT project. Akka has an SBT Plugin making it very easy to use Akka is an SBT-based project so let's use that. @@ -458,7 +461,7 @@ But before we package it up and run it, let's take a look at the full code now, latch.await() } } - + Run it as a command line application ------------------------------------ @@ -479,7 +482,7 @@ When we have compiled the source file we are ready to run the application. This Yippee! It is working. -If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. +If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. Run it inside SBT ----------------- @@ -501,7 +504,7 @@ When this in done we can run our application directly inside SBT:: Yippee! It is working. -If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. +If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. The implementation in more detail --------------------------------- @@ -514,7 +517,7 @@ The actor's life-cycle is: - Started -- ``actorRef.start()`` -- can receive messages - Stopped -- ``actorRef.stop()`` -- can **not** receive messages -Once the actor has been stopped it is dead and can not be started again. +Once the actor has been stopped it is dead and can not be started again. Conclusion ---------- From d5c7e12787b2fa55d77f1f2580b1c7f87a705045 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Mon, 18 Apr 2011 16:26:31 +0200 Subject: [PATCH 086/147] Adding possibility to use akka.japi.Option in remote typed actor --- .../akka/remote/netty/NettyRemoteSupport.scala | 2 +- .../src/main/scala/akka/actor/TypedActor.scala | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index 64e26cfdf5..8781c72ecd 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -1299,4 +1299,4 @@ class DefaultDisposableChannelGroup(name: String) extends DefaultChannelGroup(na throw new IllegalStateException("ChannelGroup already closed, cannot add new channel") } } -} +} \ No newline at end of file diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala index 800385545b..7103c3fd5b 100644 --- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala @@ -996,14 +996,20 @@ private[akka] abstract class ActorAspect { None) //TODO: REVISIT: Use another classloader? if (isOneWay) null // for void methods + else if (future.isEmpty) throw new IllegalActorStateException("No future returned from call to [" + joinPoint + "]") else if (TypedActor.returnsFuture_?(methodRtti)) future.get + else if (TypedActor.returnsOption_?(methodRtti)) { + import akka.japi.{Option => JOption} + future.get.await.resultOrException.as[JOption[AnyRef]] match { + case None => JOption.none[AnyRef] + case Some(x) if ((x eq null) || x.isEmpty) => JOption.some[AnyRef](null) + case Some(x) => x + } + } else { - if (future.isDefined) { - future.get.await - val result = future.get.resultOrException - if (result.isDefined) result.get - else throw new IllegalActorStateException("No result returned from call to [" + joinPoint + "]") - } else throw new IllegalActorStateException("No future returned from call to [" + joinPoint + "]") + val result = future.get.await.resultOrException + if(result.isDefined) result.get + else throw new IllegalActorStateException("No result returned from call to [" + joinPoint + "]") } } From 807579ee87ae0ee891af241cb1b8a38b23bd8999 Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Mon, 18 Apr 2011 23:01:08 +0200 Subject: [PATCH 087/147] optimize performance optimization (away) as suggested by Viktor --- akka-actor/src/main/scala/akka/util/ListenerManagement.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala index 349d51255d..efeb482377 100644 --- a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala +++ b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala @@ -56,8 +56,9 @@ trait ListenerManagement { val iterator = listeners.iterator while (iterator.hasNext) { val listener = iterator.next - if (listener.isShutdown) iterator.remove() - else try { + // Uncomment if those exceptions are so frequent as to bottleneck + // if (listener.isShutdown) iterator.remove() else + try { listener ! msg } catch { case e : ActorInitializationException => From 0b4fe353f884b24f451a07476c93c720efd4f1d8 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 19 Apr 2011 10:31:54 +1200 Subject: [PATCH 088/147] Fix broken compile - revert import in pi tutorial --- akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index a1844cdc45..c31f8ee2f6 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -9,6 +9,7 @@ import Actor._ import akka.routing.{Routing, CyclicIterator} import Routing._ +import System.{currentTimeMillis => now} import java.util.concurrent.CountDownLatch /** From 4cd2693340104d6d0bf5675e620924ab59a8afa9 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 19 Apr 2011 12:43:50 +1200 Subject: [PATCH 089/147] Automatically install pygments style --- akka-docs/Makefile | 17 +++++++++++++---- akka-docs/conf.py | 2 +- akka-docs/pygments/setup.py | 19 +++++++++++++++++++ akka-docs/pygments/styles/__init__.py | 0 .../akka.py => pygments/styles/simple.py} | 6 +++--- 5 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 akka-docs/pygments/setup.py create mode 100644 akka-docs/pygments/styles/__init__.py rename akka-docs/{themes/akka/pygments/akka.py => pygments/styles/simple.py} (93%) diff --git a/akka-docs/Makefile b/akka-docs/Makefile index fedddbee17..d56a2fdf1e 100644 --- a/akka-docs/Makefile +++ b/akka-docs/Makefile @@ -6,16 +6,19 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build +EASYINSTALL = easy_install +PYGMENTSDIR = pygments # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html singlehtml latex pdf +.PHONY: help clean pygments html singlehtml latex pdf help: @echo "Please use \`make ' where is one of" + @echo " pygments to locally install the custom pygments styles" @echo " html to make standalone HTML files" @echo " singlehtml to make a single large HTML file" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @@ -24,7 +27,14 @@ help: clean: -rm -rf $(BUILDDIR)/* -html: +pygments: + $(EASYINSTALL) --user $(PYGMENTSDIR) + -rm -rf $(PYGMENTSDIR)/*.egg-info $(PYGMENTSDIR)/build $(PYGMENTSDIR)/temp + @echo + @echo "Custom pygments styles have been installed." + @echo + +html: pygments $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." @@ -41,9 +51,8 @@ latex: @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." -pdf: +pdf: pygments $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - diff --git a/akka-docs/conf.py b/akka-docs/conf.py index 91c7bab0ba..00145ca0fb 100644 --- a/akka-docs/conf.py +++ b/akka-docs/conf.py @@ -19,7 +19,7 @@ copyright = u'2009-2011, Scalable Solutions AB' version = '1.1' release = '1.1' -pygments_style = 'akka' +pygments_style = 'simple' highlight_language = 'scala' add_function_parentheses = False show_authors = True diff --git a/akka-docs/pygments/setup.py b/akka-docs/pygments/setup.py new file mode 100644 index 0000000000..7c86a6a681 --- /dev/null +++ b/akka-docs/pygments/setup.py @@ -0,0 +1,19 @@ +""" +Akka syntax styles for Pygments. +""" + +from setuptools import setup + +entry_points = """ +[pygments.styles] +simple = styles.simple:SimpleStyle +""" + +setup( + name = 'akkastyles', + version = '0.1', + description = __doc__, + author = "Akka", + packages = ['styles'], + entry_points = entry_points +) diff --git a/akka-docs/pygments/styles/__init__.py b/akka-docs/pygments/styles/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/akka-docs/themes/akka/pygments/akka.py b/akka-docs/pygments/styles/simple.py similarity index 93% rename from akka-docs/themes/akka/pygments/akka.py rename to akka-docs/pygments/styles/simple.py index af9fe61bf9..bdf3c7878e 100644 --- a/akka-docs/themes/akka/pygments/akka.py +++ b/akka-docs/pygments/styles/simple.py @@ -3,7 +3,7 @@ pygments.styles.akka ~~~~~~~~~~~~~~~~~~~~~~~~ - Akka style for Scala highlighting. + Simple style for Scala highlighting. """ from pygments.style import Style @@ -11,9 +11,9 @@ from pygments.token import Keyword, Name, Comment, String, Error, \ Number, Operator, Generic, Whitespace -class AkkaStyle(Style): +class SimpleStyle(Style): """ - Akka style for Scala highlighting. + Simple style for Scala highlighting. """ background_color = "#f0f0f0" From 0f6a2d1884d24f1615f3f34eace901c85c0f0cfc Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 19 Apr 2011 12:46:35 +1200 Subject: [PATCH 090/147] Comment out pending toc entries --- akka-docs/index.rst | 135 ++++++++++++++++++++++---------------------- 1 file changed, 68 insertions(+), 67 deletions(-) diff --git a/akka-docs/index.rst b/akka-docs/index.rst index 9771b0044c..6e09dc2450 100644 --- a/akka-docs/index.rst +++ b/akka-docs/index.rst @@ -5,74 +5,75 @@ Contents :maxdepth: 2 manual/getting-started-first - pending/actor-registry-java - pending/actor-registry-scala - pending/actors-scala - pending/agents-scala - pending/articles - pending/benchmarks - pending/building-akka - pending/buildr - pending/cluster-membership - pending/companies-using-akka - pending/configuration - pending/dataflow-java - pending/dataflow-scala - pending/deployment-scenarios - pending/developer-guidelines - pending/dispatchers-java - pending/dispatchers-scala - pending/event-handler - pending/external-sample-projects - pending/fault-tolerance-java - pending/fault-tolerance-scala - pending/Feature Stability Matrix manual/fsm-scala - pending/futures-scala - pending/getting-started - pending/guice-integration - pending/Home - pending/http - pending/issue-tracking - pending/language-bindings - pending/licenses - pending/logging - pending/Migration-1.0-1.1 - pending/migration-guide-0.10.x-1.0.x - pending/migration-guide-0.7.x-0.8.x - pending/migration-guide-0.8.x-0.9.x - pending/migration-guide-0.9.x-0.10.x - pending/migration-guides - pending/Recipes - pending/release-notes - pending/remote-actors-java - pending/remote-actors-scala - pending/routing-java - pending/routing-scala - pending/scheduler - pending/security - pending/serialization-java - pending/serialization-scala - pending/servlet - pending/slf4j - pending/sponsors - pending/stm - pending/stm-java - pending/stm-scala - pending/team - pending/test - pending/testkit - pending/testkit-example - pending/third-party-integrations - pending/transactors-java - pending/transactors-scala - pending/tutorial-chat-server-java - pending/tutorial-chat-server-scala - pending/typed-actors-java - pending/typed-actors-scala - pending/untyped-actors-java - pending/use-cases - pending/web + +.. pending/actor-registry-java +.. pending/actor-registry-scala +.. pending/actors-scala +.. pending/agents-scala +.. pending/articles +.. pending/benchmarks +.. pending/building-akka +.. pending/buildr +.. pending/cluster-membership +.. pending/companies-using-akka +.. pending/configuration +.. pending/dataflow-java +.. pending/dataflow-scala +.. pending/deployment-scenarios +.. pending/developer-guidelines +.. pending/dispatchers-java +.. pending/dispatchers-scala +.. pending/event-handler +.. pending/external-sample-projects +.. pending/fault-tolerance-java +.. pending/fault-tolerance-scala +.. pending/Feature Stability Matrix +.. pending/futures-scala +.. pending/getting-started +.. pending/guice-integration +.. pending/Home +.. pending/http +.. pending/issue-tracking +.. pending/language-bindings +.. pending/licenses +.. pending/logging +.. pending/Migration-1.0-1.1 +.. pending/migration-guide-0.10.x-1.0.x +.. pending/migration-guide-0.7.x-0.8.x +.. pending/migration-guide-0.8.x-0.9.x +.. pending/migration-guide-0.9.x-0.10.x +.. pending/migration-guides +.. pending/Recipes +.. pending/release-notes +.. pending/remote-actors-java +.. pending/remote-actors-scala +.. pending/routing-java +.. pending/routing-scala +.. pending/scheduler +.. pending/security +.. pending/serialization-java +.. pending/serialization-scala +.. pending/servlet +.. pending/slf4j +.. pending/sponsors +.. pending/stm +.. pending/stm-java +.. pending/stm-scala +.. pending/team +.. pending/test +.. pending/testkit +.. pending/testkit-example +.. pending/third-party-integrations +.. pending/transactors-java +.. pending/transactors-scala +.. pending/tutorial-chat-server-java +.. pending/tutorial-chat-server-scala +.. pending/typed-actors-java +.. pending/typed-actors-scala +.. pending/untyped-actors-java +.. pending/use-cases +.. pending/web Links ===== From 14334a65afe9fea77468c64c585234cc0f7fddeb Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 19 Apr 2011 12:48:41 +1200 Subject: [PATCH 091/147] Fix sbt download link --- akka-docs/manual/getting-started-first.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst index 0da7909187..9e9562b605 100644 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -112,7 +112,7 @@ Downloading and installing SBT SBT, short for 'Simple Build Tool' is an excellent build system written in Scala. It uses Scala to write the build scripts which gives you a lot of power. It has a plugin architecture with many plugins available, something that we will take advantage of soon. SBT is the preferred way of building software in Scala and is probably the easiest way of getting through this tutorial. If you want to use SBT for this tutorial then follow the following instructions, if not you can skip this section and the next. -First browse to the `SBT download page`_ and download the ``0.7.6.RC0`` distribution. +First browse to the `SBT download page `_ and download the ``0.7.6.RC0`` distribution. To install SBT and create a project for this tutorial it is easiest to follow the instructions on `this page `_. From c179cbf623466d11a9c8826c5d18191a6f75b5e4 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 19 Apr 2011 13:03:15 +1200 Subject: [PATCH 092/147] Add includecode directive for akka-docs --- .gitignore | 1 + akka-docs/conf.py | 3 +- akka-docs/exts/includecode.py | 135 ++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 akka-docs/exts/includecode.py diff --git a/.gitignore b/.gitignore index 28bd0c884d..91c3a65819 100755 --- a/.gitignore +++ b/.gitignore @@ -46,5 +46,6 @@ multiverse.log .eprj .*.swp akka-docs/_build/ +*.pyc akka-tutorials/akka-tutorial-first/project/boot/ akka-tutorials/akka-tutorial-first/project/plugins/project/ \ No newline at end of file diff --git a/akka-docs/conf.py b/akka-docs/conf.py index 00145ca0fb..f0dd997167 100644 --- a/akka-docs/conf.py +++ b/akka-docs/conf.py @@ -7,7 +7,8 @@ import sys, os # -- General configuration ----------------------------------------------------- -extensions = ['sphinx.ext.todo'] +sys.path.append(os.path.abspath('exts')) +extensions = ['sphinx.ext.todo', 'includecode'] templates_path = ['_templates'] source_suffix = '.rst' diff --git a/akka-docs/exts/includecode.py b/akka-docs/exts/includecode.py new file mode 100644 index 0000000000..728af5f82f --- /dev/null +++ b/akka-docs/exts/includecode.py @@ -0,0 +1,135 @@ +import os +import codecs +from os import path + +from docutils import nodes +from docutils.parsers.rst import Directive, directives + +class IncludeCode(Directive): + """ + Include a code example from a file with sections delimited with special comments. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = { + 'section': directives.unchanged_required, + 'comment': directives.unchanged_required, + 'marker': directives.unchanged_required, + 'include': directives.unchanged_required, + 'exclude': directives.unchanged_required, + 'linenos': directives.flag, + 'language': directives.unchanged_required, + 'encoding': directives.encoding, + 'prepend': directives.unchanged_required, + 'append': directives.unchanged_required, + } + + def run(self): + document = self.state.document + arg0 = self.arguments[0] + (filename, sep, section) = arg0.partition('#') + + if not document.settings.file_insertion_enabled: + return [document.reporter.warning('File insertion disabled', + line=self.lineno)] + env = document.settings.env + if filename.startswith('/') or filename.startswith(os.sep): + rel_fn = filename[1:] + else: + docdir = path.dirname(env.doc2path(env.docname, base=None)) + rel_fn = path.join(docdir, filename) + try: + fn = path.join(env.srcdir, rel_fn) + except UnicodeDecodeError: + # the source directory is a bytestring with non-ASCII characters; + # let's try to encode the rel_fn in the file system encoding + rel_fn = rel_fn.encode(sys.getfilesystemencoding()) + fn = path.join(env.srcdir, rel_fn) + + encoding = self.options.get('encoding', env.config.source_encoding) + codec_info = codecs.lookup(encoding) + try: + f = codecs.StreamReaderWriter(open(fn, 'U'), + codec_info[2], codec_info[3], 'strict') + lines = f.readlines() + f.close() + except (IOError, OSError): + return [document.reporter.warning( + 'Include file %r not found or reading it failed' % filename, + line=self.lineno)] + except UnicodeError: + return [document.reporter.warning( + 'Encoding %r used for reading included file %r seems to ' + 'be wrong, try giving an :encoding: option' % + (encoding, filename))] + + comment = self.options.get('comment', '//') + marker = self.options.get('marker', comment + '#') + lenm = len(marker) + if not section: + section = self.options.get('section') + include_sections = self.options.get('include', '') + exclude_sections = self.options.get('exclude', '') + include = set(include_sections.split(',')) if include_sections else set() + exclude = set(exclude_sections.split(',')) if exclude_sections else set() + if section: + include |= {section} + within = set() + res = [] + excluding = False + for line in lines: + index = line.find(marker) + if index >= 0: + section_name = line[index+lenm:].strip() + if section_name in within: + within ^= {section_name} + if excluding and not (exclude & within): + excluding = False + else: + within |= {section_name} + if not excluding and (exclude & within): + excluding = True + res.append(' ' * index + comment + ' ' + section_name.replace('-', ' ') + ' ...\n') + elif not (exclude & within) and (not include or (include & within)): + res.append(line) + lines = res + + def countwhile(predicate, iterable): + count = 0 + for x in iterable: + if predicate(x): + count += 1 + else: + return count + + nonempty = filter(lambda l: l.strip(), lines) + tabcounts = map(lambda l: countwhile(lambda c: c == ' ', l), nonempty) + tabshift = min(tabcounts) + + if tabshift > 0: + lines = map(lambda l: l[tabshift:] if len(l) > tabshift else l, lines) + + prepend = self.options.get('prepend') + append = self.options.get('append') + if prepend: + lines.insert(0, prepend + '\n') + if append: + lines.append(append + '\n') + + text = ''.join(lines) + retnode = nodes.literal_block(text, text, source=fn) + retnode.line = 1 + retnode.attributes['line_number'] = self.lineno + if self.options.get('language', ''): + retnode['language'] = self.options['language'] + if 'linenos' in self.options: + retnode['linenos'] = True + document.settings.env.note_dependency(rel_fn) + return [retnode] + +def setup(app): + app.require_sphinx('1.0') + app.add_directive('includecode', IncludeCode) From 6151d17413f687b03d6dcec5f06248a4b6cefb3f Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 19 Apr 2011 13:40:13 +1200 Subject: [PATCH 093/147] Use new includecode directive for getting started docs --- akka-docs/exts/includecode.py | 27 +-- akka-docs/manual/examples/Pi.scala | 129 +++++++++++ akka-docs/manual/getting-started-first.rst | 235 ++------------------- 3 files changed, 165 insertions(+), 226 deletions(-) create mode 100644 akka-docs/manual/examples/Pi.scala diff --git a/akka-docs/exts/includecode.py b/akka-docs/exts/includecode.py index 728af5f82f..816126c697 100644 --- a/akka-docs/exts/includecode.py +++ b/akka-docs/exts/includecode.py @@ -15,16 +15,17 @@ class IncludeCode(Directive): optional_arguments = 0 final_argument_whitespace = False option_spec = { - 'section': directives.unchanged_required, - 'comment': directives.unchanged_required, - 'marker': directives.unchanged_required, - 'include': directives.unchanged_required, - 'exclude': directives.unchanged_required, - 'linenos': directives.flag, - 'language': directives.unchanged_required, - 'encoding': directives.encoding, - 'prepend': directives.unchanged_required, - 'append': directives.unchanged_required, + 'section': directives.unchanged_required, + 'comment': directives.unchanged_required, + 'marker': directives.unchanged_required, + 'include': directives.unchanged_required, + 'exclude': directives.unchanged_required, + 'hideexcludes': directives.flag, + 'linenos': directives.flag, + 'language': directives.unchanged_required, + 'encoding': directives.encoding, + 'prepend': directives.unchanged_required, + 'append': directives.unchanged_required, } def run(self): @@ -75,6 +76,7 @@ class IncludeCode(Directive): exclude_sections = self.options.get('exclude', '') include = set(include_sections.split(',')) if include_sections else set() exclude = set(exclude_sections.split(',')) if exclude_sections else set() + hideexcludes = 'hideexcludes' in self.options if section: include |= {section} within = set() @@ -92,7 +94,8 @@ class IncludeCode(Directive): within |= {section_name} if not excluding and (exclude & within): excluding = True - res.append(' ' * index + comment + ' ' + section_name.replace('-', ' ') + ' ...\n') + if not hideexcludes: + res.append(' ' * index + comment + ' ' + section_name.replace('-', ' ') + ' ...\n') elif not (exclude & within) and (not include or (include & within)): res.append(line) lines = res @@ -107,7 +110,7 @@ class IncludeCode(Directive): nonempty = filter(lambda l: l.strip(), lines) tabcounts = map(lambda l: countwhile(lambda c: c == ' ', l), nonempty) - tabshift = min(tabcounts) + tabshift = min(tabcounts) if tabcounts else 0 if tabshift > 0: lines = map(lambda l: l[tabshift:] if len(l) > tabshift else l, lines) diff --git a/akka-docs/manual/examples/Pi.scala b/akka-docs/manual/examples/Pi.scala new file mode 100644 index 0000000000..6bf1dea903 --- /dev/null +++ b/akka-docs/manual/examples/Pi.scala @@ -0,0 +1,129 @@ +//#imports +package akka.tutorial.scala.first + +import akka.actor.{Actor, PoisonPill} +import Actor._ +import akka.routing.{Routing, CyclicIterator} +import Routing._ + +import System.{currentTimeMillis => now} +import java.util.concurrent.CountDownLatch +//#imports + +//#app +object Pi extends App { + + calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) + + //#actors-and-messages + // ==================== + // ===== Messages ===== + // ==================== + //#messages + sealed trait PiMessage + case object Calculate extends PiMessage + case class Work(start: Int, nrOfElements: Int) extends PiMessage + case class Result(value: Double) extends PiMessage + //#messages + + // ================== + // ===== Worker ===== + // ================== + //#worker + class Worker extends Actor { + + //#calculate-pi + def calculatePiFor(start: Int, nrOfElements: Int): Double = { + var acc = 0.0 + for (i <- start until (start + nrOfElements)) + acc += 4 * math.pow(-1, i) / (2 * i + 1) + acc + } + //#calculate-pi + + def receive = { + case Work(start, nrOfElements) => + self reply Result(calculatePiFor(start, nrOfElements)) // perform the work + } + } + //#worker + + // ================== + // ===== Master ===== + // ================== + //#master + class Master( + nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) + extends Actor { + + var pi: Double = _ + var nrOfResults: Int = _ + var start: Long = _ + + //#create-workers + // create the workers + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) + + // wrap them with a load-balancing router + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() + //#create-workers + + //#master-receive + // message handler + def receive = { + //#message-handling + case Calculate => + // schedule work + for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) + + // send a PoisonPill to all workers telling them to shut down themselves + router ! Broadcast(PoisonPill) + + // send a PoisonPill to the router, telling him to shut himself down + router ! PoisonPill + + case Result(value) => + // handle result from the worker + pi += value + nrOfResults += 1 + if (nrOfResults == nrOfMessages) self.stop() + //#message-handling + } + //#master-receive + + override def preStart { + start = now + } + + override def postStop { + // tell the world that the calculation is complete + println( + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" + .format(pi, (now - start))) + latch.countDown() + } + } + //#master + //#actors-and-messages + + // ================== + // ===== Run it ===== + // ================== + def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { + + // this latch is only plumbing to know when the calculation is completed + val latch = new CountDownLatch(1) + + // create the master + val master = actorOf( + new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() + + // start the calculation + master ! Calculate + + // wait for master to shut down + latch.await() + } +} +//#app + diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/manual/getting-started-first.rst index 9e9562b605..aaa466c1dc 100644 --- a/akka-docs/manual/getting-started-first.rst +++ b/akka-docs/manual/getting-started-first.rst @@ -172,17 +172,9 @@ Start writing the code Now it's about time to start hacking. -We start by creating a ``Pi.scala`` file and adding these import statements at the top of the file:: +We start by creating a ``Pi.scala`` file and adding these import statements at the top of the file: - package akka.tutorial.scala.first - - import akka.actor.{Actor, PoisonPill} - import Actor._ - import akka.routing.{Routing, CyclicIterator} - import Routing._ - import akka.dispatch.Dispatchers - - import java.util.concurrent.CountDownLatch +.. includecode:: examples/Pi.scala#imports If you are using SBT in this tutorial then create the file in the ``src/main/scala`` directory. @@ -199,49 +191,30 @@ With this in mind, let's now create the messages that we want to have flowing in - ``Work`` -- sent from the ``Master`` actor to the ``Worker`` actors containing the work assignment - ``Result`` -- sent from the ``Worker`` actors to the ``Master`` actor containing the result from the worker's calculation -Messages sent to actors should always be immutable to avoid sharing mutable state. In scala we have 'case classes' which make excellent messages. So let's start by creating three messages as case classes. We also create a common base trait for our messages (that we define as being ``sealed`` in order to prevent creating messages outside our control):: +Messages sent to actors should always be immutable to avoid sharing mutable state. In scala we have 'case classes' which make excellent messages. So let's start by creating three messages as case classes. We also create a common base trait for our messages (that we define as being ``sealed`` in order to prevent creating messages outside our control): - sealed trait PiMessage - - case object Calculate extends PiMessage - - case class Work(start: Int, nrOfElements: Int) extends PiMessage - - case class Result(value: Double) extends PiMessage +.. includecode:: examples/Pi.scala#messages Creating the worker ------------------- -Now we can create the worker actor. This is done by mixing in the ``Actor`` trait and defining the ``receive`` method. The ``receive`` method defines our message handler. We expect it to be able to handle the ``Work`` message so we need to add a handler for this message:: +Now we can create the worker actor. This is done by mixing in the ``Actor`` trait and defining the ``receive`` method. The ``receive`` method defines our message handler. We expect it to be able to handle the ``Work`` message so we need to add a handler for this message: - class Worker extends Actor { - def receive = { - case Work(start, nrOfElements) => - self reply Result(calculatePiFor(start, nrOfElements)) // perform the work - } - } +.. includecode:: examples/Pi.scala#worker + :exclude: calculate-pi As you can see we have now created an ``Actor`` with a ``receive`` 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 ``self.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. While there are many ways we can implement this algorithm in Scala, in this introductory tutorial we have chosen an imperative style using a for comprehension and an accumulator:: +The only thing missing in our ``Worker`` actor is the implementation on the ``calculatePiFor(..)`` method. While there are many ways we can implement this algorithm in Scala, in this introductory tutorial we have chosen an imperative style using a for comprehension and an accumulator: - def calculatePiFor(start: Int, nrOfElements: Int): Double = { - var acc = 0.0 - for (i <- start until (start + nrOfElements)) - acc += 4 * math.pow(-1, i) / (2 * i + 1) - acc - } +.. includecode:: examples/Pi.scala#calculate-pi Creating the master ------------------- -The master actor is a little bit more involved. In its constructor we need to create the workers (the ``Worker`` actors) and start them. We will also wrap them in a load-balancing router to make it easier to spread out the work evenly between the workers. Let's do that first:: +The master actor is a little bit more involved. In its constructor we need to create the workers (the ``Worker`` actors) and start them. We will also wrap them in a load-balancing router to make it easier to spread out the work evenly between the workers. Let's do that first: - // create the workers - val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) - - // wrap them with a load-balancing router - val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() +.. includecode:: examples/Pi.scala#create-workers As you can see we are using the ``actorOf`` factory method to create actors, this method returns as an ``ActorRef`` which is a reference to our newly created actor. This method is available in the ``Actor`` object but is usually imported:: @@ -253,33 +226,10 @@ Now we have a router that is representing all our workers in a single abstractio - ``nrOfMessages`` -- defining how many number chunks to send out to the workers - ``nrOfElements`` -- defining how big the number chunks sent to each worker should be -Here is the master actor:: +Here is the master actor: - class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) - extends Actor { - - var pi: Double = _ - var nrOfResults: Int = _ - var start: Long = _ - - // create the workers - val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) - - // wrap them with a load-balancing router - val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() - - def receive = { ... } - - override def preStart { - start = now - } - - override def postStop { - // tell the world that the calculation is complete - println("\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis".format(pi, (now - start))) - latch.countDown() - } - } +.. includecode:: examples/Pi.scala#master + :exclude: message-handling A couple of things are worth explaining further. @@ -296,170 +246,27 @@ The ``Calculate`` handler is sending out work to all the ``Worker`` actors and a The ``Result`` handler is simpler, here we get the value from the ``Result`` message and aggregate it to our ``pi`` member variable. We also keep track of how many results we have received back, and if that matches the number of tasks sent out, the ``Master`` actor considers itself done and shuts down. -Let's capture this in code:: +Let's capture this in code: - // message handler - def receive = { - case Calculate => - // schedule work - for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) - - // send a PoisonPill to all workers telling them to shut down themselves - router ! Broadcast(PoisonPill) - - // send a PoisonPill to the router, telling him to shut himself down - router ! PoisonPill - - case Result(value) => - // handle result from the worker - pi += value - nrOfResults += 1 - if (nrOfResults == nrOfMessages) self.stop() - } +.. includecode:: examples/Pi.scala#master-receive Bootstrap the calculation ------------------------- Now the only thing that is left to implement is the runner that should bootstrap and run the calculation for us. We do that by creating an object that we call ``Pi``, here we can extend the ``App`` trait in Scala, which means that we will be able to run this as an application directly from the command line. -The ``Pi`` object is a perfect container module for our actors and messages, so let's put them all there. We also create a method ``calculate`` in which we start up the ``Master`` actor and wait for it to finish:: +The ``Pi`` object is a perfect container module for our actors and messages, so let's put them all there. We also create a method ``calculate`` in which we start up the ``Master`` actor and wait for it to finish: - object Pi extends App { - - calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) - - ... // actors and messages - - def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { - - // this latch is only plumbing to know when the calculation is completed - val latch = new CountDownLatch(1) - - // create the master - val master = actorOf( - new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() - - // start the calculation - master ! Calculate - - // wait for master to shut down - latch.await() - } - } +.. includecode:: examples/Pi.scala#app + :exclude: actors-and-messages That's it. Now we are done. -But before we package it up and run it, let's take a look at the full code now, with package declaration, imports and all:: +But before we package it up and run it, let's take a look at the full code now, with package declaration, imports and all: - package akka.tutorial.scala.first +.. includecode:: examples/Pi.scala - import akka.actor.{Actor, PoisonPill} - import Actor._ - import akka.routing.{Routing, CyclicIterator} - import Routing._ - import java.util.concurrent.CountDownLatch - - object Pi extends App { - - calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) - - // ==================== - // ===== Messages ===== - // ==================== - sealed trait PiMessage - case object Calculate extends PiMessage - case class Work(start: Int, nrOfElements: Int) extends PiMessage - case class Result(value: Double) extends PiMessage - - // ================== - // ===== Worker ===== - // ================== - class Worker extends Actor { - - // define the work - def calculatePiFor(start: Int, nrOfElements: Int): Double = { - var acc = 0.0 - for (i <- start until (start + nrOfElements)) - acc += 4 * math.pow(-1, i) / (2 * i + 1) - acc - } - - def receive = { - case Work(start, nrOfElements) => - self reply Result(calculatePiFor(start, nrOfElements)) // perform the work - } - } - - // ================== - // ===== Master ===== - // ================== - class Master( - nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) - extends Actor { - - var pi: Double = _ - var nrOfResults: Int = _ - var start: Long = _ - - // create the workers - val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) - - // wrap them with a load-balancing router - val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() - - // message handler - def receive = { - case Calculate => - // schedule work - //for (arg <- 0 until nrOfMessages) router ! Work(arg, nrOfElements) - for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) - - // send a PoisonPill to all workers telling them to shut down themselves - router ! Broadcast(PoisonPill) - - // send a PoisonPill to the router, telling him to shut himself down - router ! PoisonPill - - case Result(value) => - // handle result from the worker - pi += value - nrOfResults += 1 - if (nrOfResults == nrOfMessages) self.stop() - } - - override def preStart { - start = now - } - - override def postStop { - // tell the world that the calculation is complete - println( - "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" - .format(pi, (now - start))) - latch.countDown() - } - } - - // ================== - // ===== Run it ===== - // ================== - def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { - - // this latch is only plumbing to know when the calculation is completed - val latch = new CountDownLatch(1) - - // create the master - val master = actorOf( - new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() - - // start the calculation - master ! Calculate - - // wait for master to shut down - latch.await() - } - } Run it as a command line application ------------------------------------ From f4fd3ed814c561a4a6b02a427ecdaa5de5b72baf Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 19 Apr 2011 14:27:48 +1200 Subject: [PATCH 094/147] Rework local python packages for akka-docs Hopefully this is more cross-platform friendly. --- akka-docs/Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/akka-docs/Makefile b/akka-docs/Makefile index d56a2fdf1e..d2be158d10 100644 --- a/akka-docs/Makefile +++ b/akka-docs/Makefile @@ -7,6 +7,7 @@ SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build EASYINSTALL = easy_install +LOCALPACKAGES = $(shell pwd)/$(BUILDDIR)/site-packages PYGMENTSDIR = pygments # Internal variables. @@ -14,6 +15,10 @@ PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# Set python path to include local packages for pygments styles. +PYTHONPATH += $(LOCALPACKAGES) +export PYTHONPATH + .PHONY: help clean pygments html singlehtml latex pdf help: @@ -28,7 +33,8 @@ clean: -rm -rf $(BUILDDIR)/* pygments: - $(EASYINSTALL) --user $(PYGMENTSDIR) + mkdir $(LOCALPACKAGES) + $(EASYINSTALL) --install-dir $(LOCALPACKAGES) $(PYGMENTSDIR) -rm -rf $(PYGMENTSDIR)/*.egg-info $(PYGMENTSDIR)/build $(PYGMENTSDIR)/temp @echo @echo "Custom pygments styles have been installed." From 4f79a2a3de82a06321c2856418ee99cc45fed92d Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 19 Apr 2011 14:34:47 +1200 Subject: [PATCH 095/147] mkdir -p --- akka-docs/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-docs/Makefile b/akka-docs/Makefile index d2be158d10..7b803258cb 100644 --- a/akka-docs/Makefile +++ b/akka-docs/Makefile @@ -33,7 +33,7 @@ clean: -rm -rf $(BUILDDIR)/* pygments: - mkdir $(LOCALPACKAGES) + mkdir -p $(LOCALPACKAGES) $(EASYINSTALL) --install-dir $(LOCALPACKAGES) $(PYGMENTSDIR) -rm -rf $(PYGMENTSDIR)/*.egg-info $(PYGMENTSDIR)/build $(PYGMENTSDIR)/temp @echo From 897504f973b1dec508463304b6d63682d90bbf79 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 19 Apr 2011 14:40:04 +1200 Subject: [PATCH 096/147] Make includecode directive python 2.6 friendly --- akka-docs/exts/includecode.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/akka-docs/exts/includecode.py b/akka-docs/exts/includecode.py index 816126c697..c12ddfa7f4 100644 --- a/akka-docs/exts/includecode.py +++ b/akka-docs/exts/includecode.py @@ -78,7 +78,7 @@ class IncludeCode(Directive): exclude = set(exclude_sections.split(',')) if exclude_sections else set() hideexcludes = 'hideexcludes' in self.options if section: - include |= {section} + include |= set([section]) within = set() res = [] excluding = False @@ -87,11 +87,11 @@ class IncludeCode(Directive): if index >= 0: section_name = line[index+lenm:].strip() if section_name in within: - within ^= {section_name} + within ^= set([section_name]) if excluding and not (exclude & within): excluding = False else: - within |= {section_name} + within |= set([section_name]) if not excluding and (exclude & within): excluding = True if not hideexcludes: From 427a6cf63e2db05f0c9ce8483ab468fd9c36a572 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 19 Apr 2011 17:45:01 +1200 Subject: [PATCH 097/147] Rework routing spec - failing under jenkins --- .../test/scala/akka/routing/RoutingSpec.scala | 975 +++++++++--------- 1 file changed, 485 insertions(+), 490 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala index d79bd0651e..96f93fc160 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala @@ -1,537 +1,532 @@ package akka.actor.routing +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers + +import akka.testing._ +import akka.testing.Testing.{sleepFor, testMillis} +import akka.util.duration._ + import akka.actor.Actor import akka.actor.Actor._ - -import org.scalatest.Suite -import org.junit.runner.RunWith -import org.scalatest.junit.JUnitRunner -import org.scalatest.matchers.MustMatchers -import org.junit.Test - -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.{CountDownLatch, TimeUnit} import akka.routing._ -@RunWith(classOf[JUnitRunner]) -class RoutingSpec extends junit.framework.TestCase with Suite with MustMatchers { +import java.util.concurrent.atomic.AtomicInteger + + +class RoutingSpec extends WordSpec with MustMatchers { import Routing._ - @Test def testDispatcher = { - val (testMsg1,testMsg2,testMsg3,testMsg4) = ("test1","test2","test3","test4") - val targetOk = new AtomicInteger(0) - val t1 = actorOf( new Actor() { - def receive = { - case `testMsg1` => self.reply(3) - case `testMsg2` => self.reply(7) - } - } ).start() + "Routing" must { - val t2 = actorOf( new Actor() { - def receive = { - case `testMsg3` => self.reply(11) - } - }).start() + "dispatch" in { + val Test1 = "test1" + val Test2 = "test2" + val Test3 = "test3" - val d = dispatcherActor { - case `testMsg1`|`testMsg2` => t1 - case `testMsg3` => t2 - }.start() - - val result = for { - a <- (d !! (testMsg1, 5000)).as[Int] - b <- (d !! (testMsg2, 5000)).as[Int] - c <- (d !! (testMsg3, 5000)).as[Int] - } yield a + b + c - - result.isDefined must be (true) - result.get must be(21) - - for(a <- List(t1,t2,d)) a.stop() - } - - @Test def testLogger = { - val msgs = new java.util.concurrent.ConcurrentSkipListSet[Any] - val latch = new CountDownLatch(2) - val t1 = actorOf(new Actor { def receive = { case _ => } }).start() - val l = loggerActor(t1,(x) => { msgs.add(x); latch.countDown() }).start() - val foo : Any = "foo" - val bar : Any = "bar" - l ! foo - l ! bar - val done = latch.await(5,TimeUnit.SECONDS) - done must be (true) - msgs must ( have size (2) and contain (foo) and contain (bar) ) - t1.stop() - l.stop() - } - - @Test def testSmallestMailboxFirstDispatcher = { - val t1ProcessedCount = new AtomicInteger(0) - val latch = new CountDownLatch(500) - val t1 = actorOf(new Actor { - def receive = { - case x => - Thread.sleep(50) // slow actor - t1ProcessedCount.incrementAndGet - latch.countDown() - } - }).start() - - val t2ProcessedCount = new AtomicInteger(0) - val t2 = actorOf(new Actor { - def receive = { - case x => t2ProcessedCount.incrementAndGet - latch.countDown() - } - }).start() - val d = loadBalancerActor(new SmallestMailboxFirstIterator(t1 :: t2 :: Nil)) - for (i <- 1 to 500) d ! i - val done = latch.await(10,TimeUnit.SECONDS) - done must be (true) - t1ProcessedCount.get must be < (t2ProcessedCount.get) // because t1 is much slower and thus has a bigger mailbox all the time - for(a <- List(t1,t2,d)) a.stop() - } - - @Test def testListener = { - val latch = new CountDownLatch(2) - val foreachListener = new CountDownLatch(2) - val num = new AtomicInteger(0) - val i = actorOf(new Actor with Listeners { - def receive = listenerManagement orElse { - case "foo" => gossip("bar") - } - }) - i.start() - - def newListener = actorOf(new Actor { - def receive = { - case "bar" => - num.incrementAndGet - latch.countDown() - case "foo" => foreachListener.countDown() - } - }).start() - - val a1 = newListener - val a2 = newListener - val a3 = newListener - - i ! Listen(a1) - i ! Listen(a2) - i ! Listen(a3) - i ! Deafen(a3) - i ! WithListeners(_ ! "foo") - i ! "foo" - - val done = latch.await(5,TimeUnit.SECONDS) - done must be (true) - num.get must be (2) - val withListeners = foreachListener.await(5,TimeUnit.SECONDS) - withListeners must be (true) - for(a <- List(i,a1,a2,a3)) a.stop() - } - - @Test def testIsDefinedAt = { - import akka.actor.ActorRef - - val (testMsg1,testMsg2,testMsg3,testMsg4) = ("test1","test2","test3","test4") - - val t1 = actorOf( new Actor() { - def receive = { - case `testMsg1` => self.reply(3) - case `testMsg2` => self.reply(7) - } - } ).start() - - val t2 = actorOf( new Actor() { - def receive = { - case `testMsg1` => self.reply(3) - case `testMsg2` => self.reply(7) - } - } ).start() - - val t3 = actorOf( new Actor() { - def receive = { - case `testMsg1` => self.reply(3) - case `testMsg2` => self.reply(7) - } - } ).start() - - val t4 = actorOf( new Actor() { - def receive = { - case `testMsg1` => self.reply(3) - case `testMsg2` => self.reply(7) - } - } ).start() - - val d1 = loadBalancerActor(new SmallestMailboxFirstIterator(t1 :: t2 :: Nil)) - val d2 = loadBalancerActor(new CyclicIterator[ActorRef](t3 :: t4 :: Nil)) - - t1.isDefinedAt(testMsg1) must be (true) - t1.isDefinedAt(testMsg3) must be (false) - t2.isDefinedAt(testMsg1) must be (true) - t2.isDefinedAt(testMsg3) must be (false) - d1.isDefinedAt(testMsg1) must be (true) - d1.isDefinedAt(testMsg3) must be (false) - d2.isDefinedAt(testMsg1) must be (true) - d2.isDefinedAt(testMsg3) must be (false) - - for(a <- List(t1,t2,d1,d2)) a.stop() - } - - // Actor Pool Capacity Tests - - // - // make sure the pool is of the fixed, expected capacity - // - @Test def testFixedCapacityActorPool = { - val latch = new CountDownLatch(2) - val counter = new AtomicInteger(0) - class TestPool extends Actor with DefaultActorPool - with FixedCapacityStrategy - with SmallestMailboxSelector - { - def factory = actorOf(new Actor { + val t1 = actorOf(new Actor { def receive = { - case _ => - counter.incrementAndGet - latch.countDown() - self reply_? "success" + case Test1 => self.reply(3) + case Test2 => self.reply(7) } - }) + }).start() - def limit = 2 - def selectionCount = 1 - def partialFill = true - def instance = factory - def receive = _route - } - - val successes = new CountDownLatch(2) - implicit val successCounterActor = Some(actorOf(new Actor { - def receive = { - case "success" => successes.countDown() - } - }).start()) - - val pool = actorOf(new TestPool).start() - pool ! "a" - pool ! "b" - - latch.await(1,TimeUnit.SECONDS) must be (true) - successes.await(1,TimeUnit.SECONDS) must be (true) - counter.get must be (2) - (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be (2) - - pool stop - } - - @Test def testTicket705 = { - - val actorPool = actorOf(new Actor with DefaultActorPool - with BoundedCapacityStrategy - with MailboxPressureCapacitor - with SmallestMailboxSelector - with BasicFilter { - //with BasicNoBackoffFilter { - def lowerBound = 2 - def upperBound = 20 - 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 { - def receive = { - case req: String => { - Thread.sleep(10L) - self.reply_?("Response") - } - } - }) - }).start() - - try { - (for(count <- 1 to 500) yield actorPool.!!![String]("Test", 20000)) foreach { - _.await.resultOrException.get must be ("Response") - } - } finally { - actorPool.stop() - } - } - - // - // make sure the pool starts at the expected lower limit and grows to the upper as needed - // as influenced by the backlog of blocking pooled actors - // - @Test def testBoundedCapacityActorPoolWithActiveFuturesPressure = { - - var latch = new CountDownLatch(3) - val counter = new AtomicInteger(0) - class TestPool extends Actor with DefaultActorPool - with BoundedCapacityStrategy - with ActiveFuturesPressureCapacitor - with SmallestMailboxSelector - with BasicNoBackoffFilter - { - def factory = actorOf(new Actor { + val t2 = actorOf( new Actor() { def receive = { - case n:Int => - Thread.sleep(n) - counter.incrementAndGet + case Test3 => self.reply(11) + } + }).start() + + val d = dispatcherActor { + case Test1 | Test2 => t1 + case Test3 => t2 + }.start() + + val result = for { + a <- (d !! (Test1, testMillis(5 seconds))).as[Int] + b <- (d !! (Test2, testMillis(5 seconds))).as[Int] + c <- (d !! (Test3, testMillis(5 seconds))).as[Int] + } yield a + b + c + + result.isDefined must be (true) + result.get must be (21) + + for(a <- List(t1, t2, d)) a.stop() + } + + "have messages logged" in { + val msgs = new java.util.concurrent.ConcurrentSkipListSet[Any] + val latch = TestLatch(2) + + val actor = actorOf(new Actor { + def receive = { case _ => } + }).start() + + val logger = loggerActor(actor, x => { msgs.add(x); latch.countDown() }).start() + + val foo: Any = "foo" + val bar: Any = "bar" + + logger ! foo + logger ! bar + + latch.await + + msgs must ( have size (2) and contain (foo) and contain (bar) ) + + actor.stop() + logger.stop() + } + + "dispatch to smallest mailbox" in { + val t1Count = new AtomicInteger(0) + val t2Count = new AtomicInteger(0) + val latch = TestLatch(500) + + val t1 = actorOf(new Actor { + def receive = { + case x => + sleepFor(50 millis) // slow actor + t1Count.incrementAndGet latch.countDown() } - }) + }).start() - def lowerBound = 2 - def upperBound = 4 - def rampupRate = 0.1 - def partialFill = true - def selectionCount = 1 - def instance = factory - def receive = _route - } - - // - // first message should create the minimum number of delgates - // - val pool = actorOf(new TestPool).start() - pool ! 1 - (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be (2) - - var loops = 0 - def loop(t:Int) = { - latch = new CountDownLatch(loops) - counter.set(0) - for (m <- 0 until loops) { - pool !!! t - Thread.sleep(50) - } - } - - // - // 2 more should go thru w/out triggering more - // - loops = 2 - loop(500) - var done = latch.await(5,TimeUnit.SECONDS) - done must be (true) - counter.get must be (loops) - (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be (2) - - // - // a whole bunch should max it out - // - loops = 10 - loop(500) - - done = latch.await(5,TimeUnit.SECONDS) - done must be (true) - counter.get must be (loops) - (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be (4) - - pool stop - } - - // - // make sure the pool starts at the expected lower limit and grows to the upper as needed - // as influenced by the backlog of messages in the delegate mailboxes - // - @Test def testBoundedCapacityActorPoolWithMailboxPressure = { - - var latch = new CountDownLatch(3) - val counter = new AtomicInteger(0) - class TestPool extends Actor with DefaultActorPool - with BoundedCapacityStrategy - with MailboxPressureCapacitor - with SmallestMailboxSelector - with BasicNoBackoffFilter - { - def factory = actorOf(new Actor { + val t2 = actorOf(new Actor { def receive = { - case n:Int => - Thread.sleep(n) - counter.incrementAndGet + case x => + t2Count.incrementAndGet latch.countDown() } - }) + }).start() - def lowerBound = 2 - def upperBound = 4 - def pressureThreshold = 3 - def rampupRate = 0.1 - def partialFill = true - def selectionCount = 1 - def instance = factory - def receive = _route + val d = loadBalancerActor(new SmallestMailboxFirstIterator(t1 :: t2 :: Nil)) + + for (i <- 1 to 500) d ! i + + latch.await(10 seconds) + + // because t1 is much slower and thus has a bigger mailbox all the time + t1Count.get must be < (t2Count.get) + + for(a <- List(t1, t2, d)) a.stop() } - val pool = actorOf(new TestPool).start() - var loops = 0 - def loop(t:Int) = { - latch = new CountDownLatch(loops) - counter.set(0) - for (m <- 0 until loops) { - pool ! t + "listen" in { + val fooLatch = TestLatch(2) + val barLatch = TestLatch(2) + val barCount = new AtomicInteger(0) + + val broadcast = actorOf(new Actor with Listeners { + def receive = listenerManagement orElse { + case "foo" => gossip("bar") } - } + }).start() + + def newListener = actorOf(new Actor { + def receive = { + case "bar" => + barCount.incrementAndGet + barLatch.countDown() + case "foo" => + fooLatch.countDown() + } + }).start() + + val a1 = newListener + val a2 = newListener + val a3 = newListener + + broadcast ! Listen(a1) + broadcast ! Listen(a2) + broadcast ! Listen(a3) + + broadcast ! Deafen(a3) + + broadcast ! WithListeners(_ ! "foo") + broadcast ! "foo" + + barLatch.await + barCount.get must be (2) + + fooLatch.await + + for(a <- List(broadcast, a1 ,a2 ,a3)) a.stop() + } + + "be defined at" in { + import akka.actor.ActorRef + + val Yes = "yes" + val No = "no" + + def testActor() = actorOf( new Actor() { + def receive = { + case Yes => "yes" + } + }).start() + + val t1 = testActor() + val t2 = testActor() + val t3 = testActor() + val t4 = testActor() + + val d1 = loadBalancerActor(new SmallestMailboxFirstIterator(t1 :: t2 :: Nil)) + val d2 = loadBalancerActor(new CyclicIterator[ActorRef](t3 :: t4 :: Nil)) + + t1.isDefinedAt(Yes) must be (true) + t1.isDefinedAt(No) must be (false) + t2.isDefinedAt(Yes) must be (true) + t2.isDefinedAt(No) must be (false) + d1.isDefinedAt(Yes) must be (true) + d1.isDefinedAt(No) must be (false) + d2.isDefinedAt(Yes) must be (true) + d2.isDefinedAt(No) must be (false) + + for(a <- List(t1, t2, d1, d2)) a.stop() + } + } + + "Actor Pool" must { + + "have expected capacity" in { + val latch = TestLatch(2) + val count = new AtomicInteger(0) + + val pool = actorOf( + new Actor with DefaultActorPool + with FixedCapacityStrategy + with SmallestMailboxSelector + { + def factory = actorOf(new Actor { + def receive = { + case _ => + count.incrementAndGet + latch.countDown() + self reply_? "success" + } + }).start() + + def limit = 2 + def selectionCount = 1 + def partialFill = true + def instance = factory + def receive = _route + }).start() + + val successes = TestLatch(2) + val successCounter = Some(actorOf(new Actor { + def receive = { + case "success" => successes.countDown() + } + }).start()) + + implicit val replyTo = successCounter + pool ! "a" + pool ! "b" + + latch.await + successes.await + + count.get must be (2) + + (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be (2) + + pool.stop() + } + + + "pass ticket #705" in { + val pool = actorOf( + new Actor with DefaultActorPool + with BoundedCapacityStrategy + with MailboxPressureCapacitor + with SmallestMailboxSelector + with BasicFilter + { + def lowerBound = 2 + def upperBound = 20 + 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 { + def receive = { + case req: String => { + sleepFor(10 millis) + self.reply_?("Response") + } + } + }) + }).start() + + try { + (for (count <- 1 to 500) yield pool.!!![String]("Test", 20000)) foreach { + _.await.resultOrException.get must be ("Response") + } + } finally { + pool.stop() + } + } + + "grow as needed under pressure" in { + // make sure the pool starts at the expected lower limit and grows to the upper as needed + // as influenced by the backlog of blocking pooled actors + + var latch = TestLatch(3) + val count = new AtomicInteger(0) + + val pool = actorOf( + new Actor with DefaultActorPool + with BoundedCapacityStrategy + with ActiveFuturesPressureCapacitor + with SmallestMailboxSelector + with BasicNoBackoffFilter + { + def factory = actorOf(new Actor { + def receive = { + case n: Int => + sleepFor(n millis) + count.incrementAndGet + latch.countDown() + } + }) + + def lowerBound = 2 + def upperBound = 4 + def rampupRate = 0.1 + def partialFill = true + def selectionCount = 1 + def instance = factory + def receive = _route + }).start() + + // first message should create the minimum number of delgates + + pool ! 1 + + (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be (2) + + var loops = 0 + def loop(t: Int) = { + latch = TestLatch(loops) + count.set(0) + for (m <- 0 until loops) { + pool !!! t + sleepFor(50 millis) + } + } + + // 2 more should go thru without triggering more + + loops = 2 + + loop(500) + latch.await + count.get must be (loops) + + (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be (2) + + // a whole bunch should max it out + + loops = 10 + loop(500) + latch.await + count.get must be (loops) + + (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be (4) + + pool.stop() + } + + "grow as needed under mailbox pressure" in { + // make sure the pool starts at the expected lower limit and grows to the upper as needed + // as influenced by the backlog of messages in the delegate mailboxes + + var latch = TestLatch(3) + val count = new AtomicInteger(0) + + val pool = actorOf( + new Actor with DefaultActorPool + with BoundedCapacityStrategy + with MailboxPressureCapacitor + with SmallestMailboxSelector + with BasicNoBackoffFilter + { + def factory = actorOf(new Actor { + def receive = { + case n: Int => + sleepFor(n millis) + count.incrementAndGet + latch.countDown() + } + }) + + def lowerBound = 2 + def upperBound = 4 + def pressureThreshold = 3 + def rampupRate = 0.1 + def partialFill = true + def selectionCount = 1 + def instance = factory + def receive = _route + }).start() + + var loops = 0 + def loop(t: Int) = { + latch = TestLatch(loops) + count.set(0) + for (m <- 0 until loops) { + pool ! t + } + } + + // send a few messages and observe pool at its lower bound + loops = 3 + loop(500) + latch.await + count.get must be (loops) - // - // send a few messages and observe pool at its lower bound - // - loops = 3 - loop(500) - var done = latch.await(5,TimeUnit.SECONDS) - done must be (true) - counter.get must be (loops) (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be (2) - // // send a bunch over the theshold and observe an increment - // loops = 15 loop(500) - done = latch.await(10,TimeUnit.SECONDS) - done must be (true) - counter.get must be (loops) + latch.await(10 seconds) + count.get must be (loops) + (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be >= (3) - pool stop - } - - // Actor Pool Selector Tests - - @Test def testRoundRobinSelector = { - - var latch = new CountDownLatch(2) - val delegates = new java.util.concurrent.ConcurrentHashMap[String, String] - - class TestPool1 extends Actor with DefaultActorPool - with FixedCapacityStrategy - with RoundRobinSelector - with BasicNoBackoffFilter - { - def factory = actorOf(new Actor { - def receive = { - case _ => - delegates put(self.uuid.toString, "") - latch.countDown() - } - }) - - def limit = 1 - def selectionCount = 2 - def rampupRate = 0.1 - def partialFill = true - def instance = factory - def receive = _route + pool.stop() } - val pool1 = actorOf(new TestPool1).start() - pool1 ! "a" - pool1 ! "b" - var done = latch.await(1,TimeUnit.SECONDS) - done must be (true) - delegates.size must be (1) - pool1 stop + "round robin" in { + val latch1 = TestLatch(2) + val delegates = new java.util.concurrent.ConcurrentHashMap[String, String] - class TestPool2 extends Actor with DefaultActorPool - with FixedCapacityStrategy - with RoundRobinSelector - with BasicNoBackoffFilter - { - def factory = actorOf(new Actor { - def receive = { - case _ => - delegates put(self.uuid.toString, "") - latch.countDown() - } - }) + val pool1 = actorOf( + new Actor with DefaultActorPool + with FixedCapacityStrategy + with RoundRobinSelector + with BasicNoBackoffFilter + { + def factory = actorOf(new Actor { + def receive = { + case _ => + delegates put(self.uuid.toString, "") + latch1.countDown() + } + }) - def limit = 2 - def selectionCount = 2 - def rampupRate = 0.1 - def partialFill = false - def instance = factory - def receive = _route + def limit = 1 + def selectionCount = 2 + def rampupRate = 0.1 + def partialFill = true + def instance = factory + def receive = _route + }).start() + + pool1 ! "a" + pool1 ! "b" + + latch1.await + delegates.size must be (1) + + pool1.stop() + + val latch2 = TestLatch(2) + delegates.clear() + + val pool2 = actorOf( + new Actor with DefaultActorPool + with FixedCapacityStrategy + with RoundRobinSelector + with BasicNoBackoffFilter + { + def factory = actorOf(new Actor { + def receive = { + case _ => + delegates put(self.uuid.toString, "") + latch2.countDown() + } + }) + + def limit = 2 + def selectionCount = 2 + def rampupRate = 0.1 + def partialFill = false + def instance = factory + def receive = _route + }).start() + + pool2 ! "a" + pool2 ! "b" + + latch2.await + delegates.size must be (2) + + pool2.stop() } - latch = new CountDownLatch(2) - delegates clear + "backoff" in { + val latch = TestLatch(10) - val pool2 = actorOf(new TestPool2).start() - pool2 ! "a" - pool2 ! "b" - done = latch.await(1, TimeUnit.SECONDS) - done must be (true) - delegates.size must be (2) - pool2 stop - } - - // Actor Pool Filter Tests + val pool = actorOf( + new Actor with DefaultActorPool + with BoundedCapacityStrategy + with MailboxPressureCapacitor + with SmallestMailboxSelector + with Filter + with RunningMeanBackoff + with BasicRampup + { + def factory = actorOf(new Actor { + def receive = { + case n: Int => + sleepFor(n millis) + latch.countDown() + } + }) - // - // reuse previous test to max pool then observe filter reducing capacity over time - // - @Test def testBoundedCapacityActorPoolWithBackoffFilter = { + def lowerBound = 1 + def upperBound = 5 + def pressureThreshold = 1 + def partialFill = true + def selectionCount = 1 + def rampupRate = 0.1 + def backoffRate = 0.50 + def backoffThreshold = 0.50 + def instance = factory + def receive = _route + }).start() - var latch = new CountDownLatch(10) - class TestPool extends Actor with DefaultActorPool - with BoundedCapacityStrategy - with MailboxPressureCapacitor - with SmallestMailboxSelector - with Filter - with RunningMeanBackoff - with BasicRampup - { - def factory = actorOf(new Actor { - def receive = { - case n:Int => - Thread.sleep(n) - latch.countDown() - } - }) + // put some pressure on the pool - def lowerBound = 1 - def upperBound = 5 - def pressureThreshold = 1 - def partialFill = true - def selectionCount = 1 - def rampupRate = 0.1 - def backoffRate = 0.50 - def backoffThreshold = 0.50 - def instance = factory - def receive = _route + for (m <- 0 to 10) pool ! 250 + + sleepFor(5 millis) + + val z = (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size + + z must be >= (2) + + // let it cool down + + for (m <- 0 to 3) { + pool ! 1 + sleepFor(500 millis) + } + + (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be <= (z) + + pool.stop() } - - - // - // put some pressure on the pool - // - val pool = actorOf(new TestPool).start() - for (m <- 0 to 10) pool ! 250 - Thread.sleep(5) - val z = (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size - z must be >= (2) - var done = latch.await(10,TimeUnit.SECONDS) - done must be (true) - - - // - // - // - for (m <- 0 to 3) { - pool ! 1 - Thread.sleep(500) - } - (pool !! ActorPool.Stat).asInstanceOf[Option[ActorPool.Stats]].get.size must be <= (z) - - pool stop } } + From 04f27671764f26f23ab282fb6e352dd4027ab276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Tue, 19 Apr 2011 13:12:37 +0200 Subject: [PATCH 098/147] Added Java version of first tutorial + polished Scala version as well --- akka-docs/index.rst | 3 +- .../manual/getting-started-first-java.rst | 741 ++++++++++++++++++ .../manual/getting-started-first-scala.rst | 535 +++++++++++++ 3 files changed, 1278 insertions(+), 1 deletion(-) create mode 100644 akka-docs/manual/getting-started-first-java.rst create mode 100644 akka-docs/manual/getting-started-first-scala.rst diff --git a/akka-docs/index.rst b/akka-docs/index.rst index 9771b0044c..6bce5e8df0 100644 --- a/akka-docs/index.rst +++ b/akka-docs/index.rst @@ -4,7 +4,8 @@ Contents .. toctree:: :maxdepth: 2 - manual/getting-started-first + manual/getting-started-first-scala + manual/getting-started-first-java pending/actor-registry-java pending/actor-registry-scala pending/actors-scala diff --git a/akka-docs/manual/getting-started-first-java.rst b/akka-docs/manual/getting-started-first-java.rst new file mode 100644 index 0000000000..0c92e7da24 --- /dev/null +++ b/akka-docs/manual/getting-started-first-java.rst @@ -0,0 +1,741 @@ +Getting Started Tutorial (Java): First Chapter +============================================== + +Introduction +------------ + +Welcome to the first tutorial on how to get started with Akka and Java. We assume that you already know what Akka and Java are and will now focus on the steps necessary to start your first project. + +There are two variations of this first tutorial: + +- creating a standalone project and run it from the command line +- creating a Maven project and running it from within Maven + +Since they are so similar we will present them both. + +The sample application that we will create is using actors to calculate the value of Pi. Calculating Pi is a CPU intensive operation and we will utilize Akka Actors to write a concurrent solution that scales out to multi-core processors. This sample will be extended in future tutorials to use Akka Remote Actors to scale out on multiple machines in a cluster. + +We will be using an algorithm that is called "embarrassingly parallel" which just means that each job is completely isolated and not coupled with any other job. Since this algorithm is so parallelizable it suits the actor model very well. + +Here is the formula for the algorithm we will use: + +.. image:: pi-formula.png + +In this particular algorithm the master splits the series into chunks which are sent out to each worker actor to be processed. When each worker has processed its chunk it sends a result back to the master which aggregates the total result. + +Tutorial source code +-------------------- + +If you want don't want to type in the code and/or set up a Maven project then you can check out the full tutorial from the Akka GitHub repository. It is in the ``akka-tutorials/akka-tutorial-first`` module. You can also browse it online `here `_, with the actual source code `here `_. + +Prerequisites +------------- + +This tutorial assumes that you have Jave 1.6 or later installed on you machine and ``java`` on your ``PATH``. You also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a decent text editor or IDE to type in the Java code. + +You need to make sure that ``$JAVA_HOME`` environment variable is set to the root of the Java distribution. You also need to make sure that the ``$JAVA_HOME/bin`` is on your ``PATH``:: + + $ export JAVA_HOME=..root of java distribution.. + $ export PATH=$PATH:$JAVA_HOME/bin + +You can test your installation by invoking ``java``:: + + $ java -version + java version "1.6.0_24" + Java(TM) SE Runtime Environment (build 1.6.0_24-b07-334-10M3326) + Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02-334, mixed mode) + +Downloading and installing Akka +------------------------------- + +To build and run the tutorial sample from the command line, you have to download Akka. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. + +Let's get the ``akka-1.1`` distribution of Akka core (not Akka Modules) from `http://akka.io/downloads `_. Once you have downloaded the distribution unzip it in the folder you would like to have Akka installed in, in my case I choose to install it in ``/Users/jboner/tools/``, simply by unzipping it to this directory. + +You need to do one more thing in order to install Akka properly: set the ``AKKA_HOME`` environment variable to the root of the distribution. In my case I'm opening up a shell, navigating down to the distribution, and setting the ``AKKA_HOME`` variable:: + + $ cd /Users/jboner/tools/akka-1.1 + $ export AKKA_HOME=`pwd` + $ echo $AKKA_HOME + /Users/jboner/tools/akka-1.1 + +The distribution looks like this:: + + $ ls -l + total 16944 + drwxr-xr-x 7 jboner staff 238 Apr 6 11:15 . + drwxr-xr-x 28 jboner staff 952 Apr 6 11:16 .. + drwxr-xr-x 17 jboner staff 578 Apr 6 11:16 deploy + drwxr-xr-x 26 jboner staff 884 Apr 6 11:16 dist + drwxr-xr-x 3 jboner staff 102 Apr 6 11:15 lib_managed + -rwxr-xr-x 1 jboner staff 8674105 Apr 6 11:15 scala-library.jar + drwxr-xr-x 4 jboner staff 136 Apr 6 11:16 scripts + +- In the ``dist`` directory we have the Akka JARs, including sources and docs. +- In the ``lib_managed/compile`` directory we have Akka's dependency JARs. +- In the ``deploy`` directory we have the sample JARs. +- In the ``scripts`` directory we have scripts for running Akka. +- Finally ``scala-library.jar`` is the JAR for the latest Scala distribution that Akka depends on. + +The only JAR we will need for this tutorial (apart from the ``scala-library.jar`` JAR) is the ``akka-actor-1.1.jar`` JAR in the ``dist`` directory. This is a self-contained JAR with zero dependencies and contains everything we need to write a system using Actors. + +Akka is very modular and has many JARs for containing different features. The core distribution has seven modules: + +- ``akka-actor-1.1.jar`` -- Standard Actors +- ``akka-typed-actor-1.1.jar`` -- Typed Actors +- ``akka-remote-1.1.jar`` -- Remote Actors +- ``akka-stm-1.1.jar`` -- STM (Software Transactional Memory), transactors and transactional datastructures +- ``akka-http-1.1.jar`` -- Akka Mist for continuation-based asynchronous HTTP and also Jersey integration +- ``akka-slf4j-1.1.jar`` -- SLF4J Event Handler Listener for logging with SLF4J +- ``akka-testkit-1.1.jar`` -- Toolkit for testing Actors + +We also have Akka Modules containing add-on modules outside the core of Akka. You can download the Akka Modules distribution from TODO. It contains Akka core as well. We will not be needing any modules there today, but for your information the module JARs are these: + +- ``akka-kernel-1.1.jar`` -- Akka microkernel for running a bare-bones mini application server (embeds Jetty etc.) +- ``akka-amqp-1.1.jar`` -- AMQP integration +- ``akka-camel-1.1.jar`` -- Apache Camel Actors integration (it's the best way to have your Akka application communicate with the rest of the world) +- ``akka-camel-typed-1.1.jar`` -- Apache Camel Typed Actors integration +- ``akka-scalaz-1.1.jar`` -- Support for the Scalaz library +- ``akka-spring-1.1.jar`` -- Spring framework integration +- ``akka-osgi-dependencies-bundle-1.1.jar`` -- OSGi support + +Downloading and installing Maven +-------------------------------- + +Maven is an excellent build system that can be used to build both Java and Scala projects. If you want to use Maven for this tutorial then follow the following instructions, if not you can skip this section and the next. + +First browse to the `Maven download page `_ and download the ``3.0.3`` distribution. + +To install Maven it is easiest to follow the instructions on `this page `_. + +Creating an Akka Maven project +------------------------------ + +If you have not already done so, now is the time to create a Maven project for our tutorial. You do that by stepping into the directory you want to create your project in and invoking the ``mvn`` command:: + + $ mvn archetype:generate \ + -DgroupId=akka.tutorial.first.java \ + -DartifactId=akka-tutorial-first-java \ + -DarchetypeArtifactId=maven-archetype-quickstart \ + -DinteractiveMode=false + +Now we have the basis for our Maven-based Akka project. Let's step into the project directory:: + + $ cd akka-tutorial-first-java + +Here is the layout that Maven created:: + + akka-tutorial-first-jboner + |-- pom.xml + `-- src + |-- main + | `-- java + | `-- akka + | `-- tutorial + | `-- first + | `-- java + | `-- App.java + +As you can see we already have a Java source file called ``App.java``, let's now rename it to ``Pi.java``. + +We also need to edit the ``pom.xml`` build file. Let's add the dependency we need as well as the Maven repository it should download it from. It should now look something like this:: + + + + 4.0.0 + + akka-tutorial-first-java + akka.tutorial.first.java + akka-tutorial-first-java + jar + 1.0-SNAPSHOT + http://akka.io + + + + se.scalablesolutions.akka + akka-actor + 1.1 + + + + + + Akka + Akka Maven2 Repository + http://www.scalablesolutions.se/akka/repository/ + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + + + +So, now we are all set. Just one final thing to do; make Maven download the dependencies it needs. That can be done by invoking:: + + $ mvn package + +Maven itself needs a whole bunch of dependencies but our project will only need one; ``akka-actor-1.1.jar``. Maven downloads that as well. + +Start writing the code +---------------------- + +Now it's about time to start hacking. + +We start by creating a ``Pi.java`` file and adding these import statements at the top of the file:: + + package akka.tutorial.first.java; + + import static akka.actor.Actors.actorOf; + import static akka.actor.Actors.poisonPill; + import static java.util.Arrays.asList; + + import akka.actor.ActorRef; + import akka.actor.UntypedActor; + import akka.actor.UntypedActorFactory; + import akka.routing.CyclicIterator; + import akka.routing.InfiniteIterator; + import akka.routing.Routing.Broadcast; + import akka.routing.UntypedLoadBalancer; + + import java.util.concurrent.CountDownLatch; + +If you are using Maven in this tutorial then create the file in the ``src/main/java/akka/tutorial/first/java`` directory. + +If you are using the command line tools then create the file wherever you want. I will create it in a directory called ``tutorial`` at the root of the Akka distribution, e.g. in ``$AKKA_HOME/tutorial/akka/tutorial/first/java/Pi.java``. + +Creating the messages +--------------------- + +The design we are aiming for is to have one ``Master`` actor initiating the computation, creating a set of ``Worker`` actors. Then it splits up the work into discrete chunks, and sends these chunks to the different workers in a round-robin fashion. The master waits until all the workers have completed their work and sent back results for aggregation. When computation is completed the master prints out the result, shuts down all workers and then itself. + +With this in mind, let's now create the messages that we want to have flowing in the system. We need three different messages: + +- ``Calculate`` -- sent to the ``Master`` actor to start the calculation +- ``Work`` -- sent from the ``Master`` actor to the ``Worker`` actors containing the work assignment +- ``Result`` -- sent from the ``Worker`` actors to the ``Master`` actor containing the result from the worker's calculation + +Messages sent to actors should always be immutable to avoid sharing mutable state. So let's start by creating three messages as immutable POJOs. We also create a wrapper ``Pi`` class to hold our implementation:: + + public class Pi { + + static class Calculate {} + + static class Work { + private final int arg; + private final int nrOfElements; + + public Work(int arg, int nrOfElements) { + this.arg = arg; + this.nrOfElements = nrOfElements; + } + + public int getArg() { return arg; } + public int getNrOfElements() { return nrOfElements; } + } + + static class Result { + private final double value; + + public Result(double value) { + this.value = value; + } + + public double getValue() { return value; } + } + } + +Creating the worker +------------------- + +Now we can create the worker actor. This is done by extending in the ``UntypedActor`` base class and defining the ``onReceive`` method. The ``onReceive`` method defines our message handler. We expect it to be able to handle the ``Work`` message so we need to add a handler for this message:: + + static class Worker extends UntypedActor { + + // message handler + public void onReceive(Object message) { + if (message instanceof Work) { + Work work = (Work) message; + + // perform the work + double result = calculatePiFor(work.getArg(), work.getNrOfElements()) + + // reply with the result + getContext().replyUnsafe(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. + +The only thing missing in our ``Worker`` actor is the implementation on the ``calculatePiFor(..)`` method:: + + // define the work + private double calculatePiFor(int arg, int nrOfElements) { + double acc = 0.0; + for (int i = arg * nrOfElements; i <= ((arg + 1) * nrOfElements - 1); i++) { + acc += 4 * Math.pow(-1, i) / (2 * i + 1); + } + return acc; + } + +Creating the master +------------------- + +The master actor is a little bit more involved. In its constructor we need to create the workers (the ``Worker`` actors) and start them. We will also wrap them in a load-balancing router to make it easier to spread out the work evenly between the workers. Let's do that first:: + + static class Master extends UntypedActor { + ... + + static class PiRouter extends UntypedLoadBalancer { + private final InfiniteIterator workers; + + public PiRouter(ActorRef[] workers) { + this.workers = new CyclicIterator(asList(workers)); + } + + public InfiniteIterator seq() { + return workers; + } + } + + public Master(...) { + ... + + // create the workers + final ActorRef[] workers = new ActorRef[nrOfWorkers]; + for (int i = 0; i < nrOfWorkers; i++) { + workers[i] = actorOf(Worker.class).start(); + } + + // wrap them with a load-balancing router + ActorRef router = actorOf(new UntypedActorFactory() { + public UntypedActor create() { + return new PiRouter(workers); + } + }).start(); + } + } + +As you can see we are using the ``actorOf`` factory method to create actors, this method returns as an ``ActorRef`` which is a reference to our newly created actor. This method is available in the ``Actors`` object but is usually imported:: + + import static akka.actor.Actors.actorOf; + +In thing to note is that we used two different versions of the ``actorOf`` method. For creating the ``Worker`` actor we just pass in the class but to create the ``PiRouter`` actor we can't do that since the constructor in the ``PiRouter`` class takes arguments, instead we need to use the ``UntypedActorFactory`` which unfortunately is a bit more verbose. + +``actorOf`` is the only way to create an instance of an Actor, this is enforced by Akka runtime. The ``actorOf`` method instantiates the actor and returns, not an instance to the actor, but an instance to an ``ActorRef``. This reference is the handle through which you communicate with the actor. It is immutable, serializable and location-aware meaning that it "remembers" its original actor even if it is sent to other nodes across the network and can be seen as the equivalent to the Erlang actor's PID. + +The actor's life-cycle is: + +- Created -- ``Actor.actorOf[MyActor]`` -- can **not** receive messages +- Started -- ``actorRef.start()`` -- can receive messages +- Stopped -- ``actorRef.stop()`` -- can **not** receive messages + +Once the actor has been stopped it is dead and can not be started again. + +Now we have a router that is representing all our workers in a single abstraction. If you paid attention to the code above, you saw that we were using the ``nrOfWorkers`` variable. This variable and others we have to pass to the ``Master`` actor in its constructor. So now let's create the master actor. We have to pass in three integer variables: + +- ``nrOfWorkers`` -- defining how many workers we should start up +- ``nrOfMessages`` -- defining how many number chunks to send out to the workers +- ``nrOfElements`` -- defining how big the number chunks sent to each worker should be + +Here is the master actor:: + + static class Master extends UntypedActor { + private final int nrOfMessages; + private final int nrOfElements; + private final CountDownLatch latch; + + private double pi; + private int nrOfResults; + private long start; + + private ActorRef router; + + static class PiRouter extends UntypedLoadBalancer { + private final InfiniteIterator workers; + + public PiRouter(ActorRef[] workers) { + this.workers = new CyclicIterator(asList(workers)); + } + + public InfiniteIterator seq() { + return workers; + } + } + + public Master(int nrOfWorkers, int nrOfMessages, int nrOfElements, CountDownLatch latch) { + this.nrOfMessages = nrOfMessages; + this.nrOfElements = nrOfElements; + this.latch = latch; + + // create the workers + final ActorRef[] workers = new ActorRef[nrOfWorkers]; + for (int i = 0; i < nrOfWorkers; i++) { + workers[i] = actorOf(Worker.class).start(); + } + + // wrap them with a load-balancing router + router = actorOf(new UntypedActorFactory() { + public UntypedActor create() { + return new PiRouter(workers); + } + }).start(); + } + + // message handler + public void onReceive(Object message) { ... } + + @Override + public void preStart() { + start = System.currentTimeMillis(); + } + + @Override + public void postStop() { + // tell the world that the calculation is complete + System.out.println(String.format( + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis", + pi, (System.currentTimeMillis() - start))); + latch.countDown(); + } + } + +A couple of things are worth explaining further. + +First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing (in this specific tutorial), to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch but other abstractions and functions like ``Channel``, ``Future`` and ``!!!`` to achive the same thing in a non-blocking way. But for simplicity let's stick to a ``CountDownLatch`` for now. + +Second, we are adding a couple of life-cycle callback methods; ``preStart`` and ``postStop``. In the ``preStart`` callback we are recording the time when the actor is started and in the ``postStop`` callback we are printing out the result (the approximation of Pi) and the time it took to calculate it. In this call we also invoke ``latch.countDown()`` to tell the outside world that we are done. + +But we are not done yet. We are missing the message handler for the ``Master`` actor. This message handler needs to be able to react to two different messages: + +- ``Calculate`` -- which should start the calculation +- ``Result`` -- which should aggregate the different results + +The ``Calculate`` handler is sending out work to all the ``Worker`` actors and after doing that it also sends a ``new Broadcast(poisonPill())`` message to the router, which will send out the ``PoisonPill`` message to all the actors it is representing (in our case all the ``Worker`` actors). ``PoisonPill`` is a special kind of message that tells the receiver to shut itself down using the normal shutdown method; ``getContext().stop()``, and is created through the ``poisonPill()`` method. We also send a ``PoisonPill`` to the router itself (since it's also an actor that we want to shut down). + +The ``Result`` handler is simpler, here we get the value from the ``Result`` message and aggregate it to our ``pi`` member variable. We also keep track of how many results we have received back, and if that matches the number of tasks sent out, the ``Master`` actor considers itself done and shuts down. + +Let's capture this in code:: + + // message handler + public void onReceive(Object message) { + + if (message instanceof Calculate) { + // schedule work + for (int arg = 0; arg < nrOfMessages; arg++) { + router.sendOneWay(new Work(arg, nrOfElements), getContext()); + } + + // send a PoisonPill to all workers telling them to shut down themselves + router.sendOneWay(new Broadcast(poisonPill())); + + // send a PoisonPill to the router, telling him to shut himself down + router.sendOneWay(poisonPill()); + + } else if (message instanceof Result) { + + // handle result from the worker + Result result = (Result) message; + pi += result.getValue(); + nrOfResults += 1; + if (nrOfResults == nrOfMessages) getContext().stop(); + + } else throw new IllegalArgumentException("Unknown message [" + message + "]"); + } + +Bootstrap the calculation +------------------------- + +Now the only thing that is left to implement is the runner that should bootstrap and run the calculation for us. We do that by adding a ``main`` method to the enclosing ``Pi`` class in which we create a new instance of ``Pi`` and invoke method ``calculate`` in which we start up the ``Master`` actor and wait for it to finish:: + + public class Pi { + + public static void main(String[] args) throws Exception { + Pi pi = new Pi(); + pi.calculate(4, 10000, 10000); + } + + public void calculate(final int nrOfWorkers, final int nrOfElements, final int nrOfMessages) + throws Exception { + + // this latch is only plumbing to know when the calculation is completed + final CountDownLatch latch = new CountDownLatch(1); + + // create the master + ActorRef master = actorOf(new UntypedActorFactory() { + public UntypedActor create() { + return new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch); + } + }).start(); + + // start the calculation + master.sendOneWay(new Calculate()); + + // wait for master to shut down + latch.await(); + } + } + +That's it. Now we are done. + +Before we package it up and run it, let's take a look at the full code now, with package declaration, imports and all:: + + package akka.tutorial.first.java; + + import static akka.actor.Actors.actorOf; + import static akka.actor.Actors.poisonPill; + import static java.util.Arrays.asList; + + import akka.actor.ActorRef; + import akka.actor.UntypedActor; + import akka.actor.UntypedActorFactory; + import akka.routing.CyclicIterator; + import akka.routing.InfiniteIterator; + import akka.routing.Routing.Broadcast; + import akka.routing.UntypedLoadBalancer; + + import java.util.concurrent.CountDownLatch; + + public class Pi { + + public static void main(String[] args) throws Exception { + Pi pi = new Pi(); + pi.calculate(4, 10000, 10000); + } + + // ==================== + // ===== Messages ===== + // ==================== + static class Calculate {} + + static class Work { + private final int arg; + private final int nrOfElements; + + public Work(int arg, int nrOfElements) { + this.arg = arg; + this.nrOfElements = nrOfElements; + } + + public int getArg() { return arg; } + public int getNrOfElements() { return nrOfElements; } + } + + static class Result { + private final double value; + + public Result(double value) { + this.value = value; + } + + public double getValue() { return value; } + } + + // ================== + // ===== Worker ===== + // ================== + static class Worker extends UntypedActor { + + // define the work + private double calculatePiFor(int arg, int nrOfElements) { + double acc = 0.0; + for (int i = arg * nrOfElements; i <= ((arg + 1) * nrOfElements - 1); i++) { + acc += 4 * Math.pow(-1, i) / (2 * i + 1); + } + return acc; + } + + // message handler + public void onReceive(Object message) { + if (message instanceof Work) { + Work work = (Work) message; + + // perform the work + double result = calculatePiFor(work.getArg(), work.getNrOfElements()) + + // reply with the result + getContext().replyUnsafe(new Result(result)); + + } else throw new IllegalArgumentException("Unknown message [" + message + "]"); + } + } + + // ================== + // ===== Master ===== + // ================== + static class Master extends UntypedActor { + private final int nrOfMessages; + private final int nrOfElements; + private final CountDownLatch latch; + + private double pi; + private int nrOfResults; + private long start; + + private ActorRef router; + + static class PiRouter extends UntypedLoadBalancer { + private final InfiniteIterator workers; + + public PiRouter(ActorRef[] workers) { + this.workers = new CyclicIterator(asList(workers)); + } + + public InfiniteIterator seq() { + return workers; + } + } + + public Master(int nrOfWorkers, int nrOfMessages, int nrOfElements, CountDownLatch latch) { + this.nrOfMessages = nrOfMessages; + this.nrOfElements = nrOfElements; + this.latch = latch; + + // create the workers + final ActorRef[] workers = new ActorRef[nrOfWorkers]; + for (int i = 0; i < nrOfWorkers; i++) { + workers[i] = actorOf(Worker.class).start(); + } + + // wrap them with a load-balancing router + router = actorOf(new UntypedActorFactory() { + public UntypedActor create() { + return new PiRouter(workers); + } + }).start(); + } + + // message handler + public void onReceive(Object message) { + + if (message instanceof Calculate) { + // schedule work + for (int arg = 0; arg < nrOfMessages; arg++) { + router.sendOneWay(new Work(arg, nrOfElements), getContext()); + } + + // send a PoisonPill to all workers telling them to shut down themselves + router.sendOneWay(new Broadcast(poisonPill())); + + // send a PoisonPill to the router, telling him to shut himself down + router.sendOneWay(poisonPill()); + + } else if (message instanceof Result) { + + // handle result from the worker + Result result = (Result) message; + pi += result.getValue(); + nrOfResults += 1; + if (nrOfResults == nrOfMessages) getContext().stop(); + + } else throw new IllegalArgumentException("Unknown message [" + message + "]"); + } + + @Override + public void preStart() { + start = System.currentTimeMillis(); + } + + @Override + public void postStop() { + // tell the world that the calculation is complete + System.out.println(String.format( + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis", + pi, (System.currentTimeMillis() - start))); + latch.countDown(); + } + } + + // ================== + // ===== Run it ===== + // ================== + public void calculate(final int nrOfWorkers, final int nrOfElements, final int nrOfMessages) + throws Exception { + + // this latch is only plumbing to know when the calculation is completed + final CountDownLatch latch = new CountDownLatch(1); + + // create the master + ActorRef master = actorOf(new UntypedActorFactory() { + public UntypedActor create() { + return new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch); + } + }).start(); + + // start the calculation + master.sendOneWay(new Calculate()); + + // wait for master to shut down + latch.await(); + } + } + +Run it as a command line application +------------------------------------ + +To build and run the tutorial from the command line, you need to have the Scala library JAR on the classpath. + +Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0.RC1 release. If you pick the ``tgz`` or ``zip`` distribution then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. + +The ``scala-library.jar`` resides in the ``scala-2.9.0.RC1/lib`` directory. Copy that to your project directory. + +If you have not typed in (or copied) the code for the tutorial as ``$AKKA_HOME/tutorial/akka/tutorial/first/java/Pi.java`` then now is the time. When that's done open up a shell and step in to the Akka distribution (``cd $AKKA_HOME``). + +First we need to compile the source file. That is done with Java's compiler ``javac``. Our application depends on the ``akka-actor-1.1.jar`` and the ``scala-library.jar`` JAR files, so let's add them to the compiler classpath when we compile the source:: + + $ javac -cp dist/akka-actor-1.1.jar:scala-library.jar tutorial/Pi.scala + +When we have compiled the source file we are ready to run the application. This is done with ``java`` but yet again we need to add the ``akka-actor-1.1.jar`` and the ``scala-library.jar`` JAR files to the classpath as well as the classes we compiled ourselves:: + + $ java -cp dist/akka-actor-1.1.jar:scala-library.jar:tutorial akka.tutorial.java.first.Pi + AKKA_HOME is defined as [/Users/jboner/src/akka-stuff/akka-core], loading config from \ + [/Users/jboner/src/akka-stuff/akka-core/config/akka.conf]. + + Pi estimate: 3.1435501812459323 + Calculation time: 822 millis + +Yippee! It is working. + +If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. + +Run it inside Maven +------------------- + +If you used Maven, then you can run the application directly inside Maven. First you need to compile the project:: + + $ mvn compile + +When this in done we can run our application directly inside SBT:: + + $ mvn exec:java -Dexec.mainClass="akka.tutorial.first.java.Pi" + ... + Pi estimate: 3.1435501812459323 + Calculation time: 939 millis + +Yippee! It is working. + +If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. + +Conclusion +---------- + +We have learned how to create our first Akka project using Akka's actors to speed up a computation-intensive problem by scaling out on multi-core processors (also known as scaling up). We have also learned to compile and run an Akka project using either the tools on the command line or the SBT build system. + +Now we are ready to take on more advanced problems. In the next tutorial we will build on this one, refactor it into more idiomatic Akka and Scala code, and introduce a few new concepts and abstractions. Whenever you feel ready, join me in the `Getting Started Tutorial: Second Chapter `_. + +Happy hakking. diff --git a/akka-docs/manual/getting-started-first-scala.rst b/akka-docs/manual/getting-started-first-scala.rst new file mode 100644 index 0000000000..7f858fda0b --- /dev/null +++ b/akka-docs/manual/getting-started-first-scala.rst @@ -0,0 +1,535 @@ +Getting Started Tutorial (Scala): First Chapter +=============================================== + +Introduction +------------ + +Welcome to the first tutorial on how to get started with Akka and Scala. We assume that you already know what Akka and Scala are and will now focus on the steps necessary to start your first project. + +There are two variations of this first tutorial: + +- creating a standalone project and run it from the command line +- creating a SBT (Simple Build Tool) project and running it from within SBT + +Since they are so similar we will present them both. + +The sample application that we will create is using actors to calculate the value of Pi. Calculating Pi is a CPU intensive operation and we will utilize Akka Actors to write a concurrent solution that scales out to multi-core processors. This sample will be extended in future tutorials to use Akka Remote Actors to scale out on multiple machines in a cluster. + +We will be using an algorithm that is called "embarrassingly parallel" which just means that each job is completely isolated and not coupled with any other job. Since this algorithm is so parallelizable it suits the actor model very well. + +Here is the formula for the algorithm we will use: + +.. image:: pi-formula.png + +In this particular algorithm the master splits the series into chunks which are sent out to each worker actor to be processed. When each worker has processed its chunk it sends a result back to the master which aggregates the total result. + +Tutorial source code +-------------------- + +If you want don't want to type in the code and/or set up an SBT project then you can check out the full tutorial from the Akka GitHub repository. It is in the ``akka-tutorials/akka-tutorial-first`` module. You can also browse it online `here `_, with the actual source code `here `_. + +Prerequisites +------------- + +This tutorial assumes that you have Jave 1.6 or later installed on you machine and ``java`` on your ``PATH``. You also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a decent text editor or IDE to type in the Scala code. + +You need to make sure that ``$JAVA_HOME`` environment variable is set to the root of the Java distribution. You also need to make sure that the ``$JAVA_HOME/bin`` is on your ``PATH``:: + + $ export JAVA_HOME=..root of java distribution.. + $ export PATH=$PATH:$JAVA_HOME/bin + +You can test your installation by invoking ``java``:: + + $ java -version + java version "1.6.0_24" + Java(TM) SE Runtime Environment (build 1.6.0_24-b07-334-10M3326) + Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02-334, mixed mode) + +Downloading and installing Akka +------------------------------- + +To build and run the tutorial sample from the command line, you have to download Akka. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. + +Let's get the ``akka-1.1`` distribution of Akka core (not Akka Modules) from `http://akka.io/downloads `_. Once you have downloaded the distribution unzip it in the folder you would like to have Akka installed in, in my case I choose to install it in ``/Users/jboner/tools/``, simply by unzipping it to this directory. + +You need to do one more thing in order to install Akka properly: set the ``AKKA_HOME`` environment variable to the root of the distribution. In my case I'm opening up a shell, navigating down to the distribution, and setting the ``AKKA_HOME`` variable:: + + $ cd /Users/jboner/tools/akka-1.1 + $ export AKKA_HOME=`pwd` + $ echo $AKKA_HOME + /Users/jboner/tools/akka-1.1 + +The distribution looks like this:: + + $ ls -l + total 16944 + drwxr-xr-x 7 jboner staff 238 Apr 6 11:15 . + drwxr-xr-x 28 jboner staff 952 Apr 6 11:16 .. + drwxr-xr-x 17 jboner staff 578 Apr 6 11:16 deploy + drwxr-xr-x 26 jboner staff 884 Apr 6 11:16 dist + drwxr-xr-x 3 jboner staff 102 Apr 6 11:15 lib_managed + -rwxr-xr-x 1 jboner staff 8674105 Apr 6 11:15 scala-library.jar + drwxr-xr-x 4 jboner staff 136 Apr 6 11:16 scripts + +- In the ``dist`` directory we have the Akka JARs, including sources and docs. +- In the ``lib_managed/compile`` directory we have Akka's dependency JARs. +- In the ``deploy`` directory we have the sample JARs. +- In the ``scripts`` directory we have scripts for running Akka. +- Finally ``scala-library.jar`` is the JAR for the latest Scala distribution that Akka depends on. + +The only JAR we will need for this tutorial (apart from the ``scala-library.jar`` JAR) is the ``akka-actor-1.1.jar`` JAR in the ``dist`` directory. This is a self-contained JAR with zero dependencies and contains everything we need to write a system using Actors. + +Akka is very modular and has many JARs for containing different features. The core distribution has seven modules: + +- ``akka-actor-1.1.jar`` -- Standard Actors +- ``akka-typed-actor-1.1.jar`` -- Typed Actors +- ``akka-remote-1.1.jar`` -- Remote Actors +- ``akka-stm-1.1.jar`` -- STM (Software Transactional Memory), transactors and transactional datastructures +- ``akka-http-1.1.jar`` -- Akka Mist for continuation-based asynchronous HTTP and also Jersey integration +- ``akka-slf4j-1.1.jar`` -- SLF4J Event Handler Listener +- ``akka-testkit-1.1.jar`` -- Toolkit for testing Actors + +We also have Akka Modules containing add-on modules outside the core of Akka. You can download the Akka Modules distribution from TODO. It contains Akka core as well. We will not be needing any modules there today, but for your information the module JARs are these: + +- ``akka-kernel-1.1.jar`` -- Akka microkernel for running a bare-bones mini application server (embeds Jetty etc.) +- ``akka-amqp-1.1.jar`` -- AMQP integration +- ``akka-camel-1.1.jar`` -- Apache Camel Actors integration (it's the best way to have your Akka application communicate with the rest of the world) +- ``akka-camel-typed-1.1.jar`` -- Apache Camel Typed Actors integration +- ``akka-scalaz-1.1.jar`` -- Support for the Scalaz library +- ``akka-spring-1.1.jar`` -- Spring framework integration +- ``akka-osgi-dependencies-bundle-1.1.jar`` -- OSGi support + +Downloading and installing Scala +-------------------------------- + +To build and run the tutorial sample from the command line, you have to install the Scala distribution. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. + +Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0.RC1 release. If you pick the ``tgz`` or ``zip`` distribution then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. + +You also need to make sure that the ``scala-2.9.0.RC1/bin`` (if that is the directory where you installed Scala) is on your ``PATH``:: + + $ export PATH=$PATH:scala-2.9.0.RC1/bin + +You can test your installation by invoking scala:: + + $ scala -version + Scala code runner version 2.9.0.RC1 -- Copyright 2002-2011, LAMP/EPFL + +Looks like we are all good. Finally let's create a source file ``Pi.scala`` for the tutorial and put it in the root of the Akka distribution in the ``tutorial`` directory (you have to create it first). + +Some tools require you to set the ``SCALA_HOME`` environment variable to the root of the Scala distribution, however Akka does not require that. + +Downloading and installing SBT +------------------------------ + +SBT, short for 'Simple Build Tool' is an excellent build system written in Scala. It uses Scala to write the build scripts which gives you a lot of power. It has a plugin architecture with many plugins available, something that we will take advantage of soon. SBT is the preferred way of building software in Scala and is probably the easiest way of getting through this tutorial. If you want to use SBT for this tutorial then follow the following instructions, if not you can skip this section and the next. + +First browse to the `SBT download page`_ and download the ``0.7.6.RC0`` distribution. + +To install SBT and create a project for this tutorial it is easiest to follow the instructions on `this page `_. + +Now we need to create our first Akka project. You could add the dependencies manually to the build script, but the easiest way is to use Akka's SBT Plugin, covered in the next section. + +Creating an Akka SBT project +---------------------------- + +If you have not already done so, now is the time to create an SBT project for our tutorial. You do that by stepping into the directory you want to create your project in and invoking the ``sbt`` command answering the questions for setting up your project (just pressing ENTER will choose the default in square brackets):: + + $ sbt + Project does not exist, create new project? (y/N/s) y + Name: Tutorial 1 + Organization: Hakkers Inc + Version [1.0]: + Scala version [2.9.0.RC1]: + sbt version [0.7.6.RC0]: + +Now we have the basis for an SBT project. Akka has an SBT Plugin making it very easy to use Akka is an SBT-based project so let's use that. + +To use the plugin, first add a plugin definition to your SBT project by creating a ``Plugins.scala`` file in the ``project/plugins`` directory containing:: + + import sbt._ + + class Plugins(info: ProjectInfo) extends PluginDefinition(info) { + val akkaRepo = "Akka Repo" at "http://akka.io/repository" + val akkaPlugin = "se.scalablesolutions.akka" % "akka-sbt-plugin" % "1.1" + } + +Now we need to create a project definition using our Akka SBT plugin. We do that by creating a ``project/build/Project.scala`` file containing:: + + import sbt._ + + class TutorialOneProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject { + val akkaRepo = "Akka Repo" at "http://akka.io/repository" + } + +The magic is in mixing in the ``AkkaProject`` trait. + +Not needed in this tutorial, but if you would like to use additional Akka modules beyond ``akka-actor``, you can add these as "module configurations" in the project file. Here is an example adding ``akka-remote`` and ``akka-stm``:: + + class AkkaSampleProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject { + val akkaSTM = akkaModule("stm") + val akkaRemote = akkaModule("remote") + } + +So, now we are all set. Just one final thing to do; make SBT download the dependencies it needs. That is done by invoking:: + + > update + +SBT itself needs a whole bunch of dependencies but our project will only need one; ``akka-actor-1.1.jar``. SBT downloads that as well. + +Start writing the code +---------------------- + +Now it's about time to start hacking. + +We start by creating a ``Pi.scala`` file and adding these import statements at the top of the file:: + + package akka.tutorial.scala.first + + import akka.actor.{Actor, PoisonPill} + import Actor._ + import akka.routing.{Routing, CyclicIterator} + import Routing._ + import akka.dispatch.Dispatchers + + import java.util.concurrent.CountDownLatch + +If you are using SBT in this tutorial then create the file in the ``src/main/scala`` directory. + +If you are using the command line tools then create the file wherever you want. I will create it in a directory called ``tutorial`` at the root of the Akka distribution, e.g. in ``$AKKA_HOME/tutorial/Pi.scala``. + +Creating the messages +--------------------- + +The design we are aiming for is to have one ``Master`` actor initiating the computation, creating a set of ``Worker`` actors. Then it splits up the work into discrete chunks, and sends these chunks to the different workers in a round-robin fashion. The master waits until all the workers have completed their work and sent back results for aggregation. When computation is completed the master prints out the result, shuts down all workers and then itself. + +With this in mind, let's now create the messages that we want to have flowing in the system. We need three different messages: + +- ``Calculate`` -- sent to the ``Master`` actor to start the calculation +- ``Work`` -- sent from the ``Master`` actor to the ``Worker`` actors containing the work assignment +- ``Result`` -- sent from the ``Worker`` actors to the ``Master`` actor containing the result from the worker's calculation + +Messages sent to actors should always be immutable to avoid sharing mutable state. In scala we have 'case classes' which make excellent messages. So let's start by creating three messages as case classes. We also create a common base trait for our messages (that we define as being ``sealed`` in order to prevent creating messages outside our control):: + + sealed trait PiMessage + + case object Calculate extends PiMessage + + case class Work(start: Int, nrOfElements: Int) extends PiMessage + + case class Result(value: Double) extends PiMessage + +Creating the worker +------------------- + +Now we can create the worker actor. This is done by mixing in the ``Actor`` trait and defining the ``receive`` method. The ``receive`` method defines our message handler. We expect it to be able to handle the ``Work`` message so we need to add a handler for this message:: + + class Worker extends Actor { + def receive = { + case Work(start, nrOfElements) => + self reply Result(calculatePiFor(start, nrOfElements)) // perform the work + } + } + +As you can see we have now created an ``Actor`` with a ``receive`` 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 ``self.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. While there are many ways we can implement this algorithm in Scala, in this introductory tutorial we have chosen an imperative style using a for comprehension and an accumulator:: + + def calculatePiFor(start: Int, nrOfElements: Int): Double = { + var acc = 0.0 + for (i <- start until (start + nrOfElements)) + acc += 4 * math.pow(-1, i) / (2 * i + 1) + acc + } + +Creating the master +------------------- + +The master actor is a little bit more involved. In its constructor we need to create the workers (the ``Worker`` actors) and start them. We will also wrap them in a load-balancing router to make it easier to spread out the work evenly between the workers. Let's do that first:: + + // create the workers + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) + + // wrap them with a load-balancing router + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() + +As you can see we are using the ``actorOf`` factory method to create actors, this method returns as an ``ActorRef`` which is a reference to our newly created actor. This method is available in the ``Actor`` object but is usually imported:: + + import akka.actor.Actor.actorOf + +There are two versions of ``actorOf``; one of them taking a actor type and the other one an instance of an actor. The former one (``actorOf[MyActor]``) is used when the actor class has a no-argument constructor while the second one (``actorOf(new MyActor(..))``) is used when the actor class has a constructor that takes arguments. This is the only way to create an instance of an Actor and the ``actorOf`` method ensures this. The latter version is using call-by-name and lazily creates the actor within the scope of the ``actorOf`` method. The ``actorOf`` method instantiates the actor and returns, not an instance to the actor, but an instance to an ``ActorRef``. This reference is the handle through which you communicate with the actor. It is immutable, serializable and location-aware meaning that it "remembers" its original actor even if it is sent to other nodes across the network and can be seen as the equivalent to the Erlang actor's PID. + +The actor's life-cycle is: + +- Created -- ``Actor.actorOf[MyActor]`` -- can **not** receive messages +- Started -- ``actorRef.start()`` -- can receive messages +- Stopped -- ``actorRef.stop()`` -- can **not** receive messages + +Once the actor has been stopped it is dead and can not be started again. + +Now we have a router that is representing all our workers in a single abstraction. If you paid attention to the code above, you saw that we were using the ``nrOfWorkers`` variable. This variable and others we have to pass to the ``Master`` actor in its constructor. So now let's create the master actor. We have to pass in three integer variables: + +- ``nrOfWorkers`` -- defining how many workers we should start up +- ``nrOfMessages`` -- defining how many number chunks to send out to the workers +- ``nrOfElements`` -- defining how big the number chunks sent to each worker should be + +Here is the master actor:: + + class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) + extends Actor { + + var pi: Double = _ + var nrOfResults: Int = _ + var start: Long = _ + + // create the workers + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) + + // wrap them with a load-balancing router + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() + + def receive = { ... } + + override def preStart { + start = now + } + + override def postStop { + // tell the world that the calculation is complete + println("\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis".format(pi, (now - start))) + latch.countDown() + } + } + +A couple of things are worth explaining further. + +First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing (in this specific tutorial), to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch but other abstractions and functions like ``Channel``, ``Future`` and ``!!!`` to achive the same thing in a non-blocking way. But for simplicity let's stick to a ``CountDownLatch`` for now. + +Second, we are adding a couple of life-cycle callback methods; ``preStart`` and ``postStop``. In the ``preStart`` callback we are recording the time when the actor is started and in the ``postStop`` callback we are printing out the result (the approximation of Pi) and the time it took to calculate it. In this call we also invoke ``latch.countDown`` to tell the outside world that we are done. + +But we are not done yet. We are missing the message handler for the ``Master`` actor. This message handler needs to be able to react to two different messages: + +- ``Calculate`` -- which should start the calculation +- ``Result`` -- which should aggregate the different results + +The ``Calculate`` handler is sending out work to all the ``Worker`` actors and after doing that it also sends a ``Broadcast(PoisonPill)`` message to the router, which will send out the ``PoisonPill`` message to all the actors it is representing (in our case all the ``Worker`` actors). ``PoisonPill`` is a special kind of message that tells the receiver to shut itself down using the normal shutdown method; ``self.stop``. We also send a ``PoisonPill`` to the router itself (since it's also an actor that we want to shut down). + +The ``Result`` handler is simpler, here we get the value from the ``Result`` message and aggregate it to our ``pi`` member variable. We also keep track of how many results we have received back, and if that matches the number of tasks sent out, the ``Master`` actor considers itself done and shuts down. + +Let's capture this in code:: + + // message handler + def receive = { + case Calculate => + // schedule work + for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) + + // send a PoisonPill to all workers telling them to shut down themselves + router ! Broadcast(PoisonPill) + + // send a PoisonPill to the router, telling him to shut himself down + router ! PoisonPill + + case Result(value) => + // handle result from the worker + pi += value + nrOfResults += 1 + if (nrOfResults == nrOfMessages) self.stop() + } + +Bootstrap the calculation +------------------------- + +Now the only thing that is left to implement is the runner that should bootstrap and run the calculation for us. We do that by creating an object that we call ``Pi``, here we can extend the ``App`` trait in Scala, which means that we will be able to run this as an application directly from the command line. + +The ``Pi`` object is a perfect container module for our actors and messages, so let's put them all there. We also create a method ``calculate`` in which we start up the ``Master`` actor and wait for it to finish:: + + object Pi extends App { + + calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) + + ... // actors and messages + + def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { + + // this latch is only plumbing to know when the calculation is completed + val latch = new CountDownLatch(1) + + // create the master + val master = actorOf( + new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() + + // start the calculation + master ! Calculate + + // wait for master to shut down + latch.await() + } + } + +That's it. Now we are done. + +But before we package it up and run it, let's take a look at the full code now, with package declaration, imports and all:: + + package akka.tutorial.scala.first + + import akka.actor.{Actor, PoisonPill} + import Actor._ + import akka.routing.{Routing, CyclicIterator} + import Routing._ + + import java.util.concurrent.CountDownLatch + + object Pi extends App { + + calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) + + // ==================== + // ===== Messages ===== + // ==================== + sealed trait PiMessage + case object Calculate extends PiMessage + case class Work(start: Int, nrOfElements: Int) extends PiMessage + case class Result(value: Double) extends PiMessage + + // ================== + // ===== Worker ===== + // ================== + class Worker extends Actor { + + // define the work + def calculatePiFor(start: Int, nrOfElements: Int): Double = { + var acc = 0.0 + for (i <- start until (start + nrOfElements)) + acc += 4 * math.pow(-1, i) / (2 * i + 1) + acc + } + + def receive = { + case Work(start, nrOfElements) => + self reply Result(calculatePiFor(start, nrOfElements)) // perform the work + } + } + + // ================== + // ===== Master ===== + // ================== + class Master( + nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) + extends Actor { + + var pi: Double = _ + var nrOfResults: Int = _ + var start: Long = _ + + // create the workers + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) + + // wrap them with a load-balancing router + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() + + // message handler + def receive = { + case Calculate => + // schedule work + //for (arg <- 0 until nrOfMessages) router ! Work(arg, nrOfElements) + for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) + + // send a PoisonPill to all workers telling them to shut down themselves + router ! Broadcast(PoisonPill) + + // send a PoisonPill to the router, telling him to shut himself down + router ! PoisonPill + + case Result(value) => + // handle result from the worker + pi += value + nrOfResults += 1 + if (nrOfResults == nrOfMessages) self.stop() + } + + override def preStart { + start = now + } + + override def postStop { + // tell the world that the calculation is complete + println( + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" + .format(pi, (now - start))) + latch.countDown() + } + } + + // ================== + // ===== Run it ===== + // ================== + def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { + + // this latch is only plumbing to know when the calculation is completed + val latch = new CountDownLatch(1) + + // create the master + val master = actorOf( + new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() + + // start the calculation + master ! Calculate + + // wait for master to shut down + latch.await() + } + } + +Run it as a command line application +------------------------------------ + +If you have not typed in (or copied) the code for the tutorial as ``$AKKA_HOME/tutorial/Pi.scala`` then now is the time. When that's done open up a shell and step in to the Akka distribution (``cd $AKKA_HOME``). + +First we need to compile the source file. That is done with Scala's compiler ``scalac``. Our application depends on the ``akka-actor-1.1.jar`` JAR file, so let's add that to the compiler classpath when we compile the source:: + + $ scalac -cp dist/akka-actor-1.1.jar tutorial/Pi.scala + +When we have compiled the source file we are ready to run the application. This is done with ``java`` but yet again we need to add the ``akka-actor-1.1.jar`` JAR file to the classpath, and this time we also need to add the Scala runtime library ``scala-library.jar`` and the classes we compiled ourselves:: + + $ java -cp dist/akka-actor-1.1.jar:scala-library.jar:tutorial akka.tutorial.scala.first.Pi + AKKA_HOME is defined as [/Users/jboner/src/akka-stuff/akka-core], loading config from \ + [/Users/jboner/src/akka-stuff/akka-core/config/akka.conf]. + + Pi estimate: 3.1435501812459323 + Calculation time: 858 millis + +Yippee! It is working. + +If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. + +Run it inside SBT +----------------- + +If you used SBT, then you can run the application directly inside SBT. First you need to compile the project:: + + $ sbt + > update + ... + > compile + ... + +When this in done we can run our application directly inside SBT:: + + > run + ... + Pi estimate: 3.1435501812459323 + Calculation time: 942 millis + +Yippee! It is working. + +If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. + +Conclusion +---------- + +We have learned how to create our first Akka project using Akka's actors to speed up a computation-intensive problem by scaling out on multi-core processors (also known as scaling up). We have also learned to compile and run an Akka project using either the tools on the command line or the SBT build system. + +Now we are ready to take on more advanced problems. In the next tutorial we will build on this one, refactor it into more idiomatic Akka and Scala code, and introduce a few new concepts and abstractions. Whenever you feel ready, join me in the `Getting Started Tutorial: Second Chapter `_. + +Happy hakking. From 51a075b34cf8777ce35061a723ab350c8772c0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Tue, 19 Apr 2011 13:13:55 +0200 Subject: [PATCH 099/147] Added Maven project file to first Java tutorial --- akka-tutorials/akka-tutorial-first/pom.xml | 43 +++++++++++++++++++ .../{java/first => first/java}/Pi.java | 26 +++++++---- .../src/main/scala/Pi.scala | 12 +++--- 3 files changed, 67 insertions(+), 14 deletions(-) create mode 100644 akka-tutorials/akka-tutorial-first/pom.xml rename akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/{java/first => first/java}/Pi.java (89%) diff --git a/akka-tutorials/akka-tutorial-first/pom.xml b/akka-tutorials/akka-tutorial-first/pom.xml new file mode 100644 index 0000000000..f3d9589815 --- /dev/null +++ b/akka-tutorials/akka-tutorial-first/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + akka-tutorial-first-java + akka.tutorial.first.java + akka-tutorial-first-java + jar + 1.0-SNAPSHOT + http://akka.io + + + + se.scalablesolutions.akka + akka-actor + 1.1-SNAPSHOT + + + + + + Akka + Akka Maven2 Repository + http://www.scalablesolutions.se/akka/repository/ + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + + diff --git a/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/java/first/Pi.java b/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java similarity index 89% rename from akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/java/first/Pi.java rename to akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java index 1b2dd5e941..4ebb8428c8 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/java/first/Pi.java +++ b/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java @@ -2,7 +2,7 @@ * Copyright (C) 2009-2011 Scalable Solutions AB */ -package akka.tutorial.java.first; +package akka.tutorial.first.java; import static akka.actor.Actors.actorOf; import static akka.actor.Actors.poisonPill; @@ -27,8 +27,8 @@ import java.util.concurrent.CountDownLatch; *
  *   $ cd akka-1.1
  *   $ export AKKA_HOME=`pwd`
- *   $ javac -cp dist/akka-actor-1.1-SNAPSHOT.jar:scala-library.jar akka/tutorial/java/first/Pi.java
- *   $ java -cp dist/akka-actor-1.1-SNAPSHOT.jar:scala-library.jar:. akka.tutorial.java.first.Pi
+ *   $ javac -cp dist/akka-actor-1.1-SNAPSHOT.jar:scala-library.jar akka/tutorial/first/java/Pi.java
+ *   $ java -cp dist/akka-actor-1.1-SNAPSHOT.jar:scala-library.jar:. akka.tutorial.first.java.Pi
  *   $ ...
  * 
*

@@ -36,7 +36,7 @@ import java.util.concurrent.CountDownLatch; *

  *   $ mvn
  *   > scala:console
- *   > val pi = new akka.tutorial.java.first.Pi
+ *   > val pi = new akka.tutorial.first.java.Pi
  *   > pi.calculate(4, 10000, 10000)
  *   > ...
  * 
@@ -96,10 +96,15 @@ 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 + + // perform the work + double result = calculatePiFor(work.getArg(), work.getNrOfElements()) + + // reply with the result + getContext().replyUnsafe(new Result(result)); + } else throw new IllegalArgumentException("Unknown message [" + message + "]"); } - } // ================== // ===== Master ===== @@ -180,7 +185,9 @@ public class Pi { @Override public void postStop() { // tell the world that the calculation is complete - System.out.println(String.format("\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis", pi, (System.currentTimeMillis() - start))); + System.out.println(String.format( + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis", + pi, (System.currentTimeMillis() - start))); latch.countDown(); } } @@ -188,9 +195,10 @@ public class Pi { // ================== // ===== Run it ===== // ================== - public void calculate(final int nrOfWorkers, final int nrOfElements, final int nrOfMessages) throws Exception { + public void calculate(final int nrOfWorkers, final int nrOfElements, final int nrOfMessages) + throws Exception { - // this latch is only plumbing to kSystem.currentTimeMillis(); when the calculation is completed + // this latch is only plumbing to know when the calculation is completed final CountDownLatch latch = new CountDownLatch(1); // create the master diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index a1844cdc45..56759d4afb 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -2,7 +2,7 @@ * Copyright (C) 2009-2011 Scalable Solutions AB */ -package akka.tutorial.scala.first +package akka.tutorial.first.scala import akka.actor.{Actor, PoisonPill} import Actor._ @@ -21,7 +21,7 @@ import java.util.concurrent.CountDownLatch * $ cd akka-1.1 * $ export AKKA_HOME=`pwd` * $ scalac -cp dist/akka-actor-1.1-SNAPSHOT.jar Pi.scala - * $ java -cp dist/akka-actor-1.1-SNAPSHOT.jar:scala-library.jar:. akka.tutorial.scala.first.Pi + * $ java -cp dist/akka-actor-1.1-SNAPSHOT.jar:scala-library.jar:. akka.tutorial.first.scala.Pi * $ ... * *

@@ -30,7 +30,7 @@ import java.util.concurrent.CountDownLatch * $ sbt * > update * > console - * > akka.tutorial.scala.first.Pi.calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) + * > akka.tutorial.first.scala.Pi.calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) * > ... * > :quit * @@ -105,12 +105,14 @@ object Pi extends App { } override def preStart { - start = now + start = System.currentTimeMillis } override def postStop { // tell the world that the calculation is complete - println("\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis".format(pi, (now - start))) + println( + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" + .format(pi, (System.currentTimeMillis - start))) latch.countDown() } } From f2254a48cbaf5edd4961534ebc6f281790dc049a Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 19 Apr 2011 13:27:15 +0200 Subject: [PATCH 100/147] Putting RemoteActorRefs on a diet --- .../src/main/scala/akka/actor/ActorRef.scala | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 71f916b6e8..8affac2037 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -88,9 +88,6 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal protected[akka] var _uuid = newUuid @volatile protected[this] var _status: ActorRefInternals.StatusType = ActorRefInternals.UNSTARTED - @volatile - protected[akka] var _futureTimeout: Option[ScheduledFuture[AnyRef]] = None - protected[akka] val guard = new ReentrantGuard /** * User overridable callback/setting. @@ -572,20 +569,6 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal } override def toString = "Actor[" + id + ":" + uuid + "]" - - protected[akka] def checkReceiveTimeout = { - cancelReceiveTimeout - if (receiveTimeout.isDefined && dispatcher.mailboxSize(this) <= 0) { //Only reschedule if desired and there are currently no more messages to be processed - _futureTimeout = Some(Scheduler.scheduleOnce(this, ReceiveTimeout, receiveTimeout.get, TimeUnit.MILLISECONDS)) - } - } - - protected[akka] def cancelReceiveTimeout = { - if (_futureTimeout.isDefined) { - _futureTimeout.get.cancel(true) - _futureTimeout = None - } - } } /** @@ -598,7 +581,10 @@ class LocalActorRef private[akka] ( val homeAddress: Option[InetSocketAddress], val clientManaged: Boolean = false) extends ActorRef with ScalaActorRef { + protected[akka] val guard = new ReentrantGuard + @volatile + protected[akka] var _futureTimeout: Option[ScheduledFuture[AnyRef]] = None @volatile private[akka] lazy val _linkedActors = new ConcurrentHashMap[Uuid, ActorRef] @volatile @@ -1102,6 +1088,21 @@ class LocalActorRef private[akka] ( actor.preStart // run actor preStart Actor.registry.register(this) } + + + protected[akka] def checkReceiveTimeout = { + cancelReceiveTimeout + if (receiveTimeout.isDefined && dispatcher.mailboxSize(this) <= 0) { //Only reschedule if desired and there are currently no more messages to be processed + _futureTimeout = Some(Scheduler.scheduleOnce(this, ReceiveTimeout, receiveTimeout.get, TimeUnit.MILLISECONDS)) + } + } + + protected[akka] def cancelReceiveTimeout = { + if (_futureTimeout.isDefined) { + _futureTimeout.get.cancel(true) + _futureTimeout = None + } + } } /** From a1cd8a9f37666c050a7ef0475aef430f296b07a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Tue, 19 Apr 2011 04:32:04 -0700 Subject: [PATCH 101/147] Corrected wrong URI to source file. --- akka-docs/manual/getting-started-first-java.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-docs/manual/getting-started-first-java.rst b/akka-docs/manual/getting-started-first-java.rst index 2813285a8e..4a6e729207 100644 --- a/akka-docs/manual/getting-started-first-java.rst +++ b/akka-docs/manual/getting-started-first-java.rst @@ -26,7 +26,7 @@ In this particular algorithm the master splits the series into chunks which are Tutorial source code -------------------- -If you want don't want to type in the code and/or set up a Maven project then you can check out the full tutorial from the Akka GitHub repository. It is in the ``akka-tutorials/akka-tutorial-first`` module. You can also browse it online `here `_, with the actual source code `here `_. +If you want don't want to type in the code and/or set up a Maven project then you can check out the full tutorial from the Akka GitHub repository. It is in the ``akka-tutorials/akka-tutorial-first`` module. You can also browse it online `here `_, with the actual source code `here `_. Prerequisites ------------- From 90d6844b4aae13638d07a2b9c6f7ba571876fd33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Tue, 19 Apr 2011 14:05:53 +0200 Subject: [PATCH 102/147] Added missing semicolon to Pi.java --- .../src/main/java/akka/tutorial/first/java/Pi.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 4ebb8428c8..cd5ffc9c06 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 @@ -98,13 +98,14 @@ public class Pi { Work work = (Work) message; // perform the work - double result = calculatePiFor(work.getArg(), work.getNrOfElements()) + double result = calculatePiFor(work.getArg(), work.getNrOfElements()); // reply with the result getContext().replyUnsafe(new Result(result)); } else throw new IllegalArgumentException("Unknown message [" + message + "]"); } + } // ================== // ===== Master ===== @@ -186,7 +187,7 @@ public class Pi { public void postStop() { // tell the world that the calculation is complete System.out.println(String.format( - "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis", + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis", pi, (System.currentTimeMillis() - start))); latch.countDown(); } @@ -195,7 +196,7 @@ public class Pi { // ================== // ===== Run it ===== // ================== - public void calculate(final int nrOfWorkers, final int nrOfElements, final int nrOfMessages) + public void calculate(final int nrOfWorkers, final int nrOfElements, final int nrOfMessages) throws Exception { // this latch is only plumbing to know when the calculation is completed From 75309c6edd1171addab696ff8483baac36036c4c Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 19 Apr 2011 20:31:49 +0200 Subject: [PATCH 103/147] removed old commented line --- akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index 7521d00cad..117287f949 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -89,7 +89,6 @@ object Pi extends App { def receive = { case Calculate => // schedule work - //for (arg <- 0 until nrOfMessages) router ! Work(arg, nrOfElements) for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) // send a PoisonPill to all workers telling them to shut down themselves From 345f2f1c36913b9fd3130469b05d9143a3e4e30f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Tue, 19 Apr 2011 13:13:05 -0700 Subject: [PATCH 104/147] Fixed some typos --- akka-docs/manual/getting-started-first-java.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-docs/manual/getting-started-first-java.rst b/akka-docs/manual/getting-started-first-java.rst index 4a6e729207..ae39d63606 100644 --- a/akka-docs/manual/getting-started-first-java.rst +++ b/akka-docs/manual/getting-started-first-java.rst @@ -31,7 +31,7 @@ If you want don't want to type in the code and/or set up a Maven project then yo Prerequisites ------------- -This tutorial assumes that you have Jave 1.6 or later installed on you machine and ``java`` on your ``PATH``. You also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a decent text editor or IDE to type in the Java code. +This tutorial assumes that you have Java 1.6 or later installed on you machine and ``java`` on your ``PATH``. You also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a decent text editor or IDE to type in the Java code. You need to make sure that ``$JAVA_HOME`` environment variable is set to the root of the Java distribution. You also need to make sure that the ``$JAVA_HOME/bin`` is on your ``PATH``:: @@ -418,7 +418,7 @@ Here is the master actor:: A couple of things are worth explaining further. -First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing (in this specific tutorial), to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch but other abstractions and functions like ``Channel``, ``Future`` and ``!!!`` to achive the same thing in a non-blocking way. But for simplicity let's stick to a ``CountDownLatch`` for now. +First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing (in this specific tutorial), to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch but other abstractions and functions like ``Channel``, ``Future`` and ``!!!`` to achieve the same thing in a non-blocking way. But for simplicity let's stick to a ``CountDownLatch`` for now. Second, we are adding a couple of life-cycle callback methods; ``preStart`` and ``postStop``. In the ``preStart`` callback we are recording the time when the actor is started and in the ``postStop`` callback we are printing out the result (the approximation of Pi) and the time it took to calculate it. In this call we also invoke ``latch.countDown()`` to tell the outside world that we are done. From 95cf98ccad28192ed176dcd805344480ea1846cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Tue, 19 Apr 2011 13:13:36 -0700 Subject: [PATCH 105/147] Fixed some typos --- akka-docs/manual/getting-started-first-scala.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-docs/manual/getting-started-first-scala.rst b/akka-docs/manual/getting-started-first-scala.rst index 0846605cfe..180febddc7 100644 --- a/akka-docs/manual/getting-started-first-scala.rst +++ b/akka-docs/manual/getting-started-first-scala.rst @@ -306,7 +306,7 @@ Here is the master actor:: A couple of things are worth explaining further. -First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing (in this specific tutorial), to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch but other abstractions and functions like ``Channel``, ``Future`` and ``!!!`` to achive the same thing in a non-blocking way. But for simplicity let's stick to a ``CountDownLatch`` for now. +First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing (in this specific tutorial), to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch but other abstractions and functions like ``Channel``, ``Future`` and ``!!!`` to achieve the same thing in a non-blocking way. But for simplicity let's stick to a ``CountDownLatch`` for now. Second, we are adding a couple of life-cycle callback methods; ``preStart`` and ``postStop``. In the ``preStart`` callback we are recording the time when the actor is started and in the ``postStop`` callback we are printing out the result (the approximation of Pi) and the time it took to calculate it. In this call we also invoke ``latch.countDown`` to tell the outside world that we are done. From c0b4d6bfa93afd3160918dbf5720a40b33efd6d6 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 19 Apr 2011 22:19:27 +0200 Subject: [PATCH 106/147] fixed small typos --- akka-docs/manual/getting-started-first-java.rst | 4 ++-- akka-docs/manual/getting-started-first-scala.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/akka-docs/manual/getting-started-first-java.rst b/akka-docs/manual/getting-started-first-java.rst index 4a6e729207..8872f7364f 100644 --- a/akka-docs/manual/getting-started-first-java.rst +++ b/akka-docs/manual/getting-started-first-java.rst @@ -335,7 +335,7 @@ As you can see we are using the ``actorOf`` factory method to create actors, thi import static akka.actor.Actors.actorOf; -In thing to note is that we used two different versions of the ``actorOf`` method. For creating the ``Worker`` actor we just pass in the class but to create the ``PiRouter`` actor we can't do that since the constructor in the ``PiRouter`` class takes arguments, instead we need to use the ``UntypedActorFactory`` which unfortunately is a bit more verbose. +One thing to note is that we used two different versions of the ``actorOf`` method. For creating the ``Worker`` actor we just pass in the class but to create the ``PiRouter`` actor we can't do that since the constructor in the ``PiRouter`` class takes arguments, instead we need to use the ``UntypedActorFactory`` which unfortunately is a bit more verbose. ``actorOf`` is the only way to create an instance of an Actor, this is enforced by Akka runtime. The ``actorOf`` method instantiates the actor and returns, not an instance to the actor, but an instance to an ``ActorRef``. This reference is the handle through which you communicate with the actor. It is immutable, serializable and location-aware meaning that it "remembers" its original actor even if it is sent to other nodes across the network and can be seen as the equivalent to the Erlang actor's PID. @@ -716,7 +716,7 @@ When we have compiled the source file we are ready to run the application. This Yippee! It is working. -If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. +If you have not defined the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. Run it inside Maven ------------------- diff --git a/akka-docs/manual/getting-started-first-scala.rst b/akka-docs/manual/getting-started-first-scala.rst index 0846605cfe..8ac61b0451 100644 --- a/akka-docs/manual/getting-started-first-scala.rst +++ b/akka-docs/manual/getting-started-first-scala.rst @@ -506,7 +506,7 @@ When we have compiled the source file we are ready to run the application. This Yippee! It is working. -If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. +If you have not defined the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. Run it inside SBT ----------------- From 2e356ac0b16f59c75a0b39a0cff1687fff1c3270 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Wed, 20 Apr 2011 13:38:36 +1200 Subject: [PATCH 107/147] Add the why akka page to the docs --- akka-docs/index.rst | 1 + akka-docs/manual/why-akka.rst | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 akka-docs/manual/why-akka.rst diff --git a/akka-docs/index.rst b/akka-docs/index.rst index 6f31717995..a9fdc508ad 100644 --- a/akka-docs/index.rst +++ b/akka-docs/index.rst @@ -4,6 +4,7 @@ Contents .. toctree:: :maxdepth: 2 + manual/why-akka manual/getting-started-first-scala manual/getting-started-first-java manual/fsm-scala diff --git a/akka-docs/manual/why-akka.rst b/akka-docs/manual/why-akka.rst new file mode 100644 index 0000000000..512a669b2f --- /dev/null +++ b/akka-docs/manual/why-akka.rst @@ -0,0 +1,68 @@ +Why Akka? +========= + +What features can the Akka platform offer, over the competition? +---------------------------------------------------------------- + +Akka is an unified runtime and programming model for: + +- Scale up (Concurrency) +- Scale out (Remoting) +- Fault tolerance + +One thing to learn and admin, with high cohesion and coherent semantics. + +Akka is a very scalable piece of software, not only in the performance sense, +but in the size of applications it is useful for. The core of Akka, akka-actor, +is very small and easily dropped into an existing project where you need +asynchronicity and lockless concurrency without hassle. + +You can choose to include only the parts of akka you need in your application +and then there's the whole package, the Akka Microkernel, which is a standalone +container to deploy your Akka application in. With CPUs growing more and more +cores every cycle, Akka is the alternative that provides outstanding performance +even if you're only running it on one machine. Akka also supplies a wide array +of concurrency-paradigms, allowing for users to choose the right tool for the +job. + +The integration possibilities for Akka Actors are immense through the Apache +Camel integration. We provide Software Transactional Memory concurrency control +through the excellent Multiverse project, and have integrated that with Actors, +creating Transactors for coordinated concurrent transactions. We have Agents and +Dataflow concurrency as well. + + +What's a good use-case for Akka? +-------------------------------- + +(Web, Cloud, Application) Services - Actors lets you manage service failures +(Supervisors), load management (back-off strategies, timeouts and +processing-isolation), both horizontal and vertical scalability (add more cores +and/or add more machines). Think payment processing, invoicing, order matching, +datacrunching, messaging. Really any highly transactional systems like banking, +betting, games. + +Here's what some of the Akka users have to say about how they are using Akka: +http://stackoverflow.com/questions/4493001/good-use-case-for-akka + + +Cloudy Akka +----------- + +And that's all in the ApacheV2-licensed open source project. On top of that we +have a commercial product called Cloudy Akka which provides the following +features: + +#. Dynamically clustered ActorRegistry with both automatic and manual migration + of actors + +#. Cluster membership and cluster event subscriptions + +#. Durable actor mailboxes of different sizes and shapes - file-backed, + Redis-backed, ZooKeeper-backed, Beanstalkd-backed and with AMQP and JMS-based + in the works + +#. Monitoring influenced by Dapper for cross-machine message tracing and + JMX-exposed statistics + +Read more `here `_. From c8003e321c318c0b97dbe72db3f0d5beae9b8d45 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Wed, 20 Apr 2011 14:09:48 +1200 Subject: [PATCH 108/147] Add building akka page to docs --- akka-docs/index.rst | 2 +- .../{pending => manual}/building-akka.rst | 203 ++++++++++-------- 2 files changed, 113 insertions(+), 92 deletions(-) rename akka-docs/{pending => manual}/building-akka.rst (54%) diff --git a/akka-docs/index.rst b/akka-docs/index.rst index a9fdc508ad..f50be13dd6 100644 --- a/akka-docs/index.rst +++ b/akka-docs/index.rst @@ -7,6 +7,7 @@ Contents manual/why-akka manual/getting-started-first-scala manual/getting-started-first-java + manual/building-akka manual/fsm-scala .. pending/actor-registry-java @@ -15,7 +16,6 @@ Contents .. pending/agents-scala .. pending/articles .. pending/benchmarks -.. pending/building-akka .. pending/buildr .. pending/cluster-membership .. pending/companies-using-akka diff --git a/akka-docs/pending/building-akka.rst b/akka-docs/manual/building-akka.rst similarity index 54% rename from akka-docs/pending/building-akka.rst rename to akka-docs/manual/building-akka.rst index 31af34c687..3d4f4ca1a0 100644 --- a/akka-docs/pending/building-akka.rst +++ b/akka-docs/manual/building-akka.rst @@ -3,171 +3,185 @@ Building Akka This page describes how to build and run Akka from the latest source code. +.. contents:: :local: + + Get the source code ------------------- -Akka uses `Git `_ and is hosted at `Github `_. +Akka uses `Git `_ and is hosted at `Github +`_. -You first need Git installed on your machine. You can then clone the source repositories: -* Akka repository from ``_ -* Akka Modules repository from ``_ +You first need Git installed on your machine. You can then clone the source +repositories: -For example: +- Akka repository from ``_ +- Akka Modules repository from ``_ -:: +For example:: - git clone git://github.com/jboner/akka.git - git clone git://github.com/jboner/akka-modules.git + git clone git://github.com/jboner/akka.git + git clone git://github.com/jboner/akka-modules.git -If you have already cloned the repositories previously then you can update the code with ``git pull``: +If you have already cloned the repositories previously then you can update the +code with ``git pull``:: -:: + git pull origin master - git pull origin master SBT - Simple Build Tool ----------------------- -Akka is using the excellent `SBT `_ build system. So the first thing you have to do is to download and install SBT. You can read more about how to do that `here `_ . +Akka is using the excellent `SBT `_ +build system. So the first thing you have to do is to download and install +SBT. You can read more about how to do that `here +`_ . -The SBT commands that you'll need to build Akka are all included below. If you want to find out more about SBT and using it for your own projects do read the `SBT documentation `_. +The SBT commands that you'll need to build Akka are all included below. If you +want to find out more about SBT and using it for your own projects do read the +`SBT documentation +`_. -The Akka SBT build file is ``project/build/AkkaProject.scala`` with some properties defined in ``project/build.properties``. +The Akka SBT build file is ``project/build/AkkaProject.scala`` with some +properties defined in ``project/build.properties``. ----- Building Akka ------------- -First make sure that you are in the akka code directory: +First make sure that you are in the akka code directory:: -:: + cd akka - cd akka Fetching dependencies ^^^^^^^^^^^^^^^^^^^^^ -SBT does not fetch dependencies automatically. You need to manually do this with the ``update`` command: +SBT does not fetch dependencies automatically. You need to manually do this with +the ``update`` command:: -:: + sbt update - sbt update +Once finished, all the dependencies for Akka will be in the ``lib_managed`` +directory under each module: akka-actor, akka-stm, and so on. -Once finished, all the dependencies for Akka will be in the ``lib_managed`` directory under each module: akka-actor, akka-stm, and so on. +*Note: you only need to run update the first time you are building the code, +or when the dependencies have changed.* -*Note: you only need to run {{update}} the first time you are building the code, or when the dependencies have changed.* Building ^^^^^^^^ -To compile all the Akka core modules use the ``compile`` command: +To compile all the Akka core modules use the ``compile`` command:: -:: + sbt compile - sbt compile +You can run all tests with the ``test`` command:: -You can run all tests with the ``test`` command: + sbt test -:: +If compiling and testing are successful then you have everything working for the +latest Akka development version. - sbt test - -If compiling and testing are successful then you have everything working for the latest Akka development version. Publish to local Ivy repository ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you want to deploy the artifacts to your local Ivy repository (for example, to use from an SBT project) use the ``publish-local`` command: +If you want to deploy the artifacts to your local Ivy repository (for example, +to use from an SBT project) use the ``publish-local`` command:: -:: + sbt publish-local - sbt publish-local Publish to local Maven repository ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you want to deploy the artifacts to your local Maven repository use: +If you want to deploy the artifacts to your local Maven repository use:: -:: + sbt publish-local publish - sbt publish-local publish SBT interactive mode ^^^^^^^^^^^^^^^^^^^^ -Note that in the examples above we are calling ``sbt compile`` and ``sbt test`` and so on. SBT also has an interactive mode. If you just run ``sbt`` you enter the interactive SBT prompt and can enter the commands directly. This saves starting up a new JVM instance for each command and can be much faster and more convenient. +Note that in the examples above we are calling ``sbt compile`` and ``sbt test`` +and so on. SBT also has an interactive mode. If you just run ``sbt`` you enter +the interactive SBT prompt and can enter the commands directly. This saves +starting up a new JVM instance for each command and can be much faster and more +convenient. For example, building Akka as above is more commonly done like this: -:: +.. code-block:: none + + % sbt + [info] Building project akka 1.1-SNAPSHOT against Scala 2.9.0.RC1 + [info] using AkkaParentProject with sbt 0.7.6.RC0 and Scala 2.7.7 + > update + [info] + [info] == akka-actor / update == + ... + [success] Successful. + [info] + [info] Total time ... + > compile + ... + > test + ... - % sbt - [info] Building project akka 1.1-SNAPSHOT against Scala 2.8.1 - [info] using AkkaParentProject with sbt 0.7.5.RC0 and Scala 2.7.7 - > update - [info] - [info] == akka-actor / update == - ... - [success] Successful. - [info] - [info] Total time ... - > compile - ... - > test - ... SBT batch mode ^^^^^^^^^^^^^^ -It's also possible to combine commands in a single call. For example, updating, testing, and publishing Akka to the local Ivy repository can be done with: +It's also possible to combine commands in a single call. For example, updating, +testing, and publishing Akka to the local Ivy repository can be done with:: -:: + sbt update test publish-local - sbt update test publish-local - ----- Building Akka Modules --------------------- -To build Akka Modules first build and publish Akka to your local Ivy repository as described above. Or using: +To build Akka Modules first build and publish Akka to your local Ivy repository +as described above. Or using:: -:: + cd akka + sbt update publish-local - cd akka - sbt update publish-local +Then you can build Akka Modules using the same steps as building Akka. First +update to get all dependencies (including the Akka core modules), then compile, +test, or publish-local as needed. For example:: -Then you can build Akka Modules using the same steps as building Akka. First update to get all dependencies (including the Akka core modules), then compile, test, or publish-local as needed. For example: + cd akka-modules + sbt update publish-local -:: - - cd akka-modules - sbt update publish-local Microkernel distribution ^^^^^^^^^^^^^^^^^^^^^^^^ -To build the Akka Modules microkernel (the same as the Akka Modules distribution download) use the ``dist`` command: +To build the Akka Modules microkernel (the same as the Akka Modules distribution +download) use the ``dist`` command:: -:: + sbt dist - sbt dist +The distribution zip can be found in the dist directory and is called +``akka-modules-{version}.zip``. -The distribution zip can be found in the dist directory and is called ``akka-modules-{version}.zip``. +To run the mircokernel, unzip the zip file, change into the unzipped directory, +set the ``AKKA_HOME`` environment variable, and run the main jar file. For +example: -To run the mircokernel, unzip the zip file, change into the unzipped directory, set the ``AKKA_HOME`` environment variable, and run the main jar file. For example: +.. code-block:: none -:: + unzip dist/akka-modules-1.1-SNAPSHOT.zip + cd akka-modules-1.1-SNAPSHOT + export AKKA_HOME=`pwd` + java -jar akka-modules-1.1-SNAPSHOT.jar - unzip dist/akka-modules-1.1-SNAPSHOT.zip - cd akka-modules-1.1-SNAPSHOT - export AKKA_HOME=`pwd` - java -jar akka-modules-1.1-SNAPSHOT.jar +The microkernel will boot up and install the sample applications that reside in +the distribution's ``deploy`` directory. You can deploy your own applications +into the ``deploy`` directory as well. -The microkernel will boot up and install the sample applications that reside in the distribution's ``deploy`` directory. You can deploy your own applications into the ``deploy`` directory as well. - ----- Scripts ------- @@ -177,32 +191,38 @@ Linux/Unix init script Here is a Linux/Unix init script that can be very useful: -``_ +http://github.com/jboner/akka/blob/master/scripts/akka-init-script.sh Copy and modify as needed. + Simple startup shell script ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This little script might help a bit. Just make sure you have the Akka distribution in the '$AKKA_HOME/dist' directory and then invoke this script to start up the kernel. The distribution is created in the './dist' dir for you if you invoke 'sbt dist'. +This little script might help a bit. Just make sure you have the Akka +distribution in the '$AKKA_HOME/dist' directory and then invoke this script to +start up the kernel. The distribution is created in the './dist' dir for you if +you invoke 'sbt dist'. -``_ +http://github.com/jboner/akka/blob/master/scripts/run_akka.sh Copy and modify as needed. ----- Dependencies ------------ -If you are managing dependencies by hand you can find out what all the compile dependencies are for each module by looking in the ``lib_managed/compile`` directories. For example, you can run this to create a listing of dependencies (providing you have the source code and have run ``sbt update``): +If you are managing dependencies by hand you can find out what all the compile +dependencies are for each module by looking in the ``lib_managed/compile`` +directories. For example, you can run this to create a listing of dependencies +(providing you have the source code and have run ``sbt update``):: -:: + cd akka + ls -1 */lib_managed/compile - cd akka - ls -1 */lib_managed/compile -Here are the dependencies used by the Akka core modules. +Dependencies used by the Akka core modules +------------------------------------------ akka-actor ^^^^^^^^^^ @@ -247,8 +267,9 @@ akka-http * jsr250-api-1.0.jar * jsr311-api-1.1.jar ----- -Here are the dependencies used by the Akka modules. + +Dependencies used by the Akka modules +------------------------------------- akka-amqp ^^^^^^^^^ From 8cbcbd37938e7fd9096956e7125752d5b0d562df Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Wed, 20 Apr 2011 14:28:22 +1200 Subject: [PATCH 109/147] Add configuration to docs --- akka-docs/index.rst | 3 +- akka-docs/manual/configuration.rst | 31 +++++ akka-docs/pending/configuration.rst | 180 ---------------------------- 3 files changed, 32 insertions(+), 182 deletions(-) create mode 100644 akka-docs/manual/configuration.rst delete mode 100644 akka-docs/pending/configuration.rst diff --git a/akka-docs/index.rst b/akka-docs/index.rst index f50be13dd6..7a4301de34 100644 --- a/akka-docs/index.rst +++ b/akka-docs/index.rst @@ -8,6 +8,7 @@ Contents manual/getting-started-first-scala manual/getting-started-first-java manual/building-akka + manual/configuration manual/fsm-scala .. pending/actor-registry-java @@ -19,7 +20,6 @@ Contents .. pending/buildr .. pending/cluster-membership .. pending/companies-using-akka -.. pending/configuration .. pending/dataflow-java .. pending/dataflow-scala .. pending/deployment-scenarios @@ -80,5 +80,4 @@ Contents Links ===== -* `Akka Documentation `_ * `Support `_ diff --git a/akka-docs/manual/configuration.rst b/akka-docs/manual/configuration.rst new file mode 100644 index 0000000000..fd19b71db4 --- /dev/null +++ b/akka-docs/manual/configuration.rst @@ -0,0 +1,31 @@ +Configuration +============= + +Specifying the configuration file +--------------------------------- + +If you don't specify a configuration file then Akka uses default values. If +you want to override these then you should edit the ``akka.conf`` file in the +``AKKA_HOME/config`` directory. This config inherits from the +``akka-reference.conf`` file that you see below. Use your ``akka.conf`` to override +any property in the reference config. + +The config can be specified in various ways: + +* Define the ``-Dakka.config=...`` system property option + +* Put an ``akka.conf`` file on the classpath + +* Define the ``AKKA_HOME`` environment variable pointing to the root of the Akka + distribution. The config is taken from the ``AKKA_HOME/config`` directory. You + can also point to the AKKA_HOME by specifying the ``-Dakka.home=...`` system + property option. + + +Defining the configuration file +------------------------------- + +Here is the reference configuration file: + +.. literalinclude:: ../../config/akka-reference.conf + :language: none diff --git a/akka-docs/pending/configuration.rst b/akka-docs/pending/configuration.rst deleted file mode 100644 index 19d4a1a566..0000000000 --- a/akka-docs/pending/configuration.rst +++ /dev/null @@ -1,180 +0,0 @@ -Configuration -============= - -Specifying the configuration file ---------------------------------- - -If you don't specify a configuration file then Akka is using default values. If you want to override these then you should edit the 'akka.conf' file in the 'AKKA_HOME/config' directory. This config inherits from the 'akka-reference.conf' file that you see below, use your 'akka.conf' to override any property in the reference config. - -The config can be specified in a various of ways: - -* Define the '-Dakka.config=...' system property option. -* Put the 'akka.conf' file on the classpath. -* Define 'AKKA_HOME' environment variable pointing to the root of the Akka distribution, in which the config is taken from the 'AKKA_HOME/config' directory, you can also point to the AKKA_HOME by specifying the '-Dakka.home=...' system property option. - -Defining the configuration file -------------------------------- - -``_ -#################### -# Akka Config File # -#################### - -# This file has all the default settings, so all these could be removed with no visible effect. -# Modify as needed. - -akka { - version = "1.1-SNAPSHOT" # Akka version, checked against the runtime version of Akka. - - enabled-modules = [] # Comma separated list of the enabled modules. Options: ["remote", "camel", "http"] - - time-unit = "seconds" # Time unit for all timeout properties throughout the config - - event-handlers = ["akka.event.EventHandler$DefaultListener"] # event handlers to register at boot time (EventHandler$DefaultListener logs to STDOUT) - event-handler-level = "DEBUG" # Options: ERROR, WARNING, INFO, DEBUG - - # These boot classes are loaded (and created) automatically when the Akka Microkernel boots up - # Can be used to bootstrap your application(s) - # Should be the FQN (Fully Qualified Name) of the boot class which needs to have a default constructor - # boot = ["sample.camel.Boot", - # "sample.rest.java.Boot", - # "sample.rest.scala.Boot", - # "sample.security.Boot"] - boot = [] - - actor { - timeout = 5 # Default timeout for Future based invocations - # - Actor: !! && !!! - # - UntypedActor: sendRequestReply && sendRequestReplyFuture - # - TypedActor: methods with non-void return type - serialize-messages = off # Does a deep clone of (non-primitive) messages to ensure immutability - throughput = 5 # Default throughput for all ExecutorBasedEventDrivenDispatcher, set to 1 for complete fairness - throughput-deadline-time = -1 # Default throughput deadline for all ExecutorBasedEventDrivenDispatcher, set to 0 or negative for no deadline - dispatcher-shutdown-timeout = 1 # Using the akka.time-unit, how long dispatchers by default will wait for new actors until they shut down - - default-dispatcher { - type = "GlobalExecutorBasedEventDriven" # Must be one of the following, all "Global*" are non-configurable - # - ExecutorBasedEventDriven - # - ExecutorBasedEventDrivenWorkStealing - # - GlobalExecutorBasedEventDriven - keep-alive-time = 60 # Keep alive time for threads - core-pool-size-factor = 1.0 # No of core threads ... ceil(available processors * factor) - max-pool-size-factor = 4.0 # Max no of threads ... ceil(available processors * factor) - executor-bounds = -1 # Makes the Executor bounded, -1 is unbounded - allow-core-timeout = on # Allow core threads to time out - rejection-policy = "caller-runs" # abort, caller-runs, discard-oldest, discard - throughput = 5 # Throughput for ExecutorBasedEventDrivenDispatcher, set to 1 for complete fairness - throughput-deadline-time = -1 # Throughput deadline for ExecutorBasedEventDrivenDispatcher, set to 0 or negative for no deadline - mailbox-capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) - # If positive then a bounded mailbox is used and the capacity is set using the property - # NOTE: setting a mailbox to 'blocking' can be a bit dangerous, - # could lead to deadlock, use with care - # - # The following are only used for ExecutorBasedEventDriven - # and only if mailbox-capacity > 0 - mailbox-push-timeout-time = 10 # Specifies the timeout to add a new message to a mailbox that is full - negative number means infinite timeout - # (in unit defined by the time-unit property) - } - } - - stm { - fair = on # Should global transactions be fair or non-fair (non fair yield better performance) - max-retries = 1000 - timeout = 5 # Default timeout for blocking transactions and transaction set (in unit defined by - # the time-unit property) - write-skew = true - blocking-allowed = false - interruptible = false - speculative = true - quick-release = true - propagation = "requires" - trace-level = "none" - } - - jta { - provider = "from-jndi" # Options: - "from-jndi" (means that Akka will try to detect a TransactionManager in the JNDI) - # - "atomikos" (means that Akka will use the Atomikos based JTA impl in 'akka-jta', - # e.g. you need the akka-jta JARs on classpath). - timeout = 60 - } - - http { - hostname = "localhost" - port = 9998 - - #If you are using akka.http.AkkaRestServlet - filters = ["se.scalablesolutions.akka.security.AkkaSecurityFilterFactory"] # List with all jersey filters to use - # resource-packages = ["sample.rest.scala", - # "sample.rest.java", - # "sample.security"] # List with all resource packages for your Jersey services - resource-packages = [] - - # The authentication service to use. Need to be overridden (sample now) - # authenticator = "sample.security.BasicAuthenticationService" - authenticator = "N/A" - - # Uncomment if you are using the KerberosAuthenticationActor - # kerberos { - # servicePrincipal = "HTTP/localhost@EXAMPLE.COM" - # keyTabLocation = "URL to keytab" - # kerberosDebug = "true" - # realm = "EXAMPLE.COM" - # } - kerberos { - servicePrincipal = "N/A" - keyTabLocation = "N/A" - kerberosDebug = "N/A" - realm = "" - } - - #If you are using akka.http.AkkaMistServlet - mist-dispatcher { - #type = "GlobalExecutorBasedEventDriven" # Uncomment if you want to use a different dispatcher than the default one for Comet - } - connection-close = true # toggles the addition of the "Connection" response header with a "close" value - root-actor-id = "_httproot" # the id of the actor to use as the root endpoint - root-actor-builtin = true # toggles the use of the built-in root endpoint base class - timeout = 1000 # the default timeout for all async requests (in ms) - expired-header-name = "Async-Timeout" # the name of the response header to use when an async request expires - expired-header-value = "expired" # the value of the response header to use when an async request expires - } - - remote { - - # secure-cookie = "050E0A0D0D06010A00000900040D060F0C09060B" # generate your own with '$AKKA_HOME/scripts/generate_secure_cookie.sh' or using 'Crypt.generateSecureCookie' - secure-cookie = "" - - compression-scheme = "zlib" # Options: "zlib" (lzf to come), leave out for no compression - zlib-compression-level = 6 # Options: 0-9 (1 being fastest and 9 being the most compressed), default is 6 - - layer = "akka.remote.netty.NettyRemoteSupport" - - server { - hostname = "localhost" # The hostname or IP that clients should connect to - port = 2552 # The port clients should connect to. Default is 2552 (AKKA) - message-frame-size = 1048576 # Increase this if you want to be able to send messages with large payloads - connection-timeout = 1 - require-cookie = off # Should the remote server require that it peers share the same secure-cookie (defined in the 'remote' section)? - untrusted-mode = off # Enable untrusted mode for full security of server managed actors, allows untrusted clients to connect. - backlog = 4096 # Sets the size of the connection backlog - execution-pool-keepalive = 60# Length in akka.time-unit how long core threads will be kept alive if idling - execution-pool-size = 16# Size of the core pool of the remote execution unit - max-channel-memory-size = 0 # Maximum channel size, 0 for off - max-total-memory-size = 0 # Maximum total size of all channels, 0 for off - } - - client { - buffering { - retry-message-send-on-failure = on - capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) - # If positive then a bounded mailbox is used and the capacity is set using the property - } - reconnect-delay = 5 - read-timeout = 10 - message-frame-size = 1048576 - reap-futures-delay = 5 - reconnection-time-window = 600 # Maximum time window that a client should try to reconnect for - } - } -} -``_ From 21c1aa2f25cfaad43f6caa9b43f0aa1d2b988dc5 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Wed, 20 Apr 2011 14:59:16 +1200 Subject: [PATCH 110/147] Simplify docs html links for now --- akka-docs/themes/akka/static/akka.css_t | 2 -- akka-docs/themes/akka/theme.conf | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/akka-docs/themes/akka/static/akka.css_t b/akka-docs/themes/akka/static/akka.css_t index 7c417e9917..f05e86bb6a 100644 --- a/akka-docs/themes/akka/static/akka.css_t +++ b/akka-docs/themes/akka/static/akka.css_t @@ -30,13 +30,11 @@ div.footer { /* link colors and text decoration */ a:link { - font-weight: bold; text-decoration: none; color: {{ theme_linkcolor }}; } a:visited { - font-weight: bold; text-decoration: none; color: {{ theme_visitedlinkcolor }}; } diff --git a/akka-docs/themes/akka/theme.conf b/akka-docs/themes/akka/theme.conf index 7f45fd1718..620c88f2ae 100644 --- a/akka-docs/themes/akka/theme.conf +++ b/akka-docs/themes/akka/theme.conf @@ -7,6 +7,6 @@ pygments_style = friendly full_logo = false textcolor = #333333 headingcolor = #0c3762 -linkcolor = #dc3c01 -visitedlinkcolor = #892601 -hoverlinkcolor = #ff4500 +linkcolor = #0c3762 +visitedlinkcolor = #0c3762 +hoverlinkcolor = #0c3762 From 2f7b7b060e749ff359a02b13aa5428974d02b39d Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Wed, 20 Apr 2011 15:13:43 +1200 Subject: [PATCH 111/147] Fix another Predef.error warning --- .../src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala index b8f4eb2748..a47c895027 100644 --- a/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala +++ b/akka-remote/src/test/scala/remote/ServerInitiatedRemoteActorSpec.scala @@ -189,7 +189,7 @@ class ServerInitiatedRemoteActorSpec extends AkkaRemoteTest { while(!testDone()) { if (latch.await(200, TimeUnit.MILLISECONDS)) - error("Test didn't complete within 100 cycles") + sys.error("Test didn't complete within 100 cycles") else latch.countDown() } From 0f436f073822a332cd948998a1a1092a3f2dbba2 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Wed, 20 Apr 2011 16:12:51 +1200 Subject: [PATCH 112/147] Fix bug in actor pool round robin selector --- .../src/test/scala/akka/routing/RoutingSpec.scala | 4 ++-- akka-actor/src/main/scala/akka/routing/Pool.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala index 96f93fc160..51b805f69e 100644 --- a/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala @@ -425,7 +425,7 @@ class RoutingSpec extends WordSpec with MustMatchers { }) def limit = 1 - def selectionCount = 2 + def selectionCount = 1 def rampupRate = 0.1 def partialFill = true def instance = factory @@ -458,7 +458,7 @@ class RoutingSpec extends WordSpec with MustMatchers { }) def limit = 2 - def selectionCount = 2 + def selectionCount = 1 def rampupRate = 0.1 def partialFill = false def instance = factory diff --git a/akka-actor/src/main/scala/akka/routing/Pool.scala b/akka-actor/src/main/scala/akka/routing/Pool.scala index 8d431541f7..6ab6aa0c4d 100644 --- a/akka-actor/src/main/scala/akka/routing/Pool.scala +++ b/akka-actor/src/main/scala/akka/routing/Pool.scala @@ -104,7 +104,7 @@ trait DefaultActorPool extends ActorPool { this: Actor => /** * Selectors * These traits define how, when a message needs to be routed, delegate(s) are chosen from the pool - **/ + */ /** * Returns the set of delegates with the least amount of message backlog. @@ -141,7 +141,7 @@ trait RoundRobinSelector { else selectionCount val set = - for (i <- 0 to take) yield { + for (i <- 0 until take) yield { _last = (_last + 1) % length delegates(_last) } From bb8dca5b88a505adafe969de28a3d4781664ab79 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Wed, 20 Apr 2011 18:02:11 +1200 Subject: [PATCH 113/147] Try reorganised docs --- akka-docs/Makefile | 2 +- akka-docs/{ => _sphinx}/exts/includecode.py | 0 akka-docs/{ => _sphinx}/pygments/setup.py | 0 .../{ => _sphinx}/pygments/styles/__init__.py | 0 .../{ => _sphinx}/pygments/styles/simple.py | 0 .../{_static => _sphinx/static}/akka.png | Bin .../{_static => _sphinx/static}/logo.png | Bin .../{ => _sphinx}/themes/akka/layout.html | 0 .../themes/akka/static/akka.css_t | 0 .../themes/akka/static/alert_info_32.png | Bin .../themes/akka/static/alert_warning_32.png | Bin .../themes/akka/static/bg-page.png | Bin .../themes/akka/static/bullet_orange.png | Bin .../{ => _sphinx}/themes/akka/theme.conf | 0 akka-docs/conf.py | 10 +-- akka-docs/index.rst | 73 +----------------- akka-docs/{manual => intro}/building-akka.rst | 0 akka-docs/{manual => intro}/configuration.rst | 0 akka-docs/{manual => intro}/examples/Pi.scala | 0 .../getting-started-first-java.rst | 0 .../getting-started-first-scala.rst | 0 .../getting-started-first.rst | 0 akka-docs/intro/index.rst | 11 +++ akka-docs/{manual => intro}/pi-formula.png | Bin akka-docs/{manual => intro}/why-akka.rst | 0 akka-docs/manual/more.png | Bin 1502 -> 0 bytes .../actors-scala.rst => scala/actors.rst} | 4 +- .../{manual/fsm-scala.rst => scala/fsm.rst} | 0 akka-docs/scala/index.rst | 8 ++ 29 files changed, 29 insertions(+), 79 deletions(-) rename akka-docs/{ => _sphinx}/exts/includecode.py (100%) rename akka-docs/{ => _sphinx}/pygments/setup.py (100%) rename akka-docs/{ => _sphinx}/pygments/styles/__init__.py (100%) rename akka-docs/{ => _sphinx}/pygments/styles/simple.py (100%) rename akka-docs/{_static => _sphinx/static}/akka.png (100%) rename akka-docs/{_static => _sphinx/static}/logo.png (100%) rename akka-docs/{ => _sphinx}/themes/akka/layout.html (100%) rename akka-docs/{ => _sphinx}/themes/akka/static/akka.css_t (100%) rename akka-docs/{ => _sphinx}/themes/akka/static/alert_info_32.png (100%) rename akka-docs/{ => _sphinx}/themes/akka/static/alert_warning_32.png (100%) rename akka-docs/{ => _sphinx}/themes/akka/static/bg-page.png (100%) rename akka-docs/{ => _sphinx}/themes/akka/static/bullet_orange.png (100%) rename akka-docs/{ => _sphinx}/themes/akka/theme.conf (100%) rename akka-docs/{manual => intro}/building-akka.rst (100%) rename akka-docs/{manual => intro}/configuration.rst (100%) rename akka-docs/{manual => intro}/examples/Pi.scala (100%) rename akka-docs/{manual => intro}/getting-started-first-java.rst (100%) rename akka-docs/{manual => intro}/getting-started-first-scala.rst (100%) rename akka-docs/{manual => intro}/getting-started-first.rst (100%) create mode 100644 akka-docs/intro/index.rst rename akka-docs/{manual => intro}/pi-formula.png (100%) rename akka-docs/{manual => intro}/why-akka.rst (100%) delete mode 100644 akka-docs/manual/more.png rename akka-docs/{pending/actors-scala.rst => scala/actors.rst} (99%) rename akka-docs/{manual/fsm-scala.rst => scala/fsm.rst} (100%) create mode 100644 akka-docs/scala/index.rst diff --git a/akka-docs/Makefile b/akka-docs/Makefile index 7b803258cb..49f649367f 100644 --- a/akka-docs/Makefile +++ b/akka-docs/Makefile @@ -8,7 +8,7 @@ PAPER = BUILDDIR = _build EASYINSTALL = easy_install LOCALPACKAGES = $(shell pwd)/$(BUILDDIR)/site-packages -PYGMENTSDIR = pygments +PYGMENTSDIR = _sphinx/pygments # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 diff --git a/akka-docs/exts/includecode.py b/akka-docs/_sphinx/exts/includecode.py similarity index 100% rename from akka-docs/exts/includecode.py rename to akka-docs/_sphinx/exts/includecode.py diff --git a/akka-docs/pygments/setup.py b/akka-docs/_sphinx/pygments/setup.py similarity index 100% rename from akka-docs/pygments/setup.py rename to akka-docs/_sphinx/pygments/setup.py diff --git a/akka-docs/pygments/styles/__init__.py b/akka-docs/_sphinx/pygments/styles/__init__.py similarity index 100% rename from akka-docs/pygments/styles/__init__.py rename to akka-docs/_sphinx/pygments/styles/__init__.py diff --git a/akka-docs/pygments/styles/simple.py b/akka-docs/_sphinx/pygments/styles/simple.py similarity index 100% rename from akka-docs/pygments/styles/simple.py rename to akka-docs/_sphinx/pygments/styles/simple.py diff --git a/akka-docs/_static/akka.png b/akka-docs/_sphinx/static/akka.png similarity index 100% rename from akka-docs/_static/akka.png rename to akka-docs/_sphinx/static/akka.png diff --git a/akka-docs/_static/logo.png b/akka-docs/_sphinx/static/logo.png similarity index 100% rename from akka-docs/_static/logo.png rename to akka-docs/_sphinx/static/logo.png diff --git a/akka-docs/themes/akka/layout.html b/akka-docs/_sphinx/themes/akka/layout.html similarity index 100% rename from akka-docs/themes/akka/layout.html rename to akka-docs/_sphinx/themes/akka/layout.html diff --git a/akka-docs/themes/akka/static/akka.css_t b/akka-docs/_sphinx/themes/akka/static/akka.css_t similarity index 100% rename from akka-docs/themes/akka/static/akka.css_t rename to akka-docs/_sphinx/themes/akka/static/akka.css_t diff --git a/akka-docs/themes/akka/static/alert_info_32.png b/akka-docs/_sphinx/themes/akka/static/alert_info_32.png similarity index 100% rename from akka-docs/themes/akka/static/alert_info_32.png rename to akka-docs/_sphinx/themes/akka/static/alert_info_32.png diff --git a/akka-docs/themes/akka/static/alert_warning_32.png b/akka-docs/_sphinx/themes/akka/static/alert_warning_32.png similarity index 100% rename from akka-docs/themes/akka/static/alert_warning_32.png rename to akka-docs/_sphinx/themes/akka/static/alert_warning_32.png diff --git a/akka-docs/themes/akka/static/bg-page.png b/akka-docs/_sphinx/themes/akka/static/bg-page.png similarity index 100% rename from akka-docs/themes/akka/static/bg-page.png rename to akka-docs/_sphinx/themes/akka/static/bg-page.png diff --git a/akka-docs/themes/akka/static/bullet_orange.png b/akka-docs/_sphinx/themes/akka/static/bullet_orange.png similarity index 100% rename from akka-docs/themes/akka/static/bullet_orange.png rename to akka-docs/_sphinx/themes/akka/static/bullet_orange.png diff --git a/akka-docs/themes/akka/theme.conf b/akka-docs/_sphinx/themes/akka/theme.conf similarity index 100% rename from akka-docs/themes/akka/theme.conf rename to akka-docs/_sphinx/themes/akka/theme.conf diff --git a/akka-docs/conf.py b/akka-docs/conf.py index f0dd997167..209f747afc 100644 --- a/akka-docs/conf.py +++ b/akka-docs/conf.py @@ -7,7 +7,7 @@ import sys, os # -- General configuration ----------------------------------------------------- -sys.path.append(os.path.abspath('exts')) +sys.path.append(os.path.abspath('_sphinx/exts')) extensions = ['sphinx.ext.todo', 'includecode'] templates_path = ['_templates'] @@ -31,13 +31,13 @@ html_theme = 'akka' html_theme_options = { 'full_logo': 'true' } -html_theme_path = ['themes'] +html_theme_path = ['_sphinx/themes'] html_title = 'Akka Documentation' -html_logo = '_static/logo.png' +html_logo = '_sphinx/static/logo.png' #html_favicon = None -html_static_path = ['_static'] +html_static_path = ['_sphinx/static'] html_last_updated_fmt = '%b %d, %Y' #html_sidebars = {} @@ -65,4 +65,4 @@ latex_elements = { 'preamble': '\\definecolor{VerbatimColor}{rgb}{0.935,0.935,0.935}' } -# latex_logo = '_static/akka.png' +# latex_logo = '_sphinx/static/akka.png' diff --git a/akka-docs/index.rst b/akka-docs/index.rst index 7a4301de34..9a57f097e2 100644 --- a/akka-docs/index.rst +++ b/akka-docs/index.rst @@ -4,78 +4,9 @@ Contents .. toctree:: :maxdepth: 2 - manual/why-akka - manual/getting-started-first-scala - manual/getting-started-first-java - manual/building-akka - manual/configuration - manual/fsm-scala + intro/index + scala/index -.. pending/actor-registry-java -.. pending/actor-registry-scala -.. pending/actors-scala -.. pending/agents-scala -.. pending/articles -.. pending/benchmarks -.. pending/buildr -.. pending/cluster-membership -.. pending/companies-using-akka -.. pending/dataflow-java -.. pending/dataflow-scala -.. pending/deployment-scenarios -.. pending/developer-guidelines -.. pending/dispatchers-java -.. pending/dispatchers-scala -.. pending/event-handler -.. pending/external-sample-projects -.. pending/fault-tolerance-java -.. pending/fault-tolerance-scala -.. pending/Feature Stability Matrix -.. pending/futures-scala -.. pending/getting-started -.. pending/guice-integration -.. pending/Home -.. pending/http -.. pending/issue-tracking -.. pending/language-bindings -.. pending/licenses -.. pending/logging -.. pending/Migration-1.0-1.1 -.. pending/migration-guide-0.10.x-1.0.x -.. pending/migration-guide-0.7.x-0.8.x -.. pending/migration-guide-0.8.x-0.9.x -.. pending/migration-guide-0.9.x-0.10.x -.. pending/migration-guides -.. pending/Recipes -.. pending/release-notes -.. pending/remote-actors-java -.. pending/remote-actors-scala -.. pending/routing-java -.. pending/routing-scala -.. pending/scheduler -.. pending/security -.. pending/serialization-java -.. pending/serialization-scala -.. pending/servlet -.. pending/slf4j -.. pending/sponsors -.. pending/stm -.. pending/stm-java -.. pending/stm-scala -.. pending/team -.. pending/test -.. pending/testkit -.. pending/testkit-example -.. pending/third-party-integrations -.. pending/transactors-java -.. pending/transactors-scala -.. pending/tutorial-chat-server-java -.. pending/tutorial-chat-server-scala -.. pending/typed-actors-java -.. pending/typed-actors-scala -.. pending/untyped-actors-java -.. pending/use-cases -.. pending/web Links ===== diff --git a/akka-docs/manual/building-akka.rst b/akka-docs/intro/building-akka.rst similarity index 100% rename from akka-docs/manual/building-akka.rst rename to akka-docs/intro/building-akka.rst diff --git a/akka-docs/manual/configuration.rst b/akka-docs/intro/configuration.rst similarity index 100% rename from akka-docs/manual/configuration.rst rename to akka-docs/intro/configuration.rst diff --git a/akka-docs/manual/examples/Pi.scala b/akka-docs/intro/examples/Pi.scala similarity index 100% rename from akka-docs/manual/examples/Pi.scala rename to akka-docs/intro/examples/Pi.scala diff --git a/akka-docs/manual/getting-started-first-java.rst b/akka-docs/intro/getting-started-first-java.rst similarity index 100% rename from akka-docs/manual/getting-started-first-java.rst rename to akka-docs/intro/getting-started-first-java.rst diff --git a/akka-docs/manual/getting-started-first-scala.rst b/akka-docs/intro/getting-started-first-scala.rst similarity index 100% rename from akka-docs/manual/getting-started-first-scala.rst rename to akka-docs/intro/getting-started-first-scala.rst diff --git a/akka-docs/manual/getting-started-first.rst b/akka-docs/intro/getting-started-first.rst similarity index 100% rename from akka-docs/manual/getting-started-first.rst rename to akka-docs/intro/getting-started-first.rst diff --git a/akka-docs/intro/index.rst b/akka-docs/intro/index.rst new file mode 100644 index 0000000000..70f6caf3ce --- /dev/null +++ b/akka-docs/intro/index.rst @@ -0,0 +1,11 @@ +Introduction +============ + +.. toctree:: + :maxdepth: 2 + + why-akka + getting-started-first-scala + getting-started-first-java + building-akka + configuration diff --git a/akka-docs/manual/pi-formula.png b/akka-docs/intro/pi-formula.png similarity index 100% rename from akka-docs/manual/pi-formula.png rename to akka-docs/intro/pi-formula.png diff --git a/akka-docs/manual/why-akka.rst b/akka-docs/intro/why-akka.rst similarity index 100% rename from akka-docs/manual/why-akka.rst rename to akka-docs/intro/why-akka.rst diff --git a/akka-docs/manual/more.png b/akka-docs/manual/more.png deleted file mode 100644 index 3eb7b05c84809454c095bcb61b27d83585540bb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1502 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}EX7WqAsieW95oy%9SjT% zk|nMYCBgY=CFO}lsSM@i<$9TU*~Q6;1*v-ZMd`EO*+?-kuoif_IEF;D9-S7QBN8fc z{Qv!#H|O5ncIDoi)}U@yO|gadcDOt32;uP!OYU&HC?4rDuV$kA;y@ptD?gTWdM6!d zS)tNm>KNkWAfk1NskLcJl-JAFd*v_7^oy!0szW@vcpba1`g{AYCmOj`=Rb%i<|)a>`yt`uO3u&M7Y zj=cNJN+{#&k1dwpV;v?m-C9+Z`u4K^w8;Wz<>qhuv5!-LgGtNnY~C52wPMoK_XjQD z_`2fwa?^YAj7l6r3LGpwayC=qInJ*#FEU;Jn*F?9?(N+5^UwWnIKKQTLxw_Qc;}1A zG(FKZ=Wbm4A^7Vn_lBD-XW5FTCQVrS==rkuhlMwOT+g>>>H8h&jQ#%6zD&Q<#KN<; zUiE(_dR}9(%~}D=b2S`67v=Xdbo+XkC2r)LexkWcM&@62{f{(_f(fc-zxVF4%w_EN zFP?NDo%P)(UEc2avT$)-mn&RO&kcMen>803Sa8key^(V;joHw;uC zpL1nv&!3apDgGzUw&XaI-25;fE<42OngL5*uGxcY z@BFV!DN;${Re#7ZVYS#{6WyM^o(P6-%MUFV`S4@Q*3hF{Ur#@CcLB>;I|hcyTn&@$ zRjND143?CuJl80^=CGN&@X?o#9eKy@eb2wR-{X0TNXPx}cVExD^@Qv8;tdh)4P0!^ zGadw`IVifH5^dRcqq1jqvq1_AL%_1L%r7l1E*9A&&3ab0Km7jO&Z)Z0v$M6tM4mo- zrj+o-VFDN1g!R)79?bh=edE+#&rL zZp?jcx| z&x^MmH_Z(%e`Q(p*lgDMxjf#Q(tqRrMwnHEF)qCD*mb99$_b5}Z2=EfxTM$5u3hnT zH{-M$PrkqM?cAvUw&-)h8s^1S;%Pfy3(Cx$kbXPLNvCq|{nh6Ftmo%3y7eA@*r4Ye z&?+3L+!R0SyVRM8SKnXNu5?!QZh0Gcrs#TdMZ%eV7w$yv%_@L)n>VXH=s%k2*ZqD7a~ z%Kdm8eNQY|Z^!4?GD*rsrD4kU#6Jx})4jHBm($3c6TSJx+lZ1oIsf;*eeFE|xL-f- zAvT9h$rOf9W{Jzccg_^_KdDlXRsHz5Yv^g|SMmI_lh1YghV#q)G`J$jr)sQm>A^RX zTV+*jmsltLePr2HeAasERo2Z@XDwVUZ0`4#vxI+3WSWieO34#bI=@WyW-v3pJ=xB~ z=uVr9it&>Zih&ygO2y6x-tA)c+7SNguKL#*lB~xc&UCr7R3~lP Date: Wed, 20 Apr 2011 09:24:19 +0200 Subject: [PATCH 114/147] incorporated feedback on the java tutorial --- .../manual/getting-started-first-java.rst | 52 +++++++++---------- .../manual/getting-started-first-scala.rst | 8 +-- .../java/akka/tutorial/first/java/Pi.java | 20 +++---- .../src/main/scala/Pi.scala | 2 +- 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/akka-docs/manual/getting-started-first-java.rst b/akka-docs/manual/getting-started-first-java.rst index 4a6e729207..b883acede6 100644 --- a/akka-docs/manual/getting-started-first-java.rst +++ b/akka-docs/manual/getting-started-first-java.rst @@ -184,12 +184,6 @@ We also need to edit the ``pom.xml`` build file. Let's add the dependency we nee -So, now we are all set. Just one final thing to do; make Maven download the dependencies it needs. That can be done by invoking:: - - $ mvn package - -Maven itself needs a whole bunch of dependencies but our project will only need one; ``akka-actor-1.1.jar``. Maven downloads that as well. - Start writing the code ---------------------- @@ -235,15 +229,15 @@ Messages sent to actors should always be immutable to avoid sharing mutable stat static class Calculate {} static class Work { - private final int arg; + private final int start; private final int nrOfElements; - public Work(int arg, int nrOfElements) { - this.arg = arg; + public Work(int start, int nrOfElements) { + this.start = start; this.nrOfElements = nrOfElements; } - public int getArg() { return arg; } + public int getStart() { return start; } public int getNrOfElements() { return nrOfElements; } } @@ -271,7 +265,7 @@ Now we can create the worker actor. This is done by extending in the ``UntypedA Work work = (Work) message; // perform the work - double result = calculatePiFor(work.getArg(), work.getNrOfElements()) + double result = calculatePiFor(work.getStart(), work.getNrOfElements()) // reply with the result getContext().replyUnsafe(new Result(result)); @@ -285,10 +279,10 @@ As you can see we have now created an ``UntypedActor`` with a ``onReceive`` meth The only thing missing in our ``Worker`` actor is the implementation on the ``calculatePiFor(..)`` method:: // define the work - private double calculatePiFor(int arg, int nrOfElements) { + private double calculatePiFor(int start, int nrOfElements) { double acc = 0.0; - for (int i = arg * nrOfElements; i <= ((arg + 1) * nrOfElements - 1); i++) { - acc += 4 * Math.pow(-1, i) / (2 * i + 1); + for (int i = start * nrOfElements; i <= ((start + 1) * nrOfElements - 1); i++) { + acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1); } return acc; } @@ -335,7 +329,7 @@ As you can see we are using the ``actorOf`` factory method to create actors, thi import static akka.actor.Actors.actorOf; -In thing to note is that we used two different versions of the ``actorOf`` method. For creating the ``Worker`` actor we just pass in the class but to create the ``PiRouter`` actor we can't do that since the constructor in the ``PiRouter`` class takes arguments, instead we need to use the ``UntypedActorFactory`` which unfortunately is a bit more verbose. +One thing to note is that we used two different versions of the ``actorOf`` method. For creating the ``Worker`` actor we just pass in the class but to create the ``PiRouter`` actor we can't do that since the constructor in the ``PiRouter`` class takes arguments, instead we need to use the ``UntypedActorFactory`` which unfortunately is a bit more verbose. ``actorOf`` is the only way to create an instance of an Actor, this is enforced by Akka runtime. The ``actorOf`` method instantiates the actor and returns, not an instance to the actor, but an instance to an ``ActorRef``. This reference is the handle through which you communicate with the actor. It is immutable, serializable and location-aware meaning that it "remembers" its original actor even if it is sent to other nodes across the network and can be seen as the equivalent to the Erlang actor's PID. @@ -438,8 +432,8 @@ Let's capture this in code:: if (message instanceof Calculate) { // schedule work - for (int arg = 0; arg < nrOfMessages; arg++) { - router.sendOneWay(new Work(arg, nrOfElements), getContext()); + for (int start = 0; start < nrOfMessages; start++) { + router.sendOneWay(new Work(start, nrOfElements), getContext()); } // send a PoisonPill to all workers telling them to shut down themselves @@ -525,15 +519,15 @@ Before we package it up and run it, let's take a look at the full code now, with static class Calculate {} static class Work { - private final int arg; + private final int start; private final int nrOfElements; - public Work(int arg, int nrOfElements) { - this.arg = arg; + public Work(int start, int nrOfElements) { + this.start = start; this.nrOfElements = nrOfElements; } - public int getArg() { return arg; } + public int getStart() { return start; } public int getNrOfElements() { return nrOfElements; } } @@ -553,10 +547,10 @@ Before we package it up and run it, let's take a look at the full code now, with static class Worker extends UntypedActor { // define the work - private double calculatePiFor(int arg, int nrOfElements) { + private double calculatePiFor(int start, int nrOfElements) { double acc = 0.0; - for (int i = arg * nrOfElements; i <= ((arg + 1) * nrOfElements - 1); i++) { - acc += 4 * Math.pow(-1, i) / (2 * i + 1); + for (int i = start * nrOfElements; i <= ((start + 1) * nrOfElements - 1); i++) { + acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1); } return acc; } @@ -567,7 +561,7 @@ Before we package it up and run it, let's take a look at the full code now, with Work work = (Work) message; // perform the work - double result = calculatePiFor(work.getArg(), work.getNrOfElements()) + double result = calculatePiFor(work.getStart(), work.getNrOfElements()) // reply with the result getContext().replyUnsafe(new Result(result)); @@ -628,8 +622,8 @@ Before we package it up and run it, let's take a look at the full code now, with if (message instanceof Calculate) { // schedule work - for (int arg = 0; arg < nrOfMessages; arg++) { - router.sendOneWay(new Work(arg, nrOfElements), getContext()); + for (int start = 0; start < nrOfMessages; start++) { + router.sendOneWay(new Work(start, nrOfElements), getContext()); } // send a PoisonPill to all workers telling them to shut down themselves @@ -716,7 +710,7 @@ When we have compiled the source file we are ready to run the application. This Yippee! It is working. -If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. +If you have not defined the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. Run it inside Maven ------------------- @@ -741,6 +735,8 @@ Conclusion We have learned how to create our first Akka project using Akka's actors to speed up a computation-intensive problem by scaling out on multi-core processors (also known as scaling up). We have also learned to compile and run an Akka project using either the tools on the command line or the SBT build system. +If you have a multi-core machine then I encourage you to try out different number of workers (number of working actors) by tweaking the ``nrOfWorkers`` variable to for example; 2, 4, 6, 8 etc. to see performance improvement by scaling up. + Now we are ready to take on more advanced problems. In the next tutorial we will build on this one, refactor it into more idiomatic Akka and Scala code, and introduce a few new concepts and abstractions. Whenever you feel ready, join me in the `Getting Started Tutorial: Second Chapter `_. Happy hakking. diff --git a/akka-docs/manual/getting-started-first-scala.rst b/akka-docs/manual/getting-started-first-scala.rst index 0846605cfe..735b1fac85 100644 --- a/akka-docs/manual/getting-started-first-scala.rst +++ b/akka-docs/manual/getting-started-first-scala.rst @@ -238,7 +238,7 @@ The only thing missing in our ``Worker`` actor is the implementation on the ``ca def calculatePiFor(start: Int, nrOfElements: Int): Double = { var acc = 0.0 for (i <- start until (start + nrOfElements)) - acc += 4 * math.pow(-1, i) / (2 * i + 1) + acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1) acc } @@ -404,7 +404,7 @@ But before we package it up and run it, let's take a look at the full code now, def calculatePiFor(start: Int, nrOfElements: Int): Double = { var acc = 0.0 for (i <- start until (start + nrOfElements)) - acc += 4 * math.pow(-1, i) / (2 * i + 1) + acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1) acc } @@ -435,7 +435,7 @@ But before we package it up and run it, let's take a look at the full code now, def receive = { case Calculate => // schedule work - //for (arg <- 0 until nrOfMessages) router ! Work(arg, nrOfElements) + //for (start <- 0 until nrOfMessages) router ! Work(start, nrOfElements) for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) // send a PoisonPill to all workers telling them to shut down themselves @@ -535,6 +535,8 @@ Conclusion We have learned how to create our first Akka project using Akka's actors to speed up a computation-intensive problem by scaling out on multi-core processors (also known as scaling up). We have also learned to compile and run an Akka project using either the tools on the command line or the SBT build system. +If you have a multi-core machine then I encourage you to try out different number of workers (number of working actors) by tweaking the ``nrOfWorkers`` variable to for example; 2, 4, 6, 8 etc. to see performance improvement by scaling up. + Now we are ready to take on more advanced problems. In the next tutorial we will build on this one, refactor it into more idiomatic Akka and Scala code, and introduce a few new concepts and abstractions. Whenever you feel ready, join me in the `Getting Started Tutorial: Second Chapter `_. Happy hakking. 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 cd5ffc9c06..8c0085fb97 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 @@ -56,15 +56,15 @@ public class Pi { static class Calculate {} static class Work { - private final int arg; + private final int start; private final int nrOfElements; - public Work(int arg, int nrOfElements) { - this.arg = arg; + public Work(int start, int nrOfElements) { + this.start = start; this.nrOfElements = nrOfElements; } - public int getArg() { return arg; } + public int getStart() { return start; } public int getNrOfElements() { return nrOfElements; } } @@ -84,10 +84,10 @@ public class Pi { static class Worker extends UntypedActor { // define the work - private double calculatePiFor(int arg, int nrOfElements) { + private double calculatePiFor(int start, int nrOfElements) { double acc = 0.0; - for (int i = arg * nrOfElements; i <= ((arg + 1) * nrOfElements - 1); i++) { - acc += 4 * Math.pow(-1, i) / (2 * i + 1); + for (int i = start * nrOfElements; i <= ((start + 1) * nrOfElements - 1); i++) { + acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1); } return acc; } @@ -98,7 +98,7 @@ public class Pi { Work work = (Work) message; // perform the work - double result = calculatePiFor(work.getArg(), work.getNrOfElements()); + double result = calculatePiFor(work.getStart(), work.getNrOfElements()); // reply with the result getContext().replyUnsafe(new Result(result)); @@ -157,8 +157,8 @@ public class Pi { if (message instanceof Calculate) { // schedule work - for (int arg = 0; arg < nrOfMessages; arg++) { - router.sendOneWay(new Work(arg, nrOfElements), getContext()); + for (int start = 0; start < nrOfMessages; start++) { + router.sendOneWay(new Work(start, nrOfElements), getContext()); } // send a PoisonPill to all workers telling them to shut down themselves diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index 7521d00cad..f87d139b97 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -59,7 +59,7 @@ object Pi extends App { def calculatePiFor(start: Int, nrOfElements: Int): Double = { var acc = 0.0 for (i <- start until (start + nrOfElements)) - acc += 4 * math.pow(-1, i) / (2 * i + 1) + acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1) acc } From df4ba5d3bd7e33b37f9aaac6820f9dbb85e026f8 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Wed, 20 Apr 2011 19:49:06 +1200 Subject: [PATCH 115/147] Add meta-docs --- akka-docs/dev/documentation.rst | 18 ++++++++++++++++++ akka-docs/dev/index.rst | 7 +++++++ akka-docs/index.rst | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 akka-docs/dev/documentation.rst create mode 100644 akka-docs/dev/index.rst diff --git a/akka-docs/dev/documentation.rst b/akka-docs/dev/documentation.rst new file mode 100644 index 0000000000..d5678c58ff --- /dev/null +++ b/akka-docs/dev/documentation.rst @@ -0,0 +1,18 @@ +Documentation +============= + +The Akka documentation uses `reStructuredText +`_ as its markup and is built using +`Sphinx `_. + + +Sphinx +------ + +More to come... + + +reStructuredText +---------------- + +More to come... diff --git a/akka-docs/dev/index.rst b/akka-docs/dev/index.rst new file mode 100644 index 0000000000..05ab53742d --- /dev/null +++ b/akka-docs/dev/index.rst @@ -0,0 +1,7 @@ +Information for Developers +========================== + +.. toctree:: + :maxdepth: 2 + + documentation diff --git a/akka-docs/index.rst b/akka-docs/index.rst index 9a57f097e2..9e03dd91b7 100644 --- a/akka-docs/index.rst +++ b/akka-docs/index.rst @@ -6,7 +6,7 @@ Contents intro/index scala/index - + dev/index Links ===== From 46b646842a0e24e1a3386a0aa9775ecf576cce91 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 20 Apr 2011 10:29:11 +0200 Subject: [PATCH 116/147] Two birds with one stone, fixing #791 and #792 --- project/build/AkkaProject.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index 319883e1a6..e3eeac4fb2 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -159,8 +159,8 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val sjson = "net.debasishg" % "sjson_2.9.0.RC1" % "0.11" % "compile" //ApacheV2 lazy val sjson_test = "net.debasishg" % "sjson_2.9.0.RC1" % "0.11" % "test" //ApacheV2 - lazy val slf4j = "org.slf4j" % "slf4j-api" % "1.6.0" - lazy val logback = "ch.qos.logback" % "logback-classic" % "0.9.24" + lazy val slf4j = "org.slf4j" % "slf4j-api" % SLF4J_VERSION + lazy val logback = "ch.qos.logback" % "logback-classic" % "0.9.28" % "runtime" // Test From e18127d966b93a60b8c99d16c401b0324a8c1d07 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 20 Apr 2011 11:08:54 +0200 Subject: [PATCH 117/147] Adding Devoxx talk to docs --- akka-docs/pending/articles.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/akka-docs/pending/articles.rst b/akka-docs/pending/articles.rst index 2b2e4b1b8b..06f01f9a7d 100644 --- a/akka-docs/pending/articles.rst +++ b/akka-docs/pending/articles.rst @@ -16,6 +16,8 @@ Videos `Akka talk at Scala Days - March 2010 `_ +`Devoxx 2010 talk "Akka: Simpler Scalability, Fault-Tolerance, Concurrency" by Viktor Klang `_ + Articles -------- From a18471520d75d2a1b32afe9eaa764c837c734307 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 20 Apr 2011 12:03:54 +0200 Subject: [PATCH 118/147] Updating most of the migration docs, added a _general_ section of the docs --- akka-docs/general/index.rst | 7 + .../migration-guide-0.7.x-0.8.x.rst | 0 .../migration-guide-0.8.x-0.9.x.rst | 11 +- .../migration-guide-0.9.x-0.10.x.rst | 0 .../general/migration-guide-1.0.x-1.1.x.rst | 37 ++++ .../{pending => general}/migration-guides.rst | 2 +- akka-docs/index.rst | 1 + akka-docs/pending/Migration-1.0-1.1.rst | 32 ---- akka-docs/pending/cluster-membership.rst | 89 --------- .../scala/migration-guide-0.7.x-0.8.x.rst | 94 ++++++++++ .../scala/migration-guide-0.8.x-0.9.x.rst | 170 ++++++++++++++++++ .../scala/migration-guide-0.9.x-0.10.x.rst | 45 +++++ .../scala/migration-guide-1.0.x-1.1.x.rst | 37 ++++ akka-docs/scala/migration-guides.rst | 8 + 14 files changed, 406 insertions(+), 127 deletions(-) create mode 100644 akka-docs/general/index.rst rename akka-docs/{pending => general}/migration-guide-0.7.x-0.8.x.rst (100%) rename akka-docs/{pending => general}/migration-guide-0.8.x-0.9.x.rst (97%) rename akka-docs/{pending => general}/migration-guide-0.9.x-0.10.x.rst (100%) create mode 100644 akka-docs/general/migration-guide-1.0.x-1.1.x.rst rename akka-docs/{pending => general}/migration-guides.rst (85%) delete mode 100644 akka-docs/pending/Migration-1.0-1.1.rst delete mode 100644 akka-docs/pending/cluster-membership.rst create mode 100644 akka-docs/scala/migration-guide-0.7.x-0.8.x.rst create mode 100644 akka-docs/scala/migration-guide-0.8.x-0.9.x.rst create mode 100644 akka-docs/scala/migration-guide-0.9.x-0.10.x.rst create mode 100644 akka-docs/scala/migration-guide-1.0.x-1.1.x.rst create mode 100644 akka-docs/scala/migration-guides.rst diff --git a/akka-docs/general/index.rst b/akka-docs/general/index.rst new file mode 100644 index 0000000000..a9727dfee3 --- /dev/null +++ b/akka-docs/general/index.rst @@ -0,0 +1,7 @@ +General +======= + +.. toctree:: + :maxdepth: 2 + + migration-guides diff --git a/akka-docs/pending/migration-guide-0.7.x-0.8.x.rst b/akka-docs/general/migration-guide-0.7.x-0.8.x.rst similarity index 100% rename from akka-docs/pending/migration-guide-0.7.x-0.8.x.rst rename to akka-docs/general/migration-guide-0.7.x-0.8.x.rst diff --git a/akka-docs/pending/migration-guide-0.8.x-0.9.x.rst b/akka-docs/general/migration-guide-0.8.x-0.9.x.rst similarity index 97% rename from akka-docs/pending/migration-guide-0.8.x-0.9.x.rst rename to akka-docs/general/migration-guide-0.8.x-0.9.x.rst index 81866e1993..359cb01602 100644 --- a/akka-docs/pending/migration-guide-0.8.x-0.9.x.rst +++ b/akka-docs/general/migration-guide-0.8.x-0.9.x.rst @@ -133,7 +133,7 @@ If you are also using Protobuf then you can use the methods that work with Proto val actorRef2 = ActorRef.fromProtocol(protobufMessage) - Camel +Camel ====== Some methods of the se.scalablesolutions.akka.camel.Message class have been deprecated in 0.9. These are @@ -163,7 +163,8 @@ They will be removed in 1.0. Instead use } Usage example: -``_ -val m = Message(1.4) -val b = m.bodyAs[String] -``_ +.. code-block:: scala + + val m = Message(1.4) + val b = m.bodyAs[String] + diff --git a/akka-docs/pending/migration-guide-0.9.x-0.10.x.rst b/akka-docs/general/migration-guide-0.9.x-0.10.x.rst similarity index 100% rename from akka-docs/pending/migration-guide-0.9.x-0.10.x.rst rename to akka-docs/general/migration-guide-0.9.x-0.10.x.rst diff --git a/akka-docs/general/migration-guide-1.0.x-1.1.x.rst b/akka-docs/general/migration-guide-1.0.x-1.1.x.rst new file mode 100644 index 0000000000..c32b2545ac --- /dev/null +++ b/akka-docs/general/migration-guide-1.0.x-1.1.x.rst @@ -0,0 +1,37 @@ +Akka has now moved to Scala 2.9.x +^^^^^^^^^^^^^^^^^^^^ + +Akka HTTP +========= + +# akka.servlet.Initializer has been moved to ``akka-kernel`` to be able to have ``akka-http`` not depend on ``akka-remote``, if you don't want to use the class for kernel, just create your own version of ``akka.servlet.Initializer``, it's just a couple of lines of code and there is instructions here: `Akka Http Docs `_ +# akka.http.ListWriter has been removed in full, if you use it and want to keep using it, here's the code: `ListWriter `_ +# Jersey-server is now a "provided" dependency for ``akka-http``, so you'll need to add the dependency to your project, it's built against Jersey 1.3 + +Akka Actor +========== + +# is now dependency free, with the exception of the dependency on the ``scala-library.jar`` +# does not bundle any logging anymore, but you can subscribe to events within Akka by registering an event handler on akka.aevent.EventHandler or by specifying the ``FQN`` of an Actor in the akka.conf under akka.event-handlers; there is an ``akka-slf4j`` module which still provides the Logging trait and a default ``SLF4J`` logger adapter. +Don't forget to add a SLF4J backend though, we recommend: + +.. code-block:: scala + lazy val logback = "ch.qos.logback" % "logback-classic" % "0.9.28" + +# If you used HawtDispatcher and want to continue using it, you need to include akka-dispatcher-extras.jar from Akka Modules, in your akka.conf you need to specify: ``akka.dispatch.HawtDispatcherConfigurator`` instead of ``HawtDispatcher`` +# FSM: the onTransition method changed from Function1 to PartialFunction; there is an implicit conversion for the precise types in place, but it may be necessary to add an underscore if you are passing an eta-expansion (using a method as function value). + +Akka Typed Actor +================ + +All methods starting with 'get*' are deprecated and will be removed in post 1.1 release. + +Akka Remote +=========== + +# ``UnparsebleException`` has been renamed to ``CannotInstantiateRemoteExceptionDueToRemoteProtocolParsingErrorException(exception, classname, message)`` + +Akka Testkit +============ + +The TestKit moved into the akka-testkit subproject and correspondingly into the ``akka.testkit` package. diff --git a/akka-docs/pending/migration-guides.rst b/akka-docs/general/migration-guides.rst similarity index 85% rename from akka-docs/pending/migration-guides.rst rename to akka-docs/general/migration-guides.rst index 4c44977d2f..361f8e3c7a 100644 --- a/akka-docs/pending/migration-guides.rst +++ b/akka-docs/general/migration-guides.rst @@ -5,4 +5,4 @@ Here are migration guides for the latest releases * `Migrate 0.8.x -> 0.9.x `_ * `Migrate 0.9.x -> 0.10.x `_ * `Migrate 0.10.x -> 1.0.x `_ -* `Migrate 1.0.x -> 1.1.x `_ +* `Migrate 1.0.x -> 1.1.x `_ diff --git a/akka-docs/index.rst b/akka-docs/index.rst index 9e03dd91b7..fbb2506fab 100644 --- a/akka-docs/index.rst +++ b/akka-docs/index.rst @@ -5,6 +5,7 @@ Contents :maxdepth: 2 intro/index + general/index scala/index dev/index diff --git a/akka-docs/pending/Migration-1.0-1.1.rst b/akka-docs/pending/Migration-1.0-1.1.rst deleted file mode 100644 index b9f88bf4fc..0000000000 --- a/akka-docs/pending/Migration-1.0-1.1.rst +++ /dev/null @@ -1,32 +0,0 @@ -Moved to Scala 2.9.x -^^^^^^^^^^^^^^^^^^^^ - -Akka HTTP -========= - -# akka.servlet.Initializer has been moved to akka-kernel to be able to have akka-http not depend on akka-remote, if you don't want to use the class for kernel, just create your own version of akka.servlet.Initializer, it's just a couple of lines of code and there is instructions here: `Akka Http Docs `_ -# akka.http.ListWriter has been removed in full, if you use it and want to keep using it, here's the code: `ListWriter `_ -# Jersey-server is now a "provided" dependency for Akka-http, so you'll need to add the dependency to your project, it's built against Jersey 1.3 - -Akka Actor -========== - -# is now dependency free, with the exception of the dependency on the scala-library.jar -# does not bundle any logging anymore, but you can subscribe to events within Akka by registering an event handler on akka.aevent.EventHandler or by specifying the FQN of an Actor in the akka.conf under akka.event-handlers; there is an akka-slf4j module which still provides the Logging trait and a default SLF4J logger adapter. -# If you used HawtDispatcher and want to continue using it, you need to include akka-dispatcher-extras.jar from Akka Modules, in your akka.conf you need to specify: "akka.dispatch.HawtDispatcherConfigurator" instead of "HawtDispatcher" -# FSM: the onTransition method changed from Function1 to PartialFunction; there is an implicit conversion for the precise types in place, but it may be necessary to add an underscore if you are passing an eta-expansion (using a method as function value). - -Akka Typed Actor -================ - -All methods starting with 'get*' are deprecated and will be removed in post 1.1 release. - -Akka Remote -=========== - -# UnparsebleException => CannotInstantiateRemoteExceptionDueToRemoteProtocolParsingErrorException(exception, classname, message) - -Akka Testkit -============ - -The TestKit moved into the akka-testkit subproject and correspondingly into the akka.testkit package. diff --git a/akka-docs/pending/cluster-membership.rst b/akka-docs/pending/cluster-membership.rst deleted file mode 100644 index 6aa70e8bce..0000000000 --- a/akka-docs/pending/cluster-membership.rst +++ /dev/null @@ -1,89 +0,0 @@ -Cluster Membership (Scala) -========================== - -Module stability: **IN PROGRESS** - -Akka supports a Cluster Membership through a `JGroups `_ based implementation. JGroups is is a `P2P `_ clustering API - -Configuration -------------- - -The cluster is configured in 'akka.conf' by adding the Fully Qualified Name (FQN) of the actor class and serializer: - -.. code-block:: ruby - - remote { - cluster { - service = on - name = "default" # The name of the cluster - serializer = "akka.serialization.Serializer$Java" # FQN of the serializer class - } - } - -How to join the cluster ------------------------ - -The node joins the cluster when the 'RemoteNode' and/or 'RemoteServer' servers are started. - -Cluster API ------------ - -Interaction with the cluster is done through the 'akka.remote.Cluster' object. - -To send a message to all actors of a specific type on other nodes in the cluster use the 'relayMessage' function: - -.. code-block:: scala - - def relayMessage(to: Class[_ <: Actor], msg: AnyRef): Unit - -Here is an example: - -.. code-block:: scala - - Cluster.relayMessage(classOf[ATypeOfActor], message) - -Traversing the remote nodes in the cluster to spawn remote actors: - -Cluster.foreach: - -.. code-block:: scala - - def foreach(f : (RemoteAddress) => Unit) : Unit - -Here's an example: - -.. code-block:: scala - - for(endpoint <- Cluster) spawnRemote[KungFuActor](endpoint.hostname,endpoint.port) - -and: - -.. code-block:: scala - - Cluster.foreach( endpoint => spawnRemote[KungFuActor](endpoint.hostname,endpoint.port) ) - -Cluster.lookup: - -.. code-block:: scala - - def lookup[T](handleRemoteAddress : PartialFunction[RemoteAddress, T]) : Option[T] - -Here is an example: - -.. code-block:: scala - - val myRemoteActor: Option[SomeActorType] = Cluster.lookup({ - case RemoteAddress(hostname, port) => spawnRemote[SomeActorType](hostname, port) - }) - - myRemoteActor.foreach(remoteActor => ...) - -Here is another example: - -.. code-block:: scala - Cluster.lookup({ - case remoteAddress @ RemoteAddress(_,_) => remoteAddress - }) match { - case Some(remoteAddress) => spawnAllRemoteActors(remoteAddress) - case None => handleNoRemoteNodeFound - } diff --git a/akka-docs/scala/migration-guide-0.7.x-0.8.x.rst b/akka-docs/scala/migration-guide-0.7.x-0.8.x.rst new file mode 100644 index 0000000000..5c45eb76c1 --- /dev/null +++ b/akka-docs/scala/migration-guide-0.7.x-0.8.x.rst @@ -0,0 +1,94 @@ +Migrate from 0.7.x to 0.8.x +=========================== + +This is a case-by-case migration guide from Akka 0.7.x (on Scala 2.7.7) to Akka 0.8.x (on Scala 2.8.x) +------------------------------------------------------------------------------------------------------ + +Cases: +====== + +Actor.send is removed and replaced in full with Actor.! +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + myActor send "test" + +becomes + +.. code-block:: scala + + myActor ! "test" + +Actor.! now has it's implicit sender defaulted to None +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + def !(message: Any)(implicit sender: Option[Actor] = None) + +"import Actor.Sender.Self" has been removed because it's not needed anymore +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Remove + +.. code-block:: scala + + import Actor.Sender.Self + +Actor.spawn now uses manifests instead of concrete class types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + val someActor = spawn(classOf[MyActor]) + +becomes + +.. code-block:: scala + + val someActor = spawn[MyActor] + +Actor.spawnRemote now uses manifests instead of concrete class types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + val someActor = spawnRemote(classOf[MyActor],"somehost",1337) + +becomes + +.. code-block:: scala + + val someActor = spawnRemote[MyActor]("somehost",1337) + +Actor.spawnLink now uses manifests instead of concrete class types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + val someActor = spawnLink(classOf[MyActor]) + +becomes + +.. code-block:: scala + + val someActor = spawnLink[MyActor] + +Actor.spawnLinkRemote now uses manifests instead of concrete class types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: scala + + val someActor = spawnLinkRemote(classOf[MyActor],"somehost",1337) + +becomes + +.. code-block:: scala + + val someActor = spawnLinkRemote[MyActor]("somehost",1337) + +**Transaction.atomic and friends are moved into Transaction.Local._ and Transaction.Global._** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We now make a difference between transaction management that are local within a thread and global across many threads (and actors). diff --git a/akka-docs/scala/migration-guide-0.8.x-0.9.x.rst b/akka-docs/scala/migration-guide-0.8.x-0.9.x.rst new file mode 100644 index 0000000000..359cb01602 --- /dev/null +++ b/akka-docs/scala/migration-guide-0.8.x-0.9.x.rst @@ -0,0 +1,170 @@ +**This document describes between the 0.8.x and the 0.9 release.** + +Background for the new ActorRef +=============================== + +In the work towards 0.9 release we have now done a major change to how Actors are created. In short we have separated identity and value, created an 'ActorRef' that holds the actual Actor instance. This allows us to do many great things such as for example: + +* Create serializable, immutable, network-aware Actor references that can be freely shared across the network. They "remember" their origin and will always work as expected. +* Not only kill and restart the same supervised Actor instance when it has crashed (as we do now), but dereference it, throw it away and make it eligible for garbage collection. +* etc. much more + +These work very much like the 'PID' (process id) in Erlang. + +These changes means that there is no difference in defining Actors. You still use the old Actor trait, all methods are there etc. But you can't just new this Actor up and send messages to it since all its public API methods are gone. They now reside in a new class; 'ActorRef' and use need to use instances of this class to interact with the Actor (sending messages etc.). + +Here is a short migration guide with the things that you have to change. It is a big conceptual change but in practice you don't have to change much. + +Migration Guide +=============== + +Creating Actors with default constructor +---------------------------------------- + +From: + +.. code-block:: scala + + val a = new MyActor + a ! msg + +To: + +.. code-block:: scala + + import Actor._ + val a = actorOf[MyActor] + a ! msg + +You can also start it in the same statement: + +.. code-block:: scala + + val a = actorOf[MyActor].start + +Creating Actors with non-default constructor +-------------------------------------------- + +From: + +.. code-block:: scala + + val a = new MyActor(..) + a ! msg + +To: + +.. code-block:: scala + + import Actor._ + val a = actorOf(new MyActor(..)) + a ! msg + +Use of 'self' ActorRef API +-------------------------- + +Where you have used 'this' to refer to the Actor from within itself now use 'self': + +.. code-block:: scala + + self ! MessageToMe + +Now the Actor trait only has the callbacks you can implement: +* receive +* postRestart/preRestart +* init/shutdown + +It has no state at all. + +All API has been moved to ActorRef. The Actor is given its ActorRef through the 'self' member variable. +Here you find functions like: +* !, !!, !!! and forward +* link, unlink, startLink, spawnLink etc +* makeTransactional, makeRemote etc. +* start, stop +* etc. + +Here you also find fields like +* dispatcher = ... +* id = ... +* lifeCycle = ... +* faultHandler = ... +* trapExit = ... +* etc. + +This means that to use them you have to prefix them with 'self', like this: + +.. code-block:: scala + + self ! Message + +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 = ... + spawnLink[OtherActor] + ... + } + +Serialization +============= + +If you want to serialize it yourself, here is how to do it: + +.. code-block:: scala + + val actorRef1 = actorOf[MyActor] + + val bytes = actorRef1.toBinary + + val actorRef2 = ActorRef.fromBinary(bytes) + +If you are also using Protobuf then you can use the methods that work with Protobuf's Messages directly. + +.. code-block:: scala + + val actorRef1 = actorOf[MyActor] + + val protobufMessage = actorRef1.toProtocol + + val actorRef2 = ActorRef.fromProtocol(protobufMessage) + +Camel +====== + +Some methods of the se.scalablesolutions.akka.camel.Message class have been deprecated in 0.9. These are + +.. code-block:: scala + + package se.scalablesolutions.akka.camel + + case class Message(...) { + // ... + @deprecated def bodyAs[T](clazz: Class[T]): T + @deprecated def setBodyAs[T](clazz: Class[T]): Message + // ... + } + +They will be removed in 1.0. Instead use + +.. code-block:: scala + + package se.scalablesolutions.akka.camel + + case class Message(...) { + // ... + def bodyAs[T](implicit m: Manifest[T]): T = + def setBodyAs[T](implicit m: Manifest[T]): Message + // ... + } + +Usage example: +.. code-block:: scala + + val m = Message(1.4) + val b = m.bodyAs[String] + diff --git a/akka-docs/scala/migration-guide-0.9.x-0.10.x.rst b/akka-docs/scala/migration-guide-0.9.x-0.10.x.rst new file mode 100644 index 0000000000..68ec0cb087 --- /dev/null +++ b/akka-docs/scala/migration-guide-0.9.x-0.10.x.rst @@ -0,0 +1,45 @@ +Migration Guide from Akka 0.9.x to Akka 0.10.x +============================================== + +Module akka-camel +----------------- + +The following list summarizes the breaking changes since Akka 0.9.1. + +* CamelService moved from package se.scalablesolutions.akka.camel.service one level up to se.scalablesolutions.akka.camel. +* CamelService.newInstance removed. For starting and stopping a CamelService, applications should use +** CamelServiceManager.startCamelService and +** CamelServiceManager.stopCamelService. +* Existing def receive = produce method definitions from Producer implementations must be removed (resolves compile error: method receive needs override modifier). +* The Producer.async method and the related Sync trait have been removed. This is now fully covered by Camel's `asynchronous routing engine `_. +* @consume annotation can not placed any longer on actors (i.e. on type-level), only on typed actor methods. Consumer actors must mixin the Consumer trait. +* @consume annotation moved to package se.scalablesolutions.akka.camel. + +Logging +------- + +We've switched to Logback (SLF4J compatible) for the logging, if you're having trouble seeing your log output you'll need to make sure that there's a logback.xml available on the classpath or you'll need to specify the location of the logback.xml file via the system property, ex: -Dlogback.configurationFile=/path/to/logback.xml + +Configuration +------------- + +* The configuration is now JSON-style (see below). +* Now you can define the time-unit to be used throughout the config file: + +.. code-block:: ruby + + akka { + version = "0.10" + time-unit = "seconds" # default timeout time unit for all timeout properties throughout the config + + actor { + timeout = 5 # default timeout for future based invocations + throughput = 5 # default throughput for ExecutorBasedEventDrivenDispatcher + } + ... + } + +RemoteClient events +------------------- + +All events now has a reference to the RemoteClient instance instead of 'hostname' and 'port'. This is more flexible. Enables simpler reconnecting etc. diff --git a/akka-docs/scala/migration-guide-1.0.x-1.1.x.rst b/akka-docs/scala/migration-guide-1.0.x-1.1.x.rst new file mode 100644 index 0000000000..c32b2545ac --- /dev/null +++ b/akka-docs/scala/migration-guide-1.0.x-1.1.x.rst @@ -0,0 +1,37 @@ +Akka has now moved to Scala 2.9.x +^^^^^^^^^^^^^^^^^^^^ + +Akka HTTP +========= + +# akka.servlet.Initializer has been moved to ``akka-kernel`` to be able to have ``akka-http`` not depend on ``akka-remote``, if you don't want to use the class for kernel, just create your own version of ``akka.servlet.Initializer``, it's just a couple of lines of code and there is instructions here: `Akka Http Docs `_ +# akka.http.ListWriter has been removed in full, if you use it and want to keep using it, here's the code: `ListWriter `_ +# Jersey-server is now a "provided" dependency for ``akka-http``, so you'll need to add the dependency to your project, it's built against Jersey 1.3 + +Akka Actor +========== + +# is now dependency free, with the exception of the dependency on the ``scala-library.jar`` +# does not bundle any logging anymore, but you can subscribe to events within Akka by registering an event handler on akka.aevent.EventHandler or by specifying the ``FQN`` of an Actor in the akka.conf under akka.event-handlers; there is an ``akka-slf4j`` module which still provides the Logging trait and a default ``SLF4J`` logger adapter. +Don't forget to add a SLF4J backend though, we recommend: + +.. code-block:: scala + lazy val logback = "ch.qos.logback" % "logback-classic" % "0.9.28" + +# If you used HawtDispatcher and want to continue using it, you need to include akka-dispatcher-extras.jar from Akka Modules, in your akka.conf you need to specify: ``akka.dispatch.HawtDispatcherConfigurator`` instead of ``HawtDispatcher`` +# FSM: the onTransition method changed from Function1 to PartialFunction; there is an implicit conversion for the precise types in place, but it may be necessary to add an underscore if you are passing an eta-expansion (using a method as function value). + +Akka Typed Actor +================ + +All methods starting with 'get*' are deprecated and will be removed in post 1.1 release. + +Akka Remote +=========== + +# ``UnparsebleException`` has been renamed to ``CannotInstantiateRemoteExceptionDueToRemoteProtocolParsingErrorException(exception, classname, message)`` + +Akka Testkit +============ + +The TestKit moved into the akka-testkit subproject and correspondingly into the ``akka.testkit` package. diff --git a/akka-docs/scala/migration-guides.rst b/akka-docs/scala/migration-guides.rst new file mode 100644 index 0000000000..361f8e3c7a --- /dev/null +++ b/akka-docs/scala/migration-guides.rst @@ -0,0 +1,8 @@ +Here are migration guides for the latest releases +================================================= + +* `Migrate 0.7.x -> 0.8.x `_ +* `Migrate 0.8.x -> 0.9.x `_ +* `Migrate 0.9.x -> 0.10.x `_ +* `Migrate 0.10.x -> 1.0.x `_ +* `Migrate 1.0.x -> 1.1.x `_ From 392c947826db2d4d5fa67996377695c9eb2df006 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 20 Apr 2011 12:56:42 +0200 Subject: [PATCH 119/147] Deprecating methods as discussed on ML --- akka-actor/src/main/scala/akka/actor/ActorRef.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 8affac2037..6fa44452e0 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -109,6 +109,7 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal * Defines the default timeout for '!!' and '!!!' invocations, * e.g. the timeout for the future returned by the call to '!!' and '!!!'. */ + @deprecated("Will be replaced by implicit-scoped timeout on all methods that needs it, will default to timeout specified in config") @BeanProperty @volatile var timeout: Long = Actor.TIMEOUT @@ -183,11 +184,13 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal /** * Returns on which node this actor lives if None it lives in the local ActorRegistry */ + @deprecated("Remoting will become fully transparent in the future") def homeAddress: Option[InetSocketAddress] /** * Java API.

*/ + @deprecated("Remoting will become fully transparent in the future") def getHomeAddress(): InetSocketAddress = homeAddress getOrElse null /** @@ -253,6 +256,7 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal /** * Is the actor able to handle the message passed in as arguments? */ + @deprecated("Will be removed without replacement, it's just not reliable in the face of `become` and `unbecome`") def isDefinedAt(message: Any): Boolean = actor.isDefinedAt(message) /** @@ -378,23 +382,27 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal /** * Returns the class for the Actor instance that is managed by the ActorRef. */ + @deprecated("Will be removed without replacement, doesn't make any sense to have in the face of `become` and `unbecome`") def actorClass: Class[_ <: Actor] /** * Akka Java API.

* Returns the class for the Actor instance that is managed by the ActorRef. */ + @deprecated("Will be removed without replacement, doesn't make any sense to have in the face of `become` and `unbecome`") def getActorClass(): Class[_ <: Actor] = actorClass /** * Returns the class name for the Actor instance that is managed by the ActorRef. */ + @deprecated("Will be removed without replacement, doesn't make any sense to have in the face of `become` and `unbecome`") def actorClassName: String /** * Akka Java API.

* Returns the class name for the Actor instance that is managed by the ActorRef. */ + @deprecated("Will be removed without replacement, doesn't make any sense to have in the face of `become` and `unbecome`") def getActorClassName(): String = actorClassName /** @@ -636,11 +644,13 @@ class LocalActorRef private[akka] ( /** * Returns the class for the Actor instance that is managed by the ActorRef. */ + @deprecated("Will be removed without replacement, doesn't make any sense to have in the face of `become` and `unbecome`") def actorClass: Class[_ <: Actor] = actor.getClass.asInstanceOf[Class[_ <: Actor]] /** * Returns the class name for the Actor instance that is managed by the ActorRef. */ + @deprecated("Will be removed without replacement, doesn't make any sense to have in the face of `become` and `unbecome`") def actorClassName: String = actorClass.getName /** @@ -1174,6 +1184,7 @@ private[akka] case class RemoteActorRef private[akka] ( protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = None // ==== NOT SUPPORTED ==== + @deprecated("Will be removed without replacement, doesn't make any sense to have in the face of `become` and `unbecome`") def actorClass: Class[_ <: Actor] = unsupported def dispatcher_=(md: MessageDispatcher): Unit = unsupported def dispatcher: MessageDispatcher = unsupported From eb9f1f439623500e57ad3a4e693c9b1ba7a64238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Wed, 20 Apr 2011 18:36:49 +0200 Subject: [PATCH 120/147] Added Iulian's Akka Eclipse tutorial with some minor edits of Jonas --- akka-docs/intro/build-path.png | Bin 0 -> 121809 bytes akka-docs/intro/diagnostics-window.png | Bin 0 -> 65878 bytes akka-docs/intro/example-code.png | Bin 0 -> 54274 bytes .../getting-started-first-scala-eclipse.rst | 419 ++++++++++++++++++ akka-docs/intro/import-project.png | Bin 0 -> 85573 bytes akka-docs/intro/index.rst | 1 + akka-docs/intro/install-beta2-updatesite.png | Bin 0 -> 136664 bytes akka-docs/intro/quickfix.png | Bin 0 -> 28896 bytes akka-docs/intro/run-config.png | Bin 0 -> 126906 bytes 9 files changed, 420 insertions(+) create mode 100644 akka-docs/intro/build-path.png create mode 100644 akka-docs/intro/diagnostics-window.png create mode 100644 akka-docs/intro/example-code.png create mode 100644 akka-docs/intro/getting-started-first-scala-eclipse.rst create mode 100644 akka-docs/intro/import-project.png create mode 100644 akka-docs/intro/install-beta2-updatesite.png create mode 100644 akka-docs/intro/quickfix.png create mode 100644 akka-docs/intro/run-config.png diff --git a/akka-docs/intro/build-path.png b/akka-docs/intro/build-path.png new file mode 100644 index 0000000000000000000000000000000000000000..60f469e6d2c7a66152effa92c8393a3d08a005b7 GIT binary patch literal 121809 zcmeAS@N?(olHy`uVBq!ia0y~yV4laoz|_aV#=yXEPVd+W1_m+DOlRi+PiJR^fTH}g z%$!sPh6={35$Pc!XSc1L$6@@$;K`igbC%EN7(27I&r#7<(o*nLVP{q8=4xuv;P5bL zQ0VAV>f%Tf5n!Bn{l%x<-}imJxBvU|t5LCgpNE`%IqT-~UsrB27)%#ZR`TGGVsJW~ zv?!;yWar%2n4KFxGjK*Q$Ub1Sak>A3LpD3R_{08f@B0}`41Rb#I&l5J)!TZx1z$h+ ze`96X)Ye}6XHl(lVvXpr?0tfPh71a?68{+3sWBb%_`$>z_<?G;^uU+@36A|kqdzlGJga}V_Wcs7;)>i+xZ>;H}HEYh?7 zY-K+_y4cPyJ{gC5Bh;3hvzgd-~Z?)&pDl7$ge+Z~B;dpzsfG zgEW6_^5RGTel$BvGMF4-%sRmEKF;p|drKqB1lBGG-VA2u1g;tJ6%-aug>|mE`iZtLCJ81NS;o5=n4cv|bVvAZ{ zD2jD(3pE^3i1d)W)WV}sd_t_KanC{r4W6#nNehz$%tAP(IzL~S_JZpQ^WC;%3pHP8 ze&MQOuWc?9@L*wWVm#O=;Nqb;MbL`FxW)DmvtUPui;9wwz|M{!7u`j?7khW~?r5Lk zs-tA5U^bDV(_LtSjR)rvqagmw))H4Xg~bzAp3q4#+$46h?~QAplDwk$MEQxgHF&z@ zj`mJ+Sh=t#kWV8%L`6$B3!shgZ0-2w!1(Mc+hqmaJ}DnCt8Xb1yc&s3=j} zC2_amozs2C^NXfmyl*JM!>HU6ct|2S#z1j~iVxp1u3}E(hM9+S63sS7=x~SetZv+T zNNA(s4WAszZM?Vp=N{8buAlJmlbY$y2mEp zW8b8IH2zrs!}y2WHD74BXnxVSqnV^xq$#DzrZF#QO>j^US5Q{)uApN9&w|W?`2zI< z{w-Lve9{u5g`XCCEnc*DTiEVS-Cb;#tc}(^S zcD(Ft?7rF4+h5x`+f~|Yw)1T#YroxI?MsE17%%v|`1FF+i=`JuFLJ;1`Qqz~zc0xz zUcY?5^Zqjpi2_Lmi5!V1(srCMIJn`M$8nAGFHTg<*x2+@?XeQ0r+~@~0go0NC-#Si ziS3=up6w^+zZ40!zHD+?%vgG}*=GA~M<%uy*bJk(ixgHU>VNrx@WNyUc z$kmbWH`JvhCj=*dPCcFUI-&YlUxIk*_Ei6*^~w9s?>l+$Wa1IQgMr5kk9?ds`M}Dv zl7}LXRCa%s6}O$P7H$wO7;b;PI?pXnHczNr^Si-!DSqpILw}v~mGd~~#l|VdvEM7X z=erZKK>I7$|0`5~TVmKtyqsf|N>>qLpTwK(Gk6*lMBKLhjwG zJK8(1cb)eP@Oa^I#B+<+o=GYmv%Hsi$4&mDb5#GSR;Yrh)>rQ+$5|?|`nLMc>d9J@ z^)73F_6-ZU7QW4?%&jlzT!5bMyIps}D-A2_e`ftu=la4W#-%R8Ci+ces>ouI+oJb1 zKWbNMhiaYGG}egLey-8KazSWG=%tWfA=^SlLz6?SL-w!mTp=5j9T*)d9`rqYeR%w( z2Uiuc=3I}s^5oi=Yc|Vwt~k2b^~$QNRcp^Kf4ltdg1w8B7YQ%AyzFyic$B%B=ne>8S5F$Gk$3B(KOR6 z(m2$}*HG4MvB73zZS(3}y)3!Zd)bW}47OS%f4mcP!061US)v<^Hd-ZrJrs4q>e8<^ z-ebpZEj#pV?b@E)O>bAo?wvb5w)<{*b?k4O-<9l%?Gf#c?pf~T$J37Ms@E=eTJD;z z`CR0AwcZ-NgL>CvH?Hej$GdKJ9$Q}e-D!74?-<{eujZ(f`6lr_<*Q2Ny)TQtDOHtL zE#5nKPxPL8p|u@bkNC<9&-a>d8gF}F=sthltvc7b_kT?PaQ_WwGGXFk4r4ZDI?u9? z`x2KH-!F+Z;xTf6OpjPBQA>F};p)O;&c9XEFD(t&6!8ASJcC^uoD&Z}ob#~#@N%Ui zN=C|J%Av~V-PXCzbTxHxz9@I`da}fW<}-gI9^QDoas4Bc$6Al-j#wWnms@5lQ}&~- zDdW(}mZ>g>k8pdgP`SV4V9LBFi%+f(DhWEb+-arV%I3_POv%jC?mc}bvPWfYOTIYn zDyd5Rb{gnCy~#qJ{$f_{MpaFg=-d9Cbuy6^m7q&Ug|y4JEe8Y z;&o}+o``)rma&8jMQ@78imTe&vtMWboAkPkuGu~2pYtB(rQW&q-v7;g znGT5`71kfVU0QJc!Ji4AFPzzM_QMg`&DP1vZOYDztrq`HmP)RDyy~%S`PBDuyO&q2 z|Gn?f-*xO<{JYdd)OV>*RaP836Q@rX6BW93Dy%S_)WT?qzX}HA7Q1#%r;uav#dyQ_8;Vo(PsTsorg33PQ%KqsLzCwj>My=YK9Nx7;Pv6wIkVpJ0==3lpG>)wd& z6#6N`x@gh9Nehg^DqT}Uwg$f4v1@%>pW1Y>C12xycb;o`H{)K%zYPZ`Jj`7jeQdAV z-DTltZk>_V$fwQ}n9vUGd9u*%Ij@`EL>*Wxmh)G*5ls z_nTiAe|ydyz+u8!#LdU+X<0HWM7GLsjeU*PqgWB8S;lU<*DUq)_I>zpwB_TGrAzjT zIIVJ-^-b{f;&U(O9p7WTr@uPAYVHS{mxZTvPpjTGJpOJ=>AKK)srzCZqZ2R8JnR~o zzPd|)D$f+ZseASQhjp%4wD#G`iPq^A_x74C)7!r9<)OE2>Am-773$io^~#+l{`Z!! z-rRjP@4VcPzdg5db$4w1uJ7xDzijxzbvNL}<-M7urx(vRnNYMlVMAs{!jF@4^%u`D z>?_>4TkX5edv|*a%RsxEpEgMM?LO#!*tk5+U9SI3uZ?77)$^Ml{(Sg+P<*>MTiiGO zdv&H?s!Cqon|<=>%I*DOX<_O2&hDB2)8KF6tIJQGZ`9wL{d}KVt#Q2in)CS~`BnAL z|L@y$^;7c}-K~FfpTEkxig_>@Bg#^XIk<7MdTUZH?EsjKdIee{UfsL zdq~NZOD6BO9Dix`V!Gr@&NLs-GjDagb4uRql9@OsHKz8Cer4H@j31V)E-b7qH(Ea) zl2-C}sXnT8bh7Flm8(l`EGb(u|Kyib(@v|Nu-4Sjx};ebBo%76e$tXx3)I&2h1IXS zxUw=z+vRqUduVt}fBgKz4HqYz;z<5-x#Dih)sllbxBm3G_NVR+om<-KyD@j-UCG~0 z?M%mQmd8BTneKBvIWO~Fr`*PQ6XPb{+jvA|{^Wf}>%RV*%eb9cz4=*lxU-puf{6Sv z5j}y00+~WjC1s^2OW&3#*Z%kMp?~KJm1`+4MgE2?-*Q0N&|Ua)aR1UY6?y*?J+^|C zMqfe#rsZ!-@B5s2JEq%ab4@qXZ>RUh4_!0Uxx2Ext-RY$$xYP{ty$f)Dl@C%+{wA- z>%OkCj)|{WxHfUg$JERHVz0u7{>OUh`*Q2dbC=}YsuTaQZ*S>;_pfD7`tIo^mVZq@ANNiFd+o{p$NnGu zSKl`C2;Zc;4L|1}(+|!GE)34TG$CX4i(@a!B~v*YeMHX$ugNeA{Z`N?b&fq|wvEi5 zsG4(sR2o?hE_QNn5q9-h^e$=HqfxPL-HqHuZVP zy4BNG99?Ohbt2m+BQNW0cK_=yFXOK6UUEO}LP|t>NODYGP)gM;oy#^ym5xnHd7Az< z_i@_mwC7pxUp;vA;>nX2Iq#DmXT3~&weJ1Gw=175eY4i-$_K*_TfY^5{m#$9@5AH8 ze~!l# zXF+Sq(wM1UThn^~S$_W9x7Dsx@N1^{C#h9Z(X*>&yGN%*$8WFQx1*S|c;+{*X!iK! zFG6SPEzSFyZFQZu`b^c@yc=IF@0IL)dh@-x!>-zj4@UxS7TliOIPvAhryJ$V3%}oZ z?`?Bo&W0H!wJ+>Ntz^Gyl&R&Jy(_#Eb7%66<%Z`MJyV|VzK0{8qh4~O#AmISUq3Ew zGQWB{H@`Z}?cw(WtG~~;dsB0$X4%g}dyaja`d0d|)y-euj=g&J=KI36rwdq8-XD4Y z>+hC(!T(kF_}3r!FE9HC|*|3+(p~aA) zr9{@j!|2asdxZw3Ck<={hqES11Ta+{V5?{>n80>@%GL$kJ5yYcoLlPVUwtXQ(=$-FB&N;-WOtm%@OdS_G3ey6FA z1eYeHri*fiCYw5!D)SoqO6!`xb(m{$cXHL#Uvt^M^GxrloAJQ+=Gh}>O?B4j{9Q9? zZFjC!?((;0GBaei&S9EU9J?!4{LV!=p?Q9BlK18wNsSk?&;GxrVWL9F21k!;3s(kw z+u;0gyOQK0kBf^p{wb9FsLlDTbMDDGnm1QkyF2ySS*Oh^v<-f6WM=S*H80iUQCezK-%NQL))D3xraphN)&8LJe@)@a;g?@ed$#RG+1k@r!sE)z%Adw4 zf77_T;7#;CQ-gv$gX9?|kBri4!r$(D({jUkW9CuGL{(85LF<0zbVG;<6@@3w=3i`YD)xv>AHyqH&phU zw%j~Ep>=}q%35K5vbCNQ5MqBQr@L*rdJo%q@k&~liN66&3V$*iOFrN*Ryl2?Yimp@D!hx z)heSbrB_W-O0zCSW!+M<(%Yr7H~H68w(Gpld$#%B@VR>S&Y8zLpLC?xvaOkaGv}t@ zo2jyQWcw|*TG|&L-J!XQy+Wk=-p5-CpMKx?J@3Qo&++_IIJo%oxGLQzarE--mTc@U z>CNgtctq$i8{cOY8UH^s-b^r>p;o)ZY;J+hZ;L6C<&W-aUSIQ3X6a?#N}r$0x$5S2 zYDYEC{2w3|wJ&aFkZ(^SyV?KTk#nf9?O2?tkW~UGDt|9&p- zF#VLzJNsC4eA%h*KL4BR(`M8cF8DLMUa5oSziL}UbA6dT5v*i%K4`kozO<2}*8k%@jSK`{vp&oSu?0_d82aX%Lg|;KK^m$&}5zEt2XZ}KKH!n`CRP>x;Z+NY^wLV z?Eey2>2NeJG_d!~&Y8EaRGs9`mwr8)Ia>ABwp;D`;-xHcS6`&<*>>&o1Gm#>&(y8I z-Q~RFVe7=cjg!l3-(~x3@c%cf>TgEPw&Hc)GEe@wDgHeE+uo|*ZGYFE&N})#eEyMr zGXJdh9sFtg@A#Q@f8t&;{=djrw2A#DvxCU44S#lQ*uP(ogJGLM!``O9fA8M<`**L^ zjvEzx2N+)VZ>uwqbY^yVdWTVJt#w_&@BVM`QUQ(*mv*%UF)%Q27I;J!Gca%qgD@k* ztT_@43AX>k`v zghtZAbZ2`xjuj@XOI9~<{cvE_^wQ^4b&z^_^UB|IGw<%6nNwyi?Xlwdp(ppM^ZuWG z^X|{{dov3SrFsR_Sy~zd1ely0I5-#;6`-62dpLGX3qKmP9zzDCOikfXa33>VF-Qo) z(FkW!vNFs|!}0 zZB5ra`fO^)X>HAF-hSiEC@dDHa(gE|D!G}h>$JY}gHG#>5;q;I$K%dRFh<3Ic`RNc9nX%s5gT zPxWlP1?Fx$vOstCt&2-Ni>HKNRo{n^A|6yJCFlyRef(?RG_$jL|2~P$f2wA4+N0o> zaDUZqyEXSYe{C?X{PI&(2QAzh1a?HRyy0?+W&cw7B{5cPHox?ysn@6Zu3o-}C1WSY z`JgVp=tUQ9Eqiq8`r*(-_sluVmz0F||3D8Uj>grl2i9?zlphG(2BOCuk`tI?dG36Ey{FH$zH5bxP`=v3PIaOdrQ;bq@KXKji+J~g%mlEIO(m_w!T2C<@Rg?aHe zr>y_AeJ zUtfM6`f$>vP`i(<7-i2=)dei`_ZD4yDYHHG@u#ZYcJY~Sx4lZ5UG(dh$?d5f{^giS=Y3%736ZQtf|>r&?FrM^pFeifK$dOY*gxx_M=?V{I&PJcR|dcMNS@b*f! zqklsa-S^q~w%V@DX*IuBvAFX0x3}`^qPAwK`}?0SSQjt{Bgh|wsZN+Xw^mC}FHd`C z?_@RKU)>femyS72nJcsa6u_si-(GWAX8)Vtwx@$Cv^JjjyHULCsmtcz${XAHR#$wO zoxiU#etX{CV>@>5-hSrnSyxBLMlF3Txo?`vgIBM(`1$#tJbn82=il%5&ntcWY0oq< zr)^=&@>yp?3jM;&_+B5KdM{7E=H{W#o2K2mHS_J0zRkglF3#K_G=dRqMdGW+7FMS=&RZBO< z&R@R7VESf@YfaOO*FR6?^!Hxf%BU*Ct?TOS<*_OBX{cFt{L{qU=>Y)&>hoTTV5@dq zIX5t@4Or;pzNyY*_N6E-VgEOpi7Rttp4Xg8-(4!VO>_CDRX<%XI3=#6_-<0m6hLItJW)5L^jRtD!6&zfWwt_vC{wk{fnx# zxf&ZZch!8)m7F&!Q@H(NEqdPk(UIWp`S9t((YFhE3-b^2t&oVgxw7t-Y=a6?4GOAW zj8qOh-}UB)%uXHI>(SRr<2?VL+GRgIc)nKh+uPFO&z1iMbziDf*IXERsV^z@aFfSR zku5D}R~?^pE>latUH|mdUvmCJ;n%u+<--&AuIDdSw|w^|tor%6x%F03lIPn#8QlrC zE>HZwX(`8t@6Jmntm5EXy=KQyld@dJ=+$(G)(?`@%zrXH#Vem z{$ctXurx|EgpbcL&e2XtKi1_?YemYE^u^hq_wCqxZm;UM%X!I@pzVh6P6xBTH+LG7 zIcIXLDrAqkFUj|NKTGP(r8RSGix?#9N-r#MbJ*BrEbRB-g{!_vZqQ?`ugfFEkHECg zWYFEdclInBS7~ndbn|yhg=Vel+1mT~O^99ak&AB@Ok4b7zV_UYA&Vwuhk`<6wM~w8 z=H@dM(@I!d(yjd8-fFEE(?6D5SC=6n)b)$swq;g-8LNlGytv(B?sY!|-II#9*xV_a z`}^JFtrzad&(>2{XMcWvzI#rNj+3*qppa0Lmf}=F8JRVuHkOvGg&#k5^mkwW^dYn@ z>*}h^%~Q;@Rg}LMzu!1F;`4E*5Dspav$sMo?y5`QlJ`Ym(2$kHEDhtX5^akz6cD3yF<}xi0-)2wso%$^5+u3@~!~1XaR$JX^zJHnLN1~ta+q?0pS3Pb`l>cz= z&;N(}4(dN#$X3;udEi`nTjIADl|RdnFPsqLaIgWi^I%lx&guK2&d{U%hi3{*)S2vvK~w54|ShLcPA-L`Wbv!DI! z(c@6>j~}-g-uO^f{v!2a=(`!~cGk?fv5!-odDi0M&8x(!Z>{sa5n9R6HtnU*MxQx% zw@dTJNB16Omb!RrSrhucs#vr(Xpj_e|+i=jreu6e8%Q7IVYj| z4-Qv|V%J@&wKMz0>zv6)oTX-a$Y&bZ@syLI*eJ*)1X!ayA^|QEaCV zUH*M0b4S{eL(Ny_w6}`~6>mD2s`d3%=Js1IyTZ359qqc6vs`F$W#vzUl+^6hA922A z_wL<`lH)6$*}(97dF*cf{Px-H+J|O`EKJ=U_u)&CW0#@W@u1HW;wP{-R}0lN+dg6e6950sx0w7* zLn=l*IAN!S$-mq)6{(C?cNW&!@3~NYHjw9p=fPH$y5nV$JH0!8%5r4QU%7UY=ISWj zuPW~kKKlCX#;>h6?7ukP5nU7X*|DE}K{|`bg-3FxepB7J>wG$+qCQ?ZRI|;=?QQyQ z;YD$By;gF05+6>mzc6`YJjZf=-|t^K$1bK93DnzhE{k`HHtq0*H2>BKGR9pxY1MU9 zq1%sd^Z8$=B?V7Cwa~W~P22BtXDWyPRn4cX`3hrhuBkn@_|n^-JRY3>((I%{8=6!WTg zxAdKr_B+>kY803rx_+N`-cQCa3zpRU`1MS2v!cNCid%_ufBk~AtTpPHSeKoiE2+8V zl5*dyV)wbbt(flrK6G1^Ir{%+t+MNHI*%=Xp7@OG!Q`)>FPu-Lx31=H{$@f|JO3umuHe15xx5X^~UqZ^(VF%0g z|4Y~3`=1i)u9=Ejm*MmuYXWq24 zr`_xGjIV3yGrc=UD*wOrqYs~^{M=FdLVZSh@=BfX>Z0FLGS2QkbECx9@3#}5y-nVI zv%AmV=bzax&ty1j^GPS~nx{_9Yuz`z^5KRWF{ z-`V$kjZDO6Nw@v()}F=XiC1>7e{k~I)vYsL-r6eo@%bZx2fRB%IWNA+D>?Y(MR~B{ z&-1rGUOX+U>s{zudLkZF)-(w0;N#e^FG+UkkwrrDWx{v)-1nW@e>p_%X|dhZbBV8Q zam z`Bc&?T&SmDaqn)Q@TR+;K0olFAA`0C+9K7OIY@3y2|@15`D`u`KR%}YgR6_?D<-xk${ znLqN$+H}}zr~V^w|1rFup*H%W|AdQ|eShbj^8d%Ky)19pGxvo}=C$f-e_KtpGhUkt zeiq-p;I|!jLCR5+o_`hW|Nfrb{^I5l>HYOz^^Wn>G6_FluR8Hr^Pf2pf3`l-?uoc4 z`uOWJ+rM6wBPfY`@(PKlGkDU>Yf#Ko*x-w4+*Y%Tkv_`g8O@$nksapzUqk0 zk!jj0Z^PqX#Z}$7Os@CClKWHY_f^a4U$&d^>FxOy?u(39D@a?iE}XGBMfTm2=O1$a z?$BPgXAkpRbNiK@wZW0Q);u`5$c(!_?`LTC#p!j~+?V7w!RkYPu~yf)PRT=4 zP!!9Rp5;Pw?yM6EUbFs7pR4L?J-_gz%~zZzn@qTTI(M$vykBua5v4Id!%jv`xxB{Q zFCaB!R$Rub!*kwcWpfIKFOu0WcXYDXPqv?jC%@ZXvQ+*2t`C>f&-yP|y8PFH)_G2^ zlTI|{Ph*VJ%lxoN)uZp#)0;l8ds|g+-`IE{<>Jbksn-MN?u*-ZODM`dKi;>b>SE9G z>8sgoA{8$0JEvBdvp{!xOVPU7>_K~rA3kAaU+ta7z8@92_AF0gob>KKY`FLSLBFT$#QBcj*Qx*QovKptYX2UN|5<^envtv( z1xK`B{QW8RXTx_LIiB5f_x=5`x=z7Nu7Y){(WQy&EEBU1eK#h7qD+lRo2#L@O{s?X!X6mS*F>G z6x6wAS}r@f>|n`h*81gc)~hbh4Lch!cfb6de&zCT_sMR(31_^V)7JglKEvpO&Z7Ee z>yHwxMp7nDb7zH4oc!obg>}-!Z!a@m%=0|YcfzG@;g3aEzZ|ySIOSE{>z5p}V$B|P zZm)WLI$S*I`NQKMGi+~hUfFkT@s*64|Mygc<}z=5)Gx^4)v285xj)$Ll&}l`*55z5 zdluFj6ka@Ev1XNEr{tMK(z)z*?)$qnJ^Yq0|CnXoCwJuNm*avC_VZcY;*9r-owup) zS{9qBZ2n=b=nJW5P4(YC|NC^sVpxrN@3z02F6klt`%Og2vZV)2ZF%2);hG}Dth;xq`hBZebDu=|?duQb^fLW? z^6Ey0)Qc-w6x%KJpBILtPhAl3_U@v4@8*3u=U4t?!j3Pc-xsAGSjH|S`Kjo^yy?8n z*?KXtFZTI2bFX3vFO5+D?hfuQKX@gm(V*BH_4+FJVR^px`~+Q-3<0*}Pp&GHZ#|4( zQ2nZ1(bB9hMbJ`tuldY9Qd_znKT_4>kkH$)*pofs<~@lNm$u8_HE+JY-ZF8X(!3Ra zTXsI$w%gNk-IDGSR!NK6FaB%qeD=?|R^wp>zul5%YIlJs*(evD0GH>pldC$Q9Y`?LvHB+yL?XI})`X8t5D|YyK zSGjsI-f{L&sJGxOnXM@|_a^tkli~|rdUZ+nE>WnT-)lIhV|vEQ0>(#|i!7Y_rn&0> z;CO5w%UCBckJhu{F371Yh6{QC(hoQf3B`_UhtvG%LC@FiJRYl z_*d`h3&(R?C7EuEZV_r-&L7+B*R#K)rs~w1^a6p{hf90os~^2~H=XFA^3d~j+RMrh zTSB_h#D!nZop+B{+ES!sPt5fDJ5R3n4}28-(}u(J@=5!1dkW+99(z<=TXTcKU8wxx zUbA-}%04SyeCavce9wvW=dT{QRbMyx@+U^YRFRuk+udSKu(n1$qz3A3cxnD>-mkM4 zwj}!gsyn4Pu~KZw{}Ug*1b$6Db!tI0+nMt8Nf#F9CoD;y-O{ykcA>?V)-BJvOCNea zQjOG}-M;4!)92r6ycSy~UkM6ekK%jPy^Hr9quG^1znCW%&JJS~KXH8Njn5(L!z6xw zv5gOCY?p*~@=<@m)5BBJ|I~lTI8^`P(vIT-Mzw-fW~-NkCYc-ye3Vl2HNx4&aC^x?pw0&<6SoEBX182>_O!--FHW!6;HCX+ zbC`;6NRr+1mh_Fcx}`$g*VfkR`EB+KH+!mm@noT>Ug65M`J0z{Sl7v@uLzmUW%AmS zDP;D?FCR^}`hQAfW}eA6Z3ElH3SU3JWhXEBbpN=LozJ&f zAa!4+$?Q|_U!+W2mFD=BIs9Ri=zqF466w}rNsU63{IN% z-f4T(3+qL8ZTo(kZM$Q}`Tg#*&gAH@M9!%)*G*U~zu3;n=V9zScVLh2!524|$r<_Q z9sLsDuaN|x)1m*; zmHW26>b?D^_Qf=|tLAZQCpF)exjM66&OY}i^Y;5UC9X1SFV_6}wS0ZUA;XvMzTOpE z<0D_So24y3c3N~s*x4CX>(^fp>HAmA-M83Q?Ryo=_Ub2>TRnGmcqQ-I{mG(cPpaCs zx9{59zPD%OE`Q3h=;{T1!?%BGXV@e%+)1zLJU%PLB$mfwae7w!FVn#V%Pix(2iwN5Wyl)7`H=Ys89MGCL*dKKOI zuf(dN1Q~7*pWNW;9JttvV^goIMz&7bo8npb(s!RapIG**q_!^jRqCI;G7ne2-?CTD zvcK+e&95DHYuJyJ)$Vb5IMdO-fsx;?VaMM3gY(m5^FJ?r8mVKI*)}=dCe}mYQ_zje zk~2GszcuRpOk+{z$oa^3W7kt>;hG zA6g0hI_9JNj;ET1{jr(CLHoJ)lwY;hRL#=;>XJQYz0vcFqECMmXL|T~xOd-{bM#B= zJ6tFBcIy#vG4a&*fX(VmF*(=h%@e21?_y)NKm6rQ;fxQ5_Uqh?pRrk}zkTk{&f&H%!@T$!zuT@YB+V{@-&h z-4ffCKku1?uDEdQ#XZ+m67J5fSooidrz+j>?5oO|b-rhrEYpcT9>!tgxafyYx|5JmoArMzj#NxJ8$(XpNlGQ;=eM#>ixlEdP!fw zvHbV)>KA_{0`t}Ee!eyMbvJzGSNYtTUv^LI=nLMi#P0mz*^hLAKR1o8JTA-N{IqmQ z1j~I>7VUIq|2?0q(pb+Pnxd}!G3PYv49V16>dxwW8>Z#gHB4u(={mnp*6;i?FrV-A!cUghhpOGP0H?qLA%YgSlJJ?Prn{?HS#8#%F-oDc2;aFqTA>BNi4ei{jl-0 zZ*zp^S$@$Hdv@4(L0w(lt<}Lhc(@}~tt5H7gPRX7INM~kYVz-yaT6MnJ);A6Hp%bR zHW8@Xcf~(Txmm7e%8VIZ7Q5P)IX>m%C|DblWdC-nDtL&;;io`>yT@6n59j=jyzE|9 zZN`0jcjd`X^E}oc(BfG8qW8-co?mjyqD5=&PN?{$b4j4#c2<~E*`dfIOU1QJ&L0p6 zQNKIY>vZnjPkM@WS0mQk|MvQed+)5NTc7T}bN>K?ze;?M>!yPx6E_s>5h*f#T6)sw zz)XwM!^<9j{1v{FcWUjX!fz#WQfpjawTQ2}wu){2oRlMT{PkwJWL~vct00_yUS(m@ z#M+0Q6Qr)b5aSbHa&)fSw)lvNWq-6n-9@Fm>%xT2sXV(Jd`(-ae_r(ix8KZPtY3O< zGVQMG;{EusV5$1sFts|)BNlhFZu|?)3fjxY{&)MUO)k(Buu8DvQK$N@pyWS(p8K|) zRO;V9aRQbNf#X`+qxsf9ztZO;1-FZd$>;?B=;CwZ9)a`fvEExbTGWyhusO6Uu^8 z5`v8>vlfQ%&(>JAwENncS6>#YKi;(BNu;l;f#q_cC42TvJ6zMRZk_(>N!>5@pAT=e z+IpO|^;Hkl$@4t(tV-#?W;Lmh?7lBkc$WWKX{rTjAeb{Snnx8r^4c%NT3O=U_-^^j zL+c+K7Wt{`{M$BpTh`I3(_THhv}($ubFS5*Qzu^fS$ka7YS;4n&2^riiavAIi*}1{ zieE0h|M<*bv%R0{AHOO6{^^xayVMsi_kLMmy|}V|$Ay1|%Rh77esg*`d(dh~jXFiQ zQGI^RC&N`AHF9;duBLextTOrMx=n0>O#1RmAu}HDiZ9U3>73sALB((>i>mplzQ?DQ zeV-cpUOVxmnP}LeOB2_(Wc+=4dV2l&UaKM-=aa@W{xrzXoBL$xMwztZ-U*j4U*4L! zSthJf_UiFWubN$#PqK3d)a7#9@0=#9zw*&+Z`5}iI&E4ETMO`LhDq^6`&Dl@ zy-AQVZK9;E7d&Ez4HTuT?eqYW8}=smDF>E(op4$ zbPuXjAA~LBd&hUyIVfn>+-kY?*XMcf2bbVZ4jeJ%ELV=macl5xw$Smc|EOTW|9tCn zi@9@4=S?~_(`)AcX<-#&nmOu=;64U;$w0wVmaa_ARY9Hp z>tffR4vFdX^t=50Y01=M54FouHEnWvU2cA{YoB}5q`-T+-rh%!UaX)d2s9X>BjdB@ zFl6hv8_%_=*~=GfabubI+^T~sFNNGY{mr{(hk&mB(_>SD6Zf8PPCjn-cUkxynQhxY z_D{idNNl{>)jn`rJ*Q zEk>)(Y24R}>|SN^+Hd8Kd(WAE?by3vZP^huNIL|@pjMVQFE#hhEq?u2YfaY0Jz3-YnwAi=7V%%*FMo@A)#JriU<1JubKJ#`?IjHEbyCawd41@-GM8ER2M3v2H!e% zrfplMY>NDTe%@WH`F7`0uLS4I?z3`@_h{7*uUPeITTOIxLCRy3vQy!S(WzT{w*7nZ z>{-&`HeQR$Pb!BF9TE@}T*%2H#?da)gX>V`8TgfPJmML|cu7%#2vesm8?0wnV%DsQP4)L|BpB1vYoZ(ry z=5lDv`{;Jdb=!p4XUv+__4M@g#ZIkUFYfF#)(T(8V{dQo%d_Eyh&s!uE{1OZeJ29f z$Jwf=s603^Q8{8qfnwFySFHyQ9N3iQ_6jn<0gfinDuJb{3%*Rg_uMKZruC~``^`c* z_sug@mwtAc&+U8d#@fRRKU~@M;E(*@h7iqdFXwh?&%Ycpqju}dw=&yaN3g7k+bgv- z`?}j=w_X7m86MNqCtK%SK3BGSI*VdJ3q$?1A3r{Q{CKe??P!;1;fo83ZzbN`-oE}! zjo3m}L1f=IuJ$<~nR~SI%B0BJ?;(+?!G*h%f>KM4>3ctn460uB_|)@1T4(lgMc=l} zyZ-mnY5f&pYo%tH<*w@VD=#k>oL!db>6FQ-qOPv2r^n~%>FMF`-@bqUezVPTQc_(D z7AWMF&F#0H$N4mQWAJjnrMXU@xZ$1$WuL}P3JbnW`Lxq-@3~FeR)5))vHOVCsbbkx zQZqh%og2vD<+XWg-pY1Y#(bO0CzT%j5}Z4I!`s)95>_P|&+!{f*0b|01hg!Wi{C4_TpXZZX+n&6tIIMQ!3Q#Qo zE_Va|vHbhDNV8&hn4X=T+@8A)e(N{pyws@O_4PwY-P7ph@r8S~9J+k7FXH*;zSq|0 zGPjx4%FTXwxSc^ti+UmJdkePi`{j=`U5PwqR_`P^r2PhV4NQFlIF!n(|&u5+Vp!w%bG z!!#d}vST;b{l9Q%;33TYu?l-y%e0EmbW@$P3;w zzkuaf)1_%TxBr-CwcK6tXtLvtOJS-n4Q(CVEA`dqhb`N3=yII%xe`Cd2UcFQ3r%zguCC_A!J^r$0$xim1`FoT`WCt=BC7z&zdFKx4CS^-yb{h zi!thKT*=2{w=Z8g@sPFu@Y2IflNN_R`^2Ze;G=z?wq#V~mBQ4;JN9|S@2Lokjyk>f z5qp?v#s!Dh3cqEZcgxJ2IrElL4BuzN6UUY0Cx>5us^ph#AiFbsZDR54UiL?qTR$6= zB}l)CFyy}b?9Y=c8)Ke7{2H+-#q^iSm+Mt;-bKcK-g{;~_v^$TEFqVD$|mXFtoHjS z^7&q;)8l4qgY+|#e`IY>emPTouh?5p3;WFlGHlkE(TqncKehSIJxJ ziOqd#GUta>FI#Z`Homrg?yIE@tle*ah0YJl$Spshw|ASOqwS`P30k|Q1nu-Bc0Fjg zHuFYq@x>k%!OfDp5~inh-~Xm~ls&z6$3^wm zu8rKhOwI1*^BqNV`emO#O`h0)a`juEPrMi6UQ8+%^H%G>!XkS9-`4krY}x!98N83* z+CJa0Uru?W*{%;k#}}$jzF7CfN#%xadbLT^Y}4ok#}D^EdaAhBcjlu@nTD3KM~`27 zH1pfrkKfAG9Me13+b(;$yW~^-4EuW4f1Rhd$L>^=itl;3@NCky`ZS|y@9pxf=TF@l z+V1}0#evQlw%ayNEBbJw@=5HCzLU1MqSIXFOKujp^?~=e!P={vEj%pE&&fN^QF|Pq zD0W2t7w3DsWG(YgcYR&_5$&#kc`V=Rs@Sdi?t2^0Ejc%5zU%q(22Hu!wrzUV9u<`R zR@$g?>7tW&4?GH9zceJ`XI4e{)$(sTJ5A>5_{RNB*g2;(^7Rh8{bJA5d$bw@bS@@+ zIJQ{WT;b7YkCM9%-om$A9$&eB`hrrCrkmvPMNEDV{w}w7pFY?4MX2QI4ON9AL9g}Q zOD;*s{90tM6z$`*`p(V|KQozb@vd%pywj~kCaU2;$Mk~ycS6| zJ>s7nbx_vx$BLxbIcNV&s0#kE&i%cWtwubHmEXHg=fcGTaf%EZrQ^3m)<@*cy0$!c zd*J*>X$n_NDzl{KRy+ILvEiLx9@BWWWYZnHJxl_>Z4cezJ^bp0PRZ&D0cNJ?1jpMt zJXzK((rPXXlW*i&$X(~(tTfN<`q}3fF6^)RXr=Qe?<|A<($K#6nTPy3{Y2AeM|GV( zt{~ri{f^|M4Y|?=B@ef+n7EsNy{$Fx`96;0_R;Q-`q~t47^W-g)!yy5 zm6(K2-&ed@5R+QnY*i~Tds|KW{CgdXWO5CjKlMv+oHcKHOK(ZOj*xHhhDxovdnF%_ z7CbD^xwVBe_qflZJ8@xKmp|S;Jv#9c*Mg4+PW~vr+$~|3)*dQ%`{J9JpuUst8#CEY zdpMclM-JUyqpIzu)`h?firvYYaVB*Q@WV-e>hr<`&zG-ORn&+l~gyzISA( z?Yn9^H}dd&*T?f>FU(l-vMZ8V@Mj8-9PjkVfZI=w!dBHjIK}bi=L$}_Sv#1I*;xHA zGCW&aCbfNTOCz^W{__VXMK;Y|?r45Cqv@IQ|DB86B6Bva*z&gPmgW7Mo1X_P^%A{( zQ~&U--QTNP9;aJLZ+=_%^QpJbOe5CA?fl#49^7^{%DV8*t+d@Kh1r>>3gbTC(~O(A z>4nNC)=b|a$nw~s?CK#Q|soV>cw6hUp16i5<|5P%uciZza_X{ zVu%0b50`J-CcZAD zm$fa(J5l1|bGxTj*5|a$+SeDfcC`Ng@MdY0+3C~SQ(vUam%V@b<9aQ1JT+16}b=kD{%W6O&Rlb<|v5}JSi!G#;Y1Ajm0SGd_( zc{iHj-@CHYp<%(Rt{t|t5NG~9vo}`mcysStuaMP~ercJpUK&dBT&llXD2zal+>rOdpJt>5Q%$LEzcvRa2OvxNVH^>H<ZIIWBnH8=i>w zUkdrAYbbqfoy$9Y`G+TVFW3|Bs{0jB$(y@BV$O8aXU}h2eCm3t!tA?m$*Z@aQOT9{ z4|;cU7kqn=^5y?4frzF|V>v;`*5b(_eId)jjN1Q~y>Fh!9L+q__G11N{X?}|mMGP4 zQL^Gb?KWF)w@3ej4Rx<3@4b94#&<>#_ni&XG$%9t{phm4j$`5XIp5w`a)rKLIr(pl zgwJ8ew-XY)w|8An^xUP&{AYuLs?F3FA;xoM!V~NImlxIOd}?l6x6<3brJUWaBQk7q zOVs+N*$?BH8)dn}6HdP~%-AwBC2F>LVLk6Jse+Jm_Zn{PX|A94bphwMoiA)^cxJU8 zO}>A0`qoJQTEX)2=jlzn>3z<>PrkS$wtXglML$FJsqp(;o>w>KFUpNjZ86E~D-(QJ za+2NS#$Fa@xy~k0zO@S!{eK?`yzxtz+a$Mg_q5~JPj1_K=94=I2ee(QVcICeZ0oA= zeUj@-jjM}mqD9l5i`0L*@a5&@kFVG7&pQ9$!`H8@$GwXxcfN{Quw>4+%eS}XdT(Bs zK5_c=<#&sY7N*bD{TO*}QSeLs>O+?V%hKCZ&F^2`cYHE?@>>yxZPCl4k9SYu<>R}? zr?)(9W=&H-zt{4V6-P_%Rs1+=dBeWe;Mm%CeS!U^Cf9wv|9{gz%HQsry_^5fAJLn; zADPYa%K3YQyF~jbTMElH=g-g1UevJJ60kt*<;-Hi`>(Dn{;T=yX)WKY=bs<_{r39H z$EP!wxy*Wa>ax+*%)3+n9JQDgknkCpa0eOb7ELsbO#nOkYQ z{IBlfTe~Rw2mgKfm1)NHe?Gc*Ur0H4vasyW(qlhwd^9}4Fa0Qdb&PlUi`|m5Ui8e` zE-S7-@4y_rXZJFu&)8E~f8X5n@Rq!_7xMXZPs&Ph?)&p#$DFx>tQD7BR@y67**F=m zeDm$G%-RVXE%e_mIK1-iZrx>*nC^uh@+qBbmszKkD|c?bO?;4{?%u1)%on!j^OqQO z`|Vp_`Zrp~>h^=#cb^v+K6B5oIP*8C%2@Z_7M6(n9%buZZeFt8GXfEP)l6%T%N$y@ zJ%ckeZJ46-Uw(eA-6wCqZttl>SGTh8zK!Gln_lL=yDV|Wom#t~a9$^e z&-1o4m~u^4?{U*MRa~)kO2_|iD?8rJx{=*~^|<}TH0O)0>z179blPoTdiJL5?%?o+ zl@mApvbOZStETDAoq6`5SHS1VE3WS1-*x%ZeuL?SKXU4|+HS4fFE@Yx<)YZ?b9>J< z&$#?IvxMiP!}s&wlCNIK>$qpb{?O!_M)5++-N6@)^Q@HYC7PTg{TGUzRg;bXYyN86 z>z0n<-4~Ze$ojrDE%(0ha}k?-{GU@IrSi+`9_V;wUtgCiILqd?q_lMRuF}`9HpgBy zh@bz#;JkU9RlU&d{lDb`Z+%hRTmEG}cSlB`=)tpA$r`#IPgS=mNM76P%d6`WKYLdG z`Z<=9k6!-r^kr_uHWl{t^89UQG^#$Bd#%gttAD)e|D(r0e0*jvT|0N{lv0Tg{6}S{ zBpyF#DPe2LF)^aivzXsXXOYm>YMbsg+yRe13Z0X?+o?K#fBG!nyL(tK?v+-wS;BVr zuxIYssE)1Q*&qEATlRF@Dm%my{>IhL5B?RlZzytew)uF8N5YeJgFuAkY!?;wioCws zx!LCbHt2;()V$mIyx`3Z!wvD19{)CdyLYv*ttn$-;wza>wRM+x%v_geEzZ2W?AGr) zH}}gdPqSO@voiKpwbfhG<3~hHpGH4V-Cg(Wvemhtxi{CmF0_ifcIfiUw$n<=@qq=! z#h0hKYzipQvAcV*?2T4RuNvozcez%!zPlUlUYYoFPsz1MFP2y)u}pg-?Ij*@>478v zU&Y?EvU?fUReDA3XWYc*KK;$Yl5zTH$f{4X`|dT>YB`lp;k_Ha!@oUOmuGkQ`-aeC zIu|zjd{3JdwK1~(@B$o=ojE1~+%HSjCokTWaBEI)M^!5CuAExA zgYO=`_?aetW@)vWw7YF{+OzH=)oI$70ypom-NBT+$Y;@}!rAkFd|$m@v59~GFMg#i zk-KwbxeasO{m;xa77GmA+-2*2;__V+H;dw$n=pCR08zBnsM zsP~|E@K&wz!uk3iL`n~4+^pBo^seI1uzw_ED(QUt&4D|;mp8ndzua(>E6YD$ehDeN za@Kio%LHEg=d`Zh$@F@cSJv)~-9m`rPA~67}S#&DCuOwuGC{ z?>;-v_I6y6;cjDoZY|G}qNQsba?^sHGv9DXUXg|KQW>&p(LAW!`OK^*>%|Dwnd5JIp69VHHt?Z!tZ{1An&ujd?DB6$&(+xlh4`r&n}w$`s1>j zHxB*!^L3)r=H3fWr)>@t<+faYdu!mlFjW@z_?1sguD*VE)_=RH^)B_PUn&Hj$DXP- z>=K#Qffz7a#uAaEH~-oKpVxcloXRt~SZSHO=T>T7#lw>aDlAhbtN9kZx}urusiNQf z<gO(u>L zCQn{`w&s&|_z}*=O-cuxCZ}83+S+bazxC_K3U=SAq1-|Ja36qoGVJ)tai?TePpiY) zsI7VXY*m$oz)Q8DjUMpm^PL4OKMvYq8Dn>H;Fu!KC^+wFTbPy@hBgjHH7f7DEV#_= zUn!ryQ_enUC#o+5n4A{4Fqv=taOV8^bs)roanr4mDW*lrc|$l3kw`mqvnU8%sCXi7Lc&1_)uzlBigOvQ{024+pWvr z9l5yJ{o=OV=#aHhS2q<0X^I4?@-T+(5xi-|^!Qk+vcI=!!tFa6Tcp2P9#t}^f9@}F zWffQJ!D?xWWyyFYmOa`o0r`e*)&US{9gYX0M?$ZfnWAbOK;{`=+kcxLv`f5yl3-R(}Vzk;6LyJPY#J>XGQa1sLt{tbnIl3VB9 z+GbyzGG$6j=;|;2|wQ+Pg zCARy&h4?lr_1<08d5-saQQ{ddr~U$Yqi2baXBgP*-E7*o{F#zzm9ZahUi*sozHe3D z-0zwaGkFecQDM%1!(Z7&o#NriG3(7g9C-KmM`cCw+E}r=UaAT_lM1Y zO{V@h#3sRRs@lz1zwS@NpKmkFE0ref=sWa#;-sw{DB|fM3x!%{CV*j4S z;`f=k=NJ1iEaZLBaI*N;;-mHD34Ys;YiwsO=bEwMr@GD6InVVor%lzC+9dNg`YO}= zC9k$JO%UJA6qSFiwx!d$wqSx?~95ZwZQ9lQ#u# z61b41W9F}*C~3%4eQD3dU!CF^QH9J#Os^MKetwoABF7~*xpI0^!H*<{T|8C1-S)5f zeC)eSzVCW*RlcLDo>#AMX(s!J`zzMIxW9YtQ$^S4Z|=(H1)BMP-}5eB-WV0Ts&>^? z8I$e5e`ZG%D$L8N?C9@pubw+?hQmhP`t${YUzb*X=4fG?u_QN9RtU4?i-~8+t9p2H zy?pwVwSw_`RZPrfE!j=KYBWqI<;FuW>l4clqP%IRRU^KR0%FbK5Yn zKTZ(yJlGxW*}|5!ts`-@VS2IuF7+$2ckOQWXFi|j=6dS2kVIpx@U+K=m%j>l%lP8g zEtWNMGK~E53+B8sm@F51s&dMvMb-j zWR^nLqUZFwwlvMl6I`_}wzuRwbh>4;WNGTe2|Nwu3j?!{IK1aSyYx6C z>xaGbtd?IrT=tr2m(8yQD`WXrEiaJNJKs>W`^tvPUtheQ^5LuN)sR&)7@+3@BphsF zeGtC#L~!o<^?#N23Nkqzuxq?iqrB;JkH={}hcdfZm$TX%=0^t2QF`$39hbfpH`fQH z`*Zb5cCQT9U*6@d2Fi;JPR`Er)p^=89_y#Zd3oNyFs=9TsdIMAU)J)yHW%yEI+qwN zJ=1jeQT4Z};W1*Nt}##4ic=M>Bs+JPGj!TyTf8ev58UgyKHzoey-)#D~jDAG|z?iHRB4*U7F5c^LktQ<%w|pHXn0 z`T2cQCW7lm6DA$2>H6_~eX`bz;`i5m`1Vb&YQ6Q`J5n#>H%1HQ8UB8Gd3oUGw6lj6 zKNFL-zdyCu^7z5dmK+(I`K|ktjRQ}g?O;oEToHM2@@nm8KlEHT&X%hQ6lyp2w6qYK z?ezW3t_OXu$`=%x%3oRQWd1hhS>3Ly_V>-D%)dT7dFs@mMrQU6IX8{Iyt^BHWI2=a zKVL_Omgl}3rBemKRjx4OzUB3|x8+uRILLl+kt?@v%%&X={@?0cVaA(UmEk$Def{00 zZ%s^fX-zlpHa#{!sc2=A@kVUoBNq0$2@`fLpFFR<+3TMj$J!U^OrFOjgP$t9S?ev28>Zjy5@s@IW)z&aN1V6X;J|X$y*n>Ix%GX1+s*gB4xhF7K1h@) z9eQq8Gbha@x}Lf2)mIaq|K6JvZ8mS&@@DQ7wL_PEB0%-uscwh7JUts5n+NaSt?Sg9 zck+^1-i0+R5k@;SrX~ck&&u17S^sikSw;R|{bxMOZZfU&i}$zcjIG)!!lcD=d9{L9 z`2KxzSKVL#I(_)u_kCK7Pjjl(>ndh%j*6Mvu5R@tNbc{xustEWUmclIduh%I)wk#P zWrF(7+}fBnFFYtM>Ftd;^RMnI@85JksGk17C1JVH=i6)S|J!V<{cV5c^*!dRXRcRy zXI)RT{j>98Ro&CaMcd@+q6{{dl$vt#bEh9md;a;?V-^F|HRr2BXLqIUffHhiLf=4EWW zYv5t_iuWfE{op>`A90CmlV8%g{qK%EDLr*XZ2z%!&c^naGv%+ZFaK4xe(T)yDHWHk z-TzvDeavH0y=ldVJKx{3TzO{``1r*Jxx;Zb@|XC$Lzk)5Zg_F|<%_!X)U%IP#7>j# zdVG(c>7rh>anP}QzE8Q{u8K;3y>rqZ)-60Cvcb0|1=KB9&%HHs->L}Lh*D8YmNSCV zWxge5YgTohf5Z3j*{qVEy!DASd#elH^H2X!;aneVb#-!Of|->qQ;yYNE+(U-50AR{ zesoRI(-N~heDUhzl=>}fA@-Zg6CL*c{&;F(jMX+x*PCbeZsGaF_2!RO$g`K8r>9T- z5paC@-oLZX-zg3@IU=K{S--LBQvajo=kKr8)-_Il{yF`)po0@$Z4FrJClM7? zu|F(OYM&ye zo=*3;RV~tO|5tAD^L>k2?dSiR=2}v)?DKB1i=MAJ-t4uI-Fo+O;g|UmEfp(`tHm^; zLc{9U@mtToBbt6FR`<>QNBnli-|u!jZ@=oCccy;vl`bt3Hecxmi&=@enfBo zV!apt4{bQ~QMkQili!~@eK)g{M_-voCpqj^vzREvI{C{Jqt>@YYc7c1di~`G?|#KI zb3!uo&zx3_E@R$Tr6qE|aRUuV2o#We4QNvD6RVW-;LC$nBHZ9YFK zA?d7f=eD;eFV6YeG;M$3v$Qu~UpXYk)(NH6OtjFpX16pkUh(SVimxKi@=rBebFP;W z_rKH1ZV@=U_vtCGtnjOcD{tOB^Zp{s`t_Cz{@(k1CMEOO(~!Sc^7K0Wo~>M$SZwc8 zdvVuMTWyM&)1#kH zJZd>`*12Kb>ds`QKUa5_ysNHAcF=Z7WZEa4IP0yC!qpj*R>!$c=uZCKuNdw;t@Hoi zJ>rsDo||X>%b6+Dmw0@hYe|@1-LEI{8Tp>w2U|9jeW-nGQERF8@Mw4Q$3NHGw_drk zCHRNUD)A(%_bzwNHwP+~tyZ%#pL4p^P@B{CQ06H|@Wl85P9{6M*8AeGPFZ#byw3ga zKKEkjCG8XYEW|b4pFMcAZ)V5ORHi@8FP)?<#pV`17b-AOJ1CU4d*8gCB`%v(6!Z+G zf9lz-D0X*d*-;QJd1GsaR#Q{cmbI^cxOSW~`1(|#uFv9N-X zPgk~og@0I5oXFJdznnrV?>{fob+#|Ib#y$R=+AX^vaD}q)a+OJwsO1mf__gkob~Rm zPS5I%w>PgYc`E+Q`+T8$(FbOUk51VY71ytt&F?B-5LBUkWyfaYSN0!TrX72k<;_$`MgJ% zx7u#4S$zDw>zaiQ+24Phnr`@d|NTe1KZdwZ=bt#EknQ!oKQhl>*!jqXzd3j4SGw-q z)R*T!`o532HFrZs|Iu|88}hSba?UTTnz>wP&YzoCi?7T3XREh0Zm*HrZ1|dK@406- zPfe3#Z&nyj-K-a1vVWOOME}YECI?qlE)KZd-kQz0>b|i+^z!@t{=e#4i%V=~*!8dO zs*zi~{k(Jb+s`+onB!^~cicaGCys5~`EDcm^0gZ?uDedQb(VcRKP&T=;X3@SjFvZ)ae5O9$G`u$OvjeG9i{ie6B9G}A6!IjlJ^|;0^% zPj6dk=zaGq``whdCJa+gWd(N!T z>4BSif^B&D;{NAdzvRPqv%Lygteq6mKEF=3%Su)&&aKuk!dQ03&SHkFO5uBL{0seA z4yemi8rDBx{&ai&Zw}$|Uk@H+C6sdeDNdiu<#VHFd-XP>qqVP+Ia-yJ*^~dB+4$mi z>-A4v!c6CccT{{-TC{la#F?Jv?>cs;ax7WCJUjfPv60c1-%T=SE7F?R&8fV^NnSY)6G2!JYV)$SjkMQlu7DNK83)f6^$@<-L!E7@k(_%9k;DOy4;b6P3#{XOt}4-b@i7_b?9VueQhYQ^ zOzyw(*{UzCOe_-wv=8Xd`1q5#BK56ggKpU!Ut1@6QFPzIDZTj$ z(_PZC?mnFH?R5>y(|>*aR@)ALI{s@>tgQW!$t`I_RY_Zl|)tt87?G$*3o__6BS z9nb#MpUYe_HP7(Zf}LXX)|l4v_5It|nX@S#zm+zd z<<9Qf*Al+}cI#K&nhj6w4{!LlT_z=v_2ZYB?|)6QFFP{F`DwEE#RZou?g;)>e06EJ zhwt$p`!^p-W&gT|9S2P)9GPKBER>ZW~%?Nt=^D3 zU;Q56Vb`e4-+v0WaAvm|#{ZT6ZgY=k>$dl9=c>OaA6&6?hL9J3g_rsF?GHqAZtUf^ zYoGS;;J>#QSDfUudGy!bq%K1wE_Rn}_wA5_^C}l#-1;%)jn!|frpuEz)cs_c!!@@% z_V=*`MFnkF)7NRFo7U|;KVSAleIQgP$Dp8d1pcjvWfPq*q=+267_yywcd2G^3Tn|lv^JhVyq#g05hrFOn!kFNht zK5U===+LqM&WppZU*MEGt!w{fzM1Iy@~Gw)E9b7%|8>oWsb%+~uM=4`U$7L^T;ILL z=i+Vi$@k?CSKV(_*!F?HFwUgzpFP+1dp$Re%MKUx_$@HM%X>*{){KpoVP2cJeLgu? zH+`gCBT%YP~@T+0+@rfYw5&Fe&=&DC2D<$U_R-%5P<{P#Ymo%u6PyHD3!IQfg$ z6o+Kxl8Vx^!5rTg-xu6(R*{&M?V|USIoPuzD&}5f&flh_qY^2}_u0H&*o)h~C!k=ZDTVb2yWKdZYcfHDw!*UX56AHF$z^I%{*i!*{>$g;$s4Ro3XKWW`SWa$bMd z%Ll7ob~pa5xaHS5;d zw0n79iiy!Ylc^H6Wpa-B6FH+!&UeL)tGT6E!!JyIe97*^qCL}si;C~>c5z$8f7UAH zY3bqM$*UA5eA*EtW&Jn8a-H9*uVs1hwhf)1H*B7&f7W3?n~uu~-m2T&%bu%#JX!a! zQ?EVZc+qFQFK_?7y5bwP?`8gl;QSjC4Hg&2`9xK7D9>US{dWF9(kk9RHNG)1FP4Nm zyQ|N+WB!icK8H>33rpNP{f~{@-L_GiJfG=J>=$T0CC0`$W!>LZJ1VlzXmmg8ahv73 zcmj8~%c}O+X7&3A<9n4pepSz36uf!*ho|!C9|CwUe{a?0i)nj%CLmYto!*C!1+DwV zzfLutkUZZ~yJgt72u<7x=yFb+{cIJLrwQogLx`Ez=_rEY~N!@F+ z_n~*cU)a~(nGGI`FKa0;a{I5PeBe57-K`R>v$KLE{r$^-R0{0=B`V-j`?vnvI+g{i z7XAJoARrPb67EsxAGAQhJm#O1*CJJq1*)%}>&4H?sMGu*DmkkqwR36X(n(FKc?mlM zopw&noS%`@#4WjE#R`Q(liUBD-}(OT@uweeT=7bE(>E@kW^BCQ`ujf1;&XEh7w@Rv z-k-#}_4&Cg85R7J9f$gx0!}Y6i!3#5#b$`~yVADm4XG zReN}DU(p}FMY-15&$k_aAt{{M{oK~2ev?qM_`HWXH}dy9spZk-& zC&=#IzTf+$&+^`2d4&S?OWf*@cUH~1R#6UBSZ!VtMqb;ti5%em&?p0Jt zO(NgDUrKz=nO~pI{vN#aXlvA$wGYY-L&liuc{KZ2wW$?MrGl5^+ zm(ylC}B3bDtrcJ>Dexp?lTV#SgU7n{=Dbzjt$(U1z{~!DUyQ z{KPfSXMNybV#0mp$ZCD{i;{jJE3dV_kJFpsC&}|LyoN z?|-<<)w(3d(M)OOxf-pzjcRXf+4~P(;WYeF`($&)F(vuVz|D;Fr@qLElU83TyYjb( zl+i+FzqPa4%2%9W(mE}y=XJ@)YU|@|wdLhrvdu5v*3J0*$yj4Yk)T`et>hCTL8f=+ zw{G~z{K3Rfr1JXbC7*JhxRm|+d$m9O(c&Yn_V?E+{c;eGow#W6y;Fy^KSw``=YH`! zuCx2sdn;ML?-^-6pW|mtEb*In#QW{!v@OdE1V%pK4N*QdV@D z8md&b2L1i+ku_yzo!qln_6PRzw;%ji_RpetE5p{P>={YNoXdV$Tw4|DbH1}Yh)M0s znT)l}UpppG_MUKIf}GOd=iN<3HqXT5S3y>)^KI0$i0!?xJmS=c6YV&bOIx zP4AUdynC1%qt#vmvD|q<4Q#`WD&NzGhoctts4E|v@w(sNYr>pQm3zJ% zw5%~*zELPJC_tzoyn=z6BqfCY4RQ zu-t;_|F5gBH5M7hyKFP>$#^s4@~R{AI}d({x*%sSQ_C+shwZ@c&*wa5-D(c=$+~E9 zyZ`R*bBnsG*`Lg}>hNFb8^1lH;hJxSVkXnR|FsrzmCFSE)Q?Wss4uUR70$mkXyx9O zckHUBFa5rpZ%x!C>+;nB?eVVfMFpqHJWuRYPd<0Q=%4I!?;W!m8~*(HS*txk%xd16 zI__hW=P#creC<;AzH0|I=f2-O!7jvc3zP5cOO@xJl~_&|e)Rt1@kz0@Kh#goFAGZD z^xZKjkkQk)`-P7+ca*B`!ko0%Z63dV)J}05@^>5YUik3_Jf&UgxaKyyZ5BuBxz9ES)mnHvUt~#QGy&Vjs%J>0Lha zY5UqAQqF4o!@2~op097-WOVuF|NWi2mdttLf1&(Z+1=XtGglk#tS*k2?-s8ZblNhcqT)n($&(HD> z?u%Y-ckaJ#>ioPXprZRpyU?;fZ?7N!CbzG~obSF%#?rYyw|8t?q7^g!-szxHua7Fq zQ*SAy9(i!ITJXhn@wu}y#TX?nuWu`#pt3Jb9Q3 z|GdPelt|ZRU;q7;2R1Cs*mgp^Q&Rrn_V3k?d4FE~*`{>nWY-HXRjoh4yUo0>n1-4A zuK1hA;vf6ytR$&=U=mM7Xov!f@qH0aIxZT$M%@n_+Nldkrj__@x>trEjw(ZhiDW!e4ZxKZxf-{XMyog|3;BL3#C+KMM9}8t&;?E-3kY(#&%~ z$*$hv_dmzgEDV`4>)Xy_Y+t88YTN(2HzYIX#Ok2CciMzzZIP%;(|`4EwZ2U5*5r;$ z>27LD7rd?SE&0B2+d7}x>ijkHB>M~h?rv~j{(iqlR+W((QzC3FfcNKpw|ID#)ov^{ zJaRE?)oi}Vg@xP1`c!O}J>}_aTlg{cdcDoB3vt2iX%AmKdd+`d@3-NP_>Ctb?z?Io zzi+6g?kTc6<5s@?A7++g-(CMNS6;c!?!(hGkDn=9w)!f3Ih%3t>61m_7pf-8m`mLM zZzIMW-|g~bA&*UZw0znAHEm}Si@_#q zPeDehRMofos8{ya_g1|R->}&8mTuV3W#2sCPW8Il{^y)2|KoB!roOLBS(@upmQ9)I z_b+bq`RcznB!AUOt0*bSSF`+x{Cu={hx)};8zK++`U+jNu{tLu3RVQ6~ z(-&A;mT_~6g=wrD*NGn)kKgUPE4#Mp>(tFtUdLPP5I<)u_8{cS>Ws^m zd7kr&E_z^Zx%89qwO?N>n`P>!Z&>9UaaU2<@aVB#k5e&$b*8#X7cLdmrfFYIiD)}E zLnUqMs)S8e>X)zErJeH2)z)#+4d%Y0_$<0_f3bG|tF_)=FL_)&+}JsF;o`N5+Sc;w zm)2k2ci(!;i-fM_v#%YzeCzNb?J7CdVAI&(r60GHo(oQCcpS_nGasy3~bR?!EL)dq4ZD=o8Ai z%-mwuuXkQ5xw515u~^UR=t-|uKdW9meeb8LuUsBqLNzN^{;dD_@9Fyst+#?QV&-4j z_VtDL+t9;P!ZduIh{rpw4zU(}AG5Mz@1Hrx`fB(6_;c>_^Yh-ntt)T$6qs@|{(F|5 znz}Igc;D4m7LqEnTv#It8%q6O_)gh2OFX-F-}J{fpI0nBpZN4>bI`u$8y0piT3RvJ z|8cnS3`@83AKz~;U}9`+aBxug(4vr#l+GNz~54Nn_o4KgEeFo<*->36K=M{W(UMgzC zlyK+eHoulW_BHt_Z8EJxI?La)PB`MyM=7aaY8f66yFP3G4i)Epe^qz(e4brXFN8n4 zUb4;Rm-*)xciawr&RghqHca{D?*Exm^6QJ%KUlj80Wqnf>VzQ~&u>^V};RKhQItw@O=RolM%{p$B1er=|_ z%&vXn$HbOdz3tl1Z2r^o^uMXv;Y;!>=i4oD*O}w+fYWior1@RyOQtW92B*#iZ3+j3 z&ei?>m3Xj;^~0AhEvr{&w~7fKcb&IN@x+w3YW_zeSA~d9-}QRws+8ZaKV8}8vU*#8 z+pf7YS8~3RxIa;1Ml|cu9TmObxx(MPs;}Jlh^Ony=E$E1r0ZWL<$bK>E$C5x968(K zgVmn$X$9+E{V-uU|0DG0kD@tltW}R*&1zhzDDzJJ+#g00&3DV~QYG){eSF++uViN@ z7Z(>dsjSf`wuP0c-p!@+mi_MU z|6MNm=VusB(*I{8$FZSthhsCF=l!C8%ed7ADuM(AdY&tPSK%&v>LvI0$Bc`G5;=n5 z7U29GV9QdWAAWwG?ZX!@I=aR6z1joA`cdusVEu&dngJ=Q!TZ0QTTpiO3~-nTwM(g4*ZOj z{T*>zcC2;e_|VdNzApRzzFOW_TVK4o-{>ItR(wzCbGdcmcMe~@JNHpU0GZ_<79$xyRSvi4ypEre|5K|*c)DKXgM(1V!Ck8^0I=>lP0X{Sj+R;(%5>< z*^?8`e3gDK*Ooqe=8W2Z?qAe;B}{KJ<+Mz`wD3U(|ABWs*B>UYZZt_vX8FOgaoeu7 z`XSD$zZ3Xs+fHbezZ+ZL7{F- z)}i2aQSwJ4R!I3NgI+;*nm znxf*S1gq1GUM}1$SsYCMkKS)jun}#(eq~ACN!j@wx|YciCRs~3ucaR53(L5&LvZfu zC7nU@95&eeXu=k6i-Og8CjppG8%`N0gJj8>ppKCk0j-h99^bzX=C+dxY zxDFMjzEs&`pgv29@%l-@jyN;cYsVrZUZpm>Ouu`>;&$(qs+nc-GIBd4=l|f)>|~kt zLs>w8gM)>IDODh0*@>1J%Ed{OT7+_LUSB)o#eBd?GsE+%jrc=m!{LOXS!gYTZfBeER`{B~cBWv7#A3k_>94)?4LP(>VTq_dj#Q1@?UA@yo55 zoALXB?!l5*w$g>#P#=NIBv2+7;IQFfYWf(m!2DD$hmGx?kDIoe?}>lHd?1o>d(rWo zGI!jQMW!V@|ETjO-jv<=-Zw|-%d4hN&o;=m7wKeuzESZEBjdbt1~WJH^%-ny_il%9_KMw%TXj$Xpw>apT*7Co+p_*T~5xJfGcpcc$Z!J}z}kmvgYl$uT)y z4eM6j+#$E_-HSy3M~-D#-aC@^s>Qs#A2VYD%MPuEC`=v>a^ z5*p}ua_+2~yH@SKot3kMA_qWtG*BG_C_}#WBy%uKj+ML}`^W_Z#OR00UVv4Ix3=6S`9|z;XimZo^@6K7K z#;@>J+3nn!I*V^djw`3;)!%jAcwE65^@i&V z-dlb05XnlrC?aOHY$}hKY-|_Xx%P=sTg2wBJ|!? z6Q@o&9q*G}?Ygw`^Rvk-9JFG~Una4hN;-P}Jl)oawi3VRB+g`hkK7 zgSkCj1^wbo*Zpex-`b~#?Y#IZTYZD#yX%*#=dF&}l_i;<_NS*~g91zIQH9{e%J~}- zTyxiJoK@IZZ}F+|_Ue5-@6B`E(^j5+arI4ztBID^>NL}~P5)&dp9q%oi~ zZ=Pga@A;z>Z`Qc2C}3)A?{u&;^U3KIG*lOH3GLv{>b<7#_F$Hh*eYR;4BwBRz9tE5 zo4;%2nUB-k_212A_}?5looBV)yTfZl&dnuWKF2ZqOGbJhQ=GNV@z5*Z zx-Okk{NC?oUr-$yIm6(z{7q4VS8Y}^>Xn*~#j`943>WO}y2K;6F6~ad{kl~*6(+_a z9LWf3vGB+VnP2p=5_@WxxhCUTa_a$EyGx;(mAh&LE+=c~l`59ayCkt7-jM!;@bLYK?@w>hH?)ptv{$HB$g;)4u)sePIi9yR}op;<{dQ9zw>;F}| z*WTOn)<8vN4^Qzjcay41_RZ(EtaG`aaI4m3+ROcAF`RoR{$>ak5rottjZ)nQlJ_#c z*?zxn^+tZbl0O$+7kE$C>(6_<>cXb@?KONm{Evp{IA8e1Eb;N3gY4sDHd(@}#SFF^ zx&D9tc0<8JuUifce@a}=%PA{WV{5 zsMS4bVdm*C`y%|^wg{~_ac64vL;DNUoV5PP81vgX9^t$#xO!5Rz_iaICZdjCpT=)| zv%fK7&VgJ8r<31Ki|Idk&aX4+E>nhc3^k4)Su>a zN_M5TI(#k(uUc9!rSW~g@b<^0^{Y1>pJlLe?lIke`pfi_QyMwD&6O@}xAs|iztEBK zL}j7Kr$xsF^1j?iU+Y-mP|usZT$4A{P3BHo;Z2s3oWH71Z|g+9ZF|L^!ynI7zw$wh zO55+n%=X2HET7H&$f2}2b;In2AEp0~7KgRfXI=V!B;i_%!pgtF)ANq&tY2p$=ckzQ zBK*T`8LeK$&|hplKSd%$-*G?IX8-swyd)@eMQ@%@)@;tJPv;xv_wety75A>pq9e>; zqOfRP)t%~ynU{I}#19!}tEQICZ+)8>d~xcn89$R*j`d%Bz-eut6Jspj*Lr{I%l`?> z(#zS|x3?$v{7>3dc>L7fhY}gteSd;4Zs1;4I{QZ8(t;2ZM;2BlXgw03EGQs!Uh3x! z*X68CPZbvUxnDWdJVV@q`Lf!1rm(us#ay{jX1-JFUP<`9x!=$)w(L;;MsBH~J5LWA z9tqg>@WD;{31v1Cxu*@HV&)sp`EzLEujBF-a`ya&j@kuXs%u|4#aQeR5{~mev}5mn zCB;Mh-0E*8T+!5jAMfPzT(qXvLNs&Z`-5A>FB?8F+PGu3x|sRgj^>q_Didvc9M^u4 z4)pswo!`Uf`K-g~AKh;i#_l}zJEia7=Ia948SSQ5-U+PVw&TxZ_9G>7N3XL77n;3Q zh_kWe-utX}ed0OMv+GvG`y5t3AymzI`btaZjHi-KB|)B{t3OZNkScfR|C{dS{p$`G zKRfb3@bb$opA`Q6Q07f+zNM8lp*ic%k2-T7`(HNSmd$g0b#Q%Q;xztewd-%H&#qT( zoUzq#gT!|6cHwDCiG><|uRAyR=GuPvs$;EW!QHw4SdvEZ^kupecHNNQt#j;Q_@p|2 zkBE-k<&_HxYWE7He3PHE^!Xfvk4!etYr>T@r_OfW$GWlY^8P(tN89UNAI;hsVODD( zWVD2_ar2e1x1IhbHlGR#xb-UqzD8UAuv2sAn*Og?(tFp>2X8uhJ*;Q9eRns=p0RxK z!ZWMS?QfV-`R~N5Z8L7o>)2V@ZD^t5Smkz1caDHx^4osA9KHxYy*G(jSiI9hZYlm-)^=eybsFjx(fZ1nN?Au!wMQ91CVVrYswhuB@rYC_Ytd zVo`Iqw#Vepcb3P`DHbZc9mL}&^M3LYjc**v1(o;JZmqZAIUl)V;kL?oS48=h)HGwS zCy8aO{qskbr!H4aNNtVdiu`oVv!68GKTS{kv!+015628?p#n+s_RE(`3lp4q%v9qi zoD%&gXMgH+&e2ncAHK-VZr%3Q>XPj0@l&@Rj7#N?|JBxb9G$L=;OruD-pf@;2X>J-S~xKCIPTGi!?yXT0#1CsjXhuF@-XZ0Uc`tG#;L z3x}GOOI&@v{&o5KFhxnMy*2T#-lQpNF-AL%oApb4`w`0c;?^B5*n5A}~9o^~v1cc1F2Kc!GhHO>az)>g=U&%ui#7^!o-lNAUO%TMy5gn4y?LLz6Kql)=4Jd>tNStM z=5}3)qlD^^2FJEf~leY%fd~(|LkMCHVe4~HTm(^OemrUKVN(?{c2m8n_ZBjqI_mgVX zu03C#gsZhZsq4KB-V9V>f(gwd)ZzKOzgL1vN;=` zs-pNp!E<5I{{wnKlm8@N*LxFn_|fX4H_8^d+z)eoWd8a2pO0Tdtj`q9?-n-c%THVO zIP34Z54;f_0Yav#^E9(7za9FhvdC0)J>R=NH`nV+Yiqq8q+C5U$s%~mYd@pwm9Gn5 zE#vwr`TL`zz}t^0)s9u?u3QV8G^18zYD4xB))k-EKa8?t_cEFmzW-O6+NJf^-0SZ% zw6yP<{NURfmA|XJ=6kIuJ|S{9?u+xQJrWr|KGwYXr`54-Pt|Kl*;Omzmlf1aR4L%} z+>v`RENaE83#(STRUNCnm=k|BWc9wzFJGiz+_^8_eaKl;Pw?ia>eVM5-{(v3G2xl< z{pj~utZV)RN7%ntXxn=w_8^az!=%$Mf0jmXS-k7>f%zx7WUs25{&!H*vC0s;6=gd0 z=H^SPkyp#U&ntN;c-N-XVNyhSo>x%xO3RfV`wJ$2%YCPQ@`=-}Ei3;rO}!j)S!{0R zbXCvi<#Uv#KAC-E|1;%Xn+xPE1Lo32B z?rmn>bUCQddG(s;W&dMBe(H$C20xEKw$ga|RA>K*T20VeUqN8SP7aP^o6l?xSF8-} zEGha^z43%vp_kW}5}$s*i%-wlOLD(|x$%VVzK+nwdr_Wg;d{iZx5#*^`QF^My`i-E zSjE2T@I{MbFNV)OR(lBKJnn`{NO&9 z3hUtbE!ER5yyScPzw5%cLaRx7A%_gLSBpP%mtmALH4)MEtp5~o=efyizC*=cy=R~O z-7rOR`i%KY>psZ&dtRON=icTpfz8`lo5ge7k2U&VJl)qHwNd$5sYFw|ZSb=DvN3KG ze?2xTS^PM{&24G&jJu8%efJf5OP58Tnz*4eHhukfgVoo)cHAy+iM_wnKz-S@4Vw@4 zG-qF^oN4d*FKY4JyTu*$?lBVkMWk{Qri9nOo6219I#pd(__3TzwcLI=rMJ<0BU(KIO_m#eH-=rz_N_xHP z78CE}sjapveXaKN#OIjA4gAiDObn+K|n~s3b3MGr5A8>zv!?!Qk!^KAd;JyDmg6ub3yZSlWEq+>i4+B0FZj z>nnTvW~0ek9swiPwe>Q=Z*K?weq;Do+kjdb#_E<3Z8qLsryYW)2J9P1_$6qeqPrP}Q@ob2u z#Cs#5yxEftefF~2w*Bup>ppGLt=U|+msuwV&p#XSwM(^6Zg=DIxmF7;RHgKk>)mvo zRxV3XQ``EteusozP$QcZQLAbl%;pC-+s-j>qiYzu`{J9EZx!p+_Vx&I@qv z)!6>8=v3%Zf3{nT!=5@XU1mG=&iSI)qkk`5P^ep>WYf7#Yko*&r|=pccTIWE)U#i| z@0)hp?k`K-zAtZlv|j~#O8VA5OeNuLY;0_Nab@uGtf|@e_E?^t5uyHEFLup*=Siw}k;Xx0cusk5ne$>%hGyJz;rC&B z#Vh~peadv`V#(sk$Knb7!@0-{>fN#q2;8&l(QG^EMt~gdBeY&FKLIj_LeK>m(?h#{py_c`_Y^G zA6Tst?;5Dj2>P4*d*{SOYu%JWx4GDM{gPmw``V(H4Yn4*UafPrzFnVhOSQMwl}m!F zrr37)o@lx9%KA>mg2KhezFZeNyH?s;_+X9aJuA*mZ})|gOGUQ4_hSl3Ro*$JpkInf zHQVj!mNjL{HkY@GN(K}j`PRH;`VYsMe(Tz1%GUi|Rsq+u96Gf9$-NEp8kTdUAQVPvPk^ zYaeZ!l#u%MWVh0?Jqr>V#HO#`rhG?QN@Xe_+l0f%;#xGWfZr8jwX$dRaw{c8pz1~>&SpN32?lx|gcjArf zgtsegogsD~F&N+A@F3r{;c86lWu*ge;_HqD*0}%s@X-0f$vpz;7QUx*x4wPe{;pH8 z;&HQ!j>P5IOWfydJbg4yI_I=BQl@zwEhrSFgrxUaNhLOW!}S*!9QHGxCT$cz)}{4TS?iIn7l! z`hEFboZ&tG4-rZV68!>@jVPm%crHk*D{CMToR;9Mix_!FU-=2y289mINk|p&iQ^V7D@Xl|Z z?y>0ATaT=@&$da}?h?NDt*mi#G|xQVCtO?lLUXN8nNJDjn^ksbOaJo3!!r36R=X5F z@m$tBFL-!$KHHTmVuzRSOkBHPT9W6}am(4$3w}MG*s*l-Zh|$p1<-K<8+EZ!qRZIAlBH`&D z+{$_|-?5>WI~BC@z`d1a6_>BzvDQ@#m+Yqd(`{sngx$8Rq{tLEIHk^BB)~%>z z!F=I|>aMC?)s2*ioXxW=WYXP$`l)QXJWHi}l%1t`wTzb}Zdjle6q%l>ES0n22_r{% zz;X{E$Uq-tfdV8HvgDXdql-6{8Smhe5B)rE*Nctd=~l>86WC>-iJ2eLf)#5d&%at_ z@~?MC*D4mKeiJ5X^SqiRx7DR!WBI1+jPmmHXI+lG7~JT<>-Jz{noahHV&PbE4jTcc z^LD>=Vq#+hN}ho67TFkkV^agO`S$|uX;-#r>@)%N3FtZ&Tfb^4EYicb%5 zaa+R4!U7t1Iw0KV5EOJt_@47cTTn`Md(fF~Q~Bt(irfJ$dxwYIEOM?aGhWIVSkBBT z78STWlU?NQk-dvv8))rntS)(T!M0RxQrVgX%eaqy4y>tLlh}gf3@o zt=uswoFmzo+ho%UKTp%XydKxO^87Ekv2%;AX3jbBdxcWLer|K$TU8I*MQy!Q=BYg0 zb9$2BN%fWLx*S_yS$zq7yDeinYEU0=c3V&tbKZH~pZ5yWr%!K}wJuxWJKL#1Q#`er@mht9<0Fyfn@;j;KD%;m>4wi78m?!JA8|Zd6_|doRo>tn|A#efk1Nxj zE0uhhv3T`$(>cE^_pbV@bA*3iorcW}WeI=TCR61P3f$(?Vw*QU>|s}{{N5JYY$cRX z>e~|#P$GW4sQbgwXMZ=mzqR(2!>Y|2On))R#D8sEd+*orsf(jO2Jcg@`E@I7?!Dwm z!M@yX3yv;2Wq&2>!Y(15RXZnys!C*RTr*+X&VO8&LRalSHFHaM%H52KKKT>wd~b@K z`Z_F4r+(u*Pr21zEG`iWAIg;$+`LrC|7Z5m=><%U^<0g&wq`f$U)v&bYg*fUr>CDK z4?mULedF`<^N$a;a=*B?R{G7`w}Gp})&|uEJiWAbRmc_Ry(hiq`1_-yAmS4v+* zrEhIORaTb&P18NjTW@I@+5h7R$$hx2#QwXyb-(le)JOLZ|6Uj;t9-?1k?*`GT_;z& z$|rYM9Se%eITZNiO;Yo{zT72Wxc-a8G(5M6m#KOhCwc7iA^v$ELS+x_RqyzC#;fPZ zikbgByb6ylyyNm^g9f{7DVx%AwgdI9HZh0!6bsAxi;SkZ$oI?evj5L$kw10ti(Ky( z&a5|cUi0(^u*;l`&_pYa?Y*msFmp<(Vz2=Y}8U#scAfLzxP0_k<^6Qv%715 ze^a!zl{L@5*W$HQ%Xt6q-M3X=t)9v8p~A$})b(&1@5P1A?4GwbIX1H;*}URyU-enP zidFu2bR4I3$qNC$`F6egYJV?s?G|I(71(jab@GQpGG}`Zmlm-sn(lkB^=TZ3tH7gW z*=pgH?#dU{S$-5gR;V$|(|G2x++&NH`|^IlC!pz zH`$~IrK>7eg!s33RlkYdSam+D=c?Zl<8N+B8TR!{4lvGJ7t){IvhHr=lxK=d87GI! zJ3Wcmdvg9RhU*&N-*$&DbgPbtC}-Qg;KLcK8@=7j18iPN)iy1?ST|!|Q0r&T_XnoA zyFM)RdHIpA?QO>*>DI|=yPPu@dA}`~(dcYn(;?@ZeZ?TCA+WerP+x|1~2t{L*_S4 z@07I(;xafI=g;%IK~#L^jij<0St^xlR?e6twL$h#|JARLHXd8EN@9Y@)R+US_EzjY z9&mQM)_V>mr=QZ-!n_{azfvmRagLAKlu7j9?Lrr>+S&6iP5u3vSu*1B@!!pjS7-Mh z&h7d(+v|H8-*I^^M!P=x-Juuik#~+?wxiao<66Q`T3{#HOq$ zxL)t-a4Ka<`LiF<8j)*O$6NikHw_c@6?JSpFuB1YFi`N@`}_S*o}}3P|MOYGx=bf0 zCnsQkU9IP%kU06D*F83iM8)y>?m3YzA>Y`Y7-KQ5vL||b-ox|u|Bp;mcK`76`TWJ< z>*HSB-EHoZ!KXZjt?^RQeyJueKR>=lj~-2!I5BaR~`85dgky~;T<_2F%S`iROM3k1&=Rr&V0>G`fG=4%c9cGP3yCk+qrWr^&DQy$vw z6`go0fvqXG>d}P-cU_iqJpW#i7_;B(#w^Fpr?jmvwn=#Jw|yQM@`d@Bw(i8V+*aPD zXYKvjVv717eA_Xv!)kH(&cf<5_XL|V{?9WgoYS<{ee;s8v$v{~%Q?KCF7KAI3fGp)1_sMK+`1y?*4M>>1ty=I9*LS>k+e3v6}b3aQp%II6MJWE zT<^d3$vpA7Zm(aJ%@*p^$$h%Vz~uhwC9xBauTOX!VjZ=O@sI4QLxrcT46LV5m0>kc zTBB+zy?y45*-I;}x%}5QUw^UmhJKgaWZ?Y|p%yUjBZ}l#keKYC8 z1;>KC*In6HYX!sCG<_>|ce`YMMLfJSZ{oCFGjsWQWUt82{b?N7c>2zMuKB8eHJ@tm ze}46Cf77c1+e%wqraq^L3EH*S!xp{?kNJGoO3F2&TGw^v^18no%Pts49eKQP(#L1F zE(rCWnYC#BcY~~;E|WNZ7d@v(^OqhxcVoBQ48x{r-@e2uPWicXW&10UMQK^bCta_R zS-Mecj)Q|jfGJDF+G$H?-QJcv`B_2!-mhYAeKMWP=hsCgS31WWqUqc+*zES>GSD^P5C>SeSg12XP)_VL$r@Y$W*Z9Sg-Wq)2F$&ZQI7S zE1&Im+}r}iMA>QU{&MnKxKET4^RrWV-*bEw^N|@(WgFu6FyE8*(|&vG+Ka1O~GEY937ttwphwEE#J($ z%tdx-#H1Z&+1^2?(>7$@;z*n3S7FUDLwMKhOY4$*-*cV0@zA+v>!FJ(x#H4_w%d3Q z7C%o4mGRyE_`l0dDbZAy51q~n_O#B zotAr?;8QJM=V!-xyis`L)7$eO{HkHMtSq=JW(=)^(OxL)t zS{QxtMx~7Ir5eXq(~kwrzL8K=vMl99$F-IR_HUy*Ew8NJaAoa6S9O8x!`#jX&!#`B z$zv?qZ2a)pCe zfV^zOPf6xNyJ}&XuPkXxr4;Y4_-e0G*fEKYON|^KlFC>@dAzL%T1pQ_OJgp{jJ(jktFvQG8QfW#Wy$KTl`x!V*Z*) zoynVLUtO%etyFE!@ui99#n&H>@+-(-5dPJ6Hs?*xmJPNu?oJQZlFC1@b@tB_r%yXv zahmiiiLI){!0p+)jFjoWPKYP{UnR;>`PFi#TI+hfx%aQIX&$WGaiQ9D)8=5oY0^B; z%^&mVtkdx5Sm5BGu)x`2TFJeAwbH`selC}n`JPTSn%c93WBPdm&Kx;Av-0luiz+`q zn_-a1^y%|w)t5H?u@5_PS9vOYXuJRETg9sa^z}ggr8gpz&t&=E(CuwqeVgx6_T2us zBU7Ks_Pa2%*WdcTnSCE$n2Dh6GUcV*5nEct&jv7?$sH?EH;>&S6R`Syz=bZCk3~Ht z-FvKEHBRjGI+GZEw^{4&fxO^jffC({{nq8Es!2EVop>%JS}I*%sw6RaqE&->+IEp%EvD-)UYhbG$IO0MHj)2W{e`9( zsSmS+nD!JTdxY*?u|te)|3PkXsp%Wb<+}cTms@vZAGh%4T|p||Hh0(7(8|I1?olDmiD|&G^!-3m=KI@kQUCxRY^*OuzeD5CF!YF(86X%`3 z;R3=lJb7B(8t4LpG-v7ApefpgpH;oOBD|1(72Ubm7Z)Bwus}R5}_`tR# zd|k}Jna1f4Ucc5}sh_pP<2wiMVyO-f&*@jg|Fy55-1WeEPqgH0S<$6a0|XYfcQ)jF zSmig@>Vw3aMqg=j&!C^vbfd5Ru${m3=ZyBwU&?N)eP?&Axs@eV-MeScmfA^A3fljB z+~VFT{ytz%ciF^6pC`O5kp0eNllf?~Rq0xjoi_C?GXiF9@%`|Ud-4;Hpkqc)qz)GU z<6)`T*Ra#i{ltz5!nG1tcGNxNn6!S|fdz859cMouR^Jkz`PQTQ$=qLJ|5VQO{9L4* zllOh$&3^lRw*REswx2xu$)WzP-1%>M&MS=k=j;<~bKFzqpRWAu<@xR_;S*|}NX;vJ z$HRSh$;qHuA*BJvy4?(_O6q^pwF5WC6?WOpR_~Wr^iQY){0N@Hh&epM<;n%w92H}Zm->?o?qCI z;*^lq9G-T5npn}3nkyVuDKG!csk?mY?c!;FOs8_bX}`Sv$a!XFj~UM;Cvs(XFADlQ zG1^Hddg|4erw_+0TKZVKkV@=yQBlub-a4HoNNEUmnR=d}9m1NKK5`S^}h zy0t$%zTx45<9a*UzRsF3zv-~1q=>rq{!P<5SZ5g+SH2KWS#h_cb4|s=3D=*_@U#g# zl`y$T(J%P^*Tc6bJh9{|JN0mdr`G3iDK4E!SFI{DPJBNk&cVTQ%Do{pr}BNwa=*C= zsaGY|e5-7Kyso`?zBL(&!2+G2glB5BA@m>%3syq3DizH-!Gj>4)DF{d~Qy zQ!+003CBZifd`fn-WETzlCHW0?f?69di;ZT@A`I?p8mH@vuhgHKhffyL85<{&HeYR z(w#r`<->=K;_)>b=PVA9D4n%U@Ooxy785tSf6Gpx;;TJ&@yuZh-fFO)QT)1dvCg-g zS54Q|C%>2KQ@k~`!aBVEmZT_`^etnP2b+T>o=#|)H!m$F<&NCB z%3EzeSr|{~eek!8<0s#Yx6Z;=Ym+sbj!7m5yr^`E_;J{6QF;s8`ZdCzn3=dVe{C^qxeB#%gb!Fxgw_^hf!;Bl9-IRBU50&g1S zK9^tGWn&{1~+11tRCEK=L(6##cc~Nk4 zjfZN7u|f9z-f7bs%I&(D^N$BVk6mb7s2Q+b(JJWA*_yMDx^wgWcX2OpRqB29eC2zk zi7rx{w$=jfhh*GVhH7$enf@wl%i_(YoR?316?qb@_`pxGwq{A&(vS0?4q)t)#cZYyfi9kkPg&1sTKP3 zPwYf#NB7$Ig zcTw(7_3?r?H^QpSG$XbIGOz2}_c$-uY2s$}o;D};GxujMTzKHC^%uvC7_&p`&N{L` z|9PZ_Nwn8oFC&z-+4tndgC=KUyynea=GI&vX*GY75#RE4#?!8UOy+qTJL96Khc)ja zRV7v?RgMoVb3(<|$L?EwFT z+}MD7r+pgt{eSV)y88v^YK{)uA6cgjUo}6hyJBXz+~(Mh<*8*;etx+;rB+W*&6@jq zSE+@lsOW>^FZxv<+>hVKdtLMkSHTdpjc+KX?t4OEM zQY#lu`~BcV(d$1Ap&Bd7d2%io{!6v4%I`I_-pKL)oc0ByRaq@M%Wdaf%GxGbqm+4Q zamMnEZxvK7b$i{^v`-CcPuRI@9b>6w@{QknoaI|3cNEQKQ@)yfvEy4Ca4?&L46SVTpC0*x6d*C!d-6a?aY@C5tR)zEWT5yRzt(PMTjw@-CH` zI!ku;vA%zPMNKk#cc9ePxwDcJmv!!)FwNyrzvYYd##$lsm#puSn`}5)GyDC+_|u;= z^n81Eu}Q>MyF57Q)F7Vs>&wgLYipy!Z*9EodL-e}KDO=ej`T@hT)iOG%<`XJ>-jgm zO2rY%&a6%53`=*|y(mopa|c-=r!3${G!S zox9g3yXxSPn!dve=WJbZTxsWQ72WJB;+3-Ly5u-g%U)6zukFKgMlEhjd6E|xr?baK*G8O^PW?2m_6U7V=CB+5~5 zrR3Vate4WU|0Ms<`<$;MX~5-oZHrxOg@|40Tu;u(Q=Gy<+`S$Pm2KZdwjbQ^cv;3A z$u|A_&C?>MNgTL!a@Kv1xe6BVe|Tj|H%^)U+r#I}L?){&*6ABQ-8Y`Fdx?I{oHeCC zUuriwK26wnt#w7n;{UV0P5oE>^a0x?H^r$Bf{yRHk>qnGrRCS!qcg8NALvdo&lkTC zFr)Xgbx6kJi)?54Qj?dcq=Z~FU#*!Y@7aE8C-bczCeC3N`Z7`y`JL+<+C!P6dajhm zu%Gr z{kHI$HEWiz20YzZVe|0$YtLfyzMMnq9~=t~xVG%sDb`r0XeIrqUTyus7Yp+*D!nw8 zxH5a1S9Sf0L

4u1ixSL^m$oUmbO9vT&a4!Absg&JG_$TH1XM_djoYcy#%UP>n_R z^H(j}sCwq*;%ArsS9aa*V478Z$=~KuneO2yhKiS(-Y($Ze>^-{?nCqW+)5wW{?KRE z6*Z?4Gd2AFWlWnK zqxEZXd0WDnHM%#vg?|@^7ZoZgvnK|#Df~*--{BwOz2!&fe(uRpt{YYacii{;-*|fG zSA%D&{}_WUCT*Ik^JK#>3*9F(B;U0!nsRW#E`xrRPkR!adkW&@uC$4zdreh|T$yxH zvtMPlYPa?*lfSHTnW2;W+%K61X4MP661rShzgF++0{3ri>XkP8zMV5aWO7+(>Xlc? z|M<&4G9HV*chNP&V4Y%7zpvqj8yPF&b>^Ebt8d*=H}_t%oBuZn!3?|J{{{rz}ceiECsynmpP>s61b7W-ww=ib*#we1hvAu#*cmP429*gsr8 zk)3htTk4+O#=Mv3=r*qI%*ZlldSCZj`_Fvy!~EWAiC^mU=iL6Je`URf@A_u-{ZeiJ zqg|iYEh?HaZQINH1(COe#p^yDN%ysWbi2I#(eli5Q;N9bd9%C!mvkQRKP;Vi+Fg0W z5gVrGcJ-~N{&sGkZP(tkb$`;w1!_C0LVMMGQ;x>&<(mH>TGf6j&*yt7a{G6ey`3cX z@n_HZPtVf4Dt7pUg-uh@4LuevrncB^vWCA_?3NIPm0A<^q@Hw#r$4FqW*PUgIHGdn z6VH9mLEB_YjV%41?_B((IO4wT$D9z$$b0u&axM(sV0+_%xishD9W_`%q?r%2yq=uadF+tNVAZdtonJ(eQCv_!U0G`8!`6i+C!Vux#>N zNh#B+Pscr;R&>ZPHLg-TFu6P|F-21-Ix%&zuFSLhD_;Ha3thX-UFG`H<`4H7b>`g^ zD9^fj(B_t(WqzuXTKVULy^sIAo!f4xw|1%R+`PkW6LYP3?QV)L`S@x5rK^*UxJ$i^ z(Y=&*^TB?>W^w;xcdfTxD2|hFIs02G{j<745^ODiTDi0P=BP;rO=Z6KAwI&^|(QM_NL%-tFnyv z7QGi@ve_u+?8MXd+qq%wt#usIi3vH`si$Uz8u-)z0r`$H(uzB~anR4CMop%YuW0FL^A?b>33mciwx- zOuv~QQ{v~nuzz-U>a6-Vw>3`2n{MV#`+JQwc9+2Oor#)!Z54~}o|RePEgf}aDQoPh zQ1_sKD+Uu*ab5cI^I%k0(f0mY@2tH~!p^q5U31^$>qG4$ay&<@^{=qcqUlJo;vLL-^;J${%KLNZH4a64nO^Tw;#I?fA}Juuk`ekbw=pn<&)1OoJ!uZU;)GS zyt_`T!`7;>O-Wq0a$OsL_pDzxT=%mw@yj<>CeE7M#y@}4f*&4A4wo`s&)R4EaAJ+Y zqClobb$-S)o10P^(&CSdZGO(vpZW5MoPA)=@?R!){CD~Utg?~6&u3rceIieW zL*wSZPtF0(dxZ9JGcwnzvYEH@HLVL>Z@uq^mH6EDZ*Kpe8$JBKMQZtZu?4RW1pc_P zi({i+{DV_-%M+ttD{Wif={#|>xQ%S|qgNZ>-Y8)CAZ0A!n=f;C)(hnVIYYKS-T6LY zEyvaMTMU>_C4YN$b@e5QXFq1WnRjOS+KNSgE=-kIbDhM&#R8sXh0TlGJmUCZbMDTa z7^4e^ge$fs9yk_d5w~v#_f$`t)mG|%mVb`*>^oldhdtAFjml&7S;sH4)vZ33{PD-v z9VNB`7ysYv3DkRQI8i8G_J04lb|WU~t0v_iG|hW?S8BxOPOFHTxzO+3{|y~|UXc@? z>0F%f`Op0$p$(TS-kkl^B$=9<9p>&;f9kF8k$>shZM$_`&U+_s*b&WNvsm}bt9H(U ztN`wNx#QS%pYoohu-Ri|(iC+EeFDagNZB3->S)ok$O!AM@ zf(eJKatf9j9oO2n|L-^HZ*On6UyrYsO>uEeYI<^VpUuMQ>i>NFC8u4U`c>od(;vq* z;~%b-4v@aC?!Eb!j_UjkrNLgS*Uw1L`c=&xeUJCx{}W&Cwac$NBK>iH^2F-QYVPmN z-ij+Ht*SgKlJKS>Ki8wZjD2Sc1&RE zIj(u)H)GkG8;xeUw+lHv0Y<5Tg2l+md@ZgQ#koeYqFjP-Kb=lvgoB` zVM|~6kJqoRgp_eVbQ9ZAX~2Ha*7rm6dU-BQ9f|Wn{Z4m~HWYs7P&&ZY`Xgig@_AKU z+1J+{y|OYmF*7qV?fe`?wdaCwFEN{+ULrWZHTA~TmBDdQ%q1n2d^ewm-uU*p{^O$H z%w?Aiww_9UeZf%C^y0^gV50>x&n)&;#6O>9n!UvEbn^7$1-|9}dseOe({O&ieLuIj z-h$}ud6yiY{<6AO!nXHOf!WS0hd*x?dM+jG| zg)TTWq!&p39s8h%4t6Jqs@jcVjUY~j*+uO1@TxH#A;|&>G7VG_=a$@hLg_gT|f)x1g z>v6C(l_e+|&y{h1#o>F~dg1!LX|7&#r}pg3`xM7&?(+8Rg$WuqtuynCHKCORwb+|4os^tBal)q$ z=Vq<-@(=kL`l6uF&h~!HldxIaB`lYGnlkzP@oAxNiu~rgl!>ldH^WPH<(<4df4_YP zdwzboSGYf9s`>SVpY6gkyBFUMd${-eJ?ETHM_)}22{*_Ky=hUtamo9enw*y;ynoF; z9`V&$)$7k>jt@WQ?%BUNet%tt^Q;{Cx*v*8PEHdhOiHp&#eW+YE3pH}gi-dnGroS)R^71-c?|JdWjK{Yvrf*^Ko|N_=AkA#u zdKt$4d1W7X?)Sbq6R^kgshRBTmYcRcg>Q>&m~2ko`Zn?NjA(sP|Mr+j)^Q=w;x%#@$*`<59`JI7gak?lzPhA?zvH;L%LIG(`;FJ*@oh^W)Et_`6dY&wrEak{)MH~OT~?dFod$13&KMt(_rnI5?^-R{w{?+32gZHzFz zBUadG`{7%8QSaOj&yLF{?5S4oT2Qm}lE$iPg-PMPO*`kzt-0D-TXFiZd}mS2@&uK9 zxj3!cB3<__Os z=XYL;b^j{(>^mcsxCbwg$ zepC60a*mjChQ%^c8Mi}wY9H?V@PCo8p|SjfnUnOgFNa!4?PlS2y7s7Kxr6PkTRKtt z-%FT|RqgM*eCxMg{;{Ob6=v_(Kkw34NSefM_d8Pf|Fcu?4fnZw@Aa7#eYG|B#q+)5 z_vNbHt&iQ=@a{m+i&_2M9UtU6&d*!4am$x~$1d%xUHxIx|KqA(%jdV;=wJ45=CLjQ z-ldGI?|nLOe3#?xxdl<7T+1JIdltz2U{kC8Wy=&}`uD^=aX$0^PH!yC*w>2O-uub_ z_`>$@w^d66Uo7?HHYi;r9yRsgz7VsN_tCwE#eInfkFUR=f4tV`#ruroYpr{4Ut03= zoz^pco+CZXtGC<`d#K@it*(Bvag6Mdf1iyCIyq#S)~w4&PF9x8df55*<>lo|bwl@9 zs7d(C`Y3(iS4hZNWBGpe)q|0TC-wU$dM;0}@|JV!O|hvIy1P9;eoMy1MJ$}LCwRW? zwNkA7ZIxs5e|efw=#sY9$^1((#Iv$*PiH}Uun#zEW&5;CExs|LQ(g=#{v%p8hC$SHoP+Xv{gd+IrSG8J9?(^ zO0uw2C%!E?7u{jUcffb%^d+yH`PR1f3$MO%>(N`u17|jeYu>q2a8>2)ovkgCVzw(* zyi*jJv*fYCqu7kymgU{ke>;CT>?EI&vZ>9pITcITcrhu*Vu7s&WT^H*Y$6Dr!b@c!n3RIXN2V5o^a*!@$i#-?(Q>UxVO_a zu-xQgdBg#;TvfiFyzVJqxgY$0mj0qLMRT6wGw;b_jm(edEk0@0-Rg7NV9Nh!cc14k z{0me1w0k~pE>v5*Z+V^ndery@EjTyZ?VkvoeZ`aD<#V zV(*o-No3lhm7HZ0eH4X1GubZE40*OzJwJYFe1vA$y-h;Cs;aLAgD%z>cx#w4&tKr~ z@idUr$FZ3u^{LwGf9GfJ-LU(OUPfA6T1D+QxIuRGF<$a ze%lCH$^TXmjb^eIiWk*bd1dmBFDHz{CtY|c)_x|wjloHvnw4MfhSNOpuN@XuGh0qQ zh@7$4Qh4slRnAR+O_rq1X?oNl^8GW5@P+YowR&1+rr9rhn!h=xs&7m+OtdwShZtL z?~50vN4_&MFAR?D*za>M;;S!{+@{clPrfKxRlg5qsgG~lqWk#ei}KG8YaV_-(6e3O z*AmXedo>$>&3Se$<=ppL=($ zN(Zj2NP6I;t#nJ|Bga$KX$h7)7EJx-d-KJ4hgU5XE0!(%$?bY;%bN8q*;{2^9r(Oz z!iDsYP79=0y>d(Vv*?@CkJN|4U1rzqJh$IjaAxmW!3TRUy;*qn^l9$gTU!iY`0l>{ z?eUX*Hcu69or!3XRH+s6#m5(cyUCP25~;d+f}ka(~4ax|b%{HSX1ZS^0@=>xGjq zx5*k5{Y!iED@$eDABoO~q0copN)R>G`{_xrRHu__UzuMvUX`6ce=a#EDpxGbD2MWGxz>*RoHmL?-Lw>%Z?nWNb`O9!S&9{i7z&) z^lVwTdEuYy(lZ?HJM!&4l(Zq^p2rT;<%RaYLqq!hJFML(et-FS!EGOTORn?9EVAEP zE%OP6KoaH!HzP#z^A=k62>mu1Y9WDzz;IysgczV)KTcpBT zlzZ-;^L}id2hO@RY=8c7MoMIJ^PT*hU%W5=MjQAREMKx|*|9z0(WUz5FZx#2*bBX! zCuPgF??(Gik@u@7{@=wrv+2MIM-IiK4}L9{KAN(@pt~jDQmdcDMR~KCA5Sb?-gb~v zk$c%h$u|mTzMfZl?(|RP!peYo^IOl@-%2ice{V+1#)w_2;?l=|IL+M7eDURh^z21s!>vHlQ$%iR4zmQ6iXZ`&j@V~wqgC9}M?y=zKe&Ui3$3*)b+2Vb}?c8pwi z^UTRwv%8X?zn+}4n~Bkm;r$CocVM5m6v>yl;v^=~bakf<4XCkJ)T+&3^De{9{DvMd7VW9S(3$e36^bp4ELk z?kB_Erg+y2;qeLI*z*%h63Q(u-+L9u?ymIpgG7(|?~}&&>-&}@?=`r)mPMXjw^4qk zejaCu8t?k3L$x`Vv?|nHA8!n`7Wg^AI%?{Y#paxI%O7wt2#>iXAl@W$vpA-j&X} zb9ch)_NVk@p_ z9tm^FibUf+*I$Qk{}M6NwQQTAYsNq+nL$qtD(^F+IQj32gqO{|$vqHh;*n|uASl}lAZi_LptT;AWk z&1>2D*znof_XaIzc4~juS6*1GeDBs5-lq5EM_M;U-MBi>MM~G>%)jS<+1@S+Rp(KT ztU0`K^9-vp8@2CT$7U>8FJShYd5?+JgcfV(qOZa1F7G=I&sw;71B>v-v--T^+&)tu zZFwfkWH9^5P2HQz1?$eg=P)vzw|D-5D4_|Dv(g_-_GlG|^0%{K%Z%)P5H`E{W9{*T z?=KWNmRFtXmp!67_gGx(Uj5-@H~+%kh{FYK{8Q~i{ko;3 zdUsmROWoyiK!)iSr`>GJQwo9;IPToyFxkEJ=I_sUK7Q2PGyU_*<@1b+r*Lg|jB=WF zc*l)f2OI^Pb~p$fyK}38_du;on=iy76Bl$NAK?&BoW?uQ>40QnN$$jb7KrfK|L- z3$uCRB056(E`HdMIB(5^A9vIev$VH0z1q51SNpvXqaDl5^XJbyytuNcXv-I!dEa(j ze{=Nbp*!DnGHrKnJ$f@YB`;fXLqTV6wqbRO$+aaHpHy8Gm}|Lq#<%M42b}rsTITiJ zxk#ODo6L8+uQ6i!SKeu7KBzSosQkHir?V^UuAu3}Da+@l1Uwb>m^rbfBh-J=hKD`v zhdeC5ztWD_`gVVc-GXTaxtXee;}(?^mMNcIt?T!9x(Yj2wfXu(pZq5s_%(mCP2sZw zsTn&S3b&M%J0EZVHK{RKKc~uEMs}|Cf@=P2pG=D%NIxidwq1u?dC=?9 zci(wBrLN4GbZe=Wr`@4j{>v6svzuF;@7=QdUfl-!YJu6p|9h46YCV;YZB%icwQ;4t zmIw1SuDkO6i_WOtC~s?d_GYffqNw>R@1HrLD$RSY&R?-7z{A$3ui)Qz-lJ+s3-Z=$ zZM}X=`k-*rgfFgYmX|FxT9%~CeG<4P)*E(D!%@KO&0p62e?D<<+w+wtDgMW*huheA zpO{&_(4W2b>a7!X(^x~Eulwux=5crJ@q%ae(S>wjzL~V`^9MPhHHW7qtXRaHnG$tyZL{&L%h?agQ|lxICU27!+^Z)%d-|CLu1rxT zAFGAkwa#o>bJBu8b#K!)!=%`Md)Kbj?Rg$nkmWnKut4lqQ1qp;>fe=*-Zakjxbsi= z$7;^G8&}NWO`Xoa<}I&@?cS65@PH;Alu*YF8<@(8^K}a z{c^Uq_UziNCeFQVopb4F=LnBmd#5b4Gf~`d?93LcJxkVH<5;D2q|T+>qtI`d61;4v6v#(%Jg>!ILG%siiN> zm@>m(FIdu*^G0@o_517RdHdLJ%wqSMc zgC%LRZsqw(h3LxiJlgQOWkqlJ$Nv5)E0i;XzcfmkXS55aOyFRcmtvWa*S}nB{&(ZL zOK!gRTAdYpXX?(r9ra5v$#P}*ncorOi;n!5m%O1S+CZ#zQdbM}^RKU4@BN#VUYd2- z-zc@F=#P>N#|QWO6E{!zdMTkuPS}05cYMO{JyU2Yd0)dJ#%I~TXTlv>6vfp=2s+{ynA`XDXaI!tXB*Ee!5gX=h>AnvmL}a zo24%H2cKcN8*;~|E_$!+ykm!g^=|}AzY1`ye)%g&PA&QHm*o$t&Po3d)_%^yfA3ei zq_usA>UoCMX8DKT|J3$exaaVrdG@*7Y~P>F{rHCGR{o3$JnhrG1w=LPKxI_t3HGe@%G zN*B+3hm0e+7n@iYi1E75>r(YyT+k_|!qz{xBT_i+v8a(;-%Z1QG5fyPEBbP0_dfi* zN??1U5!;S=hfkgHc3Kf6ed@*)?ueL0SNBTz9RAw<_vaGLE73Q1$F%T7oqr+waLLN) zVyyFI`s4X*dTuU>yfe|5^RDT$3%lA*CSFpyRMxgvlzn&QW`jR_87;oFrOB`DJK?iL zrTyE(NxK`~$vG_C`7y^NO}n4tZQa!;7DgZW4(xQ@wf4rMiDJ9vt-2uKYAQb~G2?`Y zV7a>gb$C*q+kZfI{l_b_5@ys)y{7l!^ZTA}cjH{b5?R}JW~(b@tq*x+BqJWQ{G^tP zl5Fpy#T&~M6hou?JC&xqfAxwh+;-vSd0uzdEq(NLi&~P3P5g>QCq=gg>X*z~U_3F! zeR5!|yw8)1W%H zIo3a_`X{vZ)eWr;8E;c+O!F{&i(j;dvjsV zS(~B^;TA(?-tRV21`EC)`17iry-7La{XL;uO|N@5_Dk!sY}j`7qu`<0Q(9Es^wi9` zwZimx|E{oi$!l+XDEIu)`zZaIvfRzRlC#U@1tluJ%DZm6_W46?((8O1c*K_7 zShY#{#-2Rmz9|9L+wMttzR|r^HQ~#IB{y|{h<)}rA)S9l{pr^F4~8?=b_|`R&lR_9Y8yLy;9^htgzp=TydE!( zIrZYQAalCUk&UX!8!UIb+&%Hs=uJ(&MehoOB7-Z8sjTh+0YT>-MFY1MURkTP;&g!T z8w1VGZ@uUEWSdUT&fKGRao-~!-?>t&eo1G$ytGi13Rh+Q{V{Dq1fzoD4$)aw^R)fb zg!{kzbK`3)UoF)!@73O8&Bwb|{4@5bspWZi<<^lVA>AVvew{nm{b%W=Hm^Ag4KLn1 zxTF47P33eBP#IjV&tzVuSFWw_Q+>Jjb|>+FEd1BrNr;&~^$#h~7A-w+`l@Ka#0#3u zE(abpoi;si*iZV#L;>!3y{w_3s#f1#-j|T{`@4gG-sF7_afdW2*lu)Izy9!dR?(Nq zE`s;Yqvp=tA76c#`oY>nQa3}WI_D|Nmzb5gMjbK>CLE8RtF!FT z!c^tf2@99b*x~Z}*?BYfGV*uqx?@=4CJxduf=0+p7$hunAv`K z`baa+3c=>JyX-x8U387GEL<~jS%Asn-x-N(1^HGei)(IeE#2F;t+IXIp_3Q(OcVaF z;9kJW_nURu&fjOaKIz(v!#{NxgI1gmw_k0q{ZUnRrLgxQ<95cnEZtXK6Xkw9?T+}g zRkm62N$S?ncJ+8^aPLkH^GuM|uyxw^-CS$;(8#s8^2+K~v#Td2ugZ+DZl3dz=Xom^*B3wI>l2OV)dg=%?bL0% z{G&7UdB8oFoLknBIt?XCmrsI ztw=m{yrOH)w~{=Cc=N6e=%|I)#}K*OlNzZhq1k1e)Vs&!Q$&mF8m4t zd*?eIF}@dJls4~!(cg$KE;_ReW$He!cz4tI!Q5~0cZ1sBy*_d1$7bUbI?q|ZMEvZr z@OnOP{`F*D`I(CoZ=5^w6 zGfu2syGDU`5!bf0bB_LV*F8D$>|?{C|HUVw3|)NgZGE|QiDb}3QD^Pwf0nEG-z<|? zsd+gm@^aMTrmeB7e{HGyBrTWR-@B)_;rHTAYWz!iTeuIa?Ua8b*mg3aJ8Sv=^?viG z_-HJj(7a@U>=T8R3y&OH`ZDI+y&u=!M{e15f$RHR|0Bnm-<)YXGvVv&pR*rL*FIg) zx9e8hw|!A(2huTcg@zpt3~@9FJH=SphM-|V=xn{}_}Iz8p%b=hfd%E>R(a&i|juVX7c z-Ev!q@!OPpOTDMxo>YEvi_3%cP7UXm=<4aYU0>&>eWBOn#=6+uoi0UF)@he)j6IdQ zw|oB9mo?9A{#qaZsy}aA=6uV%SBp~a>baatKY!`bxx&W>nC9_-lga@VCSOsjogbER zKQC0W4Zd=iY5BqPeI5zlAGN*IDd|eMEB5_&#RTO!a!+P{Wxf#Tp7w@*!M z@l&p{`t)$`jpmXsCpNJi>RRg-VXUPX^G$k*>GERd{AmFWyxFl^y8lTSB>igqy^uG{ z(eZamh5n77a2g+9 ztI{Q$jVycAGTud3_PgAACSxSMRBN`zXVc}3%TgEaoY-`}r==@PvZkHxNN$>S zVYRf|r6nKl#3+{(t(IB;f8ML)go)~#775&4S9;)0$fc9nQ+93@6@T#SPR^bukFz%J zI5P!Y<_k667H_nUdt9k2HT(8=Q?GOR=`4Avz0cqFtWLehyVZ1B=KD7>3s-JZJGe5g zZO3M7ooaPZ_cVyZqxZS)S1T3c?Ho+ytBy@R8}t6gfn}NV>;GBS{eHXs_L6?B6!rW= z`zHT7C!QyKO?9?V<83a+Yu9E3n%@ok`{VN&F5jslR{w1#Y+~zdaB)xqb$UHmbd(B? z&%UKK)oaPJWrr?b7OwsMP4)cRWpmzZOcZ$4!Fw>$qx;i#jvHUoo~gMw2+mv)ZX+Nm zn)xF40AD2wuXOjV9-}YsQ`+Ag4SNy!S$W+tmKRee2X-Flb$rmsF1N!_^kfvcqXDw7 zU@pgoW5FQ1AG~vT&?j!Qb^XQ-8w8g7%{`>tZ^INHf1l0W=}MTu(qoqwZ9Bue_qx`~ zT@iCu%uqY`FS*`}H#I?eLbKzP8!Mu}@!5CDgj#=GaLT)(_q^GYko@L+<>kBY1nf&q z1t&C^-=J1M@K-8upM6Uz`S7ysl9G}?KAqMt{PrgDan9Ld-b-Tc9RBm(M5tGI8~?N)Pa1^TCEEGvT{vlqsif}&3j zZPrP)oVdSVW^%NNY~z=u6}1PMbY@tKf6RQf?Q)WhU~LT(*Xq|F-nu__eBO z-uuOpqStvdcB@ajB3I2(!uU2sjR%r;$3*OGXaJye*!`0x! z1)n;3rqqag+MH4l++oL}Q*mV9IxhV`@3ZIpxx!g9-4UABp#FOB&hf!I5u3Rum)2!u zXjqlLN_pga`{4F|bHBIuT12Jyhs$q>UEb9EpW}>T@8ey}OYB4B4%`r7y(jb1+i9Wo zuP+&$Us@`1{_JzO67Iff(aQw~7WdnAxwG>eXnv>C@kpgihvku&+k(k+O=EW3c0Lzf zbNULKcrMG^n4f1{#qYd+$;BSj`EN_9q(~vR;FV63b;-cJ}kwxxtIq{4c$@JatdEccrz1vg6Vv#Yeps z?s@iok<-qK<-Qq<1;zeN$_X{xrmpgOjhoNbM8Vf?1%X#J&PE)X;MhIWX6mGSo`pMa z7A>o|vTWw+&27iH_uB_AK5$7QpkCg0zqR+%kW)odrcHOwv`OI5m)pMd=`st)=8ZwY zb2=-eeskV7_WiW(?e;z1&<_9F-UG3!6`w3C7#SHW-tByTVP9?a<$Sl9y&Tf%IyNrT zHag3>moEzXpIvgC`?d3ioTF(u@%x!(Y+C+7a~rmE zI(;iOss43apk=9eJahJw1f3IH3qS5`OU*J`pfLG&t+-|6+dr>&Z@$`QV*c{wm48P& zdhYq}XZgab{`vRIBhwcfKYqeiy*eQ0yg7Ra@3*7=?eZ6Ie~->hsfuroeJclDc{?2l_!f|a{fu>)#`-J(TId7aN zq<*Q=c{=ybRtB@ayq2xTIZa}ZmvB1nXN)_t?WfgCTYbJ(+g|aydYiaQ75tqX(~dc4 z3Mp;5@9zHN>|MYAsap;PSt^)U>V0iI7qPT8&fQ&F=UiC+uM__B__U?mZSwBUc`LXp zA-s0(toQYQBO)2!Zr`C|Dm77Y<`>;5j!Fp~4AIYGb{4UgZg~|S9IUM0dGNj&TLsUj zh&>mtCABI4`&O3~^JwSZ-^(B0yL@?4c(mzF%UzoN@7WAC{71E>lPBgm zFHu3$Xmqpd)oJaSC(_qMFm#c$n(x@nX{qdiQBQm*Bv|8hq*C7 z^!%WhyP{IgCq!qm3X`wH0>kGqF|AjZtiQNARy1pEZout5tmkfTb>ls%9erc*yZx>^ zcKy<<==<%- z?RzV8=Oj+Sa~zRBnb+yb=1f{ECOgmRPI@RuU-I^}uM16d#SR+APIQ*rR_oZE*Ju0j z-2tI9@83uHt0Wz*xNa;FYk%7Dmc$|z{kyid;R~L;@{tIZ5^dS~`0tI`!7cyxy<2i> zk=dMTrGL8%bYy+b&suQCS7H7A)h{(&_7)whur4`1YmUs7n-`d*6XW(2Yq9yh?WvZi zd;R*%5L=YMQ5*9G926-v|;-jtzXuf6J;KDY@bvtU~*J`+A)WJ%Q`#G zMEbq2-jsLf)(TMRtuBO=3CQ+OIk(&2KAkwPJD-3Ycitr`GbT zaemst%XQQA3M<1{m8PjH1_-ikznbi+Eag{vm0db4Da+<`x$OkeOk4}Z12CgShskWP0T+Q`>^WIno;)o`_+_5#%q1w zD<$Og{IX23)!NqUd~@AV2{9Y*y*C!kXxY0`ev!Rz!q?_5lhTQYnpHjXuPu7N_+{2x_VTVjnq2ZK$Rcp-HkX2L*Vh}&-I9EI#l8uao)N;&w_FLcKwEiMzCSkhscu&~UtPf|oe%+0l z=iD9c^Su9mv?#~?@B0tF{(JnzoH>PlzW4HuTv#7`t>EkL5B&2zy9>D9oSEHJc-JJz zQdo%nn1#%&m;7^OUSE;T3Og^fc3F8&>Y@&pm`mvv0^d3#rU-{#)L$W9sSw^?qce9_ z)6>Y*EeZ!Uug_k-^4uBS{BoPjtBa0S%=#QDmUR5b;ag1C%z7VOTKPYrB0+lFb!1*{ViM zF&~~db5_%Ym)%#ox&8jl@OKInVZsFF(=V4vJ%fym3?yO&bd>{EwXOk z-_k<$`STvt-`iMxRi)yRg2-WO@%_`9_60As@H2BcUoO%te6_TG>Fr8gr%!de6%Nc= zDqr`5apugK6+a%f8|K}yxb&=gLFwyj39rqH8`FKl&!)aw>TP9f@MV#ZxUE6YA{+Nz z6(5yAXV8o|y}Y7r_v4ENZDs9WXV%xa^~>p=+2T{S7HOdW ze@AfJuM1wgOw*QKeW;qc_1PJJ(Ioptw)2wmQriVTJ}L27Z}i%)d7_(?kCJ+oQqa*y z%2W4FyukUxE%!+1E~z%bMKwD&@$ntHoNL>gU$Sf7w@b3Y+hUKZ+*+KvHjVA@Jx}M@ zo=N`{Cm&ej`S@4u;!6c95}l1)l?t2%E?7P1;^Q;Bp?+Ixm^J|9XRHyqR3> zgoqHwTa%^Ma1oHFou>W6YCx1moPYD(4 z5B1;wD}LA{taDq+jtvGnf}WABGj4yxT(g0xW?Csz_pU03 zrCSVd>b;uc;W?+Psn1hGC$c4MxmmK)`IkQmKPg^vTe;zPZd8lZoQ0yNd2Skh4VlyS z{N?r}zpO<^?J`}$_Da24utq`h`MfIA_^AQE|0$l4{Cf1HmEp_xTzA(o6tx{&xTve{ z2aiig)M4X8jsjVBU6=2%ZtS|^5++-$ApE>z-inBMtuvLQlsbZZ{?^~yzJNQ--^+2X zeQuxu*Y(-yA-r#&`yaoNsuCkUbwTR6svj)hTk79u^?bPGe261EHMk(q)Ftlvw$43P zmmW9-?q>;7kMk1LD=;ndJ#gA(ZUOJ2qZ1r%Wj$)k$g7FcTF18W!TK!*Y;PVs_5Cki zZ^L$VN8T2VMNW=#du?p|4ZV|>OQqK>H|@J>o47POeW~lhMNhYAENNH0I%%81oPhbw zU!@dpo4zR33Aa!_pgVErku6h{SWK4%iBI|TIM1=cS3*B_m&&>Hbl&H$*YEG+leLm4 zna|1~slrsx&a~&l#kJAqck2KDm9QvKFw48cG0&J+(uhSjYKwt>{~y^i|9o@rU2IZM zK2_a(hAmtC$Io&%MLEZ?M6N%jvxMxDgZG{~)t1S}*Lxr(*WF}R*oiqS3_r2`38++8 z|66~8SEGcptcpoZJ?eH~wXgQ7)y{4zO!aJxW#>}89-l0g)3xos&Ln3R{r9(|J!kRl zL%EAD{5!bK!uz|~<}YV*-|jK6U(4h?$=>b!27yoKY&auRPZ!RblbIMKbf~&qD9W+C zXw}h!cenGeGXC$&SI!l9fb-b0O;b{yzw4H}I`f9e{GJ-MMV5QZ%B973P7CmUnrv*A z)MCH=KIi9UjF;>GHb2TsS$^7X@4llF^=Ex<9xT~??NE=R*!_LZvx4&OlxocVAl-X) zrTWGC7x^3uEz7ewS1z3;Fs1Tj!57ZMDn^a9?&nLka=MnP=tf`Rp0~?o^2BLR4)5kX zD8cSK(dO3Y=fc}2_;{RdiBwfjKklwzq{>n~z1*enU4Kf&q`p1MM+1KB?*4wp#I%xA zLSV6i@x{blCVtNg)>i(!E&h1xlA0~qEA`^PR*O1`)wT%EQ*oJGrmLK4cVu-bzj|SY zSl!+L{fODecXjSqF+1m0`e`xVU#D1SH%(v37NTNse&*D^8|Nn_%$914Jl(W0sX}ns zm+<=jGrf%a#e)Pr)UKHwKP>oeMzhPUv_<*TlGn|7pqL`+waxXSg#K|gBUbqhWi~Up zvyYo*9$Rj$lylSMSKWT+`zoaxe$mY$(i&Q z=gJGNmV||dK0GQOFS4vB=)#`Q_kZVJKfKyuciCHm#6v7Kzg{kPvOa%px=zi#w-4X_ zGgTK}l^7WO?wg^)Z+*cGA*LM14=0rS4@^HJKif1rZ5t2w^l42O3bM}R_|Ck&Npk7` zj+rHME+%i0H(-!9jz7G3k<;Uo7H@Nx{LP=Jvh8iGNE=_es71=58uRJjl(m$m{>XXH z#PhO1%rNu%e*YfPrrfA&-SN}ESe>fOy?t#xQ)Eu~MK^!{7k!5FTNiuB*fjaeEB?NF z-e3b~l$`3!Csx0Dj#ddhIrh`+3d^gFX=bOS?({s;immhB)?HSZqwwVh$M3(+I}Qk0 z|G&OryP(|u++3-D+=j;*wa?!cVNIIa(zAL+c$C)1Y`K$$jiMFFHy-b~?HEzf^Y*7& z&FakeyBao$Xusfj>~JgY!hT2b^|t5VK777mL2+&U%`KOYHkUq^iTSc+?V7kX{LeP* z6N`D3y;)}SLR-fZ7RqAo60y=BgJdpVa_jtW+BN;Zq4`p?Q;Bzy=G`}bpEVWZ(x0T=f9r{9U>}LWy`TYjBE**H*ou#g4a=M*W^{_7ET(19jx3PxWwlB;-{_DSR)-|1{cZJSG zCp1;u_xf9PBV@mh$nL|ND`Gw!ZA$xa`QQ(upWX{uQ{V1c`t!0ZU-yf7KW{$1d!hQK zduM~o0#A+&#p|BWua~QYOv*$dHyE3RKS$#F8WW8)Ksv`U{1^8&IwmRAQoRc=$=X}N`cy|!iQ zMf+Jz{cMq2=giq8a(!~}nq^%}i*7}G>#W*Ya$xes%M*;h7C$K#`F9Av$fFI+t{Q+oEf*@zs+uA**oV)t4_` zoz-_;A@1~1OG`e9jV02TE?t^X_CkO!Rdte4uTN~LlX}3co%6o*ah@6{)VU*rf8*|BF0H<~N0K3^X6S$(ao;Mu4eB!5UZt}^;g|7Ff@%&oEwogW* z<;>*U$p+K*P15|Y&vNb7;|7x*7HmSZ@BdxX)+sajqgBUaTdA)m;i`vYdl{_x9_=l@ zckf<7VPRXBPnLe*t*P2s>$e@6t#Neyq=r);XP1ROeRwr&?SgG`Qa0HGp=2c_xJVTH%7vxcaHp%O7Py(?*6Dc`0uJyM?W9=t-e#V>QC6i?(miN zd#5jOSugjv?wj0xmYu27znuCT`*-)hvYRS%Ouu~JaYy+~e*LeP>JR_CTK7*V`1#TY zmD~FnZvM9xV*IN0Vg7rm*>gU}om)Py%Io}}J0*Kpt@0}Q@HziWlGV4oYBT-}`God8 zErGkZ&ezFv%kx_mHTPdo$W3Rpd_VKfZP9nl;z|B7CgAz1yt;XePmqg$q5d$dMC?v7xw5^JVn?2+9c zC97ptwyt0OeWBFW^bTfOr{F@jEn5wav==OWZ2Cf?_j#DK=RF_AZ)!rfsLZ zd38juN5Fn%@5*=0g-WS>PP+v5ie|F)AAjWH6X4wE`yh3l?fr>{Gaegp%YAwG^S5zA zSo#tjDJ?MzonFS&b6Hka?WZqH)JnM|yM4JpvPpHE?$p;NZ){b3y_@;_wY9f-Mf4rv z+_#8dH2sFjT#?_gFJ_3IEp%92T=B$8;mO|f51j5h*0G0$$;}WIYIk+s%~|#Pm;Tm@ zY4YXbOn>axZrUXD{M=mU>+51Y6?S^rOO~!!#wzRg+|zFMomG0PTO70=Y2E7eF{xSk z;`gFU)z_9u-(TPU?n-%Qg!}r()3@6n^nbG6dH=azzt$aEDIaMNA^B`|=K4c3djz9Q zRzD99XP4d0Zqu;DdH)Y*X<@o&TySnQG>w9l2t3Tdw z?aS(Y`|q63y70+p)vA{&?7aD<%l92-VZ2t8bJj9`{<`bum4BN*+*g10xcy|l|4ZNd z9-lnTeDCf=d!N0=rBeNxa@n`K=U3`{n(<_Dt;L7;>iv=TUY~q<-N=5oSk%QExgQc! zR|d08yHcUef85@731wPRLsN5S zVy#WxAB#J8Gv~`#o;DJ_@wFx)VZlnBW)tw#q``@-4->3*X}|ltKYseU{69;2KmQH+ z_Fv%7&%%^*g-z<5^I9cMU+mpbH-Gu6Hko-7wh1nISZH+Xz?sTp=eA`1s0kFB@ODL> zUG|lEkB;pXdzz7S`s&IKw^aWx$6Tz=>AJj_&ty|Mgzu8Xea{TGdlT7?gTNY?rT5OgG@%j|xHN|h; z^?lRtCLOJ>SM!(n@jGa-&hli>IqP!Y|A}wt?)ssy;^C85C5P{AtvIihJ-Kw2G;>dT z(8{K{#(RtZO_F3SemcX)^H#3mx)^E6aJJHG3-(OexA@6#2Wz>|r8^^j`&lfS?yKO|dyT41=_?TY0o zpT0=!tSDjL!}jiAx$FEx?(gfvuFVSn@Z}%-#I0pY@0Na9936N3w7p$h?)sRf^7qqC zOiPY`dS8|xB6{YIf&AgP`wlOse0#6b^Qw|Rt1sX2%BEtD*R?#tap#Zk|7`x?$?WwH zVm{ahe0-Uqv#fOgL1X*l?dw?=ntuHK@CwHfwbg6e;ws9m#r9p8T{l~6W7>oN?YH>< zwV&LV~irU%rzUJeNwQ3KmZ*R+e@<5kC z*1GIUZ>;y4&;OS^dX%KNC2EoJxy5^z3TwXAhz);{R6Xf*mAHNO<)7y&f0g&WjV;B#BvcwD&BR<%6gmdMxG zDVJ3uXAAi7uGF>3WUV~7nAIvb+GY9Eb3waJD`%g)t!=&CP`9M0^zCoo#m|n{T!gI! zsJ~jNVHd6XZ}OcJ^7-syD$?)yIPdZm>{U}-q&fATl*uhuDQouS$9(7ZM*ib2*r;Zg zXL4oQz0Esq8rxeBCa#lle)8bfk;2plb<5kurhn8|uCnUC#V5Ss)#iJLW^ZNOS+hf1 z;DX=1xf!op=gysLVdVP%|75dudq3|9m;SHKxXSXM=`-1v?eD+u>El|x-k{{7-XG%% zp$L}S-}C(U{Cj=-$ImMjH|G8l6aM|KN%W`t1KYZ3pT7LeRr_MUdxKFK=bl@a_^Pfm z&iK>+uOj7;kI(Gu+_v}McHNoyRN}`awm;hDe1303Zy3F1=INL2Vs?*rIliq=<7b{6 z=iGA^70X1{9k+9Te*E46<#ao@_R~{uOts1OlpKsZ5 z$MWWf+8oBd{a-AqZf!lF{Qrjpt9*I8R<`LbzJC(`C9bk_?M}<$oicS-{I5O1?pG^k z**TZWyiTe=aGQPKff>bR5q2e}oBm7oFfe)FKEG#irH1u3(a$Rui8k-zT66B-mWeIr z)IWZn`M&V{QTqj1CvzpYlz7`@r0U9*|9ad%zw*qd8>x&_j(@tl=lo~4bFq1y;`OyY z6S{u|xaV-3TeMi>pOQM;)#UHkx0%DgDEr(>A|c`p(B|x89aRF@_88S#Rsi+x46Ou*j~I z2^@EFIc}7`>enFQ{MHj@$Tt{|J$oY-YM%}-C@sh-p(P6!O1~;+k|wDK-27FtG7Au zUz+lB>(W||weOTC#|Z6-n!x_!&s>XVrY!reoO`pj{o@YnYM)%jaC84n=SrK~XB$UN zI(zV5{qf)``+buT#9U`x-j%Vo;AdCVjV0j=mKjcCGu>l;nPX}CcU48nC#`#V z-_FhvbtpZa=p1GDt1p2u`0jcBxN6CE{@~y9We?quJJ+QB+|Ne8-|2^l^&Uf2q2^uE zNh%$A2l>V9wjNp$cH@cRnUA|I@5tr9SKG7j7FSlwUBiE&2luJ&Jj<~saVvM(>xkrk z;Vn`^N7f1~xygP^x1wF%_1ums<5T+=g+?xSd9cpaLFS~&b;kF%zUy1-2~OaMaA|1u ziH?rmIJfEhxsB6H{VwnKS^Dz%{SOtPX4iC7xBcJC$6rzLK>qWj40E;2S7!fP(!x(n zls@`4Y9(t*-fUOxg&(&}Nx!1L)#01TyoxjhHoh7rX8Dgn-1B_OU6kgEEj_1|Vf9Tk z+m|nU!p_Xbb#|*Su-xVE_AKKSTA6bwCidonmz)+W*G}`PH9yOH^=a#|bmMll-?QfX zoC`k@ol&)=>BZs+-lfn523OZgUNyB%od2UyHDrUPmWX!Q+e1u3F+aVDJsm1q|YPF$Wr%**`p; zzW(8dNmozoEzz%%ZlNj~yxd(ag)vx&MIjm=UdAm~ z^+K`abIy9PIQ_{R%6G&$JpC5&XLHsvGuEqI9yRu*8T?Q8wAHuRbye$4pL{N4_0uOi z=eaE~dp38TtZVJrf|pO^&fNIU@J&R}ujMN1p;rf{#|OM~%28jxeU0c-%O|cvX7aPP zu@!HWWL|9VE_yXEu!QNkOtnJIw;&y5pZ#`$TKDQB-{~Due{<-4&+)1EH)<=&Sr^qW z-=lrv?nh0#yA!*$)hdK`+-oVhw{M~B{6F{p{dmm(LE_pYFWG(C%_26wd&;icO%4q4 z)%vuF$Lf%x)JgZLFYf7cXaFtJ%72-BbN`@8ZIj_X7J#g;=6VHbk&i1+vAJ|xvN>AMmMmKG$oAF?-#OxM5B96>5nHxWd$GP>$d!eQ3i!E{ z*b`orJ)2i&R_A{(jqae70kEKF!cE3pCawnU{ z>$C2DQkkdz{g$WQaoyOcDsPiH*DYct%l7=x-?Qm!m3ZX6>Q9{zsF(?|y5rBiee)LKE@+ z@Pa*kKQ^ceE@+ps&)r?7r{j`knBq8vC3w~5>IdCN9{*l+Zi4A?!&S%ac%=SXuF^a| zPbX>X)2%zY(pSx2USGCkrQH;tZwJ=zX}y=brS$UOd&f6^tNgTe-UQDX1@^ZOge6q! zr^(&zk+Y6J_D5E|uC(WxupPu+ru4TmSublfzhb49D#e1u=&ZvHWceVP| zhA$K6ztm`bc_X^aLndpTmU@1I=KVz*RQQ9_MHLn&NmprYKYT3N?kN94^ZdghPnAxj zX^Dj`Sy)>oV&lB$g!O%&t>t#NHcPrxo5kA%_Fe0qE8w(@qx7TlnQEge7uvb@ZE@FM zvBCBf+vmMT1sQ4i7gx`^Bf25{54-!+|NI~MVgsgzbo>yU%fmkRHGQVw0`rRaxTUe^-sUf#ccWg zYDZ7qohvQz_VZs_-U+a>;{NXRG<8ef?OO?c>bI8e`79o~Ymf9TJxvd-?6ntW&AFA) z-1RLd?0Rf#Y_@aUtUK?XM#ZQc^?H3RWce#4+3QK|VcTjXS`$~;ZsYy`=Rx`wn~!>S zKmL{eS#KpK%e#kn)yvs;^j3#(uI?`Rw|Uz4{eSifcKbd&FVbr#wMTxYzs`>jhxsqA zkGHq^aDe&H#R+!3J9m7^%I2GCts6VDm*e=nfMa!StjTi6Z?0T(|H*?s@oVh|(vPOG zuq|bpEVh9EWbCOs6&6~H1ZQuVeomrf;p3ltoLQk?U3cw1aLmm{s%C{=*u~9r5B3WP z$0)TvQgyt!$Z?)W-iL+z)qPymxLg+4KKH-y>Ems$OPj7WZmy``?Pzd2-1~Oy)zX_c zuWVoYPDyr(_tej3H~I3+k3XE~82_Xhz5-B%sa%KYO~8wNzuz6+`~6;bueAB2so`;s zx3}fChI#J4ysSm&@1NOH>hofwXJt|nVOfrJP~`+aB{+}w!Y3y=03ZQEf*`e z6y{Kxc~$P%RFmxMthLpD`flyB{`h*^@`CxDl^Y6Yy3NltTARH)@X>7V1CM><8J=1! z|9JGbm3ht$g=0GXv2usYt0yemR;Bzv=IGSp-<2o6yLd7A!Rv@l>GKK78lL|?e^5P}YV~OQlS_;{o_hSw*q7IzzTDlp zBTMV%qUqeP_N;%TsV~DAdu!4A&6iK?DYu(ceA9Ua*V3a)zuYv~aQ9E^=hQ76Ao`J^d_LPM*g-{ha7!%gkrLvz(VnY&rhf zDfMvM+T98|b9ThI z{&qWm@qz^n&mvw~vitv7f7K)?TS@ah@7cih;=;GT^(Xw`HLCd?>^2&%Pc5t&a-954C z@5iezWY!(fxz#`OY?lA^eHE2LcbTUrdzfzxJFjjqtLCa+3;W*(w)>3uA9j_nw5T{_ zKJRAr+!$dS<{0IlzNd6Y9CyauOqRZ&dl9XhBUC?fTK6rPBl^P4%jNq)?v3hxj<?hP!2lt`v9rtIboDUsgO< zNjxf{KQY_;H*?Aa$Vys9@S@3r#ey3Y&raXH+nS9>g2CV4f6woCyE}T9+V<=&e|%+S zu$AH$x9!@mCv%vzG3YmZka)83MDiq1_{pudNvnKw%0cbSG#{aiuN^$AT@6Z~n>_Aj zjofDuD}8`aasTKcE1 z9=vy?@tc-bxbIV)ZL9MZ9$db3lc|@{-mMoOpAU-noE_!7{+Qow@x{|zVpDcJe6Zl< z>cD+9IZ{lVoSYKod2?>CU)Unl8!T&I7xU-BN3Yo{3mzTeWMN~Qb!t*3Jh|=2gxRlCe|U38{yU98`d*e&+UYxX7HOkU~Si0O&@Y9^t@+k?(M z+?Cx=J?obH%?*f*)I8_4X6@ReqE`D{Ae-XP?)kYU($1)L@AXedtF0_!+p1WkCa!qJ z`ToR=t=4U>zB`q7t68fGA|;0hey$DbU&^*PoSR&mo}RvOeP64e_q3XFEwN4U_4Z7^ zs(WV6u3CI?|NQOIoz1(WKkS)$ujPp=+5**C0vG%~oXyqUx$Kn6Mi&RA57U(tzV5Hr z+_R|&oZ?U$)*#JI%Vn710gvPyh+fw7`$gg+cOn(J$Z8KxKJMUaru5>8#65Kdz5N_2 z>C@vrzc>-+^He+~u6U;#x5Q`n8n(43^0v%S*-yB^jnio6&yyH3Ud)EywWls?BI+Rkg?&hYZ z{a1untkZCgjFb#g(>!x2F1t-jzmer#z>{k+*`A*DtCbd%ZYx;*N&P2Eq$vm*!0dWf z8-8JL?`mn8nB9pdHeY_BDgXT3^TetEkx7%oIecF{dQ;>O+T&a7Tzc!`OU~a5re5!= zi)f$va`{!$Renl#r~DT?&h0xBGy)VS8!`!-+pT zE2|EiJILwh=T~xUxoL=cx#A)J)hl_O{_IQgm3An6rq+^Wx+6^RMB2%#BL1sv_B;~y zoT(XbBU(|bu_ZoLPRu={;`+3!S9O$;!iD zWOD2>S?}cdE2~A_O3&=8XYnXD{uREMXma0(b6-? zs%v$}IpK@C;tSrr3Vq_E?{MJAStkeGd#3D+$;lh+7Ej=_UgvP}lVXKJiC&UtTE8}2}N62-uHfT{K)@t##9TI*3$66*ljJRik70b z+@T4w@wKzVyvHjKo^5<*$ZR9%{N1zAQ)8AP&$U%K84f z?UCWIYF9X-!|!sw)IQ`XQ-shi|DhxNhpfm43EF|(XH-YSwQXZ2c-gPVPs-d)C9Z#}zC#jKe~jU|0Jd?c8ODFp=Rx^14o;`Rm82h5}K$L6n$LF zKR4jfflDq4Q6|T}e0RJ%Q(37wHFfv?UOM7ue^qfrR>>U(bCiW*owCTTMmzd!Oc?d7jC(FIVJ1hlP|v#dK2ZJ zDs0QpnRBzSe=$@0GjQGP7^J^FD*3}9r!_?#ZSB3ce)c~uo_*rVv^8<;`bXwjupaW5 zoRON!y#0gj6hEfS6q(zxRJmzC8WM9$PEtt=Y?#RO!LTw)3XKlvDHl>ZRxPo!PFu=CU?$vNdMG8 zt`1^IQ~ttYohT9|C!-WP8=m-ph;*(ZvYt#a#pWwrRrEU8

py9U^^WF6|5aD|Pl`$l@i}Rw_AWFUtx3IjO|g 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 literal 0 HcmV?d00001 diff --git a/akka-docs/intro/diagnostics-window.png b/akka-docs/intro/diagnostics-window.png new file mode 100644 index 0000000000000000000000000000000000000000..7036fd96fb450e606a3688ca4e5fce773da89c05 GIT binary patch literal 65878 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 literal 0 HcmV?d00001 diff --git a/akka-docs/intro/example-code.png b/akka-docs/intro/example-code.png new file mode 100644 index 0000000000000000000000000000000000000000..cd7e09f880537cca289a5b7f28db9f42bcad64b3 GIT binary patch literal 54274 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 z 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, and the `Scala plugin for Eclipse `_. + +The sample application that we will create is using actors to calculate the value of Pi. Calculating Pi is a CPU intensive operation and we will utilize Akka Actors to write a concurrent solution that scales out to multi-core processors. This sample will be extended in future tutorials to use Akka Remote Actors to scale out on multiple machines in a cluster. + +We will be using an algorithm that is called "embarrassingly parallel" which just means that each job is completely isolated and not coupled with any other job. Since this algorithm is so parallelizable it suits the actor model very well. + +Here is the formula for the algorithm we will use: + +.. image:: pi-formula.png + +In this particular algorithm the master splits the series into chunks which are sent out to each worker actor to be processed. When each worker has processed its chunk it sends a result back to the master which aggregates the total result. + +Tutorial source code +-------------------- + +If you want don't want to type in the code and/or set up an SBT project then you can check out the full tutorial from the Akka GitHub repository. It is in the ``akka-tutorials/akka-tutorial-first`` module. You can also browse it online `here `_, with the actual source code `here `_. + +Prerequisites +------------- + +This tutorial assumes that you have Java 1.6 or later installed on you machine and ``java`` on your ``PATH``. You also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a recent version of Eclipse (at least `3.6 - Helios `_). + +If you want to run the example from the command line as well, you need to make sure that ``$JAVA_HOME`` environment variable is set to the root of the Java distribution. You also need to make sure that the ``$JAVA_HOME/bin`` is on your ``PATH``:: + + $ export JAVA_HOME=..root of java distribution.. + $ export PATH=$PATH:$JAVA_HOME/bin + +You can test your installation by invoking ``java``:: + + $ java -version + java version "1.6.0_24" + Java(TM) SE Runtime Environment (build 1.6.0_24-b07-334-10M3326) + Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02-334, mixed mode) + +Downloading and installing Akka +------------------------------- + +To build and run the tutorial sample from the command line, you have to download Akka. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one. + +Let's get the ``akka-1.1`` distribution of Akka core (not Akka Modules) from `http://akka.io/downloads `_. Once you have downloaded the distribution unzip it in the folder you would like to have Akka installed in, in my case I choose to install it in ``/Users/jboner/tools/``, simply by unzipping it to this directory. + +You need to do one more thing in order to install Akka properly: set the ``AKKA_HOME`` environment variable to the root of the distribution. In my case I'm opening up a shell, navigating down to the distribution, and setting the ``AKKA_HOME`` variable:: + + $ cd /Users/jboner/tools/akka-1.1 + $ export AKKA_HOME=`pwd` + $ echo $AKKA_HOME + /Users/jboner/tools/akka-1.1 + +The distribution looks like this:: + + $ ls -l + total 16944 + drwxr-xr-x 7 jboner staff 238 Apr 6 11:15 . + drwxr-xr-x 28 jboner staff 952 Apr 6 11:16 .. + drwxr-xr-x 17 jboner staff 578 Apr 6 11:16 deploy + drwxr-xr-x 26 jboner staff 884 Apr 6 11:16 dist + drwxr-xr-x 3 jboner staff 102 Apr 6 11:15 lib_managed + -rwxr-xr-x 1 jboner staff 8674105 Apr 6 11:15 scala-library.jar + drwxr-xr-x 4 jboner staff 136 Apr 6 11:16 scripts + +- In the ``dist`` directory we have the Akka JARs, including sources and docs. +- In the ``lib_managed/compile`` directory we have Akka's dependency JARs. +- In the ``deploy`` directory we have the sample JARs. +- In the ``scripts`` directory we have scripts for running Akka. +- Finally ``scala-library.jar`` is the JAR for the latest Scala distribution that Akka depends on. + +The only JAR we will need for this tutorial (apart from the ``scala-library.jar`` JAR) is the ``akka-actor-1.1.jar`` JAR in the ``dist`` directory. This is a self-contained JAR with zero dependencies and contains everything we need to write a system using Actors. + +Akka is very modular and has many JARs for containing different features. The core distribution has seven modules: + +- ``akka-actor-1.1.jar`` -- Standard Actors +- ``akka-typed-actor-1.1.jar`` -- Typed Actors +- ``akka-remote-1.1.jar`` -- Remote Actors +- ``akka-stm-1.1.jar`` -- STM (Software Transactional Memory), transactors and transactional datastructures +- ``akka-http-1.1.jar`` -- Akka Mist for continuation-based asynchronous HTTP and also Jersey integration +- ``akka-slf4j-1.1.jar`` -- SLF4J Event Handler Listener +- ``akka-testkit-1.1.jar`` -- Toolkit for testing Actors + +We also have Akka Modules containing add-on modules outside the core of Akka. You can download the Akka Modules distribution from TODO. It contains Akka core as well. We will not be needing any modules there today, but for your information the module JARs are these: + +- ``akka-kernel-1.1.jar`` -- Akka microkernel for running a bare-bones mini application server (embeds Jetty etc.) +- ``akka-amqp-1.1.jar`` -- AMQP integration +- ``akka-camel-1.1.jar`` -- Apache Camel Actors integration (it's the best way to have your Akka application communicate with the rest of the world) +- ``akka-camel-typed-1.1.jar`` -- Apache Camel Typed Actors integration +- ``akka-scalaz-1.1.jar`` -- Support for the Scalaz library +- ``akka-spring-1.1.jar`` -- Spring framework integration +- ``akka-osgi-dependencies-bundle-1.1.jar`` -- OSGi support + +Downloading and installing the Scala IDE for Eclipse +---------------------------------------------------- + +If you want to use Eclipse for coding your Akka tutorial, you need to install the Scala plugin for Eclipse. This plugin comes with its own version of Scala, so if you don't plan to run the example from the command line, you don't need to download the Scala distribution (and you can skip the next section). + +You can install this plugin using the regular update mechanism. First choose a version of the IDE from `http://download.scala-ide.org `_. We recommend you choose 2.0.x, which comes with Scala 2.9. Copy the corresponding URL and then choose ``Help/Install New Software`` and paste the URL you just copied. You should see something similar to the following image. + +.. image:: install-beta2-updatesite.png + +Make sure you select both the ``JDT Weaving for Scala`` and the ``Scala IDE for Eclipse`` plugins. The other plugin is optional, and contains the source code of the plugin itself. + +Once the installation is finished, you need to restart Eclipse. The first time the plugin starts it will open a diagnostics window and offer to fix several settings, such as the delay for content assist (code-completion) or the shown completion proposal types. + +.. image:: diagnostics-window.png + +Accept the recommended settings, and follow the instructions if you need to increase the heap size of Eclipse. + +Check that the installation succeeded by creating a new Scala project (``File/New>Scala Project``), and typing some code. You should have content-assist, hyperlinking to definitions, instant error reporting, and so on. + +.. image:: example-code.png + +You are ready to code now! + +Downloading and installing Scala +-------------------------------- + +To build and run the tutorial sample from the command line, you have to install the Scala distribution. If you prefer to use Eclipse to build and run the sample then you can skip this section and jump to the next one. + +Scala can be downloaded from `http://www.scala-lang.org/downloads `_. Browse there and download the Scala 2.9.0.RC1 release. If you pick the ``tgz`` or ``zip`` distribution then just unzip it where you want it installed. If you pick the IzPack Installer then double click on it and follow the instructions. + +You also need to make sure that the ``scala-2.9.0.RC1/bin`` (if that is the directory where you installed Scala) is on your ``PATH``:: + + $ export PATH=$PATH:scala-2.9.0.RC1/bin + +You can test your installation by invoking scala:: + + $ scala -version + Scala code runner version 2.9.0.RC1 -- Copyright 2002-2011, LAMP/EPFL + +Looks like we are all good. Finally let's create a source file ``Pi.scala`` for the tutorial and put it in the root of the Akka distribution in the ``tutorial`` directory (you have to create it first). + +Some tools require you to set the ``SCALA_HOME`` environment variable to the root of the Scala distribution, however Akka does not require that. + +Creating an Akka project in Eclipse +--------------------------------------- + +If you have not already done so, now is the time to create an Eclipse project for our tutorial. Use the ``New Scala Project`` wizard and accept the default settings. Once the project is open, we need to add the akka libraries to the *build path*. Right click on the project and choose ``Properties``, then click on ``Java Build Path``. Go to ``Libraries`` and click on ``Add External Jars..``, then navigate to the location where you installed akka and choose ``akka-actor.jar``. You should see something similar to this: + +.. image:: build-path.png + +Using SBT in Eclipse +^^^^^^^^^^^^^^^^^^^^ + +If you are an `SBT `_ user, you can follow the :doc:`Akka Tutorial in Scala ` and additionally install the ``sbt-eclipse`` plugin. This adds support for generating Eclipse project files from your SBT project. You need to update your SBT plugins definition in ``project/plugins``:: + + import sbt._ + + class TutorialPlugins(info: ProjectInfo) extends PluginDefinition(info) { + // eclipsify plugin + lazy val eclipse = "de.element34" % "sbt-eclipsify" % "0.7.0" + + val akkaRepo = "Akka Repo" at "http://akka.io/repository" + val akkaPlugin = "se.scalablesolutions.akka" % "akka-sbt-plugin" % "1.1" + } + +and then update your SBT project definition by mixing in ``Eclipsify`` in your project definition:: + + import sbt._ + import de.element34.sbteclipsify._ + + class MySbtProject(info: ProjectInfo) extends DefaultProject(info) + with Eclipsify with AkkaProject { + // the project definition here + // akka dependencies + } + +Then run the ``eclipse`` target to generate the Eclipse project:: + + dragos@dragos-imac pi $ sbt eclipse + [info] Building project AkkaPi 1.0 against Scala 2.9.0.RC1 + [info] using MySbtProject with sbt 0.7.4 and Scala 2.7.7 + [info] + [info] == eclipse == + [info] Creating eclipse project... + [info] == eclipse == + [success] Successful. + [info] + [info] Total time: 0 s, completed Apr 20, 2011 2:48:03 PM + [info] + [info] Total session time: 1 s, completed Apr 20, 2011 2:48:03 PM + [success] Build completed successfully. + +Next you need to import this project in Eclipse, by choosing ``Eclipse/Import.. Existing Projects into Workspace``. Navigate to the directory where you defined your SBT project and choose import: + +.. image:: import-project.png + +Now we have the basis for an Akka Eclipse application, so we can.. + +Start writing the code +---------------------- + +The design we are aiming for is to have one ``Master`` actor initiating the computation, creating a set of ``Worker`` actors. Then it splits up the work into discrete chunks, and sends these chunks to the different workers in a round-robin fashion. The master waits until all the workers have completed their work and sent back results for aggregation. When computation is completed the master prints out the result, shuts down all workers and then itself. + +With this in mind, let's now create the messages that we want to have flowing in the system. + +Creating the messages +--------------------- + +We start by creating a package for our application, let's call it ``akka.tutorial.first.scala``. We start by creating case classes for each type of message in our application, so we can place them in a hierarchy, call it ``PiMessage``. Right click on the package and choose ``New Scala Class``, and enter ``PiMessage`` for the name of the class. + +We need three different messages: + +- ``Calculate`` -- sent to the ``Master`` actor to start the calculation +- ``Work`` -- sent from the ``Master`` actor to the ``Worker`` actors containing the work assignment +- ``Result`` -- sent from the ``Worker`` actors to the ``Master`` actor containing the result from the worker's calculation + +Messages sent to actors should always be immutable to avoid sharing mutable state. In Scala we have 'case classes' which make excellent messages. So let's start by creating three messages as case classes. We also create a common base trait for our messages (that we define as being ``sealed`` in order to prevent creating messages outside our control):: + + package akka.tutorial.first.scala + + sealed trait PiMessage + + case object Calculate extends PiMessage + + case class Work(start: Int, nrOfElements: Int) extends PiMessage + + case class Result(value: Double) extends PiMessage + +Creating the worker +------------------- + +Now we can create the worker actor. Create a new class called ``Worker`` as before. We need to mix in the ``Actor`` trait and defining the ``receive`` method. The ``receive`` method defines our message handler. We expect it to be able to handle the ``Work`` message so we need to add a handler for this message:: + + class Worker extends Actor { + def receive = { + case Work(start, nrOfElements) => + self reply Result(calculatePiFor(start, nrOfElements)) // perform the work + } + } + +The ``Actor`` trait is defined in ``akka.actor`` and you can either import it explicitly, or let Eclipse do it for you when it cannot resolve the ``Actor`` trait. The quick fix option (``Ctrl-F1``) will offer two options: + +.. image:: quickfix.png + +Choose the Akka Actor and move on. + +As you can see we have now created an ``Actor`` with a ``receive`` 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 ``self.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. While there are many ways we can implement this algorithm in Scala, in this introductory tutorial we have chosen an imperative style using a for comprehension and an accumulator:: + + def calculatePiFor(start: Int, nrOfElements: Int): Double = { + var acc = 0.0 + for (i <- start until (start + nrOfElements)) + acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1) + acc + } + +Creating the master +------------------- + +Now create a new class for the master actor. The master actor is a little bit more involved. In its constructor we need to create the workers (the ``Worker`` actors) and start them. We will also wrap them in a load-balancing router to make it easier to spread out the work evenly between the workers. First we need to add some imports:: + + import akka.actor.{Actor, PoisonPill} + import akka.routing.{Routing, CyclicIterator} + import Routing._ + import akka.dispatch.Dispatchers + + import java.util.concurrent.CountDownLatch + +and then we can create the workers:: + + // create the workers + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) + + // wrap them with a load-balancing router + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() + +As you can see we are using the ``actorOf`` factory method to create actors, this method returns as an ``ActorRef`` which is a reference to our newly created actor. This method is available in the ``Actor`` object but is usually imported:: + + import akka.actor.Actor.actorOf + +There are two versions of ``actorOf``; one of them taking a actor type and the other one an instance of an actor. The former one (``actorOf[MyActor]``) is used when the actor class has a no-argument constructor while the second one (``actorOf(new MyActor(..))``) is used when the actor class has a constructor that takes arguments. This is the only way to create an instance of an Actor and the ``actorOf`` method ensures this. The latter version is using call-by-name and lazily creates the actor within the scope of the ``actorOf`` method. The ``actorOf`` method instantiates the actor and returns, not an instance to the actor, but an instance to an ``ActorRef``. This reference is the handle through which you communicate with the actor. It is immutable, serializable and location-aware meaning that it "remembers" its original actor even if it is sent to other nodes across the network and can be seen as the equivalent to the Erlang actor's PID. + +The actor's life-cycle is: + +- Created -- ``Actor.actorOf[MyActor]`` -- can **not** receive messages +- Started -- ``actorRef.start()`` -- can receive messages +- Stopped -- ``actorRef.stop()`` -- can **not** receive messages + +Once the actor has been stopped it is dead and can not be started again. + +Now we have a router that is representing all our workers in a single abstraction. If you paid attention to the code above, you saw that we were using the ``nrOfWorkers`` variable. This variable and others we have to pass to the ``Master`` actor in its constructor. So now let's create the master actor. We have to pass in three integer variables: + +- ``nrOfWorkers`` -- defining how many workers we should start up +- ``nrOfMessages`` -- defining how many number chunks to send out to the workers +- ``nrOfElements`` -- defining how big the number chunks sent to each worker should be + +Here is the master actor:: + + class Master( + nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) + extends Actor { + + var pi: Double = _ + var nrOfResults: Int = _ + var start: Long = _ + + // create the workers + val workers = Vector.fill(nrOfWorkers)(actorOf[Worker].start()) + + // wrap them with a load-balancing router + val router = Routing.loadBalancerActor(CyclicIterator(workers)).start() + + def receive = { ... } + + override def preStart { + start = System.currentTimeMillis + } + + override def postStop { + // tell the world that the calculation is complete + println( + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" + .format(pi, (System.currentTimeMillis - start))) + latch.countDown() + } + } + +A couple of things are worth explaining further. + +First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing (in this specific tutorial), to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch but other abstractions and functions like ``Channel``, ``Future`` and ``!!!`` to achieve the same thing in a non-blocking way. But for simplicity let's stick to a ``CountDownLatch`` for now. + +Second, we are adding a couple of life-cycle callback methods; ``preStart`` and ``postStop``. In the ``preStart`` callback we are recording the time when the actor is started and in the ``postStop`` callback we are printing out the result (the approximation of Pi) and the time it took to calculate it. In this call we also invoke ``latch.countDown`` to tell the outside world that we are done. + +But we are not done yet. We are missing the message handler for the ``Master`` actor. This message handler needs to be able to react to two different messages: + +- ``Calculate`` -- which should start the calculation +- ``Result`` -- which should aggregate the different results + +The ``Calculate`` handler is sending out work to all the ``Worker`` actors and after doing that it also sends a ``Broadcast(PoisonPill)`` message to the router, which will send out the ``PoisonPill`` message to all the actors it is representing (in our case all the ``Worker`` actors). ``PoisonPill`` is a special kind of message that tells the receiver to shut itself down using the normal shutdown method; ``self.stop``. We also send a ``PoisonPill`` to the router itself (since it's also an actor that we want to shut down). + +The ``Result`` handler is simpler, here we get the value from the ``Result`` message and aggregate it to our ``pi`` member variable. We also keep track of how many results we have received back, and if that matches the number of tasks sent out, the ``Master`` actor considers itself done and shuts down. + +Let's capture this in code:: + + // message handler + def receive = { + case Calculate => + // schedule work + for (i <- 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) + + // send a PoisonPill to all workers telling them to shut down themselves + router ! Broadcast(PoisonPill) + + // send a PoisonPill to the router, telling him to shut himself down + router ! PoisonPill + + case Result(value) => + // handle result from the worker + pi += value + nrOfResults += 1 + if (nrOfResults == nrOfMessages) self.stop() + } + +Bootstrap the calculation +------------------------- + +Now the only thing that is left to implement is the runner that should bootstrap and run the calculation for us. We do that by creating an object that we call ``Pi``, here we can extend the ``App`` trait in Scala, which means that we will be able to run this as an application directly from the command line or using the Eclipse Runner. + +The ``Pi`` object is a perfect container module for our actors and messages, so let's put them all there. We also create a method ``calculate`` in which we start up the ``Master`` actor and wait for it to finish:: + + object Pi extends App { + + calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) + + ... // actors and messages + + def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { + + // this latch is only plumbing to know when the calculation is completed + val latch = new CountDownLatch(1) + + // create the master + val master = actorOf( + new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)).start() + + // start the calculation + master ! Calculate + + // wait for master to shut down + latch.await() + } + } + +That's it. Now we are done. + +Run it from Eclipse +------------------- + +Eclipse builds your project on every save when ``Project/Build Automatically`` is set. If not, bring you project up to date by clicking ``Project/Build Project``. If there are no compilation errors, you can right-click in the editor where ``Pi`` is defined, and choose ``Run as.. /Scala application``. If everything works fine, you should see:: + + AKKA_HOME is defined as [/Users/jboner/tools/akka-modules-1.1-M1/] + loading config from [/Users/jboner/tools/akka-modules-1.1-M1/config/akka.conf]. + + Pi estimate: 3.1435501812459323 + Calculation time: 858 millis + +If you have not defined an the ``AKKA_HOME`` environment variable then Akka can't find the ``akka.conf`` configuration file and will print out a ``Can’t load akka.conf`` warning. This is ok since it will then just use the defaults. + +You can also define a new Run configuration, by going to ``Run/Run Configurations``. Create a new ``Scala application`` and choose the tutorial project and the main class to be ``akkatutorial.Pi``. You can pass additional command line arguments to the JVM on the ``Arguments`` page, for instance to define where ``akka.conf`` is: + +.. image:: run-config.png + +Once you finished your run configuration, click ``Run``. You should see the same output in the ``Console`` window. You can use the same configuration for debugging the application, by choosing ``Run/Debug History`` or just ``Debug As``. + +Conclusion +---------- + +We have learned how to create our first Akka project using Akka's actors to speed up a computation-intensive problem by scaling out on multi-core processors (also known as scaling up). We have also learned to compile and run an Akka project using Eclipse. + +If you have a multi-core machine then I encourage you to try out different number of workers (number of working actors) by tweaking the ``nrOfWorkers`` variable to for example; 2, 4, 6, 8 etc. to see performance improvement by scaling up. + +Now we are ready to take on more advanced problems. In the next tutorial we will build on this one, refactor it into more idiomatic Akka and Scala code, and introduce a few new concepts and abstractions. Whenever you feel ready, join me in the `Getting Started Tutorial: Second Chapter `_. + +Happy hakking. diff --git a/akka-docs/intro/import-project.png b/akka-docs/intro/import-project.png new file mode 100644 index 0000000000000000000000000000000000000000..5774e9d412bef31654a802129329c888b8b91466 GIT binary patch literal 85573 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 z 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 VT_aX{jDdlH!PC{xWt~$(69B3E_YD95 literal 0 HcmV?d00001 diff --git a/akka-docs/intro/index.rst b/akka-docs/intro/index.rst index 70f6caf3ce..8df1a87a5d 100644 --- a/akka-docs/intro/index.rst +++ b/akka-docs/intro/index.rst @@ -6,6 +6,7 @@ Introduction why-akka getting-started-first-scala + getting-started-first-scala-eclipse getting-started-first-java building-akka configuration diff --git a/akka-docs/intro/install-beta2-updatesite.png b/akka-docs/intro/install-beta2-updatesite.png new file mode 100644 index 0000000000000000000000000000000000000000..4eb85682ad5caf5202105f7abe0b2697db7829c6 GIT binary patch literal 136664 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

nn&9=ru-}On&T|3c=Ef`&ujY=O|tELpA{9H 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 literal 0 HcmV?d00001 diff --git a/akka-docs/intro/quickfix.png b/akka-docs/intro/quickfix.png new file mode 100644 index 0000000000000000000000000000000000000000..f4f2811e52b4640ae899eeb427aac5e5e845e2a2 GIT binary patch literal 28896 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 z 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 E0B-%PfdBvi literal 0 HcmV?d00001 diff --git a/akka-docs/intro/run-config.png b/akka-docs/intro/run-config.png new file mode 100644 index 0000000000000000000000000000000000000000..912f958223fb343cba596aadd74f76fe89b9a0c5 GIT binary patch literal 126906 zcmeAS@N?(olHy`uVBq!ia0y~yU~XYxV0y{H#=yYvI`{=Q1A~}nrn7T^r?ay{Kv8~L zW=<*tLj~j2i1ZMVv)k6r<1l_=@MKQ$Im_pBjGfuq=cs5aX(@QBu(PUkb2T+-aCjIr zD0Flwb#bJL2ry2({^HZ_@B6;q+yDLf)u`CL&qL0>oON^guPZkh45kYyD|v88F*qGg zT9ngUvU6^1%+8IU88{;tWFIivxZHoiA)B3D{9*sL_x+3|20uI=9k~AA>TSK;g0G+Z zzp*lGYHP3kv#8cNu}1V*_CCQtLk5LciGK|2)R>NW{9xh<{J@l0^NE4|-@&QuH=gtF z`%~SwcdeXFx&(s}my)5D0K*^W8|P%7OLP1_u%5ADA@^r)KIR5K)@0xN3X9|!&iwiy z^YOxk56m)*$%*spKMViJpW*T4=W+Fk5i{jCS`_KGgSulIjj5fR?h5V56hq9jk&VKf8-(xBT?#Dn9LX z`h*P6|NWWlzh9qEx}IDAUb@5Jr+&1L62q%^1$S=$J^kz`>w&F&3=)O^H+{@JQ22+p zL7KlddGVuvKboB-8B7i^W*uO7ALn;~y`_<50&AB8Zw51S0@s=b+XBXU2gF$#0|Zzf zIT~4T89ADrU{80nT)|knfHQ_g_JPz3=IsYLcCgDeMH=vn9W?sEaP2_(25v_Iu|+K} z6vaBYg&K}1M0!YGYT;2RJ|R}rxM!h*22WS(q=m@=W+5C?ou4mEd%<;u`EJ{>g_qM{`_wOPyEbm6YR!zRFtUg zlDOOO&gs76`9;$&-ZzxsVN`AjJS34EW1u)g#fNVhS23q?!^}fEiDnxkbhyKKRyS@v zB(%}+hEI;t33lRPJ5P98dO>15Z*s1tb;eJ8J-@OIMW3EUG_ zPl}%Sd!qV;=M%3_{yxcnlK$lPk1`6KEO_VG>amoIgm+~hzkAU3_}a%GE8_NW-D8vQ zv2W5p8h@<+Vf@4GnlCh5G{0!v(M-}T(v;F<)0h{uCO9aFD<~^?SJ1J5XF+Dce1Uoa z{}wD-K52>3!cU967Oz_HYT2x1zkGcCbRE_@zxAB!5$ki;)7JCv#I`wZ6WsdSJSKYu zJ6?7+cHivj?XT^e?JDgx+xfPWwcl>9_NBs0j2C=fe0ss^#nOwS7r9^heDU?g-5S z$wQGxD!V_+irY?C3pWTC47a~to#&P(n<55pO`;le@^~c`{z8%3DzmBO)Oq4Zmio_T3P0@_;bVw$A~70 z83}rcWQkVk`Dm_DmC=mRwoz^p3>33e2~zzOAfmWRK}sb`(Mq#TAXtQ3Y_-sAA@}ap z9qpahyUu$Cc)aj9;DUM<0k*nIja9uD^$T$>#KK^<1Cd}eOrBJ^<=Hd zdY837`-X*F3*Y8c=GGTJ5BeUyK0N-? zgR2TzbFN2Rd2;Q`HJjx-R~%jJdS%tssQJ!QMs6i-Z?lUiLXMJj&c_yLY*_ zf2h7|zJvVA{3Y|3+9x`sKS&73+mLV~TO#8|){e9#2~$!`5_-(cjP(rW89y}mXqssj zX&h?gYba~B*kH4_K0>z_bm7F<7vlr)oYhKEq6`V zd@l05T5pZsLA~p-8`t%%<6Sp9k1a3#?zFq2cZ~1KS94U#e3ST|@>Qks-j_w+l&VUr z7Vn+ACwfo4(AtizM||al=X=dJjkmoobf3TOR-J3z`#+|Cxc`PTnJ{rNhcTNnooCs{ zeThqp@0Y|H@ff*3rbjH6sHMD~aCPA^=ie&omzD->3V45Ep24mS&WVQ~&Ux5=c)8LM zB_m}q~^ zlyPWf%T$-cN4Py#sN7$2FlFA8#V6MXl?0tz?zGZwWpidtrex-6_ntlz*`u$d9S35q`T3yK55zfvrA6(oSI|3ux!yYl~*YrPId+tEkEV=_tdA;yJqXo zjh*w?__+D=cbPLUFJ1nuIuYqW25)HE2>>}d`tR<@U`1tw>{}SW;btN*X$ni&v_5?Qt#Y)@Bik$ zOozme3hNKwE-kqJ;Ln857tU-r`{9V}X6t0-Hf86cra{#|M!>bumZs;##Vsk~vm)UGr;D*M+r<-hLA#%jjj({$69%URph89le# z{^Q8|ldpm=U0#3w(Y&|k2u?y=qVc+2KZ(*K2jZ!Ymq zS^s_d&GpZJFZ}&m@QB8v_nR4n-U%`?GSp?M^%vhHpGYWk@cMA;oLO@% zXXcx|GVwbqcKB8}RTlq{`uUsX7>hi|UD0>qJ2j$orfI!XPgK*?b)E1_^RL^pb#Fv> z3jGveU9@Q5qyUJI}Sen{lt>--d$|9_B8NKDJlw z?y~SRx6VlGWa*q<>$S!^=Ty$>w|Y0WTf~&TDSB1-uJ~oSY>9M{{5Oe@GT&!?ny0?+ z`^~S5zdh#;;4tAV;^yP^v@DqwB3os+#=getQLKp4EMqs_YnFO?`#yX)+Vb(p(j|LE zoL0Ha`X+dK@wu1tj_)zv(_fulHTQ$f%feH-r&Vto9)Gu`bY1AY)P1py(TNvk9(IjP zU)`lYm1m0I)V+HD!#Y22Tl^3dD1^xpfk3UzJPdgV?N|9eYV zZ|=UDcV6zt-=15!x;r+0*Y|b7Up9Q7PaCBBb{}*iNwNe?ELZD85~sE$*BC zy*kq`RV6R)%|7{b<@Wxtw6OGhXZOtiY4Er3)#WG8H|p=re!fqw);L~$&H4P0{Hprr z|MzXW`l)$~?$*D#&tK);@qaqsy4t)>^?mlc_y5`dGp+dkBJzyy8`n*%pVaQK{t?;r zJ*4EyC6jksj=!{eFIxu%=xx6^y$hpw6F++Eq;R^IKW?&Mta zbzfIm$HZ4GT${M$W9sF8v0Gsi&E|HWd;4&@k>2#!%-1_^S{8nFy}tb2{Og6bXZALk zKlxpf7j-C5z^vd)*~^6E7oL1rF8F7~iy0Sp{J;C9+Vp$AO@+3BQ>g;Pz=WSQk?hg~cUt6{Rui)R# zXU3n!znM;3|6{%MeYth!xl3|x)rtSux3~1a``5B3efRVd%fF_dkNc+oz4qk)WB(8S zt8bfmgm2Q_hM)6~=?CWo7Y1ivnvk*j#jzLVlBt}HKB8xW*JPN5ek#O}+eT(j zRL!|RDvc}$7dyGP2)lYLdY829(J7&tNfjq`SGvC3Z8X>5xBF9O(KDh;r%FsQoBBLt z-Rfy8j;^%MI+1OZk(YHgyZ`l22je&^@l_u=v4 zKgVZTyrL;Y`j$+Y>L0fw8BcPq6nzQPsg#M{^CPjB<+Mxxk_%6sOqsmWH8a%xoa|ap zefzK(g`X<2(!O*an7nSe&gPO$Pm5Umjh{a%`u=&}^Lp(cVL$$!xffYt=*sD=87-N= zv!FF)Y0Olwt!cggEI)tl+iF)T_%&1flhi7y=-E}X-J{c@MhBo+;0F-@_5lQ7^es;gKO+$6h^q^L^pk(*-Ok?~lCy z^>@p?;Qy+7{Ob?=mzRB_pq?PYxZ-`2aFaMg+I)r;Vr&i!=VV1T+)MD~Y}m`?&|=8Y zQX=c%Vf1IRy+Q-ilLj_}!&wt00+^}}uvIh`Okle{W$Oa&o@Uk!j1Lv#Ja~&5lorVB zkdkQ*6L`X5*dnSV7T7A`y5_LxTwNpKO5vZ(OIy5Lco&?!`0Yj17xNywWg?$hvwPO6 zJ5TyK`L618)%AxS7I^N^|H1Ibn=8GmeTv(Z+K^2lXRqwJ5^9oTBD!VHmeP`>lGrc1 zz6A5Qw5{)X$--ulztYgxSU&0CCX)?P8~3H3P7hCve{|w`$n%(|T92+h`gb7sRQ8eG zXRjZ7uY5quKs!PsMcGH+Mmvbj-FW+rNfnD-RxH``WZsn>C7nJC)^y2Cy|bxizthx5 zf=d%p(?z*MlTDpVm3fVQrFG5UI?T1WJGpA=ueogBd8YT&&3NE@^X!qcraEhM{;rv{ zwma7BsA>j?7;Q=dQCYJX7qzou~I@XN2KJ=^x8Z0+eQ;c?|<($Li0$!#33<~-@@#N@Ws>)E;1cHQ)Pc#2QU zYL!uz(yJyZrCFDvvTmta>FrY4oBV4k+jZXOJ==V5_*^}E=gecBPdd_T+1AXznR8R{ z%~aVtvi+7@E$s`B?$F%DULjI_@8d0nPrq;cp7-JP=Xm}p99(>PT$S#VIC}YZOEz|w z^k(%RJRHHn%|Mx5X66@<(?yudn$iv-C1=rO(ghTy=9h zwWFG6{tpm~+7~x7$T#$I!MzRNifdDwLxp$k+;94GQPuB%PpxHlhR5z|U6=cf=f1|C zmsj=eHrQI2#h3|MEvujY`QE$cpC_Y(zxIDh_doO0E_eR_9SPfR>|e6eW=qz`e?OOZ zn10IVoqa4izUf|MWUaqB2+z(=RWfMVJ1QSZHQxacT>P zd{`A>I>%}*+w^5qrp?>7+OH?C@t)}4$mUq(x@7IiESHPZ%Fl%BoLu9wMmJ|suJ#+Z zx8|~}b7SUyjy-la;N2~`lk=|cxz-tEr*c1Rzdz%qMovd(#VhSLp3@Isc=%56ZXnOa zkB0h>cph5|{}Ae)tQpj|yiqyx<%1g^AOARWXtK`oRhxGfpL<^Pe6IEb-5i}sHr0Dw z_J0YibU2z98rXYg=geDIs!np}OTV7Y9Ibk5+pTte@luw!t1r^_Y`gaPf!pb`XX@79 z?sDGouytbJ#>wTi@3MV1`2U+#^*5ttTk*PYnJ53;6n`H7ZEw}@w!dpnXC3_=KL5x* znSWOM4*sQL70(Y z)*J~21_cIB7srqa#y4}>=Y(AS`EUNrxo=ySe!mwe&MIn>#ge3C)^kwIadD?Zk6@0W zlxn9G?=h7J`cfBEA};Lb5>pVIq!FRKLcy0&P%}t;+xC~A>+f3^m%ZmR-n{vA)0F2Q zb{1Fdyl;Kx%;xu+eE}LQRzeDlsHpLAf5Yb=yHoEOa!z1CMGZm=6rTKUVDUgjeZfp3 zEyrIrKhK?vrh$P~@jkPP0}~1=m?N}6!NyPCCVw`%Ng5nKATiklrtMvZD{)ac2QC;nDnL{S`(Z>$a7LhvNYUWL1sL}@} zxXhwE!KX&Lm{Vy!^iE2YOvZjmh-J~y=#o44+>$>(nr44x(T53~hthnHQy?mBWOMJ?=RL}r+EyO)HVkmC&xLkaISuFGfc`1rQK*CKGkr-Mgbn>l8={8T&tqb;o@=JMjpiykj~r+Bku*-h3&CPN9`6N?^KE&gUAY*we+ zv7$)LHq7#D_w>A17u8X{uFzP)@`!Uw#LLc0Zz{iScxi5>Yn3-OJI_0DGuNSJf#$M{ zX@`!PHwYbF{Q1$@sHz(NMe)X%r7sDK0f+ZQXP?LF0y@|M{(DoG~pJ{ zu7ep5)B0y``1mz#GN=3W?!Mc%FYIwyXP7Fy^{@?E>?u!o*z&>4B&a&FI^wlT*X_?q zt;tJg7OI@ue9C0HMvj={4WFb7ddEPbnmm*9YM3w1yB;vhc77t7Igh6|#Pqb;_ z(8fz!VtxCkr+>N{o~|$MchRMA>qIp+z9<2?B@!Wm>EcBj52@+S%g?j5y1!&~{wJTP zk~>Q<{8-27!oB9jj!A}@$IQ3*>`}abuZ`3FdS~9{DaJ3i{yW%ps9C6`@JPaz8=HtPL=KiiLa*T@6Fr0hSPjX{weg3wNR6^}*%zDP+U{#Jaed{=qSVtG8=$5{nR_vfV_$_(26l$qbgAmvDh;QiMZ79K9Ybm>x6Mso?8 zRVoEb1V2>RM00U-Pw$GZdB692oz`RRN#3lBtdx0@TYaN+?#~mGy;d>#y0F=D1rsH| zIJ_Zs(V;+t*V( zH|A-Pq~Y?Y<9a_-vYuw{+Uw(-xHkNC$YRq}-NeZ(iMi`b8I@nW3VPMvbKr7F@UHLz+nA4K+mlvPT zm{quJO{e+Ed_KpF{W~KPeT`q1-CyGVyGohM8`NFeB+JK?fz1J!3^)(NF|8^hyR*n}l-exY^mN}(1HC1(6&dsJz zpNu^D!!PcttgL)-bF+F;QPG-jS4{2%%ubtg^y%-7Igf>MZf<6r=fLZ9P~cMV<_oL; zGVsV72(A5eOU3lu4)X&%Xjv_Pj)Q{03%%xm++>!7EvW`oJ*zB2R!GEu_Km15RkOFs zNj3Yn^kO!L>lYVo&FVWXZ}-{eu&vEJ8Dl$LH~O3BqS-;O7y7B+-IQZ_ZO# zcg3X;$h0dOyc=43@#JSUZx5?gKYJngY<~xYzC76KyzbD+z3t`}=XT6BYz8Na2M$bEtv}rRy*l8Tuk-ZD=MS8W zE#lWXECTg=MjDe*?w63vqV5>27nAmuh`2G#vbvZl6`p>oRk?FbW?1JhefDfclO+FW z)5P8R{IiW_UAqvPtr+4RtF(ODLEY2SA8)oSzF=xC&3tRC`~LTJ;&=6S2?!)^|2zNx zkz@b=h)b7Ot~I(<{NDK71@2Ah7*EUX{ zHf>?SeY-c>I$KVyDBX~Jyl+~le%xY^ucswGJbr#r>T{ct*Ebo{zghFA^Zn7wvI(uI zzU#E+Of~=R{rCD=PtV_)Cd&V^KJ8H9vDe)%L7DqP3Debz`fu?cuGu;KV@cb2PJK=@ z_c{5#X&>#)X5X8;^5xCurJv&ty*VC#X!^u0cFyMxx<0P!({3v_TRQ6hsCPt|HM~Y#ddls?=s(eH*MX{yMaZOs@?6Z*8K}|A? zRI;K;bI8Qyvrhh)m3OEt{<5znulX%gFM(ql_-65y3h-L$PCk=*WWlsjGZl{0-`^i! zw)67^YgDVWg{JB#uZp!`r&9yD!>!Y2kR%yKv zKDb>fiWgh%o&9n6#A}22ch9VY|Fzujf6?~o z?CX#7r@uQa9Us@fak=`x!wE(LtG2HZG32;o{y()xen#_za{YO`-=s(U`@3<*g#+?( zlQZ{grh1vLKVbbj>%-gbb53Zt&Dsro;D*!zLB|6xq-A^B zCjU(eemMQaN)yWkJy~XkLUI%2)-QD9JXFzp<5FqBE1kO{eAn81qjcgAt}x!vGk?)# zbK#wD-`6*Y89n_ZBAEVGMu|cD)P!$8pBJ1c@e^&4jL&c1yh>)}qD?`;mjmB@b$GpH zUro-PwC)t$q}5?-bzUzJtgNeNcqi2#>VBPR)#(!_Hk`_cc>UzrvH5jdk`Dj&@GM<) zoHIA|SEkjaOR^f=Zx1aivS)I%zq2kW<>~T6Q?EL)WU!fui95Y6$tZ3$^LGcej@vsM zYMz~1{$&IAx??T!Uyk~k_aC40G5z5||L+TY?E0f+ngVOS9_yYavFFLZ>mN>}+6D$b zcJ}&SCsB8BYQc2TMTh48s8*P%xYOv%>pY2H&%;mgOfceH{-CQhK4H&to@%czcOD#m z?{&?l-Tu$TM#uZs%+GEw{yw$%!_m^XH@9PZKA4|NU$mloCPCVzVTlj{VBle@E+)Ai}I z4K;Lm-^M0w^)6I8{IX&7DyF{mW|{FG!mHzUEm?lbzTLRP(c|#Dv$KWgrLTC+e${Bj z?DI3uCY8KgBH7($8r!;kTA<>Nc%7+g_g`7OTDl?4dKYg-yV=|5lUITyd$n2hB>T@t z_AD@NSu;mu32RYm^BjgvoBphjV-OL`^}mr8BHlBJfpda`u>eQg|0@xpYMbS`mhLq< z5tN=#sk+B*W7N_s59Zqc<$BHLfAG9p>E>Uzzn*)%BmXGFpQqabjn0}(vd@t5TOHN2 z``@Q{&CA^8|351JN${v%UGsPP-iqHBT^;^M7u;5zyP-PXQ)Rc%w4BN3-+xG7EuJlI z-yOZoR{o?t|Nfb!!u#&(-|u=~^*q0#B-Z~o0$t-fE>KdHtyQ>5@zG3!*KI|L6#9 zxVN=PQEu;Fe~#;H?|%K3w%9*?CQpv(NpW!1(CF;%P}(opa%*;%gRps(=jC4GB>&HE zm@Z5){TXGqt0z-A(>~L2#j@jUQKmnSnIH9ysoh&xD%Dq-xi4;6j+fFz)u$68gPO`R zSDp~gjt$G|GR?iLsl=;U^m4*e{ac!h_H&Z1a#z)4sE6&l^k`CxlXrgT+WKAR0`z-l zd;bnmTv3?Zd3BSNze!mAEa|ycTV3{b9KRAG9`w|`&wJUtWh*vJsc%e}m7}N36u5TQ zOQUmsiZ!uuvo`q3a?65B3ln|DpDvTu&lLLc*ikCuFZ8J>|e)Ieyay} ze`{Xb;VR>{EQ)Q^Xv$MO%J`|oMmsGjm*@$>&3*6MZg-_QJ0 zck8s`swjVWx$^wu{ zxBbyWl|`|2{AuSUyZBx@icH;Q_-FZM!>?cBZhSxZSfc#=dV`o_CYz31{y2N|@`u^2 ze^d8-Pt1^xY+LY0Yrnv>xk@^zQLkCAKg^72RDW*Yb#L9-8KtjTV#*y=<9?j4*y5kr zP?}&pPtheZiXPwNE^$G4t`Uxj?XF}KV1 zs`IRT@#bYc)B308ebSBJ;(6(^+hnnpuT?9gE57jF=+2D$vh?S&zLT4|9jYTf&*k-% zyqc_XX-QaEx6G20Rd&nYu)Ml(*Ws(sM78IE2NZWVS=})`v8qt{ZlGfPE~NuPKCYp! zOFD3mZV+EveW%@SRQX-I}A?Y8DUI}OP(>r+k6u4sZzO9us?fw@=e*p0UO=>?mJfe zO55ji*nGbKueB^oqCYk~iB><}_sjI&{ExLePTkEYes}ah^y-cKKXE;AmtG!ZbkQv< z|8wYKwR_L!mUG7m8{T;T?RmlB3w#=@IUn7b{qBIMzuv+6u(tx`%G8X7rX!JKki;y{$VxO&) zN<8_nsAB)0k3|_3xF8UdaE@wN1PHE^yc;YhDS`;tA!s_D^8ahG|!gI&bCNUbMH=EU8V~ z_nb_LVz5j6H0{`Bn@{Nr7v}93*W1sYx18UveT{aB^#!y2&WFs_cGV~{3Mn+EsS312 zy9Df?ou0HT=h~ms7bkxREVcahR!ea8tX(IjTsk3t)I?Q8h*k6DFUb}Eor3;<`OJP` zb25L3^3>3m+3Wr|`knT;@NQ4)jrE28zD?(?E$zd)N+#YEuJ&G1SoqFXZ;pXbMdekq zkluHS$rt?GPd`ig9T=fC_lNev#X;gpt>vZ6}yw-%ZGF#p)=DHnL%ha<+ zLh^Taz}ong?}9e+WLf^)Gu`pD`kt@8+Ok*sJ~#ZHc4_(d*FBeFTLph|y!mxs=8vn{ zshaDD; z_WsP*tV&+@PwI?V{_W6OeVdM2`#s{q_kM62{P6Pmf7Ddsg4NlqW_?go+JULPw_$1a z67#=Pu5b9|_Wj0XpLr?KdQl8nvb#SRfAsjctkRRy$<$Kq=AONC+TKn#OYA?+rOo1N zenCp4W#%XG^NVV$1t-MXU;dQp*rCUD@v-8jC*2`OXLC5s{I6NRXIiFFQm^(TV@_i> zo;81+t~znp+l7#>9zj32yZ^ zHlNcaOf$9Q^8Tvdv33(PUu`gH{h|pz7y0X-v8FIjQT=qYdY9;SmW5NePTpKUZ$XXR zb*rCyLJmsg?K2aVyjyV1<3Z!`e|`SvD+HR~hWcpEyt?qw+8~jryU%kJuX%6(oALgA0aqnBE&TzqL`nOXbSzboh8Oyj8%RZR^t&bTkzoVVsp3gD=%{F&%QPke=pztSG`89jCR`0h6O4OiY$)(k@6)R* zr$3xIz3VpTlB6R`et$eLY2}XJuYMOEUwl0=WBWCZD6kM7C^SAG8) z1ez_2y!170&P7w++0*v)i7%bH+b@fK_1%Ky98s68Rh2IVY6!@=D+v6vyJFb7`HY3> zJJ;oxkH~bzKR$MUr$J?e*|hT&Qm0j(Zn<2gcsT3w?H9k@Jj}Q!o7IF`Z%iv?n0>E? zaZ%#Mc9B z<|d0bNre@z_~kzL*Du|K@20kYSeGr_u=)Az)3g7${q_}X`Q~{1b-?Kx`nu`;w-1*& z%*yLG*Wg(Fq}%xWo&3cGSC^j<)Q-Kb6S;VKQh-iVmSRaq!DY?4w^y$UeBtGK?q>Qz zud+{Ters>$Tf6VF&0URUJ}fD-Y8; z{h{)q(wp-urxXjM?(Kb}r=`5n>iT}Z-+vX~+>UI#^mnSnt*=ZKtM_sqkF3Aa1L}o4 z6bp1jFeootvTKFFa-To8$8`7e_}}|z!xgtrGX2jRwS-giKVCk1>Hg2)g7?1i4-WRH zXf0Kp^XmF_LCe^O_7|2~^hj*(H*R%Bsh;Zvnf&VCrk#Gi-aD;zi$U@PB_#$`*R4}k8=9mhOqn1PEF#u2 zv2a=2(dmotmVdba{F1@z_n$wUuV=bn|407ExjoVwER;p-RxVg;`fcCDOT6K}I|U{$ zyWgCh&X^us&YrjHgRf2D@BRlrAIrp8zR>xz<9+?ZKa@yjW9 z-L{)IZ(b7=j&f&RCfMBN=DPUy+q@0FvgxS>_GY5`uCu4zEV`B`w`p_AoJ;Ogb1H&E zLi!Fp()yBbEVT2S8E>EQy&p6EKqbDf`U9ukd9f27Y99Kw_u<#7g0#iO*S8hTTP^>4 z+j|F%{mN0x*#lJFH$O|YOg+}>)97ECeEge}rSW~G(Ug8!s%Fdp_k zJ8g6P?Ucp4wn)fDzI#5&^jf6u=Lc)_4z6>$dq#~n*XraOu9<8rf4;m~xoP%GyEhZh z`;=Pd?q1}(Ox=jhv*6jH)e%yAU9C54cXFG0nJ@O%lEoRvuTNO9H9zb54Yd@wLBO1Py8gAyM<{}lkNL;CZ- z8?9u~+$Z#AwWs^?bGoT#(?nx7Sx0@G_xD7`ZQpl?H%t*Tidqo-+oob^&!)4deO+JP zyQ?uZHZJ@9!Eb9`ZJZW5C3e?VM~8nEE6;o?y1eAwI*-`b(|&+!N|gc$j++0!?_bZ< znEm8ZrIFUPFV1&b7T2DI40M~E?24FnDAi$Y1c4;x|!jrGRpVD5hV(^=b9H%^JaPPqBX)MKiwtFh~Wm8~0=dF^v? zm3byAvLIxiNGQ*$)$0piZ_l}PX3AWz&^IeZFL2oFX0|Ny6TZ4(onpvzpV?ODWNvCC zT%I>o<9qIOd0QuCwfUY+Kyg zy+1x2txu9ZcQly$y_#q6Bvilr%zQ&1PrFZo>yfH6-cZW5| zZeeiuymc=x_r_kCwLI9ybdBHgqOh=2jK_`s1|MFQbBIm#a)*;`MO^Dbx%`FJovSWy zmEDyW(7GUs`>JfmNw0O^lYZr$E4=o)K@if-UnV$#cb|>5*`v$7w*$i7xz?>`nq!?M zajARz%Q#cm49SHQrs?r@KUISwSr>MhiZ;3ZQ2n%ZjbQ7=MCD&S`mWio3vGQbOK%Y} zE8|@#*bw%3qPQ}{+suoJ!D^EG=M{tqPVEVvxGa@B_0@}if3K-M+j`YUTI*BDj+qs? zjVz2?8CFdQNq3nq*uBj5tZ}B4-xI#87Q$aoLT1z&**GfxX4pOm>9PFD^OJdTjsK6V zfT$g;N6%(5PevIo- z)zrr>Z_Jmwo^e=w_}h+^$wv}$Z6X&g?VY(vYu6*oV70iqH6_8ve|$6Ac)gr}V_-mESha%Kv0i zb77q&YvLMd!_2by!zW|Jc1(|}dbuEf5-# zS7E%H?_xfC&59Kw_j5ZBtY}=dEUD_B(m}t6Vu|^=c&Z zoXe0}T>NIA*!JVSAB@H9&Kf_I@QwD2a*K6-dT8mQIB8`LU-x7ES*N;Y*?jelF!s6g z?zs2%=^M8%eEJe?`GUs-X~hN84!n9L<^3meQNTIN@27U|%V>Y;#(tdC5htno(4bOtOVyd03&&nsa{SZc)0hUH^*iwT>!O%7O?C74$K8I$(OK1b(mg3t zX0q5_uRUL06n)!q&G6Bti;Fg{Y`S3A@Sz3+Dqr>FAKLgTf)&zlLw4Xo0s37z5V+3Wqz$W0RnEaUh?Y~ zx3P)M&3{y8Z?&ed_~`5D;F%%hH7W%GEMI^)=UczK5XgXx)zL$^wwobg5NhuifV zwYkTR_}+Mz9Wi&BXUf-X4f$0sU2Yu9RbHsDrES8W?!Rq5y1tvOh0XUpo4MiFvrWsh zEi9j}Kd?_|d+4>P6C3Z`i7|Q3t7gtI%}Ru&OgFxvb#+(ar=#JC5zBj=FCI0&^=#YI z&mZ1&%P&ydtbXi{jojXvqbtt6>Uo#j-&0*Go_D;jXhCUzUdGA)A@^TYx6Xwxy)fZt zv^5V{-u^2|Mp{t7aD~|6A4aP#CVRMj&X6?zdih@C%Ir%%HtS9pvrhiV|6Ffg+Jj@) zKhE6nOHF3(x{quzoYT(i679QUvnBibxm{6DpDJE8l+)IhT5;;l;-&-ho?bT0y!5pq z-T!Q$;k{!#m-hZ-{UT9kxy&!VsowUFRakBXfB3f-&2q-!=F4U}N0nW@eD~t^{PI8d z)m7WK$eST0tM=xGC>C!ouSZ=6H#w`boqc=r>v_i)3YTLp|8@Sky{g~w zVf*&Va)v)>{kXQ^tPH1E&*3}!?{o>@UY2x!U2jG8!*CDZUah(df#%QdTJcQx*FPY2 zzu4ksfRN3LjdIq!DNjqc!<|7YG+m&S^c0C?+19P+pMEHEqhYV^eWMl^TmiOX)kesNF4^u0cJ zHdG(hPBiQ&jQIGkSfcuy0FOk0&^b9vvG0F>id%S!9BHfkp77zs$BH+t%MC9+Jn$nj zd+j$ZGyQ{Wr}JH_+*-i1Pegys9r1U^_w)MSw~}i%ja_$8Sv~3AtqrLuhq=qUC1RAH zK3kwrsxmKbwVH?hwR6=h%F0jO`|4SwCvEWAWq#)H(q{KZ^CCYRyuLQ^;Xb`umhAG} zW0!@MKYr{_oV~T}YX4Jz`-X|lhm(se*dEVYKHd9#V*0znYfY>>o?j}M`H^2oAakK! zVeAg`#~VHB`@HjyDu4Gma$@44!*^P$rQ>#_*UXrF=keOj_na$|@^-wxvA*-Sk4;?W zMYClGjh3a`-;vxc?OU3EfO&e~kssA@2PReWM?9XRy6mAr{KGF!KPs;ud!D=DR#pMe zMcZHB6AvbDzqU_brqA0@|4_!^N0Fa&l+U}}^X}K>`@Yau<<;@M4_SNU{%*K>c~*M< zk)_J+k1l!VAF_|DmRtWnfA!S7^uh<9S030?tvB`i3)>C%x|Z43+P}W>Xv4?%lfM5u zaiThY-pi%J>4}@|LNx1_cR1FzpRZ4vv!z=5npf3D@%N9`*UxPZtyX$(k!bjSV&_&J zx4wFY_qpABUMkndX`N60IVp0Yk+R|Iy}1v6PrQD1lHI*)|JUkY`M65=2+}Gr6J^Hj zGHUzl|NlFz%l5Wh!25GXl#TdYaasRuaeIm$mc+(Oyq_fW&2e>wPiIV)c<}A$Surn* zRz6epUBIm%ywh~^@s&Y*y)(AyGXQ!;^=+(aPWxG@U*|}m`8(#Txi+Wu!fGM`t@lKlk5w zEWjYqE#u$k@6m}pv!z#cS}!Va`#Edo!%esTbr>!bWS#blfBDTvm#2xoUf{I(crSuk-woP2UAi*W$ z+pJF$9$Kdz<67-hVq>^@g9P_dPeUI%l)~}&apH$dHc@r+TNDqcRzfdnQ1%UaAE4Nd!pOZ z(st}SswFpD)Yv)sm@l_()%Uv>K7VgN-M_&?v~Avep}J$N$;(>OP8i>RHQDTcRj28) zmlMCYBpN22IHrpHy=0-+qtx?EK35j9YiU z&T>X-H?_AoL`Ftte1B(KxL($}ti|qkO>M^A^pi33y?1`t8un-FYoYj0)-Tw0hTL9p zEO71OS?*i=vlOm6t?CSsE}ibZbyAkvRlijWL%dfm3(8$8<-K)UmfqF4RR=@5SE>c) zE|WU@!t`q1)+oOP$F9~yy_{WQ5FQZ{knVUkHa@VwOc>G`C|C}rp}~G z-|{_<$MU=X*ln&_XLo5`9oLG@_te5pd|TFim~BSK_4%I{r^Iu18AfHD-ENoorTBSQ z=k-UkUV2|F{=&OM)hM+jSLJ$E+&$q$vGU|^^VOr~$DIDQ{y;(H`9B|@ZngMr_4{*$ z^St9~J@bPbEVHL2cpugMl((ksjlXi<@(ZVr9~FLoST$@8blhIWf$6w%npD-e-FJB{rM62B{WCF}2OPdHvMstVChfPxH0nV!vNARboBTYsqY@ z^uBG)o?W{{)!Ej=u^{2)%J>OU=84KV`>Qbmsn1QR~$@ zpyIi!{$JId@{y!cWZ$Uz<%=Fkde^ zXVa3SMM*cgPCcD=|JU#D?~mMzjsEcLRb)l&OOujpYo74?O;@?&P0T1Jl!h0)5+Vwr%rkLn_F$Ux66+Hw02(- z`@28GbCvzM0?F@f;fIq7nj(Z#_ZaD3IOXBLcKu7~X!FIN9lZA>{L9MO`r*xC>-?*o z=kBcgs}P)av@>;$o6lJ>b$KUmwJwR00T-58&qfXsJmk;x2UEai>9M*qSXIDa6I|uti5!*>0KF4>RtxnxI*P=er<-)Y; zlqaQnGwWV?wLA0L%Ik_=n;adGQM#Z1f92kHKd)|0PY-Ske;;OO!PLF&)aEsjf_3~Z z7LpxLTQ~kc5p+7qJMryrt55L@*I8?ey!_m7hG$OgX`LTk`;YvQ3|=!S`1qem)=cgf zlczwt>3yzDS}f^*ANTKgKGA!F{OiJVs^JeVt$ugJ@_FA;Pjw#W?|pYKTB(b#EiQg? z%B{|CSozP`uhr{~=jtG}ntuy*WT zcW{Q?;SUG6yEhg+_0oy_uq7wTQ1JBOUGE#VvBlY(6tc=*)_?5Ea`EJ|vqUTYzVA_zPz0u!F=Ee=k|xKs@faU zj?S)l)EIf=!MX#*i>nv7_g{>VO_yQkS+lTZZ|jdQdn+qaf-1Zop8CIb)!)E-0ij1$ z&Au(_tb5d4mU+Lm+++6b&1Ztv7Jr&PUB3Uqi^(%;z9(1YpOvclaZJYM!-=Ffald)2 zzEAiq{a1!>O3OsGq_2_JtKJ0Usqip|>5W^?*1y!R zc+&V$W5>UfF1{*fic6)sg}MyXImey*>(N@JI^(D_K4Nj zKgkcPIWtvR=XCy|HtAD2FMgi*^6_1BNkaCA6VCi6+MM1dr2U??j6WqgB};JI+In&S z`?c+<6X)t%&wh13q{zBt`@ejhxxZAN?cFY>x~yW!!6>opcZc?FI(XsPR_+hGPt1PS zW|*j5v)AsZL&l?NYXi*YR{H#_ui0_E^ywUS{eiWhJDn|66m8?B5w?ucP+-`t^VFjj7Y8FJ7aQw9(Tc_%tiiK^9fM z=rHG9FBX^vFS540(qypfeS~>3yTDv|+5SBLN0JiOt0lU1ybnwH>`gOi(Y{)5bqZZ>#^APCNSK{ljri%-n&N*-RpxyYxJD-guYi2fQyYU{s@NltVlQP;#Ncu3YV&uIhSoY>%kHtV{3#I>a(Jjt4$o!(QXI`aUO0eqdy>(}X>-_Uu;SX)EfXTD8YiByDu9IE= zZrxMw4Le=)=iJyQ`seBD4;NIQC;Xblq;qR0)ARCG*|%c@8LzUD`K;TY zC7zk~Jn-``{@m!>S9+D>=9j7pwdl+^`YYF-c`+4f0;hqQ2FTL@17^e zGp}q|@xNZ(?_t%+x*ghC0m|2o`$!fWKU#I2TUiA*>~-IUaqgwa7r$ki&&xHhn_IOY z*3Wy%&Px-Y`8cn-+$yqHMsrhMKuV*kZ;87P=c(?vUFY-#xDLpFIV?X(Rg+PF&qAq+ z)Wuf*7kZ`21wyYnhJ=MCJU+EOaPMj%ds%Z{OQWwQ(=I<&PwG7OW0l6Psk)}P4(o~Z1%&>J_^73bRAc0 zuXEYSWn){%vv1#@PUWh@JyZL6r^(t}zoKlOcWn2J+S5198e{ZtcZIB}m8xWwf3#L# z{^*H&8~-1cjp)|wRlRWK`Q;mR5%Zumu15pQtl0;?sMmGpPCZ@tsbk}dYwK3~@SdH% zazWNdVaFMh*rqFA6>9O@D%kqOC;e~w%tbCdr}i)4aNPT+af8Aa*4htjO4IK%m#^Tm zOJDkD;?3SuM(scBa$^3;UwO0c(A>%MEFP4VtncTFHJ3OXc}mmmSEFaC>fx-_b)3H3 zCn~Not+aZxWubbo0GIGqrdO#t&Bwx%Tb8Y^{~+d+*|Lt4<%rdmx2Jt?>{~4oYIJw~ z@rKEU3xrwYggxYDY1P=Zw`oVK!p6ugmavq}da+x2{^141#}>G^=Vh!;3b@X8DkLDg zYDEM`u!}%gW%mW6y^}c{r?P4-KNPN^(&WXHmZcc-dV#-gi<`i&y&pQ3UeMha-LgdUApc^_pNH4SAB~%Z3|f2zcFUc6A9PXURlv~`+K~P z#2;9n94Z*G|5Rl9yfPPVCu8e1|5iv^@8j4}bg}k;z|Ow)x9zgtO*{X5MP-PL252e{ zH1FXsqajb>^Rwmsi_b@jFD$;zmoafB?{wqPWgV|XH}af1#+TVwvPQSo{Zmju((Uev zGbiacX(>1b{IhIIk}XkCvAU?KrG0dz*x>~o9y^!RczNi3eOmM@m9a~`1^yQGnj^RNImlQ-U@sy^3-^J-viCdiu7N!hCyg3axkS z%CWip^hxoCPj@RNtbXV`dAei#7(C>8_n$KM%1k*V!1xpV$csL2RtrLl~*O-0s#U#i41I@Bi ztv4jS&yLuZy)EZTc+~dVrBg1{&kA_n-4mFo@qbNtWW|dR?}fi6t?Sa=>#Duw&29O7 zok~8r_1go#FPrAD{>sW#s~lZZx8}q=UMt$%oAp9N!^g2g#72d0A|fMsG_drEG3D2N z3Ev`Fv8r@MO;_;KSyq*z6Q6xv9qD`Mp$ljG@+94-{ zJ&8UQmuJk`VrH@U^HqsEbtYbSQh(fk7yj|s^m>QFuYRldaM`RC|CfGbVV6Dc>D3oo zZf*1Ve8o3qj*+hE_G0}#yZ>C>!LnuD?Z=|t38Jf9d48n?&w6|^P(aS#fA)7Tl$oFQ z2?y9$KU3TuUH>FC{l&ip*VGJ@&OX?9?fy$OLoKe*H79r3+%&u*{^_vx?v|)WXCFGn zMMZzfwB%X*`;Uxn%JYl*-q#*p?cIO4>+b)aE1`;#Vv}#VMMSJpF-bDi5h>m7KVQai zp_0+^X#1MjKaF#2BSPPw+q#d-VJq9KDM7ifD@sIKjz125ZVR6w03{v=W8oXyaC3kjb?sSmU)WK@%)!#m z$*lI{&Lg*`v-SJ%Uwy0=^ylGR_q8wQo@>Z43pP)^a&NJm=EQerTvkPJ2fIpq%QlvI zTU_ZXs|{{X)n62JWFg1l&sF`-_pdo`DEJ$EBjyk1zB}95oOi`G zneE)0YPCv6L4UfRxaq#bQSL9Af?XcIJ2dyjjdhEt!sE%B3*)@Mto(49Hzn&&-bR^SYgaCk>Q7hf*~cr|T)FPr ziK)KnRokVSJeU8MTV7_*yiSMz-v3XoYwrCEb7ARH2=QLn;biO^dR^ks)28FWvUlGU z$}eyIu%n(^fB{yeff*+JjIqa`iz~k1?)vJ_*HKu-mnv$v{kW8EAPM*lti3XW%rM)u4LB<&LFm?a~gG&p)3Q zR4sp_r6uOrzUK4hIZq;wJ9UWa`RW!kPG7iT zjew)SSzF0Ify&6ahs4~oe`N-=tk|L{eQHJP1;HsV7a7@3?J_V+;yQFAW(I1Zr*grA zDNFIo$)^I`3xlr-yt?AMmGjk=fV(Pgp)Dmg;(#VUb(l{{Kd4z z+NS~@-D_5j-`l{y{V#iHC}YV<*{*%D=Y_bJT+XuJ6TNbqWP|&fzH3rWf~|bv4mvHH zK5K|Bl+Dha(X>H~75#JpU)2eduZfCIeOmB5?a)Lm>*}t5!QBhF9Otf+6w+R}b@Pgd z*I#CIZkb}TWciY)CW99i7n~%vafNk+ZeWr=sRYhC!Z!cZBz5L9z#R@iQ^1AswC@GOo zzR@Jy%;}xhkD8ZC2cC3r3br^3Ccn~oJ?Gplo}GuEvvw(bS$aZMdhd?Cah5jw&N;0} zJsfthXv(hg$GRK2^lpEP7o1)9C8JQSs>D*1@8%x2<+E0aUeIR0yILYd@rC&5#qK(N zaa#p`l`g;7@Ww~$xsul%#Z+TsBYyb_iy%9KqPIrQ-K@s+W9E@f`nE46pG|H{@eT#+c+bZz06SEoZmV>Ock`sZ`J zH+tpa>9}AMPwu8)vL}D++qf}Mpf35vP1K_$WUN{Iq$;fJDnG}6D1XCfcu8@Jo|?Tsrli&_=Fz2Vc+sFK>7Iw`N!O zo)>fKwSJXZUcS|_>{PVyMz$`2DQ1UuPwkQyY~9uAlbY(fR$@x$B94=HLfbn|mFfE1 zl_i)zo*N)wrtD+pic)(Y*KRaEZy{YQc1px=u36MXwbvZk4@9^VH$>^QY&y8&omJLa zMUg~_k`sXflU9h?u8;V1h1E%J&E3CdVs-ySzD=8UICyGv|)!J^W%~^NYjQJ*V`R&5XR%z9wAfZvQ=JcKP-FY7c9J1=Nll z_CYK3Q#e5*y75zTTJ-md&)HcdRx>N-ozR)Kc}y#k{MNSkiRN!IUJ-maX)ib16*G=K zJAQqS*q*NX?akK(xqggnUu=A{4hmIdMNVN`BKx&RVM^H+-%S6tt-H!LY*};0D(jGp z*ZkRY8QoJAjhJt~xUg<>nX|a7LXm_=h}L41G~~dfa>0WMG_btmo7kz&=HExXIDR~- zIk=$1Ni<{EJ%97Yjn^1nSvhcKS2^g^XuYw!^F{F6`?HS!&rM%gd^`9^(Tdj$rI%-X zsB&1pQr?rldtXCp;*}!Rqpq9EE~KxO58+PqS{1x5Ysa^ouWRSRx9l)x4tMfzsv3M6(xx>Et#WE8vlDz18TSOO`9qf?d}_~ySM8E z51VbqO)GBAtGg$LPVHV$_v5{O}RVOgY%8^ibYcAdXhE@ zg++-u-u)STbY9f&J)5@q|6OyY?8Uv0)fu-})!&d>Q7NdsV53HxZmS@7vWow8xmTe< zK`p1a?s~ZC^d%|sEEKZ*X_t~R_2AaE9O~bel^R@}yrk7YQ1%&mLEqI7#o7L42b1=` z8p*!NE(@lnD(lv(@A-YJ^|~-yOp;{Dse@mWbVaxmcbln4v|nGnWAbFpHD6xvUEPts z)-mMwf+)_Xu4|j0rmC)3xoT3`R~hH+bNnNEuas=$+cw!Zw=85q>I!o%w$9QCdU}je z9FG?zvY}LU6C8p$eD*vn+w$#O`{UPKg0)8-d@V0Uy_;ZKTg{l>xkl&8W95{+^P_mK zURr+5Y2$~;Y0h7LcRh=I_`&K>LFIm#(1*wV)m8_@hN=a-X}fYv^Vof~=s>OJt&&NL z^;UUJ*k#MR`25TpQy$8Pmu4v1tdld(pN3kv@;C0ztK$KUc-~tR>ridp^p8idMb094 zQ%Y~Vn$xwZdQEz=7dCUHYD;aD>{?%TC?owQ$5AbbmgT#g)-=_bk8a-+ zY{h7~i*G~Fx-3ic4VSFu#JMK?C{hra!2DWy_Hm1#FAUX|x8g(=<)|tv8K_)yWH&N0 zzPQboJ!Mx>z(%iY8q>BM{eEso7~g{TN`H$MWo+u7+GG$Jq=PhX4$6ZU6dD(-*abSe z8?u?=1y(>BlgpyG-W|iv82~N9jE(c;t4N0iYHE-7`i^*O-jwd@rHr& zG{{;+IO#FTp|Wyk$F2sS?^EtN?Two~=PH|i}9K9aJ=Hfz$J>5msmus;0wVM=8`C?p_N zy91L-K?mmxk&A~8IlY;>de%qzNdjxJ<6+xq27$(H?ZW8D`Vw_L7Z zzPVS`#@4PsLelm_m%X-!qf9u5=R;DG)Pfz|O|2BLcDTE9x9+Un+>-*V*kx<$MzR%kk< zmR@AsyL@TdqSE#2GK!1Lm+Ufns_?6!ETQv2LxhN})zuKQxq*r+rn#DO%{py$ByQQp zwvt9A)`yHanjHKNhgeWjt3u;4fsFh6=C1jB*|a6qDE3NnBuA}LsfogLKgF-n>}OAJ z39xi>Vx5&`BATNmZ&%!1-NuYJ&DShk?q+VRO)X-ueX{AwygXgy-;nVfElR0<;Gsf5XsBwi zi|w=>tJka%X=GcneEI3n{oiNI($NZBnmEe3y$$0`?;rRpTgZlWe3NK8v&b@QM_*PjbqBxsgajmW;^q&Dh()? zSZZCvqQSv0pZ(1Rwb3KeIBS;F>x|B40?k}*Yn$ghs;s!LB+}A8-MkvI>=~N=8|5p2kurbz zDtp)6>l^-FN}Z;*?qS4qmFs%Pw@gdzTDGa?Y)6^^Ip}t-tK2@ z^V6!izfQBAmeFUG`}H_@$MbK}+4{AdG@10v69$5^Wf-ue+Yn z{p;EG{A1;6I>%@G^LcxTur>3|`L)+QYyOQ>|L;Xgn=w|;?-gyo=R12t^|kjCY7Yf$ z+4=AJvXfEu|KI#~zM8Imq43fA3D>sg=T-$X<^RnOzI4@X?#(az>WW^={wkZDRk*uzmu)>45t&T@smCF zR~|nuuvqh>e*O#gmgWyXnSMN3YFzkg>i^Z@W>!KxmV!L3OJ;@fC`KvVI-MW&r+9^h zPnbwl(g*$Tix-;G-j^+ex%}>ZE&~pa1)x`djnLvHQ?L<$bC0MQ1*|S}K3U zr{8bS$JSDh&*934qx*cyIXBrX4VxGj`>T18!7Q=I8#zQoL>{yV`#D5KMXkv}8b~-` z?snky*GHSr+x5_~Pb2Ri}3hQFCo1 z*2*ZZiT-9^QI_j|@54lm_-!ov-dYzPI(fgz`BPb~R%Ot?gtaYIv%8<|5qh5YdeZ#a zyN`GE?Emv-*O3($g6(Y#wOB7`E{=bDBO+Vz&aTqmjHxpmnA#kU_sL#d9j>2}k}^Z( z7a~m-xCj*NzN?|9XQyCy`R78=*CC(|2N(;XDX}@lst86wc+P~t92Y#TrFPx?eS;+ z{~eC$cjT@{{mpo{;^MRu(YwFM&MA2G(qiFv%^UH1q^sxuWWQDZs_WWbKgLt5Hop7Z zKhgM6PR7q?MT?LBU2t&OB*DIoQXBon?`|ud9h!1CSUvJzr}?_x(ALc3nvGYkge+OU zJoSQgqp*upaB??F~3$0bXxJ~qeK^@`Ws70gmB zx&EW@;gq(co&LHlmu?uZuT{G2^Vh<1{XT~+X$!i7rs!SR_2Aj9yze^mqNM#qE@&=V zcs6g%?-$H-Zu}S7Qub_d>2$GaR}EE?3f_v(S1LcS{gtn(@0GaZPq&tvFDofZeJgz` zqxa|e>uXhI3ctJQ)b6m?;F_F#G6)gwpn_lpr%ZK|fzpFT2Y1#wF6q|T^K1L=kNwrR z9&X7Na}n>9H>G;?C<39XJzeK@v-7z;qMQ}tB*OgrfgdG zwz+)Kqo(eFeQCNyyT5N*wK8;b(eLm@CVwydaekPhmb-cP)%vyey&-E?J-j;o;kpo! zeLoZIdFE|d7kB@9Y`LtMeq7A#vVT9HKc1OBZ)LCX1rMforavOTe{PV>fBbPZo1513 zM=MXOEq-t_-9Tsg#J3wQkjke6hMo^3L|U>A21^z#SUqj${F#}4J@p0pW^3Z>5*j5m z_cd%$FAn&Be}7n%*dfNqhu?Y~dah2M7Or?xBm6D%rC5&89lDH5OP%99Kc6V=h*5UG z@j%0FrXk1k|3!&Q=A3W{-TG>cZ>XH)9AD(l_HawI-S$mipFV7tKeqe* zzV27AvQko0BR}5qVVmWXaPLX6Vbu?_m@AJpA{Rf8>-;#=+3@(C!h;rP=KfXhk=-K6 zIy3#j$1Z_$%VwCCARX>_z){%Z8OuV!f{!OcPO0g=+Lm7-JlFbbXZRYkyZTKR^e;QE zm#{tJ75(kK>Q|N|9qxyR_wV4b>Pxx1ulj$(1fRsSnb!pcycY}3wA8gy+L5bl@;@Mg zGdjK|{*I!UZjC}-+@79b_4+>6@BEWKo|f&93pu@jZLMuoYVw@!d|rR~*dNcW3!llB zCIxhtzPiG>|Ibr>!_rqFr>|SwJt(-MC*=Rful2`YEwy)MuP**4TT%0=;*P!kADMN3 z4>Q@+yfxTyXSTuD_VW*;&G`?lS*`wBue?93F)@5`OLE5V_SF~Meyxc5&r@}mx#sJw(i7Xh&rI3jbn5Q^Ytv^GP2yjF z$68=Izb$9$!r31_b$!jKpYr0CO8!*jT>#SR1)y?6^?}gD(1nkQ}Du>OUsYISH7+T>s`z)^Z9#r+~NAajHA@P$8Z1UC#e8xvOt@k9sUny{LXwj**CRG>4a2Kl%C}BLxIa1 z;;QDo`Jwsl@X_~?Z>1Y0PCNa$eOqs`74P==y#<=5;^y`X``et{w5oPno@`W%8Qa{p zxm~NC&QFL{@jv<~UFh7jFKfEpRgB*(Ikh6??c}K;7ng9q+$j95H}zOdar@SdJ_qCv zo!-Bjck!~kp4-#p{MNBgy=*Gpc-P}?rP#cRG9|Y7YPo;5MW#i?%DKC(5;E$|?^S(_ z%+ddK%WI15WQLzED+E?Q(3XCG{6bCS@v3_o4<=r$_;YOY1ZMNLllOJCQa0=U?+cxw zdUXHSXZ9a1bn)cG@aX6TXsY{ek?asmk36Wh_lf7)O_!2Fa-v(?-Tc|r&bZI-$*TGB zO)F+mGRmom8D>n!=O}D3YwWZuS7KY2a{UKu6W{9@ovitFT-$S`E}smHS2&h&OK8H$ z2!&YYT@hEkyS61ie`n;)Bc;7SuuhB1y7bEY6Q7)|gYI>id^}qBV$b5lv$vMSy=lKx z{{G_KEaiy;IYH`crid`kkEpV)c`rW0uuN>--lvRn;wLpnFG+j)EB)f{JJuh&_v;@} zZI0?>*^>Y1ve3+DlNGPlCG3%(=du1@hk>+hn$EdN+n2vz)LoFj`$L^XQGVg=}S3KqW7Vs-D{oB@AX6@(X*T`YEZGP|F^`%{~)wZg{c)Rm|Y2^vctLwhA ziGAJA&|PSkH@&cC?=RiTxjor>lS-Sl=efS=-1m`v&c(+mD|z?q-^X_SzDDkW{!6De zczp?fdvIB-!~K1-S03ERSf0(XTvz*;e%sbHtG!Pr$a1%Vx-8&Ll^zQuIZd8^k1{U1 z|Ht=d>Z)aaA9GG*?o5eMcyeG;t8sSsGK)jI`=lCI*slB%e7|J%>{Tk+jUf}SKmYta z`*uIy3JsG(23GGA9#y^gv#2@K;g#Lj!@~R-j>~)Mwk9jTX}>HV(81Y0-{D_?PxVvr z|MT~1Gp>30;@|!WQ&ogiII>o&e)J5vGA+P+>x%R1u0^d9T~!~btE-y3cUIb^lTJA{Cd8NgWCvPH~gOf6h04e|P%#N#Ey97f_w)`EK^#GiTnPn`@nZ zZsyJ#*UY`9#NMyB_+!9f7y9h*>Uf5^k19Ftwykx%qouDAwRD$P*0$|j`tP~V2(6Qv zA+b^Ja&7g3Unag@`_Gpr9lm|<#GV~|Z{yj{#ny{2DUHfF+_n7Ml4)AKE)Gfxf)hAA zSX7vV7&{$4DhE80aJsj9yLQe-))SG(1irnoywMOAaYKul?V`e^1D^{x7dn1Ye^I%( z{&teCuTk?+uJx%$-tOJ_@E2S2me9ZVkAHoAOW}CmjR^&kFP8+pdg0+-zDhlL+by}i zZ`Xc!>d#;N{PvE0;fjwt601d-j%>O5;duCU;SZd`8?QZiI_2r?J+s66CT$fk%Up2D z+?kutS4I!bg$EwHE_kr!^~X!~kFHdGmy1}F`Yc_bd+Ubu2|NBZ8pZtW%>Q1)79Hrd z>Wt5vPrmlq?iOO1CL7eGxF#NNm|bg}6S6OUrGMX{!$qZYR@U{DctiZ%01XBkCZ=hf zC*>A0%nU^i7MQdL%RE0873Uu%_rxQ$I}xJbumf=e-f)O`dak*Vt;($-#FW49XWGP1 zCw+t(>$w_Zg7}}lZd8@$|GQjE^Ym0$h&Q+_2vJ&aCfKX$@%+d(12Z$R8c$_Gjh7r( zmQT*`UUl-+DJLfCdu_cA$GE;ztYNcWzGf3NJfO(} zZ&)O@u(RO zBK5*0t5YSa@{D$_-TKkd(H54L3*+`yW!&3isTH~^yqi{s?kp)E)VF?bi8diI(mdRrUW1 zWmmg4dtW~7yFY%)l7H*2*LL5Hyk)5K_O1RmzHbwlRQ~ZZp71EiJhH~(cFdeDy?6F4 z*E-?lGr=dMbB0>#xs)@{HvAD^z2MW)hh5S4^;YwS=cIX9&dcQs)>FOn`PbLi-Wqof zy*-`9&(A&l_V)IR`}^(Z%$@5Srfp&(a(7qh<)fdC7rejjnjxzrX`h-2oHw@X;vQ%*Mqnyo4x5%4`U%snQ z6tXhtY3`;)FRL7;&srz5xBIV`#ci48`J1EvKg#TWl)imqca!Umb87SYFH4%L`Z=AL z{C2|zgDWe8+2_rhS5R0em{rB4JmdIdsiV$pUMyXrjCEp>UT0r%oM8xC6Crr<;>AVh z`D83UZ1mtd%bOBzoS$%S-BJ(F{l3>_cT6jGQ+@6=-?lnOmcEHn`O1H8@3i;#YCx3TB7TNH})TM{&l^F)yAx7 zhnm~tpgWZhW&Z0t+rCYgfBv@Goxe^5-Bs`WnD$Wbq>xG2;b*NAChbsA%DX50L-Vb~ zv~S<+8}-t*adhsFtmttQV>5PtUevL_kZ;?!Ya6z0bdt>0zH`E+T}>~x@cA{{#b4$h zJ8)>r&JADn{cg3cJw4eg9#o8Lhpvq>HOai>V$gi;+O;6tFP>l3BK;Uy-rd>R{Nzc> zk|j$NY;9%x`ud!Te6?Ly1T1Wt5LXaNh zo7a{!hD_7jw)S+9#BC9`Jk`F-ezH$OgA%m1l|?kwZxsF4BeYFq^Oq~XEsE`!K5z4H z`&_&&L^%wZBEbM}2X;{rpY$?>O&Y{t}G$ z)GJ+7zm z{N^_V4?jk+c(j$6gwtDu4#&C~JWV|{ z#WiwL+0>=wvrX^zZ)&kW&%E~Zq+Dn5+f&NEH1N6VtbLNQ*=v2rD$~;oGuL*ln8RyQ z^jk`<_Vk0eV*xyKF7r75-ruu5O4s#PZs^I(EjgW$EM6kc62ijDd@mpDZ8kGA^Y2YD zD7+n>Vg5v8+S*!f(cDQJ4lFl&r{{O@%QUkaSL-=K+=En{Gq%mPtDac?{+{juo>$wO zmbTVB5M152X~~4&I`RgGYj=dM4*TWF{WMK8xvrCAb?2s(525duW!&B%YjwqZiqp2W z*X4GffA8?EStqCKM&~*j8FyT`6k9|x(OMMoZ&HJmWRe!T+#pB!J zk}u6$wp_S=J$!|}_^QpKMjI2@q(eo07CK)qZaowG^5F-*oXh-4t4$_lWN;+s5tXD!mNV8y){Wd->L)XuaXTyQ|t#UFNxHl?rWJ zZ{2xp@2bmPxae+csUfEqV0XuGxXN3r!Xq?%KYkCg+U( zyr@gh7MU%u$Q0k|cXaA{m$@4bO%g9JFTZi~X6MP-kr%EQzHr@qXu{>A`+pU_{v(#~ z?YhLpe1objxt(>|$_8s1C$E?nD?H2MMpl-T@Gb5zS?8CjKQi28g}vUsZ*|YS^?1R@ zjI|ZpiZ3U6IJr)VzPPhEy>N%%?w=EszZn-7{{L6I(C<{X1{1gJt9G`yHMRYFv0TKK{MNcM=j~ctk3H^g zJ@r9#$71otez%(WMC@uRr^IDAK7X07`TkBvlVF&a{BoY||W``Nw)MZiv$jS#Km07_uVwK zKmKyC_DAvmxH)&WbGw(nKeABv_DA<0VQ=njVzsXSFZ(cY_Jyr69Ur;0lUJ`>f7y7$ zkv|n#b1vBGC`uJ8e{s)vtU13mp!i4eU;jBa?Q?hcC%tc+Y*F#WKW5LYo)i0%1cj?5 zKTcn-sQ6MXZ^<3ktXH-xLS{}m%;TK*OtMaFL(r=XzRevk7XQdQtzL2OG-JNWe1G-% z)(c|~^G{iG=-r)@^6Ml^3qOT0?Ys4PmgAg-$1iQXWiWl8{>K9bJysDrHeS3@k+tsZ zucx9%W9Mw?4x0by)Mx(n`)%)~ZOmeLfAJCLw#vnab`{Qhu(Dvrc4o2Oe|*mC>f6s4 z&NkTVtvk^)EtKJ2r*y;Gzmp9fXmZ6@Vvd||uNQvz z|LFYVefz&%oVQ2kVR&lV!)t%D59Pjc_t~-C_wd2h$`|*xE!&q~|DQ8m^KkK>eWuB; zzFBwFW*tsH;#1VT;!MuGHJcx$>z#|=tNTdm_r9i+mzQy~Z7BR4UhwQm*+CAr`cIk; zJc`S+*PLChxa_1pU<JDxaXw%Fj5r_pLpY#uxQP`5aGP>+{H>Mf2Ni{-kT|FFK@I zo8mJ28RxV50y*QTZ4K{Vsg%^t6FXY*=&a zlO8Q^j{fY!(LACn%aX0PTLX~du7yZN%*)%5(EpM1X6H7VOp%{e~h;l|QWuaYIMA95{8&#vm@%0Kn^ z{*f80?siSu{$Z(YpUkTprYqjb_tk!hDZZ}LlldxEUVhKr=;==*uDEXbvTpU~=jAnf zT|U1%EzdWjzKwfXy_vIJv=v|6+&O`FcbXNwpMG5W?Y;wRXIFN!`>ivxs86@G`?Eju z$AkUK0{0&<=`DGGR9k%(duo?zWqMuGj{)A zaZgps+r|0qg`Me~2#W--3X^ZOwoLlvYg-nq+wWj}w&$?9v)JO(+WHsNnJx)zGZOf` z?uVkCD&xb=!G=f5r5y^9Je5(4PaYn)X#xB8S z@A%Z(%?~Y_{PC-U|Ed>iA?j%bZZ&L0!O8{oDcUW^RzA2^W^!ox8kgm-9FM5XImUj^ z`NOSuo*7RvTG%d6JC?)}p&DZNCzG>PGiCPQu7~ISH$1dEUAx`l%-Sutcg&d5vOB4C z$`a8`^TJn`R9z;V(u%KlnSapp)BpGHD^%|I9b9`QS}gSO@edxkyDHYq-o{(Et5_(+ zVn@Lozg3shFBPd|?ymmS`81*`;BdjQJdQ(4RBvyrd{^Lm>mj$`Z1(6zD-EFo?EGsA zUY*~ZaqY@!2dRp;f9mF5nb7JabN*#Sl=)cZzrw=rxs zxX-t$tI+s*;Ln#9a#m4K(xsx$E$7scu%A}-YWx14+SDnZ6PA4lnW)kl`p>T8w7Q?- z%M*ro>?#;`?mm>bJy%#O?-#4g@wV^0o2E<~6wedXYc zzGFXjUTxv_om2UKZFl$6%_n7oY~S5_uUzudoOP#UXGyHG`On{VE;lalUXZdYaI-9N z<2;?e-2MFl8LpVURjU%%EbqJ>K zpO8kQ1WX6_P zeEVzjABw%c9c8^}pZQ!Ny$!*NNoUXO`KXe?yDsau|C?E!65DP3llCOaJd|83>0C6J z(_Fs>9ed4d{JW#7gyUAjjTdjNthm1)c^x&iL}z`xU)18O9D=oG9Jl$O-DSRbKVxB1 zSG~g1yKUdLuGqgW@ZIvhJI=E0en%g1#J2zIS-Me|{Z0DzXI+lS-| zHfUeDF=}cF&*UwStMlc~=WODBAEbzb0N*itj; z!Qq*XyaC@r?*z8b3B9}e@YR!YXMg!9-*4yG&$C1}qV?#EriCS+CZ)}+GCQ>FyK?6| z_FX-5d=fYAn7Q!K$@>vW6(Ziv%aX2b|8ro?k-I#bczT5IXoWDv+_z*5%e86PE>lHcBu~`=;4;)kXBA z!tFQVcC3f*9}nUA9;+I|SuInhsvbT?{p!vo)>3Zk!;VT7l$JDQS6Y3XvW|aO|510_ zdyaQkeSMJdT*1ycao-)i8TR%J?aNvpYN`74-)IupfBxgU6M8?Ql`~wHKFe47^r`eaFZrS`U>%eOD(GM_$qS{38;AY<3L?^a(bD7jr7A$F0Yd%NsWhUf1; zF!~C;ZJT@K(bD^mUOe(Ic&B%@{RKaN1wZq3)ug+9dw19`Hr{rzsjq9F_6&)Q9P|5c zH|p)5cl^$IcZDy_21{1tKD15I%?j+UK5yIQe|#U?>g?YiUvA%@bn95kn!Cd6@#faD zb;oY~Kd?<`&E05`PR*XEfAuRaJzjNk_2*su2gPSwf1FjF6PFvJa`mswE`i<`D{N*ie22cEfQrJH*La^yIqFH5IBrACmwf8x!>zZ-!jLNQc z6RkVN80_6=3m$iIvnyEk_k{8zE7@f;9V8c5s1}*C*7v1V7HcP;beVB$msFc>7t89Y z`G4m~Z0RrKc&7962N%ow*F7CAuXohEGrl6q{5y1wgjmnB)5;ouQ_s8&3wI8m+jzQt zT5`w<-ZT9+&CRQu4eE8i3+C-Noy8yBVzH>ck1N71Q}EoXHy?{0WJPtdGlk0^$)8^$ zJn2f7MN439`Qx|A=^OH28nE!+PFldVPV>#f{cDU?t>}IAUh~TNlV={B*nV6=+jL&s zLhi4NTXugt{n+4UzU;1_A>!Pwr<8&&&71i`tU~Cu@}mg_mQ1n#xmw`uTPrc{r4dFN4 zo_z9(jUR4_7f9Z{cYn#;7uq4dw`aaGTJh*r$Ew4bk_fhFG*20}j z4{uAnQ56+Zva;PT?)8R@*=;8SkH2?(u3{l<@tMCz*pvOY9oV>o<%sK=<4+IB&Db~h ze!~|&pL=bYhxfVM^;a&;`I^$w^S15V>K}Ek33dB(XKZYJCobu2Xmb6zsMNH5=B@{C z&CiysUsPpVee1T@{UgOyarRthPjfc>yP%&Dx3!;R|MXQ(YkxgW^q-Y>Jy@9c(yY%C z@~hV;E%WEMs43|=wy~JQgYU?1=`Ck=#+uHVYb?g8`|PsK1BIqb&lY@E-*UCNYRO5P zXIcu6yLuW*Cl=eaN=Dx8GZ&3c$ynoa)&Ipcw(NE$V<)G=uQ>v3|73X6dMl3d-4p%9 zax1Cls?VCEceX!g_#%CcZO487=6kyBxA=a!nC6|`Fh~5{n>m4#GXs=oEcGuvb)G}Z zWV2|jwqAtFF8{uTFKjae?!2__{rhWg_o}ZyuO3#D*77Y5IDXevSjB1U)+-k;xO%p; za(qxUJXPn=DjA~E{3iU)A6ciP1^1sGo*=hIxmw;eQQ5A zsKnOKzpJa#{GR9Cl0+^|N8j~+yHa$P{(hxBZPxRdo|Btetd@$hYKok1+t2;uyz9?5 zJ`w*{+kZGL;u0gpxBvN@35U-=yU;6f;as?Yr^Uv(Z~QNQY?V@~KYVS;rY#!YZ#=gw znyxFo(SG8L8ovqNrQ*N2ihu6>#UbzihC98hYGG=!Oyz4r+Y0>uv&7Wna^aQ zRrJ-Pr=R@)^S4mKI?d)*K%|bKXq&Fq{C}ly#r1Z3X*@pW^YEk7>VCKT9*2Ifot6DU z`sdv0FG~WXd+%EagwEcj(3x|6;`5#*COk>Y_!a*jwN^~`)U^~4y#D*e6{n*TFRzN| z&aPH^E$3S#!~NYW`}eoPKi^`Dzr4FEy>p4vo{!smGwh!&S#e%SL&o}GK*tl!es zV=*NX+n9IP<#aEr{h9Z~I7V=8i%oot-TK;4f0Gc`gu|cizii;{TH@~HJI(v-vM<(R z`7E~*f9*beF_2@D*5acE30F3l2bBKf;dy`jJeOovnw02{(x1OSq&?e_74M`bF+Ed= z{q?F3xhoF6Gd=zByRKftFHX^f_?<@cl($De%~Hu-D$cB$8u|CriPjJ6z3%m}&6(mU zepACIv5qNQytcud`50%K!RKd@e|G&eiL2L-*ZO_S#Iwiw`C0pgpI0O%ZOnMSGjfNR z9Mk#*Yd@?%$Xj|uI=-ea^GHX`tdCiCObxQF9d-u|ZT7LOJAUQetaP)w&w5-kZH3IW ziuX!N4jEthcthOjVBKzm^fZ=`E!(ATM;Vx?-Cr-V)Hu>IwKZkKo~h4{o}c7o#{25( zk+V}mWcJSM&U+g*af!e2hNY`F8Jg_moxR#~LadDitl{705JW)~Je2syG=%KO){*)6A@Iy!p(p3L9A`gY|U zzC!~4Z#|0JUh}y2hwtm2#~*_~cFnMs(C*&EVKrOePJ8&4%NM8qs$Twc^TYVrR}LHR zc%Svl@6So+LwmD-)(bdVg}*)#{#{JbYwB|Aq%HeH*Bw!wuD$B&8lQ;HH)8)&(o&PZ zFWi5`Vp&i68}n47%|2%Y&)ObVab1?zv0$xp!0h7^?)7{A@07P%Z`CKce%~Q4ZjL)! z#F+PL3!A)sdD5n3z4JY{+$?XmVEb27Zi&x!l~^U1x$eAiuhSm$uZ1f-ZcoXYc;$WB zDn+(!PjfdEy+2>E_vq_iCqK$8-Rr*UhnDEx1RaBA$Fja&wO?3yrtZiYj_Qu%XQHj| zFKxA`+jsx{x`|VGD$E6eVxhednE1!Mr-n(Mi9QLakuhvPJ%w1uZ>vKVU zUf-pedPf`ix)QF=w%)V+*h#6cw>~U;FmH*topkK2steu|>}nOXRvp{+ebr;<_wxn+ za|hhMJpJ1NmE@vm^C!y_cbP~g^Du_Jf8_7I!Ry7jxz?qgGXz6sIKEpxr;pd0ZRh(> zos0W#`&5;h>^d*YS^jIwk?9ZbCoK6NP?B{cEGFjK)+?_gN|SWwo|x9U;z#$79lzQ6 zS2{`MXjLZ$G>HG&XUt)7oAvj;Mce-h#|x=-ySn}){3pw*^>lTwbcLlEL)PbbmkH%tABf6 zx&&_i-v7B+LNF;dHgHm?8_TwJDbIs?Du32qS@XJe)|1;3bv$R?x4N?Jwa=bb%;bHo zMP&N7V}^TkrYTJFo_lhC)GnXh>(**C+ZRt<=(R9@RzdLJqu+&JTPaSzloIswmC?E* z1p;J;RFJt{JVXvQ?Jr12rEtETz zKXoGaqt1*^{zAue7KJ>k+#~o;eAc9{U;2}8J=%S6f5Ocbej1CnL~qpWpW&fd_qyvu zX{Mjg!;?G=5YpY+|00yz-XD`@oOCvkcAl%lA0}MA@iAEX2cJbw zfZNt(|GvnqITUYxzit1erMy8;Di>YvasGXvq-Og|<&t$p%*A{cm{?qIUw_|xdg8nr zKR$1lxU$cX$y3YX>s6^2|E{LKdA&>U{<#<3#~P0asQb9`I`;WpzP9(^(FQ%I%ZsNk zj{EId;`Ko7%ZF7SlP~W#X$=;tyDceYAChd>?IXw9yI*5}*Da0bp)(H0|F@P~e6Lbp z-uTB}&QETQSH3gn(&9=*SYLvJ4Co-%(9=>=~QiBE3{Lqo*nq692yk`3Bb$)mBj)vr3 zmX5t3xBu<9Ww2X5{;>H+*B= zlVM-7)OFe^lgO9%{`&5?eznZ#*5<4=`(L`%J3Dy=rsdxIe(&V__+2KuJ0!1sf338o zFSgy}e8sy6RlT^FD)#OC`lhn0vMzIaKYPr1y3t_%#ridtUF<3X!m!+Q{a(sBi^4-05YQWzZ znR6exm`_}Ce79TX+m2psUsbm4Z<{}Ja&~msch8L8f9%Ws{Rez*JwEX5%6+@TojW{Y zeD*0`y1L=<)u_+U?0j2h@k@D5PMFom%Cnr=xa7V4l6V`nu-10|_ssssdFCVs{mOU! zyu|m|rVsJOQW|RyT)(hq!KCC&`>+QlA!}|oe(RgCsAMnSq4_n5>lYk;`N{9knbYez zFVFEf6|}jf*}H%%{=t6h{|EZ5^B-J(|NlU>vHF8ktB&sQh!Nwy7IA9%l=yYGxLy7m zuerZuox_V1@ap#|Feg(^SRDZwB zvUbfC?Hxs{ZT48zH*9C#&9quiVeY5rTW#0>_4+x>cIuS2EAE?Ff-`Q;J2d0w`sDDe zW6t`1$K9ruwAW?m?D=c|(EM9*wEl-OHQqJf_C>g?N`1|AJGr-PzJ&VD+Q(vs$FvGp zZq4Hyjb%azt))r|KtwE@0UzAG_Ei<rSG+w_eBHrH z*+&^~l5F{_Xv}V}s@P55+Skc3+k@ zPMe=tD*NSy`-;{#7giiy=AQi3#A&U%XML~WW`i>pZQuCceioagKVgmIQ@e_Ni*#-7 zMMsB&#^##GqS#z%K*tvV5&dSbvyX5^cb0?JPv#))fbu3rax9)dh^48k7 z%Tyk-@wug6xR+3OZqI{dZ$-??{I*QnCc1G?`HTy;d+b_{9j{uEq~`zc)6)MR6+V2p z`1s?L&n_!I>|48OiOktmRW^#x-~XFob}hrYQrYO(<=aH3tUDRq-So;a(#V)2~k9}fKJ-6`SQvc_}L$z@^grzYD< zWrhSwvu8E$_-x!d!R2K5diLMxS$kPCHC`^sm1O#Jv9fws|H+dlA6$0%$$Gwa%2k)2 z!oqjlZ+_VL<4*d{_pjdDZdoI8I;c0>&_jjCdf(KLqEnA+!uGWNR=6l2zd*43tya~x z7jDaMXI`?MqSbr%+dSJ1nU$gzzn|IauQQP@{_?6zVSlE?l^tAbbG|z}*z2=go+oN2 zd8sWu`-{iMjTdzcvu(c2KEIkfT*hhD@~>0Y{}l3MR$S7!_}D%bF)Ir%8{Z=dAJx?> z;uk(He!DO!Q}XDG_xk5j-}y;Q>%Dv6qyF`Uk9!oREIqle>r-)5_4&FC?@K%Gy6Zn$ zYILMSaIu<7%@pPBMfx>n#-h^GzBo<@X1F&;rens|nLGS{o(`?%jD1zpbH3{Gk^9yH zd#fuS@)Rb<{Lj%_$LF{pG&-sDT2X3*l85Q?*Nj&uFRhLgjXV0>*wZvGNqCLeSC+W= z0zuh%r&Z@z`0|CMcJiHH_evo4RCL=Dw|)6#N9#4tAG^kFkY1s;toPyd4afG_Wt*+h zls%k(p@3PFZ^+yidmA?Zs>XVJm^H z(KoqwpR0a8C1b5`v+!Ain71b0nRim>UEa0&SO4Kp2hK`dtK+K?Ry({+>g?3#UtHH( za&_w6kaSm{V0TKV*JJCIc?MM#k=}(jc)rWm`Ayl|>Xm7AdzV?$xi>DY->nyK=HHwa z^;oo{`Jrs$+SlLvURcx`IlTHE>eR8GlcVAK%}f>EFUQ@t-JkSi!ml2KzZ>cTlV1r> zH#on4Z$!ew2X*uFoA|f;o)&RiE7kkzu*n@K1zEYv6a6*>{+>L`nC;;5Q#Dn8m@b@u zEwGC>x_Jlhl4r74SZ%9*B=}AL*}?ts(u(5lPh0H8R_%S29;nhNbIeEaz2Y3hb;fl+ zvpDX}l2|gC=ijD}Dr`Pc%jRD_Q2Y2q%$E6Ee8r!M?+{sBqdht4$?=l&_g#Khnatbp zk^9I#`Q6>+xy|SD4`tudFj*t#ad5u!0d4*Ie;-*x<6V#ZI{)zYbTJ>+g?cx3<{Z6oJ4(4(?%e+TrOJ(ZwZVHQ zE$+C?AAF}K`}r4DUs=_LS3+M|E5E$!ezr0|sNvRUwN$H?1?IP3K2zJaZdc;A0~Xn_ zi>>+e=KB2pFEy#jNZMZK?sw%^my@TTJK{TKs=U8%_Sa<-yzE%hU!`AWsSS$0FWJ6b zI-_Lc7Dp-X*~fR4N!Q#<)%j(%u1_!i?P34JHoTK&eohe-zWMp-v|TC>>;GgvxbB)E zYyExhDfuNI?Vm{2m437Hk-H|xyE-Fq@xPzR^KVTtmn~~4uQLnY>ECs>HKHrA_uaDJ zdkm$1o_@BeXXfoK_4^~YIz$=#Vaw*5@A~EL(UOh_0?kW)pI_&uy8KtcN{@E7r74T& zY}Ky%^IF-ea|+v2rel{jT1g0J-hWzhUv9;^w<2}(-ydn)E_%{U#Laxoy2$SzX6YRB z(K{6~Z_mdq4y`E%lob+Gugv!mSm4R%AAdt)v!%}eqcRflGV6AH&7F0qqu}VXi!G0D zJ~Mb4bjUXI@n0b>bA#yZc}F`0m3a<{@Oww~$=mCNEz=2-4KV!kK4_f5-;TP07w+s!C`Qd(nM zyugk5K-ZK78IH%=#26|XgqLYNeZHdOXYk^lwTlDzw=DU0GxCV;1*Pe|8Z%xwe%0}* zTe^;`F07;}^XKhq*MfFFS+1Jrla!U1{(gwr+@{3)-{1?6qV&?sZ5pXD!VYKXmio_M~+GK+oM7KM`sG%n8Y z&7R)$UB>RrlFRKcBQG4B&m&O%wI?ND^`*|`i}|kye-FIuG;!9YE?28*B|&M`ugw1l zh4ge?zF_+_SiN;&{ZI7%IF_sW z4w@%gVk3CM&r9`O&yZgreKxN0{DW7e?S>hr&a16>&T6dZbSj(0x=}JyO+>=|%k5e2 zr?#AyIo49e_{-&F$Zv7|S*Zbji=W4M{tA-QJNHdu_dU+1TA{~egCx%;O|+AYuF{C@ zDBR;8bYSb{T@p`fAIS+@*lerHc=@?FAu~SsqV?ffzjFSq%jBwG_xgzB!q#=4>X-E! z%szg{SZAJV(q7rGN9U!j6t%sSGJku+63a7hVxB#C{Wtqz;&-86W`Axq+$wM1o#1eq z|Hbbm^OL_m-_JEPR%3Rmy*B9&{do#C$dbFnv7Wxe`blV8CP`8+vgJy~ym_$PgO zak#kc@||X{RkbHw7Re2Mc#LbIu2X(+g-NW9!i8w2CFVy~QO zl}Yc`h^U;LoinSr$ZGL1f%3(k^VT{{+qCDyA#RKMe>Pdy*ZK16`kpxB#r65t_dg#D zmh=@FMVOXmnN76ISY^g>`m|!KP~#%y17{?>dC$+WRQ{#&{q7^PHGI1^d{;jj%X9I1 zjc`b?z;5~46^WknZ#a8Ao;4xisr2t5<)D$cYs(h7Wj%9u8L0km zJ{|GBO4}+q!mv)_+x_bvT>g_BCNASy;~})#hlTse^~Gxg4C3tec4S&kx#|(Rq-)jU zWWD{OCj~r}PPSiu@=Pc^BX(WEjwPA$0Xsa@h4`BhiOrsBejeTUZUE-y%YZ+KDU zp!e(jMJ@~Ht)%=B;Wo|m{-;)#AcwdMgoa}7z?2;1K^|^Pa zNw|w!Njz5MtemuF$;NBx|0)(Oad~kuf$idjl*s~Lw^``4eSY3ADy*kEVW#We04sE>E@Tf53f6xH~qV{iMiRHck>F5gD%2m$9nA2*gmQm7<(*!Z#Z$E!m(d7 zdv?3;H$Gay()#atto9zWOx9gxOU|#ob9%|XeAngHx0Pzn|KIR*wYf&?Kj)-LJ|A6Y z9C)}AJTo^ngeQ1rbzAo(f1Z`m?~HFg*Huq6Nqsi=G6?U)IYPo}uat^WF))$8gE$&_UOs9>)d z`*L;q>-Ln`>#=-^6YlSRV(;G`U(qXVer&1t^p;g}tDoPzd9i)g%eCjGYX)EIi(g|B zYa+2cvUj~st0c=TCB?(4Olg7->W+k~$vPccwIu6S?%hSM+><9cZa(4_sdD~! zJkr&2nmx@g1q940Gh6lJZ!CVczEy6E`ubw4+S~otKEBZkYnA=@`AwbCQ~RvH-1FmO zmnjM5L@EEiQP;S1-xY|DHj zn|I~^V(tg0$Iad(_y4+r^z!XF3zo7*oZ+!6<*huz^-*)tnIC)i8^{=bG-uuFwpF{^ zAXvS}E@`6CZkGc|ESG}s9cqfR-T3(tOG_-z@!7iK4{{zc9hrXi$S+w>i926xt!(cf zVfZZ4cU8(IK6yBq<2VF+Ml|=7W=bSY&<$c=xWZ*8R;yI(dKt9oOzl4 zNi}Mb`RRzY;ua)aIG&TPl0_(!GD`ckh3i zuKw|S|B1tYmkMsabmLk3cb3yZ9&Hzl=A>wK9x!fh*m&7}{>)ReSH8$jdz5nJa+3q= zDvvtRoy(WzZITmWgzoIpuwh!-crk@>qX}FHy4MM!B7jS90#Bmmo|l_WVpGr{IDu#3 z8+L3`3#KS3NFEBRdYrmh6}0URvcC#pcnU|VJc5U`7i*~q<4J*klg;DicK9xQT@d-z zKg?;GE@CGa%%CY@EGo_!@6N4G5^%V$_IYOEftXc`j;F>ro}c)4qKiC}ZkMI(rEa^u zZu6g;6`i##-L0XLDhKs8lEn)|6cr?$jLoeJ?-%JT`>|rdQHxNSARB4vk_Gmgx>9V~ z*E@K5aRnI%ZM422eedAziB5TZ(y!k}sm$u1=Gl57F6@Ow(XWdqPTcWlXNuf7EEYqmLCFD%Ip{|CamCOU)jEH)-3gluSJWz+}}TX82V!AS>yEJ zIeIo99Xk5L99|g4?s&9eTY1y!)!K7Tl&ucn+)&)OS}4tT<)^7X<6bak{J3SZhU-Nu z>uj^cEcf1)k9$Lp7gSy5S-WMU__m`j{QcgPA^@f z9ro~nX$ELijn7<_d^Rjlwz(Z&JcDEdD{9AZ5Uvkpq z#fH|V9Q%aLn&XY6ZGY##2+Rk#idc$m`Roj_W&3`{?p78KJw#e?Q z>&dGd#s6BjCjV(oaak+d%U`|j`jp8#IIrIBJ=pVu=gBg+2mellar7FWO%%Oib#+?h z7GAw2Y09Q`=6bz3?FW3Nj+wo++PeOEiOCNuM)5<?(mRy#F6U81o%I7 zgmwS?nxHS3`7->&-zAk61rN*>ZU2~>&7Cnl`o`zS%dIjWeYgJCbm&-$>i^D4Mk&w7 zOZ@F!9S(foab!+fn4{H6^*YxEKP5~4`n#tDU!=y~Z1zvjKiFw&pLI$>%CK`|gr%qR z_sdh&OP+S}+>#IevhK}KxzBah4Z5E->JQ{E5L5VhUia1}<_|BIPd3QBCzr8mC-daA z_X2rp)v3pA&fF{VoDwjj>h3S^h5xypz3mYEX)00EuUsGWd&}E@UpNqT4@;F)B?c%ruVsnXq|Xoz$UE;fUyE>}*Ko@Mych;@3XCpQVb= zL3JyaT%EH=zKOs?!>BeLW6^t?`;)5nrxg@uw@jR@&Y+#S$Y+bqtSxRkwW|d;C72$W zVmLX%^lNI)eDf-EUi|0k_{@8b;kxS7wZ1UG_?vt+so=o?8wdx^*hWUz6$S%DC_EmMSX$)L-+ng89N)Rhv_*CW!15H4eLX?9kNJAKypu zS!_%Z*w^#TDI>z-P}n)gOV@=rzVI-5>UXT)SzUSIWto=N_Q!j#?DqWM`s3w``z=+Au1H$!skW=@Sk-$~dRo$q&ul&NhI~)kTavyW z;xDN9$-g7x$wvcM|G5)e*4{sRp!7cLkJ3DGi+fg03nxEce0k1P4*vPvtJIVhzt1`q zcGvkyURtlITd6*y`1?$i|6

U+~YekXH`NJJz?M^~9eln=%!l(>ew>8vNDfJFLGD zFKDz^y6(pl1;Z8FGAw^^UsY_yWGHp_2_4~-5~jJ|$s zhUJynMIOq?h4g|C90uW3^tf=U()zWzNc|aORDb4LxTlvX`B=sk+SU z*Vk(C(AU|*uP)6f&N#JY`^nu$XRYmuTqHAZ!tN>8k6l!Ad-S+t<>KxoRh4fI1K)26 zt~h*CJoBJ^^05cgCyH>a)zUH%u3e_;+v#wiF=}JYqW~TC-M6DJZ`_riJ>_gC>t^@j zwEdmG^q!pXJfiqW^nTO@UOB#0#`a|rM%UOMZ~Nc6J&RGgV{PPz_sawV&&IXz`vs^O zO}Wy*$7*@yqf6SE!knA)TVBsm^FCa$@kq~|pfz*RbXQgWInCy`&4gYG|39vEGk;-paQQm1nyBsUbr()qe3nz3c-$vCh7bJg;od#9v$?4tiF&bs70W2$Gv#D`WlcI{Xob#tBG)Q2w|n%!?o zy>R>2zr3!O|6Y~sTTW)P)Yw-GDuW+Ru}7(qxOy9=2uL;Ry!)On*!I)oWUtEA^D!=N zZj-Jga4kyHJ+RVb(%hSt%2_Ynj@)^6@>TAe-!okbg{2OJmWoPe^51PSTzp~MMB{aH zdk^WRtzR?W{XM6`*2@BO_W8;zvf}MD@0f7yiD7~lN5J~C*-s)nKbLDQjNF`-@hn!_ zPpVH(f1;)M)SLAh0zb~amlD0Qvumf;sU6GZ8O3((-I~Dc`KBy^>FZptGb=eBGgn(5ovSRylymBp z($-6fRd#P?ELBPK?K~AIY`rA^%gZIn$Ct+%Ci%)_ndbA!`zKX=k?*s5x=f}J3Q+%1`l4Z5 zZv5dxt%@(aCWZEXbWDBwYm%E|n6!^vv--(xYHB%gz52hxj{oXszVP?Buz|<=vue61 z4Ukqx2cs6&3%eS>I)^1WRoL%%b+mGeeA~q%YHd0Fm)qPD99*TXY?dmuxv(l%g^5pE zclG#;2lHlJ-)hxb?&>plVaKlxqE3G43r?@$o)Moewny9QK$qHP=kViPR=+Ms2|ie> z`u-yG$0bVQH~KO!9@L+belt?ps_@nm)=JmxFA*ZG4_XvgocKA{Gs-emSBz(!deIHe zi3`k%?W8KVtm(aW`{Zu*w8>$a;pK~de@eQdSh|%zx_8Osh_@Hhp5!R3kiGho_n?jR zG=&CLV4e!ll1gk%01WRwL7w`0Y`;XOqy3`Ri*GIGE%N4#| zijVmD(IRN6-ulP}ub)1= zI_bsxV^^!st%%=m_v-rEuA-_PQmJopPB&i*FVLU7mDNLF_PieL@>36whPORhe8B9$ ztkp^P6a0?p#{Bv{t!avfc0JPG`UxBnT@4GrO0(1GUEZ5!d?iLJ z*naiytS)i2G?nBR@_Oa7w(rS#x!x^3IX5f>Y>mrTpZsBK^7dT%oLLFK=Ee9tGIC74 z6z9}i=)}QTINNYuh{xK6 zqPt#nP3%kOXPmb1X4LCKy<2KePF}NEpcB>4e{jPBhA)**y(b<@e^63$>7w(FRn?{Ao8)8#t<;&j_s{NOcy9OO#p1?}KdRnWE^iDg=2_$X z_5J4AGmbP?toE06eK~c>f0yHvszMhXUiPf#=!MQ^hc(L#lEvE=nrC)MB^)s1+Pf+A z!>8z6gNdJX@;@_P>tk~3{S`HNX5H~~KkY3h&7HVGn(;lu>+(H$JHN!cyILW|md0C- zh>c!LG@U1DYUj=|oPGFHyiVbh?!%(n_nkHN{-$l_zx>8|vv$qtLhma7{`#sal9#Y8 z?{1f%vYSMUt>^ibmUbV`#`N5Ix#yLBugiiTf*icOuG{nWH2Wm~ki78SUMHqgKhIiQ zxp{_0w8e)u$F&a)qSvkP=5cg52N`Xt{JcU^W$TG# z&AmN6I$B}MqCJ0eg^tO->)EhAZsAf9Gs_bm=bP3pZkAZ0qje)<(HxzWl&1^U=x#lK z$SpN>;;*d8=-jGR>{mA%{Ok>NZu4Y;do@9lsXzH#=u)q#+gEPM*d=%2=r7kbGD~9K zgde*3te|%G-CyFy`Caqx9@FCzduTPg!fvvUym8zm5uwI3;eN$X-G$mJIYyP54i1Tb zKFyldoXlO7#Kw93Rloz)U2J~wS(7B6)ci7$+0vuZE`Bw_rJwH@=Sxeb=+f373BN9F zi%OfoeELx1jDzlGM{bCQ9Pa-CTMMwwbKAD4tD%7wR~K(!k=uD`QUg-gPF0feVrkb*rhdSt&oMY8h&W)=Q9S zO>Xe+vfK$;>6%$vUoP`%yI^8whi)2gpYU5j z+-zIwgO{P4TVI(P&USb6VwtDLGJ~CsO8j^>WzkGLM7tBZ(2c>{H90txO z#Lw41xhBbbX{ndfiPen!#yWO^_zDr zs+i=Td+1-a@5S|Ri(OCtyYfU+^KC^ILv4@f!(i>VFO?^*(bAfE@HodB6;(v5?m%!q z!};17icfY>*f`!@0Ph5X|yEyF%MHDldcm9ndU@-^k~ zXOgN3$;ry0uJbQetFJ~HDmbuM`GAiicVmU0imzsnWNN0zY_r@A+1K?Zf8U!M{kZ4C zdHcDBr+8iod+nIicK_Pk<2$}{d2wFWNG$ll>ho!J2hXoBOSU#ot_ySC9$Rf9IR6Qo zj5LpH$P}rxAn9kdEb))_J&ic?vsvu%l_lFU(*AQCu~;2vEydI8Hf2}Fa*Lxa-td`@*jsx>}((udk1fUv*1cS{o6n4kw)#nqV z`IqmzoZy&NER|n=YMbSYi(j1!xL0htA-_*RKkX0K`H6`l0&MK7^Ec?cQuuTJ>0(i- zo-pO~LcNk18+cBq^*ZP>MFwx@dr|Y<{R_X6lx@0l)zK7_Dxo`DSDfAa_wts^;F1>? z9^TArS+f_Zq&aWGR9INJVcRw_@zv#8LAKj0=djxSTE0}9{n&=I2NRXu=e^u!fAZ9H zt+ok`U%!7pzJC8du6G(;5BaXG3Uw?!#-Da~p$c!n+oNn=m)dXL|9{N$-GkSfdb_rq zdlL2KsEUfY0%u~IwHqtzfyDbFNo>y(Z(gk6`LeQO%4;o)7eyH|udYbnIFhtmK*8FY zH&L5;;;jVTN5N`}RVM>VzRqo%c2lvyLd5CJgY1s;eJ)eCNS_dE(M$IFTBadh#<z=)JiUcbu)AN28#l@tTnw3 zpRN=|m>m$_Cu}=$?;`m#M_-ChnzcdN-RQo6W9RLj?YEazWv}t^a24hk6)Pz@uD*YN zw=HAs2J0KrbDMrCTK$Z@QdWBCrtQy+-ku|QLDzG`Rav`tU3GhVpz8$R%Z00N|NXgp z;v5@h z2wykn+uGZ z#a4-1o5X&oRa`Mr+GP}weQrs}WYIv+xhwZ4)3?_FxcO|LRzgQ`RI>6 z7xz}Tdk12fZFUNvRw$t2d#N-is6M^@_VMGvpP!#koHa{p-|=TpLksT9FTd=;aeH#h zgxYB{CvzU~oTT&0nX-P3)2U@B?^hDW&7)XY|5(jUGOjemZ6S-)b#FvyNNt| z_U?7uBb=sh-*eE|+DRmN{&5}d{bu`jTKrY|a`)Kn+Gr00cO{A6```3udEV;?Irm<1 z+2tL9%5%@NZ@RANBxk+rlFQtkdH1f~c{$zJY{uMaNF#ob(PITc1D6Fo3%8g2h=~Tw2-q!)ELp;2_ooAhr%B}Ke z5c5C2>$15-pEg6~#`S8!V2z{m_P;wgS!L1LS(S^sCfj{pqN)Avdwyp7y&9{NCP6X>r6nah zKYY;an6;H*`6UKv>vR|0yB#xb$Lzj+Q zpU5TgD6If+8D-$GAjGJp$KmWO(~qxKujk@i$f)$wMm8?fJM)uD%Fji-d(N8|xK0+Y z;XQQY@TmokbF&T~JIHwM+_{2x+eG|g(-ObuRQu-hQ5A?@=(7F;4P9m~i(#EmTdQM_zuaE<{+q$Iyx+eh zgzpG{-W9goXqVD5zPamuC2F?_EDv9Omch#IxpT;Jvkmd{!`9ZtE9H3U?Ae<8Uf#Rs zO;erIt5x&+v{&=5j(fT^7(H|XRtau+bh*Mua?+g0%*Q=9Wv2a4VwH!D=iXjATkCn<4jrj}JP&S}4;sBFY=b9<-ZBIYojq6`1_Z|TXgu&{g@>>N5d zWKx8$G`q!3@i^xnSDN}HJcW*&_&9Gztu?b>$U3K^F>`LZ1uxlHw%Vi0%b}xeql)Qz z-7986ilvMAJEV+{Wk`B|*J*EUYg-h(J#UxV2jd0r!UT~ZL z*GbEJW;waHAsy29K}DeAO0e5cZsuh^etvvH!ommRr3})2&aU-om0k>P@oU=5(o0i| zc&q3BK54(uIkV$0Z@;;=e@12SpEqV@fg3ok-#qepX3DQAJ8jjc9b;iS8?~pyGx$`( z&;EKvL&d!gTGM#=B%igi#rbj{vDGt3hC-1eo5e&jsrSQO&a^}@?1k})|qziH)` zWd+JFxdjXa7wl?X;d5_IdN0${S>48tew=34@~Zb%NnM+HD~(M#dCQcdB}nZqSet)_ zn}boyvW~W|>VqRbwQ- z^7{Jv&7H?5tTMajd*e@*bbZ(p!G z;#0<|h~mQk0$!UUFtr^hVyXB)54BsM0-KqlXr3z}t2{!pb@7kcwI6Fl&duA|dWU`I zhK;(+{_DL~gr0wp+z0L{jRJ{H7McW&%M)NQ`Q^yHNf?&$Z6 zC$Q~&0coH+*8H&hc z3En!$WxL;A_tPA{TIR5@u&Qu(A6{FArCOQGoxeUZc_}t$S8l|-S0Yt{y%R2b>2)pJ z&=)3K=%ataK=^majjQz>-_+7NRsm)zTJXvVnl84(%pK~wqu-PLb-{yOuwI}9D+HxhUB)eA@A|Bs9Fzo)8 zk(B@YLrIn9j$_k}Hk&HWJhPGYu~U}TB;iMeCTqCUdUM$SOI9xs(=>W{UHaiiO%GpB zN3Ph#dt*&2zt#nxoI7*v$%W#70+!2WUe&sqZ(t%7s4Qh?((i-ZnS(~=40i{kmS-O; zpWJ{n&W|ctx$~<&a`IW?c&leh^ppLavo(*nL|QKl+^kk0+Oz07_bKf8@b@d&USDa4EO&0X; z$qKpfyyJrB_g4$5O2ZdkdzIz3_3Amd{>59jUz~b?57c7Mc>PRRpi7!LA8}j~fhHuC<^**t|=VI^HhJQL4v!$k8JG9*=EBs@+ zYS<~Yo71K&T0LWSM3$E18?LpR7II21S+JnTsM+|$d&}?V=Gt5Q`Ix`r{L9jv&*y!f zQ#|kYzUH&-&*v2Pzd3Vc(x3S{rbiUB`=&ZS>Cp*^*s^_J$=tVgx;1l3j=TCaa2hbG z9blQkAe_MD^I(_ifou-_$BXOh{&BvlWyq7US#Wr~L z&tV4rS+`ym>!+!?EPC2y&;QDKbnxkp@B2zIZK2<+%f(Jfzo{4811!!Z(bPiyWQ{V46e^NzRr@6 zS7&?d*`DSNoPiP3Wcm-?;`y^BO;BLJ)W-7P-qy@Nf1NHmXI`Pf!zw*-W^kYLBJtEy z%h$SR)HD~iFJxG4R9p}s5h(HDj>V$b=4+d#-CVLR>-KStiXR1kZoTHY5c}W2cwyk$S|!=oA;?**~K8)_%-UuPII zZ}xQM7G+JR8z2AeU(2#@>4`-`ic_Mq1f`6*&u-m+RAjwkMC6Bbp6ObwD2rcOC}D&9G)USl$}>HruPa;Eu8YbL~@(1rh7a8?HU)tx0+#WO~&X=~M= zVoxQ3-BONH##&zd2b3#Jp6z?xeke!QtN(?kKvhuv97v8ucwB&`B6#NMKb9Rk^kw!| z?=H`n63t#=)!}&Y#fk8h9G^ZfBBAzFmQ8he{*S(S{kq&GGaRJ%?%1*7q3~}> zo>>km%iIz#O4w^jH(&a7-T<=sh_{#_7NSJvC?Vz^d9xTa(*eXyv+Gh9DMlfVQ$C8pEj19u3O{FvGvyEq$3)iuAV*2ELU>pf7!&?xpPEU zv%XzE!%FH$a@Fen%nV%(BB;|CrHn3bUz%yb4#PB4>hwg=;iL6F!A2mg?l!fxfwZSt4+%R zk#>#nPv4%en^yExM`_BUxH+yfi!`1(y7$Y~PCq4k=~%Dy#~+XTt*4Ye>t2+fk$Y$M z{Y58hwuPGC-M#wbb8)uh2XD*gCMTP{T6q2P9sgdjc~RaeXOmQ4@u-<@DSPT>d-QdN zc|hXI<$I)`vIhKC*}6D4#l_h8=8WjhXE(R+EUGmA=^N;Bt2bnI=F6SM`f~~ermdZ7 z{eJ1^X=$mc5C8pszuiisj{VL0sa@AjO|2@_)7EZYzg~Y=ndtt!bC(Y`vkM9eKHM|O zdnUM021juLD@VtBWv!`RR=Y}HtJRhVuD*IHa!UEjORiQT;VtVjv$7U_ubF>zUF>cF z0f7Y}bBo2wLPJA2cyh8Ug)ZDGUw!S=dC3m0UteB+R+;GA-OM*)vl!_QSzX?*l?BU(HEV zGP0h$ln$+3GvS8AdvdX1CbC#A(W11>_FeAVE>1VA= zo3~ZJIPv40MMv4%spU)jwUeN5K+y6 zL-YX4j02?}4Xh7Ke_Z%E4JoL>#Yh5E69c!?KN+OT5vprlFq00)`pfL+x1WX9nox-u z48jk5Q*Zs`k$9aI;GNH27@WD_tIhY?y0<3#o36TDxpLt*`$_-ves7Yq<6{MuAO?&cR zWxH{B;?+4Brz7+_&gpFWvV2a@3R}0I$@bpC(VucGPW`zx({Py_Sd3qU=DZa|7$b$?lkS1T_8VG;x94g%6&9Q>%ACMEbLEZR$0wGp zW<1W*Fu!JgpWW?04`onm?*yiBcE;+n#ibQBtb6uk7N}(I`Mz622d>#YZ}neCe-GYr{cB6EN4!t0VC#aT^A(gPEn56BQ^M=l zW+y3IF@D=`5;Z<*M~>KZ?`!N*>TM{VKiBejDpKN40F_}SZcH|*S55|hywZELr0Z92 zl?kt^=_MPl(Uu-oc+C9H`cn9Y99&c zewMaqN#7rX*MX{Wx(_GxXy|G}_pE*8% ztd{%Yk?1s^X^jQyj!&$s?6#$TImltZ)9LRUQ z+w%AyzZ~kqc8qU}3uEo^g*D!6liYutoPF!U4{aUus&lnb){l5FqMn;^p3$vSlY_qU z9lf*l>!S&Yt~u-F6@T{q zIscBPm@SUnxn<5@<(G0kA1_?)U3GWMg!b}1=f!+~OhPI{K~dhgy2Zh}v~cQ<+NXD} zR82aNm1?0Vl>2D4=wh*>;+Nb?7Ajs`dE%m2tk-m@wdyx5R!=j|YYJ-ssbCy(NfEH9q3MdZ&HR}rGIKY?m|prBu=r%=-Bf** z{d(m>^V9u6X$jg6*>R8aL%GsV;R8(|%%g5n9S6@slHF^H=3$%}|zyPAt)XXgq{n7ldSmHyrpeC}mwNuK}wi>!-9 z!_IbYbGyCB*p=m+)x5O-u(beol7ZDd>-zupM9sRq>e4SIi!1VzX0PqZ)KUGvOvkOS zgnwehJh|Q%M_I4=eRZA5(Rt2Ac4|oO@;y)Egf{7zU8=SE74yT+bz9JLoVcUsuV^++ zq48I!(s4JTZ4-kxJ;}(hp5NXSntAPC%%Z1euckeYYs(hg`4p)tY zM{SDuv%pNYci&2bJY7R)2N@_`lxFuiyF!0n;||613qvwwh_Bbh1htIK&VD)kYSZ0WkcaL=#cp55j` zJ5PSry=bX?ZMoW^>N5rvL08V_yC_{crRfzF-_f>1@r0F!qRmUYx3ysl71$#T${Mbk zhw~@AzP(;a*Os?9ZHeOkLml@XemvoQ@|ec{N{fi#rst)T4a-jZ@Vqe;DBiXtC|b7Y zd-&tCosKL!7bjOAT4LXldNT0D+nFt|UM2aQpWA!&cgq~sD|OBjV!$C{fBxAZ<&({t z8?k)Mu7TRd$4&*tUY~vLzG>b;)Fz1Dga#RpT?dX`&oMSk4{*O|wp-fdx`^WM#qt7z z{~5lmi*zv+tf;>y^WX>DDe=ck5>;37J9sqSIW@II#V|8xN9-#{*NMCGZ)a|P$}QMC z<;=@=RXf+fmFF{8{J45q@ZEdO&|kM)ABbf89KeQM(7AF4hbT+T&*+E<*Fx&LZL zoq+j_F8#RfMw68H5=#^$PnTayeE6U~xo92V?J$PS@&7J|{Ttr2NcV@6y{+326a9`n-{M+CU3YoY|*!S)el6? zByY!G+itfkW94hjwO=P&e>{3cG>{|e52uonrj^wEwuMXVwU=B>{&*p||6uzSf1fj( zUr4XexWsYe_nioxs!hKwCc1S?2sfVikWk6-+F!Wm_|xuxc6;A=sd9Q=bk~{`(P91m z#*Iat@7xqMT#Y=7!p~h-)V{@Cs8Y9muN|Yg+{MK8d$XqYT73zQpTx-@s}vNTf0$*i z`QN<>>tn4?^L7eNmN@V)aofjA2lMKrp!#p~C091oBT9EriQ0JDqhWcERpaZG(%mmb zzDj@1kn+k(VO3mTB|OoZx6RgM?xBWCi?@Ou7cK;_e0}{`K*Q?0ckS=oIXfJGy$b7C zD0cR&i>K_*#A}ze4RpF6ui@?avZ`GvfAd`Z+I-E9DJ^&1(OAs`?bfH(s45lj*~R9zS+HvF zJidbymmN6w;c(8~c8&Z06^n!qPuLe5SO4-^U2{gGu-i{QE4ytHE2pOX;mj`9te9cJ z`;gi8!{>cTQV;Jef4t`F$I0q4HTD|5%6_pV@a zQ&R47+0h?W_w;aOPwr#Y`@N#`)dV_M21b~;{jg_S`ZqbyrkcaZ`%A`6x4g{E_eZ|B z-B-D!^6=+}#k`U4VuQLO?uPMnF6vF#(m2B|w%uZ`_?y-YykhjZRwGVqQ zQ=NJ7g6LntWoHUypC!s3o@5xL66k84u=g}y-`!2}zq1eWed#%JS@`0q&y{iC{t9?J zQrNXw|iqsyhI&^b$`o`?* zdMqp~8llhUw8n;piZU@XpI^CIKjZE0(Cnw*a;MhJx2v7EEpsoY{Jr;f%QWtVUsW2faXVCmhuSWL3_+T@%lktjX=5})jRE#KWut~c1!|1h`S{_Z*d6ZhYQ%qXU}4{n6T z&rM;mR?U3-$t$w`{3Ly`Y*E|l-LtpIFFyQt;zL*a*`FnQa#6>N9Jrav;xnVKd{7m( z$Y+wO3QvAfx&09vYAR{q{A0v&q{J7w5eV)>8ZfFA$O<0w>`TSy4m2nnXqsTp zj&7Ji=je*F7B!e z5B288K3Mn4tI}Kc=8BgU`I)vH}Z0=s0oHhXu= zeLd0j?Erf+*Tne+mGLJ7c4;v>PCmV1sg?Z1z0o@g_f0=0FDPoxxI1Z9;pv~-Qh%?W z=Mb%5wD(nhUp_P-AYG^fUJa}ZMUU>B`JAn$>tOjEwa`5qzc4&Y-`6^&CEdXIV_5dh zDLpyuZSCRqZr7D%Pf5Lgx#{A<7e+B551LP&UQ!oc6V`O13IAaDOj(RONNK{{9WtU}+O|bPi#Aro@};$$cvkNx-0|tZ%)$IEzdtU2A$KV}b%uwPk7*f34Wm zwltIL@7SiPDy8q2Ykt@1lJ}{mdx23>ZI;rW<2=pkS0tq7_g4L4ir;PBeCB^w((*;8 zJNEqCyzq2|;O3xzPKWgm9cL{KGw+i>>sl0I*0x^5{p&xQE9d7oJijJ4<<;#~o68(N z8G&Z|F7qwexl_!z@7TkWp-VYB<~4`NF7Q6-T>5u5$D^%nW+`(m)c;8wMDBrJw`Tn3 zr&7;UaVkT4KWA@Rq<;S2`-U>N*B)$9wB1nkUDK1ND{V`AkF(HqFX?u!*R5;^o!J*8 zJl1+L)B&Y4p#m0uNCicjwJ z(`^mEI{gY;#H>e*%AF@zRW7CJpLD)-cXCQ_)~sFCs!2{;uiTK=_hnD}Q_%Ct&S7Z2d=s_D7WO= ztTpzHHGDQz#^z^LpNVZqhohlyNKE(PnF=DgQNF*Fd;+Tz|HTP~C%-?cU^IofZr_Yu zN?hGy^-ZPPV$2^iC0=p9@HSfbZI@_uHP4~i?|kiiNrsPhuGC0h8S{+r|7gRcdB`u+#8hfM>8S(}k$cvK+o8N`^|-i|8Vv2W>^!~q^acB{ z_M6U+0#pvQ>}`Mevck$eCE<-j<*(EL7bT^P)e`nmpG)6Q3Oe#?TarhJ*q5XN?^QRq z&T>iPN?Mx~5b`7WRr|*qv-SwFx8^P0x$-#o^uyjcX^$5=M|$Z6x&J<8yCu*)blQ{t z%BRs6)GJx8EaDe8m5bZh|H?!{Y&*jY^QKeF7M!?exKwjKYK_Gp{9+4>h0~2=i#Lk5 z=$>`T|8(ee{ja$pGG&{mZ;i@iPUgHcY5L&@`@}zMZkhf&L1699;KZ3EmA%Uz zEb%e%7ZR_^Sy0+^c~iKOCi@EG6%RG}H6qQng)QfF&HEhYov-rukMhKuIfrH+k~ICf zV4}U%zKNSJl)hHlRx$L_P)6E^jF+d)g3!_?K*V$Fmq#Lql8_} zjw$=^?TlIFuf%=G+jW!h_r;eomilJR*?A*uu5q#d#h!#x_{EMGc$L5 zJSHuBOt|SqmA{h&zl?>z_Pb@$ZhbPHQ?_)b?-gN*cX2{E9?m< zx`Q^K8>7M~C@B)`A}uBL;fS!mN3dcf!#2jLX2EYRFXvz8JG*WF|G)2rc2~dK=`Pfo zyXDa;Gme=1!jo<@91aCV6vCAcii8BRA7^D{eK^c-?=ex&@NeL~fM;u->g4M!&M_)Z zJ+Jns`t0!^zucRJ<5#@feC?Iq`t#|t&pQnO&uAQP1U8!3P|P(s%0Y zwcEu$f|^s+>p{(_1>3b{AFWv=F6{MlU5H!dQokE|UUE0x*2f%0Ed?1iCiX-KnL|J_hu)!pm7__9^?ZtwZxiy~Y? zj}|t6=e=ZdDS!W8vF~@wYf z4#!&UJ`j6#>ZfT|n*uL?d^gKB_e+In=*-m{^s3e0&5>x1{xMf5{9Z47$T_lZ@%;LKJC6i8 zuXhqoopY|A!Cx}BIlS~HzsB8fyk7f$v9(5X2Yhg4-1n!(+$>+`z^;0+gs;Eu{xJA1 zow8Q*z^3#29G@+}Z}HN%e@%wCOPZMF%H3OBm#!9iFnc{>acNd-)e<-J~U(xw^-pd<>HxzFE{VuPc z@auN=hkqr3|ID)d{&s}w>mBcRQ+)B|#x4!HdGq2*Djpqt`Tp?+N3kj`F6V21C$vwg zeqyKoH;F3UJxlLoU-G&VxKrqE z@VQg98rM%na@e<5FV7BCXPKX{gv0HipMi)DYp~yQe^9MwP@Q*R`Hsp%?zs_jqDw2j zCWlyDTDWNAJUwaIF1^F9tadZ@_`E7Vny%X6!?ACNVaVMVdTvp_UNmz4?oNF7_)Tov zTi#?9$2V>K$3&FlKfO%)m6flvE*()+9k}Y~;Igx&Lr}@bW!seL>$LA_Pt<#=bEL1k z+xlH~n%Rd3>>7VB2k->R9{O~pw&L^Z08f?D&$2z`UVMoG@ygR|MOwbp`yQTU&*OCY zrToW(-ZN+Hs}cToelKraY@NfybN?Ux+Z?;3%$%o=NwxjHV|=or9G6UQya40l=6!UWA3T1`<8 zPdxv|8FRuobN}`1BZuy&Jl@&3%=fl@)vZV0CQ5ow{2kiqymLd~EerPqP#55Jyor*q z(Nd0>gO5N{*&EC*il-{6SP7Rt<67d+x$%~IN?yCjhRsp8j7nMMlkJi=DyTBu>^vQq zxb~8=x}sF~^`{AL9A}-D$Ip3TX}MyeRo3crw-{4b5<4eQ-|7k*7@2!lfzm8-$W?*PcCb%WrY#lH*zr&2taVaT5$X`F@LU$SLHubhtgE zVw}qnZw`r#t5^J3r~JuDVpWLN%QY@MQ;Q0}{XFaZgSlwKwjFZm-}0B-;py3V*g5NU z_3``*f1l6&@nG*(3mt)(vzC0)6Jb5lV>(0HRKhMU#oNhGMO^X1t{sp2?z234o;&~H z-Z^&_csulbH*GNb%duxerRJh{dsywZ-|ewEx2I@NBWiuh;ekEyrG}^{RIV6za>`vaml5xS^3NDyR|BWwZRV z`hlhmylKlef7+A#v2nWHmz3K=Gq%S32@2SIY1!7iYEXxUZI*J}Zs~|=9drIx@$6Cu zjo^FgbcVUD@%L@{J|po%i)f~)S%b$nLuy3uvA<+ps7dw*_TNZr#Bsad~p zmFtfk2h8?<{&6uz#XU9oM^{nE`}59!|8Q31y>(pKz3o*_@{GWps?t}#bi1A6pPlwA z@zbAwH^Sn$ooh|+$;o>>Jy*+%(!sf8@iP0nfy@f?h5Kr_`HuEFeA>m8nX&gn^%;pT zK2{B<9`R4K&NutEQE7|qH10E+^{KlLJiF5a?|ScGVQE<)Xr21%Yn8-H{%7AF`3qW~ zJGHB1H}A%J&f|x+Z~A!eQh34t`$-S9J6BizN%oTeQ+2VNqpRmg$Nf8e(~UR1$XcYo zX8ZR~TZ9vmR!c=Tlx1oh46bF`@M5=~#K+$gB|N^D9aFk?#C(6;-QAJ5O)q|r{kSyZ zhx(=^8im({94Zt3xEyhDO}sWee$fMd$4O^%`*jZeTVGu8n%`TnZnnJL`YEl;it5Fy zwuzr-Yj&*4+?$~J%XrIpFpAoLjcPm3=OMZ{@rfI<|R|Ma( zkciYb;gGXpXg6Pf=+V@8&-%v3lRUW@JV6t>pIUn!d0C!Rw2rU7vSnG#D$r1TdCu8W zM^3Hh^W5t(>&&hBs=;4Xw=&M#cf0w?5`D1=472yDoEOSemWKA){$8wpkg>-^R^#8p z8FzN9wetSXr{2YK=}XCx_4E1)>SsM#@Rs-fJ(Heo4c}UVUK~EpART|~^!z)0M^;#;94vd5o^!|JKKt^&jEC93 zxqSWquRBWIyCvYy`uJJ*GP_T8?iLTWljakBAbiTd`jOzBi1}^+eBk?V+d6NVm0`7aiRWxxww8zIucTG46l8wCi@W@;XwN=5r#W+7Rrd-V z`rCK^Ve9|zddDW6ZJfncVp%OR`^E0Jza}ldB(YU>_Jo;RIo*$bxhht=ws1qgP}j;u zkFLr#E!|*`8inbzo^UJ_XzG&4(d3v~^mNUZ!m8h}?y5r4o4pIp&eU0SRy;V|CGp}T z{|$LZlY*i%C0=>n+MptwwLW^|_Tvh7%HJ2fP!Lm|H@mB7npbQ7@!to}CfnGlc&F$e znEt8H;L@j~^WJ4;A2z-=XU~?6Tdk~(cdy~(EqQt`>HEWNHMzAvm)x1|mK%LatJ<@> zEtBWsJGc4Vk=rw#>!rh6IE-o+I5{~CFYT=^&vX`169P#FV@V1?OQTlm_DPL!I{68n%x+aJ@#`M!C>1g@b3zL&- z9_?D5Cv(Y-fuH@0%lQ>LU#h?Goss^^%;u(>;6vIRL}hDj2v%%;`PhY!C_MacXC?)Gm6N&oi1VKQ^PELXwSv1KP5J$?`bG6 zm!JI0^4t5IJqF6srXpu^BGW%#pV)rL(?{60?$3@R0UJ^p%M;~GZkvDP&aU^}o@H{n zB4k>_sq(2?Pw1~K*^$1~Pc!6}#`RM_EmFUvT2JtOzUPQ1$FZ_AOaB<04%^RsTuo6a zPh^qCrR_=oy)-9<#d%ELpjl?2t0wcR$w%b!Rm+nxU9Bf~Mr_zEw)vf!sOD0xGlehP zzsyPM@#AXIX1!(?w_7r}>G;9mcqP}TN}uzl=|r!$Rw}>xY5A)#8yCT2hQ-DokAJA- zxHc=mwejrZwZB%iZI@rGXj8c>)n6oZkEn1`I1b&;+wEJG>*V0TQ1#`7Vy8-A`u@tUzgOHW-?y%uZ}NbjZ*$$Q zq$p;d4Igs=ey

kQSD8$bkbPV0p2)7 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�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 Date: Wed, 20 Apr 2011 21:12:46 +0200 Subject: [PATCH 121/147] Improved documentation of dispatchers --- .../scala/akka/dispatch/Dispatchers.scala | 10 +- akka-docs/pending/dispatchers-java.rst | 112 ++++-------------- akka-docs/pending/dispatchers-scala.rst | 89 +++++--------- 3 files changed, 54 insertions(+), 157 deletions(-) diff --git a/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala b/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala index 7c52e716f2..04ff6a9504 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Dispatchers.scala @@ -20,12 +20,12 @@ import java.util.concurrent.TimeUnit *

  *   val dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher("name")
  *   dispatcher
- *     .withNewThreadPoolWithBoundedBlockingQueue(100)
+ *     .withNewThreadPoolWithLinkedBlockingQueueWithCapacity(100)
  *     .setCorePoolSize(16)
  *     .setMaxPoolSize(128)
  *     .setKeepAliveTimeInMillis(60000)
  *     .setRejectionPolicy(new CallerRunsPolicy)
- *     .buildThreadPool
+ *     .build
  * 
*

* Java API. Dispatcher factory. @@ -34,12 +34,12 @@ import java.util.concurrent.TimeUnit *

  *   MessageDispatcher dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher("name");
  *   dispatcher
- *     .withNewThreadPoolWithBoundedBlockingQueue(100)
+ *     .withNewThreadPoolWithLinkedBlockingQueueWithCapacity(100)
  *     .setCorePoolSize(16)
  *     .setMaxPoolSize(128)
  *     .setKeepAliveTimeInMillis(60000)
- *     .setRejectionPolicy(new CallerRunsPolicy)
- *     .buildThreadPool();
+ *     .setRejectionPolicy(new CallerRunsPolicy())
+ *     .build();
  * 
*

* diff --git a/akka-docs/pending/dispatchers-java.rst b/akka-docs/pending/dispatchers-java.rst index 3aa1a34f13..8462999c1b 100644 --- a/akka-docs/pending/dispatchers-java.rst +++ b/akka-docs/pending/dispatchers-java.rst @@ -12,11 +12,7 @@ The event-based Actors currently consume ~600 bytes per Actor which means that y Default dispatcher ------------------ -For most scenarios the default settings are the best. Here we have one single event-based dispatcher for all Actors created. The dispatcher used is this one: - -.. code-block:: java - - Dispatchers.globalExecutorBasedEventDrivenDispatcher(); +For most scenarios the default settings are the best. Here we have one single event-based dispatcher for all Actors created. The dispatcher used is globalExecutorBasedEventDrivenDispatcher in akka.dispatch.Dispatchers. But if you feel that you are starting to contend on the single dispatcher (the 'Executor' and its queue) or want to group a specific set of Actors for a dedicated dispatcher for better flexibility and configurability then you can override the defaults and define your own dispatcher. See below for details on which ones are available and how they can be configured. @@ -49,7 +45,6 @@ There are six different types of message dispatchers: * Event-based * Priority event-based * Work-stealing event-based -* HawtDispatch-based event-driven Factory methods for all of these, including global versions of some of them, are in the 'akka.dispatch.Dispatchers' object. @@ -68,7 +63,7 @@ It would normally by used from within the actor like this: .. code-block:: java - class MyActor extends Actor { + class MyActor extends UntypedActor { public MyActor() { getContext().setDispatcher(Dispatchers.newThreadBasedDispatcher(getContext())); } @@ -102,14 +97,18 @@ Here is an example: .. code-block:: java + import akka.actor.Actor; + import akka.dispatch.Dispatchers; + import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; + class MyActor extends UntypedActor { public MyActor() { getContext().setDispatcher(Dispatchers.newExecutorBasedEventDrivenDispatcher(name) - .withNewThreadPoolWithBoundedBlockingQueue(100) + .withNewThreadPoolWithLinkedBlockingQueueWithCapacity(100) .setCorePoolSize(16) .setMaxPoolSize(128) .setKeepAliveTimeInMillis(60000) - .setRejectionPolicy(new CallerRunsPolicy) + .setRejectionPolicy(new CallerRunsPolicy()) .build()); } ... @@ -134,7 +133,7 @@ Priority event-based Sometimes it's useful to be able to specify priority order of messages, that is done by using PriorityExecutorBasedEventDrivenDispatcher and supply a java.util.Comparator[MessageInvocation] or use a akka.dispatch.PriorityGenerator (recommended): -Creating a PriorityExecutorBasedEventDrivenDispatcher using PriorityGenerator in Java: +Creating a PriorityExecutorBasedEventDrivenDispatcher using PriorityGenerator: .. code-block:: java @@ -155,8 +154,8 @@ Creating a PriorityExecutorBasedEventDrivenDispatcher using PriorityGenerator in // Create a new PriorityGenerator, lower prio means more important PriorityGenerator gen = new PriorityGenerator() { public int gen(Object message) { - if (message == "highpriority") return 0; // "highpriority" messages should be treated first if possible - else if (message == "lowpriority") return 100; // "lowpriority" messages should be treated last if possible + if (message.equals("highpriority")) return 0; // "highpriority" messages should be treated first if possible + else if (message.equals("lowpriority")) return 100; // "lowpriority" messages should be treated last if possible else return 50; // We default to 50 } }; @@ -193,12 +192,12 @@ Work-stealing event-based The 'ExecutorBasedEventDrivenWorkStealingDispatcher' is a variation of the 'ExecutorBasedEventDrivenDispatcher' in which Actors of the same type can be set up to share this dispatcher and during execution time the different actors will steal messages from other actors if they have less messages to process. This can be a great way to improve throughput at the cost of a little higher latency. -Normally the way you use it is to create an Actor companion object to hold the dispatcher and then set in in the Actor explicitly. +Normally the way you use it is to define a static field to hold the dispatcher and then set in in the Actor explicitly. .. code-block:: java class MyActor extends UntypedActor { - public static Dispatcher dispatcher = Dispatchers.newExecutorEventBasedWorkStealingDispatcher(name); + public static MessageDispatcher dispatcher = Dispatchers.newExecutorBasedEventDrivenWorkStealingDispatcher(name).build(); public MyActor() { getContext().setDispatcher(dispatcher); @@ -209,79 +208,6 @@ Normally the way you use it is to create an Actor companion object to hold the d Here is an article with some more information: `Load Balancing Actors with Work Stealing Techniques `_ Here is another article discussing this particular dispatcher: `Flexible load balancing with Akka in Scala `_ -HawtDispatch-based event-driven -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The 'HawtDispatcher' uses the `HawtDispatch threading library `_ which is a Java clone of libdispatch. All actors with this type of dispatcher are executed on a single system wide fixed sized thread pool. The number of of threads will match the number of cores available on your system. The dispatcher delivers messages to the actors in the order that they were producer at the sender. - -A 'HawtDispatcher' instance can be shared by many actors. Normally the way you use it is to create an Actor companion object to hold the dispatcher and then set in in the Actor explicitly. - -.. code-block:: java - - import akka.actor.dispatch.HawtDispatcher; - - class MyActor extends Actor { - public static Dispatcher dispatcher = new HawtDispatcher(); - - public MyActor() { - getContext().setDispatcher(dispatcher); - } - ... - } - -Since a fixed thread pool is being used, an actor using a 'HawtDispatcher' is restricted to executing non blocking operations. For example, the actor is NOT alllowed to: -* synchronously call another actor -* call 3rd party libraries that can block -* use sockets that are in blocking mode - -HawtDispatch supports integrating non-blocking Socket IO events with your actors. Every thread in the HawtDispatch thread pool is parked in an IO event loop when it is not executing an actors. The IO events can be configured to be get delivered to the actor in either the reactor or proactor style. For an example, see `HawtDispacherEchoServer.scala `_. - -A `HawtDispatcher` will aggregate cross actor messages by default. This means that if Actor *A* is executing and sends actor *B* 10 messages, those messages will not be delivered to actor *B* until *A*'s execution ends. HawtDispatch will aggregate the 10 messages into 1 single enqueue operation on to actor *B*'s inbox. This an significantly reduce mailbox contention when actors are very chatty. If you want to avoid this aggregation behavior, then create the `HawtDispatcher` like this: - -.. code-block:: java - - Dispatcher dispatcher = new HawtDispatcher(false); - -The `HawtDispatcher` provides a companion object that lets you use more advanced HawtDispatch features. For example to pin an actor so that it always executed on the same thread in the thread poool you would: - -.. code-block:: java - - ActorRef a = ... - HawtDispatcher.pin(a); - -If you have an Actor *b* which will be sending many messages to an Actor *a*, then you may want to consider setting *b*'s dispatch target to be *a*'s dispatch queue. When this is the case, messages sent from *b* to a will avoid cross thread mailbox contention. A side-effect of this is that the *a* and *b* actors will execute as if they shared a single mailbox. - -.. code-block:: java - - ActorRef a = ... - ActorRef b = ... - HawtDispatcher.target(b, HawtDispatcher.queue(a)); - -**Java API** - -.. code-block:: java - - MessageDispatcher dispatcher = Dispatchers.newExecutorEventBasedThreadPoolDispatcher(name); - -The dispatcher for an Typed Actor can be defined in the declarative configuration: - -.. code-block:: java - - ... // part of configuration - new Component( - MyTypedActor.class, - MyTypedActorImpl.class, - new LifeCycle(new Permanent()), - dispatcher, // <<== set it here - 1000); - ... - -It can also be set when creating a new Typed Actor programmatically. - -.. code-block:: java - - MyPOJO pojo = (MyPOJO) TypedActor.newInstance(MyPOJO.class, MyPOJOImpl.class, 1000, dispatcher); - Making the Actor mailbox bounded -------------------------------- @@ -295,7 +221,7 @@ You can make the Actor mailbox bounded by a capacity in two ways. Either you def actor { default-dispatcher { mailbox-capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) - # If positive then a bounded mailbox is used and the capacity is set to the number specificed + # If positive then a bounded mailbox is used and the capacity is set to the number specified } } @@ -310,7 +236,11 @@ For the 'ExecutorBasedEventDrivenDispatcher' and the 'ExecutorBasedWorkStealingD class MyActor extends UntypedActor { public MyActor() { - getContext().setDispatcher(Dispatchers.newExecutorBasedEventDrivenDispatcher(name, throughput, mailboxCapacity)); + int capacity = 100; + MailboxType mailboxCapacity = new BoundedMailbox(false, capacity, Dispatchers.MAILBOX_PUSH_TIME_OUT()); + MessageDispatcher dispatcher = + Dispatchers.newExecutorBasedEventDrivenDispatcher(name, throughput, mailboxCapacity).build(); + getContext().setDispatcher(dispatcher); } ... } @@ -321,7 +251,9 @@ Making it bounded (by specifying a capacity) is optional, but if you do, you nee ``_ class MyActor extends UntypedActor { public MyActor() { - getContext().setDispatcher(Dispatchers.newThreadBasedDispatcher(getContext(), mailboxCapacity, pushTimeout, pushTimeUnit)); + int mailboxCapacity = 100; + Duration pushTimeout = Dispatchers.MAILBOX_PUSH_TIME_OUT(); + getContext().setDispatcher(Dispatchers.newThreadBasedDispatcher(getContext(), mailboxCapacity, pushTimeout)); } ... } diff --git a/akka-docs/pending/dispatchers-scala.rst b/akka-docs/pending/dispatchers-scala.rst index 9b67b7b58e..3831c58549 100644 --- a/akka-docs/pending/dispatchers-scala.rst +++ b/akka-docs/pending/dispatchers-scala.rst @@ -5,7 +5,7 @@ Module stability: **SOLID** The Dispatcher is an important piece that allows you to configure the right semantics and parameters for optimal performance, throughput and scalability. Different Actors have different needs. -Akka supports dispatchers for both event-driven lightweight threads, allowing creation of millions threads on a single workstation, and thread-based Actors, where each dispatcher is bound to a dedicated OS thread. +Akka supports dispatchers for both event-driven lightweight threads, allowing creation of millions of threads on a single workstation, and thread-based Actors, where each dispatcher is bound to a dedicated OS thread. The event-based Actors currently consume ~600 bytes per Actor which means that you can create more than 6.5 million Actors on 4 G RAM. @@ -47,12 +47,27 @@ There are six different types of message dispatchers: * Event-based * Priority event-based * Work-stealing -* HawtDispatch-based event-driven Factory methods for all of these, including global versions of some of them, are in the 'akka.dispatch.Dispatchers' object. Let's now walk through the different dispatchers in more detail. +Thread-based +^^^^^^^^^^^^ + +The 'ThreadBasedDispatcher' binds a dedicated OS thread to each specific Actor. The messages are posted to a 'LinkedBlockingQueue' which feeds the messages to the dispatcher one by one. A 'ThreadBasedDispatcher' cannot be shared between actors. This dispatcher has worse performance and scalability than the event-based dispatcher but works great for creating "daemon" Actors that consumes a low frequency of messages and are allowed to go off and do their own thing for a longer period of time. Another advantage with this dispatcher is that Actors do not block threads for each other. + +It would normally by used from within the actor like this: + +.. code-block:: java + + class MyActor extends Actor { + public MyActor() { + self.dispatcher = Dispatchers.newThreadBasedDispatcher(self) + } + ... + } + Event-based ^^^^^^^^^^^ @@ -80,9 +95,13 @@ Here is an example: .. code-block:: scala + import akka.actor.Actor + import akka.dispatch.Dispatchers + import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy + class MyActor extends Actor { self.dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher(name) - .withNewThreadPoolWithBoundedBlockingQueue(100) + .withNewThreadPoolWithLinkedBlockingQueueWithCapacity(100) .setCorePoolSize(16) .setMaxPoolSize(128) .setKeepAliveTimeInMillis(60000) @@ -110,12 +129,11 @@ Priority event-based Sometimes it's useful to be able to specify priority order of messages, that is done by using PriorityExecutorBasedEventDrivenDispatcher and supply a java.util.Comparator[MessageInvocation] or use a akka.dispatch.PriorityGenerator (recommended): -Creating a PriorityExecutorBasedEventDrivenDispatcher using PriorityGenerator in Java: +Creating a PriorityExecutorBasedEventDrivenDispatcher using PriorityGenerator: .. code-block:: scala import akka.dispatch._ - import akka.actor._ val gen = PriorityGenerator { // Create a new PriorityGenerator, lower prio means more important @@ -138,17 +156,11 @@ Creating a PriorityExecutorBasedEventDrivenDispatcher using PriorityGenerator in a.dispatcher.suspend(a) // Suspening the actor so it doesn't start to treat the messages before we have enqueued all of them :-) a ! 'lowpriority - a ! 'lowpriority - a ! 'highpriority - a ! 'pigdog - a ! 'pigdog2 - a ! 'pigdog3 - a ! 'highpriority a.dispatcher.resume(a) // Resuming the actor so it will start treating its messages @@ -173,7 +185,7 @@ Normally the way you use it is to create an Actor companion object to hold the d .. code-block:: scala object MyActor { - val dispatcher = Dispatchers.newExecutorEventBasedWorkStealingDispatcher(name) + val dispatcher = Dispatchers.newExecutorBasedEventDrivenWorkStealingDispatcher(name).build } class MyActor extends Actor { @@ -184,54 +196,6 @@ Normally the way you use it is to create an Actor companion object to hold the d Here is an article with some more information: `Load Balancing Actors with Work Stealing Techniques `_ Here is another article discussing this particular dispatcher: `Flexible load balancing with Akka in Scala `_ -HawtDispatch-based event-driven -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The 'HawtDispatcher' uses the `HawtDispatch threading library `_ which is a Java clone of libdispatch. All actors with this type of dispatcher are executed on a single system wide fixed sized thread pool. The number of of threads will match the number of cores available on your system. The dispatcher delivers messages to the actors in the order that they were producer at the sender. - -A 'HawtDispatcher' instance can be shared by many actors. Normally the way you use it is to create an Actor companion object to hold the dispatcher and then set in in the Actor explicitly. - -.. code-block:: scala - - import akka.dispatch.HawtDispatcher - - object MyActor { - val dispatcher = new HawtDispatcher - } - - class MyActor extends Actor { - self.dispatcher = MyActor.dispatcher - ... - } - -Since a fixed thread pool is being used, an actor using a 'HawtDispatcher' is restricted to executing non blocking operations. For example, the actor is NOT alllowed to: -* synchronously call another actor -* call 3rd party libraries that can block -* use sockets that are in blocking mode - -HawtDispatch supports integrating non-blocking Socket IO events with your actors. Every thread in the HawtDispatch thread pool is parked in an IO event loop when it is not executing an actors. The IO events can be configured to be get delivered to the actor in either the reactor or proactor style. For an example, see `HawtDispacherEchoServer.scala `_. - -A `HawtDispatcher` will aggregate cross actor messages by default. This means that if Actor *A* is executing and sends actor *B* 10 messages, those messages will not be delivered to actor *B* until *A*'s execution ends. HawtDispatch will aggregate the 10 messages into 1 single enqueue operation on to actor *B*'s inbox. This an significantly reduce mailbox contention when actors are very chatty. If you want to avoid this aggregation behavior, then create the `HawtDispatcher` like this: - -.. code-block:: scala - - val dispatcher = new HawtDispatcher(false) - -The `HawtDispatcher` provides a companion object that lets you use more advanced HawtDispatch features. For example to pin an actor so that it always executed on the same thread in the thread poool you would: - -.. code-block:: scala - - val a: ActorRef = ... - HawtDispatcher.pin(a) - -If you have an Actor *b* which will be sending many messages to an Actor *a*, then you may want to consider setting *b*'s dispatch target to be *a*'s dispatch queue. When this is the case, messages sent from *b* to a will avoid cross thread mailbox contention. A side-effect of this is that the *a* and *b* actors will execute as if they shared a single mailbox. - -.. code-block:: scala - - val a: ActorRef = ... - val b: ActorRef = ... - HawtDispatcher.target(b, HawtDispatcher.queue(a)) - Making the Actor mailbox bounded -------------------------------- @@ -245,7 +209,7 @@ You can make the Actor mailbox bounded by a capacity in two ways. Either you def actor { default-dispatcher { mailbox-capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) - # If positive then a bounded mailbox is used and the capacity is set to the number specificed + # If positive then a bounded mailbox is used and the capacity is set to the number specified } } @@ -259,7 +223,8 @@ For the 'ExecutorBasedEventDrivenDispatcher' and the 'ExecutorBasedWorkStealingD .. code-block:: scala class MyActor extends Actor { - self.dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher(name, throughput, mailboxCapacity) + val mailboxCapacity = BoundedMailbox(capacity = 100) + self.dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher(name, throughput, mailboxCapacity).build ... } From 66c7c31fbbb996c565ba38211961f60f4f916d10 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Wed, 20 Apr 2011 21:33:03 +0200 Subject: [PATCH 122/147] Additional improvement of documentation of dispatchers --- akka-docs/pending/dispatchers-java.rst | 5 +++-- akka-docs/pending/dispatchers-scala.rst | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/akka-docs/pending/dispatchers-java.rst b/akka-docs/pending/dispatchers-java.rst index 8462999c1b..7889db30fc 100644 --- a/akka-docs/pending/dispatchers-java.rst +++ b/akka-docs/pending/dispatchers-java.rst @@ -237,7 +237,8 @@ For the 'ExecutorBasedEventDrivenDispatcher' and the 'ExecutorBasedWorkStealingD class MyActor extends UntypedActor { public MyActor() { int capacity = 100; - MailboxType mailboxCapacity = new BoundedMailbox(false, capacity, Dispatchers.MAILBOX_PUSH_TIME_OUT()); + Duration pushTimeout = new FiniteDuration(10, TimeUnit.SECONDS); + MailboxType mailboxCapacity = new BoundedMailbox(false, capacity, pushTimeout); MessageDispatcher dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher(name, throughput, mailboxCapacity).build(); getContext().setDispatcher(dispatcher); @@ -252,7 +253,7 @@ Making it bounded (by specifying a capacity) is optional, but if you do, you nee class MyActor extends UntypedActor { public MyActor() { int mailboxCapacity = 100; - Duration pushTimeout = Dispatchers.MAILBOX_PUSH_TIME_OUT(); + Duration pushTimeout = new FiniteDuration(10, TimeUnit.SECONDS); getContext().setDispatcher(Dispatchers.newThreadBasedDispatcher(getContext(), mailboxCapacity, pushTimeout)); } ... diff --git a/akka-docs/pending/dispatchers-scala.rst b/akka-docs/pending/dispatchers-scala.rst index 3831c58549..35df55724f 100644 --- a/akka-docs/pending/dispatchers-scala.rst +++ b/akka-docs/pending/dispatchers-scala.rst @@ -233,7 +233,9 @@ Making it bounded (by specifying a capacity) is optional, but if you do, you nee ``_ class MyActor extends Actor { - self.dispatcher = Dispatchers.newThreadBasedDispatcher(self, mailboxCapacity, pushTimeout, pushTimeoutUnit) + import akka.util.duration._ + self.dispatcher = Dispatchers.newThreadBasedDispatcher(self, mailboxCapacity = 100, + pushTimeOut = 10 seconds) ... } ``_ From 83cac71aad15c3071930ae87abcd834855509391 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Thu, 21 Apr 2011 13:34:26 +1200 Subject: [PATCH 123/147] Added a little to meta-docs --- akka-docs/dev/documentation.rst | 65 +++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/akka-docs/dev/documentation.rst b/akka-docs/dev/documentation.rst index d5678c58ff..9e280220e6 100644 --- a/akka-docs/dev/documentation.rst +++ b/akka-docs/dev/documentation.rst @@ -1,18 +1,69 @@ -Documentation -============= -The Akka documentation uses `reStructuredText -`_ as its markup and is built using -`Sphinx `_. +.. highlightlang:: rest + +.. _documentation: + +############### + Documentation +############### + +The Akka documentation uses `reStructuredText`_ as its markup language and is +built using `Sphinx`_. + +.. _reStructuredText: http://docutils.sourceforge.net/rst.html +.. _sphinx: http://sphinx.pocoo.org Sphinx ------- +====== More to come... reStructuredText ----------------- +================ More to come... + +Sections +-------- + +Section headings are very flexible in reST. We use the following convention in +the Akka documentation: + +* ``#`` (over and under) for module headings +* ``=`` for sections +* ``-`` for subsections +* ``^`` for subsubsections +* ``~`` for subsubsubsections + + +Cross-referencing +----------------- + +Sections that may be cross-referenced across the documentation should be marked +with a reference. To mark a section use ``.. _ref-name:`` before the section +heading. The section can then be linked with ``:ref:`ref-name```. These are +unique references across the entire documentation. + +For example:: + + .. _akka-module: + + ############# + Akka Module + ############# + + This is the module documentation. + + .. _akka-section: + + Akka Section + ============ + + Akka Subsection + --------------- + + Here is a reference to "akka section": :ref:`akka-section` which will have the + name "Akka Section". + From cff4ca773228d3c37693295029681acfc2a9a17b Mon Sep 17 00:00:00 2001 From: Heiko Seeberger Date: Thu, 21 Apr 2011 11:22:31 +0200 Subject: [PATCH 124/147] Removed OSGi from all modules except for akka-actor. Added OSGi example. --- .../src/main/scala/OsgiExample.scala | 31 +++++++++++++++++++ project/build/AkkaProject.scala | 20 ++++++++---- project/plugins/Plugins.scala | 8 ++--- 3 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 akka-samples/akka-sample-osgi/src/main/scala/OsgiExample.scala diff --git a/akka-samples/akka-sample-osgi/src/main/scala/OsgiExample.scala b/akka-samples/akka-sample-osgi/src/main/scala/OsgiExample.scala new file mode 100644 index 0000000000..77134e6e3c --- /dev/null +++ b/akka-samples/akka-sample-osgi/src/main/scala/OsgiExample.scala @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2009-2011 Scalable Solutions AB + */ +package sample.osgi + +import akka.actor.{ Actor, ActorRegistry } +import Actor._ + +import org.osgi.framework.{ BundleActivator, BundleContext } + +class Activator extends BundleActivator { + + def start(context: BundleContext) { + println("Starting the OSGi example ...") + val echo = actorOf[EchoActor].start() + val answer = (echo !! "OSGi example") + println(answer getOrElse "No answer!") + } + + def stop(context: BundleContext) { + Actor.registry.shutdownAll() + println("Stopped the OSGi example.") + } +} + +class EchoActor extends Actor { + + override def receive = { + case x => self.reply(x) + } +} diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index e3eeac4fb2..8073d8831b 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -154,6 +154,8 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val netty = "org.jboss.netty" % "netty" % "3.2.3.Final" % "compile" //ApacheV2 + lazy val osgi_core = "org.osgi" % "org.osgi.core" % "4.2.0" //ApacheV2 + lazy val protobuf = "com.google.protobuf" % "protobuf-java" % "2.3.0" % "compile" //New BSD lazy val sjson = "net.debasishg" % "sjson_2.9.0.RC1" % "0.11" % "compile" //ApacheV2 @@ -271,7 +273,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { // akka-actor subproject // ------------------------------------------------------------------------------------------------------------------- - class AkkaActorProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { + class AkkaActorProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) with OsgiProject { override def bndExportPackage = super.bndExportPackage ++ Seq("com.eaio.*;version=3.2") } @@ -329,8 +331,6 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { if (!networkTestsEnabled.value) Seq(TestFilter(test => !test.endsWith("NetworkTest"))) else Seq.empty } - - override def bndImportPackage = "javax.transaction;version=1.1" :: super.bndImportPackage.toList } // ------------------------------------------------------------------------------------------------------------------- @@ -377,6 +377,12 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { class AkkaSampleFSMProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) + class AkkaSampleOsgiProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) with BNDPlugin { + val osgiCore = Dependencies.osgi_core + override protected def bndPrivatePackage = List("sample.osgi.*") + override protected def bndBundleActivator = Some("sample.osgi.Activator") + } + class AkkaSamplesParentProject(info: ProjectInfo) extends ParentProject(info) { override def disableCrossPaths = true @@ -388,6 +394,8 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { new AkkaSampleRemoteProject(_), akka_remote) lazy val akka_sample_chat = project("akka-sample-chat", "akka-sample-chat", new AkkaSampleChatProject(_), akka_remote) + lazy val akka_sample_osgi = project("akka-sample-osgi", "akka-sample-osgi", + new AkkaSampleOsgiProject(_), akka_actor) lazy val publishRelease = { val releaseConfiguration = new DefaultPublishConfiguration(localReleaseRepository, "release", false) @@ -473,8 +481,8 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { def akkaArtifacts = descendents(info.projectPath / "dist", "*-" + version + ".jar") // ------------------------------------------------------------ - class AkkaDefaultProject(info: ProjectInfo, val deployPath: Path) extends DefaultProject(info) - with DeployProject with OSGiProject with McPom { + class AkkaDefaultProject(info: ProjectInfo, val deployPath: Path) extends DefaultProject(info) with DeployProject with McPom { + override def disableCrossPaths = true override def compileOptions = super.compileOptions ++ scalaCompileSettings.map(CompileOption) @@ -526,7 +534,7 @@ trait DeployProject { self: BasicScalaProject => } } -trait OSGiProject extends BNDPlugin { self: DefaultProject => +trait OsgiProject extends BNDPlugin { self: DefaultProject => override def bndExportPackage = Seq("akka.*;version=%s".format(projectVersion.value)) } diff --git a/project/plugins/Plugins.scala b/project/plugins/Plugins.scala index ce3e609964..a0c81fb26d 100644 --- a/project/plugins/Plugins.scala +++ b/project/plugins/Plugins.scala @@ -6,7 +6,6 @@ class Plugins(info: ProjectInfo) extends PluginDefinition(info) { // All repositories *must* go here! See ModuleConigurations below. // ------------------------------------------------------------------------------------------------------------------- object Repositories { - lazy val AquteRepo = "aQute Maven Repository" at "http://www.aqute.biz/repo" lazy val DatabinderRepo = "Databinder Repository" at "http://databinder.net/repo" } @@ -17,12 +16,11 @@ class Plugins(info: ProjectInfo) extends PluginDefinition(info) { // Therefore, if repositories are defined, this must happen as def, not as val. // ------------------------------------------------------------------------------------------------------------------- import Repositories._ - lazy val aquteModuleConfig = ModuleConfiguration("biz.aQute", AquteRepo) - lazy val spdeModuleConfig = ModuleConfiguration("us.technically.spde", DatabinderRepo) + lazy val spdeModuleConfig = ModuleConfiguration("us.technically.spde", DatabinderRepo) // ------------------------------------------------------------------------------------------------------------------- // Dependencies // ------------------------------------------------------------------------------------------------------------------- - lazy val bnd4sbt = "com.weiglewilczek.bnd4sbt" % "bnd4sbt" % "1.0.1" - lazy val spdeSbt = "us.technically.spde" % "spde-sbt-plugin" % "0.4.2" + lazy val bnd4sbt = "com.weiglewilczek.bnd4sbt" % "bnd4sbt" % "1.0.2" + lazy val spdeSbt = "us.technically.spde" % "spde-sbt-plugin" % "0.4.2" } From f804340a12f5a2b717a31377a0ae894d3a8f3f0d Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Thu, 21 Apr 2011 11:25:56 +0200 Subject: [PATCH 125/147] Tweaked the migration guide documentation --- akka-docs/general/index.rst | 8 ++++++-- .../general/migration-guide-0.7.x-0.8.x.rst | 6 +++--- .../general/migration-guide-0.8.x-0.9.x.rst | 12 +++++++----- .../general/migration-guide-0.9.x-0.10.x.rst | 4 ++-- .../general/migration-guide-1.0.x-1.1.x.rst | 17 ++++++++++------- akka-docs/general/migration-guides.rst | 8 -------- 6 files changed, 28 insertions(+), 27 deletions(-) delete mode 100644 akka-docs/general/migration-guides.rst diff --git a/akka-docs/general/index.rst b/akka-docs/general/index.rst index a9727dfee3..1d716ed63a 100644 --- a/akka-docs/general/index.rst +++ b/akka-docs/general/index.rst @@ -2,6 +2,10 @@ General ======= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 - migration-guides + migration-guide-0.7.x-0.8.x + migration-guide-0.8.x-0.9.x + migration-guide-0.9.x-0.10.x + migration-guide-0.10.x-1.0.x + migration-guide-1.0.x-1.1.x diff --git a/akka-docs/general/migration-guide-0.7.x-0.8.x.rst b/akka-docs/general/migration-guide-0.7.x-0.8.x.rst index 5c45eb76c1..4bf866a765 100644 --- a/akka-docs/general/migration-guide-0.7.x-0.8.x.rst +++ b/akka-docs/general/migration-guide-0.7.x-0.8.x.rst @@ -1,11 +1,11 @@ -Migrate from 0.7.x to 0.8.x -=========================== +Migration Guide 0.7.x to 0.8.x +============================== This is a case-by-case migration guide from Akka 0.7.x (on Scala 2.7.7) to Akka 0.8.x (on Scala 2.8.x) ------------------------------------------------------------------------------------------------------ Cases: -====== +------ Actor.send is removed and replaced in full with Actor.! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/akka-docs/general/migration-guide-0.8.x-0.9.x.rst b/akka-docs/general/migration-guide-0.8.x-0.9.x.rst index 359cb01602..ae4637c35a 100644 --- a/akka-docs/general/migration-guide-0.8.x-0.9.x.rst +++ b/akka-docs/general/migration-guide-0.8.x-0.9.x.rst @@ -1,7 +1,10 @@ +Migration Guide 0.8.x to 0.9.x +============================== + **This document describes between the 0.8.x and the 0.9 release.** Background for the new ActorRef -=============================== +------------------------------- In the work towards 0.9 release we have now done a major change to how Actors are created. In short we have separated identity and value, created an 'ActorRef' that holds the actual Actor instance. This allows us to do many great things such as for example: @@ -15,8 +18,7 @@ These changes means that there is no difference in defining Actors. You still us Here is a short migration guide with the things that you have to change. It is a big conceptual change but in practice you don't have to change much. -Migration Guide -=============== + Creating Actors with default constructor ---------------------------------------- @@ -111,7 +113,7 @@ However, for convenience you can import these functions and fields like below, w } Serialization -============= +------------- If you want to serialize it yourself, here is how to do it: @@ -134,7 +136,7 @@ If you are also using Protobuf then you can use the methods that work with Proto val actorRef2 = ActorRef.fromProtocol(protobufMessage) Camel -====== +----- Some methods of the se.scalablesolutions.akka.camel.Message class have been deprecated in 0.9. These are diff --git a/akka-docs/general/migration-guide-0.9.x-0.10.x.rst b/akka-docs/general/migration-guide-0.9.x-0.10.x.rst index 68ec0cb087..85c2b54e93 100644 --- a/akka-docs/general/migration-guide-0.9.x-0.10.x.rst +++ b/akka-docs/general/migration-guide-0.9.x-0.10.x.rst @@ -1,5 +1,5 @@ -Migration Guide from Akka 0.9.x to Akka 0.10.x -============================================== +Migration Guide 0.9.x to 0.10.x +=============================== Module akka-camel ----------------- diff --git a/akka-docs/general/migration-guide-1.0.x-1.1.x.rst b/akka-docs/general/migration-guide-1.0.x-1.1.x.rst index c32b2545ac..c473c44129 100644 --- a/akka-docs/general/migration-guide-1.0.x-1.1.x.rst +++ b/akka-docs/general/migration-guide-1.0.x-1.1.x.rst @@ -1,15 +1,18 @@ -Akka has now moved to Scala 2.9.x -^^^^^^^^^^^^^^^^^^^^ +Migration Guide 1.0.x to 1.1.x +=================================== + +**Akka has now moved to Scala 2.9.x** + Akka HTTP -========= +--------- # akka.servlet.Initializer has been moved to ``akka-kernel`` to be able to have ``akka-http`` not depend on ``akka-remote``, if you don't want to use the class for kernel, just create your own version of ``akka.servlet.Initializer``, it's just a couple of lines of code and there is instructions here: `Akka Http Docs `_ # akka.http.ListWriter has been removed in full, if you use it and want to keep using it, here's the code: `ListWriter `_ # Jersey-server is now a "provided" dependency for ``akka-http``, so you'll need to add the dependency to your project, it's built against Jersey 1.3 Akka Actor -========== +---------- # is now dependency free, with the exception of the dependency on the ``scala-library.jar`` # does not bundle any logging anymore, but you can subscribe to events within Akka by registering an event handler on akka.aevent.EventHandler or by specifying the ``FQN`` of an Actor in the akka.conf under akka.event-handlers; there is an ``akka-slf4j`` module which still provides the Logging trait and a default ``SLF4J`` logger adapter. @@ -22,16 +25,16 @@ Don't forget to add a SLF4J backend though, we recommend: # FSM: the onTransition method changed from Function1 to PartialFunction; there is an implicit conversion for the precise types in place, but it may be necessary to add an underscore if you are passing an eta-expansion (using a method as function value). Akka Typed Actor -================ +---------------- All methods starting with 'get*' are deprecated and will be removed in post 1.1 release. Akka Remote -=========== +----------- # ``UnparsebleException`` has been renamed to ``CannotInstantiateRemoteExceptionDueToRemoteProtocolParsingErrorException(exception, classname, message)`` Akka Testkit -============ +------------ The TestKit moved into the akka-testkit subproject and correspondingly into the ``akka.testkit` package. diff --git a/akka-docs/general/migration-guides.rst b/akka-docs/general/migration-guides.rst deleted file mode 100644 index 361f8e3c7a..0000000000 --- a/akka-docs/general/migration-guides.rst +++ /dev/null @@ -1,8 +0,0 @@ -Here are migration guides for the latest releases -================================================= - -* `Migrate 0.7.x -> 0.8.x `_ -* `Migrate 0.8.x -> 0.9.x `_ -* `Migrate 0.9.x -> 0.10.x `_ -* `Migrate 0.10.x -> 1.0.x `_ -* `Migrate 1.0.x -> 1.1.x `_ From 4868f726c54bd17e2b0b154fd60a37e9aa0b5119 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 21 Apr 2011 21:29:57 +0200 Subject: [PATCH 126/147] add Future.channel() for obtaining a completable channel --- akka-actor/src/main/scala/akka/dispatch/Future.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index 1f86613b47..315da653f0 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -6,7 +6,7 @@ package akka.dispatch import akka.AkkaException import akka.event.EventHandler -import akka.actor.Actor +import akka.actor.{Actor, Channel} import akka.routing.Dispatcher import akka.japi.{ Procedure, Function => JFunc } @@ -217,6 +217,14 @@ object Future { dispatcher.dispatchFuture(FutureInvocation(f.asInstanceOf[CompletableFuture[Any]], () => body)) f } + + /** + * Construct a completable channel + */ + def channel(timeout: Long = Actor.TIMEOUT) = new Channel[Any] { + val future = new DefaultCompletableFuture[Any](timeout) + def !(msg: Any) = future << msg + } } sealed trait Future[+T] { From b6446f50dd4fece98730ab67e567dd78491b2c4a Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 21 Apr 2011 21:31:24 +0200 Subject: [PATCH 127/147] proxy isDefinedAt/apply through TestActorRef --- .../src/main/scala/akka/testkit/TestActorRef.scala | 14 +++++++++++++- .../test/scala/akka/testkit/TestActorRefSpec.scala | 14 +++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala index b3c197cf7a..a31582ac3a 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala @@ -18,6 +18,18 @@ class TestActorRef[T <: Actor](factory: () => T) extends LocalActorRef(factory, dispatcher = CallingThreadDispatcher.global receiveTimeout = None + /** + * Query actor's current receive behavior. + */ + override def isDefinedAt(o : Any) = actor.isDefinedAt(o) + + /** + * Directly inject messages into actor receive behavior. Any exceptions + * thrown will be available to you, while still being able to use + * become/unbecome and their message counterparts. + */ + def apply(o : Any) { actor(o) } + /** * Retrieve reference to the underlying actor, where the static type matches the factory used inside the * constructor. Beware that this reference is discarded by the ActorRef upon restarting the actor (should this @@ -68,4 +80,4 @@ object TestActorRef { "\nOR try to change: 'actorOf[MyActor]' to 'actorOf(new MyActor)'.")) }) -} \ No newline at end of file +} diff --git a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala index 1d79100b71..f95b78d028 100644 --- a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala @@ -172,6 +172,7 @@ class TestActorRefSpec extends WordSpec with MustMatchers with BeforeAndAfterEac override def preRestart(reason: Throwable) { counter -= 1 } override def postRestart(reason: Throwable) { counter -= 1 } }).start() + self.dispatcher = CallingThreadDispatcher.global self link ref def receiveT = { case "sendKill" => ref ! Kill } }).start() @@ -230,6 +231,17 @@ class TestActorRefSpec extends WordSpec with MustMatchers with BeforeAndAfterEac EventHandler.removeListener(log) } + "proxy isDefinedAt/apply for the underlying actor" in { + val ref = TestActorRef[WorkerActor].start() + ref.isDefinedAt("work") must be (true) + ref.isDefinedAt("sleep") must be (false) + val ch = Future.channel() + ref ! ch + val f = ch.future + f must be ('completed) + f() must be ("complexReply") + } + } private def stopLog() = { @@ -242,4 +254,4 @@ class TestActorRefSpec extends WordSpec with MustMatchers with BeforeAndAfterEac l foreach {a => EventHandler.addListener(Actor.actorOf[EventHandler.DefaultListener])} } -} \ No newline at end of file +} From cb332b27563e81b4830c82f5998d82c2ade5c2ce Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 21 Apr 2011 21:31:30 +0200 Subject: [PATCH 128/147] make testActor.dispatcher=CallingThreadDispatcher - it's needed for unit testing, and it does not hurt since testActor does not send anything --- akka-testkit/src/main/scala/akka/testkit/TestKit.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala index c5cfec6e43..fdad53c4ff 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala @@ -20,6 +20,8 @@ class TestActor(queue : BlockingDeque[AnyRef]) extends Actor with FSM[Int, TestA import FSM._ import TestActor._ + self.dispatcher = CallingThreadDispatcher.global + startWith(0, None) when(0, stateTimeout = 5 seconds) { case Ev(SetTimeout(d)) => From df9be277188e23e42588887741886c586e24cceb Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 21 Apr 2011 21:59:12 +0200 Subject: [PATCH 129/147] test exception reception on TestActorRef.apply() --- akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala index f95b78d028..47c4908148 100644 --- a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala @@ -235,6 +235,7 @@ class TestActorRefSpec extends WordSpec with MustMatchers with BeforeAndAfterEac val ref = TestActorRef[WorkerActor].start() ref.isDefinedAt("work") must be (true) ref.isDefinedAt("sleep") must be (false) + intercept[IllegalActorStateException] { ref("work") } val ch = Future.channel() ref ! ch val f = ch.future From 1715e3ca582e866062354bc6899618e3d144514f Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 21 Apr 2011 22:06:46 +0200 Subject: [PATCH 130/147] add testing doc (scala) --- akka-docs/scala/fsm.rst | 36 ++-- akka-docs/scala/index.rst | 1 + akka-docs/scala/testing.rst | 350 ++++++++++++++++++++++++++++++++++++ 3 files changed, 361 insertions(+), 26 deletions(-) create mode 100644 akka-docs/scala/testing.rst diff --git a/akka-docs/scala/fsm.rst b/akka-docs/scala/fsm.rst index a23fd9edf4..b982f89a4b 100644 --- a/akka-docs/scala/fsm.rst +++ b/akka-docs/scala/fsm.rst @@ -1,5 +1,6 @@ +### FSM -=== +### .. sidebar:: Contents @@ -14,7 +15,7 @@ FSM Module stability: **STABLE** Overview -++++++++ +======== The FSM (Finite State Machine) is available as a mixin for the akka Actor and is best described in the `Erlang design principles @@ -29,7 +30,7 @@ These relations are interpreted as meaning: *If we are in state S and the event E occurs, we should perform the actions A and make a transition to the state S'.* A Simple Example -++++++++++++++++ +================ To demonstrate the usage of states we start with a simple FSM without state data. The state can be of any type so for this example we create the states A, @@ -94,7 +95,7 @@ FSM is finished by calling the :func:`initialize` method as last part of the ABC constructor. State Data -++++++++++ +========== The FSM can also hold state data associated with the internal state of the state machine. The state data can be of any type but to demonstrate let's look @@ -152,7 +153,7 @@ This encapsulation is what makes state machines a powerful abstraction, e.g. for handling socket states in a network server application. Reference -+++++++++ +========= This section describes the DSL in a more formal way, refer to `Examples`_ for more sample material. @@ -194,24 +195,7 @@ The :class:`FSM` trait takes two type parameters: Defining Timeouts ----------------- -The :class:`FSM` module uses :class:`akka.util.Duration` for all timing -configuration, which includes a mini-DSL: - -.. code-block:: scala - - import akka.util.duration._ // notice the small d - - val fivesec = 5.seconds - val threemillis = 3.millis - val diff = fivesec - threemillis - -.. note:: - - You may leave out the dot if the expression is clearly delimited (e.g. - within parentheses or in an argument list), but it is recommended to use it - if the time unit is the last token on a line, otherwise semi-colon inference - might go wrong, depending on what starts the next line. - +The :class:`FSM` module uses :ref:`Duration` for all timing configuration. Several methods, like :func:`when()` and :func:`startWith()` take a :class:`FSM.Timeout`, which is an alias for :class:`Option[Duration]`. There is an implicit conversion available in the :obj:`FSM` object which makes this @@ -344,7 +328,7 @@ state variable, as everything within the FSM actor is running single-threaded anyway. Internal Monitoring -******************* +^^^^^^^^^^^^^^^^^^^ Up to this point, the FSM DSL has been centered on states and events. The dual view is to describe it as a series of transitions. This is enabled by the @@ -398,7 +382,7 @@ are still executed in declaration order, though. a certain state cannot be forgot when adding new target states. External Monitoring -******************* +^^^^^^^^^^^^^^^^^^^ External actors may be registered to be notified of state transitions by sending a message :class:`SubscribeTransitionCallBack(actorRef)`. The named @@ -476,7 +460,7 @@ As for the :func:`whenUnhandled` case, this handler is not stacked, so each invocation of :func:`onTermination` replaces the previously installed handler. Examples -++++++++ +======== A bigger FSM example can be found in the sources: diff --git a/akka-docs/scala/index.rst b/akka-docs/scala/index.rst index 645efccf41..e54c88b979 100644 --- a/akka-docs/scala/index.rst +++ b/akka-docs/scala/index.rst @@ -6,3 +6,4 @@ Scala API actors fsm + testing diff --git a/akka-docs/scala/testing.rst b/akka-docs/scala/testing.rst new file mode 100644 index 0000000000..2860bd2cef --- /dev/null +++ b/akka-docs/scala/testing.rst @@ -0,0 +1,350 @@ +##################### +Testing Actor Systems +##################### + +.. sidebar:: Contents + + .. contents:: :local: + +.. module:: akka-testkit + :synopsis: Tools for Testing Actor Systems +.. moduleauthor:: Roland Kuhn +.. versionadded:: 1.0 +.. versionchanged:: 1.1 + added :class:`TestActorRef` + +As with any piece of software, automated tests are a very important part of the +development cycle. The actor model presents a different view on how units of +code are delimited and how they interact, which has an influence on how to +perform tests. + +Akka comes with a dedicated module :mod:`akka-testkit` for supporting tests at +different levels, which fall into two clearly distinct categories: + + - Testing isolated pieces of code without involving the actor model, meaning + without multiple threads; this implies completely deterministic behavior + concerning the ordering of events and no concurrency concerns and will be + called **Unit Testing** in the following. + - Testing (multiple) encapsulated actors including multi-threaded scheduling; + this implies non-deterministic order of events but shielding from + concurrency concerns by the actor model and will be called **Integration + Testing** in the following. + +There are of course variations on the granularity of tests in both categories, +where unit testing reaches down to white-box tests and integration testing can +encompass functional tests of complete actor networks. The important +distinction lies in whether concurrency concerns are part of the test or not. +The tools offered are described in detail in the following sections. + +Unit Testing with :class:`TestActorRef` +======================================= + +Testing the business logic inside :class:`Actor` classes can be divided into +two parts: first, each atomic operation must work in isolation, then sequences +of incoming events must be processed correctly, even in the presence of some +possible variability in the ordering of events. The former is the primary use +case for single-threaded unit testing, while the latter can only be verified in +integration tests. + +Normally, the :class:`ActorRef` shields the underlying :class:`Actor` instance +from the outside, the only communications channel is the actor's mailbox. This +restriction is an impediment to unit testing, which led to the inception of the +:class:`TestActorRef`. This special type of reference is designed specifically +for test purposes and allows access to the actor in two ways: either by +obtaining a reference to the underlying actor instance, or by invoking or +querying the actor's behaviour (:meth:`receive`). Each one warrants its own +section below. + +Obtaining a Reference to an :class:`Actor` +------------------------------------------ + +Having access to the actual :class:`Actor` object allows application of all +traditional unit testing techniques on the contained methods. Obtaining a +reference is done like this: + +.. code-block:: scala + + val actorRef = TestActorRef[MyActor] + val actor = actorRef.underlyingActor + +Since :class:`TestActorRef` is generic in the actor type it returns the +underlying actor with its proper static type. From this point on you may bring +any unit testing tool to bear on your actor as usual. + +Testing the Actor's Behavior +---------------------------- + +When the dispatcher invokes the processing behavior of an actor on a message, +it actually calls :meth:`apply` on the current behavior registered for the +actor. This starts out with the return value of the declared :meth:`receive` +method, but it may also be changed using :meth:`become` and :meth:`unbecome`, +both of which have corresponding message equivalents, meaning that the behavior +may be changed from the outside. All of this contributes to the overall actor +behavior and it does not lend itself to easy testing on the :class:`Actor` +itself. Therefore the :class:`TestActorRef` offers a different mode of +operation to complement the :class:`Actor` testing: it supports all operations +also valid on normal :class:`ActorRef`. Messages sent to the actor are +processed synchronously on the current thread and answers may be sent back as +usual. This trick is made possible by the :class:`CallingThreadDispatcher` +described below; this dispatcher is set implicitly for any actor instantiated +into a :class:`TestActorRef`. + +.. code-block:: scala + + val actorRef = TestActorRef(new MyActor) + val result = actorRef !! msg + result must be (expected) + +As the :class:`TestActorRef` is a subclass of :class:`LocalActorRef` with a few +special extras, also aspects like linking to a supervisor and restarting work +properly, as long as all actors involved use the +:class:`CallingThreadDispatcher`. As soon as you add elements which include +more sophisticated scheduling you leave the realm of unit testing as you then +need to think about proper synchronization again (in most cases the problem of +waiting until the desired effect had a chance to happen). + +One more special aspect which is overridden for single-threaded tests is the +:meth:`receiveTimeout`, as including that would entail asynchronous queuing of +:obj:`ReceiveTimeout` messages, violating the synchronous contract. + +.. warning:: + + To summarize: :class:`TestActorRef` overwrites two fields: it sets the + dispatcher to :obj:`CallingThreadDispatcher.global` and it sets the + :obj:`receiveTimeout` to zero. + +The Way In-Between +------------------ + +If you want to test the actor behavior, including hotswapping, but without +involving a dispatcher and without having the :class:`TestActorRef` swallow +any thrown exceptions, then there is another mode available for you: just use +the :class:`TestActorRef` as a partial function, the calls to +:meth:`isDefinedAt` and :meth:`apply` will be forwarded to the underlying +actor: + +.. code-block:: scala + + val ref = TestActorRef[MyActor] + ref.isDefinedAt('unknown) must be (false) + intercept[IllegalActorStateException] { ref(RequestReply) } + +Use Cases +--------- + +You may of course mix and match both modi operandi of :class:`TestActorRef` as +suits your test needs: + + - one common use case is setting up the actor into a specific internal state + before sending the test message + - another is to verify correct internal state transitions after having sent + the test message + +Feel free to experiment with the possibilities, and if you find useful +patterns, don't hesitate to let the Akka forums know about them! Who knows, +common operations might even be worked into nice DSLs. + +Integration Testing with :class:`TestKit` +========================================= + +When you are reasonably sure that your actor's business logic is correct, the +next step is verifying that it works correctly within its intended environment +(if the individual actors are simple enough, possibly because they use the +:mod:`FSM` module, this might also be the first step). The definition of the +environment depends of course very much on the problem at hand and the level at +which you intend to test, ranging for functional/integration tests to full +system tests. The minimal setup consists of the test procedure, which provides +the desired stimuli, the actor under test, and an actor receiving replies. +Bigger systems replace the actor under test with a network of actors, apply +stimuli at varying injection points and arrange results to be sent from +different emission points, but the basic principle stays the same in that a +single procedure drives the test. + +The :class:`TestKit` trait contains a collection of tools which makes this +common task easy: + +.. code-block:: scala + + class MySpec extends WordSpec with MustMatchers with TestKit { + + "An Echo actor" must { + + "send back messages unchanged" in { + + val echo = Actor.actorOf[EchoActor].start() + echo ! "hello world" + expectMsg("hello world") + + } + + } + + } + +The :class:`TestKit` contains an actor named :obj:`testActor` which is +implicitly used as sender reference when dispatching messages from the test +procedure. This enables replies to be received by this internal actor, whose +only function is to queue them so that interrogation methods like +:meth:`expectMsg` can examine them. The :obj:`testActor` may also be passed to +other actors as usual, usually subscribing it as notification listener. There +is a whole set of examination methods, e.g. receiving all consecutive messages +matching certain criteria, receiving a whole sequence of fixed messages or +classes, receiving nothing for some time, etc. + +.. note:: + + The test actor shuts itself down by default after 5 seconds (configurable) + of inactivity, relieving you of the duty of explicitly managing it. + +Another important part of functional testing concerns timing: certain events +must not happen immediately (like a timer), others need to happen before a +deadline. Therefore, all examination methods accept an upper time limit within +the positive or negative result must be obtained. Lower time limits need to be +checked external to the examination, which is facilitated by a new construct +for managing time constraints: + +.. code-block:: scala + + within([min, ]max) { + ... + } + +The block given to :meth:`within` must complete after a :ref:`Duration` which +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. + +CallingThreadDispatcher +======================= + +The :class:`CallingThreadDispatcher` serves good purposes in unit testing, as +described above, but originally it was conceived in order to allow contiguous +stack traces to be generated in case of an error. As this special dispatcher +runs everything which would normally be queued directly on the current thread, +the full history of a message's processing chain is recorded on the call stack, +so long as all intervening actors run on this dispatcher. + +How it works +------------ + +When receiving an invocation, the :class:`CallingThreadDispatcher` checks +whether the receiving actor is already active on the current thread. The +simplest example for this situation is an actor which sends a message to +itself. In this case, processing cannot continue immediately as that would +violate the actor model, so the invocation is queued and will be processed when +the active invocation on that actor finishes its processing; thus, it will be +processed on the calling thread, but simply after the actor finishes its +previous work. In the other case, the invocation is simply processed +immediately on the current thread. Futures scheduled via this dispatcher are +also executed immediately. + +This scheme makes the :class:`CallingThreadDispatcher` work like a general +purpose dispatcher for any actors which never block on external events. + +In the presence of multiple threads it may happen that two invocations of an +actor running on this dispatcher happen on two different threads at the same +time. In this case, both will be processed directly on their respective +threads, where both compete for the actor's lock and the loser has to wait. +Thus, the actor model is left intact, but the price is loss of concurrency due +to limited scheduling. In a sense this is equivalent to traditional mutex style +concurrency. + +The other remaining difficulty is correct handling of suspend and resume: when +an actor is suspended, subsequent invocations will be queued in thread-local +queues (the same ones used for queuing in the normal case). The call to +:meth:`resume`, however, is done by one specific thread, and all other threads +in the system will probably not be executing this specific actor, which leads +to the problem that the thread-local queues cannot be emptied by their native +threads. Hence, the thread calling :meth:`resume` will collect all currently +queued invocations from all threads into its own queue and process them. + +Limitations +----------- + +If an actor's behavior blocks on a something which would normally be affected +by the calling actor after having sent the message, this will obviously +dead-lock when using this dispatcher. This is a common scenario in actor tests +based on :class:`CountDownLatch` for synchronization: + +.. code-block:: scala + + val latch = new CountDownLatch(1) + actor ! startWorkAfter(latch) // actor will call latch.await() before proceeding + doSomeSetupStuff() + latch.countDown() + +The example would hang indefinitely within the message processing initiated on +the second line and never reach the fourth line, which would unblock it on a +normal dispatcher. + +Thus, keep in mind that the :class:`CallingThreadDispatcher` is not a +general-purpose replacement for the normal dispatchers. On the other hand it +may be quite useful to run your actor network on it for testing, because if it +runs without dead-locking chances are very high that it will not dead-lock in +production. + +.. warning:: + + The above sentence is unfortunately not a strong guarantee, because your + code might directly or indirectly change its behavior when running on a + different dispatcher. If you are looking for a tool to help you debug + dead-locks, the :class:`CallingThreadDispatcher` may help with certain error + scenarios, but keep in mind that it has may give false negatives as well as + false positives. + +Benefits +-------- + +To summarize, these are the features with the :class:`CallingThreadDispatcher` +has to offer: + + - Deterministic execution of single-threaded tests while retaining nearly full + actor semantics + - Full message processing history leading up to the point of failure in + exception stack traces + - Exclusion of certain classes of dead-lock scenarios + +.. _Duration: + +Duration +======== + +Durations are used throughout the Akka library, wherefore this concept is +represented by a special data type, :class:`Duration`. Values of this type may +represent infinite (:obj:`Duration.Inf`, :obj:`Duration.MinusInf`) or finite +durations, where the latter are constructable using a mini-DSL: + +.. code-block:: scala + + import akka.util.duration._ // notice the small d + + val fivesec = 5.seconds + val threemillis = 3.millis + val diff = fivesec - threemillis + assert (diff < fivesec) + +.. note:: + + You may leave out the dot if the expression is clearly delimited (e.g. + within parentheses or in an argument list), but it is recommended to use it + if the time unit is the last token on a line, otherwise semi-colon inference + might go wrong, depending on what starts the next line. + +Java provides less syntactic sugar, so you have to spell out the operations as +method calls instead: + +.. code-block:: java + + final Duration fivesec = Duration.create(5, "seconds"); + final Duration threemillis = Duration.parse("3 millis"); + final Duration diff = fivesec.minus(threemillis); + assert (diff.lt(fivesec)); + assert (Duration.Zero().lt(Duration.Inf())); + + + From 78250478a770205e2ee83298bd58968c8c862756 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 21 Apr 2011 22:20:43 +0200 Subject: [PATCH 131/147] add references to testkit example from Ray --- .../scala/migration-guide-0.7.x-0.8.x.rst | 94 ---------- .../scala/migration-guide-0.8.x-0.9.x.rst | 170 ------------------ .../scala/migration-guide-0.9.x-0.10.x.rst | 45 ----- .../scala/migration-guide-1.0.x-1.1.x.rst | 37 ---- akka-docs/scala/migration-guides.rst | 8 - akka-docs/scala/testing.rst | 23 +++ akka-docs/scala/testkit-example.rst | 145 +++++++++++++++ 7 files changed, 168 insertions(+), 354 deletions(-) delete mode 100644 akka-docs/scala/migration-guide-0.7.x-0.8.x.rst delete mode 100644 akka-docs/scala/migration-guide-0.8.x-0.9.x.rst delete mode 100644 akka-docs/scala/migration-guide-0.9.x-0.10.x.rst delete mode 100644 akka-docs/scala/migration-guide-1.0.x-1.1.x.rst delete mode 100644 akka-docs/scala/migration-guides.rst create mode 100644 akka-docs/scala/testkit-example.rst diff --git a/akka-docs/scala/migration-guide-0.7.x-0.8.x.rst b/akka-docs/scala/migration-guide-0.7.x-0.8.x.rst deleted file mode 100644 index 5c45eb76c1..0000000000 --- a/akka-docs/scala/migration-guide-0.7.x-0.8.x.rst +++ /dev/null @@ -1,94 +0,0 @@ -Migrate from 0.7.x to 0.8.x -=========================== - -This is a case-by-case migration guide from Akka 0.7.x (on Scala 2.7.7) to Akka 0.8.x (on Scala 2.8.x) ------------------------------------------------------------------------------------------------------- - -Cases: -====== - -Actor.send is removed and replaced in full with Actor.! -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: scala - - myActor send "test" - -becomes - -.. code-block:: scala - - myActor ! "test" - -Actor.! now has it's implicit sender defaulted to None -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: scala - - def !(message: Any)(implicit sender: Option[Actor] = None) - -"import Actor.Sender.Self" has been removed because it's not needed anymore -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Remove - -.. code-block:: scala - - import Actor.Sender.Self - -Actor.spawn now uses manifests instead of concrete class types -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: scala - - val someActor = spawn(classOf[MyActor]) - -becomes - -.. code-block:: scala - - val someActor = spawn[MyActor] - -Actor.spawnRemote now uses manifests instead of concrete class types -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: scala - - val someActor = spawnRemote(classOf[MyActor],"somehost",1337) - -becomes - -.. code-block:: scala - - val someActor = spawnRemote[MyActor]("somehost",1337) - -Actor.spawnLink now uses manifests instead of concrete class types -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: scala - - val someActor = spawnLink(classOf[MyActor]) - -becomes - -.. code-block:: scala - - val someActor = spawnLink[MyActor] - -Actor.spawnLinkRemote now uses manifests instead of concrete class types -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: scala - - val someActor = spawnLinkRemote(classOf[MyActor],"somehost",1337) - -becomes - -.. code-block:: scala - - val someActor = spawnLinkRemote[MyActor]("somehost",1337) - -**Transaction.atomic and friends are moved into Transaction.Local._ and Transaction.Global._** -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -We now make a difference between transaction management that are local within a thread and global across many threads (and actors). diff --git a/akka-docs/scala/migration-guide-0.8.x-0.9.x.rst b/akka-docs/scala/migration-guide-0.8.x-0.9.x.rst deleted file mode 100644 index 359cb01602..0000000000 --- a/akka-docs/scala/migration-guide-0.8.x-0.9.x.rst +++ /dev/null @@ -1,170 +0,0 @@ -**This document describes between the 0.8.x and the 0.9 release.** - -Background for the new ActorRef -=============================== - -In the work towards 0.9 release we have now done a major change to how Actors are created. In short we have separated identity and value, created an 'ActorRef' that holds the actual Actor instance. This allows us to do many great things such as for example: - -* Create serializable, immutable, network-aware Actor references that can be freely shared across the network. They "remember" their origin and will always work as expected. -* Not only kill and restart the same supervised Actor instance when it has crashed (as we do now), but dereference it, throw it away and make it eligible for garbage collection. -* etc. much more - -These work very much like the 'PID' (process id) in Erlang. - -These changes means that there is no difference in defining Actors. You still use the old Actor trait, all methods are there etc. But you can't just new this Actor up and send messages to it since all its public API methods are gone. They now reside in a new class; 'ActorRef' and use need to use instances of this class to interact with the Actor (sending messages etc.). - -Here is a short migration guide with the things that you have to change. It is a big conceptual change but in practice you don't have to change much. - -Migration Guide -=============== - -Creating Actors with default constructor ----------------------------------------- - -From: - -.. code-block:: scala - - val a = new MyActor - a ! msg - -To: - -.. code-block:: scala - - import Actor._ - val a = actorOf[MyActor] - a ! msg - -You can also start it in the same statement: - -.. code-block:: scala - - val a = actorOf[MyActor].start - -Creating Actors with non-default constructor --------------------------------------------- - -From: - -.. code-block:: scala - - val a = new MyActor(..) - a ! msg - -To: - -.. code-block:: scala - - import Actor._ - val a = actorOf(new MyActor(..)) - a ! msg - -Use of 'self' ActorRef API --------------------------- - -Where you have used 'this' to refer to the Actor from within itself now use 'self': - -.. code-block:: scala - - self ! MessageToMe - -Now the Actor trait only has the callbacks you can implement: -* receive -* postRestart/preRestart -* init/shutdown - -It has no state at all. - -All API has been moved to ActorRef. The Actor is given its ActorRef through the 'self' member variable. -Here you find functions like: -* !, !!, !!! and forward -* link, unlink, startLink, spawnLink etc -* makeTransactional, makeRemote etc. -* start, stop -* etc. - -Here you also find fields like -* dispatcher = ... -* id = ... -* lifeCycle = ... -* faultHandler = ... -* trapExit = ... -* etc. - -This means that to use them you have to prefix them with 'self', like this: - -.. code-block:: scala - - self ! Message - -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 = ... - spawnLink[OtherActor] - ... - } - -Serialization -============= - -If you want to serialize it yourself, here is how to do it: - -.. code-block:: scala - - val actorRef1 = actorOf[MyActor] - - val bytes = actorRef1.toBinary - - val actorRef2 = ActorRef.fromBinary(bytes) - -If you are also using Protobuf then you can use the methods that work with Protobuf's Messages directly. - -.. code-block:: scala - - val actorRef1 = actorOf[MyActor] - - val protobufMessage = actorRef1.toProtocol - - val actorRef2 = ActorRef.fromProtocol(protobufMessage) - -Camel -====== - -Some methods of the se.scalablesolutions.akka.camel.Message class have been deprecated in 0.9. These are - -.. code-block:: scala - - package se.scalablesolutions.akka.camel - - case class Message(...) { - // ... - @deprecated def bodyAs[T](clazz: Class[T]): T - @deprecated def setBodyAs[T](clazz: Class[T]): Message - // ... - } - -They will be removed in 1.0. Instead use - -.. code-block:: scala - - package se.scalablesolutions.akka.camel - - case class Message(...) { - // ... - def bodyAs[T](implicit m: Manifest[T]): T = - def setBodyAs[T](implicit m: Manifest[T]): Message - // ... - } - -Usage example: -.. code-block:: scala - - val m = Message(1.4) - val b = m.bodyAs[String] - diff --git a/akka-docs/scala/migration-guide-0.9.x-0.10.x.rst b/akka-docs/scala/migration-guide-0.9.x-0.10.x.rst deleted file mode 100644 index 68ec0cb087..0000000000 --- a/akka-docs/scala/migration-guide-0.9.x-0.10.x.rst +++ /dev/null @@ -1,45 +0,0 @@ -Migration Guide from Akka 0.9.x to Akka 0.10.x -============================================== - -Module akka-camel ------------------ - -The following list summarizes the breaking changes since Akka 0.9.1. - -* CamelService moved from package se.scalablesolutions.akka.camel.service one level up to se.scalablesolutions.akka.camel. -* CamelService.newInstance removed. For starting and stopping a CamelService, applications should use -** CamelServiceManager.startCamelService and -** CamelServiceManager.stopCamelService. -* Existing def receive = produce method definitions from Producer implementations must be removed (resolves compile error: method receive needs override modifier). -* The Producer.async method and the related Sync trait have been removed. This is now fully covered by Camel's `asynchronous routing engine `_. -* @consume annotation can not placed any longer on actors (i.e. on type-level), only on typed actor methods. Consumer actors must mixin the Consumer trait. -* @consume annotation moved to package se.scalablesolutions.akka.camel. - -Logging -------- - -We've switched to Logback (SLF4J compatible) for the logging, if you're having trouble seeing your log output you'll need to make sure that there's a logback.xml available on the classpath or you'll need to specify the location of the logback.xml file via the system property, ex: -Dlogback.configurationFile=/path/to/logback.xml - -Configuration -------------- - -* The configuration is now JSON-style (see below). -* Now you can define the time-unit to be used throughout the config file: - -.. code-block:: ruby - - akka { - version = "0.10" - time-unit = "seconds" # default timeout time unit for all timeout properties throughout the config - - actor { - timeout = 5 # default timeout for future based invocations - throughput = 5 # default throughput for ExecutorBasedEventDrivenDispatcher - } - ... - } - -RemoteClient events -------------------- - -All events now has a reference to the RemoteClient instance instead of 'hostname' and 'port'. This is more flexible. Enables simpler reconnecting etc. diff --git a/akka-docs/scala/migration-guide-1.0.x-1.1.x.rst b/akka-docs/scala/migration-guide-1.0.x-1.1.x.rst deleted file mode 100644 index c32b2545ac..0000000000 --- a/akka-docs/scala/migration-guide-1.0.x-1.1.x.rst +++ /dev/null @@ -1,37 +0,0 @@ -Akka has now moved to Scala 2.9.x -^^^^^^^^^^^^^^^^^^^^ - -Akka HTTP -========= - -# akka.servlet.Initializer has been moved to ``akka-kernel`` to be able to have ``akka-http`` not depend on ``akka-remote``, if you don't want to use the class for kernel, just create your own version of ``akka.servlet.Initializer``, it's just a couple of lines of code and there is instructions here: `Akka Http Docs `_ -# akka.http.ListWriter has been removed in full, if you use it and want to keep using it, here's the code: `ListWriter `_ -# Jersey-server is now a "provided" dependency for ``akka-http``, so you'll need to add the dependency to your project, it's built against Jersey 1.3 - -Akka Actor -========== - -# is now dependency free, with the exception of the dependency on the ``scala-library.jar`` -# does not bundle any logging anymore, but you can subscribe to events within Akka by registering an event handler on akka.aevent.EventHandler or by specifying the ``FQN`` of an Actor in the akka.conf under akka.event-handlers; there is an ``akka-slf4j`` module which still provides the Logging trait and a default ``SLF4J`` logger adapter. -Don't forget to add a SLF4J backend though, we recommend: - -.. code-block:: scala - lazy val logback = "ch.qos.logback" % "logback-classic" % "0.9.28" - -# If you used HawtDispatcher and want to continue using it, you need to include akka-dispatcher-extras.jar from Akka Modules, in your akka.conf you need to specify: ``akka.dispatch.HawtDispatcherConfigurator`` instead of ``HawtDispatcher`` -# FSM: the onTransition method changed from Function1 to PartialFunction; there is an implicit conversion for the precise types in place, but it may be necessary to add an underscore if you are passing an eta-expansion (using a method as function value). - -Akka Typed Actor -================ - -All methods starting with 'get*' are deprecated and will be removed in post 1.1 release. - -Akka Remote -=========== - -# ``UnparsebleException`` has been renamed to ``CannotInstantiateRemoteExceptionDueToRemoteProtocolParsingErrorException(exception, classname, message)`` - -Akka Testkit -============ - -The TestKit moved into the akka-testkit subproject and correspondingly into the ``akka.testkit` package. diff --git a/akka-docs/scala/migration-guides.rst b/akka-docs/scala/migration-guides.rst deleted file mode 100644 index 361f8e3c7a..0000000000 --- a/akka-docs/scala/migration-guides.rst +++ /dev/null @@ -1,8 +0,0 @@ -Here are migration guides for the latest releases -================================================= - -* `Migrate 0.7.x -> 0.8.x `_ -* `Migrate 0.8.x -> 0.9.x `_ -* `Migrate 0.9.x -> 0.10.x `_ -* `Migrate 0.10.x -> 1.0.x `_ -* `Migrate 1.0.x -> 1.1.x `_ diff --git a/akka-docs/scala/testing.rst b/akka-docs/scala/testing.rst index 2860bd2cef..ecf86720db 100644 --- a/akka-docs/scala/testing.rst +++ b/akka-docs/scala/testing.rst @@ -2,6 +2,10 @@ Testing Actor Systems ##################### +.. toctree:: + + testkit-example + .. sidebar:: Contents .. contents:: :local: @@ -219,6 +223,25 @@ 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. +.. code-block:: scala + + class SomeSpec extends WordSpec with MustMatchers with TestKit { + "A Worker" must { + "send timely replies" in { + val worker = actorOf(...) + within (50 millis) { + worker ! "some work" + expectMsg("some result") + expectNoMsg + } + } + } + } + +Ray Roestenburg has written a great article on using the TestKit: +``_. +His full example is also available :ref:`here `. + CallingThreadDispatcher ======================= diff --git a/akka-docs/scala/testkit-example.rst b/akka-docs/scala/testkit-example.rst new file mode 100644 index 0000000000..f53543e474 --- /dev/null +++ b/akka-docs/scala/testkit-example.rst @@ -0,0 +1,145 @@ +.. _testkit-example: + +############### +TestKit Example +############### + +Ray Roestenburg's example code from `his blog `_. + +.. code-block:: scala + + package unit.akka + + import org.scalatest.matchers.ShouldMatchers + import org.scalatest.{WordSpec, BeforeAndAfterAll} + import akka.actor.Actor._ + import akka.util.duration._ + import akka.util.TestKit + import java.util.concurrent.TimeUnit + import akka.actor.{ActorRef, Actor} + import util.Random + + /** + * a Test to show some TestKit examples + */ + + class TestKitUsageSpec extends WordSpec with BeforeAndAfterAll with ShouldMatchers with TestKit { + val echoRef = actorOf(new EchoActor).start() + val forwardRef = actorOf(new ForwardingActor(testActor)).start() + val filterRef = actorOf(new FilteringActor(testActor)).start() + val randomHead = Random.nextInt(6) + val randomTail = Random.nextInt(10) + val headList = List().padTo(randomHead, "0") + val tailList = List().padTo(randomTail, "1") + val seqRef = actorOf(new SequencingActor(testActor, headList, tailList)).start() + + override protected def afterAll(): scala.Unit = { + stopTestActor + echoRef.stop() + forwardRef.stop() + filterRef.stop() + seqRef.stop() + } + + "An EchoActor" should { + "Respond with the same message it receives" in { + within(100 millis) { + echoRef ! "test" + expectMsg("test") + } + } + } + "A ForwardingActor" should { + "Forward a message it receives" in { + within(100 millis) { + forwardRef ! "test" + expectMsg("test") + } + } + } + "A FilteringActor" should { + "Filter all messages, except expected messagetypes it receives" in { + var messages = List[String]() + within(100 millis) { + filterRef ! "test" + expectMsg("test") + filterRef ! 1 + expectNoMsg + filterRef ! "some" + filterRef ! "more" + filterRef ! 1 + filterRef ! "text" + filterRef ! 1 + + receiveWhile(500 millis) { + case msg: String => messages = msg :: messages + } + } + messages.length should be(3) + messages.reverse should be(List("some", "more", "text")) + } + } + "A SequencingActor" should { + "receive an interesting message at some point " in { + within(100 millis) { + seqRef ! "something" + ignoreMsg { + case msg: String => msg != "something" + } + expectMsg("something") + ignoreMsg { + case msg: String => msg == "1" + } + expectNoMsg + } + } + } + } + + /** + * An Actor that echoes everything you send to it + */ + class EchoActor extends Actor { + def receive = { + case msg => { + self.reply(msg) + } + } + } + + /** + * An Actor that forwards every message to a next Actor + */ + class ForwardingActor(next: ActorRef) extends Actor { + def receive = { + case msg => { + next ! msg + } + } + } + + /** + * An Actor that only forwards certain messages to a next Actor + */ + class FilteringActor(next: ActorRef) extends Actor { + def receive = { + case msg: String => { + next ! msg + } + case _ => None + } + } + + /** + * An actor that sends a sequence of messages with a random head list, an interesting value and a random tail list + * The idea is that you would like to test that the interesting value is received and that you cant be bothered with the rest + */ + class SequencingActor(next: ActorRef, head: List[String], tail: List[String]) extends Actor { + def receive = { + case msg => { + head map (next ! _) + next ! msg + tail map (next ! _) + } + } + } From a60bbc5f598cba6c2de02c54d0a8d5674f1d14b3 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Sat, 23 Apr 2011 08:11:31 +0200 Subject: [PATCH 132/147] Applied patch from bruce.mitchener, with minor adjustment --- ...cutorBasedEventDrivenDispatcherActorsSpec.scala | 2 +- akka-actor/src/main/scala/akka/actor/Actor.scala | 4 ++-- .../src/main/scala/akka/actor/ActorRef.scala | 6 +++--- akka-actor/src/main/scala/akka/actor/FSM.scala | 2 +- .../src/main/scala/akka/actor/UntypedActor.scala | 4 ++-- .../ExecutorBasedEventDrivenDispatcher.scala | 2 +- .../src/main/scala/akka/dispatch/Future.scala | 4 ++-- .../main/scala/akka/util/ListenerManagement.scala | 2 +- akka-docs/general/migration-guide-1.0.x-1.1.x.rst | 2 +- akka-docs/intro/building-akka.rst | 6 +++--- akka-docs/pending/companies-using-akka.rst | 4 ++-- akka-docs/pending/dispatchers-java.rst | 2 +- akka-docs/pending/dispatchers-scala.rst | 2 +- akka-docs/pending/fault-tolerance-java.rst | 8 ++++---- akka-docs/pending/fault-tolerance-scala.rst | 8 ++++---- akka-docs/pending/futures-scala.rst | 6 +++--- akka-docs/pending/getting-started.rst | 2 +- akka-docs/pending/guice-integration.rst | 2 +- akka-docs/pending/http.rst | 4 ++-- akka-docs/pending/remote-actors-java.rst | 4 ++-- akka-docs/pending/remote-actors-scala.rst | 6 +++--- akka-docs/pending/security.rst | 2 +- akka-docs/pending/serialization-scala.rst | 14 +++++++------- akka-docs/pending/sponsors.rst | 2 +- akka-docs/pending/stm.rst | 2 +- akka-docs/pending/testkit.rst | 4 ++-- akka-docs/pending/transactors-java.rst | 2 +- akka-docs/pending/transactors-scala.rst | 2 +- akka-docs/pending/tutorial-chat-server-scala.rst | 2 +- akka-docs/pending/typed-actors-java.rst | 4 ++-- akka-docs/pending/typed-actors-scala.rst | 4 ++-- akka-docs/pending/untyped-actors-java.rst | 14 +++++++------- akka-docs/scala/actors.rst | 2 +- akka-docs/scala/fsm.rst | 2 +- akka-docs/scala/migration-guide-1.0.x-1.1.x.rst | 2 +- .../akka/remote/netty/NettyRemoteSupport.scala | 2 +- .../src/test/scala/ticket/Ticket434Spec.scala | 2 +- .../main/scala/akka/transactor/Transactor.scala | 2 +- .../actor/typed-actor/TypedActorRegistrySpec.scala | 10 +++++----- 39 files changed, 78 insertions(+), 78 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala index dfdaf9794d..a97238bf7e 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcherActorsSpec.scala @@ -8,7 +8,7 @@ import akka.actor.Actor import Actor._ /** - * Tests the behaviour of the executor based event driven dispatcher when multiple actors are being dispatched on it. + * Tests the behavior of the executor based event driven dispatcher when multiple actors are being dispatched on it. * * @author Jan Van Besien */ diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index ee3c48374f..14acfbb3e1 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -322,7 +322,7 @@ trait Actor { *

* For example fields like: *

-   * self.dispactcher = ...
+   * self.dispatcher = ...
    * self.trapExit = ...
    * self.faultHandler = ...
    * self.lifeCycle = ...
@@ -417,7 +417,7 @@ trait Actor {
   }
 
   /**
-   * Changes tha Actor's behavior to become the new 'Receive' (PartialFunction[Any, Unit]) handler.
+   * Changes the Actor's behavior to become the new 'Receive' (PartialFunction[Any, Unit]) handler.
    * Puts the behavior on top of the hotswap stack.
    * If "discardOld" is true, an unbecome will be issued prior to pushing the new behavior to the stack
    */
diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala
index 6fa44452e0..12e2b5949a 100644
--- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala
+++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala
@@ -279,7 +279,7 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal
    * Akka Java API. 

* Sends a one-way asynchronous message. E.g. fire-and-forget semantics. *

- * Allows you to pass along the sender of the messag. + * Allows you to pass along the sender of the message. *

*

    * actor.sendOneWay(message, context);
@@ -291,14 +291,14 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal
   /**
    * Akka Java API. 

* @see sendRequestReply(message: AnyRef, timeout: Long, sender: ActorRef) - * Uses the defualt timeout of the Actor (setTimeout()) and omits the sender reference + * Uses the default timeout of the Actor (setTimeout()) and omits the sender reference */ def sendRequestReply(message: AnyRef): AnyRef = sendRequestReply(message, timeout, null) /** * Akka Java API.

* @see sendRequestReply(message: AnyRef, timeout: Long, sender: ActorRef) - * Uses the defualt timeout of the Actor (setTimeout()) + * Uses the default timeout of the Actor (setTimeout()) */ def sendRequestReply(message: AnyRef, sender: ActorRef): AnyRef = sendRequestReply(message, timeout, sender) diff --git a/akka-actor/src/main/scala/akka/actor/FSM.scala b/akka-actor/src/main/scala/akka/actor/FSM.scala index 815ab1076c..f4fff52035 100644 --- a/akka-actor/src/main/scala/akka/actor/FSM.scala +++ b/akka-actor/src/main/scala/akka/actor/FSM.scala @@ -104,7 +104,7 @@ object FSM { * different concerns in different places; you may choose to use * when for describing the properties of a state, including of * course initiating transitions, but you can describe the transitions using - * onTransision to avoid having to duplicate that code among + * onTransition to avoid having to duplicate that code among * multiple paths which lead to a transition: * *

diff --git a/akka-actor/src/main/scala/akka/actor/UntypedActor.scala b/akka-actor/src/main/scala/akka/actor/UntypedActor.scala
index 77500d4059..41bf7ac048 100644
--- a/akka-actor/src/main/scala/akka/actor/UntypedActor.scala
+++ b/akka-actor/src/main/scala/akka/actor/UntypedActor.scala
@@ -24,7 +24,7 @@ import akka.japi.{Creator, Procedure}
  *
  *        } else if (msg.equals("UseSender") && getContext().getSender().isDefined()) {
  *          // Reply to original sender of message using the sender reference
- *          // also passing along my own refererence (the context)
+ *          // also passing along my own reference (the context)
  *          getContext().getSender().get().sendOneWay(msg, context);
  *
  *        } else if (msg.equals("UseSenderFuture") && getContext().getSenderFuture().isDefined()) {
@@ -36,7 +36,7 @@ import akka.japi.{Creator, Procedure}
  *          getContext().sendOneWay(msg)
  *
  *        } else if (msg.equals("ForwardMessage")) {
- *          // Retreive an actor from the ActorRegistry by ID and get an ActorRef back
+ *          // Retrieve an actor from the ActorRegistry by ID and get an ActorRef back
  *          ActorRef actorRef = Actor.registry.actorsFor("some-actor-id").head();
  *
  *        } else throw new IllegalArgumentException("Unknown message: " + message);
diff --git a/akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala b/akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala
index 261a4c8170..105028f693 100644
--- a/akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala
+++ b/akka-actor/src/main/scala/akka/dispatch/ExecutorBasedEventDrivenDispatcher.scala
@@ -63,7 +63,7 @@ import java.util.concurrent.{ TimeUnit, ExecutorService, RejectedExecutionExcept
  * @param throughput positive integer indicates the dispatcher will only process so much messages at a time from the
  *                   mailbox, without checking the mailboxes of other actors. Zero or negative means the dispatcher
  *                   always continues until the mailbox is empty.
- *                   Larger values (or zero or negative) increase througput, smaller values increase fairness
+ *                   Larger values (or zero or negative) increase throughput, smaller values increase fairness
  */
 class ExecutorBasedEventDrivenDispatcher(
   _name: String,
diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala
index c69ca82bad..430f7182e4 100644
--- a/akka-actor/src/main/scala/akka/dispatch/Future.scala
+++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala
@@ -357,12 +357,12 @@ sealed trait Future[+T] {
   /**
    * When this Future is completed, apply the provided function to the
    * Future. If the Future has already been completed, this will apply
-   * immediatly.
+   * immediately.
    */
   def onComplete(func: Future[T] => Unit): Future[T]
 
   /**
-   * When the future is compeleted with a valid result, apply the provided
+   * When the future is completed with a valid result, apply the provided
    * PartialFunction to the result.
    * 
    *   val result = future receive {
diff --git a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala
index efeb482377..ede46fc80a 100644
--- a/akka-actor/src/main/scala/akka/util/ListenerManagement.scala
+++ b/akka-actor/src/main/scala/akka/util/ListenerManagement.scala
@@ -45,7 +45,7 @@ trait ListenerManagement {
   def hasListeners: Boolean = !listeners.isEmpty
 
   /**
-   * Checks if a specfic listener is registered. ActorInitializationException leads to removal of listener if that
+   * Checks if a specific listener is registered. ActorInitializationException leads to removal of listener if that
    * one isShutdown.
    */
   def hasListener(listener: ActorRef): Boolean = listeners.contains(listener)
diff --git a/akka-docs/general/migration-guide-1.0.x-1.1.x.rst b/akka-docs/general/migration-guide-1.0.x-1.1.x.rst
index c473c44129..cd3bf5d9f2 100644
--- a/akka-docs/general/migration-guide-1.0.x-1.1.x.rst
+++ b/akka-docs/general/migration-guide-1.0.x-1.1.x.rst
@@ -15,7 +15,7 @@ Akka Actor
 ----------
 
 # is now dependency free, with the exception of the dependency on the ``scala-library.jar``
-# does not bundle any logging anymore, but you can subscribe to events within Akka by registering an event handler on akka.aevent.EventHandler or by specifying the ``FQN`` of an Actor in the akka.conf under akka.event-handlers; there is an ``akka-slf4j`` module which still provides the Logging trait and a default ``SLF4J`` logger adapter.
+# does not bundle any logging anymore, but you can subscribe to events within Akka by registering an event handler on akka.event.EventHandler or by specifying the ``FQN`` of an Actor in the akka.conf under akka.event-handlers; there is an ``akka-slf4j`` module which still provides the Logging trait and a default ``SLF4J`` logger adapter.
 Don't forget to add a SLF4J backend though, we recommend:
 
 .. code-block:: scala
diff --git a/akka-docs/intro/building-akka.rst b/akka-docs/intro/building-akka.rst
index 3d4f4ca1a0..2f2a745eeb 100644
--- a/akka-docs/intro/building-akka.rst
+++ b/akka-docs/intro/building-akka.rst
@@ -167,7 +167,7 @@ download) use the ``dist`` command::
 The distribution zip can be found in the dist directory and is called
 ``akka-modules-{version}.zip``.
 
-To run the mircokernel, unzip the zip file, change into the unzipped directory,
+To run the microkernel, unzip the zip file, change into the unzipped directory,
 set the ``AKKA_HOME`` environment variable, and run the main jar file. For
 example:
 
@@ -282,7 +282,7 @@ akka-camel
 ^^^^^^^^^^
 
 * Depends on akka-actor
-* camel-core-2.5.0.jar
+* camel-core-2.7.0.jar
 * commons-logging-api-1.1.jar
 * commons-management-1.0.jar
 
@@ -290,7 +290,7 @@ akka-camel-typed
 ^^^^^^^^^^^^^^^^
 
 * Depends on akka-typed-actor
-* camel-core-2.5.0.jar
+* camel-core-2.7.0.jar
 * commons-logging-api-1.1.jar
 * commons-management-1.0.jar
 
diff --git a/akka-docs/pending/companies-using-akka.rst b/akka-docs/pending/companies-using-akka.rst
index 3832adab1b..aae679be9d 100644
--- a/akka-docs/pending/companies-using-akka.rst
+++ b/akka-docs/pending/companies-using-akka.rst
@@ -41,7 +41,7 @@ SVT (Swedish Television)
 
 *Our system is highly asynchronous so the actor style of doing things is a perfect fit. I don’t know about how you feel about concurrency in a big system, but rolling your own abstractions is not a very easy thing to do. When using Akka you can almost forget about all that. Synchronizing between threads, locking and protecting access to state etc. Akka is not just about actors, but that’s one of the most pleasurable things to work with. It’s easy to add new ones and it’s easy to design with actors. You can fire up work actors tied to a specific dispatcher etc. I could make the list of benefits much longer, but I’m at work right now. I suggest you try it out and see how it fits your requirements.*
 
-*We saw a perfect businness reson for using Akka. It lets you concentrate on the business logic instead of the low level things. It’s easy to teach others and the business intent is clear just by reading the code. We didn’t chose Akka just for fun. It’s a business critical application that’s used in broadcasting. Even live broadcasting. We wouldn’t have been where we are today in such a short time without using Akka. We’re two developers that have done great things in such a short amount of time and part of this is due to Akka. As I said, it lets us focus on the business logic instead of low level things such as concurrency, locking, performence etc."*
+*We saw a perfect business reason for using Akka. It lets you concentrate on the business logic instead of the low level things. It’s easy to teach others and the business intent is clear just by reading the code. We didn’t chose Akka just for fun. It’s a business critical application that’s used in broadcasting. Even live broadcasting. We wouldn’t have been where we are today in such a short time without using Akka. We’re two developers that have done great things in such a short amount of time and part of this is due to Akka. As I said, it lets us focus on the business logic instead of low level things such as concurrency, locking, performance etc."*
 
 Tapad
 -----
@@ -107,7 +107,7 @@ LShift
 
 * *"Diffa is an open source data analysis tool that automatically establishes data differences between two or more real-time systems.*
 * Diffa will help you compare local or distributed systems for data consistency, without having to stop them running or implement manual cross-system comparisons. The interface provides you with simple visual summary of any consistency breaks and tools to investigate the issues.*
-* Diffa is the ideal tool to use to investigate where or when inconsistencies are occuring, or simply to provide confidence that your systems are running in perfect sync. It can be used operationally as an early warning system, in deployment for release verification, or in development with other enterprise diagnosis tools to help troubleshoot faults."*
+* Diffa is the ideal tool to use to investigate where or when inconsistencies are occurring, or simply to provide confidence that your systems are running in perfect sync. It can be used operationally as an early warning system, in deployment for release verification, or in development with other enterprise diagnosis tools to help troubleshoot faults."*
 
 ``_
 
diff --git a/akka-docs/pending/dispatchers-java.rst b/akka-docs/pending/dispatchers-java.rst
index 7889db30fc..b9d5ee9ee8 100644
--- a/akka-docs/pending/dispatchers-java.rst
+++ b/akka-docs/pending/dispatchers-java.rst
@@ -165,7 +165,7 @@ Creating a PriorityExecutorBasedEventDrivenDispatcher using PriorityGenerator:
       ref.setDispatcher(new PriorityExecutorBasedEventDrivenDispatcher("foo", gen)); 
 
           ref.start(); // Start the actor
-      ref.getDispatcher().suspend(ref); // Suspening the actor so it doesn't start to treat the messages before we have enqueued all of them :-)
+      ref.getDispatcher().suspend(ref); // Suspending the actor so it doesn't start to treat the messages before we have enqueued all of them :-)
           ref.sendOneWay("lowpriority");
           ref.sendOneWay("lowpriority");
           ref.sendOneWay("highpriority");
diff --git a/akka-docs/pending/dispatchers-scala.rst b/akka-docs/pending/dispatchers-scala.rst
index 35df55724f..62584835a4 100644
--- a/akka-docs/pending/dispatchers-scala.rst
+++ b/akka-docs/pending/dispatchers-scala.rst
@@ -153,7 +153,7 @@ Creating a PriorityExecutorBasedEventDrivenDispatcher using PriorityGenerator:
     a.dispatcher = new PriorityExecutorBasedEventDrivenDispatcher("foo", gen) 
     a.start // Start the Actor
 
-    a.dispatcher.suspend(a) // Suspening the actor so it doesn't start to treat the messages before we have enqueued all of them :-)
+    a.dispatcher.suspend(a) // Suspending the actor so it doesn't start to treat the messages before we have enqueued all of them :-)
 
      a ! 'lowpriority
      a ! 'lowpriority
diff --git a/akka-docs/pending/fault-tolerance-java.rst b/akka-docs/pending/fault-tolerance-java.rst
index 18cbb63e9e..96190c7b8e 100644
--- a/akka-docs/pending/fault-tolerance-java.rst
+++ b/akka-docs/pending/fault-tolerance-java.rst
@@ -125,7 +125,7 @@ The Actor’s supervision can be declaratively defined by creating a ‘Supervis
 
 Supervisors created like this are implicitly instantiated and started.
 
-To cofigure a handler function for when the actor underlying the supervisor recieves a MaximumNumberOfRestartsWithinTimeRangeReached message, you can specify
+To configure a handler function for when the actor underlying the supervisor receives a MaximumNumberOfRestartsWithinTimeRangeReached message, you can specify
  a Procedure2 when creating the SupervisorConfig. This handler will be called with the ActorRef of the supervisor and the
 MaximumNumberOfRestartsWithinTimeRangeReached message.
 
@@ -213,7 +213,7 @@ Here is an example:
           true)
        }));
 
-Programmatical linking and supervision of Untyped Actors
+Programmatic linking and supervision of Untyped Actors
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Untyped Actors can at runtime create, spawn, link and supervise other actors. Linking and unlinking is done using one of the 'link' and 'unlink' methods available in the 'ActorRef' (therefore prefixed with getContext() in these examples).
@@ -459,10 +459,10 @@ In the supervised TypedActor you can override the ‘preRestart’ and ‘postRe
     }
   }
 
-Programatical linking and supervision of TypedActors
+Programatic linking and supervision of TypedActors
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-TypedActors can be linked an unlinked just like UntypedActors:
+TypedActors can be linked and unlinked just like UntypedActors:
 
 .. code-block:: java
 
diff --git a/akka-docs/pending/fault-tolerance-scala.rst b/akka-docs/pending/fault-tolerance-scala.rst
index 5e02cf232a..6070f9e01e 100644
--- a/akka-docs/pending/fault-tolerance-scala.rst
+++ b/akka-docs/pending/fault-tolerance-scala.rst
@@ -121,7 +121,7 @@ The Actor's supervision can be declaratively defined by creating a "Supervisor'
 
 Supervisors created like this are implicitly instantiated and started.
 
-To cofigure a handler function for when the actor underlying the supervisor recieves a MaximumNumberOfRestartsWithinTimeRangeReached message, you can specify a function of type
+To configure a handler function for when the actor underlying the supervisor receives a MaximumNumberOfRestartsWithinTimeRangeReached message, you can specify a function of type
 (ActorRef, MaximumNumberOfRestartsWithinTimeRangeReached) => Unit when creating the SupervisorConfig. This handler will be called with the ActorRef of the supervisor and the
 MaximumNumberOfRestartsWithinTimeRangeReached message.
 
@@ -194,7 +194,7 @@ Here is an example:
         **true**)
       :: Nil))
 
-Programmatical linking and supervision of Actors
+Programmatic linking and supervision of Actors
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Actors can at runtime create, spawn, link and supervise other actors. Linking and unlinking is done using one of the 'link' and 'unlink' methods available in the 'ActorRef' (therefore prefixed with 'self' in these examples).
@@ -411,10 +411,10 @@ Then you can retrieve the Typed Actor as follows:
 Restart callbacks
 ^^^^^^^^^^^^^^^^^
 
-Programatical linking and supervision of TypedActors
+Programatic linking and supervision of TypedActors
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-TypedActors can be linked an unlinked just like actors - in fact the linking is done on the underlying actor:
+TypedActors can be linked and unlinked just like actors - in fact the linking is done on the underlying actor:
 
 .. code-block:: scala
 
diff --git a/akka-docs/pending/futures-scala.rst b/akka-docs/pending/futures-scala.rst
index 3426ce0ff6..f03990a1cf 100644
--- a/akka-docs/pending/futures-scala.rst
+++ b/akka-docs/pending/futures-scala.rst
@@ -11,7 +11,7 @@ Use with Actors
 
 There are generally two ways of getting a reply from an ``Actor``: the first is by a sent message (``actor ! msg``), which only works if the original sender was an ``Actor``) and the second is through a ``Future``.
 
-Using an ``Actor``\'s ``!!!`` method to send a message will return a Future. To wait for and retreive the actual result the simplest method is:
+Using an ``Actor``\'s ``!!!`` method to send a message will return a Future. To wait for and retrieve the actual result the simplest method is:
 
 .. code-block:: scala
 
@@ -97,7 +97,7 @@ If we do the opposite:
 
 Our little string has been processed long before our 1 second sleep has finished. Because of this, the dispatcher has moved onto other messages that need processing and can no longer calculate the length of the string for us, instead it gets calculated in the current thread just as if we weren't using a Future.
 
-Normally this works quite well for us as it means there is very little overhead to running a quick Function. If there is a possiblity of the Function taking a non-trivial amount of time to process it might be better to have this done concurrently, and for that we use 'flatMap':
+Normally this works quite well for us as it means there is very little overhead to running a quick Function. If there is a possibility of the Function taking a non-trivial amount of time to process it might be better to have this done concurrently, and for that we use 'flatMap':
 
 .. code-block:: scala
 
@@ -150,7 +150,7 @@ The example for comprehension above is an example of composing Futures. A common
 
 Here we have 2 actors processing a single message each. In the for comprehension we need to add the expected types in order to work with the results. Once the 2 results are available, they are being added together and sent to a third actor, which replies with a String, which we assign to 'result'.
 
-This is fine when dealing with a known amount of Actors, but can grow unwieldly if we have more then a handful. The 'sequence' and 'traverse' helper methods can make it easier to handle more complex use cases. Both of these methods are ways of turning a Traversable[Future[A]] into a Future[Traversable[A]]. For example:
+This is fine when dealing with a known amount of Actors, but can grow unwieldy if we have more then a handful. The 'sequence' and 'traverse' helper methods can make it easier to handle more complex use cases. Both of these methods are ways of turning a Traversable[Future[A]] into a Future[Traversable[A]]. For example:
 
 .. code-block:: scala
 
diff --git a/akka-docs/pending/getting-started.rst b/akka-docs/pending/getting-started.rst
index 8f86f5cfca..d76db9b299 100644
--- a/akka-docs/pending/getting-started.rst
+++ b/akka-docs/pending/getting-started.rst
@@ -18,7 +18,7 @@ Download the release you need (Akka core or Akka Modules) from ``_ annotations (such as ‘@Inject’ etc.).
+All Typed Actors support dependency injection using `Guice `_ annotations (such as ‘@Inject’ etc.).
 The ‘TypedActorManager’ class understands Guice and will do the wiring for you.
 
 External Guice modules
diff --git a/akka-docs/pending/http.rst b/akka-docs/pending/http.rst
index 739f443c1d..a4c7842233 100644
--- a/akka-docs/pending/http.rst
+++ b/akka-docs/pending/http.rst
@@ -179,7 +179,7 @@ Endpoints are actors that handle request messages. Minimally there must be an in
 Preparations
 ^^^^^^^^^^^^
 
-In order to use Mist you have to register the MistServlet in *web.xml* or do the analogous for the embedded server if running in Akka Micrkernel:
+In order to use Mist you have to register the MistServlet in *web.xml* or do the analogous for the embedded server if running in Akka Microkernel:
 
 .. code-block:: xml
 
@@ -419,7 +419,7 @@ As noted above, hook functions are non-exclusive. This means multiple actors can
     def receive = handleHttpRequest
 
     //
-    // this guy completes requests after other actions have occured
+    // this guy completes requests after other actions have occurred
     //
     lazy val complete = actorOf[ActionCompleteActor].start()
   }
diff --git a/akka-docs/pending/remote-actors-java.rst b/akka-docs/pending/remote-actors-java.rst
index 0e654fc698..47f27d6cef 100644
--- a/akka-docs/pending/remote-actors-java.rst
+++ b/akka-docs/pending/remote-actors-java.rst
@@ -15,7 +15,7 @@ Managing the Remote Service
 Starting remote service in user code as a library
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Here is how to start up the server and specify the hostname and port programatically:
+Here is how to start up the server and specify the hostname and port programmatically:
 
 .. code-block:: java
 
@@ -314,7 +314,7 @@ The API for server managed remote actors is really simple. 2 methods only:
 
 Actors created like this are automatically started.
 
-You can also register an actor by its UUD rather than ID or handle. This is done by prefixing the handle with the "uuid:" protocol.
+You can also register an actor by its UUID rather than ID or handle. This is done by prefixing the handle with the "uuid:" protocol.
 
 .. code-block:: scala
 
diff --git a/akka-docs/pending/remote-actors-scala.rst b/akka-docs/pending/remote-actors-scala.rst
index fef71e69e5..9389a5d284 100644
--- a/akka-docs/pending/remote-actors-scala.rst
+++ b/akka-docs/pending/remote-actors-scala.rst
@@ -15,7 +15,7 @@ Starting up the remote service
 Starting remote service in user code as a library
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Here is how to start up the RemoteNode and specify the hostname and port programatically:
+Here is how to start up the RemoteNode and specify the hostname and port programmatically:
 
 .. code-block:: scala
 
@@ -318,7 +318,7 @@ The API for server managed remote actors is really simple. 2 methods only:
 
 Actors created like this are automatically started.
 
-You can also register an actor by its UUD rather than ID or handle. This is done by prefixing the handle with the "uuid:" protocol.
+You can also register an actor by its UUID rather than ID or handle. This is done by prefixing the handle with the "uuid:" protocol.
 
 .. code-block:: scala
 
@@ -645,7 +645,7 @@ So a simple listener actor can look like this:
       case RemoteServerClientConnected(server, clientAddress)    => ... // act upon client connection
       case RemoteServerClientDisconnected(server, clientAddress) => ... // act upon client disconnection
       case RemoteServerClientClosed(server, clientAddress)       => ... // act upon client connection close
-      case RemoteServerWriteFailed(request, casue, server, clientAddress) => ... // act upon server write failure
+      case RemoteServerWriteFailed(request, cause, server, clientAddress) => ... // act upon server write failure
     }
   }).start()
 
diff --git a/akka-docs/pending/security.rst b/akka-docs/pending/security.rst
index 3600c21285..cae23fbdd5 100644
--- a/akka-docs/pending/security.rst
+++ b/akka-docs/pending/security.rst
@@ -89,7 +89,7 @@ How does it work (at least for REST actors)?
 # The browser will send the *service ticket* to the web application encoded in the header value of the *Authorization*header
 # The web application must validate the ticket based on a shared secret between the web application and the kerberos server. As a result the web application will know the name of the user
 
-To activate the kerberos/SPNEGO authentication for your REST actor you need to enable the kerberos/SPNGEOauthentication actor in the akka.conf like this:
+To activate the kerberos/SPNEGO authentication for your REST actor you need to enable the kerberos/SPNEGOauthentication actor in the akka.conf like this:
 
 .. code-block:: ruby
 
diff --git a/akka-docs/pending/serialization-scala.rst b/akka-docs/pending/serialization-scala.rst
index 93b738a176..a0b0e312e6 100644
--- a/akka-docs/pending/serialization-scala.rst
+++ b/akka-docs/pending/serialization-scala.rst
@@ -321,7 +321,7 @@ Each serialization interface/trait in
 Note however that if you are using one of the Serializable interfaces then you don’t have to do anything else in regard to sending remote messages.
 
 The ones currently supported are (besides the default which is regular Java serialization):
-* ScalaJON (Scala only)
+* ScalaJSON (Scala only)
 * JavaJSON (Java but some Scala structures)
 * SBinary (Scala only)
 * Protobuf (Scala and Java)
@@ -439,7 +439,7 @@ Here are the steps that you need to follow:
 Serializer API using reflection
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-You can also use the Serializer abstraction to serialize using reflection based serialization API of sjson. But we recommend using the type class based one, because reflection based serialization has limitations due to type erasure. Here's an example of reflection based serialization:
+You can also use the Serializer abstraction to serialize using the reflection based serialization API of sjson. But we recommend using the type class based one, because reflection based serialization has limitations due to type erasure. Here's an example of reflection based serialization:
 
 .. code-block:: scala
 
@@ -672,7 +672,7 @@ Consider the following Scala class:
   }
 
 Because of erasure, you need to add the type hint declaratively through the annotation @JSONTypeHint that
-SJSON will pick up during serialization. No we can say:
+SJSON will pick up during serialization. Now we can say:
 
 .. code-block:: scala
 
@@ -683,7 +683,7 @@ SJSON will pick up during serialization. No we can say:
     c should equal(serializer.in[Contact](co))
   }
 
-With optional generic data members, we need to provide the hint to SJSON through another annotation@OptionTypeHint.
+With optional generic data members, we need to provide the hint to SJSON through another annotation @OptionTypeHint.
 
 .. code-block:: scala
 
@@ -714,7 +714,7 @@ Serialization works ok with optional members annotated as above.
     }
   }
 
-You can also specify a custom ClassLoader while using SJSON serializer:
+You can also specify a custom ClassLoader while using the SJSON serializer:
 
 .. code-block:: scala
 
@@ -915,7 +915,7 @@ For your POJOs to be able to serialize themselves you have to extend the JavaJSO
   SerializerFactory factory = new SerializerFactory();
   MyMessage messageCopy = factory.getJavaJSON().in(json);
 
-Use the akka.serialization.SerializerFactory.getJavaJSON to do generic JSONserialization, e.g. serialize object that does not extend JavaJSON using the JSON serializer.
+Use the akka.serialization.SerializerFactory.getJavaJSON to do generic JSON serialization, e.g. serialize object that does not extend JavaJSON using the JSON serializer.
 
 .. code-block:: java
 
@@ -948,7 +948,7 @@ If you need to serialize your own user-defined objects then you have to do three
 ## toBytes: Array[Byte]
 # Create an implicit sbinary.Format[T] object for your class. Which means that you have to define its two methods:
 ## reads(in: Input): T; in which you read in all the fields in your object, using read[FieldType](in)and recreate it.
-## writes(out: Output, value: T): Unit; in which you write out all the fields in your object, usingwrite[FieldType](out, value.field).
+## writes(out: Output, value: T): Unit; in which you write out all the fields in your object, using write[FieldType](out, value.field).
 
 Here is an example:
 ``_
diff --git a/akka-docs/pending/sponsors.rst b/akka-docs/pending/sponsors.rst
index 65faac6794..88d35f1f0a 100644
--- a/akka-docs/pending/sponsors.rst
+++ b/akka-docs/pending/sponsors.rst
@@ -4,7 +4,7 @@
 Scalable Solutions
 ==================
 
-Scalable Solutions AB is commercial entity behind Akka, providing support, consulting and training around Akka.
+Scalable Solutions AB is the commercial entity behind Akka, providing support, consulting and training around Akka.
 ``_
 
 YourKit
diff --git a/akka-docs/pending/stm.rst b/akka-docs/pending/stm.rst
index c84ca4e6bb..8666425289 100644
--- a/akka-docs/pending/stm.rst
+++ b/akka-docs/pending/stm.rst
@@ -7,7 +7,7 @@ The Akka Software Transactional Memory implementation
 
 Read consistency is that all value
 
-**Read concistency and MVCC**
+**Read consistency and MVCC**
 *****************************
 
 A lot of STM (like the Clojure STM) implementations are Multi Version Concurrency Control Based (MVCC) based (TL2 of david dice could be seen as MVCC).
diff --git a/akka-docs/pending/testkit.rst b/akka-docs/pending/testkit.rst
index d2d177948f..65aeac00b6 100644
--- a/akka-docs/pending/testkit.rst
+++ b/akka-docs/pending/testkit.rst
@@ -8,7 +8,7 @@ Overview
 
 Testing actors comprises several aspects, which can have different weight according to the concrete project at hand:
 * If you have a collection of actors which performs a certain function, you may want to apply defined stimuli and observe the delivery of the desired result messages to a test actor; in this case the ***TestKit*** trait will likely interest you.
-* If you encounter undesired behaviour (exceptions, dead-locks) and want to nail down the cause, it might help to run the actors in question using the ***CallingThreadDispatcher***; this dispatcher is strictly less powerful than the general purpose ones, but its deterministic behaviour and complete message stack can help debugging, unless your setup depends on concurrent execution for correctness.
+* If you encounter undesired behavior (exceptions, dead-locks) and want to nail down the cause, it might help to run the actors in question using the ***CallingThreadDispatcher***; this dispatcher is strictly less powerful than the general purpose ones, but its deterministic behavior and complete message stack can help debugging, unless your setup depends on concurrent execution for correctness.
 * For real unit tests of one actor body at a time, there soon will be a special ***TestActorRef*** which allows access to the innards and enables running without a dispatcher.
 
 TestKit
@@ -42,7 +42,7 @@ CallingThreadDispatcher
 
 This special purpose dispatcher was conceived to enable collection of the full stack trace accumulated during processing of a complete message chain. The idea is to run invocations always on the calling thread, except when the target actor is already running on the current thread; in that case it is necessary to queue the invocation and run it after the current invocation on that actor has finished processing. This design implies that any invocation which blocks waiting on some future action to be done by the current thread will dead-lock. Hence, the CallingThreadDispatcher offers strictly more possibilities to dead-lock than a standard dispatcher.
 
-One nice property is that this feature can help verify that your design is dead-lock free: if you run only on this dispatcher and utilitze only one thread, then a successful run implies that for the given set of inputs there cannot be a dead-lock. (This is unfortunately not a hard guarantee, as long as your actor behavior depends on the dispatcher used, e.g. you could sabotage it by explicitly dead-locking only if self.dispatcher != CallingThreadDispatcher.)
+One nice property is that this feature can help verify that your design is dead-lock free: if you run only on this dispatcher and utilize only one thread, then a successful run implies that for the given set of inputs there cannot be a dead-lock. (This is unfortunately not a hard guarantee, as long as your actor behavior depends on the dispatcher used, e.g. you could sabotage it by explicitly dead-locking only if self.dispatcher != CallingThreadDispatcher.)
 
 TestActorRef (coming soon ...)
 ------------------------------
diff --git a/akka-docs/pending/transactors-java.rst b/akka-docs/pending/transactors-java.rst
index 6547063703..9cc4d522f4 100644
--- a/akka-docs/pending/transactors-java.rst
+++ b/akka-docs/pending/transactors-java.rst
@@ -190,7 +190,7 @@ Example of coordinating an increment, similar to the explicitly coordinated exam
       }
   }
 
-To exeucte directly before or after the coordinated transaction, override the ``before`` and ``after`` methods. These methods also expect partial functions like the receive method. They do not execute within the transaction.
+To execute directly before or after the coordinated transaction, override the ``before`` and ``after`` methods. These methods also expect partial functions like the receive method. They do not execute within the transaction.
 
 To completely bypass coordinated transactions override the ``normally`` method. Any message matched by ``normally`` will not be matched by the other methods, and will not be involved in coordinated transactions. In this method you can implement normal actor behavior, or use the normal STM atomic for local transactions.
 
diff --git a/akka-docs/pending/transactors-scala.rst b/akka-docs/pending/transactors-scala.rst
index 454fffb6a6..6ee4126f0a 100644
--- a/akka-docs/pending/transactors-scala.rst
+++ b/akka-docs/pending/transactors-scala.rst
@@ -172,7 +172,7 @@ Using ``sendTo`` to coordinate transactions but pass-on a different message than
     case SomeMessage => sendTo(actor1 -> Message1, actor2 -> Message2)
   }
 
-To exeucte directly before or after the coordinated transaction, override the ``before`` and ``after`` methods. These methods also expect partial functions like the receive method. They do not execute within the transaction.
+To execute directly before or after the coordinated transaction, override the ``before`` and ``after`` methods. These methods also expect partial functions like the receive method. They do not execute within the transaction.
 
 To completely bypass coordinated transactions override the ``normally`` method. Any message matched by ``normally`` will not be matched by the other methods, and will not be involved in coordinated transactions. In this method you can implement normal actor behavior, or use the normal STM atomic for local transactions.
 
diff --git a/akka-docs/pending/tutorial-chat-server-scala.rst b/akka-docs/pending/tutorial-chat-server-scala.rst
index 9d35abddd9..830bf75c22 100644
--- a/akka-docs/pending/tutorial-chat-server-scala.rst
+++ b/akka-docs/pending/tutorial-chat-server-scala.rst
@@ -358,7 +358,7 @@ It responds to two different messages; 'ChatMessage' and 'GetChatLog'. The 'Chat
 
 The 'GetChatLog' message handler retrieves all the messages in the chat log storage inside an atomic block, iterates over them using the 'map' combinator transforming them from 'Array[Byte] to 'String'. Then it invokes the 'reply(message)' function that will send the chat log to the original sender; the 'ChatClient'.
 
-You might rememeber that the 'ChatServer' was supervising the 'ChatStorage' actor. When we discussed that we showed you the supervising Actor's view. Now is the time for the supervised Actor's side of things. First, a supervised Actor need to define a life-cycle in which it declares if it should be seen as a:
+You might remember that the 'ChatServer' was supervising the 'ChatStorage' actor. When we discussed that we showed you the supervising Actor's view. Now is the time for the supervised Actor's side of things. First, a supervised Actor need to define a life-cycle in which it declares if it should be seen as a:
 
 * 'Permanent': which means that the actor will always be restarted.
 * 'Temporary': which means that the actor will not be restarted, but it will be shut down through the regular shutdown process so the 'postStop' callback function will called.
diff --git a/akka-docs/pending/typed-actors-java.rst b/akka-docs/pending/typed-actors-java.rst
index 2322698ed1..0f6c9563b5 100644
--- a/akka-docs/pending/typed-actors-java.rst
+++ b/akka-docs/pending/typed-actors-java.rst
@@ -100,7 +100,7 @@ Methods that return void are turned into ‘fire-and-forget’ semantics by asyn
 Request-reply message send
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Methods that return something (e.g. non-void methods) are turned into ‘send-and-recieve-eventually’ semantics by asynchronously firing off the message and wait on the reply using a Future.
+Methods that return something (e.g. non-void methods) are turned into ‘send-and-receive-eventually’ semantics by asynchronously firing off the message and wait on the reply using a Future.
 
 .. code-block:: java
 
@@ -118,7 +118,7 @@ The same holds for the 'request-reply-with-future' described below.
 Request-reply-with-future message send
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Methods that return a 'akka.dispatch.Future' are turned into ‘send-and-recieve-with-future’ semantics by asynchronously firing off the message and returns immediately with a Future. You need to use the 'future(...)' method in the TypedActor base class to resolve the Future that the client code is waiting on.
+Methods that return a 'akka.dispatch.Future' are turned into ‘send-and-receive-with-future’ semantics by asynchronously firing off the message and returns immediately with a Future. You need to use the 'future(...)' method in the TypedActor base class to resolve the Future that the client code is waiting on.
 
 Here is an example:
 
diff --git a/akka-docs/pending/typed-actors-scala.rst b/akka-docs/pending/typed-actors-scala.rst
index 3d03cc93b1..e9aa061672 100644
--- a/akka-docs/pending/typed-actors-scala.rst
+++ b/akka-docs/pending/typed-actors-scala.rst
@@ -95,7 +95,7 @@ Methods that return void are turned into ‘fire-and-forget’ semantics by asyn
 Request-reply message send
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Methods that return something (e.g. non-void methods) are turned into ‘send-and-recieve-eventually’ semantics by asynchronously firing off the message and wait on the reply using a Future.
+Methods that return something (e.g. non-void methods) are turned into ‘send-and-receive-eventually’ semantics by asynchronously firing off the message and wait on the reply using a Future.
 
 .. code-block:: scala
 
@@ -111,7 +111,7 @@ Generally it is preferred to use fire-forget messages as much as possible since
 Request-reply-with-future message send
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Methods that return a 'akka.dispatch.Future' are turned into ‘send-and-recieve-with-future’ semantics by asynchronously firing off the message and returns immediately with a Future. You need to use the 'future(...)' method in the TypedActor base class to resolve the Future that the client code is waiting on.
+Methods that return a 'akka.dispatch.Future' are turned into ‘send-and-receive-with-future’ semantics by asynchronously firing off the message and returns immediately with a Future. You need to use the 'future(...)' method in the TypedActor base class to resolve the Future that the client code is waiting on.
 
 Here is an example:
 
diff --git a/akka-docs/pending/untyped-actors-java.rst b/akka-docs/pending/untyped-actors-java.rst
index 760b5fd324..35e97011af 100644
--- a/akka-docs/pending/untyped-actors-java.rst
+++ b/akka-docs/pending/untyped-actors-java.rst
@@ -51,7 +51,7 @@ You can also create & start the actor in one statement:
 
   ActorRef myActor = actorOf(SampleUntypedActor.class).start();
 
-The call to 'actorOf' returns an instance of 'ActorRef'. This is a handle to the 'UntypedActor' instance which you can use to interact with the Actor, like send messages to it etc. more on this shortly. The 'ActorRef' is immutble and has a one to one relationship with the Actor it represents. The 'ActorRef' is also serializable and network-aware. This means that you can serialize it, send it over the wire and use it on a remote host and it will still be representing the same Actor on the original node, across the network.
+The call to 'actorOf' returns an instance of 'ActorRef'. This is a handle to the 'UntypedActor' instance which you can use to interact with the Actor, like send messages to it etc. more on this shortly. The 'ActorRef' is immutable and has a one to one relationship with the Actor it represents. The 'ActorRef' is also serializable and network-aware. This means that you can serialize it, send it over the wire and use it on a remote host and it will still be representing the same Actor on the original node, across the network.
 
 Creating Actors with non-default constructor
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -103,7 +103,7 @@ Messages are sent to an Actor through one of the 'send' methods.
 * 'sendRequestReply' means “send-and-reply-eventually”, e.g. send a message asynchronously and wait for a reply through a Future. Here you can specify a timeout. Using timeouts is very important. If no timeout is specified then the actor’s default timeout (set by the 'getContext().setTimeout(..)' method in the 'ActorRef') is used. This method throws an 'ActorTimeoutException' if the call timed out.
 * 'sendRequestReplyFuture' sends a message asynchronously and returns a 'Future'.
 
-In all these methods you have the option of passing along your 'ActorRef' context variable. Make it a practive of doing so because it will allow the receiver actors to be able to respond to your message, since the sender reference is sent along with the message.
+In all these methods you have the option of passing along your 'ActorRef' context variable. Make it a practice of doing so because it will allow the receiver actors to be able to respond to your message, since the sender reference is sent along with the message.
 
 Fire-forget
 ^^^^^^^^^^^
@@ -138,7 +138,7 @@ Here are some examples:
 
 .. code-block:: java
 
-  UnypedActorRef actorRef = ...
+  UntypedActorRef actorRef = ...
 
   try {
     Object result = actorRef.sendRequestReply("Hello", getContext(), 1000);
@@ -256,7 +256,7 @@ The 'replyUnsafe' method throws an 'IllegalStateException' if unable to determin
 Reply using the sender reference
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-If the sender reference (the sender's 'ActorRef') is passed into one ofe the 'send*' methods it will be implicitly passed along together with the message and will be available in the 'Option getSender()' method on the 'ActorRef. This means that you can use this field to send a message back to the sender.
+If the sender reference (the sender's 'ActorRef') is passed into one of the 'send*' methods it will be implicitly passed along together with the message and will be available in the 'Option getSender()' method on the 'ActorRef. This means that you can use this field to send a message back to the sender.
 
 On this 'Option' you can invoke 'boolean isDefined()' or 'boolean isEmpty()' to check if the sender is available or not, and if it is call 'get()' to get the reference. It's important to know that 'getSender().get()' will throw an exception if there is no sender in scope. The same pattern holds for using the 'getSenderFuture()' in the section below.
 
@@ -267,7 +267,7 @@ On this 'Option' you can invoke 'boolean isDefined()' or 'boolean isEmpty()' to
       String msg = (String)message;
       if (msg.equals("Hello")) {
         // Reply to original sender of message using the sender reference
-        // also passing along my own refererence (the context)
+        // also passing along my own reference (the context)
         if (getContext().getSender().isDefined)
           getContext().getSender().get().sendOneWay(msg + " from " + getContext().getUuid(), getContext());
       }
@@ -279,7 +279,7 @@ Reply using the sender future
 
 If a message was sent with the 'sendRequestReply' or 'sendRequestReplyFuture' methods, which both implements request-reply semantics using Future's, then you either have the option of replying using the 'reply' method as above. This method will then resolve the Future. But you can also get a reference to the Future directly and resolve it yourself or if you would like to store it away to resolve it later, or pass it on to some other Actor to resolve it.
 
-The reference to the Future resides in the 'ActorRef' instance and can be retreived using 'Option getSenderFuture()'.
+The reference to the Future resides in the 'ActorRef' instance and can be retrieved using 'Option getSenderFuture()'.
 
 CompletableFuture is a future with methods for 'completing the future:
 * completeWithResult(..)
@@ -337,7 +337,7 @@ Actors are started by invoking the ‘start’ method.
   ActorRef actor = actorOf(SampleUntypedActor.class);
   myActor.start();
 
-You can create and start the Actor in a oneliner like this:
+You can create and start the Actor in a one liner like this:
 
 .. code-block:: java
 
diff --git a/akka-docs/scala/actors.rst b/akka-docs/scala/actors.rst
index 70f9e0cfcc..7c9990cd3f 100644
--- a/akka-docs/scala/actors.rst
+++ b/akka-docs/scala/actors.rst
@@ -372,7 +372,7 @@ Actors are started by invoking the ``start`` method.
   val actor = actorOf[MyActor]
   actor.start()
 
-You can create and start the ``Actor`` in a oneliner like this:
+You can create and start the ``Actor`` in a one liner like this:
 
 .. code-block:: scala
 
diff --git a/akka-docs/scala/fsm.rst b/akka-docs/scala/fsm.rst
index a23fd9edf4..c1736bf444 100644
--- a/akka-docs/scala/fsm.rst
+++ b/akka-docs/scala/fsm.rst
@@ -42,7 +42,7 @@ B and C.
   case object B extends ExampleState
   case object C extends ExampleState
 
-Now lets create an object representing the FSM and defining the behaviour.
+Now lets create an object representing the FSM and defining the behavior.
 
 .. code-block:: scala
 
diff --git a/akka-docs/scala/migration-guide-1.0.x-1.1.x.rst b/akka-docs/scala/migration-guide-1.0.x-1.1.x.rst
index c32b2545ac..07014e3f81 100644
--- a/akka-docs/scala/migration-guide-1.0.x-1.1.x.rst
+++ b/akka-docs/scala/migration-guide-1.0.x-1.1.x.rst
@@ -12,7 +12,7 @@ Akka Actor
 ==========
 
 # is now dependency free, with the exception of the dependency on the ``scala-library.jar``
-# does not bundle any logging anymore, but you can subscribe to events within Akka by registering an event handler on akka.aevent.EventHandler or by specifying the ``FQN`` of an Actor in the akka.conf under akka.event-handlers; there is an ``akka-slf4j`` module which still provides the Logging trait and a default ``SLF4J`` logger adapter.
+# does not bundle any logging anymore, but you can subscribe to events within Akka by registering an event handler on akka.event.EventHandler or by specifying the ``FQN`` of an Actor in the akka.conf under akka.event-handlers; there is an ``akka-slf4j`` module which still provides the Logging trait and a default ``SLF4J`` logger adapter.
 Don't forget to add a SLF4J backend though, we recommend:
 
 .. code-block:: scala
diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala
index 8781c72ecd..3be65cdea3 100644
--- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala
+++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala
@@ -155,7 +155,7 @@ trait NettyRemoteClientModule extends RemoteClientModule { self: ListenerManagem
 
 /**
  * This is the abstract baseclass for netty remote clients, currently there's only an
- * ActiveRemoteClient, but otehrs could be feasible, like a PassiveRemoteClient that
+ * ActiveRemoteClient, but others could be feasible, like a PassiveRemoteClient that
  * reuses an already established connection.
  */
 abstract class RemoteClient private[akka] (
diff --git a/akka-remote/src/test/scala/ticket/Ticket434Spec.scala b/akka-remote/src/test/scala/ticket/Ticket434Spec.scala
index 971f200340..55cc4a2659 100644
--- a/akka-remote/src/test/scala/ticket/Ticket434Spec.scala
+++ b/akka-remote/src/test/scala/ticket/Ticket434Spec.scala
@@ -25,7 +25,7 @@ class Ticket434Spec extends AkkaRemoteTest {
       latch.await(1, unit) must be (true)
     }
 
-    "should be possible to set the acor id and uuuid" in {
+    "should be possible to set the actor id and uuid" in {
       val uuid = newUuid
       val actorInfo = ActorInfoProtocol.newBuilder
         .setUuid(UuidProtocol.newBuilder.setHigh(uuid.getTime).setLow(uuid.getClockSeqAndNode).build)
diff --git a/akka-stm/src/main/scala/akka/transactor/Transactor.scala b/akka-stm/src/main/scala/akka/transactor/Transactor.scala
index 8469aa53dd..57130dec07 100644
--- a/akka-stm/src/main/scala/akka/transactor/Transactor.scala
+++ b/akka-stm/src/main/scala/akka/transactor/Transactor.scala
@@ -82,7 +82,7 @@ case class SendTo(actor: ActorRef, message: Option[Any] = None)
  * }}}
  * 
* - * To exeucte directly before or after the coordinated transaction, override + * To execute directly before or after the coordinated transaction, override * the `before` and `after` methods. These methods also expect partial functions * like the receive method. They do not execute within the transaction. * diff --git a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorRegistrySpec.scala b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorRegistrySpec.scala index a83dccc18d..fd99974775 100644 --- a/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorRegistrySpec.scala +++ b/akka-typed-actor/src/test/scala/actor/typed-actor/TypedActorRegistrySpec.scala @@ -16,7 +16,7 @@ class TypedActorRegistrySpec extends WordSpec with MustMatchers { "Typed Actor" should { - "be able to be retreived from the registry by class" in { + "be able to be retrieved from the registry by class" in { Actor.registry.shutdownAll() val my = TypedActor.newInstance[My](classOf[My], classOf[MyImpl], 3000) val actors = Actor.registry.typedActorsFor(classOf[My]) @@ -24,7 +24,7 @@ class TypedActorRegistrySpec extends WordSpec with MustMatchers { Actor.registry.shutdownAll() } - "be able to be retreived from the registry by manifest" in { + "be able to be retrieved from the registry by manifest" in { Actor.registry.shutdownAll() val my = TypedActor.newInstance[My](classOf[My], classOf[MyImpl], 3000) val option = Actor.registry.typedActorFor[My] @@ -33,7 +33,7 @@ class TypedActorRegistrySpec extends WordSpec with MustMatchers { Actor.registry.shutdownAll() } - "be able to be retreived from the registry by class two times" in { + "be able to be retrieved from the registry by class two times" in { Actor.registry.shutdownAll() val my = TypedActor.newInstance[My](classOf[My], classOf[MyImpl], 3000) val actors1 = Actor.registry.typedActorsFor(classOf[My]) @@ -43,7 +43,7 @@ class TypedActorRegistrySpec extends WordSpec with MustMatchers { Actor.registry.shutdownAll() } - "be able to be retreived from the registry by manifest two times" in { + "be able to be retrieved from the registry by manifest two times" in { Actor.registry.shutdownAll() val my = TypedActor.newInstance[My](classOf[My], classOf[MyImpl], 3000) val option1 = Actor.registry.typedActorFor[My] @@ -55,7 +55,7 @@ class TypedActorRegistrySpec extends WordSpec with MustMatchers { Actor.registry.shutdownAll() } - "be able to be retreived from the registry by manifest two times (even when created in supervisor)" in { + "be able to be retrieved from the registry by manifest two times (even when created in supervisor)" in { Actor.registry.shutdownAll() val manager = new TypedActorConfigurator manager.configure( From d55c9cea10279abe368a6466b5b3dea05be1a637 Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Sat, 23 Apr 2011 09:44:59 +0200 Subject: [PATCH 133/147] add Java API to Duration --- .../src/test/java/akka/util/JavaDuration.java | 16 ++++++ .../src/main/scala/akka/util/Duration.scala | 52 ++++++++++++++++--- 2 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 akka-actor-tests/src/test/java/akka/util/JavaDuration.java diff --git a/akka-actor-tests/src/test/java/akka/util/JavaDuration.java b/akka-actor-tests/src/test/java/akka/util/JavaDuration.java new file mode 100644 index 0000000000..1cea685880 --- /dev/null +++ b/akka-actor-tests/src/test/java/akka/util/JavaDuration.java @@ -0,0 +1,16 @@ +package akka.util; + +import org.junit.Test; + +public class JavaDuration { + + @Test void testCreation() { + final Duration fivesec = Duration.create(5, "seconds"); + final Duration threemillis = Duration.parse("3 millis"); + final Duration diff = fivesec.minus(threemillis); + assert diff.lt(fivesec); + assert Duration.Zero().lteq(Duration.Inf()); + assert Duration.Inf().gt(Duration.Zero().neg()); + } + +} diff --git a/akka-actor/src/main/scala/akka/util/Duration.scala b/akka-actor/src/main/scala/akka/util/Duration.scala index 933e3cd9a9..4e105c198f 100644 --- a/akka-actor/src/main/scala/akka/util/Duration.scala +++ b/akka-actor/src/main/scala/akka/util/Duration.scala @@ -96,15 +96,32 @@ object Duration { case "ns" | "nano" | "nanos" | "nanosecond" | "nanoseconds" => NANOSECONDS } + val Zero : Duration = new FiniteDuration(0, NANOSECONDS) + trait Infinite { this : Duration => override def equals(other : Any) = false - def +(other : Duration) : Duration = this - def -(other : Duration) : Duration = this - def *(other : Double) : Duration = this - def /(other : Double) : Duration = this + def +(other : Duration) : Duration = + other match { + case _ : this.type => this + case _ : Infinite => throw new IllegalArgumentException("illegal addition of infinities") + case _ => this + } + def -(other : Duration) : Duration = + other match { + case _ : this.type => throw new IllegalArgumentException("illegal subtraction of infinities") + case _ => this + } + def *(factor : Double) : Duration = this + def /(factor : Double) : Duration = this + def /(other : Duration) : Double = + other match { + case _ : Infinite => throw new IllegalArgumentException("illegal division of infinities") + // maybe questionable but pragmatic: Inf / 0 => Inf + case x => Double.PositiveInfinity * (if ((this > Zero) ^ (other >= Zero)) -1 else 1) + } def finite_? = false @@ -126,7 +143,7 @@ object Duration { * Infinite duration: greater than any other and not equal to any other, * including itself. */ - object Inf extends Duration with Infinite { + val Inf : Duration = new Duration with Infinite { override def toString = "Duration.Inf" def >(other : Duration) = true def >=(other : Duration) = true @@ -139,7 +156,7 @@ object Duration { * Infinite negative duration: lesser than any other and not equal to any other, * including itself. */ - object MinusInf extends Duration with Infinite { + val MinusInf : Duration = new Duration with Infinite { override def toString = "Duration.MinusInf" def >(other : Duration) = false def >=(other : Duration) = false @@ -148,6 +165,11 @@ object Duration { def unary_- : Duration = Inf } + // Java Factories + def create(length : Long, unit : TimeUnit) : Duration = apply(length, unit) + def create(length : Double, unit : TimeUnit) : Duration = apply(length, unit) + def create(length : Long, unit : String) : Duration = apply(length, unit) + def parse(s : String) : Duration = unapply(s).get } /** @@ -195,7 +217,7 @@ object Duration { * val d3 = d2 + 1.millisecond *
*/ -trait Duration { +abstract class Duration { def length : Long def unit : TimeUnit def toNanos : Long @@ -215,8 +237,22 @@ trait Duration { def -(other : Duration) : Duration def *(factor : Double) : Duration def /(factor : Double) : Duration + def /(other : Duration) : Double def unary_- : Duration def finite_? : Boolean + + // Java API + def lt(other : Duration) = this < other + def lteq(other : Duration) = this <= other + def gt(other : Duration) = this > other + def gteq(other : Duration) = this >= other + def plus(other : Duration) = this + other + def minus(other : Duration) = this - other + def mul(factor : Double) = this * factor + def div(factor : Double) = this / factor + def div(other : Duration) = this / other + def neg() = -this + def isFinite() = finite_? } class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration { @@ -306,6 +342,8 @@ class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration { def /(factor : Double) = fromNanos(long2double(toNanos) / factor) + def /(other : Duration) = if (other.finite_?) long2double(toNanos) / other.toNanos else 0 + def unary_- = Duration(-length, unit) def finite_? = true From 2adc45cf2c64d825aaacdc2bde36cc30b01c6175 Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 23 Apr 2011 11:04:02 +0200 Subject: [PATCH 134/147] move Duration doc to general/util.rst move migration guide links one level down --- akka-docs/general/index.rst | 9 ++--- akka-docs/general/migration-guides.rst | 11 ++++++ akka-docs/general/util.rst | 49 ++++++++++++++++++++++++++ akka-docs/scala/testing.rst | 39 -------------------- 4 files changed, 63 insertions(+), 45 deletions(-) create mode 100644 akka-docs/general/migration-guides.rst create mode 100644 akka-docs/general/util.rst diff --git a/akka-docs/general/index.rst b/akka-docs/general/index.rst index 1d716ed63a..5b0e3c24d6 100644 --- a/akka-docs/general/index.rst +++ b/akka-docs/general/index.rst @@ -2,10 +2,7 @@ General ======= .. toctree:: - :maxdepth: 1 + :maxdepth: 2 - migration-guide-0.7.x-0.8.x - migration-guide-0.8.x-0.9.x - migration-guide-0.9.x-0.10.x - migration-guide-0.10.x-1.0.x - migration-guide-1.0.x-1.1.x + migration-guides + util diff --git a/akka-docs/general/migration-guides.rst b/akka-docs/general/migration-guides.rst new file mode 100644 index 0000000000..204b7a22e5 --- /dev/null +++ b/akka-docs/general/migration-guides.rst @@ -0,0 +1,11 @@ +Migration Guides +================ + +.. toctree:: + :maxdepth: 1 + + migration-guide-0.7.x-0.8.x + migration-guide-0.8.x-0.9.x + migration-guide-0.9.x-0.10.x + migration-guide-0.10.x-1.0.x + migration-guide-1.0.x-1.1.x diff --git a/akka-docs/general/util.rst b/akka-docs/general/util.rst new file mode 100644 index 0000000000..bb0f61e778 --- /dev/null +++ b/akka-docs/general/util.rst @@ -0,0 +1,49 @@ +######### +Utilities +######### + +.. sidebar:: Contents + + .. contents:: :local: + +This section of the manual describes miscellaneous utilities which are provided +by Akka and used in multiple places. + +.. _Duration: + +Duration +======== + +Durations are used throughout the Akka library, wherefore this concept is +represented by a special data type, :class:`Duration`. Values of this type may +represent infinite (:obj:`Duration.Inf`, :obj:`Duration.MinusInf`) or finite +durations, where the latter are constructable using a mini-DSL: + +.. code-block:: scala + + import akka.util.duration._ // notice the small d + + val fivesec = 5.seconds + val threemillis = 3.millis + val diff = fivesec - threemillis + assert (diff < fivesec) + +.. note:: + + You may leave out the dot if the expression is clearly delimited (e.g. + within parentheses or in an argument list), but it is recommended to use it + if the time unit is the last token on a line, otherwise semi-colon inference + might go wrong, depending on what starts the next line. + +Java provides less syntactic sugar, so you have to spell out the operations as +method calls instead: + +.. code-block:: java + + final Duration fivesec = Duration.create(5, "seconds"); + final Duration threemillis = Duration.parse("3 millis"); + final Duration diff = fivesec.minus(threemillis); + assert (diff.lt(fivesec)); + assert (Duration.Zero().lt(Duration.Inf())); + + diff --git a/akka-docs/scala/testing.rst b/akka-docs/scala/testing.rst index ecf86720db..9238cfd198 100644 --- a/akka-docs/scala/testing.rst +++ b/akka-docs/scala/testing.rst @@ -332,42 +332,3 @@ has to offer: exception stack traces - Exclusion of certain classes of dead-lock scenarios -.. _Duration: - -Duration -======== - -Durations are used throughout the Akka library, wherefore this concept is -represented by a special data type, :class:`Duration`. Values of this type may -represent infinite (:obj:`Duration.Inf`, :obj:`Duration.MinusInf`) or finite -durations, where the latter are constructable using a mini-DSL: - -.. code-block:: scala - - import akka.util.duration._ // notice the small d - - val fivesec = 5.seconds - val threemillis = 3.millis - val diff = fivesec - threemillis - assert (diff < fivesec) - -.. note:: - - You may leave out the dot if the expression is clearly delimited (e.g. - within parentheses or in an argument list), but it is recommended to use it - if the time unit is the last token on a line, otherwise semi-colon inference - might go wrong, depending on what starts the next line. - -Java provides less syntactic sugar, so you have to spell out the operations as -method calls instead: - -.. code-block:: java - - final Duration fivesec = Duration.create(5, "seconds"); - final Duration threemillis = Duration.parse("3 millis"); - final Duration diff = fivesec.minus(threemillis); - assert (diff.lt(fivesec)); - assert (Duration.Zero().lt(Duration.Inf())); - - - From 78beaa9e43c007136f28d2a6b921cafb6922fe23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Sat, 23 Apr 2011 11:31:00 +0200 Subject: [PATCH 135/147] added some generic lookup methods to Configuration --- .../main/scala/akka/config/Configuration.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/akka-actor/src/main/scala/akka/config/Configuration.scala b/akka-actor/src/main/scala/akka/config/Configuration.scala index aabdc5962f..d06a9eb88e 100644 --- a/akka-actor/src/main/scala/akka/config/Configuration.scala +++ b/akka-actor/src/main/scala/akka/config/Configuration.scala @@ -64,6 +64,24 @@ class Configuration(val map: Map[String, Any]) { def keys: Iterable[String] = map.keys + def getAny(key: String): Option[Any] = { + try { + Some(map(key)) + } catch { + case _ => None + } + } + + def getAny(key: String, defaultValue: Any): Any = getAny(key).getOrElse(defaultValue) + + def getSeqAny(key: String): Seq[Any] = { + try { + map(key).asInstanceOf[Seq[Any]] + } catch { + case _ => Seq.empty[Any] + } + } + def getString(key: String): Option[String] = map.get(key).map(_.toString) def getString(key: String, defaultValue: String): String = getString(key).getOrElse(defaultValue) From 18bfe152ba1b16655b0ac36e90fcb31873d119f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Sat, 23 Apr 2011 11:32:19 +0200 Subject: [PATCH 136/147] Changed default event-handler level to INFO --- akka-actor/src/main/scala/akka/event/EventHandler.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-actor/src/main/scala/akka/event/EventHandler.scala b/akka-actor/src/main/scala/akka/event/EventHandler.scala index 9d53b57787..efda95a951 100644 --- a/akka-actor/src/main/scala/akka/event/EventHandler.scala +++ b/akka-actor/src/main/scala/akka/event/EventHandler.scala @@ -85,7 +85,7 @@ object EventHandler extends ListenerManagement { lazy val EventHandlerDispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher(ID).build - val level: Int = config.getString("akka.event-handler-level", "DEBUG") match { + val level: Int = config.getString("akka.event-handler-level", "INFO") match { case "ERROR" => ErrorLevel case "WARNING" => WarningLevel case "INFO" => InfoLevel From e3a7a2cf6630ba12b7ab8cfc1290d1ae32a3047a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Sat, 23 Apr 2011 11:34:19 +0200 Subject: [PATCH 137/147] Removed logging ClassNotFoundException to EventHandler in ReflectiveAccess --- .../main/scala/akka/util/ReflectiveAccess.scala | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala index f4ceba6ebe..cda46c1a95 100644 --- a/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala +++ b/akka-actor/src/main/scala/akka/util/ReflectiveAccess.scala @@ -178,9 +178,7 @@ object ReflectiveAccess { val first = try { Option(classloader.loadClass(fqn).asInstanceOf[Class[T]]) } catch { - case c: ClassNotFoundException => - EventHandler.debug(this, c.toString) - None + case c: ClassNotFoundException => None } if (first.isDefined) first @@ -189,9 +187,7 @@ object ReflectiveAccess { val second = try { Option(Thread.currentThread.getContextClassLoader.loadClass(fqn).asInstanceOf[Class[T]]) } catch { - case c: ClassNotFoundException => - EventHandler.debug(this, c.toString) - None + case c: ClassNotFoundException => None } if (second.isDefined) second @@ -201,9 +197,7 @@ object ReflectiveAccess { if (classloader ne loader) Option(loader.loadClass(fqn).asInstanceOf[Class[T]]) else None } catch { - case c: ClassNotFoundException => - EventHandler.debug(this, c.toString) - None + case c: ClassNotFoundException => None } if (third.isDefined) third @@ -212,9 +206,7 @@ object ReflectiveAccess { try { Option(Class.forName(fqn).asInstanceOf[Class[T]]) } catch { - case c: ClassNotFoundException => - EventHandler.debug(this, c.toString) - None + case c: ClassNotFoundException => None } } } From 6e45ab7c17e8a11d593540eb9793e3c7dafceafd Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 23 Apr 2011 11:35:54 +0200 Subject: [PATCH 138/147] add Future.empty[T] --- akka-actor/src/main/scala/akka/dispatch/Future.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index 60d956e07c..0f44fb27c0 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -246,6 +246,11 @@ object Future { def !(msg: Any) = future << msg } + /** + * Create an empty Future with default timeout + */ + def empty[T](timeout : Long = Actor.TIMEOUT) = new DefaultCompletableFuture[T](timeout) + import scala.collection.mutable.Builder import scala.collection.generic.CanBuildFrom From 33f05856c1434dfcd3861b7de9236bf43cc2238c Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 23 Apr 2011 11:49:03 +0200 Subject: [PATCH 139/147] use Future.empty in Future.channel --- akka-actor/src/main/scala/akka/dispatch/Future.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index 0f44fb27c0..2b3fc6425d 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -242,7 +242,7 @@ object Future { * Construct a completable channel */ def channel(timeout: Long = Actor.TIMEOUT) = new Channel[Any] { - val future = new DefaultCompletableFuture[Any](timeout) + val future = empty[Any](timeout) def !(msg: Any) = future << msg } From 06c134ca91d276c4d2322db66fd82bca173cb1b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Sat, 23 Apr 2011 11:49:33 +0200 Subject: [PATCH 140/147] Added EventHandler.shutdown()' --- .../src/main/scala/akka/event/EventHandler.scala | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/akka-actor/src/main/scala/akka/event/EventHandler.scala b/akka-actor/src/main/scala/akka/event/EventHandler.scala index efda95a951..b29eb0ca72 100644 --- a/akka-actor/src/main/scala/akka/event/EventHandler.scala +++ b/akka-actor/src/main/scala/akka/event/EventHandler.scala @@ -44,6 +44,11 @@ import akka.AkkaException * EventHandler.error(exception, this, message) *
* + * Shut down the EventHandler: + *
+ * EventHandler.shutdown()
+ * 
+ * * @author
Jonas Bonér */ object EventHandler extends ListenerManagement { @@ -94,6 +99,14 @@ object EventHandler extends ListenerManagement { "Configuration option 'akka.event-handler-level' is invalid [" + unknown + "]") } + /** + * Shuts down all event handler listeners including the event handle dispatcher. + */ + def shutdown() = { + foreachListener(_.stop) + EventHandlerDispatcher.shutdown + } + def notify(event: Any) { if (event.isInstanceOf[Event]) { if (level >= event.asInstanceOf[Event].level) notifyListeners(event) From 2770f47f27ac12a5e6b44918de987f4a3ab4b7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Sat, 23 Apr 2011 11:55:54 +0200 Subject: [PATCH 141/147] Fixed problem in Pi calculation algorithm --- akka-docs/intro/examples/Pi.scala | 2 +- .../intro/getting-started-first-java.rst | 4 ++-- .../getting-started-first-scala-eclipse.rst | 24 +++++++++---------- akka-docs/intro/getting-started-first.rst | 2 -- .../java/akka/tutorial/first/java/Pi.java | 2 +- .../src/main/scala/Pi.scala | 2 +- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/akka-docs/intro/examples/Pi.scala b/akka-docs/intro/examples/Pi.scala index 6bf1dea903..1635229802 100644 --- a/akka-docs/intro/examples/Pi.scala +++ b/akka-docs/intro/examples/Pi.scala @@ -36,7 +36,7 @@ object Pi extends App { def calculatePiFor(start: Int, nrOfElements: Int): Double = { var acc = 0.0 for (i <- start until (start + nrOfElements)) - acc += 4 * math.pow(-1, i) / (2 * i + 1) + acc += 4.0 * math.pow(-1, i) / (2 * i + 1) acc } //#calculate-pi diff --git a/akka-docs/intro/getting-started-first-java.rst b/akka-docs/intro/getting-started-first-java.rst index df032a8970..99db6f8c07 100644 --- a/akka-docs/intro/getting-started-first-java.rst +++ b/akka-docs/intro/getting-started-first-java.rst @@ -282,7 +282,7 @@ The only thing missing in our ``Worker`` actor is the implementation on the ``ca private double calculatePiFor(int start, int nrOfElements) { double acc = 0.0; for (int i = start * nrOfElements; i <= ((start + 1) * nrOfElements - 1); i++) { - acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1); + acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1); } return acc; } @@ -550,7 +550,7 @@ Before we package it up and run it, let's take a look at the full code now, with private double calculatePiFor(int start, int nrOfElements) { double acc = 0.0; for (int i = start * nrOfElements; i <= ((start + 1) * nrOfElements - 1); i++) { - acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1); + acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1); } return acc; } diff --git a/akka-docs/intro/getting-started-first-scala-eclipse.rst b/akka-docs/intro/getting-started-first-scala-eclipse.rst index d34f242e7d..ebd0064620 100644 --- a/akka-docs/intro/getting-started-first-scala-eclipse.rst +++ b/akka-docs/intro/getting-started-first-scala-eclipse.rst @@ -4,7 +4,7 @@ Getting Started Tutorial (Scala with Eclipse): First Chapter Introduction ------------ -Welcome to the first tutorial on how to get started with Akka and Scala. We assume that you already know what Akka and Scala are and will now focus on the steps necessary to start your first project. We will be using `Eclipse `_, and the `Scala plugin for Eclipse `_. +Welcome to the first tutorial on how to get started with Akka and Scala. We assume that you already know what Akka and Scala are and will now focus on the steps necessary to start your first project. We will be using `Eclipse `_, and the `Scala plugin for Eclipse `_. The sample application that we will create is using actors to calculate the value of Pi. Calculating Pi is a CPU intensive operation and we will utilize Akka Actors to write a concurrent solution that scales out to multi-core processors. This sample will be extended in future tutorials to use Akka Remote Actors to scale out on multiple machines in a cluster. @@ -109,7 +109,7 @@ Once the installation is finished, you need to restart Eclipse. The first time t Accept the recommended settings, and follow the instructions if you need to increase the heap size of Eclipse. -Check that the installation succeeded by creating a new Scala project (``File/New>Scala Project``), and typing some code. You should have content-assist, hyperlinking to definitions, instant error reporting, and so on. +Check that the installation succeeded by creating a new Scala project (``File/New>Scala Project``), and typing some code. You should have content-assist, hyperlinking to definitions, instant error reporting, and so on. .. image:: example-code.png @@ -147,8 +147,8 @@ Using SBT in Eclipse If you are an `SBT `_ user, you can follow the :doc:`Akka Tutorial in Scala ` and additionally install the ``sbt-eclipse`` plugin. This adds support for generating Eclipse project files from your SBT project. You need to update your SBT plugins definition in ``project/plugins``:: - import sbt._ - + import sbt._ + class TutorialPlugins(info: ProjectInfo) extends PluginDefinition(info) { // eclipsify plugin lazy val eclipse = "de.element34" % "sbt-eclipsify" % "0.7.0" @@ -161,8 +161,8 @@ and then update your SBT project definition by mixing in ``Eclipsify`` in your p import sbt._ import de.element34.sbteclipsify._ - - class MySbtProject(info: ProjectInfo) extends DefaultProject(info) + + class MySbtProject(info: ProjectInfo) extends DefaultProject(info) with Eclipsify with AkkaProject { // the project definition here // akka dependencies @@ -173,14 +173,14 @@ Then run the ``eclipse`` target to generate the Eclipse project:: dragos@dragos-imac pi $ sbt eclipse [info] Building project AkkaPi 1.0 against Scala 2.9.0.RC1 [info] using MySbtProject with sbt 0.7.4 and Scala 2.7.7 - [info] + [info] [info] == eclipse == [info] Creating eclipse project... [info] == eclipse == [success] Successful. - [info] + [info] [info] Total time: 0 s, completed Apr 20, 2011 2:48:03 PM - [info] + [info] [info] Total session time: 1 s, completed Apr 20, 2011 2:48:03 PM [success] Build completed successfully. @@ -195,7 +195,7 @@ Start writing the code The design we are aiming for is to have one ``Master`` actor initiating the computation, creating a set of ``Worker`` actors. Then it splits up the work into discrete chunks, and sends these chunks to the different workers in a round-robin fashion. The master waits until all the workers have completed their work and sent back results for aggregation. When computation is completed the master prints out the result, shuts down all workers and then itself. -With this in mind, let's now create the messages that we want to have flowing in the system. +With this in mind, let's now create the messages that we want to have flowing in the system. Creating the messages --------------------- @@ -245,7 +245,7 @@ The only thing missing in our ``Worker`` actor is the implementation on the ``ca def calculatePiFor(start: Int, nrOfElements: Int): Double = { var acc = 0.0 for (i <- start until (start + nrOfElements)) - acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1) + acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1) acc } @@ -392,7 +392,7 @@ Run it from Eclipse ------------------- Eclipse builds your project on every save when ``Project/Build Automatically`` is set. If not, bring you project up to date by clicking ``Project/Build Project``. If there are no compilation errors, you can right-click in the editor where ``Pi`` is defined, and choose ``Run as.. /Scala application``. If everything works fine, you should see:: - + AKKA_HOME is defined as [/Users/jboner/tools/akka-modules-1.1-M1/] loading config from [/Users/jboner/tools/akka-modules-1.1-M1/config/akka.conf]. diff --git a/akka-docs/intro/getting-started-first.rst b/akka-docs/intro/getting-started-first.rst index aaa466c1dc..79c220d14a 100644 --- a/akka-docs/intro/getting-started-first.rst +++ b/akka-docs/intro/getting-started-first.rst @@ -266,8 +266,6 @@ But before we package it up and run it, let's take a look at the full code now, .. includecode:: examples/Pi.scala - - Run it as a command line application ------------------------------------ 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 8c0085fb97..034dec5178 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 @@ -87,7 +87,7 @@ public class Pi { private double calculatePiFor(int start, int nrOfElements) { double acc = 0.0; for (int i = start * nrOfElements; i <= ((start + 1) * nrOfElements - 1); i++) { - acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1); + acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1); } return acc; } diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index c16c53f995..e6d8b87c14 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -59,7 +59,7 @@ object Pi extends App { def calculatePiFor(start: Int, nrOfElements: Int): Double = { var acc = 0.0 for (i <- start until (start + nrOfElements)) - acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1) + acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1) acc } From b16108b6618227dfbda60ae8302ee57c62f39a1a Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Sun, 24 Apr 2011 08:33:13 +0200 Subject: [PATCH 142/147] Applied the last typo fixes also --- akka-docs/general/migration-guide-1.0.x-1.1.x.rst | 2 +- akka-docs/pending/release-notes.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-docs/general/migration-guide-1.0.x-1.1.x.rst b/akka-docs/general/migration-guide-1.0.x-1.1.x.rst index 4108d323be..3fc555abaf 100644 --- a/akka-docs/general/migration-guide-1.0.x-1.1.x.rst +++ b/akka-docs/general/migration-guide-1.0.x-1.1.x.rst @@ -20,7 +20,7 @@ Don't forget to add a SLF4J backend though, we recommend: .. code-block:: scala - lazy val logback = "ch.qos.logback" % "logback-classic" % "0.9.28" + lazy val logback = "ch.qos.logback" % "logback-classic" % "0.9.28" % "runtime" # If you used HawtDispatcher and want to continue using it, you need to include akka-dispatcher-extras.jar from Akka Modules, in your akka.conf you need to specify: ``akka.dispatch.HawtDispatcherConfigurator`` instead of ``HawtDispatcher`` # FSM: the onTransition method changed from Function1 to PartialFunction; there is an implicit conversion for the precise types in place, but it may be necessary to add an underscore if you are passing an eta-expansion (using a method as function value). diff --git a/akka-docs/pending/release-notes.rst b/akka-docs/pending/release-notes.rst index 2000b5a1d6..692e7ce244 100644 --- a/akka-docs/pending/release-notes.rst +++ b/akka-docs/pending/release-notes.rst @@ -315,7 +315,7 @@ Release 0.10 - Aug 21 2010 ||< **ADD** ||< Added isDefinedAt method to Actor for checking if it can receive a certain message ||< Jonas Bonér || ||< **ADD** ||< Added caching of Active Object generated class bytes, huge perf improvement ||< Jonas Bonér || ||< **ADD** ||< Added RemoteClient Listener API ||< Jonas Bonér || -||< **ADD** ||< Added methods to retreive children from a Supervisor ||< Jonas Bonér || +||< **ADD** ||< Added methods to retrieve children from a Supervisor ||< Jonas Bonér || ||< **ADD** ||< Rewritten Supervisor to become more clear and "correct" ||< Jonas Bonér || ||< **ADD** ||< Added options to configure a blocking mailbox with custom capacity ||< Jonas Bonér || ||< **ADD** ||< Added RemoteClient reconnection time window configuration option ||< Jonas Bonér || From e22113075fe2472af5e0fb2c4b9a88d51cab2cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Sun, 24 Apr 2011 09:01:45 -0700 Subject: [PATCH 143/147] Fixed broken pi calc algo --- akka-docs/intro/getting-started-first-scala.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-docs/intro/getting-started-first-scala.rst b/akka-docs/intro/getting-started-first-scala.rst index 17c4d265c4..d0c8e2c90e 100644 --- a/akka-docs/intro/getting-started-first-scala.rst +++ b/akka-docs/intro/getting-started-first-scala.rst @@ -238,7 +238,7 @@ The only thing missing in our ``Worker`` actor is the implementation on the ``ca def calculatePiFor(start: Int, nrOfElements: Int): Double = { var acc = 0.0 for (i <- start until (start + nrOfElements)) - acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1) + acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1) acc } From 2468f9e68a99ee553e2fcdeb3436cb8dca48ae93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Sun, 24 Apr 2011 09:02:37 -0700 Subject: [PATCH 144/147] Fixed broken pi calc algo --- akka-docs/intro/getting-started-first-scala.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-docs/intro/getting-started-first-scala.rst b/akka-docs/intro/getting-started-first-scala.rst index d0c8e2c90e..c6ea2f4fe1 100644 --- a/akka-docs/intro/getting-started-first-scala.rst +++ b/akka-docs/intro/getting-started-first-scala.rst @@ -404,7 +404,7 @@ But before we package it up and run it, let's take a look at the full code now, def calculatePiFor(start: Int, nrOfElements: Int): Double = { var acc = 0.0 for (i <- start until (start + nrOfElements)) - acc += 4 * (1 - (i % 2) * 2) / (2 * i + 1) + acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1) acc } From b041329b98f84d167c0d6dc4b9bcb157d2f25400 Mon Sep 17 00:00:00 2001 From: Derek Williams Date: Sun, 24 Apr 2011 11:24:29 -0600 Subject: [PATCH 145/147] Update Jetty to version 7.4.0 --- project/build/AkkaProject.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index 8a93603b0d..b3cb6dba6b 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -114,7 +114,7 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) { lazy val JERSEY_VERSION = "1.3" lazy val MULTIVERSE_VERSION = "0.6.2" lazy val SCALATEST_VERSION = "1.4-SNAPSHOT" - lazy val JETTY_VERSION = "7.2.2.v20101205" + lazy val JETTY_VERSION = "7.4.0.v20110414" lazy val JAVAX_SERVLET_VERSION = "3.0" lazy val SLF4J_VERSION = "1.6.0" From 2e7c76dd980eea16d3b57ad51a11a689400922de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Wed, 27 Apr 2011 00:38:10 +0200 Subject: [PATCH 146/147] Rebased from master --- .../actor/supervisor/SupervisorSpec.scala | 6 +- .../test/scala/akka/dispatch/FutureSpec.scala | 75 -------- .../src/main/scala/akka/actor/Actor.scala | 10 +- .../src/main/scala/akka/actor/ActorRef.scala | 165 ++---------------- .../main/scala/akka/actor/Supervisor.scala | 3 +- .../scala/akka/config/Configuration.scala | 2 +- .../scala/akka/config/SupervisionConfig.scala | 22 +-- .../src/main/scala/akka/dispatch/Future.scala | 47 ++--- .../remoteinterface/RemoteInterface.scala | 79 --------- .../remote/netty/NettyRemoteSupport.scala | 15 +- .../serialization/SerializationProtocol.scala | 12 +- .../ClientInitiatedRemoteActorSpec.scala | 142 --------------- .../remote/OptimizedLocalScopedSpec.scala | 6 - .../scala/remote/RemoteSupervisorSpec.scala | 9 +- .../scala/remote/RemoteTypedActorSpec.scala | 68 +------- .../remote/UnOptimizedLocalScopedSpec.scala | 6 - .../src/test/scala/ticket/Ticket519Spec.scala | 19 -- .../src/main/scala/ChatServer.scala | 6 +- .../ClientManagedRemoteActorSample.scala | 35 ---- .../scala/akka/testkit/TestActorRef.scala | 2 +- .../main/scala/akka/actor/TypedActor.scala | 122 ++----------- .../config/TypedActorGuiceConfigurator.scala | 11 +- 22 files changed, 69 insertions(+), 793 deletions(-) delete mode 100644 akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala delete mode 100644 akka-remote/src/test/scala/ticket/Ticket519Spec.scala delete mode 100644 akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala 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 253570f576..fe00811959 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 @@ -60,7 +60,11 @@ object SupervisorSpec { class Master extends Actor { self.faultHandler = OneForOneStrategy(List(classOf[Exception]), 5, testMillis(1 second).toInt) - val temp = self.spawnLink[TemporaryActor] + val temp = { + val a = actorOf[TemporaryActor] + self link a + a.start + } override def receive = { case Die => temp !! (Die, TimeoutMillis) 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 e12294a70d..b2e4dd94e5 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala @@ -134,81 +134,6 @@ class FutureSpec extends JUnitSuite { actor.stop() } - @Test def shouldFutureAwaitEitherLeft = { - val actor1 = actorOf[TestActor].start() - val actor2 = actorOf[TestActor].start() - val future1 = actor1 !!! "Hello" - val future2 = actor2 !!! "NoReply" - val result = Futures.awaitEither(future1, future2) - assert(result.isDefined) - assert("World" === result.get) - actor1.stop() - actor2.stop() - } - - @Test def shouldFutureAwaitEitherRight = { - val actor1 = actorOf[TestActor].start() - val actor2 = actorOf[TestActor].start() - val future1 = actor1 !!! "NoReply" - val future2 = actor2 !!! "Hello" - val result = Futures.awaitEither(future1, future2) - assert(result.isDefined) - assert("World" === result.get) - actor1.stop() - actor2.stop() - } - - @Test def shouldFutureAwaitOneLeft = { - val actor1 = actorOf[TestActor].start() - val actor2 = actorOf[TestActor].start() - val future1 = actor1 !!! "NoReply" - val future2 = actor2 !!! "Hello" - val result = Futures.awaitOne(List(future1, future2)) - assert(result.result.isDefined) - assert("World" === result.result.get) - actor1.stop() - actor2.stop() - } - - @Test def shouldFutureAwaitOneRight = { - val actor1 = actorOf[TestActor].start() - val actor2 = actorOf[TestActor].start() - val future1 = actor1 !!! "Hello" - val future2 = actor2 !!! "NoReply" - val result = Futures.awaitOne(List(future1, future2)) - assert(result.result.isDefined) - assert("World" === result.result.get) - actor1.stop() - actor2.stop() - } - - @Test def shouldFutureAwaitAll = { - val actor1 = actorOf[TestActor].start() - val actor2 = actorOf[TestActor].start() - val future1 = actor1 !!! "Hello" - val future2 = actor2 !!! "Hello" - Futures.awaitAll(List(future1, future2)) - assert(future1.result.isDefined) - assert("World" === future1.result.get) - assert(future2.result.isDefined) - assert("World" === future2.result.get) - actor1.stop() - actor2.stop() - } - - @Test def shouldFuturesAwaitMapHandleEmptySequence { - assert(Futures.awaitMap[Nothing,Unit](Nil)(x => ()) === Nil) - } - - @Test def shouldFuturesAwaitMapHandleNonEmptySequence { - val latches = (1 to 3) map (_ => new StandardLatch) - val actors = latches map (latch => actorOf(new TestDelayActor(latch)).start()) - val futures = actors map (actor => (actor.!!![String]("Hello"))) - latches foreach { _.open } - - assert(Futures.awaitMap(futures)(_.result.map(_.length).getOrElse(0)).sum === (latches.size * "World".length)) - } - @Test def shouldFoldResults { val actors = (1 to 10).toList map { _ => actorOf(new Actor { diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index 14acfbb3e1..200a397127 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -165,7 +165,7 @@ object Actor extends ListenerManagement { "\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)'.")) - }, None) + }) /** * Creates an ActorRef out of the Actor. Allows you to pass in a factory function @@ -185,7 +185,7 @@ object Actor extends ListenerManagement { * val actor = actorOf(new MyActor).start() *
*/ - def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory, None) + def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory) /** * Creates an ActorRef out of the Actor. Allows you to pass in a factory (Creator) @@ -195,7 +195,7 @@ object Actor extends ListenerManagement { * This function should NOT be used for remote actors. * JAVA API */ - def actorOf(creator: Creator[Actor]): ActorRef = new LocalActorRef(() => creator.create, None) + def actorOf(creator: Creator[Actor]): ActorRef = new LocalActorRef(() => creator.create) /** * Use to spawn out a block of code in an event-driven actor. Will shut actor down when @@ -245,8 +245,7 @@ object Actor extends ListenerManagement { *

* Here you find functions like: * - !, !!, !!! and forward - * - link, unlink, startLink, spawnLink etc - * - makeRemote etc. + * - link, unlink, startLink etc * - start, stop * - etc. * @@ -269,7 +268,6 @@ object Actor extends ListenerManagement { * import self._ * id = ... * dispatcher = ... - * spawnLink[OtherActor] * ... * } *

diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 12e2b5949a..27b8dd6ee8 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -449,39 +449,7 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal /** * Atomically start and link an actor. */ - def startLink(actorRef: ActorRef): Unit - - /** - * Atomically create (from actor class) and start an actor. - *

- * To be invoked from within the actor itself. - */ - @deprecated("Will be removed after 1.1, use Actor.actorOf instead") - def spawn(clazz: Class[_ <: Actor]): ActorRef - - /** - * Atomically create (from actor class), make it remote and start an actor. - *

- * To be invoked from within the actor itself. - */ - @deprecated("Will be removed after 1.1, client managed actors will be removed") - def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long): ActorRef - - /** - * Atomically create (from actor class), link and start an actor. - *

- * To be invoked from within the actor itself. - */ - @deprecated("Will be removed after 1.1, use use Actor.remote.actorOf instead and then link on success") - def spawnLink(clazz: Class[_ <: Actor]): ActorRef - - /** - * Atomically create (from actor class), make it remote, link and start an actor. - *

- * To be invoked from within the actor itself. - */ - @deprecated("Will be removed after 1.1, client managed actors will be removed") - def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long): ActorRef + def startLink(actorRef: ActorRef): ActorRef /** * Returns the mailbox size. @@ -584,10 +552,7 @@ trait ActorRef extends ActorRefShared with java.lang.Comparable[ActorRef] { scal * * @author Jonas Bonér */ -class LocalActorRef private[akka] ( - private[this] val actorFactory: () => Actor, - val homeAddress: Option[InetSocketAddress], - val clientManaged: Boolean = false) +class LocalActorRef private[akka] (private[this] val actorFactory: () => Actor) extends ActorRef with ScalaActorRef { protected[akka] val guard = new ReentrantGuard @@ -620,9 +585,8 @@ class LocalActorRef private[akka] ( __lifeCycle: LifeCycle, __supervisor: Option[ActorRef], __hotswap: Stack[PartialFunction[Any, Unit]], - __factory: () => Actor, - __homeAddress: Option[InetSocketAddress]) = { - this(__factory, __homeAddress) + __factory: () => Actor) = { + this(__factory) _uuid = __uuid id = __id timeout = __timeout @@ -634,11 +598,6 @@ class LocalActorRef private[akka] ( start } - /** - * Returns whether this actor ref is client-managed remote or not - */ - private[akka] final def isClientManaged_? = clientManaged && homeAddress.isDefined && isRemotingEnabled - // ========= PUBLIC FUNCTIONS ========= /** @@ -653,6 +612,8 @@ class LocalActorRef private[akka] ( @deprecated("Will be removed without replacement, doesn't make any sense to have in the face of `become` and `unbecome`") def actorClassName: String = actorClass.getName + final def homeAddress: Option[InetSocketAddress] = None + /** * Sets the dispatcher for this actor. Needs to be invoked before the actor is started. */ @@ -684,9 +645,6 @@ class LocalActorRef private[akka] ( if ((actorInstance ne null) && (actorInstance.get ne null)) initializeActorInstance - if (isClientManaged_?) - Actor.remote.registerClientManagedActor(homeAddress.get.getAddress.getHostAddress, homeAddress.get.getPort, uuid) - checkReceiveTimeout //Schedule the initial Receive timeout } this @@ -706,11 +664,9 @@ class LocalActorRef private[akka] ( } finally { currentMessage = null Actor.registry.unregister(this) - if (isRemotingEnabled) { - if (isClientManaged_?) - Actor.remote.unregisterClientManagedActor(homeAddress.get.getAddress.getHostAddress, homeAddress.get.getPort, uuid) + if (isRemotingEnabled) Actor.remote.unregister(this) - } + setActorSelfFields(actorInstance.get,null) } } //else if (isBeingRestarted) throw new ActorKilledException("Actor [" + toString + "] is being restarted.") @@ -755,55 +711,10 @@ class LocalActorRef private[akka] ( *

* To be invoked from within the actor itself. */ - def startLink(actorRef: ActorRef): Unit = guard.withGuard { + def startLink(actorRef: ActorRef): ActorRef = guard.withGuard { link(actorRef) actorRef.start() - } - - /** - * Atomically create (from actor class) and start an actor. - *

- * To be invoked from within the actor itself. - */ - def spawn(clazz: Class[_ <: Actor]): ActorRef = - Actor.actorOf(clazz).start() - - /** - * Atomically create (from actor class), start and make an actor remote. - *

- * To be invoked from within the actor itself. - */ - def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long = Actor.TIMEOUT): ActorRef = { - ensureRemotingEnabled - val ref = Actor.remote.actorOf(clazz, hostname, port) - ref.timeout = timeout - ref.start() - } - - /** - * Atomically create (from actor class), start and link an actor. - *

- * To be invoked from within the actor itself. - */ - def spawnLink(clazz: Class[_ <: Actor]): ActorRef = { - val actor = spawn(clazz) - link(actor) - actor.start() - actor - } - - /** - * Atomically create (from actor class), start, link and make an actor remote. - *

- * To be invoked from within the actor itself. - */ - def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long = Actor.TIMEOUT): ActorRef = { - ensureRemotingEnabled - val actor = Actor.remote.actorOf(clazz, hostname, port) - actor.timeout = timeout - link(actor) - actor.start() - actor + actorRef } /** @@ -823,10 +734,6 @@ class LocalActorRef private[akka] ( protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit = _supervisor = sup protected[akka] def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = - if (isClientManaged_?) { - Actor.remote.send[Any]( - message, senderOption, None, homeAddress.get, timeout, true, this, None, ActorType.ScalaActor, None) - } else dispatcher dispatchMessage new MessageInvocation(this, message, senderOption, None) protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout[T]( @@ -834,17 +741,10 @@ class LocalActorRef private[akka] ( timeout: Long, senderOption: Option[ActorRef], senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = { - if (isClientManaged_?) { - val future = Actor.remote.send[T]( - message, senderOption, senderFuture, homeAddress.get, timeout, false, this, None, ActorType.ScalaActor, None) - if (future.isDefined) future.get - else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString) - } else { - val future = if (senderFuture.isDefined) senderFuture else Some(new DefaultCompletableFuture[T](timeout)) - dispatcher dispatchMessage new MessageInvocation( - this, message, senderOption, future.asInstanceOf[Some[CompletableFuture[Any]]]) - future.get - } + val future = if (senderFuture.isDefined) senderFuture else Some(new DefaultCompletableFuture[T](timeout)) + dispatcher dispatchMessage new MessageInvocation( + this, message, senderOption, future.asInstanceOf[Some[CompletableFuture[Any]]]) + future.get } /** @@ -1000,11 +900,10 @@ class LocalActorRef private[akka] ( } } } - + //TODO KEEP THIS? protected[akka] def registerSupervisorAsRemoteActor: Option[Uuid] = guard.withGuard { ensureRemotingEnabled if (_supervisor.isDefined) { - if (homeAddress.isDefined) Actor.remote.registerSupervisorForActor(this) Some(_supervisor.get.uuid) } else None } @@ -1190,11 +1089,7 @@ private[akka] case class RemoteActorRef private[akka] ( def dispatcher: MessageDispatcher = unsupported def link(actorRef: ActorRef): Unit = unsupported def unlink(actorRef: ActorRef): Unit = unsupported - def startLink(actorRef: ActorRef): Unit = unsupported - def spawn(clazz: Class[_ <: Actor]): ActorRef = unsupported - def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long): ActorRef = unsupported - def spawnLink(clazz: Class[_ <: Actor]): ActorRef = unsupported - def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int, timeout: Long): ActorRef = unsupported + def startLink(actorRef: ActorRef): ActorRef = unsupported def supervisor: Option[ActorRef] = unsupported def linkedActors: JMap[Uuid, ActorRef] = unsupported protected[akka] def mailbox: AnyRef = unsupported @@ -1400,32 +1295,4 @@ trait ScalaActorRef extends ActorRefShared { ref: ActorRef => true } else false } - - /** - * Atomically create (from actor class) and start an actor. - */ - def spawn[T <: Actor: Manifest]: ActorRef = - spawn(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) - - /** - * Atomically create (from actor class), start and make an actor remote. - */ - def spawnRemote[T <: Actor: Manifest](hostname: String, port: Int, timeout: Long): ActorRef = { - ensureRemotingEnabled - spawnRemote(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]], hostname, port, timeout) - } - - /** - * Atomically create (from actor class), start and link an actor. - */ - def spawnLink[T <: Actor: Manifest]: ActorRef = - spawnLink(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) - - /** - * Atomically create (from actor class), start, link and make an actor remote. - */ - def spawnLinkRemote[T <: Actor: Manifest](hostname: String, port: Int, timeout: Long): ActorRef = { - ensureRemotingEnabled - spawnLinkRemote(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]], hostname, port, timeout) - } } diff --git a/akka-actor/src/main/scala/akka/actor/Supervisor.scala b/akka-actor/src/main/scala/akka/actor/Supervisor.scala index e32b515ae5..9b76d3035a 100644 --- a/akka-actor/src/main/scala/akka/actor/Supervisor.scala +++ b/akka-actor/src/main/scala/akka/actor/Supervisor.scala @@ -92,8 +92,7 @@ case class SupervisorFactory(val config: SupervisorConfig) { *

* The supervisor class is only used for the configuration system when configuring supervisor * hierarchies declaratively. Should not be used as part of the regular programming API. Instead - * wire the children together using 'link', 'spawnLink' etc. and set the 'trapExit' flag in the - * children that should trap error signals and trigger restart. + * wire the children together using 'link', 'startLink' etc. *

* See the ScalaDoc for the SupervisorFactory for an example on how to declaratively wire up children. * diff --git a/akka-actor/src/main/scala/akka/config/Configuration.scala b/akka-actor/src/main/scala/akka/config/Configuration.scala index d06a9eb88e..bc0d68960d 100644 --- a/akka-actor/src/main/scala/akka/config/Configuration.scala +++ b/akka-actor/src/main/scala/akka/config/Configuration.scala @@ -74,7 +74,7 @@ class Configuration(val map: Map[String, Any]) { def getAny(key: String, defaultValue: Any): Any = getAny(key).getOrElse(defaultValue) - def getSeqAny(key: String): Seq[Any] = { + def getListAny(key: String): Seq[Any] = { try { map(key).asInstanceOf[Seq[Any]] } catch { diff --git a/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala b/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala index 6b66f3415d..8ab4b4656b 100644 --- a/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala +++ b/akka-actor/src/main/scala/akka/config/SupervisionConfig.scala @@ -103,32 +103,18 @@ object Supervision { val target: Class[_], val lifeCycle: LifeCycle, val timeout: Long, - _dispatcher: MessageDispatcher, // optional - _remoteAddress: RemoteAddress // optional + _dispatcher: MessageDispatcher // optional ) extends Server { val intf: Option[Class[_]] = Option(_intf) val dispatcher: Option[MessageDispatcher] = Option(_dispatcher) - val remoteAddress: Option[RemoteAddress] = Option(_remoteAddress) def this(target: Class[_], lifeCycle: LifeCycle, timeout: Long) = - this(null: Class[_], target, lifeCycle, timeout, null: MessageDispatcher, null: RemoteAddress) + this(null: Class[_], target, lifeCycle, timeout, null: MessageDispatcher) def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Long) = - this(intf, target, lifeCycle, timeout, null: MessageDispatcher, null: RemoteAddress) - - def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Long, dispatcher: MessageDispatcher) = - this(intf, target, lifeCycle, timeout, dispatcher, null: RemoteAddress) + this(intf, target, lifeCycle, timeout, null: MessageDispatcher) def this(target: Class[_], lifeCycle: LifeCycle, timeout: Long, dispatcher: MessageDispatcher) = - this(null: Class[_], target, lifeCycle, timeout, dispatcher, null: RemoteAddress) - - def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Long, remoteAddress: RemoteAddress) = - this(intf, target, lifeCycle, timeout, null: MessageDispatcher, remoteAddress) - - def this(target: Class[_], lifeCycle: LifeCycle, timeout: Long, remoteAddress: RemoteAddress) = - this(null: Class[_], target, lifeCycle, timeout, null: MessageDispatcher, remoteAddress) - - def this(target: Class[_], lifeCycle: LifeCycle, timeout: Long, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) = - this(null: Class[_], target, lifeCycle, timeout, dispatcher, remoteAddress) + this(null: Class[_], target, lifeCycle, timeout, dispatcher) } } diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index 2b3fc6425d..7e2cac02a6 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -16,7 +16,10 @@ import java.util.concurrent.TimeUnit.{NANOSECONDS => NANOS, MILLISECONDS => MILL import java.util.concurrent.atomic. {AtomicBoolean, AtomicInteger} import java.lang.{Iterable => JIterable} import java.util.{LinkedList => JLinkedList} -import annotation.tailrec + +import scala.annotation.tailrec +import scala.collection.generic.CanBuildFrom +import scala.collection.mutable.Builder class FutureTimeoutException(message: String) extends AkkaException(message) @@ -194,37 +197,11 @@ object Futures { * This is useful for performing a parallel map. For example, to apply a function to all items of a list * in parallel. */ - def traverse[A, B](in: JIterable[A], fn: JFunc[A,Future[B]]): Future[JLinkedList[B]] = traverse(in, Actor.TIMEOUT, fn) - - // ===================================== - // Deprecations - // ===================================== - - /** - * (Blocking!) - */ - @deprecated("Will be removed after 1.1, if you must block, use: futures.foreach(_.await)") - def awaitAll(futures: List[Future[_]]): Unit = futures.foreach(_.await) - - /** - * Returns the First Future that is completed (blocking!) - */ - @deprecated("Will be removed after 1.1, if you must block, use: firstCompletedOf(futures).await") - def awaitOne(futures: List[Future[_]], timeout: Long = Long.MaxValue): Future[_] = firstCompletedOf[Any](futures, timeout).await - - - /** - * Applies the supplied function to the specified collection of Futures after awaiting each future to be completed - */ - @deprecated("Will be removed after 1.1, if you must block, use: futures map { f => fun(f.await) }") - def awaitMap[A,B](in: Traversable[Future[A]])(fun: (Future[A]) => B): Traversable[B] = - in map { f => fun(f.await) } - - /** - * Returns Future.resultOrException of the first completed of the 2 Futures provided (blocking!) - */ - @deprecated("Will be removed after 1.1, if you must block, use: firstCompletedOf(List(f1,f2)).await.resultOrException") - def awaitEither[T](f1: Future[T], f2: Future[T]): Option[T] = firstCompletedOf[T](List(f1,f2)).await.resultOrException + def traverse[A, B, M[_] <: Traversable[_]](in: M[A], timeout: Long = Actor.TIMEOUT)(fn: A => Future[B])(implicit cbf: CanBuildFrom[M[A], B, M[B]]): Future[M[B]] = + in.foldLeft(new DefaultCompletableFuture[Builder[B, M[B]]](timeout).completeWithResult(cbf(in)): Future[Builder[B, M[B]]]) { (fr, a) => + val fb = fn(a.asInstanceOf[A]) + for (r <- fr; b <-fb) yield (r += b) + }.map(_.result) } object Future { @@ -456,7 +433,7 @@ sealed trait Future[+T] { fa complete (try { Right(f(v.right.get)) } catch { - case e: Exception => + case e: Exception => EventHandler.error(e, this, e.getMessage) Left(e) }) @@ -492,7 +469,7 @@ sealed trait Future[+T] { try { fa.completeWith(f(v.right.get)) } catch { - case e: Exception => + case e: Exception => EventHandler.error(e, this, e.getMessage) fa completeWithException e } @@ -522,7 +499,7 @@ sealed trait Future[+T] { if (p(r)) Right(r) else Left(new MatchError(r)) } catch { - case e: Exception => + case e: Exception => EventHandler.error(e, this, e.getMessage) Left(e) }) diff --git a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala index 081b622f39..5210dec635 100644 --- a/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala +++ b/akka-actor/src/main/scala/akka/remoteinterface/RemoteInterface.scala @@ -151,81 +151,6 @@ abstract class RemoteSupport extends ListenerManagement with RemoteServerModule clear } - /** - * Creates a Client-managed ActorRef out of the Actor of the specified Class. - * If the supplied host and port is identical of the configured local node, it will be a local actor - *

-   *   import Actor._
-   *   val actor = actorOf(classOf[MyActor],"www.akka.io", 2552)
-   *   actor.start()
-   *   actor ! message
-   *   actor.stop()
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf(classOf[MyActor],"www.akka.io", 2552).start()
-   * 
- */ - @deprecated("Will be removed after 1.1") - def actorOf(factory: => Actor, host: String, port: Int): ActorRef = - Actor.remote.clientManagedActorOf(() => factory, host, port) - - /** - * Creates a Client-managed ActorRef out of the Actor of the specified Class. - * If the supplied host and port is identical of the configured local node, it will be a local actor - *
-   *   import Actor._
-   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552)
-   *   actor.start()
-   *   actor ! message
-   *   actor.stop()
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf(classOf[MyActor],"www.akka.io",2552).start()
-   * 
- */ - @deprecated("Will be removed after 1.1") - def actorOf(clazz: Class[_ <: Actor], host: String, port: Int): ActorRef = { - import ReflectiveAccess.{ createInstance, noParams, noArgs } - clientManagedActorOf(() => - createInstance[Actor](clazz.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( - 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)'.")), - host, port) - } - - /** - * Creates a Client-managed ActorRef out of the Actor of the specified Class. - * If the supplied host and port is identical of the configured local node, it will be a local actor - *
-   *   import Actor._
-   *   val actor = actorOf[MyActor]("www.akka.io",2552)
-   *   actor.start()
-   *   actor ! message
-   *   actor.stop()
-   * 
- * You can create and start the actor in one statement like this: - *
-   *   val actor = actorOf[MyActor]("www.akka.io",2552).start()
-   * 
- */ - @deprecated("Will be removed after 1.1") - def actorOf[T <: Actor : Manifest](host: String, port: Int): ActorRef = { - import ReflectiveAccess.{ createInstance, noParams, noArgs } - clientManagedActorOf(() => - createInstance[Actor](manifest[T].erasure.asInstanceOf[Class[_]], noParams, noArgs).getOrElse( - 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)'.")), - host, port) - } - protected override def manageLifeCycleOfListeners = false protected[akka] override def notifyListeners(message: => Any): Unit = super.notifyListeners(message) @@ -444,10 +369,6 @@ trait RemoteClientModule extends RemoteModule { self: RemoteModule => def typedActorFor[T](intfClass: Class[T], serviceId: String, implClassName: String, timeout: Long, hostname: String, port: Int, loader: ClassLoader): T = typedActorFor(intfClass, serviceId, implClassName, timeout, hostname, port, Some(loader)) - @deprecated("Will be removed after 1.1") - def clientManagedActorOf(factory: () => Actor, host: String, port: Int): ActorRef - - /** * Clean-up all open connections. */ diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index 3be65cdea3..6943aa3cf1 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -591,19 +591,6 @@ class NettyRemoteSupport extends RemoteSupport with NettyRemoteServerModule with RemoteActorRef(serviceId, className, host, port, timeout, loader) } - - def clientManagedActorOf(factory: () => Actor, host: String, port: Int): ActorRef = { - - if (optimizeLocalScoped_?) { - val home = this.address - if ((host == home.getAddress.getHostAddress || host == home.getHostName) && port == home.getPort)//TODO: switch to InetSocketAddress.equals? - return new LocalActorRef(factory, None) // Code is much simpler with return - } - - val ref = new LocalActorRef(factory, Some(new InetSocketAddress(host, port)), clientManaged = true) - //ref.timeout = timeout //removed because setting default timeout should be done after construction - ref - } } class NettyRemoteServer(serverModule: NettyRemoteServerModule, val host: String, val port: Int, val loader: Option[ClassLoader]) { @@ -1299,4 +1286,4 @@ class DefaultDisposableChannelGroup(name: String) extends DefaultChannelGroup(na throw new IllegalStateException("ChannelGroup already closed, cannot add new channel") } } -} \ No newline at end of file +} diff --git a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala index f41351f5bc..8a1fe40e13 100644 --- a/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala +++ b/akka-remote/src/main/scala/akka/serialization/SerializationProtocol.scala @@ -157,11 +157,11 @@ object ActorSerialization { } private def fromBinaryToLocalActorRef[T <: Actor]( - bytes: Array[Byte], - homeAddress: Option[InetSocketAddress], + bytes: Array[Byte], + homeAddress: Option[InetSocketAddress], format: Format[T]): ActorRef = { val builder = SerializedActorRefProtocol.newBuilder.mergeFrom(bytes) - homeAddress.foreach { addr => + homeAddress.foreach { addr => val addressProtocol = AddressProtocol.newBuilder.setHostname(addr.getAddress.getHostAddress).setPort(addr.getPort).build builder.setOriginalAddress(addressProtocol) } @@ -205,10 +205,11 @@ object ActorSerialization { else actorClass.newInstance.asInstanceOf[Actor] } + /* TODO Can we remove originalAddress from the protocol? val homeAddress = { val address = protocol.getOriginalAddress Some(new InetSocketAddress(address.getHostname, address.getPort)) - } + }*/ val ar = new LocalActorRef( uuidFrom(protocol.getUuid.getHigh, protocol.getUuid.getLow), @@ -218,8 +219,7 @@ object ActorSerialization { lifeCycle, supervisor, hotswap, - factory, - homeAddress) + factory) val messages = protocol.getMessagesList.toArray.toList.asInstanceOf[List[RemoteMessageProtocol]] messages.foreach(message => ar ! MessageSerializer.deserialize(message.getMessage)) diff --git a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala b/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala deleted file mode 100644 index af5aaffcc3..0000000000 --- a/akka-remote/src/test/scala/remote/ClientInitiatedRemoteActorSpec.scala +++ /dev/null @@ -1,142 +0,0 @@ -package akka.actor.remote - -import java.util.concurrent.{CountDownLatch, TimeUnit} -import org.scalatest.WordSpec -import org.scalatest.matchers.MustMatchers -import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} -import org.scalatest.junit.JUnitRunner -import org.junit.runner.RunWith - -import akka.dispatch.Dispatchers -import akka.actor.Actor._ -import akka.actor._ - -class ExpectedRemoteProblem(msg: String) extends RuntimeException(msg) - -object RemoteActorSpecActorUnidirectional { - val latch = new CountDownLatch(1) -} -class RemoteActorSpecActorUnidirectional extends Actor { - self.dispatcher = Dispatchers.newThreadBasedDispatcher(self) - - def receive = { - case "OneWay" => - RemoteActorSpecActorUnidirectional.latch.countDown() - } -} - -class RemoteActorSpecActorBidirectional extends Actor { - def receive = { - case "Hello" => - self.reply("World") - case "Failure" => throw new ExpectedRemoteProblem("expected") - } -} - -class SendOneWayAndReplyReceiverActor extends Actor { - def receive = { - case "Hello" => - self.reply("World") - } -} - -class CountDownActor(latch: CountDownLatch) extends Actor { - def receive = { - case "World" => latch.countDown() - } -} -/* -object SendOneWayAndReplySenderActor { - val latch = new CountDownLatch(1) -} -class SendOneWayAndReplySenderActor extends Actor { - var state: Option[AnyRef] = None - var sendTo: ActorRef = _ - var latch: CountDownLatch = _ - - def sendOff = sendTo ! "Hello" - - def receive = { - case msg: AnyRef => - state = Some(msg) - SendOneWayAndReplySenderActor.latch.countDown() - } -}*/ - -class MyActorCustomConstructor extends Actor { - var prefix = "default-" - var count = 0 - def receive = { - case "incrPrefix" => count += 1; prefix = "" + count + "-" - case msg: String => self.reply(prefix + msg) - } -} - -class ClientInitiatedRemoteActorSpec extends AkkaRemoteTest { - "ClientInitiatedRemoteActor" should { - "shouldSendOneWay" in { - val clientManaged = remote.actorOf[RemoteActorSpecActorUnidirectional](host,port).start() - clientManaged must not be null - clientManaged.getClass must be (classOf[LocalActorRef]) - clientManaged ! "OneWay" - RemoteActorSpecActorUnidirectional.latch.await(1, TimeUnit.SECONDS) must be (true) - clientManaged.stop() - } - - "shouldSendOneWayAndReceiveReply" in { - val latch = new CountDownLatch(1) - val actor = remote.actorOf[SendOneWayAndReplyReceiverActor](host,port).start() - implicit val sender = Some(actorOf(new CountDownActor(latch)).start()) - - actor ! "Hello" - - latch.await(3,TimeUnit.SECONDS) must be (true) - } - - "shouldSendBangBangMessageAndReceiveReply" in { - val actor = remote.actorOf[RemoteActorSpecActorBidirectional](host,port).start() - val result = actor !! ("Hello", 10000) - "World" must equal (result.get.asInstanceOf[String]) - actor.stop() - } - - "shouldSendBangBangMessageAndReceiveReplyConcurrently" in { - val actors = (1 to 10).map(num => { remote.actorOf[RemoteActorSpecActorBidirectional](host,port).start() }).toList - actors.map(_ !!! ("Hello", 10000)) foreach { future => - "World" must equal (future.await.result.asInstanceOf[Option[String]].get) - } - actors.foreach(_.stop()) - } - - "shouldRegisterActorByUuid" in { - val actor1 = remote.actorOf[MyActorCustomConstructor](host, port).start() - val actor2 = remote.actorOf[MyActorCustomConstructor](host, port).start() - - actor1 ! "incrPrefix" - - (actor1 !! "test").get must equal ("1-test") - - actor1 ! "incrPrefix" - - (actor1 !! "test").get must equal ("2-test") - - (actor2 !! "test").get must equal ("default-test") - - actor1.stop() - actor2.stop() - } - - "shouldSendAndReceiveRemoteException" in { - - val actor = remote.actorOf[RemoteActorSpecActorBidirectional](host, port).start() - try { - implicit val timeout = 500000000L - val f = (actor !!! "Failure").await.resultOrException - fail("Shouldn't get here!!!") - } catch { - case e: ExpectedRemoteProblem => - } - actor.stop() - } - } -} diff --git a/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala b/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala index d5aeccefa9..4251b496d2 100644 --- a/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala +++ b/akka-remote/src/test/scala/remote/OptimizedLocalScopedSpec.scala @@ -19,11 +19,5 @@ class OptimizedLocalScopedSpec extends AkkaRemoteTest { remote.actorFor("foo", host, port) must be (fooActor) } - - "Create local actor when client-managed is hosted locally" in { - val localClientManaged = Actor.remote.actorOf[TestActor](host, port) - localClientManaged.homeAddress must be (None) - } - } } \ No newline at end of file diff --git a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala index e5ff681dbc..c40ca51a8d 100644 --- a/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala +++ b/akka-remote/src/test/scala/remote/RemoteSupervisorSpec.scala @@ -69,7 +69,7 @@ class RemotePingPong3Actor extends Actor with Serializable { } } -class RemoteSupervisorSpec extends AkkaRemoteTest { +/*class RemoteSupervisorSpec extends AkkaRemoteTest { var pingpong1: ActorRef = _ var pingpong2: ActorRef = _ @@ -324,7 +324,6 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { factory.newInstance } - /* // Uncomment when the same test passes in SupervisorSpec - pending bug @Test def shouldKillMultipleActorsOneForOne2 = { clearMessageLogs @@ -338,9 +337,7 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { messageLog.poll(5, TimeUnit.SECONDS) } } -*/ - /* @Test def shouldOneWayKillSingleActorOneForOne = { clearMessageLogs @@ -435,6 +432,4 @@ class RemoteSupervisorSpec extends AkkaRemoteTest { messageLog.poll(5, TimeUnit.SECONDS) } } - */ - -} +}*/ diff --git a/akka-remote/src/test/scala/remote/RemoteTypedActorSpec.scala b/akka-remote/src/test/scala/remote/RemoteTypedActorSpec.scala index 988236b85b..43edd29404 100644 --- a/akka-remote/src/test/scala/remote/RemoteTypedActorSpec.scala +++ b/akka-remote/src/test/scala/remote/RemoteTypedActorSpec.scala @@ -4,13 +4,9 @@ package akka.actor.remote -import akka.config.Supervision._ -import akka.actor._ - import java.util.concurrent.{LinkedBlockingQueue, TimeUnit, BlockingQueue} import akka.config. {RemoteAddress, Config, TypedActorConfigurator} -import akka.testing._ object RemoteTypedActorLog { val messageLog: BlockingQueue[String] = new LinkedBlockingQueue[String] @@ -24,73 +20,11 @@ object RemoteTypedActorLog { class RemoteTypedActorSpec extends AkkaRemoteTest { - import RemoteTypedActorLog._ - - private var conf: TypedActorConfigurator = _ - - override def beforeEach { - super.beforeEach - Config.config - conf = new TypedActorConfigurator - conf.configure( - new AllForOneStrategy(List(classOf[Exception]), 3, 5000), - List( - new SuperviseTypedActor( - classOf[RemoteTypedActorOne], - classOf[RemoteTypedActorOneImpl], - Permanent, - Testing.testTime(20000), - RemoteAddress(host,port)), - new SuperviseTypedActor( - classOf[RemoteTypedActorTwo], - classOf[RemoteTypedActorTwoImpl], - Permanent, - Testing.testTime(20000), - RemoteAddress(host,port)) - ).toArray).supervise - } - - override def afterEach { - clearMessageLogs - conf.stop - super.afterEach - Thread.sleep(1000) - } "Remote Typed Actor " should { - /*"receives one-way message" in { - val ta = conf.getInstance(classOf[RemoteTypedActorOne]) + "have unit tests" in { - ta.oneWay - oneWayLog.poll(5, TimeUnit.SECONDS) must equal ("oneway") } - - "responds to request-reply message" in { - val ta = conf.getInstance(classOf[RemoteTypedActorOne]) - ta.requestReply("ping") must equal ("pong") - } */ - - "be restarted on failure" in { - val ta = conf.getInstance(classOf[RemoteTypedActorOne]) - - try { - ta.requestReply("die") - fail("Shouldn't get here") - } catch { case re: RuntimeException if re.getMessage == "Expected exception; to test fault-tolerance" => } - messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") - } - - /* "restarts linked friends on failure" in { - val ta1 = conf.getInstance(classOf[RemoteTypedActorOne]) - val ta2 = conf.getInstance(classOf[RemoteTypedActorTwo]) - - try { - ta1.requestReply("die") - fail("Shouldn't get here") - } catch { case re: RuntimeException if re.getMessage == "Expected exception; to test fault-tolerance" => } - messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") - messageLog.poll(5, TimeUnit.SECONDS) must equal ("Expected exception; to test fault-tolerance") - }*/ } } diff --git a/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala b/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala index 6c6efd9f97..05f8255190 100644 --- a/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala +++ b/akka-remote/src/test/scala/remote/UnOptimizedLocalScopedSpec.scala @@ -18,11 +18,5 @@ class UnOptimizedLocalScopedSpec extends AkkaRemoteTest { remote.actorFor("foo", host, port) must not be (fooActor) } - - "Create remote actor when client-managed is hosted locally" in { - val localClientManaged = Actor.remote.actorOf[TestActor](host, port) - localClientManaged.homeAddress must not be (None) - } - } } \ No newline at end of file diff --git a/akka-remote/src/test/scala/ticket/Ticket519Spec.scala b/akka-remote/src/test/scala/ticket/Ticket519Spec.scala deleted file mode 100644 index 6edec702b5..0000000000 --- a/akka-remote/src/test/scala/ticket/Ticket519Spec.scala +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (C) 2009-2011 Scalable Solutions AB - */ -package akka.actor.ticket - -import akka.actor._ -import akka.actor.remote.AkkaRemoteTest - - -class Ticket519Spec extends AkkaRemoteTest { - "A remote TypedActor" should { - "should handle remote future replies" in { - val actor = TypedActor.newRemoteInstance(classOf[SamplePojo], classOf[SamplePojoImpl],7000,host,port) - val r = actor.someFutureString - - r.await.result.get must equal ("foo") - } - } -} diff --git a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala index a19ed26da0..2be4a4b072 100644 --- a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala +++ b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala @@ -14,7 +14,7 @@ /****************************************************************************** Akka Chat Client/Server Sample Application - + How to run the sample: 1. Fire up two shells. For each of them: @@ -149,7 +149,7 @@ protected def chatManagement: Receive = { case msg @ ChatMessage(from, _) => getSession(from).foreach(_ ! msg) - case msg @ GetChatLog(from) => getSession(from).foreach(_ forward msg) + case msg @ GetChatLog(from) => getSession(from).foreach(_ forward msg) } private def getSession(from: String) : Option[ActorRef] = { @@ -166,7 +166,7 @@ * Creates and links a MemoryChatStorage. */ trait MemoryChatStorageFactory { this: Actor => - val storage = this.self.spawnLink[MemoryChatStorage] // starts and links ChatStorage + val storage = this.self.startLink(actorOf[MemoryChatStorage]) // starts and links ChatStorage } /** diff --git a/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala b/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala deleted file mode 100644 index 42450b0b39..0000000000 --- a/akka-samples/akka-sample-remote/src/main/scala/ClientManagedRemoteActorSample.scala +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (C) 2009-2011 Scalable Solutions AB - */ - -package sample.remote - -import akka.actor.Actor._ -import akka.actor. {ActorRegistry, Actor} -import Actor.remote - -class RemoteHelloWorldActor extends Actor { - def receive = { - case "Hello" => - self.reply("World") - } -} - -object ClientManagedRemoteActorServer { - def run = { - remote.start("localhost", 2552) - } - - def main(args: Array[String]) = run -} - -object ClientManagedRemoteActorClient { - - def run = { - val actor = remote.actorOf[RemoteHelloWorldActor]("localhost",2552).start() - val result = actor !! "Hello" - } - - def main(args: Array[String]) = run -} - diff --git a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala index a31582ac3a..b8214f0fbf 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala @@ -13,7 +13,7 @@ import akka.event.EventHandler * @author Roland Kuhn * @since 1.1 */ -class TestActorRef[T <: Actor](factory: () => T) extends LocalActorRef(factory, None) { +class TestActorRef[T <: Actor](factory: () => T) extends LocalActorRef(factory) { dispatcher = CallingThreadDispatcher.global receiveTimeout = None diff --git a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala index 7103c3fd5b..3add878dc7 100644 --- a/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-typed-actor/src/main/scala/akka/actor/TypedActor.scala @@ -408,18 +408,12 @@ object TypedActorConfiguration { new TypedActorConfiguration() } - def apply(timeout: Long) : TypedActorConfiguration = { - new TypedActorConfiguration().timeout(Duration(timeout, "millis")) + def apply(timeoutMillis: Long) : TypedActorConfiguration = { + new TypedActorConfiguration().timeout(Duration(timeoutMillis, "millis")) } - @deprecated("Will be removed after 1.1") - def apply(host: String, port: Int) : TypedActorConfiguration = { - new TypedActorConfiguration().makeRemote(host, port) - } - - @deprecated("Will be removed after 1.1") - def apply(host: String, port: Int, timeout: Long) : TypedActorConfiguration = { - new TypedActorConfiguration().makeRemote(host, port).timeout(Duration(timeout, "millis")) + def apply(timeout: Duration) : TypedActorConfiguration = { + new TypedActorConfiguration().timeout(timeout) } } @@ -430,7 +424,6 @@ object TypedActorConfiguration { */ final class TypedActorConfiguration { private[akka] var _timeout: Long = Actor.TIMEOUT - private[akka] var _host: Option[InetSocketAddress] = None private[akka] var _messageDispatcher: Option[MessageDispatcher] = None private[akka] var _threadBasedDispatcher: Option[Boolean] = None private[akka] var _id: Option[String] = None @@ -447,15 +440,6 @@ final class TypedActorConfiguration { this } - @deprecated("Will be removed after 1.1") - def makeRemote(hostname: String, port: Int): TypedActorConfiguration = makeRemote(new InetSocketAddress(hostname, port)) - - @deprecated("Will be removed after 1.1") - def makeRemote(remoteAddress: InetSocketAddress): TypedActorConfiguration = { - _host = Some(remoteAddress) - this - } - def dispatcher(messageDispatcher: MessageDispatcher) : TypedActorConfiguration = { if (_threadBasedDispatcher.isDefined) throw new IllegalArgumentException( "Cannot specify both 'threadBasedDispatcher()' and 'dispatcher()'") @@ -511,30 +495,6 @@ object TypedActor { newInstance(intfClass, factory, TypedActorConfiguration()) } - /** - * Factory method for remote typed actor. - * @param intfClass interface the typed actor implements - * @param targetClass implementation class of the typed actor - * @param host hostanme of the remote server - * @param port port of the remote server - */ - @deprecated("Will be removed after 1.1") - def newRemoteInstance[T](intfClass: Class[T], targetClass: Class[_], hostname: String, port: Int): T = { - newInstance(intfClass, targetClass, TypedActorConfiguration(hostname, port)) - } - - /** - * Factory method for remote typed actor. - * @param intfClass interface the typed actor implements - * @param factory factory method that constructs the typed actor - * @param host hostanme of the remote server - * @param port port of the remote server - */ - @deprecated("Will be removed after 1.1") - def newRemoteInstance[T](intfClass: Class[T], factory: => AnyRef, hostname: String, port: Int): T = { - newInstance(intfClass, factory, TypedActorConfiguration(hostname, port)) - } - /** * Factory method for typed actor. * @param intfClass interface the typed actor implements @@ -555,32 +515,6 @@ object TypedActor { newInstance(intfClass, factory, TypedActorConfiguration(timeout)) } - /** - * Factory method for remote typed actor. - * @param intfClass interface the typed actor implements - * @param targetClass implementation class of the typed actor - * @paramm timeout timeout for future - * @param host hostanme of the remote server - * @param port port of the remote server - */ - @deprecated("Will be removed after 1.1") - def newRemoteInstance[T](intfClass: Class[T], targetClass: Class[_], timeout: Long, hostname: String, port: Int): T = { - newInstance(intfClass, targetClass, TypedActorConfiguration(hostname, port, timeout)) - } - - /** - * Factory method for remote typed actor. - * @param intfClass interface the typed actor implements - * @param factory factory method that constructs the typed actor - * @paramm timeout timeout for future - * @param host hostanme of the remote server - * @param port port of the remote server - */ - @deprecated("Will be removed after 1.1") - def newRemoteInstance[T](intfClass: Class[T], factory: => AnyRef, timeout: Long, hostname: String, port: Int): T = { - newInstance(intfClass, factory, TypedActorConfiguration(hostname, port, timeout)) - } - /** * Factory method for typed actor. * @param intfClass interface the typed actor implements @@ -588,20 +522,7 @@ object TypedActor { * @paramm config configuration object fo the typed actor */ def newInstance[T](intfClass: Class[T], factory: => AnyRef, config: TypedActorConfiguration): T = - newInstance(intfClass, createActorRef(newTypedActor(factory),config), config) - - /** - * Creates an ActorRef, can be local only or client-managed-remote - */ - @deprecated("Will be removed after 1.1") - private[akka] def createActorRef(typedActor: => TypedActor, config: TypedActorConfiguration): ActorRef = { - config match { - case null => actorOf(typedActor) - case c: TypedActorConfiguration if (c._host.isDefined) => - Actor.remote.actorOf(typedActor, c._host.get.getAddress.getHostAddress, c._host.get.getPort) - case _ => actorOf(typedActor) - } - } + newInstance(intfClass, actorOf(newTypedActor(factory)), config) /** * Factory method for typed actor. @@ -610,7 +531,7 @@ object TypedActor { * @paramm config configuration object fo the typed actor */ def newInstance[T](intfClass: Class[T], targetClass: Class[_], config: TypedActorConfiguration): T = - newInstance(intfClass, createActorRef(newTypedActor(targetClass),config), config) + newInstance(intfClass, actorOf(newTypedActor(targetClass)), config) private[akka] def newInstance[T](intfClass: Class[T], actorRef: ActorRef): T = { if (!actorRef.actorInstance.get.isInstanceOf[TypedActor]) throw new IllegalArgumentException("ActorRef is not a ref to a typed actor") @@ -618,11 +539,8 @@ object TypedActor { } private[akka] def newInstance[T](intfClass: Class[T], targetClass: Class[_], - remoteAddress: Option[InetSocketAddress], timeout: Long): T = { - val config = TypedActorConfiguration(timeout) - if (remoteAddress.isDefined) config.makeRemote(remoteAddress.get) - newInstance(intfClass, targetClass, config) - } + remoteAddress: Option[InetSocketAddress], timeout: Long): T = + newInstance(intfClass, targetClass, TypedActorConfiguration(timeout)) private def newInstance[T](intfClass: Class[T], actorRef: ActorRef, config: TypedActorConfiguration) : T = { val typedActor = actorRef.actorInstance.get.asInstanceOf[TypedActor] @@ -634,14 +552,8 @@ object TypedActor { actorRef.timeout = config.timeout - val remoteAddress = actorRef match { - case remote: RemoteActorRef => remote.homeAddress - case local: LocalActorRef if local.clientManaged => local.homeAddress - case _ => None - } - - AspectInitRegistry.register(proxy, AspectInit(intfClass, typedActor, actorRef, remoteAddress, actorRef.timeout)) - actorRef.start() + AspectInitRegistry.register(proxy, AspectInit(intfClass, typedActor, actorRef, actorRef.homeAddress, actorRef.timeout)) + actorRef.start proxy.asInstanceOf[T] } @@ -666,20 +578,6 @@ object TypedActor { def newInstance[T](intfClass: Class[T], factory: TypedActorFactory) : T = newInstance(intfClass, factory.create) - /** - * Java API. - */ - @deprecated("Will be removed after 1.1") - def newRemoteInstance[T](intfClass: Class[T], factory: TypedActorFactory, hostname: String, port: Int) : T = - newRemoteInstance(intfClass, factory.create, hostname, port) - - /** - * Java API. - */ - @deprecated("Will be removed after 1.1") - def newRemoteInstance[T](intfClass: Class[T], factory: TypedActorFactory, timeout: Long, hostname: String, port: Int) : T = - newRemoteInstance(intfClass, factory.create, timeout, hostname, port) - /** * Java API. */ diff --git a/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala b/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala index ae19601351..bd4d68bf28 100644 --- a/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala +++ b/akka-typed-actor/src/main/scala/akka/config/TypedActorGuiceConfigurator.scala @@ -106,14 +106,7 @@ private[akka] class TypedActorGuiceConfigurator extends TypedActorConfiguratorBa val implementationClass = component.target val timeout = component.timeout - val (remoteAddress,actorRef) = - component.remoteAddress match { - case Some(a) => - (Some(new InetSocketAddress(a.hostname, a.port)), - Actor.remote.actorOf(TypedActor.newTypedActor(implementationClass), a.hostname, a.port)) - case None => - (None, Actor.actorOf(TypedActor.newTypedActor(implementationClass))) - } + val actorRef = Actor.actorOf(TypedActor.newTypedActor(implementationClass)) actorRef.timeout = timeout if (component.dispatcher.isDefined) actorRef.dispatcher = component.dispatcher.get @@ -123,7 +116,7 @@ private[akka] class TypedActorGuiceConfigurator extends TypedActorConfiguratorBa AspectInitRegistry.register( proxy, - AspectInit(interfaceClass, typedActor, actorRef, remoteAddress, timeout)) + AspectInit(interfaceClass, typedActor, actorRef, None, timeout)) typedActor.initialize(proxy) actorRef.start() From 9706d17ac1d283a8b45eed7728a050736c1c12b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bone=CC=81r?= Date: Wed, 27 Apr 2011 00:40:20 +0200 Subject: [PATCH 147/147] Added Deployer with DeployerSpec which parses the deployment configuration and adds deployment rules --- .../scala/akka/actor/actor/DeployerSpec.scala | 20 + .../src/main/scala/akka/actor/Deployer.scala | 351 ++++++++++++++++++ 2 files changed, 371 insertions(+) create mode 100644 akka-actor-tests/src/test/scala/akka/actor/actor/DeployerSpec.scala create mode 100644 akka-actor/src/main/scala/akka/actor/Deployer.scala 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 new file mode 100644 index 0000000000..592bfc4bc4 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/actor/actor/DeployerSpec.scala @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2009-2011 Scalable Solutions AB + */ + +package akka.actor + +import org.scalatest.WordSpec +import org.scalatest.matchers.MustMatchers +import DeploymentConfig._ + +class DeployerSpec extends WordSpec with MustMatchers { + + "A Deployer" must { + "be able to parse 'akka.actor.deployment._' config elements" in { + val deployment = Deployer.lookupInConfig("service-pi") + deployment must be ('defined) + deployment must equal (Some(Deploy("service-pi", RoundRobin, Clustered(Home("darkstar", 8888), Replicate(3), Stateless)))) + } + } +} diff --git a/akka-actor/src/main/scala/akka/actor/Deployer.scala b/akka-actor/src/main/scala/akka/actor/Deployer.scala new file mode 100644 index 0000000000..281ba09532 --- /dev/null +++ b/akka-actor/src/main/scala/akka/actor/Deployer.scala @@ -0,0 +1,351 @@ +/** + * Copyright (C) 2009-2011 Scalable Solutions AB + */ + +package akka.actor + +import collection.immutable.Seq + +import java.util.concurrent.ConcurrentHashMap + +import akka.event.EventHandler +import akka.AkkaException +import akka.actor.DeploymentConfig._ +import akka.config.{ConfigurationException, Config} + +/** + * Programatic deployment configuration classes. Most values have defaults and can be left out. + *

+ * Example Scala API: + *

+ *   import akka.actor.DeploymentConfig._
+ *
+ *   val deploymentHello = Deploy("service:hello", Local)
+ *
+ *   val deploymentE     = Deploy("service:e", AutoReplicate, Clustered(Home("darkstar.lan", 7887), Stateful))
+ *
+ *   val deploymentPi1   = Deploy("service:pi", Replicate(3), Clustered(Home("darkstar.lan", 7887), Stateless(RoundRobin)))
+ *
+ *   // same thing as 'deploymentPi1' but more explicit
+ *   val deploymentPi2   =
+ *     Deploy(
+ *       address = "service:pi",
+ *         replicas = 3,
+ *         scope = Clustered(
+ *           home = Home("darkstar.lan", 7887)
+ *           state = Stateless(
+ *             routing = RoundRobin
+ *           )
+ *         )
+ *       )
+ * 
+ * Example Java API: + *
+ *   import static akka.actor.*;
+ *
+ *   val deploymentHello = new Deploy("service:hello", new Local());
+ *
+ *   val deploymentE     = new Deploy("service:e", new AutoReplicate(), new Clustered(new Home("darkstar.lan", 7887), new Stateful()));
+ *
+ *   val deploymentPi1   = new Deploy("service:pi", new Replicate(3), new Clustered(new Home("darkstar.lan", 7887), new Stateless(new RoundRobin())))
+ * 
+ * + * @author Jonas Bonér + */ +object DeploymentConfig { + + // -------------------------------- + // --- Deploy + // -------------------------------- + case class Deploy(address: String, routing: Routing = Direct, scope: Scope = Local) + + // -------------------------------- + // --- Routing + // -------------------------------- + sealed trait Routing + case class CustomRouter(router: AnyRef) extends Routing + + // For Java API + case class Direct() extends Routing + case class RoundRobin() extends Routing + case class Random() extends Routing + case class LeastCPU() extends Routing + case class LeastRAM() extends Routing + case class LeastMessages() extends Routing + + // For Scala API + case object Direct extends Routing + case object RoundRobin extends Routing + case object Random extends Routing + case object LeastCPU extends Routing + case object LeastRAM extends Routing + case object LeastMessages extends Routing + + // -------------------------------- + // --- Scope + // -------------------------------- + sealed trait Scope + case class Clustered( + home: Home = Home("localhost", 2552), + replication: Replication = NoReplicas, + state: State = Stateful) extends Scope + + // For Java API + case class Local() extends Scope + + // For Scala API + case object Local extends Scope + + // -------------------------------- + // --- Home + // -------------------------------- + case class Home(hostname: String, port: Int) + + // -------------------------------- + // --- Replication + // -------------------------------- + sealed trait Replication + class ReplicationBase(factor: Int) extends Replication { + if (factor < 1) throw new IllegalArgumentException("Replication factor can not be negative or zero") + } + case class Replicate(factor: Int) extends ReplicationBase(factor) + + // For Java API + case class AutoReplicate() extends Replication + case class NoReplicas() extends ReplicationBase(1) + + // For Scala API + case object AutoReplicate extends Replication + case object NoReplicas extends ReplicationBase(1) + + // -------------------------------- + // --- State + // -------------------------------- + sealed trait State + + // For Java API + case class Stateless() extends State + case class Stateful() extends State + + // For Scala API + case object Stateless extends State + case object Stateful extends State +} + +/** + * Deployer maps actor deployments to actor addresses. + * + * @author Jonas Bonér + */ +object Deployer { + // FIXME create clustered version of this when we have clustering in place + + private val deployments = new ConcurrentHashMap[String, Deploy] + + def deploy(deployment: Seq[Deploy]) { + deployment foreach (deploy(_)) + } + + def deploy(deployment: Deploy) { + if (deployment eq null) throw new IllegalArgumentException("Deploy can not be null") + val address = deployment.address + Address.validate(address) + + if (deployments.putIfAbsent(address, deployment) != deployment) + throwDeploymentBoundException(deployment) + + deployLocally(deployment) + } + + private def deployLocally(deployment: Deploy) { + deployment match { + case Deploy(address, Direct, Clustered(Home(hostname, port), _, _)) => + val currentRemoteServerAddress = Actor.remote.address + if (currentRemoteServerAddress.getHostName == hostname) { // are we on the right server? + if (currentRemoteServerAddress.getPort != port) throw new ConfigurationException( + "Remote server started on [" + hostname + + "] is started on port [" + currentRemoteServerAddress.getPort + + "] can not use deployment configuration [" + deployment + + "] due to invalid port [" + port + "]") + + // FIXME how to handle registerPerSession +// Actor.remote.register(Actor.newLocalActorRef(address)) + } + + case Deploy(_, routing, Clustered(Home(hostname, port), replicas, state)) => + // FIXME clustered actor deployment + + case _ => // local deployment do nothing + } + } + + /** + * Undeploy is idemponent. E.g. safe to invoke multiple times. + */ + def undeploy(deployment: Deploy) { + deployments.remove(deployment.address) + } + + def undeployAll() { + deployments.clear() + } + + def lookupDeploymentFor(address: String): Option[Deploy] = { + val deployment = deployments.get(address) + if (deployments ne null) Some(deployment) + else { + val deployment = + try { + lookupInConfig(address) + } catch { + case e: ConfigurationException => + EventHandler.error(e, this, e.getMessage) + throw e + } + deployment foreach (deploy(_)) + deployment + } + } + + /** + * Same as 'lookupDeploymentFor' but throws an exception if no deployment is bound. + */ + def deploymentFor(address: String): Deploy = { + lookupDeploymentFor(address) match { + case Some(deployment) => deployment + case None => thrownNoDeploymentBoundException(address) + } + } + + def isLocal(address: String): Boolean = lookupDeploymentFor(address) match { + case Some(Deploy(_, _, Local)) => true + case _ => false + } + + def isClustered(address: String): Boolean = !isLocal(address) + + def lookupInConfig(address: String): Option[Deploy] = { + + // -------------------------------- + // akka.actor.deployment.
+ // -------------------------------- + val addressPath = "akka.actor.deployment." + address + Config.config.getSection(addressPath) match { + case None => Some(Deploy(address, Direct, Local)) + case Some(addressConfig) => + + // -------------------------------- + // akka.actor.deployment.
.router + // -------------------------------- + val router = addressConfig.getString("router", "direct") match { + case "direct" => Direct + case "round-robin" => RoundRobin + case "random" => Random + case "least-cpu" => LeastCPU + case "least-ram" => LeastRAM + case "least-messages" => LeastMessages + case customRouterClassName => + val customRouter = try { + Class.forName(customRouterClassName).newInstance.asInstanceOf[AnyRef] + } catch { + case e => throw new ConfigurationException( + "Config option [" + addressPath + ".router] needs to be one of " + + "[\"direct\", \"round-robin\", \"random\", \"least-cpu\", \"least-ram\", \"least-messages\" or FQN of router class]") + } + CustomRouter(customRouter) + } + + // -------------------------------- + // akka.actor.deployment.
.clustered + // -------------------------------- + addressConfig.getSection("clustered") match { + case None => + Some(Deploy(address, router, Local)) // deploy locally + + case Some(clusteredConfig) => + + // -------------------------------- + // akka.actor.deployment.
.clustered.home + // -------------------------------- + val home = clusteredConfig.getListAny("home") match { + case List(hostname: String, port: String) => + try { + Home(hostname, port.toInt) + } catch { + case e: NumberFormatException => + throw new ConfigurationException( + "Config option [" + addressPath + + ".clustered.home] needs to be an array on format [[\"hostname\", port]] - was [[" + + hostname + ", " + port + "]]") + } + case invalid => throw new ConfigurationException( + "Config option [" + addressPath + + ".clustered.home] needs to be an arrayon format [\"hostname\", port] - was [" + + invalid + "]") + } + + // -------------------------------- + // akka.actor.deployment.
.clustered.replicas + // -------------------------------- + val replicas = clusteredConfig.getAny("replicas", 1) match { + case "auto" => AutoReplicate + case "1" => NoReplicas + case nrOfReplicas: String => + try { + Replicate(nrOfReplicas.toInt) + } catch { + case e: NumberFormatException => + throw new ConfigurationException( + "Config option [" + addressPath + + ".clustered.replicas] needs to be either [\"auto\"] or [1-N] - was [" + + nrOfReplicas + "]") + } + } + + // -------------------------------- + // akka.actor.deployment.
.clustered.stateless + // -------------------------------- + val state = + if (clusteredConfig.getBool("stateless", false)) Stateless + else Stateful + + Some(Deploy(address, router, Clustered(home, replicas, state))) + } + } + } + + private def throwDeploymentBoundException(deployment: Deploy): Nothing = { + val e = new DeploymentBoundException( + "Address [" + deployment.address + + "] already bound to [" + deployment + + "]. You have to invoke 'undeploy(deployment) first.") + EventHandler.error(e, this, e.getMessage) + throw e + } + + private def thrownNoDeploymentBoundException(address: String): Nothing = { + val e = new NoDeploymentBoundException("Address [" + address + "] is not bound to a deployment") + EventHandler.error(e, this, e.getMessage) + throw e + } +} + +/** + * @author Jonas Bonér + */ +object Address { + private val validAddressPattern = java.util.regex.Pattern.compile("[0-9a-zA-Z\\-\\_\\$\\.]+") + + def validate(address: String) { + if (validAddressPattern.matcher(address).matches) true + else { + val e = new IllegalArgumentException("Address [" + address + "] is not valid, need to follow pattern [0-9a-zA-Z\\-\\_\\$]+") + EventHandler.error(e, this, e.getMessage) + throw e + } + } +} + +class DeploymentBoundException private[akka](message: String) extends AkkaException(message) +class NoDeploymentBoundException private[akka](message: String) extends AkkaException(message) +