352 votes

sous-processus avec délai d'attente

Voici le code Python pour exécuter une commande arbitraire retour de son stdout de données, ou de soulever une exception de non-zéro des codes de sortie:

proc = subprocess.Popen(
    cmd,
    stderr=subprocess.STDOUT,  # merge stdout and stderr
    stdout=subprocess.PIPE,
    shell=True)

communicate est utilisé pour attendre que le processus de sortie de:

stdoutdata, stderrdata = proc.communicate()

L' subprocess module ne prend pas en charge timeout--possibilité de tuer un processus en cours d'exécution pour plus de X secondes--par conséquent, communicate peut prendre une éternité à s'exécuter.

Quelle est la plus simple façon de mettre en œuvre les délais dans un programme en Python destiné à être exécuté sur Windows et Linux?

213voto

jcollado Points 18325

Je ne sais pas beaucoup sur les détails de bas niveau; mais, étant donné que, dans python 2.6 l'API offre la possibilité d'attendre pour les threads et mettre fin à des processus, ce qui sur l'exécution du processus dans un autre thread?

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 cet extrait de code dans ma machine est:

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

où l'on voit que, dans la première exécution, le processus de fini correctement (code de retour 0), tandis que dans le second, la le processus a été interrompu (code de retour -15).

Je n'ai pas testé sous windows, mais, en plus de la mise à jour de l'exemple de commande, je pense que cela devrait fonctionner car je n'ai pas trouvé dans le la documentation de tout ce qui dit ce thread.rejoindre ou de processus.mettre fin à n'est pas pris en charge.

199voto

J.F. Sebastian Points 102961

En Python 3.3+:

from subprocess import STDOUT, check_output as qx

output = qx(cmd, stderr=STDOUT, timeout=seconds)

output est une chaîne d'octets que contient la commande fusionné stdout, stderr données.

Ce code génère CalledProcessError sur la non-nulle, comme précisé dans la question du texte, contrairement à proc.communicate() méthode.

J'ai supprimé shell=True car il est souvent utilisé inutilement. Vous pouvez toujours rajouter si cmd en effet l'exige.

La fonction de temporisation est disponible sur Python 2.x via l' http://pypi.python.org/pypi/subprocess32/ backport de l'3.2+ sous-processus module. Personne ne devrait utiliser les 2.x de la bibliothèque standard sous-processus module s'ils peuvent l'aider. Utilisation subprocess32. Il fixe des tonnes de problèmes.

147voto

sussudio Points 11

jcollado la réponse peut être simplifiée en utilisant le filetage.Classe Timer:

import subprocess, shlex
from threading import Timer

def run(cmd, timeout_sec):
  proc = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, 
    stderr=subprocess.PIPE)
  kill_proc = lambda p: p.kill()
  timer = Timer(timeout_sec, kill_proc, [proc])
  timer.start()
  stdout,stderr = proc.communicate()
  timer.cancel()

84voto

Alex Martelli Points 330805

Si vous êtes sous Unix,

import signal
  ...
class Alarm(Exception):
    pass

def alarm_handler(signum, frame):
    raise Alarm

signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(5*60)  # 5 minutes
try:
    stdoutdata, stderrdata = proc.communicate()
    signal.alarm(0)  # reset the alarm
except Alarm:
    print "Oops, taking too long!"
    # whatever else

44voto

Björn Lindqvist Points 3739

Voici Alex Martelli est une solution comme un module avec un bon processus de meurtre. Les autres méthodes ne fonctionnent pas parce qu'ils n'utilisez pas de proc.communiquer(). Donc, si vous avez un processus qui produit beaucoup de sortie, il remplira son tampon de sortie et bloquer jusqu'à ce que vous lisez quelque chose d'elle.

from os import kill
from signal import alarm, signal, SIGALRM, SIGKILL
from subprocess import PIPE, Popen

def run(args, cwd = None, shell = False, kill_tree = True, timeout = -1, env = None):
    '''
    Run a command with a timeout after which it will be forcibly
    killed.
    '''
    class Alarm(Exception):
        pass
    def alarm_handler(signum, frame):
        raise Alarm
    p = Popen(args, shell = shell, cwd = cwd, stdout = PIPE, stderr = PIPE, env = env)
    if timeout != -1:
        signal(SIGALRM, alarm_handler)
        alarm(timeout)
    try:
        stdout, stderr = p.communicate()
        if timeout != -1:
            alarm(0)
    except Alarm:
        pids = [p.pid]
        if kill_tree:
            pids.extend(get_process_children(p.pid))
        for pid in pids:
            # process might have died before getting to this line
            # so wrap to avoid OSError: no such process
            try: 
                kill(pid, SIGKILL)
            except OSError:
                pass
        return -9, '', ''
    return p.returncode, stdout, stderr

def get_process_children(pid):
    p = Popen('ps --no-headers -o pid --ppid %d' % pid, shell = True,
              stdout = PIPE, stderr = PIPE)
    stdout, stderr = p.communicate()
    return [int(p) for p in stdout.split()]

if __name__ == '__main__':
    print run('find /', shell = True, timeout = 3)
    print run('find', shell = True)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X