Подпроцесс умирает, когда поток умирает

У меня есть программа, которая запускает таймеры Python для запуска подпроцессов. Эти подпроцессы должны быть прекращены, как только моя программа будет завершена или уничтожена. Чтобы сделать это, я использую "prctl hack", который устанавливает, какой сигнал ребенок должен получить после смерти своего родителя. Нежелательное поведение, которое я получаю: даже если мой основной процесс запущен, детей убивают. Следующий код воссоздает проблему:

from threading import Timer
import time
import os
import subprocess
import ctypes
import signal

def set_pdeathsig():
        print("child PID: %d" % os.getpid())
        print("child's parent PID: %d" % os.getppid())
        prctl = ctypes.CDLL("libc.so.6").prctl
        PR_SET_PDEATHSIG = 1
        prctl(PR_SET_PDEATHSIG, signal.SIGTERM)

def thread1():
        subprocess.Popen(['sleep', 'infinity'], preexec_fn=set_pdeathsig)
        time.sleep(10)
        print("thread 1 finished")

def thread2():
        subprocess.Popen(['sleep', 'infinity'], preexec_fn=set_pdeathsig)
        time.sleep(10)
        print("thread 2 finished")

print("main thread PID: %d" % os.getpid())

t1 = Timer(1, thread1)
t2 = Timer(1, thread2)

t1.start()
t2.start()

time.sleep(100)

Вы можете заметить, что, прежде чем нити умирают, sleep процессы все еще работают. После того, как потоки таймера умирают, его соответствующий подпроцесс также умирает, даже когда основной поток жив.

1 ответ

Решение

Это ожидаемое и даже задокументированное поведение. Со страницы руководства prctl(2):

      Warning: the "parent" in this case is considered to be the
      thread that created this process.  In other words, the signal
      will be sent when that thread terminates (via, for example,
      pthread_exit(3)), rather than after all of the threads in the
      parent process terminate.

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

Я хотел бы добавить еще один поток и сделать процесс запуска оттуда. Будет ли что-то вроде этой работы:

from threading import Timer
from threading import Thread
import queue
import time
import os
import subprocess
import ctypes
import signal

def set_pdeathsig():
    print("child PID: %d" % os.getpid())
    print("child's parent PID: %d" % os.getppid())
    prctl = ctypes.CDLL("libc.so.6").prctl
    PR_SET_PDEATHSIG = 1
    prctl(PR_SET_PDEATHSIG, signal.SIGTERM)

def thread1(q):
    q.put(["sleep", "infinity"])
    time.sleep(5)
    print("thread 1 finished")

def thread2(q):
    q.put(["sleep", "infinity"])
    time.sleep(5)
    print("thread 2 finished")

def process_manager(q):
    while True:
        foo = q.get()
        subprocess.Popen(foo, preexec_fn=set_pdeathsig)

print("main thread PID: %d" % os.getpid())

qu = queue.Queue()
pm_thread = Thread(group=None, target=process_manager, args=(qu,))
pm_thread.daemon = True
pm_thread.start()


t1 = Timer(1, thread1, args=(qu,))
t2 = Timer(1, thread2, args=(qu,))

t1.start()
t2.start()

time.sleep(15)

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

Другие вопросы по тегам