diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index 9d914f5381..9e4d7669cf 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -6,8 +6,6 @@ use anyhow::Context; use base64::Engine; use itertools::Itertools; -#[cfg(not(feature = "deno_core"))] -use serde_json::value::to_raw_value; use serde_json::value::RawValue; use sha2::Digest; @@ -1304,7 +1302,7 @@ try {{ tracing::error!( r#""deno_core" feature is not activated, but "//native" annotation used. Returning empty value..."# ); - return Ok(to_raw_value("").unwrap()); + return Err(error::Error::internal_err("deno_core feature is not activated, but //native annotation used. Returning empty value...".to_string())); } #[cfg(feature = "deno_core")] diff --git a/backend/windmill-worker/src/js_eval.rs b/backend/windmill-worker/src/js_eval.rs index 482bfea3ed..9aab144296 100644 --- a/backend/windmill-worker/src/js_eval.rs +++ b/backend/windmill-worker/src/js_eval.rs @@ -42,6 +42,8 @@ use uuid::Uuid; #[cfg(feature = "deno_core")] use windmill_common::error::Error; +#[cfg(feature = "deno_core")] +use windmill_common::worker::{write_file, TMP_DIR}; use windmill_common::{flow_status::JobResult, DB}; use windmill_queue::CanceledBy; @@ -885,7 +887,7 @@ pub async fn eval_fetch_timeout( let future = async { let r = tokio::select! { - r = eval_fetch(&mut js_runtime, &js_expr, Some(env_code), load_client) => Ok(r), + r = eval_fetch(&mut js_runtime, &js_expr, Some(env_code), load_client, &job_id) => Ok(r), _ = memory_limit_rx.recv() => Err(Error::ExecutionErr("Memory limit reached, killing isolate".to_string())) }; @@ -934,12 +936,44 @@ pub async fn eval_fetch_timeout( #[cfg(feature = "deno_core")] const WINDMILL_CLIENT: &str = include_str!("./windmill-client.js"); +#[cfg(feature = "deno_core")] +const ERROR_DIR: &str = const_format::concatcp!(TMP_DIR, "/native_errors"); + +#[cfg(feature = "deno_core")] +fn write_error_expr(expr: &str, uuid: &Uuid) { + if let Err(e) = std::fs::create_dir_all(ERROR_DIR) { + tracing::error!("failed to create error dir {ERROR_DIR}: {e}"); + return; + } + let dir_entries = match std::fs::read_dir(ERROR_DIR) { + Ok(entries) => entries.count(), + Err(_) => { + tracing::error!("failed to read error dir {ERROR_DIR}"); + return; + } + }; + + if dir_entries >= 100 { + tracing::info!("Too many error files in {ERROR_DIR}, skipping write"); + return; + } + + let path = format!("/{uuid}.js"); + tracing::info!( + "nativets job {uuid} failed, writing error expr to {ERROR_DIR}/{path} for debugging: {path}" + ); + if let Err(e) = write_file(ERROR_DIR, &path, expr) { + tracing::error!("failed to write error expr to file {path}: {e}"); + } +} + #[cfg(feature = "deno_core")] async fn eval_fetch( js_runtime: &mut JsRuntime, expr: &str, env_code: Option, load_client: bool, + job_id: &Uuid, ) -> anyhow::Result> { if load_client { if let Some(env_code) = env_code.as_ref() { @@ -951,26 +985,42 @@ async fn eval_fetch( .await?; } } - + use anyhow::Context; let _ = js_runtime .load_side_es_module_from_code( &deno_core::resolve_url("file:///eval.ts")?, format!("{}\n{expr}", env_code.unwrap_or_default()), ) - .await?; + .await + .map_err(|e| { + write_error_expr(expr, &job_id); + e + }) + .context("failed to load module")?; - let script = js_runtime.execute_script( - "", - r#" + let script = js_runtime + .execute_script( + "", + r#" let args = Deno.core.ops.op_get_static_args().map(JSON.parse) import("file:///eval.ts").then((module) => module.main(...args)).then(JSON.stringify) "#, - )?; + ) + .map_err(|e| { + write_error_expr(expr, &job_id); + e + }) + .context("native script initialization")?; let fut = js_runtime.resolve(script); let global = js_runtime .with_event_loop_promise(fut, PollEventLoopOptions::default()) - .await?; + .await + .map_err(|e| { + write_error_expr(expr, &job_id); + e + }) + .context("native script event loop")?; let scope = &mut js_runtime.handle_scope(); let local = v8::Local::new(scope, global);