Обеспечение остановки подпроцессов при выходе из программы Python
-
11-07-2019 - |
Вопрос
Есть ли способ гарантировать, что все созданные подпроцессы будут отключены во время выхода программы на Python?Под подпроцессом я подразумеваю те, которые созданы с помощью подпроцесса.Popen().
Если нет, должен ли я повторить все выданные kills, а затем kills -9?есть что-нибудь почище?
Решение
Вы можете использовать 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
Другие советы
В * 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
). Перед тем как убить -9
его, подождите некоторое время между сигналами, чтобы дать процессу возможность чистого выхода.
Тот Самый subprocess.Popen.wait()
это единственный способ убедиться, что они мертвы.Действительно, ОС POSIX требуют, чтобы вы обслуживали своих детей.Многие * nix'ы создадут процесс "зомби":мертвый ребенок, которого родитель не дождался.
Если дочерний элемент написан достаточно хорошо, он завершается.Часто дети читают по книге ПАЙПА.Закрытие входа - это большой намек ребенку на то, что ему следует закрыть магазин и выйти.
Если у дочернего элемента есть ошибки и он не завершается, возможно, вам придется его уничтожить.Вы должны исправить эту ошибку.
Если дочерний элемент является циклом "служить вечно" и не предназначен для завершения, вы должны либо завершить его, либо предоставить какой-либо ввод или сообщение, которое заставит его завершить.
Редактировать.
В стандартных операционных системах у вас есть os.kill( PID, 9 )
.Кстати, убить -9 - это жестоко.Если вы можете убить их с помощью SIGABRT (6?) или SIGTERM (15), это будет более вежливо.
В операционной системе Windows у вас нет os.kill
это работает.Посмотри на это Рецепт активного состояния для завершения процесса в Windows.
У нас есть дочерние процессы, которые являются серверами WSGI.Чтобы завершить их, мы делаем GET по специальному URL-адресу;это заставляет ребенка привести себя в порядок и выйти.
Предупреждение:Только для Linux!Вы можете сделать так, чтобы ваш ребенок получал сигнал, когда его родитель умирает.
Сначала установите python-prctl==1.5.0, затем измените родительский код для запуска дочерних процессов следующим образом
subprocess.Popen(["sleep", "100"], preexec_fn=lambda: prctl.set_pdeathsig(signal.SIGKILL))
Вот что здесь говорится::
- запустить подпроцесс:сон 100
- после разветвления и перед выполнением подпроцесса дочерний элемент регистрируется для "отправьте мне SIGKILL когда мой родитель завершит работу".
poll ()
Проверьте, завершен ли дочерний процесс. Возвращает атрибут кода возврата.
Мне понадобился небольшой вариант этой проблемы (очистка подпроцессов, но без выхода из самой программы Python), и поскольку он не упоминается здесь среди других ответов:
p=subprocess.Popen(your_command, preexec_fn=os.setsid)
os.killpg(os.getpgid(p.pid), 15)
setsid
запустит программу в новом сеансе, назначив ему и его дочерним процессам новую группу процессов. вызов os.killpg
для него, таким образом, также не остановит ваш собственный процесс python.
Ответ 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, но вы должны обрабатывать исключительные и не исключительные случаи отдельно.
Есть ли способ убедиться, что все созданные подпроцессы не работают во время выхода из программы на Python? Под подпроцессом я подразумеваю те, что созданы с помощью subprocess.Popen ().
Вы можете нарушить инкапсуляцию и тест , которые были завершены всеми процессами Popen, выполнив
subprocess._cleanup()
print subprocess._active == []
Если нет, должен ли я выполнить итерации по всем убивающим, а затем убить -9? что-нибудь чище?
Вы не можете гарантировать, что все подпроцессы мертвы, не выходя и не убивая каждого выжившего. Но если у вас есть эта проблема, это, вероятно, потому что у вас есть более глубокая проблема дизайна.
Решением для окон может быть использование задания win32, например, Как автоматически уничтожать дочерние процессы в Windows? р>
Вот существующая реализация Python
Мне действительно нужно было это сделать, но это включало запуск удаленных команд. Мы хотели иметь возможность остановить процессы, закрыв соединение с сервером. Кроме того, если, например, вы работаете в 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.
Найдите решение для 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))
Это то, что я сделал для моего приложения posix:
Когда ваше приложение существует, вызывайте метод kill () этого класса: http://www.pixelbeat.org/libs/subProcess.py
Пример использования здесь: http://code.google.com/p/ FSlint / источник / просмотр / багажник / FSlint-гуй # 608
справка по коду Python: http://docs.python.org/dev/library/subprocess .html # subprocess.Popen.wait
Вы можете попробовать subalive
, пакет, который я написал для аналогичной проблемы. Он использует периодический живой пинг через RPC, и подчиненный процесс автоматически завершается, когда мастер по какой-то причине останавливает живой пинг.
https://github.com/waszil/subalive
Пример для мастера:
from subalive import SubAliveMaster
# start subprocess with alive keeping
SubAliveMaster(<path to your slave script>)
# do your stuff
# ...
Пример подчиненного подпроцесса:
from subalive import SubAliveSlave
# start alive checking
SubAliveSlave()
# do your stuff
# ...