Comment gérer gracieusement le signal SIGKILL en Java

113

Comment gérez-vous le nettoyage lorsque le programme reçoit un signal d'arrêt?

Par exemple, il existe une application à laquelle je me connecte et qui souhaite qu'une application tierce (mon application) envoie une finishcommande lors de la déconnexion. Quelle est la meilleure façon d'envoyer cette finishcommande lorsque mon application a été détruite avec un kill -9?

edit 1: kill -9 ne peut pas être capturé. Merci les gars de m'avoir corrigé.

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

Begui
la source
44
kill -9signifie pour moi: "Pars, processus immonde, loin avec toi!", sur lequel le processus cessera d'être. Immédiatement.
ZoogieZork
11
Sur la plupart des * nix que je connais, kill -9 ne peut être intercepté et géré gracieusement par aucun programme, quelle que soit la langue dans laquelle il a été écrit.
President James K. Polk
2
@Begui: en plus de ce que les autres ont commenté et répondu, SI votre OS Un x ne tue pas instantanément * ET REGAIN TOUTES LES RESSOURCES utilisées par le programme en cours de destruction, eh bien ... L'OS est cassé.
SyntaxeT3rr0r
1
A propos de la commande kill -9, la page de manuel dit plus précisément: "9 KILL (non capturable, non ignorable kill)". SIGKILL est un signal géré par le système d'exploitation, pas par l'application.
user1338062
4
Ce killn'est pas la même chose que Ctrl-C, car killsans spécifier quel signal envoyer enverra SIGTERM, alors que Ctrl-C envoie SIGINT.
alesguzik

Réponses:

136

Il est impossible pour aucun programme, dans n'importe quelle langue, de gérer un SIGKILL. C'est ainsi qu'il est toujours possible de terminer un programme, même si le programme est bogué ou malveillant. Mais SIGKILL n'est pas le seul moyen de terminer un programme. L'autre consiste à utiliser un SIGTERM. Les programmes peuvent gérer ce signal. Le programme doit gérer le signal en effectuant un arrêt contrôlé, mais rapide. Lorsqu'un ordinateur s'arrête, la dernière étape du processus d'arrêt envoie à chaque processus restant un SIGTERM, donne à ces processus quelques secondes de grâce, puis leur envoie un SIGKILL.

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

Enregistre un nouveau hook d'arrêt de machine virtuelle.

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

  • Le programme se ferme normalement, lorsque le dernier thread non démon se termine ou lorsque la méthode exit (de manière équivalente, System.exit) est invoquée, ou
  • La machine virtuelle est arrêtée en réponse à une interruption utilisateur, telle que la saisie de ^ C, ou à un événement à l'échelle du système, tel que la déconnexion de l'utilisateur ou l'arrêt du système.

J'ai essayé le programme de test suivant sur OSX 10.6.3 et kill -9je n'ai PAS exécuté le crochet d'arrêt, comme prévu. Sur un kill -15il N'exécuter le crochet d'arrêt à chaque fois.

public class TestShutdownHook
{
    public static void main(String[] 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 vraiment avec élégance un kill -9dans aucun programme.

Dans de rares cas, la machine virtuelle peut abandonner, c'est-à-dire s'arrêter de fonctionner sans s'arrêter proprement. Cela se produit lorsque la machine virtuelle est arrêtée en externe, par exemple avec le signal SIGKILL sous Unix ou l'appel TerminateProcess sous Microsoft Windows.

La seule vraie option pour gérer un kill -9est d'avoir un autre programme de surveillance pour que votre programme principal disparaisse ou d'utiliser un script wrapper. Vous pouvez le faire avec un script shell qui interroge la pscommande à la recherche de votre programme dans la liste et agit en conséquence lorsqu'il disparaît.

#!/usr/bin/env bash

java TestShutdownHook
wait
# notify your other app that you quit
echo "TestShutdownHook quit"
Raedwald
la source
12

Il existe des moyens de gérer vos propres signaux dans certaines JVM - voir cet article sur HotSpot JVM par exemple.

En utilisant l' sun.misc.Signal.handle(Signal, SignalHandler)appel de méthode interne de Sun, vous pouvez également enregistrer un gestionnaire de signaux, mais probablement pas pour des signaux comme INTou TERMtels qu'ils sont utilisés par la JVM.

Pour être en mesure de gérer n'importe quel signal, vous devez sauter hors de la JVM et 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 attende la JVM en utilisant l' waitpidappel système.

Je suis alors informé de chaque sortie de la machine virtuelle Java, de la raison de sa sortie, et je peux prendre les mesures nécessaires.

Asgeir S. Nilsen
la source
3
Notez que vous pouvez capturer INTet TERMavec sun.misc.Signal, mais vous ne pouvez pas gérer QUITcar la JVM le réserve pour le débogage, ni KILLparce que le système d'exploitation mettra fin immédiatement à la JVM. Tenter de gérer l'un ou l'autre lèvera un IllegalArgumentException.
dimo414
12

Je m'attendrais à ce que la JVM interrompt gracieusement ( thread.interrupt()) tous les threads en cours d'exécution 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, permettant une annulation de thread et une finalisation des ressources en douceur de la manière standard .

Mais ce n'est pas le cas (au moins dans ma mise en œuvre 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).

Comme l'ont commenté d'autres utilisateurs, l'utilisation de hooks d'arrêt semble obligatoire.

Alors, comment puis-je gérer cela?

Eh bien d'abord, je m'en fiche dans tous les programmes, seulement dans ceux où je veux garder une trace des annulations d'utilisateurs et des fins inattendues. Par exemple, imaginez que votre programme java est un processus géré par d'autres. Vous voudrez peut-être différencier s'il a été terminé correctement (à SIGTERMpartir du processus gestionnaire) ou si un arrêt s'est produit (afin de relancer automatiquement le travail au démarrage).

Comme base, je rends toujours mes threads de longue durée régulièrement au courant de l'état interrompu et jette un message InterruptedExceptions'ils sont 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 pile de threads, InterruptedExceptionest 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, qui fait ce que je pense que la JVM devrait faire par défaut: interrompre tous les threads non démon créés par mon application qui sont toujours en cours d'exécution:

Runtime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
        System.out.println("Interrupting threads");
        Set<Thread> 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 sur github: https://github.com/idelvall/kill-test

Idelvall
la source
6

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

Lexicore
la source
12
Mais dans le cas de kill -9, il ne fonctionnera presque certainement pas.
Président James K. Polk
1

Il y a une façon de réagir à un kill -9: c'est d'avoir un processus séparé qui surveille le processus en cours de destruction et nettoie après cela si nécessaire. Cela impliquerait probablement IPC et serait un peu de travail, et vous pouvez toujours le remplacer en supprimant 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 fait et que cela peut laisser les choses dans un état incohérent.

Arno Schäfer
la source