Signaling threads blocking on read to stop

I am writing a simple multithreaded project - a server that broadcasts any received data to all clients, and a corresponding client. The client has two threads - one for receiving user input from Stdin::read and passing it to TcpStream::write, one for receiving data from the server via TcpStream::read.

use std::net::{self, TcpStream};
use std::io::{self, Read, Write};
use std::thread;

fn main() {
    let mut stream = TcpStream::connect("127.0.0.1:2137").unwrap();

    let user_input_thread = {
        let mut stream = stream.try_clone().unwrap();
        thread::spawn(move || {
            let mut bytes = [0; 128];
            loop {
                let byte_count = io::stdin().read(&mut bytes).unwrap();
                if byte_count == 0 {
                    break;
                }
                let data = &bytes[0..byte_count];
                stream.write(data).unwrap();
            }
        })
    };

    let receiver_thread = thread::spawn(move || {
        let mut bytes = [0; 128];
        loop {
            let byte_count = stream.read(&mut bytes).unwrap();
            if byte_count == 0 {
                break;
            }
            let data = &bytes[0..byte_count];
            println!("{:02x?}", data);
        }
    });

    user_input_thread.join().unwrap();
    receiver_thread.join().unwrap();
}

The problem is, when the TcpStream is disconnected (for example, simply because the server is no longer reachable), there's no way for the user input thread to know when to exit. I could send it a message via a mpsc::Sender or set some flag in a Mutex, but it will still block on Stdin::read until the user inputs some data, so it can read the message/flag and return.

It's the same other way around - if the user presses Ctrl-D or otherwise signals the end-of-file, I'd want to signal the receiver thread to stop. I found one solution here, but I'm not sure if it's a good idea - use TcpStream::shutdown to shutdown the network stream, which will make TcpStream::read read 0 bytes and break out of the loop.

To make this work with this, I could just never join the user input thread and exit the program when the receiver thread finishes, but it doesn't sound like a good idea. I don't know, maybe it actually is.

I'd imagine this would be quite a common problem when writing multithreaded software. For example, reading from a character device like a keyboard is blocking, and you might want to end the program before the keyboard thread gets any new input. Yet, I'm unable to find much information about that, including sources about C and C++, since the same issue applies to them, but I might just be bad at searching. What are the best practices when dealing with this?

Basically you need non-blocking sockets to do this.

This is fine. I recommend that you just do that.

I read some resources on that, and found that libc offers poll and eventfd, as well as SOCK_NONBLOCK for socket. So if I were to implement that using a non-blocking socket, I'd have an eventfd as well as a socket with SOCK_NONBLOCK, and I would poll both of them. I could also pass stdin (FD 0) to poll for the user input thread. I assume Windows offers APIs to do the same thing. Is my understanding of this correct?

Yeah, it's probably a bit overkill to implement all that just for this...

If I wanted to achieve this result, could it be a good idea to use async instead of OS threads...? I guess I could just select between input and receiving from a signaling channel then, since futures are cancellable.

You could do it with async, but I would not use async if this is the only reason you have for using it.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.