web-dev-qa-db-fra.com

S'assurer que les sous-processus sont morts à la sortie du programme Python

Existe-t-il un moyen de s'assurer que tous les sous-processus créés sont morts au moment de la sortie d'un programme Python? Par sous-processus, j'entends ceux créés avec le sous-processus.Popen ().

Si non, devrais-je parcourir toutes les tueries émises, puis -9? quelque chose de plus propre?

50
pupeno

Vous pouvez utiliser atexit pour cela et enregistrer toutes les tâches de nettoyage à exécuter lorsque votre programme se ferme. 

atexit.register (func [ * args [ ** kargs]])

Dans votre processus de nettoyage, vous pouvez également implémenter votre propre attente et la tuer quand le délai d'expiration souhaité se produit.

>>> 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!

Note - Les fonctions enregistrées ne seront pas exécutées si ce processus (processus parent) est tué.

La méthode windows suivante n'est plus nécessaire pour python> = 2.6

Voici un moyen de tuer un processus dans Windows. Votre objet Popen a un attribut pid, vous pouvez donc simplement l'appeler par success = win_kill (p.pid) (besoin pywin32 installed):

    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
41
monkut

Sur * nix, peut-être que l’utilisation de groupes de processus peut vous aider - vous pouvez également attraper les sous-processus générés par vos sous-processus.

if __== "__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

Une autre considération est d’escalader les signaux: de SIGTERM (signal par défaut pour kill) à SIGKILL (a.k.a kill -9). Attendez quelques instants entre les signaux pour donner au processus une chance de sortir proprement avant de kill -9 le.

32
orip

subprocess.Popen.wait() est le seul moyen de s'assurer qu'ils sont morts. En effet, les systèmes d’exploitation POSIX exigent que vous attendiez vos enfants. Beaucoup de * nix vont créer un processus "zombie": un enfant mort pour lequel le parent n'a pas attendu.

Si l'enfant est raisonnablement bien écrit, il se termine. Souvent, les enfants lisent dans PIPE. Fermer l'entrée est un indice important pour l'enfant qu'il devrait fermer le magasin et sortir.

Si l'enfant a des bugs et ne se termine pas, vous devrez peut-être le tuer. Vous devriez corriger ce bug. 

Si l'enfant est une boucle "serve-forever" et n'est pas conçu pour se terminer, vous devez soit le tuer, soit fournir une entrée ou un message qui le forcera à se terminer. 


Modifier.

Dans les systèmes d'exploitation standard, vous avez os.kill( PID, 9 ). Tuez -9 est dur, BTW. Si vous pouvez les tuer avec SIGABRT (6?) Ou SIGTERM (15), c'est plus poli.

Sous Windows, vous n'avez pas un os.kill qui fonctionne. Regardez cette ActiveState Recipe pour mettre fin à un processus sous Windows.

Nous avons des processus enfants qui sont des serveurs WSGI. Pour les terminer, nous faisons un GET sur une URL spéciale; cela oblige l'enfant à nettoyer et à sortir.

14
S.Lott

Attention: Linux uniquement! Vous pouvez faire en sorte que votre enfant reçoive un signal lorsque son parent meurt.

Installez d’abord python-prctl == 1.5.0 puis modifiez votre code parent pour lancer vos processus enfants comme suit

subprocess.Popen(["sleep", "100"], preexec_fn=lambda: prctl.set_pdeathsig(signal.SIGKILL))

Ce que cela dit c'est:

  • lancement du sous-processus: sommeil 100
  • après avoir forgé et avant l'exécution du sous-processus, l'enfant s'inscrit pour "m'envoyer un SIGKILL lorsque mon parent se termine".
4
Carl D'Halluin

la réponse d'orip est utile mais présente l'inconvénient de tuer votre processus et de retourner un code d'erreur à votre parent. J'ai évité ça comme ça:

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

Et alors:

  with CleanChildProcesses():
    # Do your work here

Bien sûr, vous pouvez le faire avec try/except/finally mais vous devez gérer les cas exceptionnels et non exceptionnels séparément.

3
Malcolm Handley

J'avais besoin d'une petite variante de ce problème (nettoyage des sous-processus, mais sans quitter le programme Python lui-même), et comme ce n'est pas mentionné ici parmi les autres réponses:

p=subprocess.Popen(your_command, preexec_fn=os.setsid)
os.killpg(os.getpgid(p.pid), 15)

setsid lancera le programme dans une nouvelle session, lui attribuant ainsi un nouveau groupe de processus et ses enfants. appeler os.killpg dessus ne fera donc pas tomber votre propre processus python également.

3
berdario

poll () 

Vérifiez si le processus enfant est terminé . Renvoie l'attribut returncode.

3
Igal Serban

Existe-t-il un moyen de s'assurer que tous les sous-processus créés sont morts au moment de la sortie d'un programme Python? Par sous-processus, j'entends ceux créés avec le sous-processus.Popen ().

Vous pouvez violer l’encapsulation et tester que tous les processus Popen se sont terminés en exécutant

subprocess._cleanup()
print subprocess._active == []

Si non, devrais-je parcourir toutes les tueries émises, puis -9? quelque chose de plus propre?

Vous ne pouvez pas vous assurer que tous les sous-processus sont morts sans sortir et tuer tous les survivants. Mais si vous avez ce problème, c'est probablement parce que vous avez un problème de conception plus profond.

2
ddaa

Trouvez une solution pour Linux (sans installer 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)) 
2
Patrick

J'avais effectivement besoin de faire cela, mais cela impliquait d'exécuter des commandes à distance. Nous voulions pouvoir arrêter les processus en fermant la connexion au serveur. De plus, si, par exemple, vous exécutez Python Repl, vous pouvez choisir de s’exécuter en tant que premier plan si vous souhaitez pouvoir utiliser Ctrl-C pour quitter.

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()

Merci à Malcolm Handley pour la conception initiale. Fait avec python2.7 sur linux.

2
matanmarkind

Une solution pour Windows peut être d’utiliser l’application win32 job, par exemple. Comment puis-je détruire automatiquement les processus enfants dans Windows?

Voici une implémentation python existante

https://Gist.github.com/ubershmekel/119697afba2eaecc6330

2
ubershmekel
0
st4rtx

Voici ce que j'ai fait pour mon application posix:

Lorsque votre application existe, appelez la méthode kill () de cette classe: http://www.pixelbeat.org/libs/subProcess.py

Exemple d'utilisation ici: http://code.google.com/p/fslint/source/browse/trunk/fslint-gui#608

0
pixelbeat

Vous pouvez essayer subalive, un paquet que j'ai écrit pour un problème similaire. Il utilise périodiquement un ping actif via RPC et le processus esclave se termine automatiquement lorsque le maître arrête les ping actifs pour une raison quelconque.

https://github.com/waszil/subalive

Exemple pour le maître:

from subalive import SubAliveMaster

# start subprocess with alive keeping
SubAliveMaster(<path to your slave script>)

# do your stuff
# ...

Exemple pour le sous-processus esclave:

from subalive import SubAliveSlave

# start alive checking
SubAliveSlave()

# do your stuff
# ...
0
waszil