Why can't I communicate with a forked child process using Tokio UnixStream?

811 Views Asked by At

I'm trying to get a parent process and a child process to communicate with each other using a tokio::net::UnixStream. For some reason the child is unable to read whatever the parent writes to the socket, and presumably the other way around.

The function I have is similar to the following:

pub async fn run() -> Result<(), Error> {
    let mut socks = UnixStream::pair()?;

    match fork() {
        Ok(ForkResult::Parent { .. }) => {
            socks.0.write_u32(31337).await?;
            Ok(())
        }

        Ok(ForkResult::Child) => {
            eprintln!("Reading from master");
            let msg = socks.1.read_u32().await?;
            eprintln!("Read from master {}", msg);
            Ok(())
        }

        Err(_) => Err(Error),
    }
}

The socket doesn't get closed, otherwise I'd get an immediate error trying to read from socks.1. If I move the read into the parent process it works as expected. The first line "Reading from master" gets printed, but the second line never gets called.

I cannot change the communication paradigm, since I'll be using execve to start another binary that expects to be talking to a socketpair.

Any idea what I'm doing wrong here? Is it something to do with the async/await?

1

There are 1 best solutions below

0
On

When you call the fork() system call:

The child process is created with a single thread—the one that called fork().

The default executor in tokio is a thread pool executor. The child process will only get one of the threads in the pool, so it won't work properly.

I found I was able to make your program work by setting the thread pool to contain only a single thread, like this:

use tokio::prelude::*;
use tokio::net::UnixStream;
use nix::unistd::{fork, ForkResult};
use nix::sys::wait;
use std::io::Error;
use std::io::ErrorKind;
use wait::wait;

// Limit to 1 thread
#[tokio::main(core_threads = 1)]
async fn main() -> Result<(), Error> {
    let mut socks = UnixStream::pair()?;

    match fork() {
        Ok(ForkResult::Parent { .. }) => {
            eprintln!("Writing!");
            socks.0.write_u32(31337).await?;
            eprintln!("Written!");
            wait().unwrap();
            Ok(())
        }

        Ok(ForkResult::Child) => {
            eprintln!("Reading from master");
            let msg = socks.1.read_u32().await?;
            eprintln!("Read from master {}", msg);
            Ok(())
        }

        Err(_) => Err(Error::new(ErrorKind::Other, "oh no!")),
    }
}

Another change I had to make was to force the parent to wait for the child to complete, by calling wait() - also something you probably do not want to be doing in a real async program.

Most of the advice I have read that if you need to fork from a threaded program, either do it before creating any threads, or call exec_ve() in the child immediately after forking (which is what you plan to do anyway).