Python: 我在管道读取循环中需要捕获EINTR吗

3 投票
2 回答
732 浏览
提问于 2025-04-17 22:10

简要概述

在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 个回答

0

我觉得这是Python里的一个bug。我找不到任何文档说明multiprocessing.Queue会因为EINTR而抛出IOError(不过在其他输入输出问题上抛出错误是有道理的,这就是你在忽略这个错误之前应该检查抛出异常的errno属性的原因;可以参考errno)。而且它也没有直接对应任何低级的C函数来提供这样的行为。之前有一些讨论提到在C层面处理所有的EINTR,但这个想法没有在3.4版本中实现(我也怀疑它会出现在2.x版本中),所以这可能还是可以报告的问题。

1

Python 3.5 通过将处理 EINTR 的责任交给 Python 运行时,而不是让应用程序代码来处理,解决了这个问题。你可以查看 PEP 475Python 3.5 更新日志 来了解更多。

撰写回答