为什么python在关闭fifo文件时会生成sigpipe异常?

为什么python在关闭fifo文件时会生成sigpipe异常?,python,exception,fifo,sigpipe,Python,Exception,Fifo,Sigpipe,TL;DR:为什么关闭接收到SIGPPIPE异常的fifo文件(命名管道)会生成另一个SIGPPIPE异常 我的python脚本正在通过FIFO文件将字节写入另一个进程,这是我的python进程的子进程。(我必须使用命名管道有一些限制。) 我必须考虑子流程可能会提前终止这一事实。如果发生这种情况,我的python脚本必须捕获死子进程并重新启动它 为了查看子进程是否死亡,我只需先尝试写入FIFO,如果出现SIGPIPE异常(实际上是IOError指示管道破裂),我知道是时候重新启动子进程了 最起码

TL;DR:为什么关闭接收到SIGPPIPE异常的fifo文件(命名管道)会生成另一个SIGPPIPE异常

我的python脚本正在通过FIFO文件将字节写入另一个进程,这是我的python进程的子进程。(我必须使用命名管道有一些限制。)

我必须考虑子流程可能会提前终止这一事实。如果发生这种情况,我的python脚本必须捕获死子进程并重新启动它

为了查看子进程是否死亡,我只需先尝试写入FIFO,如果出现SIGPIPE异常(实际上是IOError指示管道破裂),我知道是时候重新启动子进程了

最起码的例子如下:

#/usr/bin/env蟒蛇3
导入操作系统
输入信号
导入子流程
#FIFO文件。
os.mkfifo(“tmp.fifo”)
#一个子过程,只需丢弃来自FIFO的任何输入。
FNULL=open(os.devnull,'w')
proc=subprocess.Popen(['/bin/cat',tmp.fifo'],stdout=FNULL,stderr=FNULL)
打印('pid=%d'%proc.pid)
#打开FIFO,并且必须是二进制模式。
fifo=打开('tmp.fifo','wb')
#无休止地向FIFO写入。
尽管如此:
#尝试写入FIFO,按需重新启动子进程,直到成功。
尽管如此:
尝试:
#乐观地写入FIFO。
先入先出(b‘你好’)
除IOE错误外:
#子进程终止。关闭FIFO并收获子流程。
fifo.close()
os.kill(proc.pid,signal.SIGKILL)
进程等待()
#再次启动子流程。
proc=subprocess.Popen(['/bin/cat',tmp.fifo'],stdout=FNULL,stderr=FNULL)
打印('pid=%d'%proc.pid)
fifo=打开('tmp.fifo','wb')
其他:
#这篇文章写得很好。
打破
要复制结果,请运行该脚本并通过
kill-9
手动终止子进程。回溯会告诉你的

Traceback (most recent call last):
  File "./test.py", line 24, in <module>
    fifo.write(b'hello')
BrokenPipeError: [Errno 32] Broken pipe

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "./test.py", line 27, in <module>
    fifo.close()
BrokenPipeError: [Errno 32] Broken pipe

这是因为当
fifo.write失败时,Python不会清除写入缓冲区。因此,在执行
fifo.close
时,缓冲区将再次写入损坏的管道,这将导致第二个
SIGPIPE

我在
strace
的帮助下找到了原因。这里有一些细节

首先,修改Python代码的一小部分,如下所示

#!/usr/bin/env python3
import os
import signal
import subprocess

# The FIFO file.
os.mkfifo('tmp.fifo')

# A subprocess to simply discard any input from the FIFO.
FNULL = open(os.devnull, 'w')
proc = subprocess.Popen(['/bin/cat', 'tmp.fifo'], stdout=FNULL, stderr=FNULL)
print('pid = %d' % proc.pid)

# Open the FIFO, and MUST BE BINARY MODE.
fifo = open('tmp.fifo', 'wb')

i = 0
# Endlessly write to the FIFO.
while True:

    # Try to write to the FIFO, restart the subprocess on demand, until succeeded.
    while True:
        try:
            # Optimistically write to the FIFO.
            fifo.write(f'hello{i}'.encode())
            fifo.flush()
        except IOError as e:
            # The subprocess died. Close the FIFO and reap the subprocess.
            print('IOError is occured.')
            fifo.close()
            os.kill(proc.pid, signal.SIGKILL)
            proc.wait()

            # Start the subprocess again.
            proc = subprocess.Popen(['/bin/cat', 'tmp.fifo'], stdout=FNULL, stderr=FNULL)
            print('pid = %d' % proc.pid)
            fifo = open('tmp.fifo', 'wb')
        else:
            # The write goes on well.
            break
    os.kill(proc.pid, signal.SIGKILL)
    i += 1
并将其另存为
test.py

然后在shell中运行
strace-o strace.out python3 test.py
。查看
strace.out
,我们可以找到类似

openat(AT_FDCWD, "tmp.fifo", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 4
fstat(4, {st_mode=S_IFIFO|0644, st_size=0, ...}) = 0
ioctl(4, TCGETS, 0x7ffcba5cd290)        = -1 ENOTTY (Inappropriate ioctl for device)
lseek(4, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
write(4, "hello0", 6)                   = 6
kill(35626, SIGKILL)                    = 0
write(4, "hello1", 6)                   = 6
kill(35626, SIGKILL)                    = 0
write(4, "hello2", 6)                   = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=35625, si_uid=1000} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=35626, si_uid=1000, si_status=SIGKILL, si_utime=0, si_stime=0} ---
write(1, "IOError is occured.\n", 20)   = 20
write(4, "hello2", 6)                   = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=35625, si_uid=1000} ---
close(4)                                = 0
write(2, "Traceback (most recent call last"..., 35) = 35
write(2, "  File \"test.py\", line 26, in <m"..., 39) = 39
openat(AT_FDCWD,“tmp.fifo”,O_WRONLY | O|u CREAT | O|u TRUNC | O|u CLOEXEC,0666)=4
fstat(4,{st_mode=S_IFIFO | 0644,st_size=0,…})=0
ioctl(4,TCGETS,0x7ffcba5cd290)=-1 ENOTTY(设备的ioctl不合适)
lseek(4,0,SEEK_CUR)=-1 ESPIPE(非法SEEK)
写(4,“你好”,6)=6
kill(35626,SIGKILL)=0
写(4,“你好”,6)=6
kill(35626,SIGKILL)=0
写(4,“hello2”,6)=-1个EPIPE(断管)
---SIGPIPE{si_signo=SIGPIPE,si_code=si_USER,si_pid=35625,si_uid=1000}---
---SIGCHLD{si_signo=SIGCHLD,si_code=CLD_KILLED,si_pid=35626,si_uid=1000,si_status=sigcill,si_utime=0,si_stime=0}---
写入(1,“发生IOError”。\n”,20)=20
写(4,“hello2”,6)=-1个EPIPE(断管)
---SIGPIPE{si_signo=SIGPIPE,si_code=si_USER,si_pid=35625,si_uid=1000}---
关闭(4)=0
写下(2,“回溯(最近一次呼叫最后一次)”,35)=35
将(2,“文件\“test.py\”)第26行写入
openat(AT_FDCWD, "tmp.fifo", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 4
fstat(4, {st_mode=S_IFIFO|0644, st_size=0, ...}) = 0
ioctl(4, TCGETS, 0x7ffcba5cd290)        = -1 ENOTTY (Inappropriate ioctl for device)
lseek(4, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
write(4, "hello0", 6)                   = 6
kill(35626, SIGKILL)                    = 0
write(4, "hello1", 6)                   = 6
kill(35626, SIGKILL)                    = 0
write(4, "hello2", 6)                   = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=35625, si_uid=1000} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=35626, si_uid=1000, si_status=SIGKILL, si_utime=0, si_stime=0} ---
write(1, "IOError is occured.\n", 20)   = 20
write(4, "hello2", 6)                   = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=35625, si_uid=1000} ---
close(4)                                = 0
write(2, "Traceback (most recent call last"..., 35) = 35
write(2, "  File \"test.py\", line 26, in <m"..., 39) = 39