web-dev-qa-db-fra.com

Comment tuer un processus enfant python créé avec subprocess.check_output () lorsque le parent meurt?

J'exécute sur une machine Linux un script python qui crée un processus enfant en utilisant subprocess.check_output () comme suit:

subprocess.check_output(["ls", "-l"], stderr=subprocess.STDOUT)

Le problème est que même si le processus parent meurt, l'enfant est toujours en cours d'exécution. Existe-t-il un moyen de tuer le processus enfant également lorsque le parent décède?

27
Clara

Votre problème est d'utiliser subprocess.check_output - vous avez raison, vous ne pouvez pas obtenir le PID enfant en utilisant cette interface. Utilisez plutôt Popen:

proc = subprocess.Popen(["ls", "-l"], stdout=PIPE, stderr=PIPE)

# Here you can get the PID
global child_pid
child_pid = proc.pid

# Now we can wait for the child to complete
(output, error) = proc.communicate()

if error:
    print "error:", error

print "output:", output

Pour vous assurer de tuer l'enfant à la sortie:

import os
import signal
def kill_child():
    if child_pid is None:
        pass
    else:
        os.kill(child_pid, signal.SIGTERM)

import atexit
atexit.register(kill_child)
21
cdarke

Oui, vous pouvez y parvenir par deux méthodes. Les deux nécessitent que vous utilisiez Popen au lieu de check_output. La première est une méthode plus simple, utilisant try..finally, comme suit:

from contextlib import contextmanager

@contextmanager
def run_and_terminate_process(*args, **kwargs):
try:
    p = subprocess.Popen(*args, **kwargs)
    yield p        
finally:
    p.terminate() # send sigterm, or ...
    p.kill()      # send sigkill

def main():
    with run_and_terminate_process(args) as running_proc:
        # Your code here, such as running_proc.stdout.readline()

Cela interceptera sigint (interruption du clavier) et sigterm, mais pas sigkill (si vous tuez votre script avec -9).

L'autre méthode est un peu plus complexe et utilise le prctl PR_SET_PDEATHSIG de ctypes. Le système enverra un signal à l'enfant une fois que le parent sera sorti pour une raison quelconque (même sigkill).

import signal
import ctypes
libc = ctypes.CDLL("libc.so.6")
def set_pdeathsig(sig = signal.SIGTERM):
    def callable():
        return libc.prctl(1, sig)
    return callable
p = subprocess.Popen(args, preexec_fn = set_pdeathsig(signal.SIGTERM))
24
micromoses

Je ne connais pas les détails, mais le meilleur moyen est toujours de détecter les erreurs (et peut-être même toutes les erreurs) avec le signal et d'y mettre fin tous les processus restants.

import signal
import sys
import subprocess
import os

def signal_handler(signal, frame):
    sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)

a = subprocess.check_output(["ls", "-l"], stderr=subprocess.STDOUT)

while 1:
    pass # Press Ctrl-C (breaks the application and is catched by signal_handler()

Ce n'est qu'une maquette, vous devez attraper plus que SIGINT mais l'idée pourrait vous aider à démarrer et vous devrez toujours vérifier le processus généré.

http://docs.python.org/2/library/os.html#os.killhttp://docs.python.org/2/library/subprocess.html# subprocess.Popen.pidhttp://docs.python.org/2/library/subprocess.html#subprocess.Popen.kill

Je recommanderais de réécrire une version personnalisée de check_output parce que je viens de réaliser que check_output est vraiment juste pour un simple débogage, etc. car vous ne pouvez pas interagir autant avec lui pendant l'exécution.

Réécrire check_output:

from subprocess import Popen, PIPE, STDOUT
from time import sleep, time

def checkOutput(cmd):
    a = Popen('ls -l', Shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
    print(a.pid)
    start = time()
    while a.poll() == None or time()-start <= 30: #30 sec grace period
        sleep(0.25)
    if a.poll() == None:
        print('Still running, killing')
        a.kill()
    else:
        print('exit code:',a.poll())
    output = a.stdout.read()
    a.stdout.close()
    a.stdin.close()
    return output

Et faites ce que vous voulez avec, peut-être stockez les exécutions actives dans une variable temporaire et tuez-les à la sortie avec un signal ou d'autres moyens de détecter les erreurs/arrêts de la boucle principale.

En fin de compte, vous devez toujours intercepter les terminaisons dans l'application principale afin de tuer tous les enfants en toute sécurité, la meilleure façon d'aborder cela est avec try & except ou signal.

1
Torxed