-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
async_hooks: after hook called twice in case of uncaught exception #33771
Comments
node/lib/internal/process/execution.js Lines 189 to 192 in 5ae5262
|
seems similar to #22982 |
Still replicating on the latest version (v16.0.0):
|
It still replicates on the latest version (v17.0.1):
|
I tested the nightly build which contains #41424 and it did not fix the issue. |
Is there any known workaround for this bug other than don't use |
Stack from a debug build - maybe this is helpful for someone:
|
Is there any solution to this issue? Application keeps crashing giving the below error.
|
@anujkumar05 Would you mind posting your code? |
this seems to be fixed in 20.9 @mancasg can you confirm? |
I can no longer reproduce this with Node-API; it's working as expected. use std::{
ffi::{c_char, c_void},
mem::MaybeUninit,
ptr::{self, NonNull},
sync::OnceLock,
thread,
time::Duration,
};
#[derive(Copy, Clone)]
#[repr(transparent)]
struct Env(*mut c_void);
#[derive(Copy, Clone)]
#[repr(transparent)]
struct Value(NonNull<c_void>);
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
#[must_use]
struct Status(u32);
#[repr(transparent)]
struct ThreadSafeFunction(NonNull<c_void>);
unsafe impl Send for ThreadSafeFunction {}
unsafe impl Sync for ThreadSafeFunction {}
impl Status {
fn is_err(self) -> bool {
self.0 != 0
}
}
#[repr(u32)]
pub enum ThreadSafeFunctionCallMode {
NonBlocking = 0,
Blocking = 1,
}
type Finalize = NonNull<c_void>;
type FatalException = unsafe extern "C" fn(Env, Value) -> Status;
type CreateString = unsafe extern "C" fn(Env, *const c_char, usize, *mut Value) -> Status;
type ThreadSafeFunctionCallJs = unsafe extern "C" fn(Env, Option<Value>, *mut c_void, *mut c_void);
type CreateThreadSafeFunction = unsafe extern "C" fn(
Env,
Option<Value>,
Option<Value>,
Value,
usize,
usize,
*mut c_void,
Option<Finalize>,
*mut c_void,
Option<ThreadSafeFunctionCallJs>,
*mut ThreadSafeFunction,
) -> Status;
type CallThreadSafeFunction =
unsafe extern "C" fn(ThreadSafeFunction, *mut c_void, ThreadSafeFunctionCallMode) -> Status;
static FATAL_EXCEPTION: OnceLock<FatalException> = OnceLock::new();
static CREATE_STRING: OnceLock<CreateString> = OnceLock::new();
unsafe extern "C" fn cause_fatal_exception(
env: Env,
_callback: Option<Value>,
_context: *mut c_void,
_data: *mut c_void,
) {
let msg = create_string(env, "Oh, no!").unwrap();
if (FATAL_EXCEPTION.get().unwrap())(env, msg).is_err() {
panic!("Failed to create fatal exception");
}
}
unsafe fn create_string(env: Env, s: &str) -> Result<Value, Status> {
let mut v = MaybeUninit::uninit();
let status = (CREATE_STRING.get().unwrap())(env, s.as_ptr().cast(), s.len(), v.as_mut_ptr());
if status.is_err() {
return Err(status);
}
Ok(v.assume_init())
}
#[no_mangle]
unsafe extern "C" fn napi_register_module_v1(env: Env, m: Value) -> Value {
#[cfg(not(windows))]
let host = libloading::os::unix::Library::this();
#[cfg(windows)]
let host = libloading::os::windows::Library::this().unwrap();
let fatal_exception = host.get::<FatalException>(b"napi_fatal_exception").unwrap();
FATAL_EXCEPTION.set(*fatal_exception).unwrap();
let create_string_utf8 = host
.get::<CreateString>(b"napi_create_string_utf8")
.unwrap();
CREATE_STRING.set(*create_string_utf8).unwrap();
let create_thread_safe_function = host
.get::<CreateThreadSafeFunction>(b"napi_create_threadsafe_function")
.unwrap();
let call_thread_safe_function = host
.get::<CallThreadSafeFunction>(b"napi_call_threadsafe_function")
.unwrap();
let name = create_string(env, "thread safe function").unwrap();
let mut thread_safe_function = MaybeUninit::uninit();
let status = create_thread_safe_function(
env,
None,
None,
name,
0,
1,
ptr::null_mut(),
None,
ptr::null_mut(),
Some(cause_fatal_exception),
thread_safe_function.as_mut_ptr(),
);
if status.is_err() {
panic!("Failed to create thread safe function");
}
let thread_safe_function = thread_safe_function.assume_init();
thread::spawn(move || {
thread::sleep(Duration::from_secs(1));
let status = call_thread_safe_function(
thread_safe_function,
ptr::null_mut(),
ThreadSafeFunctionCallMode::Blocking,
);
if status.is_err() {
panic!("Failed to call thread safe function");
}
});
m
} |
Version: v12.14.1 v14.3.0
Platform: Linux somehost 3.10.0-1062.4.1.el7.x86_64 #1 SMP Fri Oct 18 17:15:30 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
Subsystem: async_hooks
What steps will reproduce the bug?
Node crashes with an assertion error whenever the fatal exception handler is called from an async resource' callback, when error domains are in use.
I've reduced the replication scenario down to:
Our actual case involved an error thrown inside an async worker's done callback (wrapped inside an error domain of course). I can provide a repo with a minimal async worker replication scenario if you think it's necessary.
How often does it reproduce? Is there a required condition?
Every time the above scenario is met.
What is the expected behavior?
I'd expect it to log the error, then exit.
What do you see instead?
Additional information
Debug session log
As can be seen from the above debug session log, the hooks are called from the
uvimpl::Work::AfterThreadPoolWork
method insrc/node_errors.cc
, where:CallbackScope
constructor will trigger the before hook, which will increment the domain weakref count, while it's destructor will call the after hook, which will decrement the ref count, when it goes out of scope (line 894).trigger_fatal_exception
function will callnode::errors::TriggerUncaughtException
which in turn will call into jsprocess._fatalException
.Now, the js function
process._fatalException
will trigger some unhandled exception events and will continue to emit an after event. Note that theCallbackScope
above is not yet destroyed, meaning that it will emit yet another after event when it goes out of scope. The latter leading to our assertion failure.The text was updated successfully, but these errors were encountered: