New Tokio release, now with filesystem support

May 5, 2018

It took a bit longer than I had initially hoped (as it always does), but a new Tokio version has been released. This release includes, among other features, a new set of APIs that allow performing filesystem operations from an asynchronous context.

Filesystem APIs

Interacting with files (and other filesystem types) requires* blocking system calls and we all know that blocking and asynchronous do not mix. So, historically, when people ask “how do I read from and write to files?”, the answer is to use a thread pool. The idea is that when a blocking read or write must be performed, it is done on a thread pool so that it does not block the asynchronous reactor.

Requiring a separate thread pool for performing file operations requires message passing. The asynchronous task must send a message to the thread pool asking it to do a read from the file, the thread pool does the read and fills a buffer with the result. Then the thread pool sends the buffer back to the asynchronous task. Not only does this add the overhead for dispatching messages, but it also requires allocating buffers to send the data back and forth.

Now, with Tokio’s new filesystem APIs, this message passing overhead is no longer needed. A new File type is added. This type looks very similar to the type provided by std, but it implements AsyncRead and AsyncWrite, making it safe to use directly from an asynchronous task running on the Tokio runtime.

Because the File type implements AsyncRead and AsyncWrite, it can be used in much the same way that a TCP socket would be used from Tokio.

As of today, the filesystem APIs are pretty minimal. There are many other APIs that need to be implemented to bring the Tokio filesystem APIs in line with std, but those are left as an exercise to the reader to submit as PRs!

* Yes, there are some operating systems that provide fully asynchronous filesystem APIs, but these are either incomplete or not portable.

Standard in and out

This release of Tokio also includes asynchronous standard input and standard output APIs. Because it is difficult to provide true asynchronous standard input and output in a portable way, the Tokio versions use a similar strategy as the blocking file operation APIs.

blocking

These new APIs are made possible thanks to a new blocking API that allows annotating sections of code that will block the current thread. These blocking sections can include blocking system calls, waiting on mutexes, or CPU heavy computations.

By informing the Tokio runtime that the current thread will block, the runtime is able to move the event loop from the current thread to another thread, freeing the current thread up to permit blocking.

This is the opposite of using message passing to run blocking operations on a threadpool. Instead of moving the blocking operation to another thread, the entire event loop is moved.

In practice, moving the event loop to another thread is much cheaper than moving the blocking operation. Doing so only requires a few atomic operations. The Tokio runtime also keeps a pool of standby threads ready to allow moving the event loop as fast as possible.

This also means that using the blocking annotation and tokio-fs must be done from the context of the Tokio runtime and not other futures aware executors.

Current thread runtime

The release also includes a “current thread” version of the runtime (thanks kpp). This is similar to the existing runtime, but runs all components on the current thread. This allows running futures that do not implement Send.