Intercepter les signaux kill

Intercepter l’arrêt de la JVM

Avec l’objet Runtime, il est possible d’ajouter des tâches exécutées au moment de l’arrêt de la JVM.

Runtime.getRuntime()
       .addShutdownHook(
            new Thread(() -> System.out.println("Cleaning before shutdown"))
       );

Les signaux Linux

Pour arrêter sagement un process :

$ kill 1234
ou
$ kill -TERM 1234

Pour simuler un CTRL+C :

$ kill -INT 1234
ou
$ kill -2 1234

Quelques signaux :

  • HUP (1) : hangup

  • INT (2) : terminal interrupt (CTRL+C)

  • QUIT (3) : terminal quit, with core dump

  • KILL (9) : kill (cannot be caught)

  • TERM (15) : termination (default)

  • …​

La plupart de ces signaux provoquent l’arrêt du process, avec éventuellement un core dump (QUIT, ILL, TRAP,…​). Ce comportement par défaut peut être redéfini par le process. Seul le signal KILL ne peut pas être intercepté.

La liste complète des signaux peut être obtenue par la commande kill -l.

Les signaux Linux en Java

Le signal QUIT est intercepté par le JVM pour générer un thread dump.

Il est possible de définir ses propres inteceptions avec la classe sun.misc.Signal.

Signal.handle(
        new Signal("INT"),
        this::handleINT
    );

Attention, le comportement est très différent des shutdown hooks. En effet, ici on n’ajoute pas d’action mais on remplace l’action par défaut. Par conséquent, si on veut que le process s’arrête, il faut le lui demander explicitement : System.exit(0).

Comme spécifié plus haut, KILL ne peut pas être intercepté. Si on essaie, ça déclenche une exception.

java.lang.IllegalArgumentException: Signal already used by VM or OS: SIGKILL
	at java.base/jdk.internal.misc.Signal.handle(Signal.java:172)
	at jdk.unsupported/sun.misc.Signal.handle(Signal.java:157)
	at ...

Il est aussi possible d’envoyer un signal au process courant, à condition que celui-ci le gère :

Signal.raise(new Signal("HUP"));

Attention, la classe Signal n’est pas dans les packages standards. Elle est dans le module jdk.unsupported ce qui signifie qu’elle peut disparaitre.

Elle existe depuis le JDK 1.2, et est toujours utilisable dans le JDK 17. Son support n’a été ajouté que tardivement dans GraalVM, en version 21.1.

Avec Docker

A l’intérieur du conteneur, la principale subtilité vient du fait que l’application est souvent sur le process numéro 1 et qu’on ne peut pas lui adresser de signal KILL. Les autres signaux fonctionnent bien.

Depuis l’hôte, on peut transmettre un signal par la commande docker kill. Le signal par défaut, si on ne spécifie par --signal, est KILL.

$ docker kill --signal=INT <container-name>