Nix crate's fork() call

Hi, I'm trying to learn more Rust by writing the examples in Beej's Guide to Network Programming from C to Rust. :blush:

I have this block code (TCP server accepting connections from a client):

    match listener.accept() {
        Ok((mut stream, addr)) => {
            println!("connection accepted: {:?}", addr);
            match fork() {
                Ok(ForkResult::Child) => {
                    stream.write(b"Hello world!")?;
                    stream.flush()?;
                    stream.shutdown(Shutdown::Both).expect("Child shutdown failed")
                },
                Ok(ForkResult::Parent {child: _, ..}) => {
                    println!("Parent...");
                    stream.shutdown(Shutdown::Both).expect("Parent shutdown failed")
                },
                Err(_) => {
                    println!("fork() failed")
                },
            }
        },
        Err(e) => println!("attempt from client: {:?} failed", e),
    }

I am using the nix crate to fork() a new process. I was expecting the stream in both the parent block and the child block to be different copies. But, it looks like they are the same. Running the code above, I get the following error when a client connects:

Parent...
Error: Os { code: 32, kind: BrokenPipe, message: "Broken pipe" }

But if I comment out the stream.shutdown() call in the parent block, then the client can connect and receive the message.

Is this behavior inherent to the Rust language? -OR- is it an implementation specific behavior and I should be asking the folks from nix for clarifications about this?

Shuts down the read, write, or both halves of this connection.

This function will cause all pending and future I/O on the specified portions to return immediately with an appropriate value (see the documentation of Shutdown ).

By forking a process you can't magically duplicate a TCP connection. A TCP connection is uniquely determined by source/destination ip and port. stream.shutdown() sends an RST packet to the system you are connected with closing the connection on both ends.

3 Likes

I see. So is it correct to say that in this context, both the parent and the child processes are sharing the same resource (TCP connection)?

Yes

To be honest fork-per-connection is a strategy which was common before we invented the thread. Linux and other OSes invented the thread because the process-per-connection model is too slow for internet scale servers, and just before the millennium we invented the async programming because the thread-per-connection model is too slow for web scale servers, AKA C10K problem.

I'd recommend not to use fork() for concurrency/parallelism, especially for Rust. It would not works well with modern multithreading tools, which is really common in the Rust ecosystem.

2 Likes