2010-09-17 16 views
18

Gibt es ein Argument oder Optionen, um ein Timeout für Pythons subprocess.Popen-Methode einzurichten?Python-Subprozess-Timeout?

Etwas wie folgt aus:

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

+1

bezogen werden: [subprocess mit Timeout] (http://stackoverflow.com/q/1191374/4279) – jfs

Antwort

15

Ich würde empfehlen, einen Blick auf die Timer class im Threading-Modul. Ich benutzte es, um ein Timeout für einen Popen zu implementieren.

Erstellen Sie zunächst einen Rückruf:

def timeout(p): 
     if p.poll() is None: 
      print 'Error: process taking too long to complete--terminating' 
      p.kill() 

dann den Prozess öffnen:

proc = Popen(...) 

Dann einen Timer erstellen, die den Rückruf Passieren der Prozess wird es nennen.

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

Irgendwo später im Programm, können Sie die Zeile hinzufügen möchten:

t.cancel() 

Andernfalls wird das Python-Programm läuft weiter, bis der Timer läuft beendet.

EDIT: Ich wurde darauf hingewiesen, dass es eine Race-Bedingung gibt, dass der Subprozess p zwischen den p.poll() und p.kill() Aufrufen beendet werden kann. Ich glaube, der folgende Code das in Ordnung bringen kann:

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 

Obwohl Sie die Ausnahme reinigen möchten speziell behandeln Umgang nur die besondere Ausnahme, die auftritt, wenn der Teilprozess bereits normal beendet hat.

+0

Dieser Code hat eine Racebedingung. –

+0

Mike, könnten Sie einen Fix oben erarbeiten oder bearbeiten? Ich habe einen ähnlichen Code mehrmals verwendet, und wenn es ein Problem gibt, möchte ich es definitiv beheben. – dvntehn00bz

+1

'print 'Fehler: Prozess dauert zu lange, um abzuschließen - termining'' kann auch dann ausgeführt werden, wenn es eine Lüge ist und Ihr Prozess endet, ohne dass Sie ihn töten (weil es in den Momenten zwischen Ihren' Poll'- und' Kill'-Aufrufen geschieht). –

8

subprocess.Popen nicht blockiert, so dass Sie etwas tun können:

import time 

p = subprocess.Popen(['...']) 
time.sleep(20) 
if p.poll() is None: 
    p.kill() 
    print 'timed out' 
else: 
    print p.communicate() 

Es hat den Nachteil, dass Sie immer mindestens 20 Sekunden warten muss, bis es beendet wird.

+3

Dies würde diesen Prozess für 20 Sekunden einfrieren. Ist das akzeptabel? –

+3

besiegt das irgendwie den Punkt der Verwendung eines Unterprozesses nicht? – aaronasterling

+0

scheint es ist! Ich denke, das ist eine kleine Unannehmlichkeit der Verwendung von Subprozess. – sultan

2

Leider gibt es keine solche Lösung. Ich schaffte es, dies mit einem Thread-Timer zu tun, der zusammen mit dem Prozess starten würde, der ihn nach dem Timeout beenden würde, aber ich stieß auf einige veraltete Datei-Deskriptor-Probleme aufgrund von Zombie-Prozessen oder ähnlichem.

+0

+1 Das wäre meine Lösung. – aaronasterling

+1

Ich habe ein solches Problem auch mit Dateideskriptoren bei Zombie-Prozessen erlebt. – sultan

+0

Sultan. Es sollte möglich sein, diese zu korrigieren. Es gelang mir schließlich, meine Bewerbung in etwas Machbares zu verwandeln, aber es war nicht allgemein genug, um es zu veröffentlichen. –

2

Nein, es gibt keine Auszeit. Ich denke, was Sie suchen, ist, den Unterprozess nach einiger Zeit zu beenden. Da Sie den Unterprozess signalisieren können, sollten Sie ihn auch töten können.

generischer Ansatz ein Signal an subprocess senden an:

proc = subprocess.Popen([command]) 
time.sleep(1) 
print 'signaling child' 
sys.stdout.flush() 
os.kill(proc.pid, signal.SIGUSR1) 

Sie können diesen Mechanismus verwenden, nach einer Auszeit Frist zu kündigen.

+0

Ist es universal sys.stdout.flush() für alle Apps, die Subprozess verwenden? – sultan

+2

Das ist ungefähr wie Abhisheks Antwort. Er benutzt SIGKILL und du benutzt SIGUSR1. Es hat die gleichen Probleme. –

+0

Ok danke Jungs! – sultan

4

Sie könnten

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), ...) 

mit Verdrehte der asynchronen Prozess API tun.

0

Für Linux können Sie ein Signal verwenden. Dies ist plattformabhängig, daher ist eine andere Lösung für Windows erforderlich. Es kann jedoch mit Mac funktionieren.

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

Ein Python subprocess Auto-Timeout wird nicht gebaut, so dass Sie Ihre eigenen haben werden zu bauen.

Dies funktioniert für mich auf Ubuntu 12.10 Python 2.7.3

Setzen Sie diese in einer Datei 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() 

Speichern nannte es ausgeführt wird, und führen Sie es:

python test.py 

Der Befehl sleep 20 dauert 20 Sekunden. Wenn es nicht in 3 Sekunden endet (wird es nicht), dann wird der Prozess beendet.

Zwischen dem Ausführen des Prozesses und dem Beenden des Prozesses liegen drei Sekunden.

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) 

Der Ausgang dieses sollte sein:

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

wo es, dass in der ersten Ausführung zu sehen ist, während der Prozess korrekt beendet (Rückkehrcode 0), die in der zweiten der Prozess wurde beendet (Rückgabecode -15).

Ich habe nicht in Windows getestet; Aber abgesehen von der Aktualisierung des Beispielbefehls sollte es funktionieren, da ich in der Dokumentation nichts gefunden habe, was besagt, dass thread.join oder process.terminate nicht unterstützt wird.

1

Ja, https://pypi.python.org/pypi/python-subprocess2 den Popen Modul mit zwei zusätzlichen Funktionen erweitern,

Popen.waitUpTo(timeout=seconds) 

Diese wartet bis werden Anzahl der Sekunden für den Prozess acertain abzuschließen, sonst return None

auch

Popen.waitOrTerminate 

Dies wird zu einem Punkt warten, und dann rufen .terminate(), dann .kill(), eine oderdie andere oder eine Kombination von beiden, siehe Dokumentation für weitere Informationen:

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