When some cleanup works should be done when a program ends, a shutdown hook can be registered. Such a hook is executed on System.exit() or when the process receives a SIGINT (Ctrl-C). But it’s nice to also cleanup things directly when a resource is released normally.

This leads to code like this:

public class Resource implements AutoCloseable {
    private final Thread shutdownHook;

    public Resource() {
        shutdownHook = new Thread(new Runnable() {
            public void run() {
                // do the cleanup
            }
        });
        Runtime.getRuntime().addShutdownHook(shutdownHook);
    }

    public void close() throws Exception {
        shutdownHook.start();
        shutdownHook.join();
    }
}

This is nice and works perfectly. Most of the time. It’s broken.

The problem is that the shutdown hook is a Thread and not a Runnable. A Thread can only run once. When the resource is released by the close method, the shutdown hook is run. Fine.

But when the JVM terminates, the hook is executed a second time and… nothing happens. At least nothing visible. Executing start() on an already executed Thread throws an IllegalThreadStateException, but this exception is silently swallowed by the JDK.

Even this is not so bad, as the resource is already closed. But when there are other shutdown hooks registered, it’s possible that they are not executed any more. A source of nasty bugs.

I had this situation when I played with SonarQube to analyze a project. I added JaCoCo to track test coverage. But the coverage was always zero. JaCoCo registers a shutdown hook to write out coverage data to a file when the JVM terminates. Except when a bad shutdown hook causes an exception.

Ironically, most critical issues that Sonar found in my project were “Either log or rethrow this exception”.

I couldn’t agree more.