Comment gérer avec élégance le signal SIGKILL en Java

Comment gérez-vous le nettoyage lorsque le programme reçoit un signal de suppression?

Par exemple, une application à laquelle je me connecte souhaite que toute application tierce (mon application) envoie une commande de finish lors de la déconnexion. Quel est le meilleur argument pour envoyer cette commande lorsque mon application a été détruite avec un kill -9 ?

edit 1: kill -9 ne peut pas être capturé. Merci les gars de me corriger.

edit 2: Je suppose que ce serait le cas quand on appelle juste tuer ce qui est le même que ctrl-c

La façon de gérer cela pour autre chose que kill -9 serait d’enregistrer un hook d’ arrêt . Si vous pouvez utiliser ( SIGTERM ) kill -15 le crochet d’arrêt fonctionnera. ( SIGINT ) kill -2 DOIT que le programme quitte et exécute normalement les hooks d’arrêt.

Enregistre un nouveau crochet d’arrêt de la machine virtuelle.

La machine virtuelle Java s’arrête en réponse à deux types d’événements:

 * The program exits normally, when the last non-daemon thread exits or 

lorsque la méthode exit (équivalence, System.exit) est appelée ou

 * The virtual machine is terminated in response to a user 

interrompre, par exemple taper ^ C ou un événement à l’échelle du système, tel que la fermeture de session ou l’arrêt du système.

J’ai essayé le programme de test suivant sur OSX 10.6.3 et sur kill -9 il n’a pas exécuté le hook d’arrêt, je ne le pensais pas. Sur un kill -15 le crochet d’arrêt est exécuté à chaque fois.

 public class TestShutdownHook { public static void main(final Ssortingng[] args) throws InterruptedException { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { System.out.println("Shutdown hook ran!"); } }); while (true) { Thread.sleep(1000); } } } 

Il n’y a aucun moyen de gérer efficacement un kill -9 dans un programme.

Dans de rares circonstances, la machine virtuelle peut abandonner, c’est-à-dire cesser de fonctionner sans fermer proprement. Cela se produit lorsque la machine virtuelle est terminée en externe, par exemple avec le signal SIGKILL sur Unix ou l’appel TerminateProcess sur Microsoft Windows.

La seule véritable option pour gérer un kill -9 est de faire en sorte qu’un autre programme de surveillance surveille la disparition de votre programme principal ou utilise un script wrapper. Vous pourriez faire cela avec un script shell qui a interrogé la commande ps pour rechercher votre programme dans la liste et agir en conséquence quand il a disparu.

 #!/usr/bin/env bash java TestShutdownHook wait # notify your other app that you quit echo "TestShutdownHook quit" 

Il existe des moyens de gérer vos propres signaux dans certaines JVM – voir cet article sur la machine virtuelle Java HotSpot par exemple.

En utilisant l’appel de méthode sun.misc.Signal.handle(Signal, SignalHandler) interne à Sun sun.misc.Signal.handle(Signal, SignalHandler) vous pouvez également enregistrer un gestionnaire de signal, mais probablement pas pour des signaux tels que INT ou TERM car ils sont utilisés par la JVM.

Pour pouvoir traiter n’importe quel signal, vous devrez sauter de la JVM et pénétrer dans le territoire du système d’exploitation.

Ce que je fais généralement pour (par exemple) détecter une terminaison anormale est de lancer ma JVM dans un script Perl, mais que le script attend la JVM en utilisant l’ waitpid système waitpid .

Je suis alors informé à chaque fois que la JVM se termine, et pourquoi il est sorti, et je peux prendre les mesures nécessaires.

Vous pouvez utiliser Runtime.getRuntime().addShutdownHook(...) , mais vous ne pouvez pas être Runtime.getRuntime().addShutdownHook(...) qu’il sera appelé dans tous les cas .

Je m’attendrais à ce que la JVM interrompt gracieusement ( thread.interrupt() ) tous les threads créés par l’application, au moins pour les signaux SIGINT (kill -2) et SIGTERM (kill -15) .

De cette façon, le signal leur sera transmis, ce qui permettra une annulation des threads et une finalisation des ressources de manière standard .

Mais ce n’est pas le cas (du moins dans mon implémentation JVM: Java(TM) SE Runtime Environment (build 1.8.0_25-b17), Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode) exécution Java(TM) SE Runtime Environment (build 1.8.0_25-b17), Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode) .

Comme d’autres utilisateurs l’ont dit, l’utilisation de hooks d’arrêt semble obligatoire.

Alors, comment puis-je le gérer?

D’abord, je ne m’en préoccupe pas dans tous les programmes, mais uniquement dans ceux où je souhaite suivre les annulations d’utilisateurs et les fins inattendues. Par exemple, imaginez que votre programme Java soit un processus géré par d’autres. Vous voudrez peut-être différencier si elle a été terminée correctement ( SIGTERM du processus du gestionnaire) ou si un arrêt s’est produit (afin de relancer automatiquement le travail au démarrage).

Comme base, je fais toujours mes threads de longue durée régulièrement au courant de l’état interrompu et jette une InterruptedException s’ils ont été interrompus. Cela permet la finalisation de l’exécution de manière contrôlée par le développeur (produisant également le même résultat que les opérations de blocage standard). Ensuite, au niveau supérieur de la stack de threads, InterruptedException est capturé et un nettoyage approprié est effectué. Ces threads sont codés pour savoir comment répondre à une demande d’interruption. Conception à haute cohésion .

Donc, dans ces cas, j’ajoute un hook d’arrêt, ce qui fait ce que je pense que la JVM devrait faire par défaut: interrompre tous les threads non-daemon créés par mon application qui sont encore en cours d’exécution:

 Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { System.out.println("Interrupting threads"); Set runningThreads = Thread.getAllStackTraces().keySet(); for (Thread th : runningThreads) { if (th != Thread.currentThread() && !th.isDaemon() && th.getClass().getName().startsWith("org.brutusin")) { System.out.println("Interrupting '" + th.getClass() + "' termination"); th.interrupt(); } } for (Thread th : runningThreads) { try { if (th != Thread.currentThread() && !th.isDaemon() && th.isInterrupted()) { System.out.println("Waiting '" + th.getName() + "' termination"); th.join(); } } catch (InterruptedException ex) { System.out.println("Shutdown interrupted"); } } System.out.println("Shutdown finished"); } }); 

Application de test complète à github: https://github.com/idelvall/kill-test

Il y a une façon de réagir à un kill -9: avoir un processus séparé qui surveille le processus en cours de suppression et le nettoyer si nécessaire. Cela impliquerait probablement IPC et serait un peu difficile, et vous pouvez toujours le remplacer en tuant les deux processus en même temps. Je suppose que cela ne vaudra pas la peine dans la plupart des cas.

Quiconque tue un processus avec -9 devrait théoriquement savoir ce qu’il / elle fait et que cela peut laisser les choses dans un état incohérent.