Linux FIFO在我预期的时候没有返回EOF

时间:2016-03-11 14:53:43

标签: python linux pipe

让我们考虑以下Python代码,由Linux系统上的cpython执行(警告:它将尝试创建或覆盖/tmp/first/tmp/second/tmp/third中的文件

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import subprocess
import os
import sys
import threading

class ThreadizedPopen(threading.Thread):

    def __init__(self, command, stdin_name, stdout_name):
        super(ThreadizedPopen, self).__init__()
        self.command = command
        self.stdin_name = stdin_name
        self.stdout_name = stdout_name
        self.returncode = None

    def run(self):
        with open(self.stdin_name, 'rb') as fin:
            with open(self.stdout_name, 'wb') as fout:
                popen = subprocess.Popen(self.command, stdin=fin, stdout=fout, stderr=None)
                popen.communicate()
                self.returncode = popen.returncode

def main():
    os.system('mkfifo /tmp/first')
    os.system('mkfifo /tmp/second')
    os.system('mkfifo /tmp/third')

    popen1 = ThreadizedPopen(['cat'], '/tmp/first', '/tmp/second')
    popen2 = ThreadizedPopen(['cat'], '/tmp/second', '/tmp/third')
    popen1.start()
    popen2.start()
    with open('/tmp/third') as fin:
        print fin.read()
    popen1.join()
    popen2.join()

if __name__ == '__main__':
    main()

然后我执行它,在另一个shell上,我在/tmp/first中写了一些东西(比如echo test > /tmp/first)。我希望Python程序能够快速退出并打印出我送到第一个FIFO的相同内容。

理论上应该发生的是,我在/tmp/first中编写的字符串被我的程序生成的两个cat进程复制到其他两个FIFO,然后被主Python程序选中写在它的标准。一旦每个cat进程完成,它应该关闭其写入FIFO的末尾,使相应的读取结束返回EOF并触发后续cat进程的终止。使用strace查看程序会发现测试字符串已通过所有三个FIFO正确复制,并由主Python程序读取。第一个FIFO也正确关闭(第一个cat进程与其管理器Python线程一起退出)。但是,第二个cat进程停留在read()调用中,期望来自其读取FIFO的数据。

我不明白为什么会这样。从pipe(t) man page(据我所知,也涵盖了这种FIFO)来看,一旦写入结束(及其所有重复)被关闭,FIFO上的读取就会返回EOF。根据{{​​1}},这似乎是跟踪(特别是strace进程已经死了,因此它的所有文件描述符都被关闭;它的管理线程也关闭了它的描述符,我可以看到它cat输出。)

你能告诉我为什么会这样吗?如果它有用,我可以发布strace输出。

1 个答案:

答案 0 :(得分:1)

我找到了this question 并简单地将close_fds=True添加到您的subprocess电话中。您的代码现在显示为:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import subprocess
import os
import sys
import threading

class ThreadizedPopen(threading.Thread):

    def __init__(self, command, stdin_name, stdout_name):
        super(ThreadizedPopen, self).__init__()
        self.command = command
        self.stdin_name = stdin_name
        self.stdout_name = stdout_name
        self.returncode = None

    def run(self):
        with open(self.stdin_name, 'rb') as fin:
            with open(self.stdout_name, 'wb') as fout:
                popen = subprocess.Popen(self.command, stdin=fin, stdout=fout, stderr=None, close_fds=True)
                popen.communicate()
                self.returncode = popen.returncode

def main():
    os.system('mkfifo /tmp/first')
    os.system('mkfifo /tmp/second')
    os.system('mkfifo /tmp/third')

    popen1 = ThreadizedPopen(['cat'], '/tmp/first', '/tmp/second')
    popen2 = ThreadizedPopen(['cat'], '/tmp/second', '/tmp/third')
    popen1.start()
    popen2.start()
    with open('/tmp/third') as fin:
        print fin.read()
    popen1.join()
    popen2.join()

if __name__ == '__main__':
    main()

我将您的代码放在名为fifo_issue.py的脚本中,并在终端中运行。脚本正如您所期望的那样空闲(忽略mkfifo: cannot create fifo):

$ python fifo_issue.py 
mkfifo: cannot create fifo ‘/tmp/first’: File exists
mkfifo: cannot create fifo ‘/tmp/second’: File exists
mkfifo: cannot create fifo ‘/tmp/third’: File exists

然后,在第二个终端,我输入了:

$ echo "I was echoed to /tmp/first!" > /tmp/first

回到第一个仍在运行空闲线程的终端:

$ python fifo_issue.py 
mkfifo: cannot create fifo ‘/tmp/first’: File exists
mkfifo: cannot create fifo ‘/tmp/second’: File exists
mkfifo: cannot create fifo ‘/tmp/third’: File exists
I was echoed to /tmp/first!

之后python正确退出