recipe № 016

handling exit calls from external library called via JNI (atexit)

In this sample I will show how to avoid killing your JVM by the external library that makes a call to exit function. We will use atexit function to set a handler for exit calls, and then we will jump out of it using long jump.

After you complete this tutorial, you should end up with the layout similar to this one:

In this tutorial we are using two Java classes that call methods from a single library – libSigTerm. SigTerm.java calls riskyCode() and wraps it with atexit based handler (check man atexit). SigTermNoHandler.java, on the other hand, makes the call to riskyCode() but doesn’t install atexit based handler.

First, let’s take a look at SigTerm.java code. You can find it below. Pay attention to atexit call (we are setting stopExit() to be the code that will be called whenever someone calls exit() – make sure to check man exit -S 3. Call to riskyCode() is wrapped by callExitCode().

Second code, makes the same thing, but it calls different wrapper in JNI. This time, we call callExitCodeNoHandler(). In this case, native code will call riskyCode() without any precautions. You have to take a look at the JNI code to get the feeling what’s the difference.

When it comes to JNI code generated by javah it’s plain simple.

As you can see – no surprises here. We just declare two, external functions: Java_recipeNo016_SigTermNoHandler_callExitCodeNoHandler and Java_recipeNo016_SigTerm_callExitCode.

Implementation is much more interesting. Take a look at different implementation of the call to riskyCode(). Compare Java_recipeNo016_SigTerm_callExitCode and Java_recipeNo016_SigTermNoHandler_callExitCodeNoHandler.

Can you see declaration of riskyCode()? This is the function from our nasty library that we use in our Java code. This function is defined inside externalLib.c, and later on, compiled into shared library.

When everything is in place, we can build all the stuff and perform the test

When we start the test, we can see that we get two different outcomes of the execution

As you can see, first call (wrapped with atexit) thrown an exception that was handled in JVM. Second call, without atexit function, made whole JVM to exit.