diff --git a/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala index f50b9bd381..770a6d0dc3 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/TypedActorSpec.scala @@ -98,7 +98,7 @@ object TypedActorSpec { } def futureComposePigdogFrom(foo: Foo): Future[String] = { - implicit val timeout = TypedActor.system.settings.ActorTimeout + implicit val timeout = TypedActor.context.system.settings.ActorTimeout foo.futurePigdog(500).map(_.toUpperCase) } diff --git a/akka-actor/src/main/scala/akka/actor/TypedActor.scala b/akka-actor/src/main/scala/akka/actor/TypedActor.scala index 413d30482e..cc6d30f678 100644 --- a/akka-actor/src/main/scala/akka/actor/TypedActor.scala +++ b/akka-actor/src/main/scala/akka/actor/TypedActor.scala @@ -229,7 +229,7 @@ object TypedActor extends ExtensionId[TypedActorExtension] with ExtensionIdProvi } private val selfReference = new ThreadLocal[AnyRef] - private val currentSystem = new ThreadLocal[ActorSystem] + private val currentContext = new ThreadLocal[ActorContext] /** * Returns the reference to the proxy when called inside a method call in a TypedActor @@ -255,23 +255,30 @@ object TypedActor extends ExtensionId[TypedActorExtension] with ExtensionIdProvi } /** - * Returns the akka system (for a TypedActor) when inside a method call in a TypedActor. + * Returns the ActorContext (for a TypedActor) when inside a method call in a TypedActor. */ - def system = currentSystem.get match { - case null ⇒ throw new IllegalStateException("Calling TypedActor.system outside of a TypedActor implementation method!") + def context = currentContext.get match { + case null ⇒ throw new IllegalStateException("Calling TypedActor.context outside of a TypedActor implementation method!") case some ⇒ some } /** * Returns the default dispatcher (for a TypedActor) when inside a method call in a TypedActor. */ - implicit def dispatcher = system.dispatcher + implicit def dispatcher = context.dispatcher /** * Implementation of TypedActor as an Actor */ private[akka] class TypedActor[R <: AnyRef, T <: R](val proxyVar: AtomVar[R], createInstance: ⇒ T) extends Actor { - val me = createInstance + val me = try { + TypedActor.selfReference set proxyVar.get + TypedActor.currentContext set context + createInstance + } finally { + TypedActor.selfReference set null + TypedActor.currentContext set null + } override def preStart(): Unit = me match { case l: PreStart ⇒ l.preStart() @@ -305,7 +312,7 @@ object TypedActor extends ExtensionId[TypedActorExtension] with ExtensionIdProvi def receive = { case m: MethodCall ⇒ TypedActor.selfReference set proxyVar.get - TypedActor.currentSystem set context.system + TypedActor.currentContext set context try { if (m.isOneWay) m(me) else { @@ -325,7 +332,7 @@ object TypedActor extends ExtensionId[TypedActorExtension] with ExtensionIdProvi } } finally { TypedActor.selfReference set null - TypedActor.currentSystem set null + TypedActor.currentContext set null } } } diff --git a/akka-docs/scala/code/TypedActorDocSpec.scala b/akka-docs/scala/code/TypedActorDocSpec.scala index 0f8ef33efa..c8bc976342 100644 --- a/akka-docs/scala/code/TypedActorDocSpec.scala +++ b/akka-docs/scala/code/TypedActorDocSpec.scala @@ -1,9 +1,10 @@ package akka.docs.actor //#imports -import akka.actor.{ TypedActor, Props } import akka.dispatch.{ Promise, Future, Await } import akka.util.duration._ +import akka.actor.{ ActorContext, TypedActor, Props } + //#imports import org.scalatest.{ BeforeAndAfterAll, WordSpec } @@ -12,6 +13,7 @@ import akka.testkit._ //#typed-actor-iface trait Squarer { + //#typed-actor-iface-methods def squareDontCare(i: Int): Unit //fire-forget def square(i: Int): Future[Int] //non-blocking send-request-reply @@ -19,11 +21,15 @@ trait Squarer { def squareNowPlease(i: Int): Option[Int] //blocking send-request-reply def squareNow(i: Int): Int //blocking send-request-reply + //#typed-actor-iface-methods } //#typed-actor-iface //#typed-actor-impl -class SquarerImpl extends Squarer { +class SquarerImpl(val name: String) extends Squarer { + + def this() = this("default") + //#typed-actor-impl-methods import TypedActor.dispatcher //So we can create Promises def squareDontCare(i: Int): Unit = i * i //Nobody cares :( @@ -33,25 +39,90 @@ class SquarerImpl extends Squarer { def squareNowPlease(i: Int): Option[Int] = Some(i * i) def squareNow(i: Int): Int = i * i + //#typed-actor-impl-methods } //#typed-actor-impl +//#typed-actor-supercharge +trait Foo { + def doFoo(times: Int): Unit = println("doFoo(" + times + ")") +} + +trait Bar { + import TypedActor.dispatcher //So we have an implicit dispatcher for our Promise + def doBar(str: String): Future[String] = Promise successful str.toUpperCase +} + +class FooBar extends Foo with Bar +//#typed-actor-supercharge + class TypedActorDocSpec extends AkkaSpec(Map("akka.loglevel" -> "INFO")) { - "create a typed actor" in { - //#typed-actor-create + "get the TypedActor extension" in { + val someReference: AnyRef = null - val mySquarer = TypedActor(system).typedActorOf[Squarer, SquarerImpl]() - //#typed-actor-create + try { + //#typed-actor-extension-tools + + import akka.actor.TypedActor + + //Returns the Typed Actor Extension + val extension = TypedActor(system) //system is an instance of ActorSystem + + //Returns whether the reference is a Typed Actor Proxy or not + TypedActor(system).isTypedActor(someReference) + + //Returns the backing Akka Actor behind an external Typed Actor Proxy + TypedActor(system).getActorRefFor(someReference) + + //Returns the current ActorContext, + // method only valid within methods of a TypedActor implementation + val c: ActorContext = TypedActor.context + + //Returns the external proxy of the current Typed Actor, + // method only valid within methods of a TypedActor implementation + val s: Squarer = TypedActor.self[Squarer] + + //Returns a contextual instance of the Typed Actor Extension + //this means that if you create other Typed Actors with this, + //they will become children to the current Typed Actor. + TypedActor(TypedActor.context) + + //#typed-actor-extension-tools + } catch { + case e: Exception ⇒ //dun care + } + } + + "create a typed actor" in { + //#typed-actor-create1 + val mySquarer: Squarer = + TypedActor(system).typedActorOf[Squarer, SquarerImpl]() + //#typed-actor-create1 + //#typed-actor-create2 + val otherSquarer: Squarer = + TypedActor(system).typedActorOf(classOf[Squarer], + new SquarerImpl("foo"), + Props(), + "name") + //#typed-actor-create2 //#typed-actor-calls + //#typed-actor-call-oneway mySquarer.squareDontCare(10) + //#typed-actor-call-oneway + //#typed-actor-call-future val fSquare = mySquarer.square(10) //A Future[Int] + //#typed-actor-call-future + //#typed-actor-call-option val oSquare = mySquarer.squareNowPlease(10) //Option[Int] + //#typed-actor-call-option + //#typed-actor-call-strict val iSquare = mySquarer.squareNow(10) //Int + //#typed-actor-call-strict //#typed-actor-calls Await.result(fSquare, 3 seconds) must be === 100 @@ -60,6 +131,24 @@ class TypedActorDocSpec extends AkkaSpec(Map("akka.loglevel" -> "INFO")) { iSquare must be === 100 + //#typed-actor-stop TypedActor(system).stop(mySquarer) + //#typed-actor-stop + + //#typed-actor-poisonpill + TypedActor(system).poisonPill(otherSquarer) + //#typed-actor-poisonpill + } + + "supercharge" in { + //#typed-actor-supercharge-usage + val awesomeFooBar = TypedActor(system).typedActorOf[Foo with Bar, FooBar]() + + awesomeFooBar.doFoo(10) + val f = awesomeFooBar.doBar("yes") + + TypedActor(system).poisonPill(awesomeFooBar) + //#typed-actor-supercharge-usage + Await.result(f, 3 seconds) must be === "YES" } } diff --git a/akka-docs/scala/typed-actors.rst b/akka-docs/scala/typed-actors.rst index 6f845a0601..9d7998abd3 100644 --- a/akka-docs/scala/typed-actors.rst +++ b/akka-docs/scala/typed-actors.rst @@ -15,52 +15,149 @@ The advantage of Typed Actors vs. Actors is that with TypedActors you have a sta Typed Actors are implemented using `JDK Proxies `_ which provide a pretty easy-worked API to intercept method calls. -Typed Actor is an Akka Extension and could as such be implemented as a User Level API, but is provided in the core Akka package. + +The tools of the trade +---------------------- + +Before we create our first Typed Actor we should first go through the tools that we have at our disposal, +it's located in ``akka.actor.TypedActor``. + +.. includecode:: code/TypedActorDocSpec.scala + :include: typed-actor-extension-tools + +.. warning:: + + Same as not exposing ``this`` of an Akka Actor, it's important not to expose ``this`` of a Typed Actor, + instead you should pass the external proxy reference, which is obtained from within your Typed Actor as + ``TypedActor.self``, this is your external identity, as the ``ActorRef`` is the external identity of + and Akka Actor. + +Creating Typed Actors +--------------------- + +To create a Typed Actor you need to have one or more interfaces, and one implementation. + +Our example interface: + +.. includecode:: code/TypedActorDocSpec.scala + :include: imports,typed-actor-iface + :exclude: typed-actor-iface-methods + +Our example implementation of that interface: + +.. includecode:: code/TypedActorDocSpec.scala + :include: imports,typed-actor-impl + :exclude: typed-actor-impl-methods + +The most trivial way of creating a Typed Actor instance +of our Squarer: + +.. includecode:: code/TypedActorDocSpec.scala + :include: typed-actor-create1 + +First type is the type of the proxy, the second type is the type of the implementation. +If you need to call a specific constructor you do it like this: + +.. includecode:: code/TypedActorDocSpec.scala + :include: typed-actor-create2 + +Since you supply a Props, you can specify which dispatcher to use, what the default timeout should be used and more. +Now, our Squarer doesn't have any methods, so we'd better add those. + +.. includecode:: code/TypedActorDocSpec.scala + :include: imports,typed-actor-iface + +Alright, now we've got some methods we can call, but we need to implement those in SquarerImpl. + +.. includecode:: code/TypedActorDocSpec.scala + :include: imports,typed-actor-impl + +Alright, now we have an interface and an implementation of that interface, +and we know how to create a Typed Actor from that, so let's look at calling these methods. Method dispatch semantics ------------------------- Methods returning: - * ``Unit`` will be dispatched with ``fire-and-forget`` semantics, exactly like :meth:``Actor.tell`` - * ``akka.dispatch.Future[_]`` will use ``send-request-reply`` semantics, exactly like :meth:``Actor.ask`` + * ``Unit`` will be dispatched with ``fire-and-forget`` semantics, exactly like ``Actor.tell`` + * ``akka.dispatch.Future[_]`` will use ``send-request-reply`` semantics, exactly like ``Actor.ask`` * ``scala.Option[_]`` or akka.japi.Option[_] will use ``send-request-reply`` semantics, but _will_ block to wait for an answer, and return None if no answer was produced within the timout, or scala.Some/akka.japi.Some containing the result otherwise. Any exception that was thrown during this call will be rethrown. * Any other type of value will use ``send-request-reply`` semantics, but _will_ block to wait for an answer, throwing ``java.util.concurrent.TimeoutException`` if there was a timeout or rethrow any exception that was thrown during this call. -Creating Typed Actors ---------------------- +Messages and immutability +------------------------- -To create a Typed Actor you need to have one or more interfaces, and one implementation: - -.. includecode:: code/TypedActorDocSpec.scala - :include: imports,typed-actor-create - -If you need to call a specific constructor you do it like this: - -INSERT EXAMPLE HERE - -Since you supply a Props, you can specify which dispatcher to use etc. - -Sending messages ----------------- +While Akka cannot enforce that the parameters to the methods of your Typed Actors are immutable, +we *strongly* recommend that parameters passed are immutable. One-way message send ^^^^^^^^^^^^^^^^^^^^ +.. includecode:: code/TypedActorDocSpec.scala + :include: typed-actor-call-oneway + +As simple as that! The method will be executed on another thread; asynchronously. + Request-reply message send ^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. includecode:: code/TypedActorDocSpec.scala + :include: typed-actor-call-option + +This will block for as long as the timeout that was set in the Props of the Typed Actor, +if needed. It will return ``None`` if a timeout occurs. + +.. includecode:: code/TypedActorDocSpec.scala + :include: typed-actor-call-strict + +This will block for as long as the timeout that was set in the Props of the Typed Actor, +if needed. It will throw a ``java.util.concurrent.TimeoutException`` if a timeout occurs. + Request-reply-with-future message send ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. includecode:: code/TypedActorDocSpec.scala + :include: typed-actor-call-future + +This call is asynchronous, and the Future returned can be used for asynchronous composition. + Stopping Typed Actors --------------------- +Since Akkas Typed Actors are backed by Akka Actors they must be stopped when they aren't needed anymore. + +.. includecode:: code/TypedActorDocSpec.scala + :include: typed-actor-stop + +This asynchronously stops the Typed Actor associated with the specified proxy ASAP. + +.. includecode:: code/TypedActorDocSpec.scala + :include: typed-actor-poisonpill + +This asynchronously stops the Typed Actor associated with the specified proxy +after it's done with all calls that were made prior to this call. + +Typed Actor Hierarchies +----------------------- + +Since you can obtain a contextual Typed Actor Extension by passing in an ``ActorContext`` +you can create child Typed Actors by invoking ``typedActorOf(..)`` on that. + +This also works for creating child Typed Actors in regular Akka Actors. + Lifecycle callbacks ------------------- -Messages and immutability -------------------------- \ No newline at end of file +By having your Typed Actor implementation class implement ``TypedActor.PreStart``, ``TypedActor.PostStop``, ``TypedActor.PreRestart`` and/or ``TypedActor.PostRestart`` you can hook into the lifecyle of your Typed Actor. + +Supercharging +------------- + +Here's an example on how you can use traits to mix in behavior in your Typed Actors. + +.. includecode:: code/TypedActorDocSpec.scala + :include: typed-actor-supercharge,typed-actor-supercharge-usage \ No newline at end of file