Убедитесь, что подпроцессы не работают при выходе из программы Python - PullRequest
54 голосов
/ 26 ноября 2008

Есть ли способ убедиться, что все созданные подпроцесс не работают во время выхода из программы на Python? Под подпроцессом я подразумеваю те, что созданы с помощью subprocess.Popen ().

Если нет, должен ли я повторить все убийства, связанные с выдачей, а затем убить -9? что-нибудь чище?

Ответы [ 14 ]

41 голосов
/ 26 ноября 2008

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

atexit.register (func [, * args [, ** kargs]])

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

>>> import atexit
>>> import sys
>>> import time
>>> 
>>> 
>>>
>>> def cleanup():
...     timeout_sec = 5
...     for p in all_processes: # list of your processes
...         p_sec = 0
...         for second in range(timeout_sec):
...             if p.poll() == None:
...                 time.sleep(1)
...                 p_sec += 1
...         if p_sec >= timeout_sec:
...             p.kill() # supported from python 2.6
...     print 'cleaned up!'
...
>>>
>>> atexit.register(cleanup)
>>>
>>> sys.exit()
cleaned up!

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

Следующий метод windows больше не нужен для python> = 2.6

Вот способ убить процесс в Windows. Ваш объект Popen имеет атрибут pid, так что вы можете просто вызвать его по success = win_kill (p.pid) (необходимо pywin32 установлено):

    def win_kill(pid):
        '''kill a process by specified PID in windows'''
        import win32api
        import win32con

        hProc = None
        try:
            hProc = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, pid)
            win32api.TerminateProcess(hProc, 0)
        except Exception:
            return False
        finally:
            if hProc != None:
                hProc.Close()

        return True
33 голосов
/ 27 ноября 2008

На * nix'ах, может быть, вам поможет использование групп процессов - вы можете также отследить подпроцессы, порожденные вашими подпроцессами.

if __name__ == "__main__":
  os.setpgrp() # create new process group, become its leader
  try:
    # some code
  finally:
    os.killpg(0, signal.SIGKILL) # kill all processes in my group

Другое соображение заключается в расширении сигналов: от SIGTERM (сигнал по умолчанию для kill) до SIGKILL (a.k.a kill -9). Подождите некоторое время между сигналами, чтобы дать процессу возможность чистого выхода, прежде чем kill -9 it.

14 голосов
/ 26 ноября 2008

subprocess.Popen.wait() - единственный способ убедиться, что они мертвы. Действительно, ОС POSIX требуют, чтобы вы ждали своих детей. Многие * nix's создадут процесс «зомби»: мертвый ребенок, которого родитель не ждал.

Если ребенок достаточно хорошо написан, он заканчивается. Часто дети читают из ТРУБ. Закрытие ввода - это большой намек на то, что ребенок должен закрыть магазин и выйти.

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

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


Редактировать.

В стандартных ОС у вас есть os.kill( PID, 9 ). Kill -9 суров, кстати. Если вы можете убить их с помощью SIGABRT (6?) Или SIGTERM (15), это более вежливо.

В ОС Windows у вас нет os.kill, который работает. Посмотрите на этот ActiveState Recipe для завершения процесса в Windows.

У нас есть дочерние процессы, которые являются серверами WSGI. Чтобы прекратить их, мы делаем GET по специальному URL; это заставляет ребенка убирать и выходить.

4 голосов
/ 04 декабря 2014

Внимание: только для Linux! Вы можете заставить своего ребенка получать сигнал, когда его родитель умирает.

Сначала установите python-prctl == 1.5.0, затем измените ваш родительский код, чтобы запустить ваши дочерние процессы следующим образом

subprocess.Popen(["sleep", "100"], preexec_fn=lambda: prctl.set_pdeathsig(signal.SIGKILL))

Что это говорит:

  • запуск подпроцесса: сон 100
  • после разветвления и перед выполнением подпроцесса дочерний процесс регистрируется как «отправь мне SIGKILL» когда мой родитель заканчивается ".
3 голосов
/ 08 января 2015

orip ответ полезен, но имеет недостаток в том, что он убивает ваш процесс и возвращает код ошибки вашего родителя. Я избегал этого так:

class CleanChildProcesses:
  def __enter__(self):
    os.setpgrp() # create new process group, become its leader
  def __exit__(self, type, value, traceback):
    try:
      os.killpg(0, signal.SIGINT) # kill all processes in my group
    except KeyboardInterrupt:
      # SIGINT is delievered to this process as well as the child processes.
      # Ignore it so that the existing exception, if any, is returned. This
      # leaves us with a clean exit code if there was no exception.
      pass

А потом:

  with CleanChildProcesses():
    # Do your work here

Конечно, вы можете сделать это с помощью try / исключением / finally, но вы должны обрабатывать исключительные и не исключительные случаи отдельно.

3 голосов
/ 22 марта 2014

Мне нужна была небольшая вариация этой проблемы (очистка подпроцессов, но без выхода из самой программы Python), и поскольку она не упоминается здесь среди других ответов:

p=subprocess.Popen(your_command, preexec_fn=os.setsid)
os.killpg(os.getpgid(p.pid), 15)

setsid запустит программу в новом сеансе, назначив ему и его дочерним процессам новую группу процессов. вызов os.killpg для него, таким образом, также не остановит ваш собственный процесс Python.

3 голосов
/ 26 ноября 2008

опрос ()

Проверьте, завершен ли дочерний процесс. Возвращает атрибут кода возврата.

2 голосов
/ 01 апреля 2017

Найдите решение для Linux (без установки prctl):

def _set_pdeathsig(sig=signal.SIGTERM):
    """help function to ensure once parent process exits, its childrent processes will automatically die
    """
    def callable():
        libc = ctypes.CDLL("libc.so.6")
        return libc.prctl(1, sig)
    return callable


subprocess.Popen(your_command, preexec_fn=_set_pdeathsig(signal.SIGTERM)) 
2 голосов
/ 29 декабря 2016

Мне действительно нужно было это сделать, но это включало запуск удаленных команд. Мы хотели иметь возможность остановить процессы, закрыв соединение с сервером. Также, если, например, вы работаете в Python Repl, вы можете выбрать запуск в качестве переднего плана, если вы хотите использовать Ctrl-C для выхода.

import os, signal, time

class CleanChildProcesses:
    """
    with CleanChildProcesses():
        Do work here
    """
    def __init__(self, time_to_die=5, foreground=False):
        self.time_to_die = time_to_die  # how long to give children to die before SIGKILL
        self.foreground = foreground  # If user wants to receive Ctrl-C
        self.is_foreground = False
        self.SIGNALS = (signal.SIGHUP, signal.SIGTERM, signal.SIGABRT, signal.SIGALRM, signal.SIGPIPE)
        self.is_stopped = True  # only call stop once (catch signal xor exiting 'with')

    def _run_as_foreground(self):
        if not self.foreground:
            return False
        try:
            fd = os.open(os.ctermid(), os.O_RDWR)
        except OSError:
            # Happens if process not run from terminal (tty, pty)
            return False

        os.close(fd)
        return True

    def _signal_hdlr(self, sig, framte):
        self.__exit__(None, None, None)

    def start(self):
        self.is_stopped = False
        """
        When running out of remote shell, SIGHUP is only sent to the session
        leader normally, the remote shell, so we need to make sure we are sent 
        SIGHUP. This also allows us not to kill ourselves with SIGKILL.
        - A process group is called orphaned when the parent of every member is 
            either in the process group or outside the session. In particular, 
            the process group of the session leader is always orphaned.
        - If termination of a process causes a process group to become orphaned, 
            and some member is stopped, then all are sent first SIGHUP and then 
            SIGCONT.
        consider: prctl.set_pdeathsig(signal.SIGTERM)
        """
        self.childpid = os.fork()  # return 0 in the child branch, and the childpid in the parent branch
        if self.childpid == 0:
            try:
                os.setpgrp()  # create new process group, become its leader
                os.kill(os.getpid(), signal.SIGSTOP)  # child fork stops itself
            finally:
                os._exit(0)  # shut down without going to __exit__

        os.waitpid(self.childpid, os.WUNTRACED)  # wait until child stopped after it created the process group
        os.setpgid(0, self.childpid)  # join child's group

        if self._run_as_foreground():
            hdlr = signal.signal(signal.SIGTTOU, signal.SIG_IGN)  # ignore since would cause this process to stop
            self.controlling_terminal = os.open(os.ctermid(), os.O_RDWR)
            self.orig_fore_pg = os.tcgetpgrp(self.controlling_terminal)  # sends SIGTTOU to this process
            os.tcsetpgrp(self.controlling_terminal, self.childpid)
            signal.signal(signal.SIGTTOU, hdlr)
            self.is_foreground = True

        self.exit_signals = dict((s, signal.signal(s, self._signal_hdlr))
                                 for s in self.SIGNALS)                                     

    def stop(self):
        try:
            for s in self.SIGNALS:
                #don't get interrupted while cleaning everything up
                signal.signal(s, signal.SIG_IGN)

            self.is_stopped = True

            if self.is_foreground:
                os.tcsetpgrp(self.controlling_terminal, self.orig_fore_pg)
                os.close(self.controlling_terminal)
                self.is_foreground = False

            try:
                os.kill(self.childpid, signal.SIGCONT)
            except OSError:
                """
                can occur if process finished and one of:
                - was reaped by another process
                - if parent explicitly ignored SIGCHLD
                    signal.signal(signal.SIGCHLD, signal.SIG_IGN)
                - parent has the SA_NOCLDWAIT flag set 
                """
                pass

            os.setpgrp()  # leave the child's process group so I won't get signals
            try:
                os.killpg(self.childpid, signal.SIGINT)
                time.sleep(self.time_to_die)  # let processes end gracefully
                os.killpg(self.childpid, signal.SIGKILL)  # In case process gets stuck while dying
                os.waitpid(self.childpid, 0)  # reap Zombie child process
            except OSError as e:
                pass
        finally:
            for s, hdlr in self.exit_signals.iteritems():
                signal.signal(s, hdlr)  # reset default handlers

    def __enter__(self):
        if self.is_stopped:
            self.start()

    def __exit__(self, exit_type, value, traceback):
        if not self.is_stopped:
            self.stop()

Спасибо Малкольму Хэндли за первоначальный дизайн. Сделано с python2.7 на Linux.

2 голосов
/ 23 февраля 2016

Решением для windows может быть использование задания win32, например, Как автоматически уничтожить дочерние процессы в Windows?

Вот существующая реализация Python

https://gist.github.com/ubershmekel/119697afba2eaecc6330

...