Pregunta

¿Hay alguna manera de garantizar que todos los subprocesos creados estén muertos al momento de la salida de un programa Python? Por subproceso me refiero a los creados con subprocess.Popen ().

Si no, ¿debería iterar sobre todos los asesinatos que emiten y luego mata -9? algo más limpio?

¿Fue útil?

Solución

Puede usar atexit para esto y registrarse cualquier tarea de limpieza que se ejecutará cuando su programa salga.

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

En su proceso de limpieza, también puede implementar su propia espera y eliminarla cuando ocurra el tiempo de espera deseado.

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

Nota : las funciones registradas no se ejecutarán si este proceso (proceso principal) se elimina.

El siguiente método de Windows ya no es necesario para python > = 2.6

Aquí hay una manera de matar un proceso en Windows. Su objeto Popen tiene un atributo pid, por lo que puede llamarlo por success = win_kill (p.pid) (necesita pywin32 instalado):

    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

Otros consejos

En * nix's, tal vez el uso de grupos de procesos puede ayudarlo; también puede detectar subprocesos generados por sus subprocesos.

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

Otra consideración es escalar las señales: de SIGTERM (señal predeterminada para kill ) a SIGKILL (a.k.a kill -9 ). Espere un momento entre las señales para dar al proceso la oportunidad de salir limpiamente antes de kill -9 .

El subproceso .Popen.wait () es la única forma de asegurar que estén muertos. De hecho, los sistemas operativos POSIX requieren que espere a sus hijos. Muchos * nix crearán un "zombie" proceso: un niño muerto que el padre no esperó.

Si el niño está razonablemente bien escrito, termina. A menudo, los niños leen de PIPE's. Cerrar la entrada es una gran pista para el niño de que debe cerrar la tienda y salir.

Si el niño tiene errores y no termina, puede que tenga que matarlo. Deberías arreglar este error.

Si el niño es un "servir para siempre" bucle, y no está diseñado para terminar, debe matarlo o proporcionar alguna entrada o mensaje que lo obligará a terminar.


Editar.

En los sistemas operativos estándar, tiene os.kill (PID, 9) . Matar -9 es duro, por cierto. Si puedes matarlos con SIGABRT (6?) O SIGTERM (15), eso es más cortés.

En el sistema operativo Windows, no tiene un os.kill que funcione. Mire esta ActiveState Recipe para finalizar un proceso en Windows.

Tenemos procesos secundarios que son servidores WSGI. Para terminarlos hacemos un GET en una URL especial; esto hace que el niño limpie y salga.

Advertencia: ¡solo para Linux! Puede hacer que su hijo reciba una señal cuando su padre muera.

Primero instale python-prctl == 1.5.0 y luego cambie su código principal para iniciar sus procesos secundarios de la siguiente manera

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

Lo que esto dice es:

  • iniciar subproceso: dormir 100
  • después de bifurcar y antes de que se ejecute el subproceso, el niño se registra para enviarme un SIGKILL cuando mi padre termina " ;.
  

encuesta ()

     

Verifique si el proceso hijo ha finalizado.   Devuelve el atributo returncode.

Necesitaba una pequeña variación de este problema (limpiar subprocesos, pero sin salir del programa Python), y como no se menciona aquí entre las otras respuestas:

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

setsid ejecutará el programa en una nueva sesión, asignándole así un nuevo grupo de procesos a él y a sus hijos. invocar os.killpg en él, por lo tanto, no derribará su propio proceso de Python también.

La respuesta de

orip es útil, pero tiene el inconveniente de que mata su proceso y devuelve un código de error a su padre. Lo evité así:

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

Y luego:

  with CleanChildProcesses():
    # Do your work here

Por supuesto, puede hacer esto con try / except / finally pero tiene que manejar los casos excepcionales y no excepcionales por separado.

  

¿Hay alguna manera de garantizar que todos los subprocesos creados estén muertos al momento de la salida de un programa Python? Por subproceso me refiero a los creados con subprocess.Popen ().

Podría violar la encapsulación y la prueba de que todos los procesos de Popen han terminado haciendo

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

Si no, ¿debería iterar sobre todos los asesinatos que emiten y luego mata -9? algo más limpio?

No puedes asegurarte de que todos los subprocesos estén muertos sin salir y matar a todos los sobrevivientes. Pero si tiene este problema, probablemente sea porque tiene un problema de diseño más profundo.

Una solución para Windows puede ser utilizar la API de trabajo win32, p. ej. ¿Cómo destruyo automáticamente los procesos secundarios en Windows?

Aquí hay una implementación de Python existente

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

Realmente necesitaba hacer esto, pero implicaba ejecutar comandos remotos. Queríamos poder detener los procesos cerrando la conexión al servidor. Además, si, por ejemplo, está ejecutando en la réplica de Python, puede seleccionar ejecutar como primer plano si desea poder usar Ctrl-C para salir.

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

Gracias a Malcolm Handley por el diseño inicial. Hecho con python2.7 en Linux.

Encuentre una solución para Linux (sin instalar 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)) 

Esto es lo que hice para mi aplicación posix:

Cuando su aplicación exista, llame al método kill () de esta clase: http://www.pixelbeat.org/libs/subProcess.py

Ejemplo de uso aquí: http://code.google.com/p/ fslint / source / browse / trunk / fslint-gui # 608

Puedes probar subalive , un paquete que escribí para un problema similar. Utiliza ping activo periódico a través de RPC, y el proceso esclavo termina automáticamente cuando el maestro detiene los pings vivos por alguna razón.

https://github.com/waszil/subalive

Ejemplo para maestro:

from subalive import SubAliveMaster

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

# do your stuff
# ...

Ejemplo para subproceso esclavo:

from subalive import SubAliveSlave

# start alive checking
SubAliveSlave()

# do your stuff
# ...
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top