mirror of
https://github.com/gorules/zen.git
synced 2026-10-05 16:02:40 +00:00
feat: replace v8 with quickjs (#119)
* feat: replace v8 with quickjs * fix format; fix pipelines * fix: workflow * fix: remove incorrect target in node windows pipeline
This commit is contained in:
+37
-30
@@ -33,30 +33,18 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings:
|
||||
- host: windows-latest
|
||||
target: 'x86_64-pc-windows-msvc'
|
||||
build: |
|
||||
yarn lerna exec "yarn build --target x86_64-pc-windows-msvc" --concurrency 1 --stream --no-prefix
|
||||
|
||||
- host: macos-latest
|
||||
target: 'x86_64-apple-darwin'
|
||||
build: |
|
||||
yarn build &&
|
||||
rustup target add x86_64-apple-darwin
|
||||
yarn lerna exec "yarn build --target x86_64-apple-darwin" --concurrency 1 --stream --no-prefix
|
||||
strip -x ./*.node
|
||||
- host: windows-latest
|
||||
build: |
|
||||
yarn build
|
||||
target: 'x86_64-pc-windows-msvc'
|
||||
- host: ubuntu-latest
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
|
||||
build: >-
|
||||
set -e &&
|
||||
rustup target add x86_64-unknown-linux-gnu &&
|
||||
cd bindings/nodejs &&
|
||||
yarn lerna exec "yarn build --target x86_64-unknown-linux-gnu" --concurrency 1 --stream --no-prefix &&
|
||||
strip ./*.node
|
||||
# - host: ubuntu-latest
|
||||
# target: 'x86_64-unknown-linux-musl'
|
||||
# docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
|
||||
# build: |
|
||||
# cd bindings/nodejs &&
|
||||
# yarn build
|
||||
|
||||
- host: macos-latest
|
||||
target: 'aarch64-apple-darwin'
|
||||
build: |
|
||||
@@ -67,22 +55,41 @@ jobs:
|
||||
export CFLAGS="-isysroot $SYSROOT -isystem $SYSROOT";
|
||||
yarn lerna exec "yarn build --target aarch64-apple-darwin" --concurrency 1 --stream --no-prefix
|
||||
strip -x ./*.node
|
||||
|
||||
- host: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
|
||||
build: |
|
||||
set -e
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y patch
|
||||
cd bindings/nodejs
|
||||
rustup target add x86_64-unknown-linux-gnu
|
||||
yarn lerna exec "yarn build --target x86_64-unknown-linux-gnu" --concurrency 1 --stream --no-prefix
|
||||
strip ./*.node
|
||||
|
||||
- host: ubuntu-latest
|
||||
target: 'aarch64-unknown-linux-gnu'
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
|
||||
build: >-
|
||||
rustup target add aarch64-unknown-linux-gnu &&
|
||||
cd bindings/nodejs &&
|
||||
yarn lerna exec "yarn build --target aarch64-unknown-linux-gnu" --concurrency 1 --stream --no-prefix &&
|
||||
build: |
|
||||
set -e
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y patch
|
||||
cd bindings/nodejs
|
||||
rustup target add aarch64-unknown-linux-gnu
|
||||
yarn lerna exec "yarn build --target aarch64-unknown-linux-gnu" --concurrency 1 --stream --no-prefix
|
||||
llvm-strip ./*.node
|
||||
|
||||
# - host: ubuntu-latest
|
||||
# target: 'x86_64-unknown-linux-musl'
|
||||
# build: |
|
||||
# cd bindings/nodejs &&
|
||||
# yarn build
|
||||
# - host: ubuntu-latest
|
||||
# target: 'aarch64-unknown-linux-musl'
|
||||
# downloadTarget: 'aarch64-unknown-linux-musl'
|
||||
# docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
|
||||
# build: >-
|
||||
# set -e &&
|
||||
# rustup target add aarch64-unknown-linux-musl &&
|
||||
# cd bindings/nodejs &&
|
||||
# yarn lerna exec "yarn build --target aarch64-unknown-linux-musl" --concurrency 1 --stream --no-prefix
|
||||
|
||||
name: stable - ${{ matrix.settings.target }} - node@18
|
||||
@@ -163,7 +170,7 @@ jobs:
|
||||
target: 'x86_64-apple-darwin'
|
||||
- host: windows-latest
|
||||
target: 'x86_64-pc-windows-msvc'
|
||||
node: ['14', '16', '18']
|
||||
node: ['16', '18', '20']
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
|
||||
steps:
|
||||
@@ -207,7 +214,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node: ['14', '16', '18']
|
||||
node: ['16', '18', '20']
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
@@ -38,13 +38,17 @@ jobs:
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
uses: stefan-gorules/maturin-action@main
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist --find-interpreter -m bindings/python/Cargo.toml
|
||||
sccache: 'true'
|
||||
manylinux: auto
|
||||
manylinux: 2_28
|
||||
before-script-linux: |
|
||||
apt update
|
||||
apt install -y patch
|
||||
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
const { existsSync, readFileSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
|
||||
@@ -11,7 +17,7 @@ function isMusl() {
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
||||
try {
|
||||
const lddPath = require('child_process').execSync('which ldd').toString().trim();
|
||||
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
||||
return readFileSync(lddPath, 'utf8').includes('musl')
|
||||
} catch (e) {
|
||||
return true
|
||||
@@ -231,6 +237,35 @@ switch (platform) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'riscv64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'zen-engine.linux-riscv64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./zen-engine.linux-riscv64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('@gorules/zen-engine-linux-riscv64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'zen-engine.linux-riscv64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./zen-engine.linux-riscv64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('@gorules/zen-engine-linux-riscv64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ bincode = { workspace = true, optional = true }
|
||||
petgraph = { workspace = true }
|
||||
serde_json = { workspace = true, features = ["arbitrary_precision"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_v8 = { version = "0.162" }
|
||||
once_cell = { workspace = true }
|
||||
fixedbitset = "0.4.2"
|
||||
futures = { workspace = true }
|
||||
v8 = { version = "0.83.1" }
|
||||
rquickjs = { version = "0.4.3", features = ["macro", "loader", "rust-alloc"] }
|
||||
itertools = "0.12.1"
|
||||
zen-expression = { path = "../expression", version = "0.17.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,6 +1,9 @@
|
||||
import 'dayjs';
|
||||
import 'big';
|
||||
|
||||
const log = [];
|
||||
|
||||
const console = {
|
||||
globalThis.console = {
|
||||
log: (...args) => {
|
||||
try {
|
||||
log.push({
|
||||
@@ -16,7 +19,7 @@ const console = {
|
||||
}
|
||||
};
|
||||
|
||||
const main = (input) => JSON.stringify({
|
||||
output: handler(input, {moment: __GLOBAL__DAYJS, dayjs: __GLOBAL__DAYJS, Big: Big, env: __GLOBAL__ENV}),
|
||||
globalThis.main = (input) => JSON.stringify({
|
||||
output: handler(input, {moment: dayjs, dayjs: dayjs, Big: Big, env: {}}),
|
||||
log,
|
||||
});
|
||||
@@ -4,6 +4,7 @@ use crate::loader::DecisionLoader;
|
||||
use crate::model::DecisionNodeKind;
|
||||
use anyhow::{anyhow, Context};
|
||||
use async_recursion::async_recursion;
|
||||
use rquickjs::Runtime;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -11,14 +12,16 @@ pub struct DecisionHandler<T: DecisionLoader> {
|
||||
trace: bool,
|
||||
loader: Arc<T>,
|
||||
max_depth: u8,
|
||||
js_runtime: Option<Runtime>,
|
||||
}
|
||||
|
||||
impl<T: DecisionLoader> DecisionHandler<T> {
|
||||
pub fn new(trace: bool, max_depth: u8, loader: Arc<T>) -> Self {
|
||||
pub fn new(trace: bool, max_depth: u8, loader: Arc<T>, js_runtime: Option<Runtime>) -> Self {
|
||||
Self {
|
||||
trace,
|
||||
loader,
|
||||
max_depth,
|
||||
js_runtime,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +39,8 @@ impl<T: DecisionLoader> DecisionHandler<T> {
|
||||
loader: self.loader.clone(),
|
||||
iteration: request.iteration + 1,
|
||||
trace: self.trace,
|
||||
})?;
|
||||
})?
|
||||
.with_runtime(self.js_runtime.clone());
|
||||
|
||||
let result = sub_tree
|
||||
.evaluate(&request.input)
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
use itertools::Itertools;
|
||||
use rquickjs::{Ctx, Error as QError, FromJs, Type, Value as QValue};
|
||||
use serde_json::{json, Map, Number, Value};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct JsValue(pub(crate) Value);
|
||||
|
||||
impl<'js> FromJs<'js> for JsValue {
|
||||
fn from_js(ctx: &Ctx<'js>, v: QValue<'js>) -> rquickjs::Result<Self> {
|
||||
let computed_value = match v.type_of() {
|
||||
Type::Uninitialized | Type::Undefined | Type::Null => Value::Null,
|
||||
Type::Bool => Value::Bool(
|
||||
v.as_bool()
|
||||
.ok_or_else(|| QError::new_from_js("bool", "bool"))?,
|
||||
),
|
||||
Type::Int => Value::Number(Number::from(
|
||||
v.as_int()
|
||||
.ok_or_else(|| QError::new_from_js("int", "number"))?,
|
||||
)),
|
||||
Type::BigInt => Value::Number(Number::from(
|
||||
v.as_big_int()
|
||||
.map(|b| b.clone().to_i64().ok())
|
||||
.flatten()
|
||||
.ok_or_else(|| QError::new_from_js("bigint", "number"))?,
|
||||
)),
|
||||
Type::Float => Value::Number(
|
||||
v.as_float()
|
||||
.map(|n| Number::from_f64(n))
|
||||
.flatten()
|
||||
.ok_or_else(|| QError::new_from_js("float", "number"))?,
|
||||
),
|
||||
Type::String => Value::String(
|
||||
v.as_string()
|
||||
.map(|s| s.to_string().ok())
|
||||
.flatten()
|
||||
.ok_or_else(|| QError::new_from_js("string", "string"))?,
|
||||
),
|
||||
Type::Array => {
|
||||
let arr = v
|
||||
.as_array()
|
||||
.ok_or_else(|| QError::new_from_js("array", "array"))?;
|
||||
|
||||
let js_arr: Vec<Value> = arr
|
||||
.iter::<QValue>()
|
||||
.map_ok(|n| JsValue::from_js(ctx, n.clone()).map(|js_val| js_val.0))
|
||||
.flatten()
|
||||
.try_collect()
|
||||
.map_err(|_| QError::new_from_js("array", "array"))?;
|
||||
|
||||
Value::Array(js_arr)
|
||||
}
|
||||
Type::Object => {
|
||||
let object = v
|
||||
.as_object()
|
||||
.ok_or_else(|| QError::new_from_js("object", "object"))?;
|
||||
|
||||
let js_object: Map<String, Value> = object
|
||||
.props::<String, QValue>()
|
||||
.map_ok(|(key, value)| {
|
||||
JsValue::from_js(ctx, value.clone()).map(|js_val| (key, js_val.0))
|
||||
})
|
||||
.flatten()
|
||||
.try_collect()
|
||||
.map_err(|_| QError::new_from_js("object", "object"))?;
|
||||
|
||||
Value::Object(js_object)
|
||||
}
|
||||
Type::Exception => {
|
||||
let exception = v
|
||||
.as_exception()
|
||||
.ok_or_else(|| QError::new_from_js("exception", "object"))?;
|
||||
|
||||
let message = exception
|
||||
.message()
|
||||
.ok_or_else(|| QError::new_from_js("exception", "object"))?;
|
||||
let description = exception.to_string();
|
||||
|
||||
json!({ "message": message, "description": description })
|
||||
}
|
||||
Type::Function => json!("[Function]"),
|
||||
Type::Module => json!("[Module]"),
|
||||
Type::Constructor => json!("[Constructor]"),
|
||||
Type::Symbol => json!("[Symbol]"),
|
||||
Type::Unknown => json!("[Unknown]"),
|
||||
};
|
||||
|
||||
Ok(JsValue(computed_value))
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,27 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use std::time::Duration;
|
||||
use rquickjs::Runtime;
|
||||
use serde_json::json;
|
||||
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::handler::function::script::{EvaluateResponse, Script};
|
||||
use crate::handler::function::script::Script;
|
||||
use crate::handler::node::{NodeRequest, NodeResponse, NodeResult};
|
||||
use crate::model::DecisionNodeKind;
|
||||
|
||||
mod js_value;
|
||||
pub(crate) mod runtime;
|
||||
mod script;
|
||||
mod vm;
|
||||
|
||||
async fn evaluate(source: &str, args: &Value) -> anyhow::Result<EvaluateResponse> {
|
||||
let mut script = Script::new().with_timeout(Duration::from_millis(50));
|
||||
script.call(source, args).await
|
||||
}
|
||||
|
||||
pub struct FunctionHandler {
|
||||
trace: bool,
|
||||
runtime: Runtime,
|
||||
}
|
||||
|
||||
static MAX_DURATION: Duration = Duration::from_millis(500);
|
||||
|
||||
impl FunctionHandler {
|
||||
pub fn new(trace: bool) -> Self {
|
||||
Self { trace }
|
||||
pub fn new(trace: bool, runtime: Runtime) -> Self {
|
||||
Self { trace, runtime }
|
||||
}
|
||||
|
||||
pub async fn handle(&self, request: &NodeRequest<'_>) -> NodeResult {
|
||||
@@ -30,11 +30,19 @@ impl FunctionHandler {
|
||||
_ => Err(anyhow!("Unexpected node type")),
|
||||
}?;
|
||||
|
||||
let result = evaluate(content.as_str(), &request.input).await?;
|
||||
let start = Instant::now();
|
||||
let interrupt_handler = Box::new(move || start.elapsed() > MAX_DURATION);
|
||||
self.runtime.set_interrupt_handler(Some(interrupt_handler));
|
||||
|
||||
let mut script = Script::new(self.runtime.clone());
|
||||
let result_response = script.call(content, &request.input).await;
|
||||
|
||||
self.runtime.set_interrupt_handler(None);
|
||||
|
||||
let response = result_response?;
|
||||
Ok(NodeResponse {
|
||||
output: result.output,
|
||||
trace_data: self.trace.then(|| json!({ "log": result.log })),
|
||||
output: response.output,
|
||||
trace_data: self.trace.then(|| json!({ "log": response.log })),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
use anyhow::Context;
|
||||
use rquickjs::loader::Bundle;
|
||||
use rquickjs::{embed, Runtime};
|
||||
|
||||
static JS_BUNDLE: Bundle = embed! {
|
||||
"dayjs": "js/dayjs.js",
|
||||
"big": "js/big.js",
|
||||
"internals": "js/internals.js"
|
||||
};
|
||||
|
||||
pub(crate) fn create_runtime() -> anyhow::Result<Runtime> {
|
||||
let runtime = Runtime::new().context("Failed to create runtime")?;
|
||||
runtime.set_loader(JS_BUNDLE, JS_BUNDLE);
|
||||
|
||||
Ok(runtime)
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
use std::fmt::Debug;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use anyhow::Context as _;
|
||||
use rquickjs::{Context, Ctx, Error as QError, FromJs, Runtime};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::handler::function::vm::BASE_VM;
|
||||
use crate::handler::function::js_value::JsValue;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -16,84 +15,47 @@ pub struct EvaluateResponse {
|
||||
}
|
||||
|
||||
pub struct Script {
|
||||
isolate: v8::OwnedIsolate,
|
||||
timeout: Option<Duration>,
|
||||
runtime: Runtime,
|
||||
}
|
||||
|
||||
impl Script {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
isolate: v8::Isolate::new(
|
||||
v8::CreateParams::default().snapshot_blob(BASE_VM.as_slice()),
|
||||
),
|
||||
timeout: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_timeout(mut self, timeout: Duration) -> Self {
|
||||
assert!(timeout > Duration::ZERO);
|
||||
|
||||
self.timeout = Some(timeout);
|
||||
self
|
||||
pub fn new(runtime: Runtime) -> Self {
|
||||
Self { runtime }
|
||||
}
|
||||
|
||||
pub async fn call<P>(&mut self, source: &str, args: &P) -> anyhow::Result<EvaluateResponse>
|
||||
where
|
||||
P: Serialize,
|
||||
{
|
||||
let handle = self.isolate.thread_safe_handle();
|
||||
let runtime = &self.runtime;
|
||||
let context = Context::full(&runtime).context("Failed to create context")?;
|
||||
|
||||
let args_str =
|
||||
serde_json::to_string(args).context("Failed to serialize function arguments")?;
|
||||
|
||||
let js_code_source = format!("const now = Date.now(); main({});", args_str);
|
||||
let json_response = context.with(|ctx| -> anyhow::Result<String> {
|
||||
let _ = ctx
|
||||
.clone()
|
||||
.compile("main", "import 'internals'; globalThis.now = Date.now();")
|
||||
.map_err(|e| map_js_error(&ctx, e))?;
|
||||
|
||||
let handle_scope = &mut v8::HandleScope::new(&mut self.isolate);
|
||||
let context = v8::Context::new(handle_scope);
|
||||
let scope = &mut v8::ContextScope::new(handle_scope, context);
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
let _ = ctx
|
||||
.globals()
|
||||
.set("log", Vec::<isize>::new())
|
||||
.map_err(|e| map_js_error(&ctx, e))?;
|
||||
|
||||
let src = v8::String::new(tc_scope, source).context("Failed to compile source code")?;
|
||||
ctx.eval::<String, _>(format!("{source};main({args_str})"))
|
||||
.map_err(|e| map_js_error(&ctx, e))
|
||||
})?;
|
||||
|
||||
let js_src = v8::String::new(tc_scope, js_code_source.as_str())
|
||||
.context("Failed to compile source code")?;
|
||||
|
||||
if let Some(timeout) = self.timeout {
|
||||
thread::spawn(move || {
|
||||
thread::sleep(timeout);
|
||||
handle.terminate_execution();
|
||||
});
|
||||
}
|
||||
|
||||
let Some(src_script) = v8::Script::compile(tc_scope, src, None) else {
|
||||
let exception = tc_scope.exception().context("Failed to load script")?;
|
||||
return Err(anyhow!(exception.to_rust_string_lossy(tc_scope)));
|
||||
};
|
||||
|
||||
if let None = src_script.run(tc_scope) {
|
||||
let exception = tc_scope.exception().unwrap();
|
||||
return Err(anyhow!(exception.to_rust_string_lossy(tc_scope)));
|
||||
}
|
||||
|
||||
let Some(js_script) = v8::Script::compile(tc_scope, js_src, None) else {
|
||||
let exception = tc_scope.exception().context("Failed to load script")?;
|
||||
return Err(anyhow!(exception.to_rust_string_lossy(tc_scope)));
|
||||
};
|
||||
|
||||
let Some(result) = js_script.run(tc_scope) else {
|
||||
if tc_scope.has_terminated() {
|
||||
return Err(anyhow!("Timeout exceeded"));
|
||||
}
|
||||
|
||||
let exception = tc_scope
|
||||
.exception()
|
||||
.context("Failed to run loaded script")?;
|
||||
return Err(anyhow!(exception.to_rust_string_lossy(tc_scope)));
|
||||
};
|
||||
|
||||
let result_string: String =
|
||||
serde_v8::from_v8(tc_scope, result).context("Failed to parse function result")?;
|
||||
|
||||
serde_json::from_str(result_string.as_str()).context("Failed to parse function result")
|
||||
serde_json::from_str(json_response.as_str()).context("Failed to parse function result")
|
||||
}
|
||||
}
|
||||
|
||||
fn map_js_error(ctx: &Ctx, e: QError) -> anyhow::Error {
|
||||
let error = JsValue::from_js(&ctx, ctx.catch())
|
||||
.map(|v| v.0)
|
||||
.unwrap_or(Value::String(e.to_string()));
|
||||
|
||||
anyhow::Error::msg(error.to_string())
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,61 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use v8::FunctionCodeHandling;
|
||||
|
||||
const EXPOSED_PREFIX: &'static str = "ZEN_EXPOSED_";
|
||||
|
||||
pub static BASE_VM: Lazy<Vec<u8>> = Lazy::new(|| {
|
||||
let platform = v8::new_default_platform(0, false).make_shared();
|
||||
v8::V8::initialize_platform(platform);
|
||||
v8::V8::initialize();
|
||||
|
||||
let env_src_string = {
|
||||
let mut env_vars = HashMap::new();
|
||||
env::vars().for_each(|(key, value)| {
|
||||
if let Some(mod_key) = key.strip_prefix(EXPOSED_PREFIX) {
|
||||
env_vars.insert(mod_key.to_string(), value);
|
||||
}
|
||||
});
|
||||
|
||||
format!(
|
||||
"const __GLOBAL__ENV = {}",
|
||||
serde_json::to_string(&env_vars).unwrap()
|
||||
)
|
||||
};
|
||||
|
||||
let mut isolate = v8::Isolate::snapshot_creator(Default::default());
|
||||
|
||||
{
|
||||
let handle_scope = &mut v8::HandleScope::new(&mut isolate);
|
||||
let context = v8::Context::new(handle_scope);
|
||||
|
||||
let scope = &mut v8::ContextScope::new(handle_scope, context);
|
||||
|
||||
let dayjs_src = v8::String::new(scope, include_str!("scripts/dayjs.js")).unwrap();
|
||||
let bigjs_src = v8::String::new(scope, include_str!("scripts/big.js")).unwrap();
|
||||
let internal_src = v8::String::new(scope, include_str!("scripts/internals.js")).unwrap();
|
||||
let env_src = v8::String::new(scope, env_src_string.as_str()).unwrap();
|
||||
|
||||
v8::Script::compile(scope, dayjs_src, None)
|
||||
.unwrap()
|
||||
.run(scope);
|
||||
v8::Script::compile(scope, bigjs_src, None)
|
||||
.unwrap()
|
||||
.run(scope);
|
||||
v8::Script::compile(scope, internal_src, None)
|
||||
.unwrap()
|
||||
.run(scope);
|
||||
v8::Script::compile(scope, env_src, None)
|
||||
.unwrap()
|
||||
.run(scope);
|
||||
|
||||
scope.set_default_context(context);
|
||||
}
|
||||
|
||||
isolate
|
||||
.create_blob(FunctionCodeHandling::Keep)
|
||||
.unwrap()
|
||||
.to_vec()
|
||||
});
|
||||
@@ -4,6 +4,7 @@ use std::time::Instant;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use petgraph::algo::is_cyclic_directed;
|
||||
use rquickjs::Runtime;
|
||||
use serde::ser::SerializeMap;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use serde_json::Value;
|
||||
@@ -11,6 +12,7 @@ use thiserror::Error;
|
||||
|
||||
use crate::handler::decision::DecisionHandler;
|
||||
use crate::handler::expression::ExpressionHandler;
|
||||
use crate::handler::function::runtime::create_runtime;
|
||||
use crate::handler::function::FunctionHandler;
|
||||
use crate::handler::node::NodeRequest;
|
||||
use crate::handler::table::zen::DecisionTableHandler;
|
||||
@@ -25,6 +27,7 @@ pub struct DecisionGraph<'a, L: DecisionLoader> {
|
||||
trace: bool,
|
||||
max_depth: u8,
|
||||
iteration: u8,
|
||||
runtime: Option<Runtime>,
|
||||
}
|
||||
|
||||
pub struct DecisionGraphConfig<'a, T: DecisionLoader> {
|
||||
@@ -68,9 +71,26 @@ impl<'a, L: DecisionLoader> DecisionGraph<'a, L> {
|
||||
trace: config.trace,
|
||||
loader: config.loader.clone(),
|
||||
max_depth: config.max_depth,
|
||||
runtime: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn with_runtime(mut self, runtime: Option<Runtime>) -> Self {
|
||||
self.runtime = runtime;
|
||||
self
|
||||
}
|
||||
|
||||
fn get_or_insert_runtime(&mut self) -> anyhow::Result<Runtime> {
|
||||
if let Some(runtime) = &self.runtime {
|
||||
return Ok(runtime.clone());
|
||||
}
|
||||
|
||||
let runtime = create_runtime()?;
|
||||
self.runtime.replace(runtime.clone());
|
||||
|
||||
Ok(runtime)
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), DecisionGraphValidationError> {
|
||||
let input_count = self.node_kind_count(DecisionNodeKind::InputNode);
|
||||
if input_count != 1 {
|
||||
@@ -180,7 +200,12 @@ impl<'a, L: DecisionLoader> DecisionGraph<'a, L> {
|
||||
});
|
||||
}
|
||||
DecisionNodeKind::FunctionNode { .. } => {
|
||||
let res = FunctionHandler::new(self.trace)
|
||||
let runtime = self.get_or_insert_runtime().map_err(|e| NodeError {
|
||||
source: e.into(),
|
||||
node_id: node.id.clone(),
|
||||
})?;
|
||||
|
||||
let res = FunctionHandler::new(self.trace, runtime)
|
||||
.handle(&node_request)
|
||||
.await
|
||||
.map_err(|e| NodeError {
|
||||
@@ -199,13 +224,18 @@ impl<'a, L: DecisionLoader> DecisionGraph<'a, L> {
|
||||
});
|
||||
}
|
||||
DecisionNodeKind::DecisionNode { .. } => {
|
||||
let res = DecisionHandler::new(self.trace, self.max_depth, self.loader.clone())
|
||||
.handle(&node_request)
|
||||
.await
|
||||
.map_err(|e| NodeError {
|
||||
source: e.into(),
|
||||
node_id: node.id.to_string(),
|
||||
})?;
|
||||
let res = DecisionHandler::new(
|
||||
self.trace,
|
||||
self.max_depth,
|
||||
self.loader.clone(),
|
||||
self.runtime.clone(),
|
||||
)
|
||||
.handle(&node_request)
|
||||
.await
|
||||
.map_err(|e| NodeError {
|
||||
source: e.into(),
|
||||
node_id: node.id.to_string(),
|
||||
})?;
|
||||
|
||||
walker.set_node_data(nid, res.output.clone());
|
||||
trace!({
|
||||
|
||||
@@ -101,7 +101,7 @@ async fn engine_errors() {
|
||||
match infinite_fn.unwrap_err().deref() {
|
||||
EvaluationError::NodeError(e) => {
|
||||
assert_eq!(e.node_id, "e0fd96d0-44dc-4f0e-b825-06e56b442d78");
|
||||
assert_eq!(e.source.to_string(), "Timeout exceeded");
|
||||
assert!(e.source.to_string().contains("interrupted"));
|
||||
}
|
||||
_ => assert!(false, "Wrong error type"),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user