【问题标题】:Linux FIFO not returning EOF when I expect it to当我期望它时,Linux FIFO 没有返回 EOF
【发布时间】:2016-03-11 14:53:43
【问题描述】:

让我们考虑以下 Python 代码,由 cpython 在 Linux 系统上执行(警告:它将尝试创建或覆盖 /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。根据strace,这似乎是跟踪(特别是cat 进程已死,因此其所有文件描述符都已关闭;其管理线程也已关闭其描述符,我可以在strace 中看到它输出)。

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

【问题讨论】:

    标签: python linux pipe


    【解决方案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正确退出

    【讨论】:

    • 谢谢,它有效。我真的不明白为什么我以前的代码不起作用,所以只有在合理的时间内没有人能够更深入地了解实际发生的事情时,我才会接受答案。
    • @giomasce 是的,这样做。我只是为人们提供完整的工作示例,如果他们有兴趣,可以复制/粘贴——我没有添加太多信息。我想我应该评论一下。无论如何,我们需要一个 Unix 忍者或一个非常有耐心的文档阅读器来详细说明幕后发生的事情。
    猜你喜欢
    • 2014-04-18
    • 1970-01-01
    • 2023-02-09
    • 2021-05-30
    • 1970-01-01
    • 1970-01-01
    • 2017-07-24
    • 2021-12-11
    • 2018-05-11
    相关资源
    最近更新 更多