From 73a704a19e44691610380b40fc956282c8d39d1b Mon Sep 17 00:00:00 2001 From: Markus <28785953+MarkusJx@users.noreply.github.com> Date: Sat, 15 Jul 2023 06:07:14 +0200 Subject: [PATCH] feat(napi): keep stack traces in a deferred context (#1637) * feat(napi): keep stack traces in deferred context * chore: reformat code Signed-off-by: Markus <28785953+MarkusJx@users.noreply.github.com> * chore: use napi wrappers Signed-off-by: Markus <28785953+MarkusJx@users.noreply.github.com> * test(napi): add test for deferred trace Signed-off-by: Markus <28785953+MarkusJx@users.noreply.github.com> * chore: fix format Signed-off-by: Markus <28785953+MarkusJx@users.noreply.github.com> --------- Signed-off-by: Markus <28785953+MarkusJx@users.noreply.github.com> --- crates/napi/Cargo.toml | 1 + crates/napi/src/js_values/deferred.rs | 130 +++++++++++++++++- examples/napi/Cargo.toml | 1 + .../__snapshots__/typegen.spec.ts.md | 2 + .../__snapshots__/typegen.spec.ts.snap | Bin 4015 -> 4024 bytes examples/napi/__tests__/values.spec.ts | 8 ++ examples/napi/index.d.ts | 2 + examples/napi/src/error.rs | 5 + 8 files changed, 144 insertions(+), 5 deletions(-) diff --git a/crates/napi/Cargo.toml b/crates/napi/Cargo.toml index 6e71f230..573864f0 100644 --- a/crates/napi/Cargo.toml +++ b/crates/napi/Cargo.toml @@ -51,6 +51,7 @@ tokio_stats = ["tokio/stats"] tokio_sync = ["tokio/sync"] tokio_test_util = ["tokio/test-util"] tokio_time = ["tokio/time"] +deferred_trace = ["napi4"] [dependencies] ctor = "0.2" diff --git a/crates/napi/src/js_values/deferred.rs b/crates/napi/src/js_values/deferred.rs index 0265190b..4459f7d9 100644 --- a/crates/napi/src/js_values/deferred.rs +++ b/crates/napi/src/js_values/deferred.rs @@ -6,9 +6,116 @@ use std::ptr; use crate::bindgen_runtime::{ToNapiValue, THREAD_DESTROYED}; use crate::{check_status, JsError, JsObject, Value}; use crate::{sys, Env, Error, Result}; +#[cfg(feature = "deferred_trace")] +use crate::{NapiRaw, NapiValue}; + +#[cfg(feature = "deferred_trace")] +/// A javascript error which keeps a stack trace +/// to the original caller in an asynchronous context. +/// This is required as the stack trace is lost when +/// an error is created in a different thread. +/// +/// See this issue for more details: +/// https://github.com/nodejs/node-addon-api/issues/595 +struct DeferredTrace { + value: sys::napi_ref, + #[cfg(not(feature = "noop"))] + env: sys::napi_env, +} + +#[cfg(feature = "deferred_trace")] +impl DeferredTrace { + fn new(raw_env: sys::napi_env) -> Self { + let env = unsafe { Env::from_raw(raw_env) }; + let reason = env.create_string("none").unwrap(); + + let mut js_error = ptr::null_mut(); + let create_error_status = + unsafe { sys::napi_create_error(raw_env, ptr::null_mut(), reason.raw(), &mut js_error) }; + debug_assert!(create_error_status == sys::Status::napi_ok); + + let mut result = ptr::null_mut(); + let status = unsafe { sys::napi_create_reference(raw_env, js_error, 1, &mut result) }; + debug_assert!(status == sys::Status::napi_ok); + + Self { + value: result, + #[cfg(not(feature = "noop"))] + env: raw_env, + } + } + + fn into_rejected(self, raw_env: sys::napi_env, err: Error) -> Result { + let env = unsafe { Env::from_raw(raw_env) }; + let raw = unsafe { DeferredTrace::to_napi_value(raw_env, self)? }; + + let mut obj = unsafe { JsObject::from_raw(raw_env, raw)? }; + if err.reason.is_empty() && err.status == crate::Status::GenericFailure { + // Can't clone err as the clone containing napi pointers will + // be freed when this function returns, causing err to be freed twice. + // Someone should probably fix this. + let err_obj = JsError::from(err).into_unknown(env).coerce_to_object()?; + + if err_obj.has_named_property("message")? { + // The error was already created inside the JS engine, just return it + Ok(unsafe { JsError::from(Error::from(err_obj.into_unknown())).into_value(raw_env) }) + } else { + obj.set_named_property("message", "")?; + obj.set_named_property("code", "")?; + Ok(raw) + } + } else { + obj.set_named_property("message", env.create_string(&err.reason)?)?; + obj.set_named_property("code", env.create_string_from_std(err.status.to_string())?)?; + Ok(raw) + } + } +} + +#[cfg(feature = "deferred_trace")] +impl ToNapiValue for DeferredTrace { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + let mut value = ptr::null_mut(); + check_status!(unsafe { sys::napi_get_reference_value(env, val.value, &mut value) })?; + + if value.is_null() { + // This shouldn't happen but a panic is better than a segfault + Err(Error::new( + crate::Status::GenericFailure, + "Failed to get deferred error reference", + )) + } else { + Ok(value) + } + } +} + +#[cfg(feature = "deferred_trace")] +impl Drop for DeferredTrace { + fn drop(&mut self) { + #[cfg(not(feature = "noop"))] + { + if !self.env.is_null() && !self.value.is_null() { + let delete_reference_status = unsafe { sys::napi_delete_reference(self.env, self.value) }; + debug_assert!( + delete_reference_status == sys::Status::napi_ok, + "Delete Error Reference failed" + ); + } + } + } +} + +struct DeferredData Result> { + resolver: Result, + #[cfg(feature = "deferred_trace")] + trace: DeferredTrace, +} pub struct JsDeferred Result> { tsfn: sys::napi_threadsafe_function, + #[cfg(feature = "deferred_trace")] + trace: DeferredTrace, _data: PhantomData, _resolver: PhantomData, } @@ -52,6 +159,8 @@ impl Result> JsDeferred Result> JsDeferred) { + let data = DeferredData { + resolver: result, + #[cfg(feature = "deferred_trace")] + trace: self.trace, + }; + // Call back into the JS thread via a threadsafe function. This results in napi_resolve_deferred being called. let status = unsafe { sys::napi_call_threadsafe_function( self.tsfn, - Box::into_raw(Box::from(result)) as *mut c_void, + Box::into_raw(Box::from(data)) as *mut c_void, sys::ThreadsafeFunctionCallMode::nonblocking, ) }; @@ -113,8 +228,9 @@ extern "C" fn napi_resolve_deferred } } let deferred = context as sys::napi_deferred; - let resolver = unsafe { Box::from_raw(data as *mut Result) }; - let result = resolver + let deferred_data = unsafe { Box::from_raw(data as *mut DeferredData) }; + let result = deferred_data + .resolver .and_then(|resolver| resolver(unsafe { Env::from_raw(env) })) .and_then(|res| unsafe { ToNapiValue::to_napi_value(env, res) }); @@ -128,8 +244,12 @@ extern "C" fn napi_resolve_deferred ); } Err(e) => { - let status = - unsafe { sys::napi_reject_deferred(env, deferred, JsError::from(e).into_value(env)) }; + #[cfg(feature = "deferred_trace")] + let error = deferred_data.trace.into_rejected(env, e).unwrap(); + #[cfg(not(feature = "deferred_trace"))] + let error = unsafe { crate::JsError::from(e).into_value(env) }; + + let status = unsafe { sys::napi_reject_deferred(env, deferred, error) }; debug_assert!( status == sys::Status::napi_ok, "Reject promise failed {:?}", diff --git a/examples/napi/Cargo.toml b/examples/napi/Cargo.toml index 7ccf12a6..cecb2959 100644 --- a/examples/napi/Cargo.toml +++ b/examples/napi/Cargo.toml @@ -23,6 +23,7 @@ napi = { path = "../../crates/napi", default-features = false, features = [ "experimental", "latin1", "chrono_date", + "deferred_trace", ] } napi-derive = { path = "../../crates/macro", features = ["type-def"] } napi-shared = { path = "../napi-shared" } diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md index 1cad4f57..3b6ab303 100644 --- a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md @@ -512,6 +512,8 @@ Generated by [AVA](https://avajs.dev). ␊ export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void␊ ␊ + export function throwAsyncError(): Promise␊ + ␊ export function throwError(): void␊ ␊ export function toJsObj(): object␊ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap index 65b9a97356019a95857ef8256665995c245b17b4..9b0a029a85bc9927457a0c551e2ec04934e4d522 100644 GIT binary patch literal 4024 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 V{~Uhz;C{#C{SOnZZqo!j007h<+1~&F diff --git a/examples/napi/__tests__/values.spec.ts b/examples/napi/__tests__/values.spec.ts index 1d5da401..b1765927 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -132,6 +132,7 @@ import { returnFromSharedCrate, chronoNativeDateTime, chronoNativeDateTimeReturn, + throwAsyncError, } from '..' test('export const', (t) => { @@ -420,6 +421,13 @@ test('Result', (t) => { } }) +test('Async error with stack trace', async (t) => { + const err = await t.throwsAsync(() => throwAsyncError()) + t.not(err?.stack, undefined) + t.deepEqual(err!.message, 'Async Error') + t.regex(err!.stack!, /.+at .+values\.spec\.ts:\d+:\d+.+/gm) +}) + test('custom status code in Error', (t) => { t.throws(() => customStatusCode(), { code: 'Panic', diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index aedd504f..cca48a99 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -502,6 +502,8 @@ export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): v export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void +export function throwAsyncError(): Promise + export function throwError(): void export function toJsObj(): object diff --git a/examples/napi/src/error.rs b/examples/napi/src/error.rs index 344a8ed0..56d967fb 100644 --- a/examples/napi/src/error.rs +++ b/examples/napi/src/error.rs @@ -33,3 +33,8 @@ impl AsRef for CustomError { pub fn custom_status_code() -> Result<(), CustomError> { Err(Error::new(CustomError::Panic, "don't panic")) } + +#[napi] +pub async fn throw_async_error() -> Result<()> { + Err(Error::new(Status::InvalidArg, "Async Error".to_owned())) +}