2016-06-14 4 views
0

Ich schreibe einen Watchdog für Prozesse in einer Testsuite. Ich muss feststellen, ob ein Test hängt.Ermittlung der Zeit seit der letzten Ausgabe des Prozesses - mit subprocess.Popen

Ich könnte einfach den Prozess mit subprocess.Popen(...) starten, und Popen.wait(timeout=to) oder Popen.poll() verwenden und meinen eigenen Timer behalten. Die Tests unterscheiden sich jedoch stark in der Ausführungszeit, was es unmöglich macht, einen guten "Timeout" -Wert zu haben, der für alle Tests sinnvoll ist.

Ich habe festgestellt, dass ein guter Weg, um zu bestimmen, ob ein Test hängen geblieben ist, eine "Zeitüberschreitung" für das letzte Mal, dass der Prozess nichts ausgegeben hat. Zu diesem Zweck hielt ich

process = subprocess.Popen(args='<program>', stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ...) 

und Popen.communicate() verwendet wird, zu bestimmen, wann stdout und/oder stderr nicht None sind. Das Problem ist, dass Popen.communicate(), ohne ein 'Timeout' wird nur warten, bis der Prozess beendet wird, und mit einem 'Timeout' wird eine TimeoutExpired Ausnahme auslösen, aus dem ich nicht feststellen kann, ob etwas gelesen wurde. TimeoutExpired.output ist leer, BTW.

Ich konnte nichts in der Dokumentation finden, die es ermöglicht, die "Lesevorgänge" manuell durchzuführen. Außerdem gibt es normalerweise eine Menge von Ausgaben aus dem Prozess, so dass es von Vorteil wäre, mit stdout=<open_file_descriptor> zu beginnen, da ich keine Bedenken für überlaufende Pipe-Buffer haben würde.

Update/Lösung:

Popen.stdout und Popen.stderr gibt ein "lesbares Stream-Objekt", die man manuell verwenden können abfragen/auswählen und lesen. Ich landete mit select 'Polling Objects', die den poll() Systemaufruf verwenden, wie unten:

import os 
import select 
import subprocess 

p = subprocess.Popen(args="<program>", shell=True, universal_newlines=True, 
        stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
poll_obj = select.poll() 
poll_obj.register(p.stdout, select.POLLIN) 
poll_obj.register(p.stderr, select.POLLIN) 

while p.poll() is None: 
    events = True 
    while events: 
     events = poll_obj.poll(10) 
     for fd, event in events: 
      if event & select.POLLIN: 
       print("STDOUT: " if fd == p.stdout.fileno() else "STDERR: ") 
       print(os.read(fd, 1024).decode()) 
      # else some other error (see 'Polling Objects') 
+0

Windows oder Linux? – rrauenza

+0

@rrauenza Linux – Ramon

Antwort

2

Dies ist eine Art here von abgedeckt ..

Im Wesentlichen müssen Sie select() verwenden, um die fd der abzufragen, um zu sehen, ob sie haben Eingang:

#!/usr/bin/python 

import fcntl import os import select import subprocess 


def setnonblocking(fd): 
    fl = fcntl.fcntl(fd, fcntl.F_GETFL) 
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) 
    return fd 

p = subprocess.Popen("/bin/sh -c 'c=10; while [ $c -gt 0 ]; do echo $c hello; sleep 1; >&2 echo world; sleep 1; let c=$c-1; done'", stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True) 

process_fds = map(setnonblocking, [p.stdout, p.stderr]) 

while process_fds: 
    readable, writable, exceptional = select.select(process_fds, [], process_fds, 100) 
    print "Select: ", readable, writable, exceptional 
    print "Exitcode: ", p.poll() 
    for fd in readable: 
     data = os.read(fd.fileno(), 1024) 
     if data == "": # EOF 
      process_fds.remove(fd) 
      continue 
     if fd == p.stdout: 
      print "STDOUT: ", 
     if fd == p.stderr: 
      print "STDERR: ", 
     print data, 
    for fd in exceptional: 
     process_fds.remove(fd) 

Ausgang:

Select: [<open file '<fdopen>', mode 'rb' at 0x7fed75daa6f0>] [] [] 
Exitcode: None 
STDOUT: 10 hello 
Select: [<open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] [] 
Exitcode: None 
STDERR: world 
Select: [<open file '<fdopen>', mode 'rb' at 0x7fed75daa6f0>] [] [] 
Exitcode: None 
STDOUT: 9 hello 
Select: [<open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] [] 
Exitcode: None 
[...] 
STDOUT: 1 hello 
Select: [<open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] [] 
Exitcode: None 
STDERR: world 
Select: [<open file '<fdopen>', mode 'rb' at 0x7fed75daa6f0>, <open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] [] 
Exitcode: 1 

os.read() wird anstelle von fd.read() verwendet, da nicht zeilenorientiert gelesen werden muss. fd.read() wartet, bis eine neue Zeile gefunden wird - aber dann werden Sie möglicherweise blockieren. Mit dieser Methode können Sie auch Ihre stderr und stdout teilen.

edit: Überarbeitete Prozess zu handhaben schließen, bevor EOF von p.stdout und p.stderr

+0

@ J.F.Sebastian Danke für den Fang! Ich habe nur eine Schleife überarbeitet, bis beide Fds EOF sind. – rrauenza

+0

Verwende 'while some_list:' anstelle von 'while len (some_list):' in Python. Ich bin mir nicht sicher, ob es sicher ist, 'select()' mit blockierenden fds zu verwenden. – jfs

+0

@ J.F.Sebastian Stilweise bevorzuge ich generell die Explizitheit von len (list). Von der 'select()' Systemaufruf-Manpage: * Ein Dateideskriptor gilt als bereit, wenn es möglich ist, die entsprechende I/O-Operation (z. B. read (2)) ohne Blockierung durchzuführen. * Das bedeutet, dass die fd andernfalls blockieren könnte . – rrauenza

0

Hier ist, wie "da Subprocess' letzte Ausgabe Timeout" auf Unix in Python implementieren 3:

#!/usr/bin/env python3 
import os 
import selectors 
import sys 
from subprocess import Popen, PIPE, _PopenSelector as Selector 

timeout = 1 # seconds 
with Popen([sys.executable, '-c', '''import time 
for i in range(10): # dummy script 
    time.sleep(i) 
    print(i, flush=True) 
'''], stdout=PIPE, stderr=PIPE) as process: 
    pipes = {process.stdout: 1, process.stderr: 2} # where to echo data 
    with Selector() as sel: 
     for pipe in pipes: 
      os.set_blocking(pipe.fileno(), False) 
      sel.register(pipe, selectors.EVENT_READ) 
     while pipes: 
      events = sel.select(timeout) 
      if not events: # timeout 
       process.kill() 
      for key, mask in events: 
       assert mask == selectors.EVENT_READ 
       data = os.read(key.fd, 512) 
       if data == b'': # EOF 
        sel.unregister(key.fileobj) 
        del pipes[key.fileobj] 
       else: # echo data 
        os.write(pipes[key.fileobj], data) 

Hinweis: die Die Schleife wird nicht auf process.poll() beendet. Es sind keine Daten verloren. Der Code verwendet den gleichen Selektor, den subprocess Autoren bevorzugen, andernfalls könnte sel = selectors.DefaultSelector() verwendet werden. Wenn ein Enkelprozess die Pipes erben kann, sollten Sie die Schleife beim Timeout aggressiver unterbrechen (EOF may be delayed).Für die Umsetzung os.set_blocking() vor Python 3.5, könnten Sie fcntl:

from fcntl import fcntl, F_GETFL, F_SETFL 

def set_nonblocking(fd): 
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | os.O_NONBLOCK) # set O_NONBLOCK 
Verwandte Themen