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_clone
d 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
andSOCK_CLOEXEC
options insocket(2)
andepoll_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.