'Poll raw file descriptor for data

I would like to poll a raw file descriptor for data and process it as it gets written to. This needs to be done on a separate thread so that my main thread is not blocked. I would also need to be able to close both the file descriptor as well as the new separate thread without blocking my main thread.

Some options that I looked into were Mio (https://github.com/tokio-rs/mio) as well as Tokio (https://github.com/tokio-rs/tokio) but they both seem to be focused more on networking. I tried looking through the examples section but could not find one that satisfies my requirements.



Solution 1:[1]

It may not be exactly what you need, but you can indeed use mio to listen for events on raw file descriptors. Here's a short example using a Unix Socket (but you should be able to adjust it for your needs):

use std::os::unix::net::UnixListener;
use std::os::unix::prelude::AsRawFd;
use std::error::Error;
use std::io;
use mio::unix::SourceFd;
use mio::{Events, Interest, Poll, Token};

// A unique Token to help us distinguish events, see `mio` docs:
// https://tokio-rs.github.io/mio/doc/mio/struct.Token.html
const TOKEN_SOCKET: Token = Token(0);

fn main() -> Result<(), Box<dyn Error>> {
    // Setup mio event loop
    // NOTE: this currently requires the "os-ext" crate feature for `mio`
    let mut poll = Poll::new()?;
    let mut events = Events::with_capacity(128);

    // Setup our raw file descriptor (using a Unix Socket)
    let socket = UnixListener::bind("/tmp/server.sock")?;
    // This is needed to reads never block indefinitely and halt the loop
    socket.set_nonblocking(true)?;
    
    // Now, we register our socket as an event source for mio
    // We use the Interest::READABLE to tell mio to notify us when the raw
    // file descriptor is ready to be read
    poll.registry().register(&mut SourceFd(&socket.as_raw_fd()), TOKEN_SOCKET, Interest::READABLE)?;

    // Our event loop
    loop {
        let poll_timeout = None;
        poll.poll(&mut events, poll_timeout)?;

        for event in &events {
            match event.token() {
                TOKEN_SOCKET => {
                    // Here, mio detected that our socket is readable!
                    // we can most likely read events from it!
                    loop {
                        match socket.accept() {
                            // We have a client connection, now do something with it
                            Ok((conn, addr)) => {},
                            // There are no more waiting connections!
                            // This is why we set the socket to non-blocking. If we didn't
                            // then this would block forever when there are no more connections
                            Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
                            // Some other error occurred when accepting the connection
                            Err(e) => panic!("Socket error: {}", e),
                        }
                    }
                }
                _ => unimplemented!("other token responses")
            }
        }
    }
}

Of course, this only shows you how to use mio to notify you when a raw file descriptor can be read from. You can use the Interest::WRITEABLE to get notified for when it can be written to as well.

It also doesn't only apply for Unix Sockets, but any raw file descriptor. Hopefully this is enough of an example to get you started!

This needs to be done on a separate thread so that my main thread is not blocked. I would also need to be able to close both the file descriptor as well as the new separate thread without blocking my main thread.

In my example I did everything on the main thread, but it would be trivial for you to spawn a new thread (or use crossbeam to send events to an existing one) when you've received a notification from the mio event loop that your file descriptor most likely has some data waiting to be read. (Or even spawn a mio event loop on a new background thread whenever you need one!)

I believe you can register and deregister event sources for the mio Poll registry, which should also cover your use-case of needing to close (and perhaps re-open) the file descriptor without stopping the main event loop. Though you could also just as easily do this by spawning a new background thread with an entirely new event loop each time, if you only needed the event loop once per thread. (I'm unsure of your exact requirements, just guessing from your question.)


One last tip, if you need to trigger events from within your application, look into mio's Waker API.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1