Async -> blocking -> async sandwich causes tokio panics?

I have async code, and I'm using a library that is unfortunately sync. That library uses the blocking interface of reqwest internally.

I'm running into issues when instantiating that struct from within async code. I'm only calling the actual blocking code from a rayon pool, so that should be fine. I don't understand why instantiating it would cause issues though:

thread 'main' panicked at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/runtime/blocking/shutdown.rs:51:21:
Cannot drop a runtime in a context where blocking is not allowed. This happens when a runtime is dropped from within an asynchronous context.
stack backtrace:
   0: __rustc::rust_begin_unwind
             at /rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/panicking.rs:697:5
   1: core::panicking::panic_fmt
             at /rustc/1159e78c4747b02ef996e55082b704c09b970588/library/core/src/panicking.rs:75:14
   2: tokio::runtime::blocking::shutdown::Receiver::wait
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/runtime/blocking/shutdown.rs:51:21
   3: tokio::runtime::blocking::pool::BlockingPool::shutdown
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/runtime/blocking/pool.rs:263:29
   4: <tokio::runtime::blocking::pool::BlockingPool as core::ops::drop::Drop>::drop
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/runtime/blocking/pool.rs:284:14
   5: core::ptr::drop_in_place<tokio::runtime::blocking::pool::BlockingPool>
             at /home/arvid/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:804:1
   6: core::ptr::drop_in_place<tokio::runtime::runtime::Runtime>
             at /home/arvid/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:804:1
   7: reqwest::blocking::wait::enter
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/reqwest-0.12.23/src/blocking/wait.rs:80:21
   8: reqwest::blocking::wait::timeout
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/reqwest-0.12.23/src/blocking/wait.rs:13:5
   9: reqwest::blocking::client::ClientHandle::new
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/reqwest-0.12.23/src/blocking/client.rs:1417:15
  10: reqwest::blocking::client::ClientBuilder::build
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/reqwest-0.12.23/src/blocking/client.rs:116:9
  11: reqwest::blocking::client::Client::new
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/reqwest-0.12.23/src/blocking/client.rs:1219:30
  12: debuginfod::client::Client::new
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/debuginfod-0.2.1/src/client.rs:70:18
  13: debuginfod::client::Client::from_env
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/debuginfod-0.2.1/src/client.rs:92:5
  14: wtf::main::{{closure}}
             at ./src/main.rs:3:18
  15: tokio::runtime::park::CachedParkThread::block_on::{{closure}}
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/runtime/park.rs:285:71
  16: tokio::task::coop::with_budget
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/task/coop/mod.rs:167:5
  17: tokio::task::coop::budget
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/task/coop/mod.rs:133:5
  18: tokio::runtime::park::CachedParkThread::block_on
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/runtime/park.rs:285:31
  19: tokio::runtime::context::blocking::BlockingRegionGuard::block_on
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/runtime/context/blocking.rs:66:14
  20: tokio::runtime::scheduler::multi_thread::MultiThread::block_on::{{closure}}
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/runtime/scheduler/multi_thread/mod.rs:87:22
  21: tokio::runtime::context::runtime::enter_runtime
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/runtime/context/runtime.rs:65:16
  22: tokio::runtime::scheduler::multi_thread::MultiThread::block_on
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/runtime/scheduler/multi_thread/mod.rs:86:9
  23: tokio::runtime::runtime::Runtime::block_on_inner
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/runtime/runtime.rs:358:50
  24: tokio::runtime::runtime::Runtime::block_on
             at /home/arvid/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/runtime/runtime.rs:330:18
  25: wtf::main
             at ./src/main.rs:3:57
  26: core::ops::function::FnOnce::call_once
             at /home/arvid/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:253:5

The following code snippet reproduces this:

#[tokio::main]
async fn main() {
    let _client = debuginfod::Client::from_env().unwrap();
}

Why is this, and what can I do about it (without forking the library, I tried to look into adding async support, but it would be exceedingly painful to do so without just duplicating all the code in the library to have two versions, and the author is not particularly interested)?

Even creating the reqwest client already blocks for a bit until the worker thread is spawned and initialized. And at reqwest/src/blocking/wait.rs at 994b8a0b7aa0a0ff6f87471f9e0d1a4dd2936fcd · seanmonstar/reqwest · GitHub there is a check for any blocking operation that it isn't run from within another tokio runtime.

1 Like

It's been a while since I've needed it, but I think you can just use spawn_blocking as with any blocking task....

To perform an async -> blocking -> async sandwich, you must use spawn_blocking to perform the async -> blocking transition. You can't do it at the blocking -> async transition - it's too late at that point.

2 Likes

So does that mean that spawning on rayon with rayon::spawn (with a one shot channel for the reply) is not suitable, why would that be less workable than spawn_blocking? Doing that does seem to work for the actual queries.

But spawn_blocking will be useful for other cases, such as the initialisation.

The rayon::spawn method is a suitable alternative to spawn_blocking. Main difference is perf, where rayon::spawn is preferred for CPU bound operations, whereas spawn_blocking is preferred for IO bound operations.

2 Likes

Thanks, I guess I should be using rayon when I have tasks that are an unpredictable mix of IO and CPU bound?

I recommend against using rayon on code that is complex enough that you end up with a "sandwich" of anything.

For example, you can't use locks (including indirectly blocking functions) inside rayon tasks that could be waiting for anything that also uses rayon. It will lead to deadlock of the entire rayon pool.

Rayon will size the thread pool to CPU size, and I/O will waste it.

2 Likes

Yeah probably you just want spawn_blocking.

1 Like