【问题标题】:Redirected output from a subprocess call getting lost?子进程调用的重定向输出丢失?
【发布时间】: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)。 (此时将取决于磁盘硬件。)

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

所以,我的实际问题:

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

  2. 是否有可能以某种方式等待子进程写入文件的所有数据实际上已被操作系统同步到磁盘?

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

【问题讨论】:

  • “所有打开的带有未写入缓冲数据的 C 流都被刷新,所有打开的 C 流都被关闭”。那里不是有两个声明说流都被刷新和关闭了吗? Linux exit man page 有类似的措辞:“所有打开的 stdio(3) 流都被刷新和关闭。”
  • flush 和 close 都与调用 fsync() 不同,因此在进程退出后,操作系统可能会将数据保留在自己的缓冲区中一段时间​​,直到稍后才将其写入物理磁盘。虽然数据在 OS 缓冲区中并且尚未物理上在磁盘上,但它对其他进程可见吗?

标签: python c linux subprocess fsync


【解决方案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() 在这里帮不上忙。

问:为了清楚起见,我看到您断言数据确实应该由其他进程读取,因为相关的操作系统缓冲区在进程之间共享。但是你的断言的来源是什么?您可以在规范或 Linux 文档中指出保证这些缓冲区是共享的吗?

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

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

【讨论】:

  • 所以操作系统不保证当你关闭文件描述符时数据被写入磁盘,但它保证文件系统上的所有其他读取器都会看到你的写入?
  • @interfect: close(fd) 不相关。是的,成功write(fd, data) 后,无法保证数据会物理写入磁盘,但不会阻止您将其读回。
  • 其他进程读取它呢?保存尚未提交到磁盘的数据的缓冲区是全局的还是每个进程的?我担心的是文件系统可能只提供 read-your-own-writes 一致性,并且可能会任意重新排序不同进程的写入和读取。请参阅此处stackoverflow.com/a/19406462/402891 的问题和答案示例,当它收到文件已关闭的 inotify 消息时,其他进程无法读取数据。
  • @interfect:同样,您链接的答案与您的案例完全无关(它讨论了数据何时物理写入磁盘)。是的,if you write to the same file from different processes without synchronization then there is no definite order。我看不出它与您的情况有什么关系:只有孩子写入文件,在您的情况下孩子已经死后,只有父母从文件中读取——顺序是固定的——这是使用的顺序由孩子。
猜你喜欢
  • 1970-01-01
  • 2021-03-25
  • 1970-01-01
  • 2013-06-02
  • 2012-07-04
  • 1970-01-01
  • 1970-01-01
  • 2012-05-28
相关资源
最近更新 更多