[][src]Crate io_mux

A Mux provides a single receive end and multiple send ends. Data sent to any of the send ends comes out the receive end, in order, tagged by the sender.

Each send end works as a file descriptor. For instance, with io-mux you can collect stdout and stderr from a process, and highlight any error output from stderr, while preserving the relative order of data across both stdout and stderr.

Example

let mut mux = io_mux::Mux::new()?;

let mut child = std::process::Command::new("sh")
    .arg("-c")
    .arg("echo out1 && echo err1 1>&2 && echo out2")
    .stdout(mux.make_untagged_sender()?)
    .stderr(mux.make_tagged_sender("e")?)
    .spawn()?;

let mut done_sender = mux.make_tagged_sender("d")?;
std::thread::spawn(move || match child.wait() {
    Ok(status) if status.success() => {
        let _ = write!(done_sender, "Done\n");
    }
    Ok(status) => {
        let _ = write!(done_sender, "Child process failed\n");
    }
    Err(e) => {
        let _ = write!(done_sender, "Error: {:?}\n", e);
    }
});

loop {
    let tagged_data = mux.read()?;
    if let Some(tag) = tagged_data.tag {
        print!("{}: ", tag);
        if tag == "d" {
            break;
        }
    }
    std::io::stdout().write_all(tagged_data.data)?;
}

Internals

Internally, Mux creates a UNIX datagram socket for the receive end, and a separate UNIX datagram socket for each sender. Datagram sockets support recvfrom, which provides the address of the sender, so Mux::read can use the sender address as the tag for the packet received.

However, datagram sockets require reading an entire datagram with each recvfrom call, so Mux::read needs to find out the size of the next datagram before calling recvfrom. Linux supports directly asking for the next packet size using recv with MSG_PEEK | MSG_TRUNC. On other UNIX systems, we have to repeatedly call recv with MSG_PEEK and an increasingly large buffer, until we receive the entire packet, then make one more call without MSG_PEEK to tell the OS to discard it.

Mux creates UNIX sockets within a temporary directory, removed when dropping the Mux.

Note that Mux::read cannot provide any indication of end-of-file. When using Mux, you will need to have some other indication that no further output will arrive, such as the exit of the child process producing output.

Structs

Mux

A Mux provides a single receive end and multiple send ends. Data sent to any of the send ends comes out the receive end, in order, tagged by the sender.

MuxSender

A send end of a Mux. You can convert a MuxSender to a std::process::Stdio for use with a child process, obtain the underlying file descriptor using IntoRawFd, or send data using std::io::Write.

TaggedData

Data received through a mux, along with the tag if any.