读取python 2中的sys.stdin时,奇怪的阻塞行为,并在适当的位置上设有自定义信号处理程序



考虑这个小的python脚本 odd-read-blocking.py

#!/usr/bin/python
import signal
import sys
sig = None

def handler(signum, frame):
    global sig
    sig = signum

signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGTERM, handler)
x = sys.stdin.read(3)
print 'signal', sig
print 'read bytes', len(x)
exit(0)

我运行此操作,并用两个标准输入数据('a' ' n'(馈送它:

> echo a | ./odd-read-blocking.py 
signal None
read bytes 2
>

罚款。

现在,我用相同的两个字节(通过将'a' ' n'键入其标准输入(进行馈送。请注意,标准输入尚未达到EOF,并且有可能有更多数据。因此,读取块,正如它期望的一个字节一样。我在脚本上使用 ctrl c

> ./odd-read-blocking.py 
a
^Csignal 2
read bytes 2
>

很好。我们看到已经读取了两个字节并收到了信号2。

现在我打开一个标准输入流,但不要在其上发送任何字节。读取块如预期。如果我现在使用 ctrl c 在脚本上,它将继续坐在那里等待。读取不会中断。Sigint将不会被处理。

> ./odd-read-blocking.py 
^C

这里什么都没有。脚本仍在运行(似乎在读取时被阻止(。

现在击中一次返回,然后 ctrl c 再次:

^Csignal 2
read bytes 1
>

因此,只有在收到至少某些数据(在这种情况下为单个 n'(的标准输入后,脚本会按照我的期望,并正确中断被阻止的读取并告诉我它已收到信号2和读1个字节

替代性1:而不是使用 ctrl c ,如上所示,我尝试使用同样的东西使用来自单独的终端的kill pid。行为是相同的。

替代2:而不是如上所述使用壳标准输入,而是这样做了:

> sleep 2000 | ./odd-read-blocking.py

使用kill pid将Sigterm发送到odd-read-blocking.py进程时,我会得到相同的行为。在这里,脚本过程只能使用Sigkill杀死(9(。

为什么读取的读取不中断,当它阻塞尚未为空但仍处于活动状态的标准输入流?

我觉得这个奇怪。谁没有?谁可以解释?

简短版本

如果python信号处理程序引发了放弃正在进行的file.read的例外,则所有已经读取的数据is oft >。(任何异步异常(如默认KeyboardInterrupt(,除非您有办法掩盖它,否则基本上不可能防止这种失败。(

为了最大程度地减少对此的需求, file.read返回(即,,,用一个比请求的较短字符串(在被信号中断时,请注意,这是对EOF和非EOF和非eof和non-的添加的添加阻止已记录的I/O案例!但是,当它尚无数据时,它不能执行此操作,因为它返回了空字符串以指示eof。

详细信息

一如既往,理解这样的行为的方法是用strace

阅读(2(

当该过程被阻止时,当信号到达时,实际的read系统调用存在困境。首先,(c(信号处理程序被调用—但是,由于任何两个说明之间都可能发生这种情况,所以除了设置标志(或写入自pipe(之外,它几乎无法做到。那呢?如果设置了SA_RESTART,则恢复呼叫;否则…

如果尚未传输数据,read可能会失败,客户可以检查其信号标志。特殊EINTR失败了,以澄清I/O。

实际上没有出错

如果已经将某些数据写入(用户空间(缓冲区,则不能仅仅返回"失败",因为数据将丢失— client不知道在缓冲区中有多少(如果有(数据有多少(如果有(。因此,它只是返回成功(到目前为止读取的字节数(!这样的简短读数总是可能性的:客户必须再次致电read才能检查其已达到文件末端。(就像 file.read一样,对0字节的简短读取将 be eof。(因此,客户必须在每次读取后都必须检查其信号标志,无论它是否成功。(请注意,这仍然不是完全可靠的,但是对于许多交互式用例来说已经足够好了。(

file.read((

系统调用不是全部:毕竟,终端的正常配置在看到新线后立即返回。Python  2的低级file.readfread的包装器,如果一个人很短,它将发布另一个read。但是,当读取使用EINTR失败时,fread会提早返回,file.read致电您的(Python(信号处理程序。(如果向其添加输出,即使file.read不返回,您也会看到它立即为您发送的每个信号调用。(

然后面临着类似于系统呼叫的困境:正如所讨论的,简短的读数不能为空,因为这意味着EOF。但是,与C信号处理程序不同,Python可以做任意的工作(包括提出一个例外,以立即中止I/O,以危险的数据丢失为代价(,这被认为是一种方便的简化,接口隐藏可能性EINTR。因此,fread呼叫只是默默地重复。

Python 3.5

重试的规则在3.5中更改。现在,io.IOBase.read即使手头有数据,也可以恢复。这是更一致的,但是它迫使使用异常停止阅读,这意味着您不能选择等待某些数据以免丢失任何已经拥有的数据。非常重量的解决方案是切换到多路复用I/O并使用signal.set_wakeup_fd();这具有允许Sigint影响主线程的额外优点,而不必打扰其他所有内容。

最新更新