Процессы Python перестают отвечать на SIGTERM/SIGINT после перезапуска

У меня странная проблема с некоторыми процессами Python, работающими с использованием процесса сторожевого таймера.

Процесс сторожевого таймера написан на python и является родительским, и имеет функцию с именем start_child(name), которая использует subprocess.Popen для открытия дочернего процесса. Объект Popen записывается, чтобы сторожевой таймер мог отслеживать процесс с помощью poll() и, в конечном итоге, завершать его с помощью terminate(), когда это необходимо. Если дочерний элемент неожиданно умирает, сторожевой таймер снова вызывает start_child(name) и записывает новый объект Popen.

Есть 7 дочерних процессов, все они тоже python. Если я запускаю любой из дочерних процессов вручную, я могу отправить SIGTERM или SIGINT с помощью kill и получить ожидаемые результаты (процесс завершается).

Однако при запуске из сторожевого процесса дочерний процесс завершится только после сигнала FIRST. Когда сторожевой таймер перезапускает дочерний процесс, новый дочерний процесс больше не отвечает на SIGTERM или SIGINT. Я понятия не имею, что вызывает это.

watchdog.py

class watchdog:
    # <snip> various init stuff

    def start(self):
        self.running = true

        kids = ['app1', 'app2', 'app3', 'app4', 'app5', 'app6', 'app7']
        self.processes = {}

        for kid in kids:
            self.start_child(kid)

        self.thread = threading.Thread(target=self._monitor)
        self.thread.start()

        while self.running:
            time.sleep(10)

    def start_child(self, name):
        try:
            proc = subprocess.Popen(name)
            self.processes[name] = proc
        except:
            print "oh no"
        else:
            print "started child ok"

    def _monitor(self):
        while self.running:
            time.sleep(1)
            if self.running:
                for kid, proc in self.processes.iteritems():
                    if proc.poll() is not None: # process ended
                        self.start_child(kid)

Итак, что происходит, watchdog.start() запускает все 7 процессов, и если я отправлю SIGTERM какому-либо процессу, он завершится, и поток монитора запустит его снова. Однако, если я затем отправлю новый процесс SIGTERM, он его проигнорирует.

Я должен иметь возможность снова и снова отправлять kill -15 перезапущенным процессам. Почему они игнорируют его после перезапуска?


person gdm    schedule 15.07.2009    source источник
comment
Таким образом, кажется, что это вызвано открытием процесса внутри потока Python. Согласно blogs.gentoo.org/agaffney/2005/03/18/python_sucks , Python устанавливает маску сигнала, чтобы блокировать все сигналы в процессах, запущенных из потоков. Какого черта, Питон? Теперь я пытаюсь использовать ctypes для вызова sigprocmask() и сброса маски сигнала, чтобы она не блокировалась.   -  person gdm    schedule 16.07.2009


Ответы (2)


Как описано здесь: http://blogs.gentoo.org/agaffney/2005/03/18/python_sucks , когда Python создает новый поток, он блокирует все сигналы для этого потока (и для любых процессов, порожденных этим потоком).

Я исправил это с помощью sigprocmask, вызываемого через ctypes. Это может быть или не быть «правильным» способом сделать это, но он работает.

В дочернем процессе во время __init__:

libc = ctypes.cdll.LoadLibrary("libc.so")
mask = '\x00' * 17 # 16 byte empty mask + null terminator 
libc.sigprocmask(3, mask, None) # '3' on FreeBSD is the value for SIG_SETMASK
person gdm    schedule 15.07.2009
comment
Смешивание любых двух из fork/exec, потоков и сигналов трудно получить правильно. Смешивание всех трех — это рецепт катастрофы. - person Miles; 16.07.2009
comment
Я упоминал, что сам процесс сторожевого таймера является демоном, который несколько раз разветвляется, чтобы отсоединиться? вкусная катастрофа. - person gdm; 16.07.2009
comment
sigprocmask() теперь запланирован для Python 3.2: ‹bugs.python.org/issue8407 - person Martin Carpenter; 25.06.2010
comment
Я пишу тестовую программу, которая запускает несколько экземпляров сервера (с его собственными подпроцессами) и проверяет их взаимодействие. Ваш ответ спас мою возможность сделать это на чистом питоне. - person nflacco; 27.06.2013

Не лучше ли восстановить обработчики сигналов по умолчанию в Python, а не через ctypes? В вашем дочернем процессе используйте сигнальный модуль:

import signal
for sig in range(1, signal.NSIG):
    try:
        signal.signal(sig, signal.SIG_DFL)
    except RuntimeError:
        pass

RuntimeError возникает при попытке установить такие сигналы, как SIGKILL, которые невозможно перехватить.

person mhawke    schedule 16.07.2009
comment
Это не работает, потому что все сигналы маскируются. Независимо от того, что вы делаете с signal.signal(), процесс никогда не получит сигнал. На самом деле я использую signal.signal() для установки своих обработчиков для SIGTERM (чтобы я мог очиститься при выходе), но вам все равно нужно использовать sigprocmask, чтобы процесс мог видеть SIGTERM. - person gdm; 16.07.2009
comment
@gdm: извините за это, я не знаю, как сделать это в Python, поэтому вызов через ctype, вероятно, единственный способ. - person mhawke; 17.07.2009