Fork me on GitHub

Graceless Failures

Tips, tricks, missteps, and minor revelations on the path to Scala wisdom.

Warning About Tail Calls

We ran into a scary case last week where a code change could reliably make the JVM (1.6) segfault
every 3-4 hours. At first, I thought it might be a “cosmic ray” kind of phenomenon, like a
particular alignment of the bytecode or some edge case in the scala (2.7.7) compiler, but as we
started rolling back the code to triage, it was clear that some particular piece of code was
reliably causing the segfault. Code bisection pinned it down to a tail-call recursion:

def foreach(blocking: Boolean, f: Job => Unit) {
  val continue = {
    getJob(blocking) match {
      case None =>
        false
      case Some(job) =>
        f(job)
        true
    }
  }
  if (continue) {
    foreach(blocking, f)
  }
}

(The code is simplified a bit for clarity.)

The tail-call recursion is pulled out of any potential
block because otherwise the recursive call may be nested inside an inner class, which would prevent
scalac from being able to create a tail-call. But it doesn’t seem to have helped, because a dump of
the compiled bytecode shows that scalac failed to create a tail-call anyway:

186:	iload_3
187:	ifeq	196
190:	aload_0
191:	iload_1
192:	aload_2
193:	invokevirtual	#245; //Method foreach:(ZLscala/Function1;)V
196:	return

Translation: If local-3 (continue) is false, return; otherwise, do a normal java recursive call.

Possibly the JVM doesn’t correctly catch a stack overflow inside java (?) and the segfault was just
the java thread running out of stack space for recursion.

For now, I would recommend avoiding tail calls. It’s very tricky to figure out when scalac will be
able to avoid actual java-stack recursion. Apparently scala 2.8 will have an annotation you can add
to methods to make the compiler warn you when it can’t properly optimize the recursion into a tail
call.