Package

org.querki

requester

Permalink

package requester

Visibility
  1. Public
  2. All

Type Members

  1. class RequestM[T] extends AnyRef

    Permalink

    The request "monad".

    The request "monad". It's actually a bit nasty, in that it's mutable, but by and large this behaves the way you expect a monad to work. In particular, it works with for comprehensions, allowing you to compose requests much the way you normally do Futures. But since it is mutable, it should never be used outside the context of an Actor.

  2. trait Requester extends Actor with RequesterImplicits

    Permalink

    Easy and relatively safe variant of "ask".

    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.

  3. trait RequesterImplicits extends AnyRef

    Permalink

    Implicit that hooks into *other* Actors, to provide the nice request() syntax to send messages to them.

    Implicit that hooks into *other* Actors, to provide the nice request() syntax to send messages to them. These implicits are available to any Actor that mixes in Requester, but RequesterImplicits should also be mixed into any other class that wants access to this capability. Those other classes must have access to a Requester -- usually, they should be functional classes owned *by* a Requester.

    This trait gives you the functions that you actually call directly -- request() and requestFor(). But those calls mostly create RequestM objects, and the actual work gets done by the associated Requester.

Value Members

  1. object RequestM

    Permalink

Ungrouped