fix: improve error display of nativets exceptions

This commit is contained in:
Ruben Fiszel
2025-05-11 11:35:31 +02:00
parent 5061358c89
commit c76bc4a9cc
7 changed files with 73 additions and 31 deletions

View File

@@ -66,6 +66,8 @@ pub enum Error {
DatabaseMigration(#[from] MigrateError),
#[error("Non-zero exit status for {0}: {1}")]
ExitStatus(String, i32),
#[error("ExecutionRawError: {0}")]
ExecutionRawError(Box<serde_json::value::RawValue>),
#[error("Error: {error:#} @{location:#}")]
Anyhow { error: anyhow::Error, location: String },
#[error("Error: {0:#?}")]

View File

@@ -1,6 +1,5 @@
use std::collections::HashMap;
use anyhow::anyhow;
use futures::{stream, TryStreamExt};
use serde_json::{json, value::RawValue};
use sqlx::types::Json;
@@ -134,11 +133,13 @@ pub async fn do_graphql(
.map_err(|e| Error::ExecutionErr(e.to_string()))?;
if let Some(errors) = result.errors {
return Err(anyhow!(errors
.into_iter()
.map(|x| x.message)
.collect::<Vec<_>>()
.join("\n"),));
return Err(Error::ExecutionErr(
errors
.into_iter()
.map(|x| x.message)
.collect::<Vec<_>>()
.join("\n"),
));
}
// And then check that we got back the same string we sent over.

View File

@@ -526,7 +526,7 @@ pub async fn run_future_with_polling_update_job_poller<Fut, T, S>(
get_mem: S,
) -> error::Result<T>
where
Fut: Future<Output = anyhow::Result<T>>,
Fut: Future<Output = windmill_common::error::Result<T>>,
S: stream::Stream<Item = i32> + Unpin,
{
let (tx, rx) = broadcast::channel::<()>(3);

View File

@@ -789,7 +789,7 @@ pub async fn eval_fetch_timeout(
w_id: &str,
load_client: bool,
occupation_metrics: &mut OccupancyMetrics,
) -> anyhow::Result<Box<RawValue>> {
) -> windmill_common::error::Result<Box<RawValue>> {
use windmill_queue::append_logs;
let (sender, mut receiver) = oneshot::channel::<IsolateHandle>();
@@ -933,7 +933,7 @@ pub async fn eval_fetch_timeout(
let r = runtime.block_on(future)?;
// tracing::info!("total: {:?}", instant.elapsed());
r
r as windmill_common::error::Result<Box<RawValue>>
});
let res = run_future_with_polling_update_job_poller(
@@ -942,7 +942,7 @@ pub async fn eval_fetch_timeout(
conn,
mem_peak,
canceled_by,
async { result_f.await? },
async { result_f.await.map_err(windmill_common::error::to_anyhow)? },
worker_name,
w_id,
&mut Some(occupation_metrics),
@@ -1004,22 +1004,26 @@ async fn eval_fetch(
script_entrypoint_override: Option<String>,
load_client: bool,
job_id: &Uuid,
) -> anyhow::Result<Box<RawValue>> {
) -> windmill_common::error::Result<Box<RawValue>> {
if load_client {
if let Some(env_code) = env_code.as_ref() {
let _ = js_runtime
.load_side_es_module_from_code(
&deno_core::resolve_url("file:///windmill.ts")?,
&deno_core::resolve_url("file:///windmill.ts").map_err(error::to_anyhow)?,
format!("{env_code}\n{}", WINDMILL_CLIENT.to_string()),
)
.await?;
.await
.map_err(error::to_anyhow)?;
}
}
use anyhow::Context;
use deno_core::error::CoreError;
use windmill_common::{error, worker::to_raw_value};
let source = format!("{}\n{expr}", env_code.unwrap_or_default());
let _ = js_runtime
.load_side_es_module_from_code(
&deno_core::resolve_url("file:///eval.ts")?,
format!("{}\n{expr}", env_code.unwrap_or_default()),
&deno_core::resolve_url("file:///eval.ts").map_err(error::to_anyhow)?,
source.to_string(),
)
.await
.map_err(|e| {
@@ -1052,15 +1056,50 @@ import("file:///eval.ts").then((module) => module.{main_override}(...args)).then
.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);
// Deserialize a `v8` object into a Rust type using `serde_v8`,
// in this case deserialize to a JSON `Value`.
let r = serde_v8::from_v8::<Option<String>>(scope, local)?;
Ok(unsafe_raw(r.unwrap_or_else(|| "null".to_string())))
match global {
Ok(global) => {
let scope = &mut js_runtime.handle_scope();
let local = v8::Local::new(scope, global);
// Deserialize a `v8` object into a Rust type using `serde_v8`,
// in this case deserialize to a JSON `Value`.
let r = serde_v8::from_v8::<Option<String>>(scope, local).map_err(error::to_anyhow)?;
Ok(unsafe_raw(r.unwrap_or_else(|| "null".to_string())))
}
Err(CoreError::Js(e)) => {
let stack_head = e.frames.first().and_then(|f| {
if f.file_name.as_ref().is_some_and(|x| x == "file:///eval.ts") {
Some(format!(
"{}\n",
source
.lines()
.nth((f.line_number.unwrap_or(1)) as usize - 1)
.unwrap_or("")
.to_string()
))
} else {
None
}
});
let stack_s = format!(
"{}{}",
stack_head.unwrap_or("".to_string()),
e.stack.unwrap_or("".to_string())
);
let stack = if stack_s.is_empty() {
None
} else {
Some(stack_s)
};
Err(Error::ExecutionRawError(to_raw_value(&serde_json::json!({
"message": e.message,
"stack": stack,
"name": e.name,
}))))
}
Err(e) => Err(Error::ExecutionErr(e.print_with_cause())),
}
}
#[cfg(feature = "deno_core")]

View File

@@ -68,7 +68,7 @@ fn do_postgresql_inner<'a>(
column_order: Option<&'a mut Option<Vec<String>>>,
siz: &'a AtomicUsize,
skip_collect: bool,
) -> error::Result<BoxFuture<'a, anyhow::Result<Box<RawValue>>>> {
) -> error::Result<BoxFuture<'a, error::Result<Box<RawValue>>>> {
let mut query_params = vec![];
let arg_indices = parse_pg_statement_arg_indices(&query);
@@ -136,17 +136,17 @@ fn do_postgresql_inner<'a>(
if *CLOUD_HOSTED {
let siz = siz.load(Ordering::Relaxed);
if siz > MAX_RESULT_SIZE * 4 {
return Err(anyhow::anyhow!(
return Err(Error::ExecutionErr(format!(
"Query result too large for cloud (size = {} > {})",
siz,
MAX_RESULT_SIZE & 4
));
MAX_RESULT_SIZE & 4,
)));
}
}
if let Ok(v) = r {
res.push(v);
} else {
return Err(to_anyhow(r.err().unwrap()));
return Err(to_anyhow(r.err().unwrap()).into());
}
}
}

View File

@@ -373,6 +373,7 @@ pub async fn process_result(
}
}
}
Error::ExecutionRawError(e) => to_raw_value(&e),
err @ _ => to_raw_value(&SerializedError {
message: format!("execution error:\n{err:#}",),
name: "ExecutionErr".to_string(),

View File

@@ -2,13 +2,12 @@ use base64::{engine, Engine as _};
use chrono::Datelike;
use core::fmt::Write;
use futures::future::BoxFuture;
use futures::{FutureExt, TryFutureExt};
use futures::FutureExt;
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
use reqwest::{Client, Response};
use serde_json::{json, value::RawValue, Value};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use windmill_common::error::to_anyhow;
use windmill_common::worker::Connection;
use windmill_common::{error::Error, worker::to_raw_value};
@@ -428,7 +427,7 @@ pub async fn do_snowflake(
conn,
mem_peak,
canceled_by,
result_f.map_err(to_anyhow),
result_f,
worker_name,
&job.workspace_id,
&mut Some(occupancy_metrics),