Python: нужно ли перехватывать EINTR в цикле чтения канала
ТЛ; др
Должен ли я обрабатывать ошибки EINTR "прервал системный вызов" при чтении канала в Python, и если да, то как мне проверить такой код?
Описание
В следе ниже, self._dataq
это multiprocessing.Queue
(технически я использую billiard
библиотека, но я думаю, что они в основном один и тот же код). Подпроцесс Python иногда записывает данные на другой конец очереди. Я думаю, что произошло то, что системный вызов считывал канал, который питает очередь, и поступил сигнал - возможно, SIGINT из второго события Ctrl+C (первый SIGINT произошел там, где вы видите пользователя ^C
во второй строке вывода журнала, и мой обработчик сигнала перехватил этот SIGINT, как вы можете видеть в сообщении WARNING в журнале).
[INFO 2014-03-05 14:16:06,000] Doing some work, la-dee-da
^C[WARNING 2014-03-05 14:16:07,344] Commencing shutdown. (Signal SIGINT, process 2469.). Press Ctrl+C again to exit immediately.
[DEBUG 2014-03-05 14:16:07,347] Terminating subprocess
Traceback (most recent call last):
[... a bunch of stuff omitted]
File "mycode.py", line 97, in __next__
result = self._dataq.get(timeout=0.1)
File "/usr/local/lib/python2.7/site-packages/billiard/queues.py", line 103, in get
if timeout < 0 or not self._poll(timeout):
IOError: [Errno 4] Interrupted system call
Заявление result = self._dataq.get(timeout=0.1)
в приведенной выше трассировке находится в середине цикла, который выглядит следующим образом. Основная цель цикла состоит в том, чтобы я мог отказаться от попыток чтения из self._dataq
когда self.timedout()
начинает возвращаться True
,
import queue
while True:
try:
result = self._dataq.get(timeout=0.1)
except queue.Empty:
if self.timedout():
self.close()
raise MyTimedoutError()
else:
break
Вопрос
Если моя теория о том, почему IOError
произошло правильно, то try
...except
блок выше должен ловить и игнорировать IOError
s, когда они вызваны прерванными системными вызовами. Если это сигнал, вызвавший ошибку EINTR, то просто возвращение в Python для запуска except IOError:
оператор позволит запускать обработчики сигналов уровня Python.
Это верно? Если да, возможно ли проверить это изменение в моем коде? Для меня не очевидно, как бы я написал модульный тест, который не содержал бы серьезного состояния гонки.
2 ответа
Python 3.5 решает эту проблему, возлагая ответственность за EINTR
в среде выполнения Python, а не в коде приложения. См. PEP 475 и список изменений Python 3.5.
Я собираюсь назвать это ошибкой в Python. Я не могу найти документацию, которая multiprocessing.Queue
разрешено поднимать IOError
на EINTR
(хотя имеет смысл поднять вопрос о других проблемах ввода-вывода, поэтому вам следует проверить атрибут errno возникшего исключения, прежде чем его игнорировать; см. errno
), и он не отображается напрямую на любую низкоуровневую функцию C, которая бы обеспечивала такое поведение. Было некоторое обсуждение обработки всех EINTR
s на уровне C, но это не сделало это в 3.4 (и я сомневаюсь, что оно вообще превратится в 2.x), так что это, вероятно, все еще справедливая игра для отчета.