web-dev-qa-db-fra.com

Délai d'expiration du sous-processus Python?

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)?

18
sultan

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.

15
dvntehn00bz

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.

8
Abhishek Amit
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.

5
Blairg23

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.

4
Mike Graham

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é.

3
Eric Leschinski

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.

2
Noufal Ibrahim

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.

2
pyfunc

Depuis Python 3.3, il existe également un argument timeout pour les fonctions d'assistance au blocage dans le module de sous-processus.

https://docs.python.org/3/library/subprocess.html

1
Mike Graham

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:

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

1
Tim Savannah

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
0
fja0568