6

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

我发现在所有 wait() 调用返回之前,我无法删除 stdout/stderr 日志。在此之前,我收到“WindowsError: [Error 32] The process cannot access the file because it is being used by another process”。只要至少有一个子进程打开,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() 都在文件访问错误上旋转,直到所有子进程都完成为止。

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

更新: close_fds=True 给出一个错误(在重定向标准输出/标准错误时在 Windows 上不支持),并且在 wait() 调用之后使用 del p 删除 Popen 对象对问题没有任何影响。

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

更新:对于我当前的紧急使用,我找到了一种解决方法,即创建一个单独的脚本,该脚本将命令行和 stderr/stdout 日志文件作为参数并运行重定向的子进程。然后父级只需使用 os.system() 执行此帮助程序脚本。日志文件随后被成功释放并被删除。但是,我仍然对这个问题的答案很感兴趣。对我来说,这感觉像是一个特定于 WinXP 的错误,但我仍然有可能只是做错了什么。

4

2 回答 2

1

这个问题很老了,这个BUG已经在Python 3.4+上修复了。作为记录,这是我们一直用来解决 python 2.7 或 python 3.3 上的问题的一个 hacky 技巧-

这个函数是用纯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 的数量。

于 2018-02-07T18:02:19.080 回答
0

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

于 2014-05-21T07:31:46.453 回答