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)
?
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)
?
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.
Questo codice ha una condizione di competizione. –
Mike, potresti elaborare o modificare una correzione sopra? Ho usato codice simile più volte, e se c'è un problema, mi piacerebbe sicuramente aggiustarlo. – dvntehn00bz
'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'). –
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.
Questo congelerebbe questo processo per 20 secondi. È accettabile? –
non impedisce questo tipo di utilizzo di un sottoprocesso? – aaronasterling
sembra che lo sia! Penso che questo sia un piccolo inconveniente nell'usare il sottoprocesso. – sultan
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.
+1 Questa sarebbe la mia soluzione. – aaronasterling
Ho riscontrato anche questo problema con i descrittori di file sui processi di zombie. – sultan
Sultan. Dovrebbe essere possibile correggerli. Sono riuscito a lucidare finalmente la mia applicazione in qualcosa di lavorabile ma non è stata abbastanza generica da pubblicare. –
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.
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.
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
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.
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.
A partire da Python 3.3, esiste anche un argomento timeout
per le funzioni di supporto di blocco nel modulo di sottoprocesso.
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:
correlati: [sottoprocesso con timeout] (http://stackoverflow.com/q/1191374/4279) – jfs