J’ai de l’automatisation python, qui génère des sessions telnet
que je connecte avec la commande de script
linux; Il existe deux ID de processus de script
(un parent et un enfant) pour chaque session de journalisation.
Je dois résoudre un problème où, si le script d’automatisation python meurt, les sessions de script
ne se ferment jamais d’elles-mêmes; pour une raison quelconque, c’est beaucoup plus difficile qu’il ne devrait l’être.
Jusqu’à présent, j’ai implémenté watchdog.py
(voir en bas de la question), qui se démode, et interroge le PID du script Python Automation dans une boucle. Quand il voit le PID Python Automation disparaître de la table de processus du serveur, il tente de tuer les sessions de script
.
Mon problème est:
script
sessions de script
génèrent toujours deux processus distincts, l’une des sessions de script
est le parent de l’autre session de script
. watchdog.py
ne tue pas les sessions de script
enfant, si je lance des sessions de script
partir du script d’automatisation (voir EXEMPLE D’AUTOMATION , ci-dessous) 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"
Maintenant, l’exemple de ce qui se passe lorsque je lance l’automatisation ci-dessus … notez que le PID 30017 génère 30018 et le PID 30020 génèrent 30021. Tous les PID susmentionnés sont des sessions de 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 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]$
Après avoir exécuté l’automatisation ci-dessus, toutes les sessions de script
enfant sont toujours en cours d’exécution.
[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]$
Je lance l’automatisation sous Python 2.6.6, sur un système 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
).
Il semble que le démon ne survit pas au crash du processus de reproduction. Comment puis-je réparer watchdog.py pour fermer toutes les sessions de script si l’automatisation meurt (comme illustré dans l’exemple ci-dessus)?
Un journal watchdog.py
qui illustre le problème (malheureusement, les PID ne coïncident pas avec la question d’origine) …
[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 ~]$
Le problème était essentiellement une condition de course. Lorsque j’ai essayé de tuer les processus de script
“parent”, ils étaient déjà morts en même temps que l’événement d’automatisation …
Pour résoudre le problème … tout d’abord, le démon watchdog devait identifier la liste complète des enfants à tuer avant d’interroger le PID observé (mon script d’origine a tenté d’identifier les enfants après l’écrasement du PID observé). Ensuite, j’ai dû modifier mon démon watchdog pour permettre à certains processus de script
mourir avec le PID observé.
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)
Votre problème est que ce script n’est pas détaché du script d’automatisation après la génération, il fonctionne donc comme un enfant et lorsque le parent meurt, il rest ingérable.
Pour gérer le script python exit, vous pouvez utiliser le module atexit . Pour surveiller les processus enfants, vous pouvez utiliser os.wait ou gérer le signal SIGCHLD
Vous pouvez essayer de tuer le groupe de processus complet contenant: le script
parent, le script
enfant, le script
généré par le script
et – peut-être – même le processus telnet
.
Le manuel kill(2)
dit:
Si pid est inférieur à -1, alors sig est envoyé à chaque processus du groupe de processus dont l’ID est -pid.
Donc, l’équivalent de kill -TERM -$PID
fera l’affaire.
Oh, le pid dont vous avez besoin est celui du script
parent.
modifier
La mise à mort du groupe de processus semble fonctionner pour moi si j’adapte les deux fonctions suivantes dans 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
Peut-être que vous pourriez utiliser os.system () et faire un killall dans votre chien de garde pour tuer toutes les instances de / usr / bin / script
À l’inspection, il semble que psu_proc.kill()
(en fait send_signal()
) devrait OSError
en cas d’échec, mais juste au cas où – avez-vous essayé de vérifier la terminaison avant de définir l’indicateur? Un péché:
if not psu_proc.is_running(): finished = True