Using actix actor framework with reqwest

Hi All,

I've been struggling with this problem for a while now. The problem can be illustrated in code below. I have a much larger project that uses the actix actor framework, some of the actors make web requests using the reqwest crate. Recently i updated the project dependencies and i now get the following runtime error: thread 'main' 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.'

Even though reqwest is blocking via reqwest::Blocking, its still just using async in the background (reqwest::blocking - Rust) and request::Blocking cannot be used within an async context. Given async fn main() its in an async context, but fn handle() is not async, so i'm forced to use reqwest::Blocking, but that panics (as the docs say it will). ... It appears that there is a way to call async code within the fn handle() function by using Box::pin( ), but that method doesn't appear to allow for any return data .

I'm at a loss on how to proceed. Should i abandon actix, or reqwest (both play a large part in the project)? Is there something more fundamental that i'm missing?

Thank you for you insights.

use actix::prelude::*;

// this is our Message
#[derive(Message)]
#[rtype(result = "reqwest::Result<String>")]
struct HttpPing(String);

// Actor definition
struct HttpPinger;

impl Actor for HttpPinger {
    type Context = Context<Self>;
}

impl Handler<HttpPing> for HttpPinger {
    type Result = reqwest::Result<String>; // <- Message response type

    fn handle(&mut self, msg: HttpPing, _ctx: &mut Context<Self>) -> Self::Result {
        let client = reqwest::blocking::Client::builder().build()?;
        let body = client.get(msg.0).send()?.text()?;

        //  use async reqwest? is that possible, maybe w/ tokio::spawn()?
        /*
        let body = tokio::spawn(async move {
            let client = reqwest::Client::builder().build().unwrap();
            let body = client.get(msg.0).send().await.unwrap().text().await.unwrap();
            body
        });
        */

        Ok(body)
    }
}

#[actix::main] 
async fn main() {
    let addr = HttpPinger.start();
    let res = addr.send(HttpPing("https://www.rust-lang.org".to_string())).await;
    match res {
        Ok(Ok(result)) => println!("body: {}", result),
        _ => println!("Actor has failed"),
    }
}

As far as I remember, there's some pretty complicated way of accomplishing it, but I generally find that the various actor crates a simply bad. I wrote this article due to similar frustrations, which explains how to do this using Tokio but without a dedicated actor library.

1 Like

Thank you very much. I admit that my use of actix is fairly pedestrian and i've considered rolling my own in the past, but it was fairly/very convenient and it did function just fine until recently. Your article makes the rolling-own experience approachable since you layed it all out for me.

Many thanks for your response it is greatly appreciated.