9

Because Rust does not have have the built-in ability to read from a file in a non-blocking manner, I have to spawn a thread which reads the file /dev/input/fs0 in order to get joystick events. Suppose the joystick is unused (nothing to read), so the reading thread is blocked while reading from the file.

Is there a way for the main thread to force the blocking read of the reading thread to resume, so the reading thread may exit cleanly?

In other languages, I would simply close the file in the main thread. This would force the blocking read to resume. But I have not found a way to do so in Rust, because reading requires a mutable reference to the file.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Pentagolo
  • 121
  • 6
  • Is `mio` too heavy for this use? – WiSaGaN Mar 25 '16 at 01:52
  • @WiSaGaN MIO expressly does not handle async IO for files. A good read on the [background of async file IO](http://blog.libtorrent.org/2012/10/asynchronous-disk-io/). – Shepmaster Mar 25 '16 at 02:36
  • 1
    @Shepmaster OP does not need async IO as it seems. Non-blocking IO can be achieved by using `mio` with a `RawFd` `Evented`. – WiSaGaN Mar 25 '16 at 02:49
  • @WiSaGaN any idea if that will work on all primary platforms (Linux, Windows, OS X)? – Shepmaster Mar 25 '16 at 03:05
  • 3
    @Shepmaster no, it's just that OP is using `/dev/input/fs0`, so at least it is not Windows. :p – WiSaGaN Mar 25 '16 at 03:06

1 Answers1

2

The idea is to call File::read only when there is available data. If there is no available data, we check a flag to see if the main thread requested to stop. If not, wait and try again.

Here is an example using nonblock crate:

extern crate nonblock;

use std::fs::File;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

use nonblock::NonBlockingReader;

fn main() {
    let f = File::open("/dev/stdin").expect("open failed");
    let mut reader = NonBlockingReader::from_fd(f).expect("from_fd failed");

    let exit = Arc::new(Mutex::new(false));
    let texit = exit.clone();

    println!("start reading, type something and enter");

    thread::spawn(move || {
        let mut buf: Vec<u8> = Vec::new();
        while !*texit.lock().unwrap() {
            let s = reader.read_available(&mut buf).expect("io error");
            if s == 0 {
                if reader.is_eof() {
                    println!("eof");
                    break;
                }
            } else {
                println!("read {:?}", buf);
                buf.clear();
            }
            thread::sleep(Duration::from_millis(200));
        }
        println!("stop reading");
    });

    thread::sleep(Duration::from_secs(5));

    println!("closing file");
    *exit.lock().unwrap() = true;

    thread::sleep(Duration::from_secs(2));
    println!("\"stop reading\" was printed before the main exit!");
}

fn read_async<F>(file: File, fun: F) -> thread::JoinHandle<()>
    where F: Send + 'static + Fn(&Vec<u8>)
{
    let mut reader = NonBlockingReader::from_fd(file).expect("from_fd failed");
    let mut buf: Vec<u8> = Vec::new();
    thread::spawn(move || {
        loop {
            let s = reader.read_available(&mut buf).expect("io error");
            if s == 0 {
                if reader.is_eof() {
                    break;
                }
            } else {
                fun(&buf);
                buf.clear();
            }
            thread::sleep(Duration::from_millis(100));
        }
    })
}

Here is an example using poll binding of nix crate. The function poll waits (with timeout) for specific events:

extern crate nix;

use std::io::Read;
use std::os::unix::io::AsRawFd;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

use nix::poll;

fn main() {
    let mut f = std::fs::File::open("/dev/stdin").expect("open failed");
    let mut pfd = poll::PollFd {
        fd: f.as_raw_fd(),
        events: poll::POLLIN, // is there input data?
        revents: poll::EventFlags::empty(),
    };

    let exit = Arc::new(Mutex::new(false));
    let texit = exit.clone();

    println!("start reading, type something and enter");

    thread::spawn(move || {
        let timeout = 100; // millisecs
        let mut s = unsafe { std::slice::from_raw_parts_mut(&mut pfd, 1) };
        let mut buffer = [0u8; 10];
        loop {
            if poll::poll(&mut s, timeout).expect("poll failed") != 0 {
                let s = f.read(&mut buffer).expect("read failed");
                println!("read {:?}", &buffer[..s]);
            }
            if *texit.lock().unwrap() {
                break;
            }
        }
        println!("stop reading");
    });

    thread::sleep(Duration::from_secs(5));

    println!("closing file");
    *exit.lock().unwrap() = true;

    thread::sleep(Duration::from_secs(2));
    println!("\"stop reading\" was printed before the main exit!");

}
carols10cents
  • 6,943
  • 7
  • 39
  • 56
malbarbo
  • 10,717
  • 1
  • 42
  • 57