How can I get signal to quit while reading character device?

Dear Rustaceans,

I'm writing a Rust binary that reads from character device (i.e. read from a File indefinitely).
To handle other commands as well, I'm thinking to spawn a Thread and read from there.
But I'm stuck how to quit the thread gracefully.

Can I send and receive signal to quit?
File::read() is blocking by default and can't receive any signal.
Nonblocking read requires experimental API and using it requires endless loop in thread for nonblocking read and checking signal -- thread must be running all the time so doesn't look efficient.

What would be the recommended practice for this?
Can I just drop File outside of the thread? It feels bad even if it's possible..

I found an example with std::select!, but it seems to be gone. (i.e. no select! documentation in std)

What's the purpose of the graceful quit, and is it necessary to do some cleanup in the same thread that is blocked on a read? An option I would consider is to just terminate the app without joining the thread. "Ungraceful quit" if you will.

But assuming there is a good reason for a graceful quit, you might benefit from non-blocking I/O and scheduling wakeups when data is available to read. Use mio for that, or even tokio if you prefer a higher-level abstraction.

BTW, you probably heard about select! from tokio or futures. I don't think I've heard of any similar proposals for std, and I'm having trouble finding any RFCs or other sources where it might show up.

1 Like

Unfortunately this is inherently a complex issue, since to properly handle this you need file io cancellation, which is not supported by std, not really supported by futures (since completion based cancellation is inherently async and there's no async_drop), and not implemented in mio.

Since tokio fs is just a wrapper for std::fs (for complex reasons), its probably not a great idea to just leave it hanging (I expect the file will just never got closed, flushed out, etc. whatever else you might do with the handle waiting for a read to finish. See also this issue I forgot I commented on!).

Even the crappy answer of polling with a read with a timeout isn't supported by pretty much anything!

A far as I know your best option is to avoid a "File" type completely, if possible. Use Unix sockets, for example, or their tokio equivalent. If not, sacrificing a thread to be the "blocking on read()" is the least wrong simple option.

If you're willing to drop down to raw fds and OS wrapper crates like nix, your character device probably works in select(), so you should be able to select the fd and an event you set from the signal handler.

There's likely something much nicer and fancier and also far more internally ugly and complex if you're willing to wade into io_uring, and have the kernel support, though! Here's an example of what that looks like raw: io-uring — async Rust library // Lib.rs, but there's a few stabs at making it less awful out there. You probably want it to be an async runtime, but nothing looked like it was good enough that I'd want to use it yet (fair enough, this is fairly new stuff).

Thank you for the answer. I'll have a look on the tokio and/or mio.

Re: std::select!: A chatbot recommended it and I could only found following links. Perhaps very experimental APIs in earlier Rust?

Uh, good to know that there's no good support with it.

Actually I have looked forward the system call equivalent (select() / poll() / ...). If there's no good high-level wrapper, then I'll find ffi or something similar.