Python: select () no señala todas las entradas desde la tubería

Estoy intentando cargar un progtwig de línea de comandos externo con Python y comunicarme con él a través de tuberías. El progtwig toma la entrada de texto a través de la entrada estándar y produce una salida de texto en líneas a la salida estándar. La comunicación debe ser asíncrona utilizando select ().

El problema es que no todas las salidas del progtwig se señalizan en select (). Usualmente las últimas una o dos líneas no están señalizadas. Si select () regresa con un tiempo de espera y estoy intentando leer de la tubería de todos modos, readline () regresa inmediatamente con la línea enviada desde el progtwig. Ver código a continuación.

El progtwig no almacena en búfer la salida y envía toda la salida en líneas de texto. La conexión al progtwig a través de tuberías en muchos otros idiomas y entornos hasta ahora ha funcionado bien.

He probado Python 3.1 y 3.2 en Mac OSX 10.6.

import subprocess import select engine = subprocess.Popen("Engine", bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE) engine.stdin.write(b"go\n") engine.stdin.flush() while True: inputready,outputready,exceptready = select.select( [engine.stdout.fileno()] , [], [], 10.0) if (inputready, outputready, exceptready) == ([], [], []): print("trying to read from engine anyway...") line = engine.stdout.readline() print(line) for s in inputready: line = engine.stdout.readline() print(line) 

Tenga en cuenta que internamente file.readlines([size]) bucle e invoca el sistema de read() más de una vez, intentando llenar un búfer interno de size . La primera llamada a read() regresará inmediatamente, ya que select () indicó que fd era legible. Sin embargo, la segunda llamada se bloqueará hasta que los datos estén disponibles, lo que anula el propósito de usar seleccionar. En cualquier caso, es complicado usar file.readlines([size]) en una aplicación asíncrona.

Debe llamar a os.read(fd, size) una vez en cada fd para cada paso a través de selección. Esto realiza una lectura sin locking, y le permite almacenar líneas parciales hasta que los datos estén disponibles y detecte EOF de forma inequívoca.

os.read tu código para ilustrar usando os.read . También se lee desde el proceso ‘ stderr :

 import os import select import subprocess from cStringIO import StringIO target = 'Engine' PIPE = subprocess.PIPE engine = subprocess.Popen(target, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE) engine.stdin.write(b"go\n") engine.stdin.flush() class LineReader(object): def __init__(self, fd): self._fd = fd self._buf = '' def fileno(self): return self._fd def readlines(self): data = os.read(self._fd, 4096) if not data: # EOF return None self._buf += data if '\n' not in data: return [] tmp = self._buf.split('\n') lines, self._buf = tmp[:-1], tmp[-1] return lines proc_stdout = LineReader(engine.stdout.fileno()) proc_stderr = LineReader(engine.stderr.fileno()) readable = [proc_stdout, proc_stderr] while readable: ready = select.select(readable, [], [], 10.0)[0] if not ready: continue for stream in ready: lines = stream.readlines() if lines is None: # got EOF on this stream readable.remove(stream) continue for line in lines: print line