Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I read non-blocking from stdin?

Tags:

rust

Is there a way to check whether data is available on stdin in Rust, or to do a read that returns immediately with the currently available data?

My goal is to be able to read the input produced for instance by cursor keys in a shell that is setup to return all read data immediately. For instance with an equivalent to: stty -echo -echok -icanon min 1 time 0.

I suppose one solution would be to use ncurses or similar libraries, but I would like to avoid any kind of large dependencies.

So far, I got only blocking input, which is not what I want:

let mut reader = stdin();
let mut s = String::new();

match reader.read_to_string(&mut s) {...} // this blocks :(
like image 821
smarr Avatar asked May 03 '15 10:05

smarr


People also ask

What is non-blocking IO in C?

Sometimes it's convenient to have I/O that doesn't block i.e we don't want a read call to block on one in case of input from the other. Solution for this is the given function: To specify non-blocking option: #include<fcntl. h> int fd; fcntl(fd, F_SETFL, O_NONBLOCK);

Is read non-blocking?

By default, read() waits until at least one byte is available to return to the application; this default is called “blocking” mode. Alternatively, individual file descriptors can be switched to “non-blocking” mode, which means that a read() on a slow file will return immediately, even if no bytes are available.

What is read blocking?

A blocking read will wait until there is data available (or a timeout, if any, expires), and then returns from the function call. A non-blocking read will (or at least should) always return immediately, but it might not return any data, if none is available at the moment.


2 Answers

Converting OP's comment into an answer:

You can spawn a thread and send data over a channel. You can then poll that channel in the main thread using try_recv.

use std::io;
use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::TryRecvError;
use std::{thread, time};

fn main() {
    let stdin_channel = spawn_stdin_channel();
    loop {
        match stdin_channel.try_recv() {
            Ok(key) => println!("Received: {}", key),
            Err(TryRecvError::Empty) => println!("Channel empty"),
            Err(TryRecvError::Disconnected) => panic!("Channel disconnected"),
        }
        sleep(1000);
    }
}

fn spawn_stdin_channel() -> Receiver<String> {
    let (tx, rx) = mpsc::channel::<String>();
    thread::spawn(move || loop {
        let mut buffer = String::new();
        io::stdin().read_line(&mut buffer).unwrap();
        tx.send(buffer).unwrap();
    });
    rx
}

fn sleep(millis: u64) {
    let duration = time::Duration::from_millis(millis);
    thread::sleep(duration);
}
like image 81
masonforest Avatar answered Oct 27 '22 01:10

masonforest


Most operating systems default to work with the standard input and output in a blocking way. No wonder then that the Rust library follows in stead.

To read from a blocking stream in a non-blocking way you might create a separate thread, so that the extra thread blocks instead of the main one. Checking whether a blocking file descriptor produced some input is similar: spawn a thread, make it read the data, check whether it produced any data so far.

Here's a piece of code that I use with a similar goal of processing a pipe output interactively and that can hopefully serve as an example. It sends the data over a channel, which supports the try_recv method - allowing you to check whether the data is available or not.

Someone has told me that mio might be used to read from a pipe in a non-blocking way, so you might want to check it out too. I suspect that passing the stdin file descriptor (0) to PipeReader::from_fd should just work.

like image 33
ArtemGr Avatar answered Oct 27 '22 01:10

ArtemGr