Last Updated: September 27, 2021
·
3.138K
· colindean

Catch InterruptedException in Scala with try{]catch{}, not scala.util.Try

Say you've got a method that you're going to run as a thread:

def functionAsThread[F](function: => F, name: Option[String] = None) = {
    val t = new Thread( new Runnable() { def run() { function } } )
    t.start()
    name.foreach(t.setName)
    t
  }

def periodicallyDoSomething(): Unit = {
  while(keepDoingIt_?) {
    doSomething()
    Thread.sleep(frequency)
  }
}

val somethingDoer = functionAsThread(periodicallyDoSomething, Some("SomethingDoer"))

When inevitably the thread must stop, such as in a clean shutdown, you will of course tell somethingDoer to halt with somethingDoer.interrupt().

How should you handle the potential InterruptedException thrown by Thread.sleep?

Your Scala brain should lead you to wrap function in a Try and pass in a PartialFunction for recovery, like so:

def functionAsThread[F](function: => F, name: Option[String] = None, recoverWith: PartialFunction[Throwable, Unit]) = {
    val t = new Thread( new Runnable() { def run() { Try(function).recover(recoverWith) } } )
    t.start()
    name.foreach(t.setName)
    t
  }

You'll be as frustrated as I was when this doesn't work. The exception will still be uncaught!

Digging into Try a bit more, we see why this doesn't work:

object Try {
  def apply[T](r: => T): Try[T] =
    try Success(r) catch {
      case NonFatal(e) => Failure(e)
    }
}

That's interesting. Try is just a try{}catch{} that returns a convenient Success or Failure object. What's this NonFatal business, though?

object NonFatal {
   def apply(t: Throwable): Boolean = t match {
     // VirtualMachineError includes OutOfMemoryError and other fatal errors
     case _: VirtualMachineError | _: ThreadDeath | _: InterruptedException | _: LinkageError | _: ControlThrowable => false
     case _ => true
   }
  def unapply(t: Throwable): Option[Throwable] = if (apply(t)) Some(t) else None

Scala considers InterruptedException to be a fatal exception. That is, there is a deliberate design decision that an implementor must use try{}catch{} to handle it. This makes sense because a thread that is sleeping should explicitly be told what to do if it is interrupted. That's just a part of using Thread.sleep.

So, a way to correct the method is:

def periodicallyDoSomething(): Unit = {
  try {
    while(keepDoingIt_?) {
      doSomething()
      Thread.sleep(frequency)
    }
  } catch {
    case e: InterruptedException => handleInterruption()
}

It might be better just to try the Thread.sleep, but I'll leave that improvement as an exercise for the reader!

1 Response
Add your response

Thanks! I just encountered this very issue today and couldn't understand why the InterruptedException wasn't being caught.

over 1 year ago ·