Глубокий кросс-платформенный процесс убивает демона

У меня есть автоматизация python, которая порождает сеансы telnet которые я регистрирую с помощью команды linux script ; для каждого сеанса регистрации есть два идентификатора процесса script (родительский и дочерний).

Мне нужно решить проблему, где, если скрипт автоматизации python умирает, сеансы script никогда не закрываются сами по себе; по какой-то причине это намного сложнее, чем должно быть.

До сих пор я реализовал watchdog.py (см. Нижнюю часть вопроса), которая демонизирует себя, и проверяет PID сценария автоматизации python в цикле. Когда он видит, что PID автоматизации автоматизации python исчезает из таблицы процессов сервера, он пытается убить сеансы script .

Моя проблема:

  • script всегда порождают два отдельных процесса, один из сеансов script является родителем другого сеанса script .
  • watchdog.py не будет убивать сеансы дочернего script , если я запускаю сеансы script из сценария автоматизации (см. пункт АВТОМАТИЗАЦИЯ , ниже)

ПРИМЕР АВТОМАТИЗАЦИИ ( reproduce_bug.py )

 import pexpect as px from subprocess import Popen import code import time import sys import os def read_pid_and_telnet(_child, addr): time.sleep(0.1) # Give the OS time to write the PIDFILE # Read the PID in the PIDFILE fh = open('PIDFILE', 'r') pid = int(''.join(fh.readlines())) fh.close() time.sleep(0.1) # Clean up the PIDFILE os.remove('PIDFILE') _child.expect(['#', '\$'], timeout=3) _child.sendline('telnet %s' % addr) return str(pid) pidlist = list() child1 = px.spawn("""bash -c 'echo $$ > PIDFILE """ """&& exec /usr/bin/script -f LOGFILE1.txt'""") pidlist.append(read_pid_and_telnet(child1, '10.1.1.1')) child2 = px.spawn("""bash -c 'echo $$ > PIDFILE """ """&& exec /usr/bin/script -f LOGFILE2.txt'""") pidlist.append(read_pid_and_telnet(child2, '10.1.1.2')) cmd = "python watchdog.py -o %s -k %s" % (os.getpid(), ','.join(pidlist)) Popen(cmd.split(' ')) print "I started the watchdog with:\n %s" % cmd time.sleep(0.5) raise RuntimeError, "Simulated script crash. Note that script child sessions are hung" 

Теперь пример того, что происходит при запуске автоматизации выше … Обратите внимание, что PID 30017 порождает 30018, а PID 30020 порождает 30021. Все вышеупомянутые PID – это сеансы script .

 [mpenning@Hotcoffee Network]$ python reproduce_bug.py I started the watchdog with: python watchdog.py -o 30016 -k 30017,30020 Traceback (most recent call last): File "reproduce_bug.py", line 35, in <module> raise RuntimeError, "Simulated script crash. Note that script child sessions are hung" RuntimeError: Simulated script crash. Note that script child sessions are hung [mpenning@Hotcoffee Network]$ 

После того, как я запустил автоматизацию выше, все сеансы дочернего script все еще запущены.

 [mpenning@Hotcoffee Models]$ ps auxw | grep script mpenning 30018 0.0 0.0 15832 508 ? S 12:08 0:00 /usr/bin/script -f LOGFILE1.txt mpenning 30021 0.0 0.0 15832 516 ? S 12:08 0:00 /usr/bin/script -f LOGFILE2.txt mpenning 30050 0.0 0.0 7548 880 pts/8 S+ 12:08 0:00 grep script [mpenning@Hotcoffee Models]$ 

Я запускаю автоматизацию под Python 2.6.6, на Linux-системе Debian Squeeze (uname -a: Linux Hotcoffee 2.6.32-5-amd64 #1 SMP Mon Jan 16 16:22:28 UTC 2012 x86_64 GNU/Linux ).

ВОПРОС:

Похоже, что демон не переживает краха процесса нереста. Как я могу исправить watchdog.py, чтобы закрыть все сеансы скриптов, если автоматизация умирает (как показано в примере выше)?

watchdog.py который иллюстрирует проблему (к сожалению, PID не совпадают с исходным вопросом) …

 [mpenning@Hotcoffee ~]$ cat watchdog.log 2012-02-22,15:17:20.356313 Start watchdog.watch_process 2012-02-22,15:17:20.356541 observe pid = 31339 2012-02-22,15:17:20.356643 kill pids = 31352,31356 2012-02-22,15:17:20.356730 seconds = 2 [mpenning@Hotcoffee ~]$ 

разрешение

Проблема была в основном состоянием гонки. Когда я пытался убить «родительские» script процессы, они уже умерли случайно с событием автоматизации …

Чтобы решить проблему … во-первых, демон сторожевого таймера должен был идентифицировать весь список детей, которые будут убиты, прежде чем опросить наблюдаемый PID (мой оригинальный скрипт попытался идентифицировать детей после того, как обнаруженный PID разбился). Затем мне пришлось модифицировать своего сторожевого демона, чтобы можно было предположить, что некоторые script могут умереть с наблюдаемым PID.


watchdog.py:

 #!/usr/bin/python """ Implement a cross-platform watchdog daemon, which observes a PID and kills other PIDs if the observed PID dies. Example: -------- watchdog.py -o 29322 -k 29345,29346,29348 -s 2 The command checks PID 29322 every 2 seconds and kills PIDs 29345, 29346, 29348 and their children, if PID 29322 dies. Requires: ---------- * https://github.com/giampaolo/psutil * http://pypi.python.org/pypi/python-daemon """ from optparse import OptionParser import datetime as dt import signal import daemon import logging import psutil import time import sys import os class MyFormatter(logging.Formatter): converter=dt.datetime.fromtimestamp def formatTime(self, record, datefmt=None): ct = self.converter(record.created) if datefmt: s = ct.strftime(datefmt) else: t = ct.strftime("%Y-%m-%d %H:%M:%S") s = "%s,%03d" % (t, record.msecs) return s def check_pid(pid): """ Check For the existence of a unix / windows pid.""" try: os.kill(pid, 0) # Kill 0 raises OSError, if pid isn't there... except OSError: return False else: return True def kill_process(logger, pid): try: psu_proc = psutil.Process(pid) except Exception, e: logger.debug('Caught Exception ["%s"] while looking up PID %s' % (e, pid)) return False logger.debug('Sending SIGTERM to %s' % repr(psu_proc)) psu_proc.send_signal(signal.SIGTERM) psu_proc.wait(timeout=None) return True def watch_process(observe, kill, seconds=2): """Kill the process IDs listed in 'kill', when 'observe' dies.""" logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) logfile = logging.FileHandler('%s/watchdog.log' % os.getcwd()) logger.addHandler(logfile) formatter = MyFormatter(fmt='%(asctime)s %(message)s',datefmt='%Y-%m-%d,%H:%M:%S.%f') logfile.setFormatter(formatter) logger.debug('Start watchdog.watch_process') logger.debug(' observe pid = %s' % observe) logger.debug(' kill pids = %s' % kill) logger.debug(' seconds = %s' % seconds) children = list() # Get PIDs of all child processes... for childpid in kill.split(','): children.append(childpid) p = psutil.Process(int(childpid)) for subpsu in p.get_children(): children.append(str(subpsu.pid)) # Poll observed PID... while check_pid(int(observe)): logger.debug('Poll PID: %s is alive.' % observe) time.sleep(seconds) logger.debug('Poll PID: %s is *dead*, starting kills of %s' % (observe, ', '.join(children))) for pid in children: # kill all child processes... kill_process(logger, int(pid)) sys.exit(0) # Exit gracefully def run(observe, kill, seconds): with daemon.DaemonContext(detach_process=True, stdout=sys.stdout, working_directory=os.getcwd()): watch_process(observe=observe, kill=kill, seconds=seconds) if __name__=='__main__': parser = OptionParser() parser.add_option("-o", "--observe", dest="observe", type="int", help="PID to be observed", metavar="INT") parser.add_option("-k", "--kill", dest="kill", help="Comma separated list of PIDs to be killed", metavar="TEXT") parser.add_option("-s", "--seconds", dest="seconds", default=2, type="int", help="Seconds to wait between observations (default = 2)", metavar="INT") (options, args) = parser.parse_args() run(options.observe, options.kill, options.seconds) - #!/usr/bin/python """ Implement a cross-platform watchdog daemon, which observes a PID and kills other PIDs if the observed PID dies. Example: -------- watchdog.py -o 29322 -k 29345,29346,29348 -s 2 The command checks PID 29322 every 2 seconds and kills PIDs 29345, 29346, 29348 and their children, if PID 29322 dies. Requires: ---------- * https://github.com/giampaolo/psutil * http://pypi.python.org/pypi/python-daemon """ from optparse import OptionParser import datetime as dt import signal import daemon import logging import psutil import time import sys import os class MyFormatter(logging.Formatter): converter=dt.datetime.fromtimestamp def formatTime(self, record, datefmt=None): ct = self.converter(record.created) if datefmt: s = ct.strftime(datefmt) else: t = ct.strftime("%Y-%m-%d %H:%M:%S") s = "%s,%03d" % (t, record.msecs) return s def check_pid(pid): """ Check For the existence of a unix / windows pid.""" try: os.kill(pid, 0) # Kill 0 raises OSError, if pid isn't there... except OSError: return False else: return True def kill_process(logger, pid): try: psu_proc = psutil.Process(pid) except Exception, e: logger.debug('Caught Exception ["%s"] while looking up PID %s' % (e, pid)) return False logger.debug('Sending SIGTERM to %s' % repr(psu_proc)) psu_proc.send_signal(signal.SIGTERM) psu_proc.wait(timeout=None) return True def watch_process(observe, kill, seconds=2): """Kill the process IDs listed in 'kill', when 'observe' dies.""" logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) logfile = logging.FileHandler('%s/watchdog.log' % os.getcwd()) logger.addHandler(logfile) formatter = MyFormatter(fmt='%(asctime)s %(message)s',datefmt='%Y-%m-%d,%H:%M:%S.%f') logfile.setFormatter(formatter) logger.debug('Start watchdog.watch_process') logger.debug(' observe pid = %s' % observe) logger.debug(' kill pids = %s' % kill) logger.debug(' seconds = %s' % seconds) children = list() # Get PIDs of all child processes... for childpid in kill.split(','): children.append(childpid) p = psutil.Process(int(childpid)) for subpsu in p.get_children(): children.append(str(subpsu.pid)) # Poll observed PID... while check_pid(int(observe)): logger.debug('Poll PID: %s is alive.' % observe) time.sleep(seconds) logger.debug('Poll PID: %s is *dead*, starting kills of %s' % (observe, ', '.join(children))) for pid in children: # kill all child processes... kill_process(logger, int(pid)) sys.exit(0) # Exit gracefully def run(observe, kill, seconds): with daemon.DaemonContext(detach_process=True, stdout=sys.stdout, working_directory=os.getcwd()): watch_process(observe=observe, kill=kill, seconds=seconds) if __name__=='__main__': parser = OptionParser() parser.add_option("-o", "--observe", dest="observe", type="int", help="PID to be observed", metavar="INT") parser.add_option("-k", "--kill", dest="kill", help="Comma separated list of PIDs to be killed", metavar="TEXT") parser.add_option("-s", "--seconds", dest="seconds", default=2, type="int", help="Seconds to wait between observations (default = 2)", metavar="INT") (options, args) = parser.parse_args() run(options.observe, options.kill, options.seconds) 

    4 Solutions collect form web for “Глубокий кросс-платформенный процесс убивает демона”

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

    Чтобы обработать выход скрипта python, вы можете использовать модуль atexit . Чтобы контролировать выход дочерних процессов, вы можете использовать команду os.wait или обрабатывать сигнал SIGCHLD

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

    Руководство kill(2) гласит:

    Если pid меньше -1, тогда sig отправляется каждому процессу в группе процессов , идентификатор которого -p.

    Таким образом, эквивалент kill -TERM -$PID будет выполнять эту работу.

    О, нужный pid вам нужен родительский script .


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

    Убийство группы процессов, похоже, работает для меня, если я буду адаптировать следующие две функции в watchdog.py:

     def kill_process_group(log, pid): log.debug('killing %s' % -pid) os.kill(-pid, 15) return True def watch_process(observe, kill, seconds=2): """Kill the process IDs listed in 'kill', when 'observe' dies.""" logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) logfile = logging.FileHandler('%s/watchdog.log' % os.getcwd()) logger.addHandler(logfile) formatter = MyFormatter(fmt='%(asctime)s %(message)s',datefmt='%Y-%m-%d,%H:%M:%S.%f') logfile.setFormatter(formatter) logger.debug('Start watchdog.watch_process') logger.debug(' observe pid = %s' % observe) logger.debug(' kill pids = %s' % kill) logger.debug(' seconds = %s' % seconds) while check_pid(int(observe)): logger.debug('PID: %s is alive.' % observe) time.sleep(seconds) logger.debug('PID: %s is *dead*, starting kills' % observe) for pid in kill.split(','): # Kill the children... kill_process_group(logger, int(pid)) sys.exit(0) # Exit gracefully 

    Возможно, вы можете использовать os.system () и сделать killall в своем сторожевом таймере, чтобы убить все экземпляры / usr / bin / script

    При проверке кажется, что psu_proc.kill() (на самом деле send_signal() ) должен поднять OSError при сбое, но на всякий случай – вы пытались проверить завершение до установки флага? Как в:

     if not psu_proc.is_running(): finished = True 
    Python - лучший язык программирования в мире.