How to make a blocking call from a warp function?

I'm playing with Warp and Rust and I have my webserver running so that it will accept REST API calls, however once I get the call I need to call a seperate webserver, which is a blocking operations, and get this error:
thread 'tokio-runtime-worker' panicked at 'Cannot drop a runtime in a context where blocking is not allowed. This happens when a runtime is dropped from within an asynchronous context.', C:\Users\Owner.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-0.2.22\src\runtime\blocking\shutdown.rs:49:21

I could make that call asynchronously but then I don't see a way to return the results from the warp web server.

Code found here, it's a challenge problem for an interview:

The error is being thrown after I added the following code to the 'list_users' function in handlers.rs line 19:

let mut res = reqwest::blocking::get("https://jsonplaceholder.typicode.com/users").unwrap();
let mut body = String::new();

res.read_to_string(&mut body);

//temporarily printing to string
println!("Body:\n{}",body);

I suppose I could work around this, something like this returns a token, then makes polling calls using that token to see if it's result is done. Seems like there should be a way to do this without using something like that such as telling warp that the API will block.

Thanks!

Warp is an asynchronous web framework. Making blocking IO within the warp handler only hurts both usability and performance. It's Fileter::and_then() to return the results from the warp handler.

Thanks Hyeonu, I'm sure you're right but what is the proper way to accomplish this if I want to return info from a GET request from the webserver but that GET request requires either a blocking or Asynchronous call itself before it has the data it can return? Can I make async calls from within a warp handler? I don't think async works that way as warp handler still needs to either block or return something immediately, but maybe I'm misunderstanding async.

Yes, using filter::and_then(), and getting that error.

Since you are using reqwest, you should use its async API rather than reqwest::blocking when combining it with and_then.

The answer to the original question is to wrap it in spawn_blocking, but this is not the recommended approach in this case.

4 Likes

Thank you Alice, using async did it for me.

Makes me curious about how async and await work.. I thought that await would also have to block until ready. Do you know why await is ok, blocking is not?

Async/await is a tool that lets you run many operations at the same time on a single (or a few) threads. It does this by alternating between the currently running task very rapidly. When you use .await, it is still able to pause your task in the middle of the request, whereas with block_on it is unable to swap to another task until the full web request has finished.

Preventing the task from being swapped out like this is called blocking, and is bad because other tasks running on the same runtime will not be able to run in the mean-time.

1 Like

The thing to realize is that when you write async code there are no actual threads involved (See note below).

What you have is "cooperative multi-threading". If your endless loop never "suspends", or "yields" control somehow, so that other endless loops get a turn, it is hanging up the whole system and nothing else can run.

With async your code is not compiled into simple machine instructions that do what you want in order as you wrote it. Like a C compiler, for example. Rather it is compiled into a state-machine that steps through various states, one by one, when the executor calls it.

So ".await" is the suspend/yield point. It returns control to the executor so that it can then run some other task. The executor will call your state machine again when whatever it is waiting on becomes available.

But if you make a blocking call then control is stuck waiting on that call, control never returns to the executor, so it cannot run anything else. The system as a whole stalls.

The note below:

  1. It's probably better to not think of async "threads". They are not threads as scheduled by the operating system. Rather they are cooperative "tasks", scheduled by the executor. In fact that is what tokio calls them.

  2. Systems like tokio do actually spread their "tasks" over one or more real "threads", so as to make use of any other cores you have on your machine. I think it's better not to think about that, conceptually the whole async thing runs on one thread on one core.

  3. If you want a far better explanation of all this than I can ever give the watch this:
    Steve Klabni: "The Talk You've Been Await-ing for": https://www.youtube.com/watch?v=NNwK5ZPAJCk&t=12s

1 Like