⏹️ Making your JVM application interruptible
Today we’ll discuss how to make your JVM application interruptible, what exactly it means, why you would need it, and how it can improve your development experience.
- How we run JVM applications
- What exactly does “make an application interruptible” mean?
- Why do I need it then and why not run in a forked JVM?
- How to make an application interruptible?
How we run JVM applications
Before we start, to better dive into the context of the problem, let’s see how we can run our applications. In general, there are at least two ways to run a JVM application during development. Let’s compare them.
Standalone Java process
This is the case when an application starts in its own Java process. This is what happens when we set fork := true in sbt, how Gradle runs your application, and how it runs in production.
When fork flag is set, running sbt run starts your application in a completely separate process, which is not related to sbt in any way.
A notable downside of this approach is that it makes everything a little bit slower because the JVM starts from scratch, loads all the classes, nothing is JIT-compiled yet, and so on.
But the advantage is that it allows you to handle OS signals, like SIGTERM, SIGINT, SIGUSR2, and others. Catching these signals is what frameworks do for you, and they rely on them to initiate shutdown logic, which is usually implemented using Runtime.addShutdownHook(Thread). In a common scenario, frameworks set shutdown hooks that are triggered when the JVM process stops, for example, when a container stops in production or when you press CTRL + C to cancel a forked run.
As an additional advantage, running an application in a separate process better shows how it would run in production.
Run within existing JVM process
As opposed to running an application in a separate process, during development you can also run it in an already existing JVM.
This is the default behavior for sbt run and it makes everything a little bit faster because you don’t need to start another JVM, system classes are already loaded, many optimizations are already applied, and so on.
Problems of running uninterruptible applications within existing JVM
Firstly, as you might have guessed, you can no longer catch OS signals, and shutdown hooks won’t be called either. When you press CTRL + C, sbt (or any other tool which wraps your application) throws java.lang.InterruptedException in your application’s main thread and expects it to stop. If your application or framework doesn’t handle it correctly, you may encounter memory leaks, unreleased ports, strange behavior in subsequent runs, or perhaps your CTRL + C will be completely ignored, among other unpleasant issues. It’s not a rare case.
There is even an open issue in sbt where people advocate for making “fork := true” the default because their application doesn’t work well in a non-forked JVM. This mostly happens because their framework (or perhaps the application itself) incorrectly handles InterruptedException, which is clearly a bug. You can see quotes such as:
@jrudolph said: I have to say, that adding
fork in run := trueto an sbt project is one of the first things I do in many cases.
@AugustNagro said: I just spent 30 minutes trying to figure out why a Vertx endpoint was not being updated.
@altrack said: From my team’s experience we had more issue than gains from having
fork := false, and eventually converted all dozens repos tofork := true. Usually we had some strange startup/shutdown errors/issues that were resolved by addingfork =: true.
Such issues are possible not only with the sbt runner but also with any other tool that “wraps” your application and restarts it within a single JVM process.
What exactly does “make an application interruptible” mean?
There is no strict definition, I guess, so I’ll form it freely. It means the following:
- Make your application handle
java.lang.InterruptedExceptionby initiating shutdown and releasing all resources. - Make your
static void mainmethod run all the time while an application is alive.
Nowadays, it’s rare for people to make their applications and frameworks interruptible, probably because it doesn’t affect production and usually doesn’t heavily affect the development process.
Why do I need it then and why not run in a forked JVM?
You need it if your application is managed by some tool (or call it runner) and runs in an existing tool’s JVM. It could be your build system, IDE, or some other tool that is responsible for running an application for some reason. For example, it’s necessary if you’re using ♾️ Live Reloading on JVM. If that’s your case, then you need your application to be interruptible.
If not, feel free to run an application in a forked JVM.
How to make an application interruptible?
Well, there is no universal solution, and every case must be investigated individually, but the very basic example is:
// Before
public class Application {
public static void main(String[] args) {
new MyWebServer("localhost", 8080).start();
}
}
// After
public class Application {
public static void main(String[] args) {
var server = new MyWebServer("localhost", 8080);
try {
server.start();
Thread.currentThread().join();
} catch (InterruptedException ex) {
server.close();
}
}
}
The general idea is to shut down everything if you catch InterruptedException - that’s it. And don’t forget to ensure that main method is blocking and runs all the time while an application is alive.
Conclusion
Thank you for reading this little article, I hope it will be useful to someone. Feel free to reach me if you have something to say.
And also take a look on the posts on similar topics: