Cómo manejar con gracia la señal SIGKILL en Java

113

¿Cómo maneja la limpieza cuando el programa recibe una señal de interrupción?

Por ejemplo, hay una aplicación a la que me conecto que quiere que cualquier aplicación de terceros (mi aplicación) envíe un finishcomando al cerrar la sesión. ¿Cuál es la mejor opción para enviar ese finishcomando cuando mi aplicación ha sido destruida con un kill -9?

edición 1: kill -9 no se puede capturar. Gracias chicos por corregirme.

edición 2: Supongo que este caso sería cuando el que llama simplemente kill, que es lo mismo que ctrl-c

Begui
fuente
44
kill -9significa para mí: "¡Vete, proceso inmundo, vete!", tras lo cual el proceso dejará de existir. Inmediatamente.
ZoogieZork
11
En la mayoría de los * nixes que conozco, kill -9 no puede ser interceptado y manejado con elegancia por ningún programa, sin importar en qué idioma esté escrito.
Presidente James K. Polk
2
@Begui: además de lo que los demás han comentado y respondido, SI tu SO Un x no mata instantáneamente * Y RECUPERA TODOS LOS RECURSOS utilizados por el programa que está matando -9'ed , bueno ... El SO está roto.
Sintaxis T3rr0r
1
Acerca del comando kill -9, la página de manual dice con más precisión: "9 KILL (kill no capturable, no ignorable)". SIGKILL una señal que es manejada por el sistema operativo, no por la aplicación.
user1338062
4
Simplemente killno es lo mismo que Ctrl-C, ya que killsin especificar qué señal enviar enviará SIGTERM, mientras que Ctrl-C envía SIGINT.
alesguzik

Respuestas:

136

Es imposible para cualquier programa, en cualquier idioma, manejar un SIGKILL. Esto es para que siempre sea posible terminar un programa, incluso si el programa tiene errores o es malicioso. Pero SIGKILL no es el único medio para finalizar un programa. La otra es utilizar un SIGTERM. Los programas pueden manejar esa señal. El programa debe manejar la señal haciendo un apagado controlado, pero rápido. Cuando una computadora se apaga, la etapa final del proceso de apagado envía a todos los procesos restantes un SIGTERM, les da a esos procesos unos segundos de gracia y luego les envía un SIGKILL.

La manera de manejar esto para nada otra que kill -9sería registrar un apagado gancho. Si puede usar ( SIGTERM ), kill -15el gancho de apagado funcionará. ( SIGINT ) kill -2 HACE que el programa salga de manera elegante y ejecute los ganchos de cierre.

Registra un nuevo gancho de cierre de máquina virtual.

La máquina virtual Java se apaga en respuesta a dos tipos de eventos:

  • El programa sale normalmente, cuando sale el último subproceso que no es un demonio o cuando se invoca el método exit (equivalentemente, System.exit), o
  • La máquina virtual finaliza en respuesta a una interrupción del usuario, como al escribir ^ C, o un evento en todo el sistema, como el cierre de sesión del usuario o el apagado del sistema.

He intentado lo siguiente programa de pruebas en OSX 10.6.3 y en el kill -9que lo hizo no correr el gancho de cierre, como se esperaba. En un kill -15, ejecuta el gancho de cierre cada vez.

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);
        }
    }
}

No hay forma de manejar con gracia un kill -9en ningún programa.

En raras circunstancias, la máquina virtual puede abortar, es decir, dejar de funcionar sin apagarse limpiamente. Esto ocurre cuando la máquina virtual se termina externamente, por ejemplo, con la señal SIGKILL en Unix o la llamada TerminateProcess en Microsoft Windows.

La única opción real para manejar un kill -9es hacer que otro programa de observación esté atento a que su programa principal desaparezca o usar un script de envoltura. Puede hacerlo con un script de shell que sondea el pscomando en busca de su programa en la lista y actúa en consecuencia cuando desaparece.

#!/usr/bin/env bash

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

No son formas de manejar sus propias señales en ciertos JVM - ver este artículo sobre el HotSpot JVM por ejemplo.

Al usar la sun.misc.Signal.handle(Signal, SignalHandler)llamada al método interno de Sun, también puede registrar un manejador de señales, pero probablemente no para señales como INTo TERMcomo las usa la JVM.

Para poder manejar cualquier señal, tendría que saltar de la JVM al territorio del sistema operativo.

Lo que generalmente hago para (por ejemplo) detectar una terminación anormal es lanzar mi JVM dentro de un script de Perl, pero hacer que el script espere a que la JVM use la waitpidllamada al sistema.

Luego se me informa cada vez que sale la JVM, y por qué salió, y puedo tomar las medidas necesarias.

Asgeir S. Nilsen
fuente
3
Tenga en cuenta que puede capturar INTy TERMcon sun.misc.Signal, pero no puede manejarlo QUITporque la JVM lo reserva para la depuración, ni KILLporque el sistema operativo terminará la JVM inmediatamente. Intentar manejar cualquiera de ellos generará un IllegalArgumentException.
dimo414
12

Esperaría que la JVM interrumpa con gracia ( thread.interrupt()) todos los subprocesos en ejecución creados por la aplicación, al menos para las señales SIGINT (kill -2)y SIGTERM (kill -15).

De esta manera, la señal se les enviará, lo que permitirá una elegante cancelación de subprocesos y finalización de recursos en las formas estándar .

Pero este no es el caso (al menos en mi aplicación 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).

Como comentaron otros usuarios, el uso de ganchos de cierre parece obligatorio.

Entonces, ¿cómo lo manejaría?

Bueno, primero, no me importa en todos los programas, solo en aquellos en los que quiero hacer un seguimiento de las cancelaciones de usuarios y los finales inesperados. Por ejemplo, imagine que su programa java es un proceso administrado por otro. Es posible que desee diferenciar si se ha terminado correctamente ( SIGTERMdel proceso del administrador) o si se ha producido un cierre (para reiniciar automáticamente el trabajo al inicio).

Como base, siempre hago que mis hilos de larga duración sean conscientes periódicamente del estado interrumpido y lanzo un mensaje InterruptedExceptionsi se interrumpieron. Esto permite la finalización de la ejecución de forma controlada por el desarrollador (que también produce el mismo resultado que las operaciones de bloqueo estándar). Luego, en el nivel superior de la pila de subprocesos, InterruptedExceptionse captura y se realiza la limpieza adecuada. Estos hilos están codificados para saber cómo responder a una solicitud de interrupción. Diseño de alta cohesión .

Entonces, en estos casos, agrego un gancho de apagado, que hace lo que creo que la JVM debería hacer de manera predeterminada: interrumpir todos los subprocesos que no son demonios creados por mi aplicación que aún se están ejecutando:

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");
    }
});

Solicitud de prueba completa en github: https://github.com/idelvall/kill-test

idelvall
fuente
6

Puede usar Runtime.getRuntime().addShutdownHook(...), pero no se le puede garantizar que se llamará en cualquier caso .

lexicore
fuente
12
Pero en el caso de kill -9 es casi seguro que no se ejecutará.
Presidente James K. Polk
1

Hay una forma de reaccionar ante un kill -9: es tener un proceso separado que supervise el proceso que se está matando y lo limpie si es necesario. Esto probablemente involucraría IPC y sería bastante trabajo, y aún puede anularlo eliminando ambos procesos al mismo tiempo. Supongo que no valdrá la pena en la mayoría de los casos.

Quien mata un proceso con -9 debería saber teóricamente lo que está haciendo y que puede dejar las cosas en un estado inconsistente.

Arno Schäfer
fuente