Как заставить дочерний процесс умереть после родительского выхода? - PullRequest
194 голосов
/ 12 ноября 2008

Предположим, у меня есть процесс, который порождает ровно один дочерний процесс. Теперь, когда родительский процесс завершается по какой-либо причине (обычно или ненормально, из-за kill, ^ C, подтверждения ошибки или чего-то еще), я хочу, чтобы дочерний процесс умер Как это сделать правильно?


Некоторые похожие вопросы по stackoverflow:


Некоторые похожие вопросы по стеку потока для Windows :

Ответы [ 23 ]

4 голосов
/ 21 марта 2017

Это решение сработало для меня:

  • Передать канал stdin потомку - вам не нужно записывать данные в поток.
  • Ребенок читает бесконечно от стандартного ввода до EOF. EOF сигнализирует, что родитель ушел.
  • Это надежный и портативный способ определить, когда родитель ушел. Даже если родительский сбой, ОС закроет канал.

Это был процесс рабочего типа, существование которого имело смысл только тогда, когда родитель был жив.

4 голосов
/ 12 ноября 2008

Установите обработчик ловушек для перехвата SIGINT, который убивает ваш дочерний процесс, если он еще жив, хотя другие постеры верны, что он не перехватит SIGKILL.

Откройте .lock-файл с монопольным доступом и сделайте так, чтобы дочерний опрос пытался его открыть - если открытие завершится успешно, дочерний процесс должен завершиться

3 голосов
/ 13 марта 2013

На некоторых постерах уже упоминались трубы и kqueue. Фактически вы также можете создать пару подключенных доменных сокетов Unix с помощью вызова socketpair(). Тип сокета должен быть SOCK_STREAM.

Предположим, у вас есть два дескриптора файлов сокетов: fd1, fd2. Теперь fork() для создания дочернего процесса, который будет наследовать fds. В родительском вы закрываете fd2, а в дочернем вы закрываете fd1. Теперь каждый процесс может poll() оставшийся открытый файл на своем конце для события POLLIN. Пока каждая сторона явно не указывает close() свой fd в течение обычного времени жизни, вы можете быть совершенно уверены, что флаг POLLHUP должен указывать окончание другой стороны (независимо от того, чиста она или нет). После уведомления об этом событии ребенок может решить, что ему делать (например, умереть).

#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdio.h>

int main(int argc, char ** argv)
{
    int sv[2];        /* sv[0] for parent, sv[1] for child */
    socketpair(AF_UNIX, SOCK_STREAM, 0, sv);

    pid_t pid = fork();

    if ( pid > 0 ) {  /* parent */
        close(sv[1]);
        fprintf(stderr, "parent: pid = %d\n", getpid());
        sleep(100);
        exit(0);

    } else {          /* child */
        close(sv[0]);
        fprintf(stderr, "child: pid = %d\n", getpid());

        struct pollfd mon;
        mon.fd = sv[1];
        mon.events = POLLIN;

        poll(&mon, 1, -1);
        if ( mon.revents & POLLHUP )
            fprintf(stderr, "child: parent hung up\n");
        exit(0);
    }
}

Вы можете попробовать скомпилировать приведенный выше код проверки концепции и запустить его в терминале, например ./a.out &. У вас есть примерно 100 секунд, чтобы поэкспериментировать с уничтожением родительского PID различными сигналами, иначе он просто выйдет. В любом случае, вы должны увидеть сообщение «child: parent Hangable».

По сравнению с методом, использующим обработчик SIGPIPE, этот метод не требует попытки вызова write().

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

Это решение вызывает только функции POSIX. Я пробовал это в Linux и FreeBSD. Я думаю, что это должно работать на других Unix, но я действительно не проверял.

Смотри также:

  • unix(7) справочных страниц Linux, unix(4) для FreeBSD, poll(2), socketpair(2), socket(7) для Linux.
3 голосов
/ 09 декабря 2010

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

1 голос
/ 08 января 2013

В случае, если это имеет отношение к кому-либо еще, когда я порождаю экземпляры JVM в разветвленных дочерних процессах из C ++, единственный способ заставить экземпляры JVM правильно завершиться после завершения родительского процесса - это сделать следующее. Надеюсь, кто-то может оставить отзыв в комментариях, если это не лучший способ сделать это.

1) Вызовите prctl(PR_SET_PDEATHSIG, SIGHUP) для дочернего процесса разветвления, как было предложено, перед запуском приложения Java через execv и

2) Добавьте хук отключения к приложению Java, который опрашивает, пока его родительский PID не станет равным 1, затем выполните сложный Runtime.getRuntime().halt(0). Опрос выполняется путем запуска отдельной оболочки, которая запускает команду ps (см .: Как мне найти мой PID в Java или JRuby в Linux? ).

РЕДАКТИРОВАТЬ 130118:

Кажется, это не было надежным решением. Я все еще немного пытаюсь понять нюансы происходящего, но я все еще иногда получал бесхозные процессы JVM при запуске этих приложений в сеансах screen / SSH.

Вместо того, чтобы опрашивать PPID в приложении Java, я просто заставил хук выключения выполнить очистку с последующей полной остановкой, как описано выше. Затем я убедился, что вызывал waitpid в родительском приложении C ++ для порожденного дочернего процесса, когда пришло время завершать все. Это кажется более надежным решением, так как дочерний процесс гарантирует его завершение, в то время как родитель использует существующие ссылки, чтобы убедиться, что его дочерние элементы завершаются. Сравните это с предыдущим решением, в котором родительский процесс завершался всякий раз, когда ему было удобно, и когда дети пытались выяснить, остались ли они сиротами перед завершением.

1 голос
/ 12 ноября 2008

Под POSIX , функции exit(), _exit() и _Exit() определены для:

  • Если процесс является процессом управления, сигнал SIGHUP должен отправляться каждому процессу в группе процессов переднего плана управляющего терминала, принадлежащего вызывающему процессу.

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

Обратите внимание, что вам, возможно, придется прочитать довольно много мелкого шрифта - включая раздел «Базовые определения (определения)», а также информацию о системных службах для exit() и setsid() и setpgrp() - чтобы получить полное картина. (Я бы тоже!)

1 голос
/ 06 декабря 2011

Если вы отправляете сигнал на pid 0, используя, например,

kill(0, 2); /* SIGINT */

этот сигнал отправляется всей группе процессов, тем самым фактически убивая дочернего элемента.

Вы можете легко проверить это с помощью чего-то вроде:

(cat && kill 0) | python

Если вы затем нажмете ^ D, вы увидите текст "Terminated" как указание на то, что интерпретатор Python действительно был убит, а не просто закрыт из-за закрытия стандартного ввода.

0 голосов
/ 08 марта 2017

Исторически в UNIX v7 система процессов обнаружила потерю процессов, проверив родительский идентификатор процесса. Как я уже говорил, исторически системный процесс init(8) - это особый процесс только по одной причине: он не может умереть. Он не может умереть, потому что алгоритм ядра, чтобы иметь дело с назначением нового идентификатора родительского процесса, зависит от этого факта. когда процесс выполняет свой вызов exit(2) (посредством системного вызова процесса или внешней задачи, отправляющей ему сигнал или тому подобное), ядро ​​переназначает всем дочерним элементам этого процесса идентификатор процесса init в качестве идентификатора их родительского процесса. Это приводит к самому легкому тестированию и наиболее портативному способу узнать, не стал ли процесс потерянным. Просто проверьте результат системного вызова getppid(2) и, если это идентификатор процесса init(2), то процесс потерял связь с системой перед системным вызовом.

При таком подходе возникают две проблемы, которые могут привести к проблемам:

  • Во-первых, у нас есть возможность изменить процесс init на любой пользовательский процесс. Как мы можем гарантировать, что процесс init всегда будет родительским для всех потерянных процессов? Ну, в коде системного вызова exit есть явная проверка, чтобы увидеть, является ли процесс, выполняющий вызов, процессом init (процесс с pid, равным 1), и если это так, ядро ​​паникует (не должно быть в состоянии больше для поддержания иерархии процессов), поэтому процессу init не разрешается делать вызов exit(2).
  • во-вторых, в базовом тесте, описанном выше, есть состояние гонки. Идентификатор процесса init предполагается исторически равным 1, но это не гарантируется подходом POSIX, который утверждает (как показано в другом ответе), что для этой цели зарезервирован только идентификатор процесса системы. Практически ни одна реализация posix не делает этого, и вы можете предположить, что в исходных системах, производных от Unix, наличие 1 в качестве ответа системного вызова getppid(2) достаточно, чтобы предположить, что процесс потерян. Другой способ проверить это сделать getppid(2) сразу после разветвления и сравнить это значение с результатом нового вызова. Это просто не работает во всех случаях, так как оба вызова не являются атомарными, и родительский процесс может умереть после fork(2) и до первого системного вызова getppid(2). Процесс parent id only changes once, when its parent does an exit (2) call, so this should be enough to check if the getppid (2) result changed between calls to see that parent process has exit. This test is not valid for the actual children of the init process, because they are always children of init (8) `, но вы можете смело предполагать, что эти процессы также не имеют родителя (кроме случаев, когда вы заменяете в системе процесс init)
0 голосов
/ 21 августа 2016

Мне удалось создать портативное решение без опроса с 3 процессами, злоупотребив терминальным управлением и сеансами. Это умственная мастурбация, но работает.

Хитрость:

  • процесс А запущен
  • процесс A создает канал P (и никогда не читает из него)
  • процесс A переходит в процесс B
  • процесс B создает новый сеанс
  • процесс B выделяет виртуальный терминал для этого нового сеанса
  • процесс B устанавливает обработчик SIGCHLD, чтобы умереть при выходе ребенка
  • процесс B устанавливает обработчик SIGPIPE
  • процесс B переходит в процесс C
  • процесс C делает все, что ему нужно (например, exec () - неизмененный двоичный файл или выполняет любую логику)
  • процесс B записывает в канал P (и блокирует таким образом)
  • process A wait () s для процесса B и завершается, когда он умирает

Таким образом:

  • если процесс A умирает: процесс B получает SIGPIPE и умирает
  • если процесс B умирает: ожидание процесса A () возвращается и умирает, процесс C получает SIGHUP (поскольку, когда лидер сеанса сеанса с подключенным терминалом умирает, все процессы в группе процессов переднего плана получают SIGHUP)
  • если процесс C умирает: процесс B получает SIGCHLD и умирает, поэтому процесс A умирает

Недостатки:

  • процесс C не может обработать SIGHUP
  • процесс C будет запущен в другом сеансе
  • процесс C не может использовать API сеанса / группы процессов, потому что он сломает хрупкую настройку
  • создание терминала для каждой такой операции - не самая лучшая идея
0 голосов
/ 25 октября 2016

Несмотря на то, что прошло уже 7 лет, я только что столкнулся с этой проблемой, так как я запускаю приложение SpringBoot, которому нужно запускать webpack-dev-server во время разработки и нужно убить его, когда серверный процесс останавливается.

Я пытаюсь использовать Runtime.getRuntime().addShutdownHook, но это работает на Windows 10, но не на Windows 7.

Я изменил его, чтобы использовать выделенный поток, ожидающий завершения процесса, или InterruptedException, который, кажется, работает правильно в обеих версиях Windows.

private void startWebpackDevServer() {
    String cmd = isWindows() ? "cmd /c gradlew webPackStart" : "gradlew webPackStart";
    logger.info("webpack dev-server " + cmd);

    Thread thread = new Thread(() -> {

        ProcessBuilder pb = new ProcessBuilder(cmd.split(" "));
        pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
        pb.directory(new File("."));

        Process process = null;
        try {
            // Start the node process
            process = pb.start();

            // Wait for the node process to quit (blocking)
            process.waitFor();

            // Ensure the node process is killed
            process.destroyForcibly();
            System.setProperty(WEBPACK_SERVER_PROPERTY, "true");
        } catch (InterruptedException | IOException e) {
            // Ensure the node process is killed.
            // InterruptedException is thrown when the main process exit.
            logger.info("killing webpack dev-server", e);
            if (process != null) {
                process.destroyForcibly();
            }
        }

    });

    thread.start();
}
...