У меня есть автоматизация на Python, которая порождает сеансы telnet
, которые я регистрирую с помощью команды linux script
; есть два скрипта
идентификатора процесса (родительский и дочерний) для каждого сеанса регистрации.
Мне нужно решить проблему, когда, если скрипт автоматизации python умирает, сеансы скрипта
никогда не закрываются сами по себе; почему-то это намного сложнее, чем должно быть.
До сих пор я реализовал watchdog.py
(см. Нижнюю часть вопроса), который демонизирует себя и опрашивает PID сценария автоматизации python в цикле. Когда он видит, что PID автоматизации python исчезает из таблицы процессов сервера, он пытается убить сеансы script
.
Моя проблема: сеансы
script
всегда порождают два отдельных процесса, один из сеансов script
является родительским для другого сеанса script
. watchdog.py
не будет убивать сеансы дочернего сценария
, если я запускаю сеансы сценария
из сценария автоматизации (см. ПРИМЕР АВТОМАТИЗАЦИИ ], ниже) воспроизводить_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 - это сеансы скрипта
.
[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
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]$
После того, как я запустил описанную выше автоматизацию, все сеансы дочерних скриптов
все еще работают.
[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 в системе Debian Squeeze linux (uname -a: Linux Hotcoffee 2.6.32-5-amd64 # 1 SMP Mon 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 ~]$
Проблема, по сути, заключалась в состоянии гонки. Когда я пытался убить «родительские» скриптовые
процессы, они уже умерли по совпадению с событием автоматизации ...
Чтобы решить проблему ... во-первых, сторожевой таймер должен был идентифицировать весь список детей, которые должны быть убиты перед опросом наблюдаемого PID (мой оригинальный скрипт пытался идентифицировать детей после того, как наблюдаемый PID разбился). Затем мне пришлось изменить мой сторожевой таймер, чтобы учесть возможность того, что некоторые скриптовые
процессы могут умереть с наблюдаемым 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)