首页 文章

Windows上的Python Popen具有多线程 - 无法删除stdout / stderr日志

提问于
浏览
6

在Windows上使用python 2.7.4(注意:WinXP - 下面的评论者建议在Win7上正常工作),我有一个脚本创建几个线程,每个线程通过Popen运行子进程,stdout / stderr重定向到文件和调用等待() . 每个Popen都有自己的stdout / stderr文件 . 在每个进程返回后,我有时必须删除文件(实际上将它们移动到别处) .

我发现在所有wait()调用返回之前我都无法删除stdout / stderr日志 . 在此之前,我得到“WindowsError:[错误32]该进程无法访问该文件,因为它正被另一个进程使用” . 看起来,只要至少有一个子进程打开,Popen就会以某种方式保留stderr文件,即使这些文件没有共享 .

测试代码重现如下 .

C:\ test1.py

import subprocess
import threading
import os

def retryDelete(p, idx):
    while True:
        try:
            os.unlink(p)
        except Exception, e:
            if "The process cannot access the file because it is being used by another process" not in e:
                raise e
        else:
            print "Deleted logs", idx
            return

class Test(threading.Thread):
    def __init__(self, idx):
        threading.Thread.__init__(self)
        self.idx = idx

    def run(self):
        print "Creating %d" % self.idx
        stdof = open("stdout%d.log" % self.idx, "w")
        stdef = open("stderr%d.log" % self.idx, "w")
        p = subprocess.Popen("c:\\Python27\\python.exe test2.py %d" % self.idx,
                             stdout=stdof, stderr = stdef)
        print "Waiting %d" % self.idx
        p.wait()
        print "Starting deleting logs %d" % self.idx
        stdof.close()
        stdef.close()
        retryDelete("stderr%d.log" % self.idx, self.idx)
        print "Done %d" % self.idx

threads = [Test(i) for i in range(0, 10)]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()

C:\ test2.py:

import time
import sys

print "Sleeping",sys.argv[1]
time.sleep(int(sys.argv[1]))
print "Exiting",sys.argv[1]

如果运行此命令,您将看到每个retryDelete()都会对文件访问错误进行旋转,直到所有子进程都完成为止 .

UPDATE :即使stdof和stdef文件描述符未传递给Popen构造函数,也会出现问题 . 但是,如果删除了Popen并且用time.sleep(self.idx)替换了wait(),则不会发生(即删除立即发生) . 由于Popen似乎对未传递给它的文件描述符有影响,我想知道这个问题是否与句柄继承有关 .

UPDATE :close_fds = True给出错误(重定向stdout / stderr时Windows不支持),并且在wait()调用之后用del p删除Popen对象对问题没有影响 .

UPDATE :使用sysinternals进程资源管理器查找具有文件句柄的进程 . 将测试减少到仅2个线程/儿童,并使第二个线程保持打开状态很长时间 . 句柄搜索显示唯一具有stderr0.log句柄的进程是父python进程,它有两个句柄打开 .

UPDATE :对于我目前的紧急使用情况,我仍然对这个问题的答案感兴趣 . 对我来说这感觉就像一个特定于WinXP的错误,但是它只是做错了什么 .

2 回答

  • 0

    这个问题很老了,这个BUG已在Python 3.4上修复 . 为了记录,这里有一个hacky技巧,我们一直用于解决python 2.7或python 3.3-上的问题

    这个函数是用纯python(没有外部API)制作的,只适用于Windows!

    ==>在启动子进程之前,请调用以下函数

    def _hack_windows_subprocess():
        """HACK: python 2.7 file descriptors.
        This magic hack fixes https://bugs.python.org/issue19575
        by adding HANDLE_FLAG_INHERIT to all already opened file descriptors.
        """
        # Extracted from https://github.com/secdev/scapy/issues/1136
        import stat
        from ctypes import windll, wintypes
        from msvcrt import get_osfhandle
    
        HANDLE_FLAG_INHERIT = 0x00000001
    
        for fd in range(100):
            try:
                s = os.fstat(fd)
            except:
                break
            if stat.S_ISREG(s.st_mode):
                handle = wintypes.HANDLE(get_osfhandle(fd))
                mask   = wintypes.DWORD(HANDLE_FLAG_INHERIT)
                flags  = wintypes.DWORD(0)
                windll.kernel32.SetHandleInformation(handle, mask, flags)
    

    此函数将处理已打开的最后100个文件描述符,并将其设置为“无继承模式”,这将修复该错误 . 如果需要,可以增加100号码 .

  • 1

    您可以尝试更新到Win7,我知道这是WinXP用户中的常见错误 .

相关问题