Python и FIFO

Я пытался понять FIFO, используя Python под Linux, и обнаружил странное поведение, которого не понимаю.

Далее fifoserver.py

import sys
import time

def readline(f):
    s = f.readline()
    while s == "":
        time.sleep(0.0001)
        s = f.readline()
    return s

while True:
    f = open(sys.argv[1], "r")
    x = float(readline(f))
    g = open(sys.argv[2], "w")
    g.write(str(x**2) + "\n")
    g.close()
    f.close()
    sys.stdout.write("Processed " + repr(x) + "\n")

а это fifoclient.py

import sys
import time

def readline(f):
    s = f.readline()
    while s == "":
        time.sleep(0.0001)
        s = f.readline()
    return s

def req(x):
    f = open("input", "w")
    f.write(str(x) + "\n")
    f.flush()
    g = open("output", "r")
    result = float(readline(g))
    g.close()
    f.close()
    return result

for i in range(100000):
    sys.stdout.write("%i, %s\n" % (i, i*i == req(i)))

Я также создал два FIFO, используя mkfifo input и mkfifo output.

Чего я не понимаю, так это почему, когда я запускаю сервер (с python fifoserver.py input output) и клиент (с python fifoclient.py) с двух консолей, после некоторых запросов клиент вылетает с ошибкой "сломан канал" на f.flush(). Обратите внимание, что перед сбоем я видел от нескольких сотен до нескольких тысяч правильно обработанных запросов, которые работали нормально.

В чем проблема в моем коде?


person 6502    schedule 22.02.2011    source источник
comment
Я думаю, что у вас есть опечатка копирования и вставки здесь. Код клиента и сервера одинаковый.   -  person Jeff Bauer    schedule 23.02.2011
comment
@Jeff Bauer: Извините... Думаю, я никогда не привыкну к этой дурацкой обработке буфера обмена в X.   -  person 6502    schedule 23.02.2011
comment
Ваш сервер пропускает дескрипторы файлов: на каждой итерации цикла вы открываете новый дескриптор файла для sys.argv[2] и никогда не закрываете его. Не думайте, что сборщик мусора позаботится об этом за вас — очистите его явным образом с помощью вызова close() или, что еще лучше, используйте оператор with.   -  person Adam Rosenfield    schedule 23.02.2011
comment
И ваш клиент тоже пропускает дескрипторы файлов по той же причине.   -  person Adam Rosenfield    schedule 23.02.2011
comment
@Adam: файлы будут закрыты автоматически, поскольку задание f = open(...) использует имя f для нового файлового объекта, тем самым удаляя последнюю ссылку на старый. Старый файловый объект немедленно подвергается сборке мусора, включая его закрытие.   -  person Sven Marnach    schedule 23.02.2011
comment
@Adam Rosenfield (и два других комментатора): Нет. Я использовал версию Python, которую не нужно было явно закрывать, но даже ее добавление не решает проблему. Теперь код закрывает дескрипторы, а также совместим как с python2.x, так и с python3.x. Тем не менее проблема остается на обеих версиях.   -  person 6502    schedule 23.02.2011


Ответы (2)


Как упоминалось в других комментариях, у вас есть состояние гонки.

Я подозреваю, что в случае сбоя сервер приостанавливается после одной из этих строк:

g.write(str(x**2) + "\n")
g.close()

Затем клиент может прочитать результат, распечатать его на экране и вернуться обратно. Затем он снова открывает f — что успешно, потому что он все еще открыт на стороне сервера — и записывает сообщение. Тем временем серверу удалось закрыть f. Затем сброс на стороне клиента выполняет системный вызов write() в канале, который запускает SIGPIPE, потому что теперь он закрыт на другой стороне.

Если я прав, вы сможете исправить это, переместив f.close() сервера выше g.write(...).

person caf    schedule 23.02.2011
comment
Это была проблема из-за сломанной трубы! И это также то, что я не понял о FIFO... Я думал, что повторное открытие канала после последовательности open/write/close от клиента застопорилось бы, если бы сервер сделал только open/read и еще не closed (по сути, сервер находился в же сеанс первого открытия). Такое поведение значительно усложняет IMO использование двух FIFO для двунаправленной связи. - person 6502; 23.02.2011
comment
@6502: Да. На практике сокеты домена UNIX обычно гораздо лучше подходят для такого взаимодействия между локальными процессами. - person caf; 23.02.2011

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

Я не понимаю, почему вы все время открываете и закрываете трубу.

Попробуйте запустить процесс, который сначала читает канал, пусть он откроет канал, и он будет ждать данных.

Затем запустите pipe-writer и заставьте его выкачать все данные, которые вы хотите отправить. Он остановится, если вырвется вперед. Когда писатель закрывает канал, читатель получает нулевые байты вместо блокировки и должен закрыться. IIRC, Python обнаруживает это и возвращает EOF.

person Ian    schedule 22.02.2011
comment
С открытием FIFO для записи не прерывается, если никто не читает... он просто ждет. На самом деле я могу запустить сначала либо сервер, либо клиент, и все работает для нескольких запросов (я видел от нескольких сотен до 60000+). Затем, по непонятным мне причинам, я получаю сломанную трубу при выполнении команды сброса в клиенте. - person 6502; 23.02.2011
comment
Не закрывайте файл. Пока сервер записывает в stdout, файл f закрывается, поэтому сброс клиента невозможен. - person Ian; 23.02.2011
comment
Сервер закрывает вход только после записи вывода, поэтому клиент должен был уже пройти вызов flush и открыть выходной FIFO для чтения. Я что-то пропустил? - person 6502; 23.02.2011