Skip to content
laforge49 edited this page Jul 30, 2011 · 4 revisions

Control flow in an actor can be quite unpredictable. Consider three actors, User, Cache and Reader, where User and Cache are using the same mailbox and Reader has its own mailbox. Requests sent by User to Cache will be synchronous and normally the response by Cache will be synchronous as well. So normally the User's request is executed immediately. But when Cache needs to get data from Reader, the request is sent by Cache to Reader asynchronously and the response is not immediately received by Cache. When this happens, the response sent back by Cache to User is returned asynchronously. This means that there is no way for User to predict how the responses will be returned by Reader--mostly they will be returned synchronously but sometimes they will be returned asynchronously. So how do you handle exceptions?

Actors provide an exception handling facility to make this easy. Normally you do not need exception handling except on a few of the higher-level methods. So exception handling is enabled for individual message types. One advantage of this is that different exception handlers can be supplied for different types of messages. The syntax for this is a bit of a mess, but it is consistent--so it is just a matter of learning the pattern and applying it. Lets look at a simple actor that does this.

class S extends Actor {
  bind(classOf[SE], se)
  private def se(msg: AnyRef, rf: Any => Unit) {
    throw new IllegalStateException
  }
  bind(classOf[SNE], {(msg, rf) =>
    exceptionHandler(msg, rf, se){ex =>
      println("S got exception "+ex.toString)
      rf(null)
    }
  })
}

This actor processes two types of messages, SE and SNE. Both of these messages are processed by the se function, which immediately throws an exception.

When an actor sends a SE message to actor S, the sending actor receives an exception. The sending actor can process that exception if it is using an exception handler. Otherwise the exception is passed up to the actor which sent it a message, recursively. And the behavior is the same for both synchronous and asynchronous processing. But when an actor sends a SNE message to actor S, it receives a null response. This is because the SNE message type is bound to an exception handler which traps the exception.

Looking a little closer at how the exception handler is implemented, we see that the exceptionHandler function is used, together with an anonymous function which implements the exception handler logic. Here then is the signature of exceptionHandler.

def exceptionHandler(msg: AnyRef,
                     responseFunction: Any => Unit,
                     messageFunction: (AnyRef, Any => Unit) => Unit)
                    (exceptionFunction: Exception => Unit)

In the call to bind (see the code in actor S, above), the first two parameters to exceptionHandler come from bind itself, with the third parameter specifying the message processing function to be wrapped by the exception handler. The fourth parameter is the anonymous function which handles the exception.

Now because an anonymous function is used to implement the exception handler, the exception handling logic can access the message that was sent (msg in this case) and the response function (rf) in addition to the exception which is passed to it (ex). Member variables of the actor class (S) can also be accessed by the exception handling logic. So everything is available to make writing the exception handling logic straight forward. Exception which the exception handling logic does not care to handle can simply be re-thrown (throw ex). Any exceptions coming out of the exception handler are simply passed up to the actor which sent the message causing the exception.

There is one especially interesting case to consider. What happens when an exception occurs in the logic which processes a response? Logically, this exception should NOT be handled by the exception handler of the actor which is returning the response, and that is exactly the case. This is done (when a response is being returned synchronously) by wrapping the exception in another class, TransparentException, which is detected by the exceptionHandler function of the actor which is returning the response, unwrapped, and passed up to the actor which sent the request.

ExceptionTest

Tutorial

Clone this wiki locally