首页 文章

子进程调用的重定向输出是否丢失?

提问于
浏览
1

我有一些大致相似的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)

基本上,我正在启动一个子进程,它应该为我下载一些数据并将其打印到标准输出 . 我正在将数据重定向到一个文件,然后,一旦子进程调用返回,我就关闭文件的句柄,然后将文件复制到其他地方 .

I'm observing that, sometimes, the tail end of the data I'm expecting isn't making it into the copy. 现在,有可能 bcftools 偶尔不会写这些数据,但是我担心我可能会做一些不安全的事情,并且在 subprocess.check_call() 返回后以某种方式访问该文件,但在子进程写入标准之前的数据之前输出使它进入我可以看到的磁盘 .

看看C标准(因为bcftools是用C / C实现的),看起来当程序正常退出时,所有打开的流(包括标准输出)都会被刷新和关闭 . 请参阅 [lib.support.start.term] section here,描述 exit() 的行为,当 main() 返回时会隐式调用:

  • 接下来,刷新所有打开的C流(由声明的函数签名调解)和未写入的缓冲数据,刷新所有打开的C流,并通过调用tmp-file()创建的所有文件都被删除.30) - - 最后,控制权返回给主机环境 . 如果status为零或EXIT_SUCCESS,则返回状态成功终止的实现定义形式 . 如果status为EXIT_FAILURE,则返回状态为不成功终止的实现定义形式 . 否则返回的状态是实现定义的.31)

因此,在子进程退出之前,它会关闭(并因此刷新)标准输出 .

但是,manual page for Linux close(2) 注意到关闭文件描述符并不一定能保证写入它的任何数据实际上已经使它成为磁盘:

成功关闭并不能保证数据已成功保存到磁盘,因为内核会延迟写入 . 当流关闭时,文件系统刷新缓冲区并不常见 . 如果需要确保数据是物理存储的,请使用fsync(2) . (这取决于此时的磁盘硬件 . )

因此,似乎当进程退出时,其标准输出流被刷新,但如果该流实际上由指向磁盘上文件的文件描述符支持,则不能保证写入磁盘已完成 . 我怀疑这可能是这里发生的事情 .

So, my actual questions:

  • 我对规格的解读是否正确?子进程在其重定向标准输出可用于磁盘之前是否可以在其父进程中终止?

  • 是否有可能等到子进程写入文件的所有数据实际上已被OS同步到磁盘?

  • 我应该在父进程的文件对象副本上调用 flush() 或某些Python版本的 fsync() 吗?该强制是否可以通过子进程将相同的文件描述符写入磁盘?

1 回答

  • 1

    是的,可能需要几分钟才能将数据写入磁盘(物理上) . 但是你可以在很久之前阅读它 .

    除非你担心电源故障或内核恐慌;数据是否在磁盘上并不重要 . 内核是否认为数据是写入的重要部分 .

    check_call() 返回后立即从文件中读取是安全的 . 如果你没有看到所有的数据;它可能表示 bcftools 中的错误或 writeGlobalFile() 未上传文件中的所有数据 . 您可以尝试通过禁用 bsftools '标准输出(provide a pseudo-tty, use unbuffer command-line utility, etc)的块缓冲模式来解决前者问题 .

    问:我对规格的解读是否正确?子进程在其重定向标准输出可用于磁盘之前是否可以在其父进程中终止?

    是 . 是 .

    问:是否有可能等到子进程写入文件的所有数据实际上已被OS同步到磁盘?

    没有 . fsync() 在一般情况下是不够的 . 可能,你无论如何都不需要它(读取数据是一个不同的问题,从确保它写入磁盘) .

    问:我应该在父进程的文件对象副本上调用flush()还是某些Python版本的fsync()?该强制是否可以通过子进程将相同的文件描述符写入磁盘?

    这将毫无意义 . .flush() 刷新父进程内部的缓冲区(可以使用 open(filename, 'wb', 0) 避免在父母中创建不必要的缓冲区 .

    fsync()适用于文件描述符(子文件具有自己的文件描述符) . 我不知道 - 如果你观察到数据丢失(没有崩溃); fsync() 在这里没有帮助 .

    问:为了清楚起见,我看到你断言数据确实应该被其他进程读取,因为相关的OS缓冲区在进程之间共享 . 但是这个断言的来源是什么?在规范或Linux文档中是否有一个地方可以指出,以保证共享这些缓冲区?

    寻找"After a write() to a regular file has successfully returned"

    来自该写入修改的文件中每个字节位置的任何成功read()都将返回该位置的write()指定的数据,直到再次修改这些字节位置 .

相关问题