【发布时间】:2016-01-06 00:21:46
【问题描述】:
我有一些 Python 代码大致是这样的,使用一些你可能拥有或没有的库:
# Open it for writing
vcf_file = open(local_filename, "w")
# Download the region to the file.
subprocess.check_call(["bcftools", "view",
options.truth_url.format(sample_name), "-r",
"{}:{}-{}".format(ref_name, ref_start, ref_end)], stdout=vcf_file)
# Close parent process's copy of the file object
vcf_file.close()
# Upload it
file_id = job.fileStore.writeGlobalFile(local_filename)
基本上,我正在启动一个子进程,该子进程应该为我下载一些数据并将其打印到标准输出。我将该数据重定向到一个文件,然后,一旦子进程调用返回,我将关闭该文件的句柄,然后将该文件复制到其他地方。
我观察到,有时,我期望的数据的尾部没有进入副本。现在,bcftools 可能只是偶尔不写入该数据,但我担心我可能会做一些不安全的事情并在subprocess.check_call() 返回之后以某种方式访问该文件,但是在子进程写入标准输出的数据之前放到我可以看到的磁盘上。
看 C 标准(因为 bcftools 是用 C/C++ 实现的),看起来当程序正常退出时,所有打开的流(包括标准输出)都被刷新和关闭。参见[lib.support.start.term] 部分here,描述exit() 的行为,当main() 返回时隐式调用:
--接下来,所有打开的 C 流(由函数签名介导) 在 ) 中声明的未写入缓冲数据被刷新,所有 打开的 C 流被关闭,所有通过调用 tmp- 创建的文件 文件()被删除。30)
--最后,控制权返回宿主环境。如果状态是 零或 EXIT_SUCCESS,实现定义的状态形式 返回成功终止。如果状态为 EXIT_FAILURE,则 状态不成功终止的实现定义形式 被退回。否则返回的状态为 实现定义.31)
所以在子进程退出之前,它会关闭(从而刷新)标准输出。
但是,用于 Linux 的 manual page close(2) 指出,关闭文件描述符并不一定保证写入它的任何数据都已实际写入磁盘:
成功关闭并不能保证数据已经 成功保存到磁盘,因为内核延迟写入。它不是 当流是文件系统时,文件系统通常会刷新缓冲区 关闭。如果您需要确保数据是物理存储的, 使用 fsync(2)。 (此时将取决于磁盘硬件。)
因此,当进程退出时,它的标准输出流会被刷新,但如果该流实际上由指向磁盘上文件的文件描述符支持,则不能保证写入磁盘已完成.我怀疑这可能就是这里发生的事情。
所以,我的实际问题:
我对规格的解读是否正确?子进程能否在其重定向的标准输出在磁盘上可用之前在其父进程看来已终止?
是否有可能以某种方式等待子进程写入文件的所有数据实际上已被操作系统同步到磁盘?
我应该在父进程的文件对象副本上调用
flush()还是fsync()的某个Python 版本?是否可以强制子进程写入相同的文件描述符以提交到磁盘?
【问题讨论】:
-
“所有打开的带有未写入缓冲数据的 C 流都被刷新,所有打开的 C 流都被关闭”。那里不是有两个声明说流都被刷新和关闭了吗? Linux exit man page 有类似的措辞:“所有打开的 stdio(3) 流都被刷新和关闭。”
-
flush 和 close 都与调用 fsync() 不同,因此在进程退出后,操作系统可能会将数据保留在自己的缓冲区中一段时间,直到稍后才将其写入物理磁盘。虽然数据在 OS 缓冲区中并且尚未物理上在磁盘上,但它对其他进程可见吗?
标签: python c linux subprocess fsync