Adventures in Rust: Futures and Tokio

 


We can use the oneshot module to provide a communication channel back to the sender, allowing us to respond with a lookup result.

The Future trait


The future then attempts to complete. If the future is able to complete, it returns Async:: If the future is unable to complete due to being blocked on an internal resource such as a TCP socket , it returns Async:: The future implementation then saves its state internally so that the next time poll is called after an external event is receied , it resumes processing from the point it left off.

Work is not repeated. The hello world future requires no asynchronous processing and is immediately ready, so it returns Ok Async:: Tokio is responsible for running futures to completion.

This is done by passing the future to tokio:: This is because Tokio only executes the futures, it does not do anything with values. Finally, we use the Core to run the future and produce the lookup result.

There are a couple of things that aren't great about this function. First, every time we call this function, we are creating an event loop for the express purpose of executing a single dns lookup. Secondly, when we execute core. To make matters worse attempting to call this function directly from my hyper service resulted in a panic with the message cannot recursively call into core.

I was able to work around this by spawning a new thread to perform this task and waiting on the result:. Note that the call function in this example is essentially a request handler used by Hyper to handle Http requests as they come in. While this technically worked, I knew that things had gone awry. At this point, we're spawning a separate thread that creates it's own event loop to perform a single task which we're then blocking on to synchronously wait on the result.

At this point I tried a lot of different things that didn't work. At the time, all I thought I had to do was to get ahold of that resulting Future and then weave it into the Future I created as part of my Hyper service invocation in it's call function.

I still held onto the idea that a Future in rust was something that was already running asynchronously and all I had to do was just wait on the result to be materialized. Several times I was able to find a code path to successfully get the future over to my Hyper service using hook or crook, yet it never seemed to work out.

As a result the service would always hang and I'd never receive a result. It was at this point that I finally took a step back and decided to read the documentation for futures-rs in an attempt to figure out what exactly I was doing wrong. While reading through the documentation, I found this important bit under "Runtime Characteristics" in the documentation for the poll function:.

Futures alone are inert; they must be actively polled to make progress, meaning that each time the current task is woken up, it should actively re-poll pending futures that it still has an interest in. At this point things started to click for me. Rust's Future implementation is a much lower level representation of a Future than I was previously used to. I won't delve deep into the specifics here, but suffice to say that a Future is essentially a state machine that does nothing until it's actively polled.

Additionally as futures are composed using combinators such as map and join , we are actually building a more elaborate state machine, not manipulating the result of something that is already running somewhere.

While this epiphany was very helpful in understanding how Futures operate in Rust, it still didn't explain the behavior I was seeing. However, this wasn't the case. Armed with my newfound knowledge, I next turned my attention to the Tokio reactor Core. Admittedly before this point I did know that creating a new reactor Core on every DNS lookup was at least partially the reason that things weren't working out, I just didn't know why.

Now that I understood Futures better, I also could see the reason why the reactor Core existed and in addition it gave me a better appreciation for why Tokio itself needed to exist. Now that we know more about Futures, let's take a look at the documentation for the Tokio reactor Core:.

It was at this point that it began to come apparent to me what was going wrong in my attempts to simply extract a Future that I could use to materialize the DNS lookup result. First, let's take a quick look back at the two places we were using the reactor Core that was being created on each invocation:.

The first interesting bit we see here is that we are providing a Handle to the reactor Core when creating a the Resolver. The next interesting bit we see here is that we are invoking core. One thing that was not immediately apparent to me can be gleaned from the first line of documentation for the reactor Core's run function:.

Runs a future until completion, driving the event loop while we're otherwise waiting for the future to complete. Not only does the run function run a Future until completion, but it also drives the event loop as a whole while doing so. This is something that makes a lot of sense when you think about it, but did not initially dawn on me.

Putting these two facts together, we can now see that the Resolver is spawning tasks into the reactor Core event loop and by calling core. Otherwise those tasks would never be completed and as a result the Future itself never completed either! Now that we understand the problem and the underlying concept much better, we're ready to create a solution.

Our previous naive solution has two major weaknesses that we need to solve. Firstly we are creating a new event loop on every single lookup we perform and secondly we are blocking the current thread of execution until that result has been fully realized.

The first thing we need to do is use a single reactor Core that can be used to consistently handle all reverse DNS lookups for us. Based on the documentation for reactor Core, we have two options to drive the event loop.

Option one is the run function that takes a Future and runs the event loop until the future completes. Our second option is to use the turn function which will perform a single iteration of the event loop. As per the documentation we could use the turn function inside of an infinite loop to run the event loop indefinitely.

While we can probably create a solution using turn , what would really be ideal here is to build a Future that never ends and continually produces lookup requests that results in a reverse DNS lookup on our single event loop. Luckily, this very thing exists! In many cases, you have a specific future you want to run, and you'd drive the reactor until that completes.

In other cases, your run the reactor "forever", endlessly handling new work. In this case, you can use Core:: You don't need to and probably shouldn't do this in modern Rust. It's preferred to return an anonymous type:. My understanding is that this level of Tokio is reserved for more complicated setups. Many people can just use tokio:: By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service , privacy policy and cookie policy , and that your continued use of the website is subject to these policies.

Shepmaster k 13