Solving the problem
This is a reduced example:
use tokio; // 1.0.2
#[tokio::main]
async fn inner_example() {}
#[tokio::main]
async fn main() {
inner_example();
}
thread 'main' panicked at 'Cannot start a runtime from within a runtime. This happens because a function (like `block_on`) attempted to block the current thread while the thread is being used to drive asynchronous tasks.', /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.0.2/src/runtime/enter.rs:39:9
To avoid this, you need to run the code that creates the second Tokio runtime on a completely independent thread. The easiest way to do this is to use std::thread::spawn
:
use std::thread;
#[tokio::main]
async fn inner_example() {}
#[tokio::main]
async fn main() {
thread::spawn(|| {
inner_example();
}).join().expect("Thread panicked")
}
For improved performance, you may wish to use a threadpool instead of creating a new thread each time. Conveniently, Tokio itself provides such a threadpool via spawn_blocking
:
#[tokio::main]
async fn inner_example() {}
#[tokio::main]
async fn main() {
tokio::task::spawn_blocking(|| {
inner_example();
}).await.expect("Task panicked")
}
In some cases you don't need to actually create a second Tokio runtime and can instead reuse the parent runtime. To do so, you pass in a Handle
to the outer runtime. You can optionally use a lightweight executor like futures::executor
to block on the result, if you need to wait for the work to finish:
use tokio::runtime::Handle; // 1.0.2
fn inner_example(handle: Handle) {
futures::executor::block_on(async {
handle
.spawn(async {
// Do work here
})
.await
.expect("Task spawned in Tokio executor panicked")
})
}
#[tokio::main]
async fn main() {
let handle = Handle::current();
tokio::task::spawn_blocking(|| {
inner_example(handle);
})
.await
.expect("Blocking task panicked")
}
See also:
Avoiding the problem
A better path is to avoid creating nested Tokio runtimes in the first place. Ideally, if a library uses an asynchronous executor, it would also offer the direct asynchronous function so you could use your own executor.
It's worth looking at the API to see if there is a non-blocking alternative, and if not, raising an issue on the project's repository.
You may also be able to reorganize your code so that the Tokio runtimes are not nested but are instead sequential:
struct Data;
#[tokio::main]
async fn inner_example() -> Data {
Data
}
#[tokio::main]
async fn core(_: Data) {}
fn main() {
let data = inner_example();
core(data);
}