Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTTP request inside actix-web handler -> Multiple executors at once: EnterError

When creating a hyper post request inside an actix-web resolver, the following error is thrown - how can one send one a http request by spawning the request into the existing executor?

thread 'actix-rt:worker:1' panicked at 'Multiple executors at once: EnterError { reason: "attempted to run an executor while another executor is already running" }', src/libcore/result.rs:999:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Panic in Arbiter thread, shutting down system.

main.rs

extern crate actix_web;
extern crate serde_json;
extern crate actix_rt;
extern crate hyper;

use serde_json::{Value, json};
use hyper::{Client, Uri, Body, Request};
use actix_web::{middleware, web, App, HttpResponse, HttpServer};
use actix_rt::System;
use actix_web::client;
use futures::future::{Future, lazy};

fn main() {
    println!("Start server...");
    listen();
}

pub fn listen() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(middleware::Logger::default())
            .data(web::JsonConfig::default().limit(4096))
            .service(web::resource("/push").route(web::post().to(index)))
            .service(web::resource("/test").route(web::post().to(test)))
    })
    .bind("127.0.0.1:8080")?
    .run()
}


fn index(item: web::Json<Value>) -> HttpResponse {
    println!("model: {:?}", &item);
    send(json!({
        "hello": "world"
    }));

    HttpResponse::Ok().json(item.0) // <- send response
}

fn test(item: web::Json<Value>) -> HttpResponse {
    println!("recevied test call!");
    println!("{:?}", &item);

    HttpResponse::Ok().json(item.0) // <- send response
}



pub fn send(mut data: serde_json::Value) {
    println!("# Start running log post future...");

    // if the following line is removed, the call is not received by the test function above
    System::new("test").block_on(lazy(|| {
        let req = Request::builder()
            .method("POST")
            .uri("http://localhost:8080/test")
            .body(Body::from(data.to_string()))
            .expect("request builder");

        let client = Client::new();
        let future = client.request(req)
        .and_then(|res| {
            println!("status: {}", res.status());
            Ok(())
        })
        .map_err(|err| {
            println!("error: {}", err);
        });
        return future;
    }));

    println!("# Finish running log post future")
}

cargo.toml

[package]
name = "rust-tokio-event-loop-madness"
version = "0.1.0"
authors = [""]
edition = "2018"

[dependencies]
serde_json = "1.0.39"
actix-web = "1.0.0"
serde_derive = "1.0.92"
actix-rt = "*"
hyper = "0.12.30"
futures = "*"

curl command to trigger error:

curl -X POST -H 'Content-Type: application/json' -d '{"test":1}' http://localhost:8080/push

Repo with example: https://github.com/fabifrank/rust-tokio-event-loop-madness

like image 982
Techradar Avatar asked Jun 17 '19 15:06

Techradar


2 Answers

Got it working by using the tokio function spawn to add the future to the running executor of tokio.

So instead of:

System::new("test").block_on(lazy(|| {

use:

spawn(lazy(move || {

and of course add tokio as dependency in cargo.toml and include the crate.

like image 114
Techradar Avatar answered Oct 09 '22 16:10

Techradar


This is because actix-web uses Tokio since version 1.0.0. As Reqwest does it as well, you end up with two runtimes.

One of the best way to handle this is to switch to the async version of both your handler and the reqwest request. The process can be a bit involved but is worth it in the long run. This article does a good job at explaining the transition.

like image 2
fstephany Avatar answered Oct 09 '22 16:10

fstephany