Actorでの例外処理

Scala の Actor は、メッセージ処理中に例外が発生すると Actor 自体が終了するようになっています。
例外を処理したい場合に、今までは case を try 〜 catch で囲んでいたのですがどうもいまいちな感じでした。以下は try 〜 catch で囲んでいる例です。

actor {
  loop { react {
    case 'foo => try {
        ...
      } catch {
        ...
      }

    case 'bar => try {
        ...
      } catch {
        ...
      }
  } }
}

しばらくは忘れていたのですが、ふとしたときに link を使って例外を処理する Actor を作ればよいことに気付きました。
下記の例では、1 秒後に正常終了、2 秒後に例外発生、3 秒後にプログラムを終了する(トリガになる) Actor が起動します。

import scala.actors._
import Actor._

object HandleException {
  def main(arguments: Array[String]): Unit = {
    val workers = actor {
      self.trapExit = true
      loop { react {
        case work: Work =>
          val worker = new Worker(work)
          link(worker)
          worker.start
        case Exit(worker:Worker, reason:Throwable) =>
          printf("[Failure]The worker(%s) died.(reason=%s)\n", worker.name, reason)
        case Exit(worker:Worker, reason) =>
          printf("[Success]The worker(%s) completed.(reason=%s)\n", worker.name, reason)
        case 'exit =>
          exit
      }}
    }

    workers ! new Work("foo", 1000, {println("[Work]foo work.")})
    workers ! new Work("bar", 2000, {throw new Exception("bar fail.")})
    workers ! new Work("baz", 3000, {workers ! 'exit})
  }
}

class Work(val name: String, val delay: Int, f: => Unit) {
  def apply(): Unit = f
}

class Worker(work: Work) extends Actor {
  val name = work.name
  def act = reactWithin(work.delay) {
    case TIMEOUT => work()
  }
}

実行結果は以下のとおり。


[Work]foo work.
[Success]The worker(foo) completed.(reason='normal)
[Failure]The worker(bar) died.(reason=java.lang.Exception: bar fail.)

link を使うことで、link 先の Actor が終了したときに Exit メッセージが送られてきます。これは例外によって終了してしまった場合でも同様です。
例外によって終了した場合、Exit の reason に例外のインスタンスが設定されています。上記例では、reason の型が Throwable の場合とそうでない場合とで case を分けることで、正常終了と異常終了を切り分けています。

すべては Actor というのをちょっと実感しました。例外を処理したいなら、例外を処理する Actor を間に挟めってことですね・・・