Jak z wdziękiem obsługiwać sygnał SIGKILL w Javie


113

Jak radzisz sobie z czyszczeniem, gdy program otrzymuje sygnał zabicia?

Na przykład istnieje aplikacja, z którą się łączę, która chce, aby każda aplikacja innej firmy (moja aplikacja) wysyłała finishpolecenie podczas wylogowywania. Jak najlepiej wysłać to finishpolecenie, gdy moja aplikacja została zniszczona za pomocą kill -9?

edycja 1: kill -9 nie może zostać przechwycone. Dziękuję za poprawienie mnie.

edycja 2: Myślę, że ten przypadek miałby miejsce, gdy wywołuje się po prostu kill, co jest tym samym, co ctrl-c


44
kill -9znaczy dla mnie: „Precz, plugawy proces, precz z tobą!”, po czym proces ten ustanie. Natychmiast.
ZoogieZork

11
W większości * nixów, o których wiem, kill -9 nie może być przechwycone i z wdziękiem obsługiwane przez żaden program, bez względu na język, w którym został napisany.
Prezydent James K. Polk

2
@Begui: oprócz tego, co inni skomentowali i odpowiedzieli, JEŚLI Twój Un x OS nie zabija natychmiast * I ODZYSKIWA WSZYSTKIE ZASOBY używane przez program, który został zabity -9 ' , cóż ... System operacyjny jest uszkodzony.
Składnia T3rr0r

1
O poleceniu kill -9 strona podręcznika mówi dokładniej: „9 KILL (nieuchwytne, niezauważalne zabójstwo)”. SIGKILL sygnał obsługiwany przez system operacyjny, a nie przez aplikację.
user1338062

4
Po prostu killto nie to samo, co Ctrl-C, ponieważ killbez określenia, który sygnał do wysłania wyśle ​​SIGTERM, podczas gdy Ctrl-C wysyła SIGINT.
alesguzik

Odpowiedzi:


136

To jest niemożliwe dla każdego programu, w dowolnym języku, aby obsłużyć SIGKILL. Dzięki temu zawsze można zakończyć działanie programu, nawet jeśli zawiera on błędy lub jest złośliwy. Ale SIGKILL to nie jedyny sposób na zakończenie programu. Drugim jest użycie SIGTERM. Programy mogą obsłużyć ten sygnał. Program powinien obsłużyć sygnał, wykonując kontrolowane, ale szybkie zamknięcie. Gdy komputer się wyłącza, ostatni etap procesu zamykania wysyła do każdego pozostałego procesu SIGTERM, daje tym procesom kilka sekund przerwy, a następnie wysyła im SIGKILL.

Sposób obsługi to do niczego innego niż kill -9byłoby zarejestrować zamykania haka. Jeśli możesz użyć ( SIGTERM ), kill -15zaczep zamykający będzie działał. ( SIGINT ) kill -2 POWODUJE, że program wdzięcznie kończy pracę i uruchamia przechwyty zamykające.

Rejestruje nowy punkt zaczepienia zamykania maszyny wirtualnej.

Wirtualna maszyna Java wyłącza się w odpowiedzi na dwa rodzaje zdarzeń:

  • Program kończy działanie normalnie, gdy kończy się ostatni wątek niebędący demonem lub gdy wywoływana jest metoda exit (równoważnie System.exit) lub
  • Maszyna wirtualna jest przerywana w odpowiedzi na przerwanie użytkownika, takie jak wpisanie ^ C, lub zdarzenie dotyczące całego systemu, takie jak wylogowanie użytkownika lub zamknięcie systemu.

Wypróbowałem następujący program testowy na OSX 10.6.3 i na kill -9nim NIE uruchomiłem haka zamykającego, zgodnie z oczekiwaniami. Na zasadzie kill -15, że NIE uruchomić hakiem wyłączanie za każdym razem.

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

Nie ma sposobu, aby naprawdę wdzięcznie obsłużyć a kill -9w żadnym programie.

W rzadkich przypadkach maszyna wirtualna może przerwać działanie, to znaczy przestać działać bez czystego zamykania. Dzieje się tak, gdy maszyna wirtualna zostanie zakończona zewnętrznie, na przykład przez sygnał SIGKILL w systemie Unix lub wywołanie TerminateProcess w systemie Microsoft Windows.

Jedyną realną opcją do obsługi a kill -9jest posiadanie innego programu obserwującego, który obserwuje twój główny program, aby odszedł lub użyć skryptu opakowującego. Możesz to zrobić za pomocą skryptu powłoki, który odpytywał pspolecenie szukające twojego programu na liście i działał odpowiednio, gdy zniknął.

#!/usr/bin/env bash

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

12

Tam sposoby obsługi własnych sygnałów w pewnych JVMs - patrz artykuł o HotSpot JVM na przykład.

Korzystając z wewnętrznego sun.misc.Signal.handle(Signal, SignalHandler)wywołania metody Sun, możesz również zarejestrować obsługę sygnału, ale prawdopodobnie nie dla sygnałów takich jak INTlub w takiej TERMpostaci, w jakiej są one używane przez maszynę JVM.

Aby móc obsłużyć dowolny sygnał, musiałbyś wyskoczyć z JVM na terytorium systemu operacyjnego.

To, co zwykle robię, aby (na przykład) wykryć nieprawidłowe zakończenie, to uruchomienie maszyny JVM w skrypcie Perla, ale skrypt czekał na maszynę JVM przy użyciu waitpidwywołania systemowego.

Następnie jestem informowany o zamknięciu wirtualnej maszyny języka Java i o jego przyczynach oraz o możliwości podjęcia niezbędnych działań.


3
Zauważ, że możesz przechwytywać INTi za TERMpomocą sun.misc.Signal, ale nie możesz tego obsłużyć, QUITponieważ JVM rezerwuje ją do debugowania lub KILLponieważ system operacyjny natychmiast zakończy JVM. Próba obsłużenia jednego z nich spowoduje podniesienie IllegalArgumentException.
dimo414

12

Spodziewałbym się, że JVM z wdziękiem przerywa ( thread.interrupt()) wszystkie uruchomione wątki utworzone przez aplikację, przynajmniej dla sygnałów SIGINT (kill -2)i SIGTERM (kill -15).

W ten sposób sygnał zostanie do nich przekazany, umożliwiając wdzięczne anulowanie wątku i finalizację zasobów w standardowy sposób .

Ale to nie jest tak (przynajmniej w moim implementacji 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).

Jak komentowali inni użytkownicy, użycie haków zamykających wydaje się obowiązkowe.

Jak więc sobie z tym poradzę?

Po pierwsze, nie przejmuję się tym we wszystkich programach, tylko w tych, w których chcę śledzić anulowania użytkowników i nieoczekiwane zakończenia. Na przykład wyobraź sobie, że twój program java jest procesem zarządzanym przez inne. Możesz chcieć rozróżnić, czy zostało zakończone z wdziękiem ( SIGTERMz procesu menedżera), czy też nastąpiło zamknięcie (w celu automatycznego ponownego uruchomienia zadania podczas uruchamiania).

Jako podstawę, zawsze okresowo informuję moje długo działające wątki o stanie przerwania i wyrzucam, InterruptedExceptionjeśli zostały przerwane. Umożliwia to finalizację wykonania w sposób kontrolowany przez dewelopera (również z wynikiem identycznym jak standardowe operacje blokujące). Następnie na najwyższym poziomie stosu nici InterruptedExceptionjest przechwytywany i wykonywane jest odpowiednie czyszczenie. Wątki te są zakodowane, aby wiedzieć, jak odpowiedzieć na żądanie przerwania. Konstrukcja o wysokiej spójności .

Dlatego w takich przypadkach dodaję hak zamykający, który robi to, co myślę, że JVM powinna robić domyślnie: przerywa wszystkie wątki niebędące demonami utworzone przez moją aplikację, które nadal działają:

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

Kompletna aplikacja testowa na github: https://github.com/idelvall/kill-test


6

Możesz użyć Runtime.getRuntime().addShutdownHook(...), ale nie możesz zagwarantować, że zostanie wywołany w każdym przypadku .


12
Ale w przypadku kill -9 prawie na pewno nie zadziała.
Prezydent James K. Polk

1

Jest jeden sposób, aby zareagować na zabicie -9: to znaczy mieć oddzielny proces, który monitoruje zabijany proces i czyści po nim, jeśli to konieczne. Prawdopodobnie wymagałoby to IPC i wymagałoby sporo pracy, a nadal można to zastąpić, zabijając oba procesy w tym samym czasie. Zakładam, że w większości przypadków nie będzie to warte zachodu.

Ktokolwiek zabija proces z -9, powinien teoretycznie wiedzieć, co robi i że może to pozostawić rzeczy w stanie niespójności.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.