Using smol with a Tokio based crate at the same time?


I'm working on a networking library/tools which uses smol. I would like to interact with the webrtc crate, however it uses Tokio. What options are available to use it? I haven't had much luck finding answers on search engines. Is it as easy as somehow running both at once, or as painful as PRing/forking the webrtc project?

I'm uncertain on how async works internally.

The whole app is running under smol::block_on(async {}).

An error I get when trying to use one of the functions:

thread 'smol-1' panicked at 'there is no reactor running, must be called from the context of a Tokio 1.x runtime',\interceptor-0.8.2\src\stats\

Full backtrace here: gist:d0e2d43ff4bc436de7b598624c4f9b1c ยท GitHub



I was able to start a Tokio runtime and use the reference to spawn the webrtc call that was failing earlier, and now it compiles and runs but I suspect I will need to either block or use channels to grab the result.

    let runtime = tokio::runtime::Builder::new_multi_thread()

    runtime.spawn(async move { api.new_peer_connection(config).await });

Is this is an appropriate way to go forward? I'm totally guessing here.

I'm unfamiliar with asynchronous Rust, but I see smol's README says

1 Like

I'm not sure how I missed that. Thanks :slight_smile:

async-compat goes a long way, but doesn't seem to handle tokio::spawn. Will continue investigating.

Generally, smol things work inside a Tokio runtime, but Tokio things do not work inside smol. Therefore, just make sure to always use spawn/block_on from Tokio, and the smol things will work too.

(The reason is that the smol runtime is always global, whereas Tokio runtimes are not.)

1 Like

I think I was wrong about that!

I was able to successfully use the same method that did use a tokio::spawn in webrtc, when I moved Compat closer to the actual call. I was calling Compat over the whole app beforehand, and now calling it just before it enters webrtc/tokio world. I guess a smol::spawn somehow removes the Compat?

So far so good...

For people from the future, all I had to do was, instead of (for example):

let (mut connection, answer) = WebRtcConnection::answer(offer).await;

I wrapped it with async_compat::Compat:

let (mut connection, answer) = Compat::new(WebRtcConnection::answer(offer)).await;

Also, thanks for the explanation Alice, that makes me understand the situation better.

Unless you're bridging between the futures and tokio AsyncRead/Write traits, you don't need any compat tools when you're doing like I mentioned earlier. And if you do need to bridge them, Tokio also provides a tool for that in the tokio-util crate.

Sorry, I don't understand.

Unless you're bridging between the futures and tokio AsyncRead/Write traits

I don't know what "bridging" means exactly. I am calling async functions in the external crate, which internally has Tokio specific traits presumably, and a few tokio::spawns. I don't have a need to convert (bridge?) the external crate's data types as they're not those Tokio traits (so far).

Therefore, just make sure to always use spawn/block_on from Tokio, and the smol things will work too.

Can you clarify what "from Tokio" mean? I have no control of Tokio in the third party crate, unless I run the executor myself. I'm using smol in my crate, so it sounds like I'm calling things "from smol". Are you suggesting I run a Tokio executor?


You need to start up and run in the context of a Tokio executor in order for Tokio's root futures to function. smol's root futures function independently of any executor. The easiest way to do so is to replace your root smol::block_on with tokio::Runtime::block_on (or use #[tokio::main]) and smol::spawn with tokio::spawn. Smol/async-std futures will transparently work on the Tokio executor, so you won't need to replace any of your smol usage with Tokio except for the block_on/spawn.

The compat layer is only required when passing smol types to generic functions expecting Tokio traits or the other way around. If you just .await the future, then you don't need to use a compat adapter.

By bridging, I mean the case where you have something that implements futures::io::AsyncRead, and you want to pass it to a function that requires a tokio::io::AsyncRead. It sounds like this is not relevant to you.

Yes, I am suggesting you run a Tokio executor. By "from Tokio", I mean tokio::spawn instead of smol::spawn, and tokio::runtime::{Runtime, Handle}::block_on instead of smol::block_on.

CAD97 and alice, many thanks for the explanation.

I investigated async_compat and it does indeed run a Tokio executor which solved my original problem.

There's a feature in my code that uses the external crate, so if I need to make it more clear to the developer (rather than using async_compat), I will conditionally compile tokio::Runtime::block_on instead of smol::block_on.