5

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

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Techradar
  • 3,506
  • 3
  • 18
  • 28
  • 1
    Please provide a better [mcve]: One should preferably copy the error message from the compiler _verbatim_. Add the necessary `use` statements for the program to compile. Knowing the specific versions of all dependencies, or at least actix-web and reqwest may be relevant. Whatever is in `[...]` is either important or should be removed. The [Rust tag page](https://stackoverflow.com/tags/rust/info) provides a few more tips. – E_net4 Jun 17 '19 at 15:43
  • 1
    @SirE_net4theDownvoter please check again. The example has been added now and all additional details which are relevant. Unfortunately rust playground does not seem to offer `cargo.toml` crates otherwise I would have added that as well but instead added a link to a github repo. – Techradar Jun 20 '19 at 07:15

2 Answers2

2

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.

Techradar
  • 3,506
  • 3
  • 18
  • 28
2

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.

fstephany
  • 2,254
  • 3
  • 25
  • 32