Убивание детей родителем

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

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

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

Я пробовал atexit, двойную вилку и ptys. atexit не работает для SIGKILL; двойная вилка не работает вообще; и ptys Я не нашел способа работать с использованием python.

Сегодня я узнал о prctl(PR_SET_PDEATHSIG, SIGKILL), который должен быть способом для дочерних процессов заказывать убийство на себя, когда их родитель умирает. Я попытался использовать его с popen, но он не имеет никакого эффекта:

import ctypes, subprocess
libc = ctypes.CDLL('/lib/libc.so.6')
PR_SET_PDEATHSIG = 1; TERM = 15
implant_bomb = lambda: libc.prctl(PR_SET_PDEATHSIG, TERM)
subprocess.Popen(['gnuchess'], preexec_fn=implant_bomb)

В приведенном выше примере создается дочерний элемент, и родительский элемент выходит из него. Теперь вы ожидаете, что gnuchess получит SIGKILL и умрет, но это не так. Я все еще могу найти его в своем диспетчере процессов, используя 100% -ный процессор.

Может кто-нибудь сказать мне, если что-то не так с моим использованием prctl?, или вы знаете, как терминалы могут убивать своих детей?

Ответ 1

prctl PR_SET_DEATHSIG может быть установлен только для этого самого процесса, вызывающего prctl - не для любой другой процесс, включая этих конкретных детей процесса. То, как на странице man, на которую я указываю, выражает это: "Это значение очищается на fork()" - fork, конечно же, это способ, которым генерируются другие процессы (в Linux и любой другой ОС Unix-y).

Если у вас нет контроля над кодом, который вы хотите запустить в подпроцессах (как это было бы, по существу, для вашего примера gnuchess), я предлагаю вам сначала создать отдельный небольшой "мониторный" процесс с ролью отслеживая всех своих братьев и сестер (ваш родительский процесс может позволить монитору узнать о том, кто из этих братьев и сестер, как он их порождает) и отправки им сигналов убийцы, когда общий родитель умирает (монитор должен опросить это, просыпаясь каждые N секунд для некоторого N вашего выбора, чтобы проверить, жив ли родитель, используйте select для ожидания дополнительной информации от родителя с тайм-аутом в течение N секунд в цикле).

Не тривиально, но тогда таких системных задач часто нет. Терминалы делают это по-разному (через концепцию "контрольного терминала" для группы процессов), но, конечно, для любого ребенка тривиально блокировать его (двойные вилки, nohup и т.д.).

Ответ 2

Я знаю, что это были годы, но я нашел простое (слегка взломанное) решение этой проблемы. Из вашего родительского процесса, перенося все ваши вызовы по очень простой программе на C, которая вызывает prctl(), а затем exec() решает эту проблему в Linux. Я называю это "yeshup":

#include <linux/prctl.h>
#include <signal.h>
#include <unistd.h>

int main(int argc, char **argv) {
     if(argc < 2)
          return 1;
     prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
     return execvp(argv[1], &argv[1]);
}

Когда нерестится ваш ребенок с Python (или любого другого языка), вы можете запустить "yeshup gnuchess [argments]". Вы обнаружите, что, когда родительский процесс убит, все ваши дочерние процессы (должны) должны быть даны SIGHUP красиво.

Это работает, потому что Linux будет выполнять вызов prctl (не очистить его) даже после вызова execvp (который эффективно "трансформирует" процесс yeshup в процесс gnuchess или любую команду, которую вы там укажете), в отличие от fork().

Ответ 3

На самом деле я обнаружил, что ваш оригинальный подход работал отлично для меня - вот точный пример кода, который я тестировал, с которым работал:

echoer.py

#!/bin/env python

import time
import sys
i = 0
try:
    while True:
        i += 1
        print i
        time.sleep(1)
except KeyboardInterrupt:
    print "\nechoer caught KeyboardInterrupt"
    exit(0)

parentProc.py

#!/bin/env python

import ctypes
import subprocess
import time

libc = ctypes.CDLL('/lib64/libc.so.6')
PR_SET_PDEATHSIG = 1
SIGINT = 2
SIGTERM = 15

def set_death_signal(signal):
    libc.prctl(PR_SET_PDEATHSIG, signal)

def set_death_signal_int():
    set_death_signal(SIGINT)

def set_death_signal_term():
    set_death_signal(SIGTERM)

#subprocess.Popen(['./echoer.py'], preexec_fn=set_death_signal_term)
subprocess.Popen(['./echoer.py'], preexec_fn=set_death_signal_int)
time.sleep(1.5)
print "parentProc exiting..."

Ответ 4

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

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

Ответ 5

Я видел очень неприятные способы "очистки", используя такие вещи, как ps xuawww | grep myApp | awk '{ print $1}' | xargs -n1 kill -9

Клиентский процесс, если он пополняется, может поймать SIG_PIPE и умереть. Есть много способов сделать это, но это действительно зависит от множества факторов. Если вы выбросите код ping (ping to parent) в дочерний элемент, вы можете гарантировать, что SIG_PIPE будет выпущен при смерти. Если он поймает его, что он должен, он прекратится. Для этого вам понадобится двунаправленная связь, чтобы она работала правильно... или всегда блокировать клиента как создателя связи. Если вы не хотите изменять ребенка, игнорируйте это.

Предполагая, что вы не ожидаете, что фактический интерпретатор Python будет segfault, вы можете добавить каждый PID в последовательность, а затем убить при выходе. Это должно быть безопасным для выхода и даже исключенных исключений. У Python есть возможности для выполнения кода выхода... для очистки.

Здесь немного более безопасно: добавьте каждый дочерний PID в файл, включая ваш мастер-процесс (отдельный файл). Используйте блокировку файлов. Создайте сторожевого демона, который смотрит на состояние стаи() вашего основного pid. Если он не заблокирован, убейте каждый PID в списке PID вашего ребенка. Запустите тот же код при запуске.

Более неприятно: напишите PID в файлы, как указано выше, затем запустите свое приложение в под-оболочке: (./myMaster; ./killMyChildren)

Ответ 6

Мне интересно, очищается ли флаг PR_SET_PDEATHSIG, даже если вы установили его после fork (и до exec), поэтому кажется, что из документов не должно быть очищено.

Чтобы проверить эту теорию, вы можете попробовать следующее: использовать тот же код для запуска подпроцесса, написанного на C, и в основном просто вызывает prctl(PR_GET_PDEATHSIG, &result) и выводит результат.

Еще одна вещь, которую вы можете попробовать: добавив явные нули для arg3, arg4 и arg5, когда вы вызываете prctl. I.e.:

>>> implant_bomb = lambda: libc.prctl(PR_SET_PDEATHSIG, TERM, 0, 0, 0)

Ответ 7

Есть какое-то ограничение безопасности, которое нужно учитывать, потому что, если мы будем называть setuid после execv, он не сможет получить сигнал. Полный список этих ограничений здесь

удачи!
/Mohamed