A great 2018, an even better 2019

December 19, 2018

A year ago, Tokio was a very different library. It includes the (now deprecated) tokio-core which provided a future executor, I/O selector, and basic TCP/UDP types in a single library. It also included tokio-proto, but we won't talk about that. Over the past year, Tokio has grown to become Rust's asynchronous I/O platform. It has been adopted by a number of large companies to build apps.

A lot was achieved in 2018. Some highlights include:

  • A high-performance, multi-threaded, work-stealing, scheduler was introduced.
  • Timers were rebuilt from the ground up.
  • A file system API was introduced.
  • UDS, TLS, signals, and other APIs were added to Tokio.
  • Most importantly, the Tokio runtime was introduced as a batteries included platform upon which asynchronous applications can be built.

This is not including the myriad of smaller improvements. Improvements like revamped documentation, bug fixes, performance improvements, and refined APIs. These improvements were contributed by an impressive 165 individual contributors.

Indeed, 2018 was a great year for Tokio and it is all thanks to you. That said, we are just getting started and 2019 is setting up to be even better. I want to highlight a few big things (already in the works) expected to land in 2019.

Async / await

Async / await is being built into the Rust language. It enables writing asynchronous code (almost) as if it were synchronous. This work has been on going for a while, and should land in Rust stable sometime in 2019. The implication of async / await is a large ergonomic improvements when using Tokio.

If you are willing to use the Rust nightly compiler, you can use async / await with Tokio today. In short, depend on tokio with the async-await-preview feature and try it out.

Here is a taste:

pub fn main() {
    tokio::run_async(async {
        let client = Client::new();

        let uri = "http://httpbin.org/ip".parse().unwrap();

        let response = await!({
            client.get(uri)
                .timeout(Duration::from_secs(10))
        }).unwrap();

        println!("Response: {}", response.status());

        let mut body = response.into_body();

        while let Some(chunk) = await!(body.next()) {
            let chunk = chunk.unwrap();
            println!("chunk = {}", str::from_utf8(&chunk[..]).unwrap());
        }
    });
}

Full example

So, what is the path for Tokio to fully adopt async/await? Let's talk a bit about that.

First, async/await has to land on Rust stable. The exact target date is unknown, but it is expected to happen on 2019. Once this happens, Tokio will immediately add support in a backwards compatible way. The strategy by which this will happen is being explored today with the experimental async-await-preview feature flag. At a high level, async/await specific APIs will be added with an _async suffix. For example, tokio::run_async will be the way to start the Tokio runtime using an async fn (or a std::future::Future).

Once async/await support has had a moment to mature, Tokio will issue a breaking change and drop the _async suffix. tokio::run will take an async function by default.

What about futures 0.1? We can't immediately drop support for futures 0.1. There is a growing ecosystem, including production apps, built on top of Tokio and that includes using futures 0.1. It will take time to transition off. This will be done in a multi step way.

First, support for async/await is added in a backwards compatible way. This adds simultaneous support for both async/await and futures 0.1. Then, async/await becomes the primary API and futures 0.1 can be used via a compatibility layer. This will enable using libraries that haven't been updated yet with the latest Tokio.

We are sensitive to the fact that change is hard for an established ecosystem and look forward to discussing the transition process with the community.

A tracking issue has been opened here. This is the place to discuss Tokio's async/await plans and track progress.

Tokio Trace

Visibility into execution behavior is critical when dealing with production applications. This includes questions like:

  • How many tasks are currently executing?
  • Why did this task hang?
  • Which tasks are taking longer than expected to poll, and what was the cause?

Right now, there is no good way to answer. To help improve the visibility and debuggability of Tokio, we (mostly hawkw) are working on a major new feature: Tokio Trace.

Tokio trace has been discussed in this issue. At a high level, Tokio trace is a structured logging system in which log events cover periods of time instead of a fixed point. "Period of time" is a crucial feature. The instrumentation API will allow specifying when an event starts and when it ends. By doing this, we can infer a parent / child relationship. Events that are contained within other events are children, building out a tree.

Once the parent / child dependency is built, it becomes easy to do things like filtering log events related to a buggy task. It also becomes possible to track log events related to a buggy task across multiple tasks.

The second part of Tokio trace is the "structured" part. Instead of logging basic strings, event data can be included using primitive types. For example, a Stream combinator can be instrumented to track the number of messages processed per poll by doing something like:

trace!(messages = num_processed);

where num_processed is a usize. Subscribers are able to receive the event with the usize value.

By combining the parent/child structure and the structured logging, we can answer the "Which tasks are taking longer than expected to poll, and what was the cause?" question by tracking the 99.9th percentile of task poll durations, and checking common causes for increased latencies, such as processing streams with many messages without yielding. Many of these checks could be implemented by tools that listen to the events emitted by Tokio trace.

More about this will be blogged about once the feature lands in Tokio proper.

Teams

The final big thing for Tokio in 2019 is something that I've had on my mind for a while. Tokio has grown in terms of functionality and adoption past a point that is sane for me to manage, even with the group of amazing regular contributors who make up an informal Tokio team. It is time to scale out Tokio development and maintenance.

The strategy by which to do this is not new and I think Rust provides a good model to follow. We will need to introduce a set of teams, each of which focus on various aspects of Tokio.

The specifics regarding what teams will exist and who will be part of them are still to be determined and will change over time. There will be blog posts throughout the year discussing the evolution on that front.

We also need newcomers to help join in the effort of building and growing Tokio. This includes us spending time mentoring. So, consider this a pre call to action. Do you rely on Tokio at work or do you simply have an interest in Rust asynchronous I/O? It doesn't matter if you feel like you don't have the experience needed or are too much of a "newbie". Join us now in the Gitter channel and help us figure out the process of transitioning to teams.

Finally, an extra big thanks to those who are going above and beyond, spending significant time helping out with the Tokio dev, maintenance, docs, and helping users in Gitter.