web-dev-qa-db-fra.com

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

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

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

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

edit 2: Je suppose que ce serait le cas lorsque l'un appelle simplement kill, ce qui revient au même que ctrl-c

98
Begui

La manière de gérer cela pour tout autre que kill -9 serait d’enregistrer un shutdown hook. Si vous pouvez utiliser (SIGTERM) kill -15, le crochet d'arrêt fonctionnera. (SIGINT) kill -2NE FAIT que le programme quitte normalement et exécute les crochets 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:

  • Le programme se ferme normalement, à la sortie du dernier thread non démon ou à l'invocation de la méthode exit (de manière équivalente, System.exit), ou
  • La machine virtuelle est terminée en réponse à une interruption de l'utilisateur, telle que la saisie de ^ C, ou à un événement système, tel que la fermeture de session de l'utilisateur 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 a exécuté NOT _ _, comme prévu, le crochet d'arrêt. Sur un kill -15 il FAIT exécutez le point 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’existe aucun moyen de gérer avec élégance un kill -9 dans un programme quelconque.

Dans de rares circonstances, le virtuel la machine peut abandonner, c'est-à-dire arrêter courir sans arrêter proprement . Cela se produit lorsque la machine virtuelle se termine à l'extérieur, par exemple avec le signal SIGKILL sous Unix ou le Appel TerminateProcess sur Microsoft Les fenêtres.

La seule option réelle pour gérer un kill -9 consiste à demander à un autre programme observateur de voir votre programme principal disparaître ou à utiliser un script wrapper. Vous pouvez le faire avec un script Shell qui interroge la commande ps à la recherche de votre programme dans la liste et qui 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"
115
user177800

Il y a des moyens de gérer vos propres signaux dans certaines machines virtuelles - voir cet article sur la machine virtuelle HotSpot par exemple.

En utilisant l'appel à la méthode Sun.misc.Signal.handle(Signal, SignalHandler) interne à Sun, vous pouvez également enregistrer un gestionnaire de signaux, mais probablement pas pour les signaux tels que INT ou TERM tels qu'ils sont utilisés par la machine virtuelle Java.

Pour pouvoir traiter tout signal, vous devez quitter la machine virtuelle Java et accéder au territoire du système d'exploitation.

Par exemple, ce que je fais pour détecter (par exemple) une terminaison anormale est de lancer ma machine virtuelle dans un script Perl, mais de le laisser attendre la machine virtuelle à l'aide de l'appel système waitpid

Je suis ensuite informé de la sortie de la machine virtuelle Java, de la raison pour laquelle elle est sortie et de la possibilité de prendre les mesures nécessaires.

13
Asgeir S. Nilsen

Je m'attendrais à ce que la machine virtuelle Java 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, ce qui permettra une annulation en douceur du fil et une finalisation des ressources de la manière standard .

Mais ce n'est pas le cas (du moins dans mon implémentation de machine virtuelle Java: 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 commenté, l'utilisation de shutdown hooks semble obligatoire.

Alors, comment puis-je le gérer? 

Eh bien d’abord, je ne me soucie pas de cela dans tous les programmes, mais seulement dans ceux où je veux garder une trace des annulations d’utilisateur et des fins inattendues. Par exemple, imaginez que votre programme Java soit un processus géré par un autre. Vous souhaiterez peut-être différencier s'il a été résilié correctement (SIGTERM du processus du gestionnaire) ou s'il a été arrêté (afin de relancer automatiquement le travail au démarrage).

En guise de base, je tiens toujours mes threads de longue durée régulièrement informés de l'état d'interruption et jette une InterruptedException s'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, InterruptedException est capturé et le nettoyage approprié est effectué. Ces threads sont codés pour savoir comment répondre à une demande d'interruption. Haute cohésion design.

Donc, dans ces cas, j'ajoute un point d'arrêt qui fait ce que la machine virtuelle Java devrait faire par défaut: interrompre tous les threads non démon créés par mon application et 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

8
idelvall

Vous pouvez utiliser Runtime.getRuntime().addShutdownHook(...), mais rien ne garantit qu’elle sera appelée dans tous les cas .

6
lexicore

Il existe un moyen de réagir à kill -9: un processus distinct surveille le processus en cours de suppression et le nettoie si nécessaire. Cela impliquerait probablement IPC et représenterait beaucoup de travail. 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 est en train de faire et qu'il peut laisser des choses incohérentes.

0
Arno Schäfer