2008-11-26 12 views
43

¿Hay alguna forma de garantizar que todos los subprocesos creados estén muertos en el momento de la salida de un programa de Python? Por subproceso me refiero a los creados con subproceso.Popen().Asegurando que los subprocesos están muertos al salir del programa de Python

Si no, ¿debo iterar sobre todas las muertes emitidas y luego matar -9? algo más limpio?

+0

relacionados: [Cómo interrumpir un subproceso pitón puesto en marcha con la cáscara = True] (http://stackoverflow.com/q/4789837/ 4279) – jfs

+1

relacionado: [Python: cómo matar proceso (s) hijo (s) cuando el padre muere?] (Http://stackoverflow.com/q/23434842/4279) – jfs

Respuesta

36

Puede use atexit para esto y registre las tareas de limpieza que se ejecutarán cuando su programa finalice.

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

En el proceso de limpieza, también se puede poner en práctica su propia espera, y acabar con él cuando se produce un tiempo de espera de su deseada.

>>> 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 - La última funciones no se ejecutarán si este proceso (proceso padre) se mató.

El siguiente método ventanas ya no es necesaria para el pitón> = 2.6

He aquí una manera de matar a un proceso en Windows. Su objeto Popen tiene un atributo pid, por lo que sólo se puede llamar por éxito = 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 
+0

¿Puede explicarme un poco lo que está haciendo en el código de Windows? –

+0

¿Por qué es necesario el 'win_kill' dado que p.kill() existe? ¿Es para usuarios pre-2.6 de python? –

+0

Sí, creo que en ese momento, 2.5 todavía estaba en uso amplio, y p.kill() no estaba disponible en Windows. – monkut

4

sondeo()

Compruebe si el proceso hijo ha terminado. Devuelve el atributo de código de retorno.

2

¿Hay una manera de asegurar que todo el subproceso creado han muerto en el momento de la salida de un programa en Python? Por subproceso me refiero a los creados con subproceso.Popen().

Se podría violar la encapsulación y prueba que todos los procesos Popen han terminado por hacer

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

Si no es así, debería iterar sobre todas las muertes que emiten y luego mata -9? algo más limpio?

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

14

El subprocess.Popen.wait() es la única manera de asegurarse de que están muertos. De hecho, los sistemas operativos POSIX requieren que esperes a tus hijos. Muchos * nix crearán un proceso "zombie": un niño muerto para el cual el padre no esperó.

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

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

Si el elemento secundario es un ciclo "serve-forever", y no está diseñado para terminar, debe eliminarlo o proporcionar alguna entrada o mensaje que lo forzará a terminar.


Editar.

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

En el sistema operativo Windows, no tiene un os.kill que funcione. Mira esto ActiveState Recipe para terminar 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.

28

En * nix de, tal vez utilizando grupos de procesos puede ayudar a salir - también puede capturar 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 para escalar las señales: de SIGTERM (señal predeterminada para kill) para SIGKILL (a.k.a kill -9). Espere un momento entre las señales para darle al proceso la oportunidad de salir limpiamente antes de kill -9.

2

necesitaba una pequeña variación de este problema (la limpieza de los subprocesos, pero sin salir del programa Python en sí), y ya que 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 y sus elementos secundarios. llamando al os.killpg en él no reducirá su propio proceso de python también.

+0

No, no puedes ... eso cambiará la sesión del proceso en sí, si lo que buscas es matar solo a los niños no es lo que quieres – berdario

+0

He leído la pregunta, ¿has leído mi respuesta? Explícitamente escribí que necesitaba una pequeña variación de este problema ... si buscas en google "subprocess exit children", este será el primer resultado que encuentres. De hecho, poder matar a los niños sin salir es un problema más general que simplemente hacerlo al salir, por lo que será útil para otras personas que tropiecen con el mismo problema. – berdario

+0

Stackoverflow faq dice: '¿En qué consiste específicamente la pregunta? Asegúrate de que tu respuesta lo proporcione, o una alternativa viable. "He proporcionado una alternativa y una respuesta útil, mientras tanto, bajaste las dos respuestas que publiqué.Si dejaras de hostigar a las personas que intentan ayudar a los usuarios de stackoverflow, estoy seguro de que toda la comunidad lo agradecería. – berdario

4

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

Primero instale python-prctl == 1.5.0 entonces cambiar el código de los padres para poner en marcha sus procesos secundarios de la siguiente manera

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

Lo que esto dice es: el subproceso

  • lanzamiento: dormir 100
  • después de que se bifurcan y antes de exec del subproceso, el niño registra para "enviarme un SIGKILL cuando mi padre termina".
3

La respuesta de orip es útil, pero tiene la desventaja de que mata su proceso y devuelve un código de error a su padre. Me evitado que de esta manera:

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 se puede hacer esto con try/except/finally, pero hay que manejar los casos excepcionales y no excepcionales por separado.

+0

exactamente lo que necesitaba ¡gracias! – neok

2

realidad que tenía que hacer esto, pero se trataba de la ejecución de comandos remotos. Queríamos poder detener los procesos al cerrar la conexión al servidor. Además, si, por ejemplo, está ejecutando en python repl, puede seleccionar ejecutar en 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.

1

encontrar una solución para Linux (sin prctl la instalación):

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)) 
Cuestiones relacionadas