J Pullar
J Pullar

Reputation: 1935

Scala how to use akka actors to handle a timing out operation efficiently

I am currently evaluating javascript scripts using Rhino in a restful service. I wish for there to be an evaluation time out. I have created a mock example actor (using scala 2.10 akka actors).

case class Evaluate(expression: String)

class RhinoActor extends Actor {

  override def preStart() = { println("Start context'"); super.preStart()}

  def receive = {
    case Evaluate(expression) ⇒ {
      Thread.sleep(100)
      sender ! "complete"
    }
  }

  override def postStop() = { println("Stop context'"); super.postStop()}
}

Now I run use this actor as follows:

  def run {
    val t = System.currentTimeMillis()
    val system = ActorSystem("MySystem")

    val actor = system.actorOf(Props[RhinoActor])

    implicit val timeout = Timeout(50 milliseconds)
    val future = (actor ? Evaluate("10 + 50")).mapTo[String]

    val result = Try(Await.result(future, Duration.Inf))

    println(System.currentTimeMillis() - t)
    println(result)
    actor ! PoisonPill

    system.shutdown()
  }

Is it wise to use the ActorSystem in a closure like this which may have simultaneous requests on it?

Should I make the ActorSystem global, and will that be ok in this context?

Is there a more appropriate alternative approach?

EDIT: I think I need to use futures directly, but I will need the preStart and postStop. Currently investigating. EDIT: Seems you don't get those hooks with futures.

Upvotes: 0

Views: 1868

Answers (1)

cmbaxter
cmbaxter

Reputation: 35443

I'll try and answer some of your questions for you.

First, an ActorSystem is a very heavy weight construct. You should not create one per request that needs an actor. You should create one globally and then use that single instance to spawn your actors (and you won't need system.shutdown() anymore in run). I believe this covers your first two questions.

Your approach of using an actor to execute javascript here seems sound to me. But instead of spinning up an actor per request, you might want to pool a bunch of the RhinoActors behind a Router, with each instance having it's own rhino engine that will be setup during preStart. Doing this will eliminate per request rhino initialization costs, speeding up your js evaluations. Just make sure you size your pool appropriately. Also, you won't need to be sending PoisonPill messages per request if you adopt this approach.

You also might want to look into the non-blocking callbacks onComplete, onSuccess and onFailure as opposed to using the blocking Await. These callbacks also respect timeouts and are preferable to blocking for higher throughput. As long as whatever is way way upstream waiting for this response can handle the asynchronicity (i.e. an async capable web request), then I suggest going this route.

The last thing to keep in mind is that even though code will return to the caller after the timeout if the actor has yet to respond, the actor still goes on processing that message (performing the evaluation). It does not stop and move onto the next message just because a caller timed out. Just wanted to make that clear in case it wasn't.

EDIT

In response to your comment about stopping a long execution there are some things related to Akka to consider first. You can call stop the actor, send a Kill or a PosionPill, but none of these will stop if from processing the message that it's currently processing. They just prevent it from receiving new messages. In your case, with Rhino, if infinite script execution is a possibility, then I suggest handling this within Rhino itself. I would dig into the answers on this post (Stopping the Rhino Engine in middle of execution) and setup your Rhino engine in the actor in such a way that it will stop itself if it has been executing for too long. That failure will kick out to the supervisor (if pooled) and cause that pooled instance to be restarted which will init a new Rhino in preStart. This might be the best approach for dealing with the possibility of long running scripts.

Upvotes: 2

Related Questions