Announcing Mio 0.7-alpha.1

December 17th, 2019

We are excited to announce Thomas de Zeeuw as the new lead of Mio. Mio is the low level I/O portability abstraction that backs Tokio and other Rust projects. Thomas has been behind most of the Mio 0.7 effort and will be continuing to lead the crate to 1.0. He has written the rest of the announcement.


Mio 0.7 is the work of various contributors over the course of roughly half a year. Compared to Mio version 0.6, version 0.7 reduces the size of the provided API in an attempt to simplify the implementation and usage. The API version 0.7 will be close to the proposed API for a future version 1.0. The scope of the crate was reduced to providing a cross-platform event notification mechanism and commonly used types such as cross-thread poll waking and non-blocking networking I/O primitives.

Major changes

Since this is a large release only some highlights are described here, all changes can be found in the change log. Overall a large number of API changes have been made to reduce the complexity of the implementation and remove overhead where possible.

Wrapping native OS types

In version 0.6, Mio defined its own Event structure to which the native OS types were converted. This meant that after polling the native types (i.e. kevent or epoll_event structures) would be converted into Mio's Event type. In version 0.7, the Event was changed to simply be a type wrapper around kevent or epoll_event, which provides convenience methods to get the event readiness indicators (such as readable or writable). Similar changes have been made throughout the crate. For example Poll is now just a file descriptor on Unix.

The way events are handled using the Events struct has also changed. In version 0.6.10, index access was already deprecated and replaced with an iteration API. The index access was completely removed in version 0.7, and the iteration API was changed to return a reference to Event rather than making a copy. All methods on Event only need a reference, so this shouldn't be a problem.

Removal of the user space queue & deprecated types

Mio version 0.6 had a user space queue, available through the SetReadiness and Registration types, but this has been removed in version 0.7 as it was deemed to be outside of the scope for Mio. Users who need a user space queue can use one of the queue types found in the crossbeam crate.

One use case for the user space queue was to wake a polling thread (i.e. calling Poll::poll) from another thread. To support this use case, a new Waker type has been introduced. Calling Waker::wake will wake the associated Poll.

Registering & I/O resource usages changes

By far the biggest user facing change of version 0.7 is the way Mio registers I/O sources. In version 0.6 resources were registered using Token, Ready, and PollOpt arguments. For example the following code would register socket with readable interests and edge triggers.

poll.register(&socket, Token(0), Ready::readable(), PollOpt::edge())?;

As mentioned above the Event type was changed to be a wrapper around the native OS type. In turn this removed the Ready type in favour of having methods on Event to check for the readiness indicators and getting the Token. The Ready type, as used in registration, was changed to Interest to better reflect its usage. The API of Interest was also changed to take advantage of the (somewhat) new associated constants, ensuring that is no longer possible to register an event source with empty interests.

In version 0.7 Mio registers all sources with edge triggers, removing the need for PollOpt. The use of edge triggers is explained below.

The trait that defined how to register an event source in version 0.6 was called Evented. This was changed to Source and is now referred to as event::Source, as the type lives inside the event module. event::Source has the same three methods as Evented: register, reregister, and deregister but are changed as described above.

Finally the registration functions have moved from Poll to a new Registry type which can be try_cloned and used to register sources from different threads. All together that means registering the same socket with readable interests now looks like this:

poll.registry().register(&socket, Token(0), Interest::READABLE)?;

Moving to edge triggers

As already mentioned Mio now registers all I/O sources with edge triggers which means that users of one-shot and level trigger need to change how they respond to events.

A problem is that certain uses of level and one-shot triggers show differences between OSes that Mio can't cancel without unacceptable overhead. We want Mio to present a nice cross-platform experience, so we decided to make all I/O sources edge-triggered. This way the behavior is identical on all platforms.

Users that previously used one-shot triggers should now deregister the I/O source after receiving an event.

Users of level triggers basically need to put a loop around all I/O operations. When using edge triggers the user is required to perform I/O after receiving an event. For example, in response to a read event, the user must read from the I/O source until it would block (it returns an io::Error of kind io::ErrorKind::WouldBlock). Only once an operation returns a WouldBlock error will Mio report any more events for that Interest on that I/O source. Below is an example of how to read from a TcpStream using edge triggers.

let stream: TcpStream = ...;

// When we polled we received a readable event for our `TcpStream`.

let mut buf = [0; 4096];
// With edge triggers we need to read all data available on the socket.
loop {
    let bytes_read = match stream.read(&mut buf) {
        // Read successful, proceed like normal.
        Ok(bytes_read) => bytes_read,
        // No more data to read at the moment. We will receive another event
        // once more data is available to read.
        Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => break,
        // Got interrupted, try again.
        Err(ref err) if err.kind() == io::ErrorKind::Interrupted => continue,
        // Hit an actual error.
        Err(err) => return Err(err),
    };

    process_byte(&buf[0..bytes_read]);
}

Note: this needs to happen for all I/O operations on registered event sources so be careful when using automated tools to convert to the new register API.

Addition of Unix socket API

New in the net module are the UnixListener, UnixStream and UnixDatagram types that support Unix sockets with an API similar to that found in the standard library.

These APIs are currently only support on Unix based OSes.

Removal of deprecated API

In Mio version 0.6 various types, such as the old event loop related and channel types, were deprecated. In version 0.7 all deprecated type were removed.

Removed support for OSes

Support for the following OSes has been dropped:

  • Linux below version 2.6.27 (and glibc 2.9), we are using newer APIs not present on older Linux versions, specifically eventfd(2), SOCK_NONBLOCK and SOCK_CLOEXEC options in socket(2) and epoll_create1(2).
  • Fuchsia: didn't have any CI coverage and we don't have enough maintainers to properly support it.
  • Bitrig: development on the OS seems to have stopped and rustc no longer supports it.

Increased minimum supported Rust version

As of writing this the minimum supported Rust version is 1.36. For version 1.0 we aim to target Rust version 1.39, as this is the version in which async become stable (a major feature which a lot of our dependent crates are using). So keep those compilers up to date!

Maintainer changes

Apart from code, we have also made some management changes. The repository has moved to the Tokio organization and multiple new people have joined the team.

—Thomas de Zeeuw