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!
Written by Colin Dean
Related protips
1 Response
Thanks! I just encountered this very issue today and couldn't understand why the InterruptedException wasn't being caught.