Firing off a CTRL C will not call the shutdown hook in the following example when running as native. It will fire when running with java jar
import java.util.concurrent.CountDownLatch;
public class Agent {
private final CountDownLatch shutdownLatch;
public Agent() {
this.shutdownLatch = new CountDownLatch(1);
}
public void run() throws InterruptedException {
System.out.println("Running");
// Add a shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
shutdown();
}));
try {
shutdownLatch.await();
System.out.println("Latch released");
} catch (InterruptedException e) {
System.out.println("InterruptedException");
}
}
public synchronized void shutdown() {
System.out.println("Shutdown called");
shutdownLatch.countDown();
}
public static void main(String[] args) throws Exception {
new Agent().run();
}
}
This boils down to the question "how much should we do during the startup of an application"? We want to do as little as possible during startup, but registering a signal handler would require us to start a new thread that handles the signal and invokes the shutdown hooks.
In addition, we want to be embedding-friendly: we do not want to register any signal handlers unless they are registered explicitly by the user. So even registering a signal handler when the first shutdown hook is installed feels intrusive.
Luckily, you can easily register the signal handler explicitly yourself if you want:
import java.util.concurrent.CountDownLatch;
import sun.misc.Signal;
public class Agent {
private final CountDownLatch shutdownLatch;
public Agent() {
this.shutdownLatch = new CountDownLatch(1);
}
public void run() throws InterruptedException {
// Register a signal handler for Ctrl-C that runs the shutdown hooks
Signal.handle(new Signal("INT"), sig -> System.exit(0));
System.out.println("Running");
// Add a shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
shutdown();
}));
try {
shutdownLatch.await();
System.out.println("Latch released");
} catch (InterruptedException e) {
System.out.println("InterruptedException");
}
}
public synchronized void shutdown() {
System.out.println("Shutdown called");
shutdownLatch.countDown();
}
public static void main(String[] args) throws Exception {
new Agent().run();
}
}
Invoking System.exit on SIGINT gives you the behavior you want, because shutdown hooks run as part of System.exit.
Note that the output Latch released of your application is not guaranteed to happen. It depends on thread scheduling, i.e., the main thread needs to resume in the short time window between the countDown of the latch and the actual exit of the application. On the Java HotSpot VM, shutdown seems to be "slow enough" so that Latch released is printed reliably on my machine, while with a native image shutdown is faster and therefore it is not printed.
Thanks. This should work for my use-case.
@christianwimmer given this is a difference between running with normal java and with a native-image binary, do you think it would be worthwhile to put a note in https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md mentioning the bit about registering a signal handler, perhaps under Features That May Operate Differently in Native Image?
@paul-bjorkstrand, information on signal handlers is available on https://github.com/oracle/graal/blob/master/substratevm/Limitations.md#signal-handlers and https://github.com/oracle/graal/blob/master/substratevm/README.md#build-a-native-image pages.
Thanks!
Most helpful comment
Thanks. This should work for my use-case.