Announcing Tokio 0.3 and the path to 1.0
October 15, 2020
The Tokio team is excited to announce the release of Tokio 0.3. This release functions as a Tokio 1.0 beta. API rough edges have been fixed. This release is an opportunity to validate the changes before stabilizing them as part of the 1.0 release. As most of these issues were small, we expect that upgrading from 0.2 to 0.3 will be easy.
Plan for 1.0
The Tokio team plans to release Tokio 1.0 by the end of December 2020. This date is fast approaching. We ask that you, the Tokio community, try out Tokio 0.3 and give us feedback through GitHub Issues or our Discord channel. Once we release Tokio 1.0, we will commit to the following stability guarantees:
- A minimum of 5 years of maintenance.
- A minimum of 3 years before a hypothetical 2.0 release.
What’s new?
The main changes in Tokio 0.3 are:
- Changes to IO traits.
- New runtime builder.
- The I/O driver is overhauled
- The API is future proofed.
You can find the full list on the changelog.
Changes to IO traits
Following the lead of @sfackler’s RFC, we changed the AsyncRead
and AsyncWrite
traits to support reading into uninitialized memory. This change only affects users who implement the AsyncRead
or AsyncWrite
traits or manually calling the poll_*
methods. If the AsyncReadExt
or AsyncWriteExt
traits are used, no change is required.
Below is a simplified version of the new AsyncRead
:
pub trait AsyncRead {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>
) -> Poll<Result<()>>;
}
pub struct ReadBuf<'a> {
buf: &'a mut [MaybeUninit<u8>],
filled: usize,
initialized: usize,
}
impl<'a> ReadBuf<'a> {
// functions here, see RFC
}
The &mut [u8]
argument to Tokio 0.2’s poll_read
method has some unintended sharp edges: an implementer, not the end consumer, of the AsyncRead
trait can read the data stored in the &mut [u8]
slice. If a programmer creates a mutable slice of bytes (a &mut [u8]
) that references uninitialized memory (e.g., the excess capacity in a vector), then reading the &mut [u8]
data would cause undefined behavior. The new design mitigates this issue by providing a ReadBuf
struct that tracks the uninitialized memory, removing the need to initialize memory.
Additionally, the poll_read_buf
and poll_write_buf
methods have been removed from both traits. In practice, these methods were rarely implemented. Vectored operations will be implemented directly on types that support them.
Feature flag simplification
We have reduced the amount of feature flags needed to use the core features of Tokio. The dns
, tcp
, udp
, and uds
feature flags are collapsed into a single net
feature flag. We have also combined rt-core
and rt-util
into a single rt
feature flag. This rt
feature flag now encompasses everything needed to execute futures except for the multi-threaded runtime, which now lives under a rt-multi-thread
feature flag (was called rt-threaded
in 0.2).
dns
,tcp
,udp
anduds
->net
rt-util
andrt-core
->rt
rt-threaded
->rt-multi-thread
Runtime and Builder refactor
The Tokio team has improved the runtime Builder
to provide better consistency accross feature flag permutations and to be more mis-use resistant. To achieve this, we have added two new constructors to the Builder
type to support building both variations of the Tokio runtime. Additionally, Tokio will no longer choose the runtime variant based on feature flags when using Runtime::new
, but will instead always use the multi-threaded runtime, which is only available under the rt-multi-thread
feature flag. In addition, we’ve renamed the core_threads
builder method to the more accurate worker_threads
.
We have also refactored the runtime module. Tokio 0.2 exposed two types to interact with the Tokio runtime: Runtime
and Handle
. Runtime
has a block_on
method that required &mut self
, whereas Handle
has a block_on
method that required &self
. With Tokio 0.3, we collapsed Runtime
and Handle
into a single Runtime
. The block_on
method on the new runtime takes &self
, which means that it can be put in an Arc
without problems.
An example configuring a multi-threaded runtime with 6 worker threads:
use tokio::runtime::Builder;
let rt = Builder::new_multi_thread()
.worker_threads(6)
.enable_all()
.build()
.unwrap();
rt.block_on(async {
println!("Hello world!");
});
Methods changed to &self
We have overhauled how we handle wakers within our socket types to allow concurrent calls via &self
rather than &mut self
. To achieve this, we had to rewrite how we handle wakers internally. The problem comes with how poll_*
based methods work. Under the futures contract it states that a poll_*
fn should only store the last waker that it was called with.
Note that on multiple calls to poll, only the Waker from the Context passed to the most recent call should be scheduled to receive a wakeup.
From Future
This means that these poll_*
methods cannot be called or woken concurrently. To make &self
work for our async fn
on our sockets types, we refactored our io driver to use an internal intrusive linked-list to store the wakers. By doing this, our socket types can accept &self
rather than &mut self
when called.
Removal of non-1.0 crates and future proofing for 1.0
In our push for 1.0 we have removed many non-1.0 dependencies from the public API. This includes bytes
and mio
. This will allow us to push for a future proof and more stable 1.0 while we continue to innovate in our low-level crates.
Mio 0.7
We have finally also upgraded mio
to 0.7, which was originally released in an alpha in December of 2019 and did not make the cut for Tokio 0.2. Since then mio
0.7 has had time to mature and is finally making it into this release of Tokio 0.3. This upgrades some key dependencies like winapi
from 0.2 to 0.3. Additionally, mio
0.7 now comes with a pure-rust implementation of wepoll
which is a library that implements the epoll
API for Windows based applications.
Compatibility between 0.2 and 0.3
It is possible to upgrade to Tokio 0.3 incrementally via the tokio-compat-02
crate. This crate will spin up a single threaded background runtime from Tokio 0.2 and will allow you to wrap any future in its context. This will then allow you to run libraries that require Tokio 0.2 inside any runtime, including Tokio 0.3. Below is an example of how you can send a request using hyper
0.13, which requires Tokio 0.2.
use hyper::{Client, Uri};
use tokio_compat_02::FutureExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
// This will not panic because we are wrapping it in the
// Tokio 0.2 context via the `FutureExt::compat` fn.
client
.get(Uri::from_static("http://tokio.rs"))
.compat()
.await?;
Ok(())
}
Conclusion
We've had over 50 contributors help us out since the release of Tokio 0.2. We can't thank our community enough for helping us find bugs and helping us fix them. As we continue to move towards 1.0 feedback will be more critical than ever, so please feel free to open issues or join us on Discord to help us get there.