7

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.

Chris
  • 39,719
  • 45
  • 189
  • 235
  • 1
    [You shouldn't block the thread where async operations meant to poll](https://stackoverflow.com/questions/48735952/why-does-futureselect-choose-the-future-with-a-longer-sleep-period-first). 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 Feb 23 '21 at 09:02
  • 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 Feb 23 '21 at 23:06
  • 1
    @Chris Yes, every function that await's other futures should be async, otherwise you will be performing blocking operations. – Ibraheem Ahmed Feb 23 '21 at 23:12
  • 2
    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 Feb 23 '21 at 23:52
  • 1
    @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 Feb 24 '21 at 07:00

1 Answers1

6

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);
  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.

Ibraheem Ahmed
  • 11,652
  • 2
  • 48
  • 54
  • Thankyou so much :) I understand that it's not ideal to block, however if i make store_temporary_password async, then do i also have to make its callers async, and their callers async too, and so on and so forth? – Chris Feb 23 '21 at 23:24
  • 1
    @Chris Yeah, that's pretty much how it works. The compiler turns `async` functions into a really efficient state machine, and `.await` points become yield points so that the executor (tokio) can go work on other things instead of blocking. You have to mark your functions as `async` to take advantage of this. – Ibraheem Ahmed Feb 23 '21 at 23:30
  • 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 Feb 23 '21 at 23:41
  • @Chris Well, you are using tokio, so you kind of have to change everything. You could just not use tokio and just use `futures::executor::block_on` everything if you don't really want/need async. – Ibraheem Ahmed Feb 24 '21 at 02:17
  • Thanks! Unfortunately rusoto uses tokio so there's no way around it, there's no other AWS sdk. But otherwise the rest of the codebase isn't async, because it's an aws lambda, there's not much need. I might just make it all async to be honest. – Chris Feb 24 '21 at 02:20
  • 1
    [It seems like Rusoto does not require tokio](https://github.com/rusoto/rusoto/issues/1746), so you could just `block_on` instead of using tokio. – Ibraheem Ahmed Feb 24 '21 at 02:35