Python: select () не сигнализирует все входные данные из канала - PullRequest
10 голосов
/ 30 марта 2011

Я пытаюсь загрузить внешнюю программу командной строки с Python и связаться с ней по каналам.Программа принимает ввод текста через стандартный ввод и выводит строки в стандартный вывод.Связь должна быть асинхронной с использованием select ().

Проблема заключается в том, что не все выходные данные программы сигнализируются в select ().Обычно последние одна или две строки не сигнализируются.Если select () возвращается с таймаутом, и я в любом случае пытаюсь прочитать из канала, readline () немедленно возвращается со строкой, отправленной из программы.См. Код ниже.

Программа не буферизует вывод и отправляет весь вывод в текстовых строках.Пока что подключение к программе через каналы во многих других языках и средах работало нормально.

Я пробовал Python 3.1 и 3.2 на 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)

1 Ответ

16 голосов
/ 30 марта 2011

Обратите внимание, что внутренне file.readlines([size]) зацикливается и вызывает системный вызов read() более одного раза, пытаясь заполнить внутренний буфер size. Первый вызов read() немедленно вернется, так как select () указал, что fd был читаемым. Однако 2-й вызов будет блокироваться до тех пор, пока не станут доступны данные, что лишает смысла использование select. В любом случае сложно использовать file.readlines([size]) в асинхронном приложении.

Вы должны вызывать os.read(fd, size) один раз для каждого fd для каждого прохода через select. Это выполняет неблокирующее чтение и позволяет буферизовать частичные строки до тех пор, пока данные не станут доступны, и однозначно обнаруживает EOF.

Я изменил ваш код, чтобы проиллюстрировать, используя os.read. Он также читает из процесса '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
...