it-swarm.com.de

Python, Popen und select - Warten auf einen Prozessabschluss oder ein Timeout

Ich führe einen Unterprozess aus mit:

  p = subprocess.Popen("subprocess", 
                       stdout=subprocess.PIPE, 
                       stderr=subprocess.PIPE, 
                       stdin=subprocess.PIPE)

Dieser Unterprozess könnte entweder sofort mit einem Fehler in stderr beendet werden oder weiter ausgeführt werden. Ich möchte eine dieser Bedingungen erkennen - die letztere, indem ich einige Sekunden warte.

Ich habe das versucht:

  SECONDS_TO_WAIT = 10
  select.select([], 
                [p.stdout, p.stderr], 
                [p.stdout, p.stderr],
                SECONDS_TO_WAIT)

aber es kehrt einfach zurück:

  ([],[],[])

in beiden Fällen. Was kann ich machen?

22
George Yuan

Haben Sie versucht, die Popen.Poll () -Methode zu verwenden. Sie könnten das einfach tun:

p = subprocess.Popen("subprocess", 
                   stdout=subprocess.PIPE, 
                   stderr=subprocess.PIPE, 
                   stdin=subprocess.PIPE)

time.sleep(SECONDS_TO_WAIT)
retcode = p.poll()
if retcode is not None:
   # process has terminated

Dies führt dazu, dass Sie immer 10 Sekunden warten, aber wenn der Fehlerfall selten ist, wird dies über alle Erfolgsfälle hinweg amortisiert.


Bearbeiten:

Wie wäre es mit:

t_nought = time.time()
seconds_passed = 0

while(p.poll() is not None and seconds_passed < 10):
    seconds_passed = time.time() - t_nought

if seconds_passed >= 10:
   #TIMED OUT

Dies hat die Häßlichkeit, ein hektisches Warten zu sein, aber ich denke, es erfüllt, was Sie wollen.

Wenn Sie sich noch einmal die Select-Call-Dokumentation ansehen, möchten Sie sie vielleicht wie folgt ändern:

SECONDS_TO_WAIT = 10
  select.select([p.stderr], 
                [], 
                [p.stdout, p.stderr],
                SECONDS_TO_WAIT)

Da Sie normalerweise von stderr lesen möchten, möchten Sie wissen, wann etwas zum Lesen verfügbar ist (dh der Fehlerfall).

Ich hoffe das hilft.

15
grieve

Das ist was ich mir ausgedacht habe. Funktioniert bei Bedarf und muss für den p-Prozess kein Zeitlimit haben, jedoch mit einer halb ausgelasteten Schleife.

def runCmd(cmd, timeout=None):
    '''
    Will execute a command, read the output and return it back.

    @param cmd: command to execute
    @param timeout: process timeout in seconds
    @return: a Tuple of three: first stdout, then stderr, then exit code
    @raise OSError: on missing command or if a timeout was reached
    '''

    ph_out = None # process output
    ph_err = None # stderr
    ph_ret = None # return code

    p = subprocess.Popen(cmd, Shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
    # if timeout is not set wait for process to complete
    if not timeout:
        ph_ret = p.wait()
    else:
        fin_time = time.time() + timeout
        while p.poll() == None and fin_time > time.time():
            time.sleep(1)

        # if timeout reached, raise an exception
        if fin_time < time.time():

            # starting 2.6 subprocess has a kill() method which is preferable
            # p.kill()
            os.kill(p.pid, signal.SIGKILL)
            raise OSError("Process timeout has been reached")

        ph_ret = p.returncode


    ph_out, ph_err = p.communicate()

    return (ph_out, ph_err, ph_ret)
8
Darjus Loktevic

Hier ist ein schönes Beispiel:

from threading import Timer
from subprocess import Popen, PIPE

def kill_proc():
    proc.kill()

proc = Popen("ping 127.0.0.1", Shell=True)
t = Timer(60, kill_proc)
t.start()
proc.wait()
2
Evan

Die Verwendung von select und das Schlafen macht eigentlich keinen Sinn. select (oder ein beliebiger Kernel-Abfragemechanismus) ist inhärent nützlich für die asynchrone Programmierung, aber Ihr Beispiel ist synchron. Schreiben Sie also Ihren Code um, um die normale Blockierungsmethode zu verwenden, oder erwägen Sie die Verwendung von Twisted:

from twisted.internet.utils import getProcessOutputAndValue
from twisted.internet import reactor    

def stop(r):
    reactor.stop()
def eb(reason):
    reason.printTraceback()
def cb(result):
    stdout, stderr, exitcode = result
    # do something
getProcessOutputAndValue('/bin/someproc', []
    ).addCallback(cb).addErrback(eb).addBoth(stop)
reactor.run()

Mit Twisted ist dies übrigens sicherer, wenn Sie Ihr eigenes ProcessProtocol schreiben:

http://twistedmatrix.com/projects/core/documentation/howto/prozess.html

1

Wenn Sie, wie Sie in den Kommentaren oben sagten, die Ausgabe jedes Mal anpassen und den Befehl erneut ausführen, würden die folgenden Aktionen funktionieren?

from threading import Timer
import subprocess

WAIT_TIME = 10.0

def check_cmd(cmd):
    p = subprocess.Popen(cmd,
        stdout=subprocess.PIPE, 
            stderr=subprocess.PIPE)
    def _check():
        if p.poll()!=0:
            print cmd+" did not quit within the given time period."

    # check whether the given process has exited WAIT_TIME
    # seconds from now
    Timer(WAIT_TIME, _check).start()

check_cmd('echo')
check_cmd('python')

Wenn der Code oben ausgeführt wird, wird Folgendes ausgegeben:

python did not quit within the given time period.

Der einzige Nachteil des obigen Codes, den ich mir vorstellen kann, sind die möglicherweise überlappenden Prozesse, während Sie check_cmd weiter ausführen.

1
shsmurfy

Python 3.3

import subprocess as sp

try:
    sp.check_call(["/subprocess"], timeout=10,
                  stdin=sp.DEVNULL, stdout=sp.DEVNULL, stderr=sp.DEVNULL)
except sp.TimeoutError:
    # timeout (the subprocess is killed at this point)
except sp.CalledProcessError:
    # subprocess failed before timeout
else:
    # subprocess ended successfully before timeout

Siehe TimeoutExpired docs .

1
jfs

Dies ist eine Paraphrase über Evans Antwort, berücksichtigt jedoch Folgendes:

  1. Das Timer-Objekt explizit abbrechen: Wenn das Timer-Intervall lang wäre und der Prozess "willentlich" beendet wird, könnte dies Ihr Skript hängen lassen :(
  2. Es gibt ein intrinsisches Rennen im Timer-Ansatz (der Timer-Versuch, den Prozess abzubrechen kurz danach der Prozess ist abgestorben und dies wird unter Windows eine Ausnahme auslösen). 

      DEVNULL = open(os.devnull, "wb")
      process = Popen("c:/myExe.exe", stdout=DEVNULL) # no need for stdout
    
      def kill_process():
      """ Kill process helper"""
      try:
         process.kill()
       except OSError:
         pass  # Swallow the error
    
      timer = Timer(timeout_in_sec, kill_process)
      timer.start()
    
      process.wait()
      timer.cancel()
    
0
Shmil The Cat