Simplest possible block_on?

Dooohhh..

Fixed that...

The application panicked (crashed).
Message:  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.
Location: C:\Users\JR\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-0.2.22\src\runtime\enter.rs:38

More progress...
There seems to be some libraries that don't like the
tokio::runtime::Handle::current.block_on()...
And some that do...
reqwest works, trust-dns does not... which is weird, since reqwest uses trust-dns under the hood.

I'll do some more testing and report back.

I appreciate all the assistance...
JR

What do you mean with them not liking handle.block_on()? Perhaps verify that the versions are correct, i.e. that the library didn't expect Tokio 0.3. You can do this on docs.rs at the top of the page. Here you can open a drop-down by clicking the crate name in the toolbar, and the dropdown will have a list of dependencies including their version.

@alice,

It looks like the issue(s) may be related to repeated calls to block_on in the same stack.
I'm back in the cave...

  • Tokio is initialized
  • We're in a spawn_blocking context
  • We're in a block_on
  • I have to transition from sync to async
  • All I have is a Future
  • it may spawn it's own threads or runtime
  • Handle::block_on() may cause panic
11: tokio::runtime::enter::enter
    at C:\Users\JR\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-0.2.22\src\runtime\enter.rs:38
      36 │     }
      37 │
      38 >     panic!(
      39 │         "Cannot start a runtime from within a runtime. This happens \
      40 │          because a function (like `block_on`) attempted to block the \
12: tokio::runtime::handle::{{impl}}::block_on::{{closure}}<reqwest::async_impl::client::Pending>
13: tokio::runtime::context::enter<closure-0,core::result::Result<reqwest::async_impl::response::Response, reqwest::error::Error>>
14: tokio::runtime::handle::Handle::enter<closure-0,core::result::Result<reqwest::async_impl::response::Response, reqwest::error::Error>>
15: tokio::runtime::handle::Handle::block_on<reqwest::async_impl::client::Pending>
    at C:\Users\JR\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-0.2.22\src\runtime\handle.rs:275
     273 │         ///
     274 │         pub fn block_on<F: Future>(&self, future: F) -> F::Output {
     275 >             self.enter(|| {
     276 │                 let mut enter = crate::runtime::enter(true);
     277 │                 enter.block_on(future).expect("failed to park thread")
16: lib_prtg::apis::await_future_exec<reqwest::async_impl::client::Pending>
    at C:\Development\Rust\-Sensors\libs\prtg\src\apis\mod.rs:202
     200 │ pub fn await_future_exec<F: Future>(f: F) -> F::Output {
     201 │     if let Ok(handle) = tokio::runtime::Handle::try_current() {
     202 >         handle.block_on(f)
     203 │     } else {
     204 │         futures::executor::block_on( async { f.await})
17: lib_prtg::apis::APIEntry::exec_content<lib_prtg::apis::prtg_api::ExchangePassHash,lib_prtg::apis::prtg_api::ExchangePassHashParams>
    at C:\Development\Rust\-Sensors\libs\prtg\src\apis\mod.rs:289
     287 │         {  println!("Sending Request: {:?}", req);}
     288 │         //let resp = await_future( async { client.execute(req).await}, std::time::Duration::from_secs(30))??;
     289 >         let resp = await_future_exec( client.execute(req))?;
     290 │
     291 │
...
24: tokio::runtime::enter::{{impl}}::block_on::{{closure}}<core::future::from_generator::GenFuture<generator-0>>
*
35: tokio::runtime::Runtime::block_on<core::future::from_generator::GenFuture<generator-0>>
36: abscissa_tokio::run::{{closure}}<agent::application::AgentApp,core::future::from_generator::GenFuture<generator-0>>
37: core::result::Result<tokio::runtime::Runtime, abscissa_core::error::framework::FrameworkError>::map<tokio::runtime::Runtime,abscissa_core::error::framework::FrameworkError,tuple<>,closure-0>
38: abscissa_tokio::run<agent::application::AgentApp,core::future::from_generator::GenFuture<generator-0>>
39: agent::commands::agentcmd::{{impl}}::run
40: agent::commands::_DERIVE_Runnable_FORagentCmd::{{impl}}::run
41: abscissa_core::command::entrypoint::{{impl}}::run<agent::commands::AgentCmd>
42: abscissa_core::application::Application::run<agent::application::AgentApp,alloc::vec::Vec<alloc::string::String>>
43: agent::commands::svcmain::alt_boot<agent::application::AgentApp>
44: agent::commands::svcmain::agent_svc_main::{{closure}}
45: tokio::runtime::blocking::task::{{impl}}::poll<closure-0,tuple<>>
...
66: std::thread::{{impl}}::spawn_unchecked::{{closure}}::{{closure}}<closure-0,tuple<>>
*
spawn_blocking(...)

I have already streamlined the versions...

PS C:\Development\Rust\Sensors\agent> cargo tree -i -p tokio
tokio v0.2.22
├── abscissa_tokio v0.5.1 (C:\Development\Rust\rust_cargo_sources\abscissa\tokio)
│   └── agent v0.1.9 (C:\Development\Rust\Sensors\agent)
├── axis_sensor v0.1.0 (C:\Development\Rust\Sensors\libs\axis-sensor)
│   └── agent v0.1.9 (C:\Development\Rust\Sensors\agent)
├── graph-error v0.1.0 (C:\Development\Rust\rust_cargo_sources\graph-rs\graph-error)
│   ├── graph-oauth v0.1.0 (C:\Development\Rust\rust_cargo_sources\graph-rs\graph-oauth)
│   │   ├── graph-rs v0.1.0 (C:\Development\Rust\rust_cargo_sources\graph-rs)
│   │   │   └── sensor_msgraph v0.1.0 (C:\Development\Rust\Sensors\libs\msgraph)
│   │   │       └── agent v0.1.9 (C:\Development\Rust\Sensors\agent)
│   │   └── sensor_msgraph v0.1.0 (C:\Development\Rust\Sensors\libs\msgraph) (*)
│   ├── graph-rs v0.1.0 (C:\Development\Rust\rust_cargo_sources\graph-rs) (*)
│   └── sensor_msgraph v0.1.0 (C:\Development\Rust\Sensors\libs\msgraph) (*)
├── graph-rs v0.1.0 (C:\Development\Rust\rust_cargo_sources\graph-rs) (*)
├── h2 v0.2.7
│   └── hyper v0.13.8
│       ├── graph-error v0.1.0 (C:\Development\Rust\rust_cargo_sources\graph-rs\graph-error) (*)
│       ├── hyper-rustls v0.21.0
│       │   └── reqwest v0.10.8
│       │       ├── graph-error v0.1.0 (C:\Development\Rust\rust_cargo_sources\graph-rs\graph-error) (*)
│       │       ├── graph-oauth v0.1.0 (C:\Development\Rust\rust_cargo_sources\graph-rs\graph-oauth) (*)
│       │       ├── graph-rs v0.1.0 (C:\Development\Rust\rust_cargo_sources\graph-rs) (*)
│       │       ├── moah_client v0.1.0 (C:\Development\Rust\Sensors\libs\moah_client)
│       │       │   ├── axis_sensor v0.1.0 (C:\Development\Rust\Sensors\libs\axis-sensor) (*)
│       │       │   ├── prtg-rs v0.1.0 (C:\Development\Rust\Sensors\libs\prtg-rs)
│       │       │   │   ├── axis_sensor v0.1.0 (C:\Development\Rust\Sensors\libs\axis-sensor) (*)
│       │       │   │   ├── local_sys v0.1.0 (C:\Development\Rust\Sensors\libs\local_sys)
│       │       │   │   │   └── agent v0.1.9 (C:\Development\Rust\Sensors\agent)
│       │       │   │   ├── ping_sensor v0.1.0 (C:\Development\Rust\Sensors\libs\ping)
│       │       │   │   │   └── agent v0.1.9 (C:\Development\Rust\Sensors\agent)
│       │       │   │   ├── agent v0.1.9 (C:\Development\Rust\Sensors\agent)
│       │       │   │   ├── sabaki-rs v0.1.0 (C:\Development\Rust\Sensors\libs\sabaki-rs)
│       │       │   │   │   ├── axis_sensor v0.1.0 (C:\Development\Rust\Sensors\libs\axis-sensor) (*)
│       │       │   │   │   ├── local_sys v0.1.0 (C:\Development\Rust\Sensors\libs\local_sys) (*)
│       │       │   │   │   ├── ping_sensor v0.1.0 (C:\Development\Rust\Sensors\libs\ping) (*)
│       │       │   │   │   ├── agent v0.1.9 (C:\Development\Rust\Sensors\agent)
│       │       │   │   │   └── sensor_msgraph v0.1.0 (C:\Development\Rust\Sensors\libs\msgraph) (*)
│       │       │   │   └── sensor_msgraph v0.1.0 (C:\Development\Rust\Sensors\libs\msgraph) (*)
│       │       │   ├── agent v0.1.9 (C:\Development\Rust\Sensors\agent)
│       │       │   ├── sabaki-rs v0.1.0 (C:\Development\Rust\Sensors\libs\sabaki-rs) (*)
│       │       │   └── sensor_msgraph v0.1.0 (C:\Development\Rust\Sensors\libs\msgraph) (*)
│       │       ├── oauth2 v4.0.0-alpha.2
│       │       │   ├── moah_client v0.1.0 (C:\Development\Rust\Sensors\libs\moah_client) (*)
│       │       │   └── openidconnect v2.0.0-alpha.1 (C:\Development\Rust\rust_cargo_sources\openidconnect-rs)
│       │       │       └── moah_client v0.1.0 (C:\Development\Rust\Sensors\libs\moah_client) (*)
│       │       ├── prtgclib_netlic v1.0.0 (C:\Development\Rust\Sensors\libs\netlicensing)
│       │       │   └── sabaki-rs v0.1.0 (C:\Development\Rust\Sensors\libs\sabaki-rs) (*)
│       │       ├── prtgclib_prtg v1.0.0 (C:\Development\Rust\Sensors\libs\prtg)
│       │       │   └── prtg-rs v0.1.0 (C:\Development\Rust\Sensors\libs\prtg-rs) (*)
│       │       └── sentry v0.20.1
│       │           └── sabaki-rs v0.1.0 (C:\Development\Rust\Sensors\libs\sabaki-rs) (*)
│       ├── hyper-tls v0.4.3
│       │   └── reqwest v0.10.8 (*)
│       └── reqwest v0.10.8 (*)
├── hyper v0.13.8 (*)
├── hyper-rustls v0.21.0 (*)
├── hyper-tls v0.4.3 (*)
├── local_sys v0.1.0 (C:\Development\Rust\Sensors\libs\local_sys) (*)
├── moah_client v0.1.0 (C:\Development\Rust\Sensors\libs\moah_client) (*)
├── ping_sensor v0.1.0 (C:\Development\Rust\Sensors\libs\ping) (*)
├── agent v0.1.9 (C:\Development\Rust\Sensors\agent)
├── prtgclib_netlic v1.0.0 (C:\Development\Rust\Sensors\libs\netlicensing) (*)
├── prtgclib_prtg v1.0.0 (C:\Development\Rust\Sensors\libs\prtg) (*)
├── reqwest v0.10.8 (*)
├── sabaki-rs v0.1.0 (C:\Development\Rust\Sensors\libs\sabaki-rs) (*)
├── sensor_msgraph v0.1.0 (C:\Development\Rust\Sensors\libs\msgraph) (*)
├── tokio-rustls v0.14.1
│   ├── hyper-rustls v0.21.0 (*)
│   └── reqwest v0.10.8 (*)
├── tokio-tls v0.3.1
│   ├── hyper-tls v0.4.3 (*)
│   └── reqwest v0.10.8 (*)
├── tokio-util v0.3.1
│   └── h2 v0.2.7 (*)
├── trust-dns-proto v0.19.5
│   └── trust-dns-resolver v0.19.5
│       ├── reqwest v0.10.8 (*)
│       └── sabaki-rs v0.1.0 (C:\Development\Rust\Sensors\libs\sabaki-rs) (*)
└── trust-dns-resolver v0.19.5 (*)

I must say... However frustrating these things are...
We've come a long way from : "bus error, core dumped"

Thanks again for working with me.....
JR

If your last operation was block_on, then you are currently in an async context, so it doesn't make sense to do a "sync → async" transition.

@alice...

OK...
Then I must have missed something fundamental...
How do you await a response from a sync function...

use tokio; // 0.2.22
use futures; // 0.3.6;
use reqwest::{
    self, Error as ReqwError
}; // 0.10.8

async fn some_async_fn(url: &str) -> Result<String, ReqwError> {
    let res = 
    reqwest::get(url)
         .await?
         .text()
         .await?;
    Ok(res)
}
// ----- The cavernous cave -------------------------
pub fn our_cave(url: &str) -> Result<String, ReqwError> {
    futures::executor::block_on( some_async_fn(url))
}
// --------------------------------------------------

#[tokio::main] // threaded_scheduler
async fn main() {
    let resp = some_async_fn("https://hyper.rs").await.unwrap();
    println!("{:#?}", resp);
    let resp = our_cave("https://hyper.rs").unwrap();
    println!("{:#?}", resp);
}

When you're in "pub fn our_cave", what do you use to await the response ?
since you can't await, and you can't
futures::executor::block_on...

Thanks
JR

Well the problem is that if you call a sync function from async code without a spawn_blocking, then that function may not block. The mistake is in main where you called our_cave, or that our_cave is not an async fn.

@alice,

Yes... That is the problem...
Imagine there is a 50+ stack frames between main and cave...
Quite frequently, when dealing with external libraries and systems, this is a necessary evil...

If I read your response correctly...
After calling one async function, it would be a mistake to call a sync function, that may or may not call an async function again...
That's not practical in the real world.
Without rewriting all impl, trait, and every crate out there to be async, we will all hit this bump at some point.

So, again philosophy aside...
What is the best / correct approach to dealing with it.

As an example...
reqwest has both a sync and async implementation...
The sync implementation relies on the async one under the hood.
and it may spawn a tokio runtime...

// reqwest/src/blocking/client.rs Client::execute_reqwest()
                let f = async move {
                    body.send().await?;
                    rx.await.map_err(|_canceled| event_loop_panicked())
                };
                wait::timeout(f, timeout)

So, it is something people will have to do, and a generic solution would be optimal....

JR

1 Like

It is the reality. It is true that there are some crates this prevents you from using, but that's a consequence of how async/await works. It is not compatible with all other crates.

You can try to use block_in_place, but as I mentioned before, it's a footgun due to its extra non-local requirements.

There are no other possible solutions than what is offered by block_in_place. It is simply theoretically impossible to fix it in any other way besides completely changing how async/await is implemented.

@alice,

OK, I understand...
I all systems, there are compromises...

I love the language and the libraries... So, these are just issues we'll have to find a way around...
Even though we'll have to use the sledge-hammer and fancy-footwork at times..
Perhaps someone will come up with a good generic solution and publish it in a crate...
I like @seanmonstar solution in reqwest, a wait with a timeout...
Too bad it is "pub(crate)"...

Again, I really appreciate the assistance and insight...
JR

The timeout in reqwest is for when people actually want a timeout on their http requests, not as a workaround for people calling the blocking version in async code.

As for your sledge-hammer, that is block_in_place.

I will try the block_in_place....

It is indeed....
There is little difference in my mind between the http timeout and a timeout for a runaway thread.
The goal is to prevent one sub-system / thread from monopolizing the resources.
A timeout is gold if a subsystem is stuck in a poll with a runaway process...
At least the effects are limited..

Philosophically...
All sledge-hammers should have an option of a timeout.

Thanks again..
JR

You can certainly combine block_in_place with tokio::time::timeout if you want. It wont work if you do another block_in_place inside it though.

I appreciate the insight.
We'll see how it goes.
JR

I've made a tiny crate, spin_on, that implements this very simple executor. I added a spin_loop_hint inside the spin loop that might help with performance on systems that support it.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.