Domanda Garantire che i sottoprocessi siano morti all'uscita dal programma Python


C'è un modo per garantire che tutti i sottoprocessi creati siano morti al momento dell'uscita di un programma Python? Per sottoprocesso intendo quelli creati con subprocess.Popen ().

In caso contrario, dovrei ripetere tutte le uccisioni emesse e poi uccidere -9? niente di più pulito?


44
2017-11-26 10:21


origine


risposte:


Puoi usare atexit per questo, e registrare eventuali attività di pulizia da eseguire all'uscita del programma.

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

Nel tuo processo di pulizia, puoi anche implementare la tua attesa e ucciderla quando si verifica il timeout desiderato.

>>> 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 - Le funzioni registrate non verranno eseguite se questo processo (processo genitore) viene ucciso.

Il seguente metodo di Windows non è più necessario per python> = 2.6

Ecco un modo per uccidere un processo in Windows. Il tuo oggetto Popen ha un attributo pid, quindi puoi chiamarlo semplicemente per success = win_kill (p.pid) (Esigenze pywin32 installato):

    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

38
2017-11-26 13:36



Su * nix, forse usando i gruppi di processi puoi aiutarti - puoi catturare anche i sottoprocessi generati dai tuoi sottoprocessi.

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

Un'altra considerazione è l'escalation dei segnali: da SIGTERM (segnale predefinito per kill) a SIGKILL (a.k.a kill -9). Attendi qualche istante tra i segnali per dare al processo la possibilità di uscire in modo pulito prima di te kill -9 esso.


30
2017-11-26 22:02



Il subprocess.Popen.wait() è l'unico modo per assicurarsi che siano morti. In effetti, i sistemi operativi POSIX richiedono che tu aspetti i tuoi figli. Molti * nix creeranno un processo "zombi": un bambino morto per il quale il genitore non ha aspettato.

Se il bambino è ragionevolmente ben scritto, termina. Spesso i bambini leggono da PIPE. Chiudere l'input è un grande suggerimento per il bambino che dovrebbe chiudere il negozio e uscire.

Se il bambino ha bug e non termina, potrebbe essere necessario ucciderlo. Dovresti correggere questo bug.

Se il bambino è un ciclo "serve per sempre" e non è progettato per terminare, dovresti ucciderlo o fornire qualche input o messaggio che lo costringerà a terminare.


Modificare.

Nei sistemi operativi standard, hai os.kill( PID, 9 ). Uccidere -9 è duro, a proposito. Se riesci a ucciderli con SIGABRT (6?) O SIGTERM (15) è più educato.

Nel sistema operativo Windows, non hai un os.kill che funzioni. Guarda questo Ricetta di ActiveState per terminare un processo in Windows.

Abbiamo processi figlio che sono server WSGI. Per terminarli facciamo un GET su un URL speciale; questo fa sì che il bambino si ripulisca e esca.


14
2017-11-26 10:56



Attenzione: solo per Linux! Puoi dare al tuo bambino un segnale quando muore suo genitore.

Prima installa python-prctl == 1.5.0, quindi modifica il codice genitore per avviare i processi figli come segue

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

Quello che dice è:

  • avvia il sottoprocesso: sleep 100
  • dopo aver biforcuto e prima dell'esecuzione del sottoprocesso, il bambino si registra per "inviarmi un SIGKILL quando il mio genitore termina ".

5
2017-12-04 13:59



sondaggio ()

Controlla se il processo figlio è terminato.   Restituisce l'attributo returncode.


3
2017-11-26 10:32



La risposta di orip è utile ma ha il rovescio della medaglia che uccide il tuo processo e restituisce un codice di errore tuo genitore. L'ho evitato in questo modo:

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

E poi:

  with CleanChildProcesses():
    # Do your work here

Naturalmente puoi farlo con try / except / finally ma devi gestire i casi eccezionali e non eccezionali separatamente.


3
2018-01-08 02:17



C'è un modo per garantire che tutti i sottoprocessi creati siano morti al momento dell'uscita di un programma Python? Per sottoprocesso intendo quelli creati con subprocess.Popen ().

Potresti violare l'incapsulamento e test che tutti i processi di Popen si sono conclusi facendo

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

In caso contrario, dovrei ripetere tutte le uccisioni emesse e poi uccidere -9? niente di più pulito?

Non è possibile garantire che tutti i sottoprocessi siano morti senza uscire e uccidere tutti i sopravvissuti. Ma se hai questo problema, è probabilmente perché hai un problema di progettazione più profondo.


2
2017-11-26 10:54



Avevo bisogno di una piccola variazione di questo problema (ripulendo i sottoprocessi, ma senza uscire dal programma Python stesso), e poiché non è menzionato qui tra le altre risposte:

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

setsid eseguirà il programma in una nuova sessione, assegnando così un nuovo gruppo di processi ad esso e ai suoi figli. chiamata os.killpg su di esso quindi non porterà giù anche il tuo processo Python.


2
2018-03-22 19:46



Una soluzione per Windows potrebbe essere quella di usare l'API di lavoro win32, ad es. Come faccio a distruggere automaticamente i processi figli in Windows?

Ecco una implementazione Python esistente

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


2
2018-02-22 22:17



In realtà dovevo farlo, ma comportava l'esecuzione di comandi remoti. Volevamo essere in grado di interrompere i processi chiudendo la connessione al server. Inoltre, se, ad esempio, stai eseguendo il python repl, puoi scegliere di eseguire come foreground se vuoi essere in grado di usare Ctrl-C per uscire.

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

Grazie a Malcolm Handley per il design iniziale. Fatto con python2.7 su linux.


2
2017-12-29 14:39



Scopri una soluzione per Linux (senza installare 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)) 

1
2018-04-01 03:23