improve debuggability of failed native jobs

This commit is contained in:
Ruben Fiszel
2025-02-05 18:31:20 +01:00
parent 1e5cd282d7
commit e7e751c3bf
2 changed files with 59 additions and 11 deletions

View File

@@ -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")]

View File

@@ -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<String>,
load_client: bool,
job_id: &Uuid,
) -> anyhow::Result<Box<RawValue>> {
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(
"<anon>",
r#"
let script = js_runtime
.execute_script(
"<anon>",
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);