Tokio-compat and Cargo's multiple version support feature
This week I decided that my Pocket to Buffer via IFTTT setup wasn’t cutting it anymore. Predominantly because I use a trial account of buffer and I can only queue ten items at a time. The developer in me screamed “you can build it yourself.” Although normally I would lean away from the idea I haven’t had much time to write Rust lately and decided it would be nice to throw together a few crates and get the job done.
I reached out for cargo, got things started and pretty quickly hit the following error.
thread 'main' panicked at 'not currently running on a Tokio 0.2.x runtime.'
I had included tokio but I was running the latest version
1.15.0. The error was pretty shallow telling me it was originating on the main
call which I happened to setup as a tokio::main
. First thing first lets get a
deeper stack trace.
root@b4c0a85e75ae:/usr/src# RUST_BACKTRACE=1 cargo run
This eventually lead me to a call being made by one of the dependencies I had included. I looked up the deps of that dependency and noticed it had pinned tokio 0.2.0. A look in the build dependencies directory shows sure enough the app built with both tokio 0.2.0 and 1.15.0.
My assumption was that the higher pinned version would have taken precedence, and this is when I learned that Cargo supports multiple version dependencies.
If multiple packages have a common dependency with semver-incompatible versions, then Cargo will allow this, but will build two separate copies of the dependency.
This all means the dependency I brought in is locked to tokio 0.2.0 and attempting to operate in the 1.15.0 runtime. The creators of tokio have prepared for these circumstances and this is where tokio-compat comes in. As tokio progressed with breaking changes they introduced methods to encapsulate older api usage.
1 |
|
To fix my particular problem I used run_std
to encapsulate the external crate
call.
1 |
|
With the tokio_compat::run_std
function and an anonymous async
function we’re ready for a successful compilation, but how does it work?
Tokio relies on the runtime services that Tokio provides. These runtime services include the ability to spawn other tasks; the I/O driver, which allows tasks to be notified by the operating system's async I/O APIs, and the timer. Futures which rely on Tokio 0.1's runtime services will panic when they try to access those runtime services (such as by spawning a task or creating a timer) on the Tokio 0.2 runtime, even if they are converted to the std::future::Future trait. This is because the new runtime does not provide these services in a way compatible with Tokio 0.1's APIs.