gorules / zen

Open-source Business Rules Engine for your Rust, NodeJS, Python or Go applications.
https://gorules.io
MIT License
770 stars 74 forks source link

Issue evaluating inside an async fn with + Send #190

Closed iman38 closed 2 months ago

iman38 commented 3 months ago

Hi @stefan-gorules ,

I found that I cannot evaluate inside an async fn having + Send trait (for thread safety). Below is a simple snippet to reproduce the issue. It's complaining about QuickJS runtime. I tried forking and changing the runtime (and context) to AsyncRuntime & AsyncContext, but found the problem on bumpalo as well. I would like to know if I miss anything I cannot find on the doc to do this properly, or if this is not supported yet.

p.s. please ignore the fact that the JSON file is not attached. However, this should reproduce a compile error.

use async_trait::async_trait;
use serde_json::json;
use std::env;
use std::fs::File;
use std::io::BufReader;
use welfares_common::AirlineError;
use zen_engine::model::DecisionContent;
use zen_engine::DecisionEngine;

#[async_trait]
pub trait AirlineConnector {
    async fn search(&self) -> Result<bool, AirlineError>;
}

pub struct TestController {
    pub id: String,
}

impl TestController {
    pub fn new(id: &str) -> TestController {
        TestController { id: id.to_string() }
    }
}
#[async_trait]
impl AirlineConnector for TestController {
    async fn search(&self) -> Result<bool, AirlineError> {
        let wf_env = env::var("WF_ENV").expect("Environment variable WF_ENV not found");
        let file = File::open(format!("config_{}/test_rule.json", wf_env))
            .expect("file commission_rule.json should be able to be open read only");
        let reader = BufReader::new(file);

        let search_decision_content: DecisionContent = serde_json::from_reader(reader).unwrap();
        let engine = DecisionEngine::default();
        let comm_decision = engine.create_decision(search_decision_content.into());
        let commission_param = json!({});
        let result = comm_decision.evaluate(&commission_param).await;
        Ok(true)
    }
}

#[tokio::main]
async fn main() {
    let c = TestController::new("new controller");
    c.search().await.expect("TODO: panic message");
}

Some errors I saw:

error[E0277]: `Rc<rquickjs_core::safe_ref::Mut<rquickjs_core::runtime::raw::RawRuntime>>` cannot be sent between threads safely
   --> src/bin/ts_zen.rs:26:58
    |
26  |       async fn search(&self) -> Result<bool, AirlineError> {
    |  __________________________________________________________^
27  | |         let wf_env = env::var("WF_ENV").expect("Environment variable WF_ENV not found");
28  | |         let file = File::open(format!("config_{}/test_rule.json", wf_env))
29  | |             .expect("file commission_rule.json should be able to be open read only");
...   |
38  | |         Ok(true)
39  | |     }
    | |     ^
    | |     |
    | |_____`Rc<rquickjs_core::safe_ref::Mut<rquickjs_core::runtime::raw::RawRuntime>>` cannot be sent between threads safely
    |       within this `{async block@src/bin/ts_zen.rs:26:58: 39:6}`
    |
    = help: within `{async block@src/bin/ts_zen.rs:26:58: 39:6}`, the trait `Send` is not implemented for `Rc<rquickjs_core::safe_ref::Mut<rquickjs_core::runtime::raw::RawRuntime>>`, which is required by `{async block@src/bin/ts_zen.rs:26:58: 39:6}: Send`
note: required because it appears within the type `rquickjs_core::runtime::base::Runtime`

mut bumpalo::Bumpcannot be sent between threads safely [E0277] Help: within{async block@src/bin/ts_zen.rs:26:58: 38:6}, the traitstd::marker::Sendis not implemented formut bumpalo::Bump, which is required by{async block@src/bin/ts_zen.rs:26:58: 38:6}: std::marker::SendHelp: the traitstd::marker::Sendis implemented forbumpalo::BumpNote: required because it appears within the typezenexpression::arena::UnsafeArena<'>Note: required because it appears within the typezenexpression::isolate::Isolate<'>Note: required because it appears within the typezenengine::handler::table::zen::DecisionTableHandler<'>Note: required because it's used within thisasyncfn body Note: required because it's used within thisasyncfn body Note: required because it's used within thisasyncfn body Note: required because it's used within thisasyncblock Note: required for the cast fromstd::pin::Pin<std::boxed::Box<{async block@src/bin/ts_zen.rs:26:58: 38:6}>>toPin<Box<dyn Future<Output = Result<bool, AirlineError>> + Send>>`

stefan-gorules commented 3 months ago

Hi @iman38, you are getting error above because it's not safe for async to be running across multiple threads. You need to use a function such as block_in_place. See #112.

stefan-gorules commented 2 months ago

@iman38 Closing issue for now, feel free to re-open if problem still persists.