Removing conflicting versions of typedActorOf and added Scala docs for TypedActor
This commit is contained in:
parent
0c44258050
commit
77e5596ad6
4 changed files with 228 additions and 35 deletions
|
|
@ -98,7 +98,7 @@ object TypedActorSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
def futureComposePigdogFrom(foo: Foo): Future[String] = {
|
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)
|
foo.futurePigdog(500).map(_.toUpperCase)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@ object TypedActor extends ExtensionId[TypedActorExtension] with ExtensionIdProvi
|
||||||
}
|
}
|
||||||
|
|
||||||
private val selfReference = new ThreadLocal[AnyRef]
|
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
|
* 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 {
|
def context = currentContext.get match {
|
||||||
case null ⇒ throw new IllegalStateException("Calling TypedActor.system outside of a TypedActor implementation method!")
|
case null ⇒ throw new IllegalStateException("Calling TypedActor.context outside of a TypedActor implementation method!")
|
||||||
case some ⇒ some
|
case some ⇒ some
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the default dispatcher (for a TypedActor) when inside a method call in a TypedActor.
|
* 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
|
* Implementation of TypedActor as an Actor
|
||||||
*/
|
*/
|
||||||
private[akka] class TypedActor[R <: AnyRef, T <: R](val proxyVar: AtomVar[R], createInstance: ⇒ T) extends 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 {
|
override def preStart(): Unit = me match {
|
||||||
case l: PreStart ⇒ l.preStart()
|
case l: PreStart ⇒ l.preStart()
|
||||||
|
|
@ -305,7 +312,7 @@ object TypedActor extends ExtensionId[TypedActorExtension] with ExtensionIdProvi
|
||||||
def receive = {
|
def receive = {
|
||||||
case m: MethodCall ⇒
|
case m: MethodCall ⇒
|
||||||
TypedActor.selfReference set proxyVar.get
|
TypedActor.selfReference set proxyVar.get
|
||||||
TypedActor.currentSystem set context.system
|
TypedActor.currentContext set context
|
||||||
try {
|
try {
|
||||||
if (m.isOneWay) m(me)
|
if (m.isOneWay) m(me)
|
||||||
else {
|
else {
|
||||||
|
|
@ -325,7 +332,7 @@ object TypedActor extends ExtensionId[TypedActorExtension] with ExtensionIdProvi
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
TypedActor.selfReference set null
|
TypedActor.selfReference set null
|
||||||
TypedActor.currentSystem set null
|
TypedActor.currentContext set null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package akka.docs.actor
|
package akka.docs.actor
|
||||||
|
|
||||||
//#imports
|
//#imports
|
||||||
import akka.actor.{ TypedActor, Props }
|
|
||||||
import akka.dispatch.{ Promise, Future, Await }
|
import akka.dispatch.{ Promise, Future, Await }
|
||||||
import akka.util.duration._
|
import akka.util.duration._
|
||||||
|
import akka.actor.{ ActorContext, TypedActor, Props }
|
||||||
|
|
||||||
//#imports
|
//#imports
|
||||||
|
|
||||||
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
|
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
|
||||||
|
|
@ -12,6 +13,7 @@ import akka.testkit._
|
||||||
|
|
||||||
//#typed-actor-iface
|
//#typed-actor-iface
|
||||||
trait Squarer {
|
trait Squarer {
|
||||||
|
//#typed-actor-iface-methods
|
||||||
def squareDontCare(i: Int): Unit //fire-forget
|
def squareDontCare(i: Int): Unit //fire-forget
|
||||||
|
|
||||||
def square(i: Int): Future[Int] //non-blocking send-request-reply
|
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 squareNowPlease(i: Int): Option[Int] //blocking send-request-reply
|
||||||
|
|
||||||
def squareNow(i: Int): Int //blocking send-request-reply
|
def squareNow(i: Int): Int //blocking send-request-reply
|
||||||
|
//#typed-actor-iface-methods
|
||||||
}
|
}
|
||||||
//#typed-actor-iface
|
//#typed-actor-iface
|
||||||
|
|
||||||
//#typed-actor-impl
|
//#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
|
import TypedActor.dispatcher //So we can create Promises
|
||||||
|
|
||||||
def squareDontCare(i: Int): Unit = i * i //Nobody cares :(
|
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 squareNowPlease(i: Int): Option[Int] = Some(i * i)
|
||||||
|
|
||||||
def squareNow(i: Int): Int = i * i
|
def squareNow(i: Int): Int = i * i
|
||||||
|
//#typed-actor-impl-methods
|
||||||
}
|
}
|
||||||
//#typed-actor-impl
|
//#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")) {
|
class TypedActorDocSpec extends AkkaSpec(Map("akka.loglevel" -> "INFO")) {
|
||||||
|
|
||||||
"create a typed actor" in {
|
"get the TypedActor extension" in {
|
||||||
//#typed-actor-create
|
val someReference: AnyRef = null
|
||||||
|
|
||||||
val mySquarer = TypedActor(system).typedActorOf[Squarer, SquarerImpl]()
|
try {
|
||||||
//#typed-actor-create
|
//#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-calls
|
||||||
|
//#typed-actor-call-oneway
|
||||||
mySquarer.squareDontCare(10)
|
mySquarer.squareDontCare(10)
|
||||||
|
//#typed-actor-call-oneway
|
||||||
|
|
||||||
|
//#typed-actor-call-future
|
||||||
val fSquare = mySquarer.square(10) //A Future[Int]
|
val fSquare = mySquarer.square(10) //A Future[Int]
|
||||||
|
//#typed-actor-call-future
|
||||||
|
|
||||||
|
//#typed-actor-call-option
|
||||||
val oSquare = mySquarer.squareNowPlease(10) //Option[Int]
|
val oSquare = mySquarer.squareNowPlease(10) //Option[Int]
|
||||||
|
//#typed-actor-call-option
|
||||||
|
|
||||||
|
//#typed-actor-call-strict
|
||||||
val iSquare = mySquarer.squareNow(10) //Int
|
val iSquare = mySquarer.squareNow(10) //Int
|
||||||
|
//#typed-actor-call-strict
|
||||||
//#typed-actor-calls
|
//#typed-actor-calls
|
||||||
|
|
||||||
Await.result(fSquare, 3 seconds) must be === 100
|
Await.result(fSquare, 3 seconds) must be === 100
|
||||||
|
|
@ -60,6 +131,24 @@ class TypedActorDocSpec extends AkkaSpec(Map("akka.loglevel" -> "INFO")) {
|
||||||
|
|
||||||
iSquare must be === 100
|
iSquare must be === 100
|
||||||
|
|
||||||
|
//#typed-actor-stop
|
||||||
TypedActor(system).stop(mySquarer)
|
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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 <http://docs.oracle.com/javase/6/docs/api/java/lang/reflect/Proxy.html>`_ which provide a pretty easy-worked API to intercept method calls.
|
Typed Actors are implemented using `JDK Proxies <http://docs.oracle.com/javase/6/docs/api/java/lang/reflect/Proxy.html>`_ 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
|
Method dispatch semantics
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
Methods returning:
|
Methods returning:
|
||||||
|
|
||||||
* ``Unit`` will be dispatched with ``fire-and-forget`` semantics, exactly like :meth:``Actor.tell``
|
* ``Unit`` will be dispatched with ``fire-and-forget`` semantics, exactly like ``Actor.tell``
|
||||||
* ``akka.dispatch.Future[_]`` will use ``send-request-reply`` semantics, exactly like :meth:``Actor.ask``
|
* ``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,
|
* ``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.
|
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 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,
|
* 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.
|
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:
|
While Akka cannot enforce that the parameters to the methods of your Typed Actors are immutable,
|
||||||
|
we *strongly* recommend that parameters passed are immutable.
|
||||||
.. 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
|
|
||||||
----------------
|
|
||||||
|
|
||||||
One-way message send
|
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
|
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
|
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
|
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
|
Lifecycle callbacks
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
Messages and immutability
|
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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue