Accessing the current tokio runtime to call block_on

Hi

I have a web service using Warp where I have #[tokio::main] on my main function.

I have some authentication code which expects an object that implements a trait defined as

pub trait UserAuthStore: fmt::Debug + Send + Sync {
    fn get_password_salt(&self, username: &str) -> HaystackResult<String>;
    fn get_salted_password(&self, username: &str) -> HaystackResult<String>;
}

In my implementation of this trait I use postgres-tokio to get the salt values.
I can't call .await on my postgres-tokio call as the trait is not marked as async and not seem to be allowed.

So I want to try to block with something like

let future = conn.get_salts_for_haystack_user(username);   
let mut rt = Runtime::new().unwrap();
let result = rt.block_on(future);

As this gets called from a warp filter though I get the "Cannot start a runtime from within a runtime" as I would expect. Is there a way to access the current tokio runtime to use ?

If not was would be the recommended way to solve this.
I have to use warp and tokio-postgres as they are used throughout the project.

Thanks

It looks like you probably want tokio::runtime::Handle::try_current(). There may be a better way to accomplish your overall goal, but I’ll have to defer to others more familiar with tokio than I am.

Even if you use a Handle to get the current runtime, it will still fail. There is simply no correct way whatsoever to call block_on from within an async function.

Refactor your code such that you can use .await instead of block_on. You may want to check out the async-trait crate.

2 Likes

Yes, I'm thinking if you have warp running with tokio and it wants to use data from tokio-postgres then your whole system is asyc already and there is no reason for a blocking call anywhere.

Looks like something you have introduced between warp and tokio-postgres that is causing a problem you should not have.

Though it’s generally best to expose an async API when possible, what’s the best way to use some async code to implement a library with a sync API (that might be used downstream in an async context); something like this?

let future_result = match tokio::runtime::Handle::try_current() {
    Err(_) => tokio::runtime::new().block_on( the_future ),
    Ok(handle) => {
        task::block_in_place(move || {
            crossbeam::scope(move |scope| {
                scope.spawn(move || {
                    handle.block_on( the_future )
                })
            })
        })
    }
};

(I’m imagining a scenario where a library is trying to migrate from sync to async without a breaking change for current users)

You should not try to write code that detects whether it's in an async context or not. If your API is blocking, your function should simply not be called directly from async code. Instead, you should use a spawn_blocking layer when transitioning from async to sync code, inside which blocking is perfectly fine.

Avoid block_in_place like the plague.

1 Like

This assumes that you have control of how the library is called. While conceding the point that it’s something that users shouldn’t do, I want to take into accout that it’s something they might have done— how can a library maintainer keep backwards compatibility (even for dumb user choices) while using async internally?

Presumably, there would also be a proper async API exposed that is the preferred choice when users have an async environment available.

You mean if users called a blocking function from async code? There is no good solution. The block_in_place function will panic if used on a single-threaded runtime, so that's also a breaking change.

Even worse, the Handle::try_current call will succeed when used inside spawn_blocking. So you would be making it worse for users who are doing it correctly.

1 Like

So the options are:

  • Maintain separate sync and async implementations
  • Have the async API dispatch to the sync API running on a background thread worker
  • Have the sync API dispatch to async via block_on (breaking change)
  • Have the sync API dispatch to block_on in a worker thread that doesn’t have an active tokio runtime (breaking change if Send / Sync bounds must be added)
  • Use a bespoke executor in the sync API that doesn’t care whether or not tokio is active

In principle, the crossbeam::scope thing will "work" if you remove the block_in_place call.

1 Like