首页 > 解决方案 > 从子进程单独和同时捕获 stdout/stderr 会导致错误的总顺序(libc/unix)

问题描述

我正在编写一个库,它应该在子进程中执行程序,捕获输出,并以逐行(字符串向量)的方式使输出可用。STDOUT 有一个向量,STDERR 有一个向量,“STDCOMBINED”有一个向量,即所有输出都按照程序打印的顺序。子进程通过两个管道连接到父进程。一根管子用于 STDOUT,一根管子用于 STDERR。在父进程中,我从管道的读取端读取,在子进程中,我将dup2()STDOUT/STDERR 编辑到管道的写入端。

我的问题:我想捕获 STDOUT、STDERR和“STDCOMBINED”(=两者都按它们出现的顺序)。但是组合向量中的顺序与原始顺序不同。

我的方法:我迭代直到两个管道都显示 EOF 并且子进程退出。在每次迭代中,我从 STDOUT 中准确读取一行(或 EOF),从 STDERR 中准确读取一行(或 EOF)。到目前为止,这有效。但是当我在父进程中捕获这些行时,STDOUT 和 STDERR 的顺序与我在 shell 中执行程序并查看输出的顺序不同。

为什么会这样,我该如何解决?这可能吗?我知道在子进程中我可以将 STDOUT 和 STDERR 都重定向到单个管道,但我需要分别使用 STDOUT 和 STDERR 以及“STDCOMBINED”。


PS:我熟悉 libc/unix 系统调用,如dup2(),pipe()等。因此我没有发布代码。我的问题是关于一般方法,而不是特定语言的编码问题。我在 Rust 中针对原始 libc 绑定执行此操作。

PPS:我做了一个简单的测试程序,混合了 5 个标准输出和 5 个标准错误消息。这足以重现问题。

标签: cunixpipeposixlibc

解决方案


在每次迭代中,我从 STDOUT 中准确读取一行(或 EOF),从 STDERR 中准确读取一行(或 EOF)。

这就是问题。如果这正是子进程中的输出顺序,这只会捕获正确的顺序。

您需要捕捉野兽的异步性质:使您的管道端点无阻塞,select* 在管道上,并在select返回后立即读取存在的任何数据。然后,您将捕获输出的正确顺序。当然,现在您不能读取“恰好一行”:您将不得不读取任何可用的数据并且不再读取,这样您就不会阻塞,并维护一个每个管道缓冲区,您可以在其中添加新数据,提取任何存在的行,将未处理的输出推到开头,然后重复。您也可以使用循环缓冲区来保存一点点memcpy-ing,但这可能不是很重要。

由于您在 Rust 中执行此操作,我认为您已经可以利用一个很好的异步反应模式(我猜我被 go 宠坏了,并将希望寄托在毫无戒心的人身上)。

*总是喜欢特定平台的高性能原语,如epollLinux/dev/pollSolarispollset。在 AIX 上

另一种可能性是LD_PRELOAD使用一个专用库启动目标进程,该库接管 glibc 的 POSIX write,检测对管道的写入,并通过在数据包前面加上一个具有 (原子更新)存储在其中的进程范围递增计数器,以及写入的大小。这样的标头可以在管道的另一端轻松解码,以更高的成功机会重新排序写入。


推荐阅读