trait Requester extends Actor with RequesterImplicits
Easy and relatively safe variant of "ask".
The idea here is that it would be lovely to have a replacement for the "ask" pattern. Ask is powerful, but quite dangerous -- in particular, handling the response in the most obvious way, using the Future's completion callbacks, is a fine way to break your Actor's threading and cause strange timing bugs.
So the idea here is to build something with similar semantics to ask, but deliberately a bit dumbed-down and safer for routine use. Where ask returns a Future that you can then put callbacks on, request() takes those callbacks as parameters, and runs them *in this Actor's main thread*.
In other words, I want to be able to say something like:
def receive = { ... case MyMessage(a, b) => { otherActor.request(MyRequest(b)).foreach { case OtherResponse(c) => ... } } }
While OtherResponse is lexically part of MyRequest, it actually *runs* during receive, just like any other incoming message, so it isn't prone to the threading problems that ask is.
How does this work? Under the hood, it actually does use ask, but in a very specific and constrained way. We send the message off using ask, and then hook the resulting Future. When the Future completes, we wrap the response and the handler together in a RequestedResponse message, and loop that back around as a message to the local Actor.
Note that the original sender is preserved, so the callback can use it without problems. (This is the most common error made when using ask, and was one of the motivations for creating Requester.)
Note that, to make this work, the Request trait mixes in its own version of unhandled(). For this to work properly, therefore, it is very important that, if your own Actor overrides unhandled, it calls super.unhandled() for unknown messages!
That unhandled() override is usually enough to catch the looped-back messages, so you usually just need to mix Requester into your Actor. However, if your Actor's receive function is intercepting *all* messages (so nothing makes it to unhandled), then you will need to call handleRequestResponse at the beginning of your receive; otherwise, your Actor can wind up deadlocked. This can particularly happen when using stash() during Actor startup:
def receive = handleRequestResponse orElse { case Start => { persistence.request(LoadMe(myId)) foreach { myState => setState(myState) unstashAll() become(mainReceive) } } case _ => stash() }
In this startup pattern, we are stashing all messages until the persister responds with the Actor's state. However, if we didn't have handleRequestResponse there, the response would also get stashed, so the Actor would never receive the state message, and the Actor would be stuck.
IMPORTANT: Requester is *not* compatible with stateful versions of become() -- that is, if you are using become() in a method where you are capturing the parameters in the closure of become(), Requester will probably not work right. This is because the body of the response handler will capture the closed-over parameter, and if the Actor has become() something else in the meantime, the handler will use the *old* data, not the new.
More generally, Requester should be used with caution if your Actor changes state frequently. While it *can* theoretically be used with FSM, it may not be wise to do so, since the state machine may no longer be in a compatible state by the time the response is received. Requester is mainly intended for Actors that spend most or all of their time in a single state; it generally works quite well for those.
- Alphabetic
- By Inheritance
- Requester
- RequesterImplicits
- Actor
- AnyRef
- Any
- Hide All
- Show All
- Public
- All
Type Members
-
type
Receive = PartialFunction[Any, Unit]
- Definition Classes
- Actor
-
case class
RequestedResponse[T](response: Try[T], handler: RequestM[T]) extends Product with Serializable
The response from request() will be wrapped up in here and looped around.
The response from request() will be wrapped up in here and looped around. You shouldn't need to use this directly.
-
implicit
class
RequestableActorRef extends AnyRef
Hook to add the request() methods to a third-party Actor.
Hook to add the request() methods to a third-party Actor.
- Definition Classes
- RequesterImplicits
-
implicit
class
RequestableActorSelection extends AnyRef
Similar to RequestableActorRef, but works with an ActorSelection.
Similar to RequestableActorRef, but works with an ActorSelection.
- Definition Classes
- RequesterImplicits
Abstract Value Members
-
abstract
def
receive: akka.actor.Actor.Receive
- Definition Classes
- Actor
Concrete Value Members
-
final
def
!=(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
-
final
def
##(): Int
- Definition Classes
- AnyRef → Any
-
final
def
==(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
-
def
aroundPostRestart(reason: Throwable): Unit
- Attributes
- protected[akka]
- Definition Classes
- Actor
- Annotations
- @InternalApi()
-
def
aroundPostStop(): Unit
- Attributes
- protected[akka]
- Definition Classes
- Actor
- Annotations
- @InternalApi()
-
def
aroundPreRestart(reason: Throwable, message: Option[Any]): Unit
- Attributes
- protected[akka]
- Definition Classes
- Actor
- Annotations
- @InternalApi()
-
def
aroundPreStart(): Unit
- Attributes
- protected[akka]
- Definition Classes
- Actor
- Annotations
- @InternalApi()
-
def
aroundReceive(receive: akka.actor.Actor.Receive, msg: Any): Unit
- Attributes
- protected[akka]
- Definition Classes
- Actor
- Annotations
- @InternalApi()
-
final
def
asInstanceOf[T0]: T0
- Definition Classes
- Any
-
def
clone(): AnyRef
- Attributes
- protected[java.lang]
- Definition Classes
- AnyRef
- Annotations
- @native() @throws( ... )
-
implicit
val
context: ActorContext
- Definition Classes
- Actor
-
def
doRequest[T](otherActor: ActorRef, msg: Any, handler: RequestM[T])(implicit tag: ClassTag[T]): Unit
Send a request, and specify the handler for the received response.
Send a request, and specify the handler for the received response. You may also specify a failHandler, which will be run if the operation fails for some reason. (Most often, because we didn't receive a response within the timeout window.)
- def doRequestGuts[T](f: Future[Any], handler: RequestM[T])(implicit tag: ClassTag[T]): Unit
-
final
def
eq(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
-
def
equals(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
-
def
finalize(): Unit
- Attributes
- protected[java.lang]
- Definition Classes
- AnyRef
- Annotations
- @throws( classOf[java.lang.Throwable] )
-
final
def
getClass(): Class[_]
- Definition Classes
- AnyRef → Any
- Annotations
- @native()
-
def
handleRequestResponse: Receive
Normally you don't need to invoke this manually -- Requester defines an unhandled() override that deals with these responses.
Normally you don't need to invoke this manually -- Requester defines an unhandled() override that deals with these responses. But if your receive method intercepts *all* messages for some reason (for example, it stashes everything), then you should add this at the front of your receive so that it deals with responses.
-
def
hashCode(): Int
- Definition Classes
- AnyRef → Any
- Annotations
- @native()
-
final
def
isInstanceOf[T0]: Boolean
- Definition Classes
- Any
-
implicit
def
loopback[T](f: Future[T])(implicit tag: ClassTag[T], enclosing: FullName, file: File, line: Line): RequestM[T]
Convert a Future into a Request.
Convert a Future into a Request.
This takes the specified Future, and runs it in the Requester's main loop, to make it properly safe. As usual, sender will be preserved.
This is implicit, so if you are in a context that already expects a Request (such as a for comprehension with a Request at the top), it will quietly turn the Future into a Request. If Request isn't already expected, though, you'll have to specify loopback explicitly.
- Definition Classes
- RequesterImplicits
-
final
def
ne(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
-
final
def
notify(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native()
-
final
def
notifyAll(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native()
-
def
postRestart(reason: Throwable): Unit
- Definition Classes
- Actor
- Annotations
- @throws( classOf[java.lang.Exception] )
-
def
postStop(): Unit
- Definition Classes
- Actor
- Annotations
- @throws( classOf[java.lang.Exception] )
-
def
preRestart(reason: Throwable, message: Option[Any]): Unit
- Definition Classes
- Actor
- Annotations
- @throws( classOf[java.lang.Exception] )
-
def
preStart(): Unit
- Definition Classes
- Actor
- Annotations
- @throws( classOf[java.lang.Exception] )
-
implicit
def
request2Future[T](req: RequestM[T]): Future[T]
Convert a Request into a Future.
Convert a Request into a Future.
Sometimes, at the edges of the API, you need to think in terms of Futures. When this is necessary, this implicit will take your RequestM and turn it into a Future of the matching type.
- Definition Classes
- RequesterImplicits
-
implicit
val
requestTimeout: Timeout
Override this to specify the timeout for requests
Override this to specify the timeout for requests
TODO: this is suspicious, since it does not follow Akka's preferred pattern for timeouts. We might change how this works.
-
val
requester: Requester
The actual Requester that is going to send the requests and process the responses.
The actual Requester that is going to send the requests and process the responses. If you mix RequesterImplicits into a non-Requester, this must point to that Actor, which does all the real work. (If you are using this from within Requester, it's already set.)
- Definition Classes
- Requester → RequesterImplicits
-
implicit final
val
self: ActorRef
- Definition Classes
- Actor
-
final
def
sender(): ActorRef
- Definition Classes
- Actor
-
def
supervisorStrategy: SupervisorStrategy
- Definition Classes
- Actor
-
final
def
synchronized[T0](arg0: ⇒ T0): T0
- Definition Classes
- AnyRef
-
def
toString(): String
- Definition Classes
- AnyRef → Any
-
def
unhandled(message: Any): Unit
- Definition Classes
- Requester → Actor
-
final
def
wait(): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws( ... )
-
final
def
wait(arg0: Long, arg1: Int): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws( ... )
-
final
def
wait(arg0: Long): Unit
- Definition Classes
- AnyRef
- Annotations
- @native() @throws( ... )