español - subprocess python windows
Python subprocess timeout? (10)
¿Hay algún argumento u opción para configurar un tiempo de espera para el método de subproceso.Popen de Python?
Algo como esto:
subprocess.Popen([''..''], ..., timeout=20)
?
A partir de Python 3.3, también hay un argumento de timeout
para las funciones de ayuda de bloqueo en el módulo de subproceso.
Desafortunadamente, no hay tal solución. Logré hacer esto usando un temporizador de hilos que se iniciaría junto con el proceso que lo mataría después del tiempo de espera, pero me encontré con algunos problemas de descriptor de archivos obsoletos debido a procesos zombie o similares.
No, no hay tiempo fuera. Supongo que lo que estás buscando es matar el subproceso después de un tiempo. Ya que eres capaz de señalar el subproceso, deberías poder matarlo también.
enfoque genérico para enviar una señal a subproceso:
proc = subprocess.Popen([command])
time.sleep(1)
print ''signaling child''
sys.stdout.flush()
os.kill(proc.pid, signal.SIGUSR1)
Podría usar este mecanismo para terminar después de un período de tiempo de espera.
Para Linux, puedes usar una señal. Esto depende de la plataforma, por lo que se requiere otra solución para Windows. Aunque puede funcionar con Mac.
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
Podrías hacerlo
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), ...)
utilizando la API del proceso asíncrono de Twisted.
Recomendaría echar un vistazo a la clase de temporizador en el módulo de subprocesos. Lo usé para implementar un tiempo de espera para un Popen.
Primero, crea una devolución de llamada:
def timeout( p ):
if p.poll() is None:
print ''Error: process taking too long to complete--terminating''
p.kill()
Luego abre el proceso:
proc = Popen( ... )
Luego cree un temporizador que llamará a la devolución de llamada y le pasará el proceso.
t = threading.Timer( 10.0, timeout, [proc] )
t.start()
t.join()
En algún lugar más adelante en el programa, es posible que desee agregar la línea:
t.cancel()
De lo contrario, el programa python continuará ejecutándose hasta que el temporizador haya terminado de ejecutarse.
EDITAR: Me informaron que existe una condición de carrera para que el subproceso p pueda terminar entre las llamadas p.poll () y p.kill (). Creo que el siguiente código puede arreglar eso:
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
Aunque es posible que desee limpiar el manejo de excepciones para tratar específicamente solo la excepción particular que se produce cuando el subproceso ya ha finalizado normalmente.
Sí, https://pypi.python.org/pypi/python-subprocess2 extenderá el módulo Popen con dos funciones adicionales,
Popen.waitUpTo(timeout=seconds)
Esto esperará hasta un cierto número de segundos para que el proceso se complete, de lo contrario, devuelva Ninguno
además,
Popen.waitOrTerminate
Esto esperará hasta un punto y luego llamará a .terminate (), luego a .kill (), a uno u otro o a alguna combinación de ambos, consulte la documentación para obtener detalles completos:
subprocess.Popen no se bloquea, por lo que puedes hacer algo como esto:
import time
p = subprocess.Popen([''...''])
time.sleep(20)
if p.poll() is None:
p.kill()
print ''timed out''
else:
print p.communicate()
Tiene el inconveniente de que siempre debe esperar al menos 20 segundos para que finalice.
Un auto-tiempo de espera de subproceso de Python no está integrado, por lo que tendrá que crear el suyo propio.
Esto me funciona en Ubuntu 12.10 ejecutando python 2.7.3
Pon esto en un archivo llamado 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()
Guárdalo y ejecútalo.
python test.py
El comando sleep 20
tarda 20 segundos en completarse. Si no finaliza en 3 segundos (no lo hará), el proceso finaliza.
el@apollo:~$ python test.py
el@apollo:~$
Hay tres segundos entre el momento en que se ejecuta el proceso y se termina.
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)
La salida de esto debería ser:
Thread started
Process started
Process finished
Thread finished
0
Thread started
Process started
Terminating process
Thread finished
-15
donde se puede ver que, en la primera ejecución, el proceso finalizó correctamente (código de retorno 0), mientras que en la segunda se terminó el proceso (código de retorno -15).
No he probado en windows; pero, aparte de actualizar el comando de ejemplo, creo que debería funcionar ya que no he encontrado en la documentación nada que diga que thread.join o process.terminate no sea compatible.