Обеспечение того, что подпроцессы мертвы при выходе из программы Python
Есть ли способ гарантировать, что весь созданный подпроцесс мертв во время выхода программы Python? Подпроцессом я подразумеваю те, которые созданы с помощью подпроцесса .Popen().
Если нет, должен ли я перебирать все выпуски, а затем убивать -9? ничего более чистого?
Ответы
Ответ 1
Вы можете использовать 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
Здесь можно убить процесс в окнах. У вашего объекта 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
Ответ 2
В * 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
его.
Ответ 3
subprocess.Popen.wait()
- единственный способ убедиться, что они мертвы. Действительно, ОС POSIX требует, чтобы вы ждали своих детей. Многие * nix создадут процесс "зомби": мертвый ребенок, для которого родитель не ожидал.
Если ребенок достаточно хорошо написан, он завершается. Часто дети читают из PIPE. Закрытие ввода - это большой намек на то, что ребенок должен закрыть магазин и выйти.
Если у ребенка есть ошибки и не завершается, возможно, вам придется его убить. Вы должны исправить эту ошибку.
Если дочерний элемент является циклом "служить навсегда" и не предназначен для завершения, вы должны либо убить его, либо предоставить какой-либо ввод или сообщение, которое заставит его прекратить действие.
Изменить.
В стандартных ОС у вас есть os.kill( PID, 9 )
. Убить -9 сурово, кстати. Если вы можете убить их с помощью SIGABRT (6?) Или SIGTERM (15), которые будут более вежливыми.
В ОС Windows у вас нет os.kill
, который работает. Посмотрите на этот ActiveState Recipe для завершения процесса в Windows.
У нас есть дочерние процессы, которые являются серверами WSGI. Чтобы их прервать, мы делаем GET по специальному URL-адресу; это заставляет ребенка очищаться и выходить.
Ответ 4
poll()
Проверьте, завершился ли дочерний процесс. Возвращает атрибут returncode.
Ответ 5
Предупреждение: только для Linux! Вы можете заставить вашего ребенка получать сигнал, когда его родитель умирает.
Сначала установите python-prctl == 1.5.0, затем измените родительский код, чтобы запустить дочерние процессы следующим образом
subprocess.Popen(["sleep", "100"], preexec_fn=lambda: prctl.set_pdeathsig(signal.SIGKILL))
Что это говорит:
- запуск подпроцесса: sleep 100
- после форкирования и перед выполнением подпроцесса ребенок регистрируется для "присылайте мне SIGKILL
когда мой родитель заканчивается ".
Ответ 6
orip answer полезен, но имеет недостаток, что он убивает ваш процесс и возвращает код ошибки вашего родителя. Я избегал этого:
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/except/finally, но вам придется обрабатывать исключительные и не исключительные случаи отдельно.
Ответ 7
Есть ли способ гарантировать, что весь созданный подпроцесс мертв во время выхода программы Python? Подпроцессом я подразумеваю те, которые созданы с помощью подпроцесса .Popen().
Вы можете нарушить инкапсуляцию и проверить, что все процессы Popen завершились, выполнив
subprocess._cleanup()
print subprocess._active == []
Если нет, должен ли я перебирать все выпуски, а затем убивать -9? ничего более чистого?
Вы не можете гарантировать, что все подпроцессы мертвы, не выходя из дома и убивая каждого оставшегося в живых. Но если у вас есть эта проблема, возможно, потому, что у вас есть более глубокие проблемы с дизайном.
Ответ 8
Мне понадобилась небольшая вариация этой проблемы (очистка подпроцессов, но без выхода из самой программы Python), и поскольку она не упоминается здесь среди других ответов:
p=subprocess.Popen(your_command, preexec_fn=os.setsid)
os.killpg(os.getpgid(p.pid), 15)
setsid
запускает программу в новом сеансе, тем самым присваивая ей новую группу процессов и ее дочерние элементы. вызов os.killpg
на нем, таким образом, также не приведет к снижению вашего собственного процесса python.
Ответ 9
Мне действительно нужно было это сделать, но это включало запуск удаленных команд. Мы хотели иметь возможность остановить процессы, закрыв соединение с сервером. Кроме того, если, например, вы работаете в реплике python, вы можете выбрать запуск в качестве переднего плана, если вы хотите использовать 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 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 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.
Ответ 10
Это то, что я сделал для своего приложения posix:
Когда ваше приложение существует, вызовите метод kill() этого класса:
http://www.pixelbeat.org/libs/subProcess.py
Пример использования здесь:
http://code.google.com/p/fslint/source/browse/trunk/fslint-gui#608
Ответ 11
помощь для кода python:
http://docs.python.org/dev/library/subprocess.html#subprocess.Popen.wait
Ответ 12
Решение для окон может заключаться в использовании задания win32 api, например. Как автоматически уничтожать дочерние процессы в Windows?
Здесь существует существующая реализация python
https://gist.github.com/ubershmekel/119697afba2eaecc6330
Ответ 13
Найдите решение для 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))