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.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.