子进程调用的重定向输出会丢失吗?

时间:2016-01-06 00:21:46

标签: python c linux subprocess fsync

我有一些大致相似的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流(由函数签名调解)       声明的,用未写入的缓冲数据刷新,全部       关闭open C流,并通过调用tmp-创建所有文件       file()被删除.30)

     

- 最后,控制权返回给主机环境。如果状态是       零或EXIT_SUCCESS,一种实现定义的状态形式       成功终止返回。如果状态为EXIT_FAILURE,则为       实现定义的状态不成功终止的形式       退回。否则返回的状态是       实施-defined.31)

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

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

  

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

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

所以,我的实际问题是:

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

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

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

1 个答案:

答案 0 :(得分:1)

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

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

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

  

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

是肯定的。是

  

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

没有。在一般情况下,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修改后应返回write()指定的数据   该位置直到再次修改这些字节位置。