Capturing stdout/stderr separately and simultaneously from child process results in wrong total order (libc/unix) Capturing stdout/stderr separately and simultaneously from child process results in wrong total order (libc/unix) unix unix

Capturing stdout/stderr separately and simultaneously from child process results in wrong total order (libc/unix)


At each iteration I read exactly one line (or EOF) from STDOUT and exactly one line (or EOF) from STDERR.

This is the problem. This will only capture the correct order if that was exactly the order of output in the child process.

You need to capture the asynchronous nature of the beast: make your pipe endpoints nonblocking, select* on the pipes, and read whatever data is present, as soon as select returns. Then you'll capture the correct order of the output. Of course now you can't be reading "exactly one line": you'll have to read whatever data is available and no more, so that you won't block, and maintain a per-pipe buffer where you append new data, extract any lines that are present, shove the unprocessed output to the beginning, and repeat. You could also use a circular buffer to save a little bit of memcpy-ing, but that's probably not very important.

Since you're doing this in Rust, I presume there's already a good asynchronous reaction pattern that you could leverage (I'm spoiled with go, I guess, and project the hopes on the unsuspecting).

*Always prefer platform-specific higher-performance primitives like epoll on Linux, /dev/poll on Solaris, pollset &c. on AIX

Another possibility is to launch the target process with LD_PRELOAD, with a dedicated library that it takes over glibc's POSIX write, detects writes to the pipes, and encapsulates such writes (and only those) in a packet by prepending it with a header that has an (atomically updated) process-wide incrementing counter stored in it, as well as the size of the write. Such headers can be easily decoded on the other end of the pipe to reorder the writes with a higher chance of success.


I think it's not possible to strictly do what you want to do.

If you think about how it's done when running a command in an interactive shell, what happens is that both stdout and stderr point to the same file descriptor (the TTY), so the total ordering is correct by means of synchronization against the same file.

To illustrate, imagine what happens if the child process has 2 completely independent threads, one only writing to stderr, and to other only writing to stdout. The total ordering would depend on however the scheduler decided to schedule these threads, and if you wanted to capture that, you'd need to synchronize those threads against something.

And of course, something can write thousands of lines to stdout before writing anything to stderr.

There are 2 ways to relax your requirements into something workable:

  1. Have the user pass a flag waiving separate stdout and stderr streams in favor of a correct stdcombined, and then redirect both to a single file descriptor. You might need to change the buffering settings (like stdbuf does) before you execute the process.

  2. Assume that stdout and stderr are "reasonably interleaved", an assumption pointed out by @Nate Eldredge, in which case you can use @Unslander Monica's answer.