Domanda

C'è un modo per assicurarsi 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? qualcosa di più pulito?

È stato utile?

Soluzione

Puoi utilizzare atexit e registrarti tutte le attività di pulizia da eseguire alla chiusura del programma.

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

Nel 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 padre) viene interrotto.

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

Ecco un modo per terminare un processo in Windows. Il tuo oggetto Popen ha un attributo pid, quindi puoi semplicemente chiamarlo con success = win_kill (p.pid) (Needs 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

Altri suggerimenti

Su * nix, forse usare i gruppi di processi può aiutarti - puoi catturare i sottoprocessi generati anche 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 è di intensificare i 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 uccidere -9 .

Il subprocess.Popen.wait () è l'unico modo per assicurarsi che siano morti. In effetti, i sistemi operativi POSIX richiedono di aspettare i tuoi figli. Molti * nix creano uno "zombi" processo: 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 ed uscire.

Se il bambino ha dei bug e non termina, potresti doverlo uccidere. Dovresti correggere questo errore.

Se il figlio è un "servizio per sempre" loop e non è progettato per terminare, dovresti ucciderlo o fornire un input o un messaggio che lo costringerà a terminare.


Modifica.

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

Nel sistema operativo Windows, non hai un os.kill che funziona. Guarda questa ricetta 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 pulisca ed esca.

Avvertenza: solo Linux! Puoi fare in modo che tuo figlio riceva un segnale quando muore il suo genitore.

Prima installa python-prctl == 1.5.0 poi cambia il tuo codice genitore per avviare i tuoi processi figlio come segue

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

Ciò che dice questo è:

  • avvia sottoprocesso: sleep 100
  • dopo il fork e prima del exec del sottoprocesso, il figlio si registra per " mandami un SIGKILL quando il mio genitore termina " ;.
  

poll ()

     

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

Avevo bisogno di una piccola variazione di questo problema (ripulire 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 a esso e ai suoi figli. chiamando os.killpg in questo modo non si abbatterà anche il proprio processo python.

la risposta di orip è utile ma ha il rovescio della medaglia che uccide il tuo processo e restituisce un codice di errore al 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

Ovviamente puoi farlo con try / tranne / infine, ma devi gestire separatamente i casi eccezionali e non eccezionali.

  

C'è un modo per assicurarsi 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 Popen hanno terminato eseguendo

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

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

Non puoi assicurarti 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.

Una soluzione per Windows potrebbe essere quella di utilizzare l'API del lavoro win32, ad es. Come posso distruggere automaticamente i processi figlio in Windows?

Ecco un'implementazione esistente di Python

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

In realtà dovevo farlo, ma implicava 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 selezionare l'esecuzione in primo piano se vuoi poter 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 la progettazione iniziale. Fatto con python2.7 su Linux.

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

Questo è quello che ho fatto per la mia app posix:

Quando la tua app esiste, chiama il metodo kill () di questa classe: http://www.pixelbeat.org/libs/subProcess.py

Esempio usare qui: http://code.google.com/p/ fslint / source / browse / trunk / fslint-gui # 608

Puoi provare subalive , un pacchetto che ho scritto per problemi simili. Utilizza il ping attivo periodico tramite RPC e il processo slave termina automaticamente quando il master interrompe i ping attivi per qualche motivo.

https://github.com/waszil/subalive

Esempio per master:

from subalive import SubAliveMaster

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

# do your stuff
# ...

Esempio per sottoprocesso slave:

from subalive import SubAliveSlave

# start alive checking
SubAliveSlave()

# do your stuff
# ...
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top