Monday, December 16, 2013

JVM Threads, ???, and Open Source

Lately, open source is important to me. Today, for instance: it starts with a useful library, complicated by Java threading, confused by a Scala language feature, and ends with happiness and joy because Open Source.

I'm using scalaz.concurrent.Task, a monad (LINK) with at least three special powers. First, it use a Future internally, which perform asynchronous computation. Second, it uses scalaz.concurrent.Future (as opposed to the built-in one) because this one trampolines from thread to thread so that we never run out of stack no matter how many "and then do this" computations we hook up before running it. Finally, Task handles errors, wrapping all its work in a try/catch and retaining exceptions in scalaz's version of Either (called \/ for \/ictory).

I can build up a Task starting with one delayed computation, adding on transformations, and then binding on transformations that produce other Tasks, as many as I like. (See the functor and monad LINK posts for context of the drawings.)
Task wraps each calculation in a try/catch, translating exceptions into data, so they can get passed back to whoever evaluated the Task.
Then, scalaz.concurrent.Future tosses each transformation over to another thread. Only the original thread blocks; after that the calculation hops from thread to thread until it's all executed (or failed), and then an answer returns to the waiting thread.



This is all a giant game of "You can't catch me" with StackOverflowError. It works, until...

a train car blows up in one of the threads. If that caution tape isn't wound tight enough, and the code throws an exception and nothing catches it, well - that thread just keels over.
The blocking thread waits, and waits, and waits... the program does not terminate. This is how Java threads work. An uncaught Throwable doesn't destroy the process, only the one thread.
I hate it when that happens! and it kept happening to me!

In this case, my code was deliberately throwing an exception. I was testing exception handling. But...
object format: JsonFormat[BadGuy] = {
   def read = ???
   def write = throw new RuntimeException("poo")
}
unbeknownst to me, my code called read before write. Here comes the other tricky bit: ??? is Scala for "throw NotImplementedError." Which is often useful; it's basically "WTF you weren't supposed to call this yet."

??? throws an Error, not an Exception. Both are subclasses of Throwable. In general, it's bad form to catch Throwable, because Errors are not intended to be anything you can recover from, like OutOfMemory or StackOverflow. I guess one doesn't normally recover from unimplemented methods, so it's an Error.

Task followed this rule of thumb: it caught Exception but not Throwable. That's how ??? caused thread death; that's why my test never terminated. It kept chewing on that Task forever.
What to do? I think the library is wrong. Sure you're not supposed to catch Throwable most of the time, but this seems like an exception. If we want Error to terminate the program, the Task should catch it, pass it back to the original thread, and throw it there. How can I ever be sure my programs won't die a painful slow undeath of starvation?

Open Source to the rescue! I can do more than kick my desk, more than gripe to my coworkers. More than complain on twitter or here. First, I posted the question to the mailing list. Scalaz's Task was written by Paul Chuisano, and he responded superfast. Not only did I get an answer, I got to fix it!

Instead of bemoaning my fate, I have the warm fuzzies and pride of contributing to a library that I care about. Future users of Task get to miss out on this particular source of undying, unmoving processes. Everyone wins!

So the next time your program terminates, say a prayer of thanks open source software.

1 comment:

  1. "Sure you're not supposed to catch Throwable most of the time, but this seems like an exception."

    Pun intended?

    In any case, thanks for fixing the bug! If I use scalaz.concurrent.Task in the future, I'll do so knowing that zombies won't be lurking around!

    ReplyDelete