Help with writing low level future using `mio`

Hi. I want to write custom low-level Future using mio as abstraction over epoll(2). My basic understanding is that I should open file descriptor with NONBLOCK option, and then in Future::poll issue a syscall. If it succeeds then I return Poll::Ready, and if it fails with EWOULDBLOCK I have to register file descriptor to mio::Poll with proper interest, store waker provided by std::task::Context and once I get woken up by mio, then I should call wake on stored waker.

I know that tokio uses mio as its reactor and that tokio's runtime runs a separate thread that manages mio::Poll instance. I don't want to manage separate mio::Poll instance. I want to hook into reactor already managed by tokio. However after reading documentation I don't see how it can be done. I know that some libraries are tied to executor, because they rely on executor's reactor, so there must be some way to connect to it, mustn't it?

Can you please explain how to do it? Show some 3rd party (not tokio project's) library that uses mio as reactor for waiting futures? And if my reasoning is flawed, can you please explain where I make mistake?

1 Like

mio is mainly used as a building block for reactors, so... unless you are writing your own reactor, mio probably isn't the right tool for your async library (or application).

low level futures and reactors are tied together. you can view the mio::Poll as a "bare" reactor and write your futures on top of it, but then your futures can only be used with such bare reactor. this also means, although some adapters (e.g. async-compat) allow you to mix low level futures from different runtimes, you just end up running multiple reactors simultaneously.

the thing is, reactors usually need to manage more states than a mio::Poll even queue, e.g. timers are usually implemented without using OS syscall, completely in user space, as part of the reactor, thus most reactors need a daemon thread to manage the timer queue too.

if the reactor doesn't expose the event queue in its API, it's impossible to tap into an existing reactor and add your custom io types. as far as I know, tokio does not allow you to do this.

smol, on the other hand, does provide an adapter API, so you can add custom socket-like types to its reactor. the adapter is simply named Async<T>.

however, this API is very limited, you don't have much control over it. or rather, you don't need to do much for your type to be compatible with it: you just implement AsFd (or AsSocket for windows) for your custom type MySocket, then Async<MySocket> automatically implements AsyncRead and AsyncWrite.

note, smol's reactor doesn't use mio, but its own cross platform event queue crate named polling.

1 Like

Thank you @nerditation for your explanation. It helped me to steer my search in correct direction.

After more digging I actually found what I have been looking for. tokio provides tokio::io::unix::AsyncFd, which allows to register any epoll compatible file descriptor to tokio's reactor. From AsyncFd's docs:

Associates an IO object backed by a Unix file descriptor with the tokio reactor, allowing for readiness to be polled. The file descriptor must be of a type that can be used with the OS polling facilities (ie, poll, epoll, kqueue, etc), such as a network socket or pipe, and the file descriptor must have the nonblocking mode set to true.

Creating an AsyncFd registers the file descriptor with the current tokio Reactor, allowing you to directly await the file descriptor being readable or writable. Once registered, the file descriptor remains registered until the AsyncFd is dropped.

1 Like

interesting, I didn't know AsyncFd, it seems to be serving similar purpose as smol's Async<T> adapter.

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.