2010-09-17 24 views
18

Existe-t-il des arguments ou des options pour configurer un délai d'attente pour la méthode subprocess.Popen de Python?Délai d'expiration du sous-processus Python?

Quelque chose comme ceci:

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

+1

liés: [subprocess avec timeout] (http://stackoverflow.com/q/1191374/4279) – jfs

Répondre

15

Je vous conseille de regarder le Timer class dans le module de filetage. Je l'ai utilisé pour mettre en place 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() 

Ouvrez ensuite le processus:

proc = Popen(...) 

Ensuite, créez une minuterie qui appellera la fonction de rappel qui passe le processus à lui.

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

Quelque part plus tard dans le programme, vous pouvez ajouter la ligne:

t.cancel() 

Sinon, le programme Python en cours d'exécution jusqu'à ce que la garder minuterie a fini.

EDIT: J'ai été informé qu'il existe une condition de concurrence dans 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 pouvez nettoyer l'exception de manutention pour traiter spécifiquement que l'exception particulière qui se produit lorsque le sous-processus est déjà terminé normalement.

+0

Ce code a une condition de concurrence. –

+0

Mike, pourriez-vous élaborer ou modifier un correctif ci-dessus? J'ai utilisé un code similaire plusieurs fois, et s'il y a un problème, je voudrais vraiment le réparer. – dvntehn00bz

+1

'print 'Erreur: le processus prend trop de temps - terminating'' peut s'exécuter même si c'est un mensonge et que votre processus se termine sans que vous le détruisiez (parce qu'il le fait dans les moments entre vos appels' poll' et 'kill'). –

8

subprocess.Popen ne bloque pas que vous pouvez 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() 

Il a un inconvénient que vous devez toujours attendre au moins 20 secondes à la fin.

+3

Cela gelerait ce processus pendant 20 secondes. Est-ce acceptable? –

+3

cela ne va-t-il pas à l'encontre de l'utilisation d'un sous-processus? – aaronasterling

+0

semble que c'est! Je pense que c'est un petit inconvénient d'utiliser un sous-processus. – sultan

2

Malheureusement, il n'y a pas une telle solution. J'ai réussi à faire cela en utilisant une minuterie filetée qui se lancerait avec le processus qui le tuerait après le délai d'attente, mais j'ai rencontré des problèmes de descripteur de fichier périmés à cause de processus de zombies ou autres.

+0

+1 Ce serait ma solution. – aaronasterling

+1

J'ai aussi rencontré un tel problème avec les descripteurs de fichiers sur les processus zombies. – sultan

+0

Sultan. Il devrait être possible de les corriger. J'ai réussi à enfin polir mon application en quelque chose de réalisable, mais ce n'était pas assez générique pour être publié. –

2

Il n'y a pas de temps mort. Je suppose que ce que vous cherchez est de tuer le sous-processus après un certain temps. Puisque vous êtes capable de signaler le sous-processus, vous devriez pouvoir le tuer aussi.

approche générique pour envoyer un signal à 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 se terminer après une période de temporisation.

+0

Est-il universel sys.stdout.flush() pour toutes les applications utilisant un sous-processus? – sultan

+2

Cela ressemble beaucoup à la réponse d'Abhishek. Il utilise SIGKILL et vous utilisez SIGUSR1. Il a les mêmes problèmes. –

+0

Ok merci les gars! – sultan

4

Vous pouvez 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.

0

Pour Linux, vous pouvez utiliser un signal. Cela dépend de la plateforme, donc une autre solution est requise pour Windows. Cela peut fonctionner avec 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 
3

Un auto-délai subprocess python n'est pas construit, donc vous allez devoir construire votre propre.

Cela fonctionne pour moi sur Ubuntu 12.10 python en cours d'exécution 2.7.3

Mettre cela 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() 

Enregistrer, et l'exécuter:

python test.py 

La commande sleep 20 prend 20 secondes pour se terminer. S'il ne se termine pas dans 3 secondes (ce ne sera pas le cas), le processus est terminé.

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

Il s'écoule trois secondes entre l'exécution du processus et sa fin.

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) 

La sortie de ce devrait être:

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

où il peut être vu que, dans la première exécution, le processus terminé correctement (code de retour 0), tandis que le dans le second la le processus a été terminé (code de retour -15).

Je n'ai pas testé dans Windows; mais, mis à part la mise à jour de la commande example, je pense que cela devrait fonctionner puisque je n'ai trouvé dans la documentation rien qui dise que thread.join ou process.terminate n'est pas supporté.

1

Oui, https://pypi.python.org/pypi/python-subprocess2 étendra le module Popen avec deux fonctions supplémentaires,

Popen.waitUpTo(timeout=seconds) 

Cela attendra jusqu'à acertain nombre de secondes pour que le processus soit terminé, retournez autrement Aucun

aussi,

Popen.waitOrTerminate 

Cela attendra jusqu'à un certain point, puis appelez .terminate(), puis .kill(), l'un ou l'autre oula une combinaison des deux, voir docs pour plus de détails:

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