I wanted to make my application more robust against this kind of problem by catching the exception, delivering an error message, and continuing. For a normal method, this would have been straightforward, but the execution path in an Actor using react (rather than receive) is not quite normal. The canonical act() method in an Actor using react looks like this:
def act() { loop { react { case a => ... case b => ... } } }
//This does not work def act() { loop { try { react { case a => ... case b => ... } } catch { case ex:Exception => ... } } }
I mentioned in my previous blog posting how the react method takes an argument of type PartialFunction[Any,Unit]. It has a couple of other non-intuitive behaviors, which are a result of the event-based mechanism used in Scala to allow concurrently using huge numbers of Actors.
- When it has finished executing its body, it throws a SuspendActorException rather than simply returning.
- Rather than directly executing the supplied PartialFunction, it passes it to a scheduler, which may execute that function on a separate thread.
In order to catch an exception thrown by the PartialFunction, the code that catches the exception must therefore be something within the body of the react statement. In other words, I needed to wrap my PartialFunction in another PartialFunction that catches and handles exceptions. I called this PartialFunction that catches exceptions PFCatch.
PFCatch is a PartialFunction that takes as an argument another PartialFunction, referred to here as the target function. PartialFunction defines two methods that relate to the computation defined by that PartialFunction:
apply
and isDefinedAt
.
The PFCatch class proxies these two methods to the target function.
The call to the apply method of the target function is wrapped in a
try/catch block, which is where I catch and handle the exceptions
thrown by the target function.
I did not bother to wrap the call to the isDefinedAt method of the
target function because that method was not throwing exceptions,
so was not giving me any problems.
The PFCatch class thus looks something like this:
class PFCatch[T](f:PartialFunction[T,Unit]) extends PartialFunction[T,Unit] { def apply(x:T) = { try { f(x) } catch { case ex:Exception => handleException(ex) } } def isDefinedAt(x:T) = f.isDefinedAt(x) def handleException(ex:Exception):Unit = ... }
object PFCatch { def apply[T](f:PartialFunction[T,Unit]) = new PFCatch(f) }
def act() { loop { react (PFCatch(handleMessage)) } private val handleMessage:PartialFunction[Any,Unit] = { case a => ... case b => ... } }
react (PFCatch(handleSubscribe orElse handleOther))
My actual implementation of PFCatch takes a couple of extra arguments, which I use in order to better handle the exception. The complete listing of that version can be found as PFCatch.scala in the net.jimmc.util package of my Mimprint application, which is distributed under the GPL.
3 comments:
What you're looking for are the "link" methods. Linked actors get each other's exceptions, and die as a unit. If a linked actor sets "trapExit = true" in its act() method, it doesn't die but rather receives a message with the Exit case class. So then it can restart the whole shebang. Erlang design uses this a lot, and Scala copies it.
Julian: Thanks for pointing out what the "link" methods do, since that functionality is very poorly documented. Using link does sound like a better approach for a more sophisticated system, since it would probably be simpler and you could have one actor that handles restarting a bunch of other actors.
Post a Comment