简体   繁体   English

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

[英]Why does python generate sigpipe exception on closing a fifo file?

TL;DR: Why does closing a fifo file (named pipe) that received a SIGPIPE exception generate another SIGPIPE exception? TL;DR:为什么关闭收到 SIGPIPE 异常的 fifo 文件(命名管道)会生成另一个 SIGPIPE 异常?

My python script is writing bytes to another process, which is a subprocess of my python process, through a FIFO file.我的 python 脚本正在通过 FIFO 文件将字节写入另一个进程,该进程是我的 python 进程的子进程。 (There are some restrictions that I must use a named pipe.) (有一些限制,我必须使用命名管道。)

I have to take account the fact that the subprocess might terminate prematurely.我必须考虑到子进程可能过早终止的事实。 If that happens, my python script must reap the dead subprocess and start it again.如果发生这种情况,我的 python 脚本必须收割死子进程并重新启动它。

To see whether the subprocess dies, I simply try to write to the FIFO first, and if I get a SIGPIPE exception (actually IOError indicating broken pipe), I know it is time to restart my subprocess.要查看子进程是否终止,我只需尝试先写入 FIFO,如果出现 SIGPIPE 异常(实际上是 IOError 表示管道损坏),我知道是时候重新启动我的子进程了。

The minimum example goes as follows:最小示例如下:

#!/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')

# 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(b'hello')
        except IOError as e:
            # The subprocess died. Close the FIFO and reap the subprocess.
            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

To reproduce the result, run that script and manually kill the subprocess by kill -9 <pid> .要重现结果,请运行该脚本并通过kill -9 <pid>手动终止子kill -9 <pid> The traceback will tell that回溯会告诉你

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

So why does closing the FIFO file generate another SIGPIPE exception?那么为什么关闭 FIFO 文件会产生另一个 SIGPIPE 异常呢?

I ran the test on the following platforms and the results are same.我在以下平台上进行了测试,结果相同。

Python 3.7.6 @ Darwin Kernel Version 19.3.0 (MacOS 10.15.3)
Python 3.6.8 @ Linux 4.18.0-147.3.1.el8_1.x86_64 (Centos 8)

It is because Python won't clear the write buffer when fifo.write fails.这是因为当fifo.write失败时,Python 不会清除写入缓冲区。 So the buffer will be wrote to the broken pipe again when executing fifo.close , which causes the second SIGPIPE .所以缓冲区将在执行fifo.close时再次写入损坏的管道,这会导致第二个SIGPIPE

I found the reason with the help of strace .我在strace的帮助下找到了原因。 Here are some details.这里有一些细节。

First, modify a small part of that Python code, as following,首先,修改 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

and save it as test.py .并将其保存为test.py

Then run strace -o strace.out python3 test.py in the shell.然后在 shell 中运行strace -o strace.out python3 test.py Check the strace.out and we can find something like检查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

Note that Python tried to write hello2 twice, during fifo.flush and fifo.close respectively.请注意,Python 尝试编写两次hello2 ,分别在fifo.flushfifo.close期间。 The output explains why two SIGPIPE exceptions are generated well.输出解释了为什么会很好地生成两个 SIGPIPE 异常。

In order to solve the problem, we can use open('tmp.fifo', 'wb', buffering=0) to disable the write buffer.为了解决这个问题,我们可以使用open('tmp.fifo', 'wb', buffering=0)来禁用写缓冲区。 Then only one SIGPIPE exception will be generated.那么只会产生一个 SIGPIPE 异常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM