Python: 我在管道读取循环中需要捕获EINTR吗
简要概述
在Python中读取管道时,我是否应该处理EINTR“系统调用被中断”的错误?如果应该,我该如何测试这样的代码呢?
描述
在下面的错误追踪中,self._dataq
是一个 multiprocessing.Queue
(严格来说,我使用的是 billiard
库,但我觉得它们的代码基本相同)。一个Python子进程偶尔会向队列的另一端写入数据。我认为发生的情况是,系统调用正在读取供队列使用的管道时,收到了一个信号——可能是第二次Ctrl+C事件发出的SIGINT信号(第一次SIGINT信号发生在你看到用户的^C
的地方,日志输出的第二行,我的信号处理程序捕获了这个SIGINT信号,这在日志中的警告信息中可以看到)。
[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.timedout()
开始返回True
时放弃尝试从self._dataq
读取数据。
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
。如果是信号导致了EINTR错误,那么仅仅返回到Python执行except IOError:
语句,就会让Python级别的信号处理程序运行。
这样说对吗?如果对,那我是否可以测试我代码中的这个改动?我不太清楚如何编写一个不会出现严重竞争条件的单元测试。
2 个回答
我觉得这是Python里的一个bug。我找不到任何文档说明multiprocessing.Queue
会因为EINTR
而抛出IOError
(不过在其他输入输出问题上抛出错误是有道理的,这就是你在忽略这个错误之前应该检查抛出异常的errno属性的原因;可以参考errno
)。而且它也没有直接对应任何低级的C函数来提供这样的行为。之前有一些讨论提到在C层面处理所有的EINTR
,但这个想法没有在3.4版本中实现(我也怀疑它会出现在2.x版本中),所以这可能还是可以报告的问题。
Python 3.5 通过将处理 EINTR
的责任交给 Python 运行时,而不是让应用程序代码来处理,解决了这个问题。你可以查看 PEP 475 和 Python 3.5 更新日志 来了解更多。