调用close()后,大文件没有立即刷新到磁盘?

Vyk*_*tor 14 python windows io large-files python-3.x

我用我的python脚本创建大文件(1GB实际上有8个).在我创建它们之后,我必须创建将使用这些文件的进程.

该脚本如下所示:

# This is more complex function, but it basically does this:
def use_file():
    subprocess.call(['C:\\use_file', 'C:\\foo.txt']);


f = open( 'C:\\foo.txt', 'wb')
for i in 10000:
    f.write( one_MB_chunk)
f.flush()
os.fsync( f.fileno())
f.close()

time.sleep(5) # With this line added it just works fine

t = threading.Thread( target=use_file)
t.start()
Run Code Online (Sandbox Code Playgroud)

但应用程序use_file行为foo.txt是空的.有一些奇怪的事情发生了:

  • 如果我C:\use_file C:\foo.txt在控制台中执行(脚本完成后),我得到正确的结果
  • 如果我use_file()在另一个python控制台中手动执行,我得到正确的结果
  • C:\foo.txtopen()被调用后立即在磁盘上可见,但0B在脚本结束之前一直保持大小
  • 如果我添加time.sleep(5)它只是按预期开始工作(或者更确切地说是必需的)

我已经发现:

  • os.fsync()但它似乎没有工作(从结果use_file作为是否C:\foo.txt是空的)
  • 使用buffering=(1<<20)(打开文件时)似乎也不起作用

我对这种行为越来越好奇.

问题:

  • python fork close()操作进入后台吗?这记录在哪里?
  • 如何解决这个问题?
  • 我错过了什么吗?
  • 添加之后sleep:是windows/python的bug吗?

注意:(对于另一方出现问题的情况)应用程序use_data使用:

handle = CreateFile("foo.txt", GENERIC_READ, FILE_SHARE_READ, NULL,
                               OPEN_EXISTING, 0, NULL);
size = GetFileSize(handle, NULL)
Run Code Online (Sandbox Code Playgroud)

然后size从中处理字节foo.txt.

Kat*_*iel 10

f.close()调用f.flush(),将数据发送到OS.这并不一定是数据写入磁盘,因为操作系统对其进行缓冲.正如您所做的那样,如果您想强制操作系统将其写入磁盘,您需要os.fsync().

您是否考虑过直接将数据传输到use_file


编辑:你说os.fsync()"不起作用".澄清,如果你这样做

f = open(...)
# write data to f
f.flush()
os.fsync(f.fileno())
f.close()

import pdb; pdb.set_trace()
Run Code Online (Sandbox Code Playgroud)

然后查看磁盘上的文件,它有数据吗?

  • 虽然flush()不一定立即将数据写入物理磁盘,但它应该立即对其他应用程序(来自缓存)可见. (8认同)

Emi*_*rke 6

编辑:使用特定于Python 3.x的信息进行更新

https://bugs.python.org/issue4944上有一个超级老错误报告讨论了一个可疑的类似问题.我做了一个小测试,显示了这个错误:https://gist.github.com/estyrke/c2f5d88156dcffadbf38

在上面的bug链接中从用户eryksun获得了精彩的解释之后,我现在明白为什么会发生这种情况,并且它本身并不是一个bug.在Windows上创建子进程时,默认情况下它会从父进程继承所有打开的文件句柄.因此,您所看到的实际上可能是共享冲突,因为您尝试在子进程中读取的文件是打开的,可以通过另一个子进程中的继承句柄进行写入.导致此事件的可能事件序列(使用上面Gist中的复制示例):

Thread 1 opens file 1 for writing
  Thread 2 opens file 2 for writing
  Thread 2 closes file 2
  Thread 2 launches child 2
  -> Inherits the file handle from file 1, still open with write access
Thread 1 closes file 1
Thread 1 launches child 1
-> Now it can't open file 1, because the handle is still open in child 2
Child 2 exits
-> Last handle to file 1 closed
Child 1 exits
Run Code Online (Sandbox Code Playgroud)

当我编译简单的C子程序并在我的机器上运行脚本时,它在大多数情况下使用Python 2.7.8在至少一个线程中失败.使用Python 3.2和3.3,没有重定向的测试脚本不会失败,因为close_fds参数的默认值subprocess.call现在True是在不使用重定向时.在这些版本中,使用重定向的其他测试脚本仍然失败.在Python 3.4中,两个测试都成功,因为PEP 446默认情况下使所有文件句柄都不可继承.

结论

从Python中的线程生成子进程意味着子进程继承所有打开的文件句柄,甚至从生成子进程的其他线程继承.至少对我来说,这不是特别直观.

可能的解决方案:

  • 升级到Python 3.4,默认情况下文件句柄是不可继承的.
  • 传递close_fds=Truesubprocess.call完全禁用继承(这是Python 3.x中的默认值).请注意,这可以防止重定向子进程的标准输入/输出/错误.
  • 确保在生成新进程之前关闭所有文件.
  • 用于在Windows上os.open打开带有该os.O_NOINHERIT标志的文件.
    • tempfile.mkstemp 也使用这个标志.
  • 请改用win32api.传递lpSecurityAttributes参数的NULL指针也会阻止继承描述符:

    from contextlib import contextmanager
    import win32file
    
    @contextmanager
    def winfile(filename):
        try:
            h = win32file.CreateFile(filename, win32file.GENERIC_WRITE, 0, None, win32file.CREATE_ALWAYS, 0, 0)
            yield h
        finally:
            win32file.CloseHandle(h)
    
    with winfile(tempfilename) as infile:
        win32file.WriteFile(infile, data)
    
    Run Code Online (Sandbox Code Playgroud)