2010-09-17 16 views
18

Esiste un argomento o opzioni per impostare un timeout per il metodo subprocess.Popen di Python?Timeout del sottoprocesso Python?

Qualcosa di simile a questo:

subprocess.Popen(['..'], ..., timeout=20)?

+1

correlati: [sottoprocesso con timeout] (http://stackoverflow.com/q/1191374/4279) – jfs

risposta

15

Si consiglia di dare un'occhiata allo Timer class nel modulo di threading. L'ho usato per implementare un timeout per un Popen.

In primo luogo, creare un callback:

def timeout(p): 
     if p.poll() is None: 
      print 'Error: process taking too long to complete--terminating' 
      p.kill() 

Poi aprire il processo:

proc = Popen(...) 

quindi creare un timer che chiamerà la richiamata passando il processo ad esso.

t = threading.Timer(10.0, timeout, [proc]) 
    t.start() 
    t.join() 

Da qualche parte più avanti nel programma, si consiglia di aggiungere la riga:

t.cancel() 

In caso contrario, il programma Python continuerà a girare fino a quando il timer ha terminato l'esecuzione.

MODIFICA: mi è stato comunicato che esiste una condizione di competizione in cui il sottoprocesso p può terminare tra le chiamate p.poll() e p.kill(). Credo che il seguente codice può rimediare:

import errno 

    def timeout(p): 
     if p.poll() is None: 
      try: 
       p.kill() 
       print 'Error: process taking too long to complete--terminating' 
      except OSError as e: 
       if e.errno != errno.ESRCH: 
        raise 

anche se si consiglia di pulire la gestione per gestire in modo specifico proprio la particolare eccezione che si verifica quando il sottoprocesso ha già terminato normalmente eccezione.

+0

Questo codice ha una condizione di competizione. –

+0

Mike, potresti elaborare o modificare una correzione sopra? Ho usato codice simile più volte, e se c'è un problema, mi piacerebbe sicuramente aggiustarlo. – dvntehn00bz

+1

'print 'Errore: il processo richiede troppo tempo per completare - terminating'' può essere eseguito anche se è una bugia e il processo termina senza che lo si uccida (perché lo fa nei momenti tra le chiamate' poll' e 'kill'). –

8

subprocess.Popen non bloccare in modo da poter fare qualcosa di simile:

import time 

p = subprocess.Popen(['...']) 
time.sleep(20) 
if p.poll() is None: 
    p.kill() 
    print 'timed out' 
else: 
    print p.communicate() 

Ha uno svantaggio in quanto si deve sempre attendere almeno 20 secondi che finisca.

+3

Questo congelerebbe questo processo per 20 secondi. È accettabile? –

+3

non impedisce questo tipo di utilizzo di un sottoprocesso? – aaronasterling

+0

sembra che lo sia! Penso che questo sia un piccolo inconveniente nell'usare il sottoprocesso. – sultan

2

Sfortunatamente, non esiste una soluzione del genere. Sono riuscito a farlo usando un timer filettato che si avviava con il processo che l'avrebbe ucciso dopo il timeout ma ho incontrato alcuni problemi dei descrittori di file stantii a causa di processi di zombi o altri.

+0

+1 Questa sarebbe la mia soluzione. – aaronasterling

+1

Ho riscontrato anche questo problema con i descrittori di file sui processi di zombie. – sultan

+0

Sultan. Dovrebbe essere possibile correggerli. Sono riuscito a lucidare finalmente la mia applicazione in qualcosa di lavorabile ma non è stata abbastanza generica da pubblicare. –

2

No non c'è tempo. Immagino, quello che stai cercando è quello di uccidere il processo secondario dopo un po 'di tempo. Dato che sei in grado di segnalare il sottoprocesso, dovresti essere in grado di ucciderlo anche tu.

approccio generico per l'invio di un segnale al sottoprocesso:

proc = subprocess.Popen([command]) 
time.sleep(1) 
print 'signaling child' 
sys.stdout.flush() 
os.kill(proc.pid, signal.SIGUSR1) 

Si potrebbe utilizzare questo meccanismo per terminare dopo un periodo di timeout.

+0

È universale sys.stdout.flush() per tutte le app che utilizzano il sottoprocesso? – sultan

+2

Questa è praticamente la risposta di Abhishek. Sta usando SIGKILL e stai usando SIGUSR1. Ha gli stessi problemi. –

+0

Ok grazie ragazzi! – sultan

4

Si potrebbe fare

from twisted.internet import reactor, protocol, error, defer 

class DyingProcessProtocol(protocol.ProcessProtocol): 
    def __init__(self, timeout): 
     self.timeout = timeout 

    def connectionMade(self): 
     @defer.inlineCallbacks 
     def killIfAlive(): 
      try: 
       yield self.transport.signalProcess('KILL') 
      except error.ProcessExitedAlready: 
       pass 

     d = reactor.callLater(self.timeout, killIfAlive) 

reactor.spawnProcess(DyingProcessProtocol(20), ...) 

utilizzando asincrona API processo di ritorto.

0

Per Linux, è possibile utilizzare un segnale. Questo dipende dalla piattaforma, quindi è necessaria un'altra soluzione per Windows. Potrebbe funzionare con Mac però.

def launch_cmd(cmd, timeout=0): 
    '''Launch an external command 

    It launchs the program redirecting the program's STDIO 
    to a communication pipe, and appends those responses to 
    a list. Waits for the program to exit, then returns the 
    ouput lines. 

    Args: 
     cmd: command Line of the external program to launch 
     time: time to wait for the command to complete, 0 for indefinitely 
    Returns: 
     A list of the response lines from the program  
    ''' 

    import subprocess 
    import signal 

    class Alarm(Exception): 
     pass 

    def alarm_handler(signum, frame): 
     raise Alarm 

    lines = [] 

    if not launch_cmd.init: 
     launch_cmd.init = True 
     signal.signal(signal.SIGALRM, alarm_handler) 

    p = subprocess.Popen(cmd, stdout=subprocess.PIPE) 
    signal.alarm(timeout) # timeout sec 

    try: 
     for line in p.stdout: 
      lines.append(line.rstrip()) 
     p.wait() 
     signal.alarm(0) # disable alarm 
    except: 
     print "launch_cmd taking too long!" 
     p.kill() 

    return lines   
launch_cmd.init = False 
3

Un auto-timeout python sottoprocesso non è integrato, in modo che stai andando ad avere per costruire il proprio.

questo funziona per me su Ubuntu 12.10 in esecuzione pitone 2.7.3

Mettere questo in un file chiamato test.py

#!/usr/bin/python 
import subprocess 
import threading 

class RunMyCmd(threading.Thread): 
    def __init__(self, cmd, timeout): 
     threading.Thread.__init__(self) 
     self.cmd = cmd 
     self.timeout = timeout 

    def run(self): 
     self.p = subprocess.Popen(self.cmd) 
     self.p.wait() 

    def run_the_process(self): 
     self.start() 
     self.join(self.timeout) 

     if self.is_alive(): 
      self.p.terminate() #if your process needs a kill -9 to make 
           #it go away, use self.p.kill() here instead. 

      self.join() 

RunMyCmd(["sleep", "20"], 3).run_the_process() 

Salva esso, ed eseguirlo:

python test.py 

Il comando sleep 20 richiede 20 secondi per essere completato. Se non termina in 3 secondi (non lo farà), il processo termina.

[email protected]:~$ python test.py 
[email protected]:~$ 

Ci sono tre secondi tra l'esecuzione del processo e la sua conclusione.

5
import subprocess, threading 

class Command(object): 
    def __init__(self, cmd): 
     self.cmd = cmd 
     self.process = None 

    def run(self, timeout): 
     def target(): 
      print 'Thread started' 
      self.process = subprocess.Popen(self.cmd, shell=True) 
      self.process.communicate() 
      print 'Thread finished' 

     thread = threading.Thread(target=target) 
     thread.start() 

     thread.join(timeout) 
     if thread.is_alive(): 
      print 'Terminating process' 
      self.process.terminate() 
      thread.join() 
     print self.process.returncode 

command = Command("echo 'Process started'; sleep 2; echo 'Process finished'") 
command.run(timeout=3) 
command.run(timeout=1) 

L'uscita di questo dovrebbe essere:

Thread started 
Process started 
Process finished 
Thread finished 
0 
Thread started 
Process started 
Terminating process 
Thread finished 
-15 

dove si può vedere che, nella prima realizzazione, il procedimento terminato correttamente (codice di ritorno 0), mentre la nella seconda la processo è stato terminato (codice di ritorno -15).

Non ho testato in Windows; ma, a parte l'aggiornamento del comando di esempio, penso che dovrebbe funzionare poiché non ho trovato nella documentazione nulla che dica che thread.join o process.terminate non è supportato.

1

Sì, https://pypi.python.org/pypi/python-subprocess2 si estenderà il modulo Popen con due funzioni aggiuntive,

Popen.waitUpTo(timeout=seconds) 

Ciò attendere fino a acertain numero di secondi per il completamento del processo, altrimenti restituisce None

anche,

Popen.waitOrTerminate 

Questo attenderà fino a un certo punto, e quindi chiamare .terminate(), poi .kill(), uno orthe altro o una combinazione di entrambi, vedi documentazione per tutti i dettagli:

http://htmlpreview.github.io/?https://github.com/kata198/python-subprocess2/blob/master/doc/subprocess2.html