Announcing the tokio-io Crate
March 17, 2017
Today we're happy to announce a new crate and several new tools to work with in the Tokio stack. This represents the culmination of a number of parallel updates to various bits and pieces, they just happened to conveniently land all around the same time! In a nutshell the improvements are:
- A new tokio-io crate extracted from tokio-core, deprecating the
tokio_core::io
module. - Introduction of the bytes crate to tokio-io allowing abstraction over buffering and leveraging underlying functionality like vectored I/O.
- Addition of a new method,
close
, to theSink
trait to express graceful shutdown.
These changes improve the organization and abstractions of Tokio to address
several long-standing concerns and should provide a stable foundation for all
future development. At the same time, the changes are not breaking since the old
io
module is still available in deprecated form. You can start using all these
crates immediately via cargo update
and using the most recent 0.1.*
versions
of the crates!
Let's dive a bit more into each change in detail to see what's available now.
Adding a tokio-io
crate
The existing tokio_core::io
module gives a number of useful abstractions but
they're not specific to tokio-core itself, and the major purpose of the
tokio-io crate is to provide these core utilities without the implication of a
runtime. With tokio-io crates can depend on asynchronous I/O semantics without
tying themselves to a particular runtime, for example tokio-core. The
tokio-io crate is intended to be similar to the std::io
standard library
module in terms of serving a common abstraction for the asynchronous ecosystem.
The concepts and traits set forth in tokio-io are the foundation for all I/O
done in the Tokio stack.
The primary contents of tokio-io are the AsyncRead
and AsyncWrite
traits. These two traits are sort of a "split Io
trait" and were chosen to
demarcate types which implement Tokio-like read/write semantics (nonblocking and
notifying to a future's task). These traits then integrate with the bytes
crate to provide some convenient functions and retain old functionality like
split
.
With a clean slate we also took the chance to refresh the Codec
trait in the
tokio-core crate to Encoder
and Decoder
traits which operate over
types in the bytes crate (EasyBuf
is not present in tokio-io and it's
now deprecated in tokio-core). These types allows you to quickly move from a
stream of bytes to a Sink
and a Stream
ready to accept framed messages.
A great example of this is that with tokio-io we can use the new
length_delimited
module combined with tokio-serde-json to get up and
running with a JSON RPC server in no time as we'll see later in this post.
Overall with tokio-io we were also able to revisit several minor issues in the API designed. This in turns empowered us to close a slew of issues against tokio-core. We feel tokio-io is a great addition to the Tokio stack moving forward. Crates can choose to be abstract over tokio-io without pulling in runtimes such as tokio-core, if they'd like.
Integration with bytes
One longstanding wart with tokio-core is its EasyBuf
byte buffer type.
This type is basically what it says on the tin (an "easy" buffer) but is
unfortunately typically not what you want in high performance use cases. We've
long wanted to have a better abstraction (and a better concrete implementation)
here.
With tokio-io you'll find that the bytes crate on crates.io is much more
tightly integrated and provides the abstractions necessary for high-performance
and "easy" buffers simultaneously. The main contents of the bytes crate are
the Buf
and BufMut
traits. These two traits serve as the ability to
abstract over arbitrary byte buffers (both readable and writable) and are
integrated with read_buf
and write_buf
on all asynchronous I/O objects
now.
In addition to traits to abstract over many kinds of buffers the bytes crate
comes with two high-quality implementations of these traits, the Bytes
and
BytesMut
type (implementing the Buf
and BufMut
traits respectively).
In a nutshell these types represent reference-counted buffers which allow
zero-copy extraction of slices of data in an efficient fashion. To boot they
also support a wide array of common operations such as tiny buffers (inline
storage), single owners (can use a Vec
internally), shared owners with
disjoint views (BytesMut
), and shared owners with possibly overlapping views
(Bytes
).
Overall the bytes crate we hope is your one-stop-shop for byte buffer abstractions as well as high-quality implementations to get you running quickly. We're excited to see what's in store for the bytes crate!
Addition of Sink::close
The final major change that we've landed recently is the addition of a new
method on the Sink
trait, close
. Up to now there hasn't been a great
story around implementing "graceful shutdown" in a generic fashion because there
was no clean way to indicate to a sink that no more items will be pushed into
it. The new close
method is intended precisely for this purpose.
The close
method allows informing a sink that no more messages will be
pushed into it. Sinks can then take this opportunity to flush messages and
otherwise perform protocol-specific shutdown. For example a TLS connection at
that point would initiate a shutdown operation or a proxied connection might
issue a TCP-level shutdown. Typically this'll end up bottoming out to the new
AsyncWrite::shutdown
method.
Addition of codec::length_delimited
One large feature that is landing with tokio-io is the addition of the
length_delimited
module (inspired by Netty's
LengthFieldBasedFrameDecoder
). Many protocols delimit frames by using a
frame header that includes the length of the frame. As a simple example, take a
protocol that uses a frame header of a u32
to delimit the frame payload. Each
frame on the wire looks like this:
+----------+--------------------------------+
| len: u32 | frame payload |
+----------+--------------------------------+
Parsing this protocol can easily be handled with length_delimited::Framed
:
// Bind a server socket
let socket = TcpStream::connect(
&"127.0.0.1:17653".parse().unwrap(),
&handle);
socket.and_then(|socket| {
// Delimit frames using a length header
let transport = length_delimited::FramedWrite::new(socket);
})
In the above example, transport
will be a Sink + Stream
of buffer values,
where each buffer contains the frame payload. This makes encoding and decoding
the frame to a value fairly easy to do with something like serde. For example,
using tokio-serde-json, we can quickly implement a JSON based protocol where
each frame is length delimited and the frame payload is encoded using JSON:
// Bind a server socket
let socket = TcpStream::connect(
&"127.0.0.1:17653".parse().unwrap(),
&handle);
socket.and_then(|socket| {
// Delimit frames using a length header
let transport = length_delimited::FramedWrite::new(socket);
// Serialize frames with JSON
let serialized = WriteJson::new(transport);
// Send the value
serialized.send(json!({
"name": "John Doe",
"age": 43,
"phones": [
"+44 1234567",
"+44 2345678"
]
}))
})
The full example is here.
The length_delimited
module contains enough configuration settings to handle
parsing length delimited frames with more complex frame headers, like the
HTTP/2.0 protocol.
What's next?
All of these changes put together closes quite a large number of issues in the futures and tokio-core crates and we feel positions Tokio precisely where we'd like it for common I/O and buffering abstractions. As always we'd love to hear feedback on issue trackers and are more than willing to merge PRs if you find a problem! Otherwise we look forward to seeing all of these changes in practice!
With the foundations of tokio-core, tokio-io, tokio-service, and tokio-proto solidifying the Tokio team is looking forward to accommodating and implementing more ambitious protocols such as HTTP/2. We're working closely with @seanmonstar and Hyper to develop these foundational HTTP libraries as well. Finally we're looking to expand the middleware story in the near future with relation to both HTTP and generic tokio-service implementations. More on this coming soon!