Existe-t-il des arguments ou des options permettant de définir un délai d'attente pour la méthode subprocess.Popen de Python?
Quelque chose comme ça:
subprocess.Popen(['..'], ..., timeout=20)
?
Je conseillerais de jeter un coup d'œil à la classe Timer dans le module de threading. Je l'ai utilisé pour implémenter un délai d'attente pour un Popen.
Tout d'abord, créez un rappel:
def timeout( p ):
if p.poll() is None:
print 'Error: process taking too long to complete--terminating'
p.kill()
Puis ouvrez le processus:
proc = Popen( ... )
Créez ensuite une minuterie qui appellera le rappel en lui passant le processus.
t = threading.Timer( 10.0, timeout, [proc] )
t.start()
t.join()
Quelque part plus tard dans le programme, vous voudrez peut-être ajouter la ligne suivante:
t.cancel()
Sinon, le programme python continuera à s'exécuter jusqu'à ce que la minuterie soit terminée.
EDIT: On m'a informé qu'il existe une condition de concurrence critique selon laquelle le sous-processus p peut se terminer entre les appels p.poll () et p.kill (). Je crois que le code suivant peut résoudre ce problème:
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
Bien que vous souhaitiez peut-être nettoyer la gestion des exceptions pour gérer uniquement l’exception particulière qui se produit lorsque le sous-processus s’est déjà terminé normalement.
subprocess.Popen ne bloque pas pour que vous puissiez faire quelque chose comme ceci:
import time
p = subprocess.Popen(['...'])
time.sleep(20)
if p.poll() is None:
p.kill()
print 'timed out'
else:
print p.communicate()
Son inconvénient est que vous devez toujours attendre au moins 20 secondes avant la fin.
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)
Le résultat de ceci devrait être:
Thread started
Process started
Process finished
Thread finished
0
Thread started
Process started
Terminating process
Thread finished
-15
où on peut voir que, lors de la première exécution, le processus s'est terminé correctement (code retour 0), tandis que dans le second, le processus s'est terminé (code retour -15).
Je n'ai pas testé sous Windows; mais, à part la mise à jour de la commande exemple, je pense que cela devrait fonctionner car je n'ai trouvé dans la documentation aucun élément indiquant que thread.join ou process.terminate n'est pas pris en charge.
Vous pourriez faire
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), ...)
en utilisant l'API de processus asynchrone de Twisted.
Le délai d'attente automatique d'un sous-processus python n'est pas intégré, vous devez donc créer le vôtre.
Cela fonctionne pour moi sur Ubuntu 12.10 exécutant python 2.7.3
Mettez ceci dans un fichier appelé 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()
Enregistrez-le et lancez-le:
python test.py
La commande sleep 20
prend 20 secondes. S'il ne se termine pas dans les 3 secondes (ce ne sera pas le cas), le processus est terminé.
el@apollo:~$ python test.py
el@apollo:~$
Il y a trois secondes entre le moment où le processus est exécuté et celui-ci est terminé.
Malheureusement, il n'y a pas une telle solution. J'ai réussi à le faire en utilisant un minuteur fileté qui se lancerait avec le processus qui le tuerait après l'expiration du délai, mais je me suis heurté à des problèmes de descripteur de fichier périmés à cause de processus zombis ou autres.
Non, il n'y a pas de temps mort. Je suppose que ce que vous recherchez, c’est de tuer le processus secondaire après un certain temps. Puisque vous êtes en mesure de signaler le sous-processus, vous devriez également pouvoir le tuer.
approche générique pour envoyer un signal au sous-processus:
proc = subprocess.Popen([command])
time.sleep(1)
print 'signaling child'
sys.stdout.flush()
os.kill(proc.pid, signal.SIGUSR1)
Vous pouvez utiliser ce mécanisme pour terminer après une période de temporisation.
Depuis Python 3.3, il existe également un argument timeout
pour les fonctions d'assistance au blocage dans le module de sous-processus.
Oui, https://pypi.python.org/pypi/python-subprocess2 étendra le module Popen avec deux fonctions supplémentaires,
Popen.waitUpTo(timeout=seconds)
Cela va attendre jusqu'à un certain nombre de secondes pour que le processus se termine, sinon retourne None
également,
Popen.waitOrTerminate
Cela va attendre jusqu'à un point, puis appeler .terminate (), puis .kill (), l'un ou l'autre ou une combinaison des deux, voir docs pour plus de détails:
Pour Linux, vous pouvez utiliser un signal. Ceci dépend de la plate-forme, une autre solution est donc requise pour Windows. Cela peut fonctionner avec Mac cependant.
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