3
votes

when running code like this:

use futures::executor;
...
pub fn store_temporary_password(email: &str, password: &str) -> Result<(), Box<dyn Error>> {
  let client = DynamoDbClient::new(Region::ApSoutheast2);
  ...
  let future = client.put_item(input);
  executor::block_on(future)?; <- crashes here
  Ok(())
}

I get the error:

thread '<unnamed>' panicked at 'there is no reactor running, must be called from the context of a Tokio 1.x runtime

My main has the tokio annotation as it should:

#[tokio::main]
async fn main() {
  ...

My cargo.toml looks like:

[dependencies]
...
futures = { version="0", features=["executor"] }
tokio = "1"

My cargo.lock shows that i only have 1 version of both futures and tokio ("1.2.0" and "0.3.12" respectively).

This exhausts the explanations I found elsewhere for this problem. Any ideas? Thanks.

1
You shouldn't block the thread where async operations meant to poll. If you are using async fn main with tokio executor, future.await should be enough, you don't need an additional executor from futures-rs. - Ömer Erden
Thanks, however is the alternative that i have to make every function in my codebase async? If only async functions can 'await' other async functions, it seems i'd have to refactor everything quite radically? Am i missing something? - Chris
@Chris Yes, every function that await's other futures should be async, otherwise you will be performing blocking operations. - Ibraheem Ahmed
Thanks again :) What is the idiomatic way to do this? Do people really end up changing most of their functions to 'async' because one function way down in the callstack wants to make a network call? Honest question, i'm not sure, but it seems heavy handed to me. :) - Chris
@Chris I have to make every function in my codebase async? Sorry I thought I had edited my previous comment, if you are using asyn fn main this means your main thread will be used for polling by tokio executor. you can always create a new thread to handle your non-async context - Ömer Erden

1 Answers

1
votes

You have to enter the tokio runtime context before calling block_on:

let handle = tokio::runtime::Handle::current();
handle.enter();
executor::block_on(future)?;

Note that your code is violating the rule that async functions should never spend a long time without reaching a .await. Ideally, store_temporary_password should be marked as async to avoid blocking the current thread:

pub async fn store_temporary_password(email: &str, password: &str) -> Result<(), Box<dyn Error>> {
  ...
  let future = client.put_item(input);
  executor::block_on(future).await?;
  Ok(())
}

If that is not an option, you should wrap any calls to store_temporary_password in tokio::spawn_blocking to run the blocking operation on a separate threadpool.