Как изящно обработать сигнал SIGKILL в Java

113

Как вы справляетесь с очисткой, когда программа получает сигнал уничтожения?

Например, есть приложение, к которому я подключаюсь, которое хочет, чтобы какое-либо стороннее приложение (мое приложение) отправляло finishкоманду при выходе из системы. Что лучше всего сказать, чтобы отправить эту finishкоманду, когда мое приложение было уничтожено с помощью kill -9?

редактировать 1: kill -9 не может быть захвачен. Спасибо, ребята, что поправили меня.

изменить 2: я думаю, этот случай будет, когда один вызывает просто kill, что совпадает с ctrl-c

Беги
источник
44
kill -9означает для меня: «Уходи, гнусный процесс, прочь с собой!», после чего процесс прекратит свое существование. Немедленно.
ZoogieZork
11
На большинстве известных мне * никсов kill -9 не может быть перехвачен и корректно обработан любой программой, на каком бы языке она ни была написана.
Президент Джеймс К. Полк,
2
@Begui: в дополнение к тому, что другие прокомментировали и ответили, ЕСЛИ ваша ОС Un x не убивает мгновенно * И СОЗДАВАЕТ ВСЕ РЕСУРСЫ, используемые программой, которую убивает -9 , ну ... ОС сломана.
SyntaxT3rr0r
1
О команде kill -9 на странице руководства более точно сказано: «9 УБИЙСТВ (неуловимое, не игнорируемое убийство)». SIGKILL - это сигнал, который обрабатывается ОС, а не приложением.
user1338062
4
Просто killне то же самое, что Ctrl-C, поскольку killбез указания того, какой сигнал отправлять, будет отправляться SIGTERM, тогда как Ctrl-C отправляет SIGINT.
alesguzik

Ответы:

136

Это невозможно для любой программы, на любом языке, обрабатывать SIGKILL. Это значит, что всегда можно завершить программу, даже если она содержит ошибки или вредоносна. Но SIGKILL - не единственное средство для завершения программы. Другой - использовать SIGTERM. Программы могут обрабатывать этот сигнал. Программа должна обрабатывать сигнал, выполняя контролируемое, но быстрое завершение работы. Когда компьютер выключается, последний этап процесса выключения отправляет каждому оставшемуся процессу сигнал SIGTERM, дает этим процессам отсрочку в несколько секунд, а затем отправляет им сигнал SIGKILL.

Способ справиться с этим что - нибудь другое , чем kill -9было бы зарегистрировать выключение крючок. Если вы можете использовать ( SIGTERM ), kill -15ловушка выключения будет работать. ( SIGINT ) kill -2 ДЕЙСТВИТЕЛЬНО заставляет программу корректно завершиться и запустить обработчики завершения работы.

Регистрирует новую ловушку завершения работы виртуальной машины.

Виртуальная машина Java выключается в ответ на два типа событий:

  • Программа завершается нормально, когда завершается последний поток, не являющийся демоном, или когда вызывается метод exit (эквивалентно System.exit), или
  • Виртуальная машина завершает работу в ответ на прерывание пользователя, такое как ввод ^ C, или общесистемное событие, такое как выход пользователя из системы или завершение работы системы.

Я попробовал следующую тестовую программу на OSX 10.6.3, но kill -9она НЕ запустила ловушку выключения, как ожидалось. На kill -15нем каждый раз ДЕЙСТВИТЕЛЬНО запускается ловушка выключения.

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

Нет никакого способа действительно изящно обработать a kill -9в любой программе.

В редких случаях виртуальная машина может аварийно завершить работу, то есть перестать работать без полного завершения работы. Это происходит, когда виртуальная машина завершается извне, например, сигналом SIGKILL в Unix или вызовом TerminateProcess в Microsoft Windows.

Единственный реальный вариант для обработки a kill -9- это поручить другой программе-наблюдателю следить за тем, чтобы ваша основная программа ушла, или использовать сценарий-оболочку. Вы можете сделать это с помощью сценария оболочки, который опрашивал psкоманду, ищущую вашу программу в списке, и действовал соответствующим образом, когда она исчезла.

#!/usr/bin/env bash

java TestShutdownHook
wait
# notify your other app that you quit
echo "TestShutdownHook quit"
Raedwald
источник
12

Там являются способы обработки собственных сигналов в определенных виртуальных машинах - см этой статьи о HotSpot JVM , например.

Используя внутренний sun.misc.Signal.handle(Signal, SignalHandler)вызов метода Sun, вы также можете зарегистрировать обработчик сигнала, но, вероятно, не для сигналов, подобных INTили TERMиспользуемых JVM.

Чтобы иметь возможность обрабатывать любой сигнал, вам придется выпрыгнуть из JVM на территорию операционной системы.

Что я обычно делаю (например), чтобы обнаружить ненормальное завершение, - это запустить мою JVM внутри сценария Perl, но заставить сценарий ждать JVM с помощью waitpidсистемного вызова.

Затем я получаю информацию о выходе из JVM и о причинах выхода и могу предпринять необходимые действия.

Асгейр С. Нильсен
источник
3
Обратите внимание, что вы можете захватывать INTи TERMс sun.misc.Signal, но вы не можете обрабатывать, QUITпотому что JVM резервирует его для отладки, а также KILLпотому, что ОС немедленно завершит JVM. Попытка справиться с любым из них приведет к возникновению IllegalArgumentException.
dimo414
12

Я ожидал, что JVM корректно прервет ( thread.interrupt()) все запущенные потоки, созданные приложением, по крайней мере, для сигналов SIGINT (kill -2)и SIGTERM (kill -15).

Таким образом, сигнал будет переадресован им, что позволит изящно отменить поток и завершить ресурс стандартными способами .

Но это не так (по крайней мере , в моей реализации 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).

Как отмечали другие пользователи, использование хуков выключения кажется обязательным.

Итак, как мне с этим справиться?

Ну, во-первых, меня это не волнует во всех программах, только в тех, где я хочу отслеживать отмены пользователей и неожиданные окончания. Например, представьте, что ваша java-программа - это процесс, которым управляет другой. Возможно, вы захотите различить, было ли оно корректно завершено ( SIGTERMиз процесса менеджера) или произошло завершение работы (чтобы автоматически перезапустить задание при запуске).

Как правило, я всегда заставляю свои долго работающие потоки периодически узнавать о состоянии прерывания и бросать, InterruptedExceptionесли они прерваны. Это позволяет завершить выполнение способом, контролируемым разработчиком (что также дает тот же результат, что и стандартные операции блокировки). Затем InterruptedExceptionзахватывается верхний уровень стека потоков и выполняется соответствующая очистка. Эти потоки закодированы, чтобы знать, как отвечать на запрос прерывания. Конструкция с высокой степенью сцепления .

Итак, в этих случаях я добавляю ловушку выключения, которая делает то, что, по моему мнению, JVM должна делать по умолчанию: прерывает все не-демонические потоки, созданные моим приложением, которые все еще работают:

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

Заполните тестовое приложение на github: https://github.com/idelvall/kill-test

Idelvall
источник
6

Можно использовать Runtime.getRuntime().addShutdownHook(...), но нельзя гарантировать, что он будет вызван в любом случае .

lexicore
источник
12
Но в случае kill -9 он почти наверняка не запустится.
Президент Джеймс К. Полк,
1

Есть один способ отреагировать на kill -9: создать отдельный процесс, который следит за убиваемым процессом и при необходимости очищает его после него. Это, вероятно, будет связано с IPC и потребует довольно много работы, и вы все равно можете переопределить его, убив оба процесса одновременно. Я полагаю, что в большинстве случаев это не стоит того.

Тот, кто убивает процесс с помощью -9, теоретически должен знать, что он / она делает, и что это может привести к несогласованному состоянию.

Арно Шефер
источник