iomux allows multiplexing of file descriptors using any Go supported unix domain network. It makes it possible to distinctly capture exec.Cmd stdout/stderr keeping the original output order:
mux := &iomux.Mux[OutputType]{}
defer mux.Close()
cmd := exec.Command("sh", "-c", "echo out1 && echo err1 1>&2 && echo out2")
stdout, _ := mux.Tag(StdOut) // errors ignored for brevity
stderr, _ := mux.Tag(StdErr)
cmd.Stdout = stdout
cmd.Stderr = stderr
taggedData, _ := mux.ReadWhile(func() error {
return cmd.Run()
})
for _, td := range taggedData {
var w io.Writer
switch td.Tag {
case StdOut:
w = os.Stdout
case StdErr:
w = os.Stderr
}
binary.Write(w, binary.BigEndian, td.Data)
}
When using unixgram networking, ordering is guaranteed (see Limitations below). More can be found in the examples directory.
This module was inspired by Josh Triplett's Rust crate https://github.com/joshtriplett/io-mux/.
On all platforms except macOS the network defaults to unixgram. On Linux, unixgram behaves like a pipe and will behave exactly as you'd expect, and always see messages in order. On other UNIXes, there is a possibility of different behaviour when buffers are full, but it's unlikely a reader will be outpaced.
Using unixgram on macOS when you cannot control the write size of the sender, is impossible due a message size limit of 2048 bytes:
write /dev/stdout: message too long
So on macOs, the default network is unix. It is connection oriented, so it doesn't come with the ordering guarantees of unixgram. It's possible to see see writes out of order, but on a MacBook Pro M1 0.1ms is the threshold for writes being read out of order, so for real world use cases it's unlikely to be a problem.
These limitations do not affect the read order of an individual connection, so output for an individual tag is always consistent. If you prefer a different network type, the default can be overridden using the convenience constructors NewMuxUnix, NewMuxUnixGram and NewMuxUnixPacket.