fix(napi): abort threadsafe functions upon env cleanup

This fixes a deadlock/panic in Electron when the window is reloaded.
This commit is contained in:
Dave Ceddia 2022-01-10 16:57:11 -05:00
parent 110f2196a4
commit 1006b3a489

View file

@ -211,9 +211,13 @@ impl<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
) )
})?; })?;
let aborted = Arc::new(AtomicBool::new(false));
let aborted_ptr = Arc::into_raw(aborted.clone()) as *mut c_void;
check_status!(unsafe { sys::napi_add_env_cleanup_hook(env, Some(cleanup_cb), aborted_ptr) })?;
Ok(ThreadsafeFunction { Ok(ThreadsafeFunction {
raw_tsfn, raw_tsfn,
aborted: Arc::new(AtomicBool::new(false)), aborted,
ref_count: Arc::new(AtomicUsize::new(initial_thread_count)), ref_count: Arc::new(AtomicUsize::new(initial_thread_count)),
_phantom: PhantomData, _phantom: PhantomData,
}) })
@ -321,6 +325,11 @@ impl<T: 'static, ES: ErrorStrategy::T> Drop for ThreadsafeFunction<T, ES> {
} }
} }
unsafe extern "C" fn cleanup_cb(cleanup_data: *mut c_void) {
let aborted = unsafe { Arc::<AtomicBool>::from_raw(cleanup_data.cast()) };
aborted.store(true, Ordering::SeqCst);
}
unsafe extern "C" fn thread_finalize_cb<T: 'static, V: NapiRaw, R>( unsafe extern "C" fn thread_finalize_cb<T: 'static, V: NapiRaw, R>(
_raw_env: sys::napi_env, _raw_env: sys::napi_env,
finalize_data: *mut c_void, finalize_data: *mut c_void,
@ -341,6 +350,11 @@ unsafe extern "C" fn call_js_cb<T: 'static, V: NapiRaw, R, ES>(
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>, R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>,
ES: ErrorStrategy::T, ES: ErrorStrategy::T,
{ {
// env and/or callback can be null when shutting down
if raw_env.is_null() || js_callback.is_null() {
return;
}
let ctx: &mut R = unsafe { &mut *context.cast::<R>() }; let ctx: &mut R = unsafe { &mut *context.cast::<R>() };
let val: Result<T> = unsafe { let val: Result<T> = unsafe {
match ES::VALUE { match ES::VALUE {
@ -410,10 +424,10 @@ unsafe extern "C" fn call_js_cb<T: 'static, V: NapiRaw, R, ES>(
unsafe { sys::napi_get_and_clear_last_exception(raw_env, &mut error_result) }, unsafe { sys::napi_get_and_clear_last_exception(raw_env, &mut error_result) },
sys::Status::napi_ok sys::Status::napi_ok
); );
assert_eq!(
unsafe { sys::napi_fatal_exception(raw_env, error_result) }, // When shutting down, napi_fatal_exception sometimes returns another exception
sys::Status::napi_ok let stat = unsafe { sys::napi_fatal_exception(raw_env, error_result) };
); assert!(stat == sys::Status::napi_ok || stat == sys::Status::napi_pending_exception);
} else { } else {
let error_code: Status = status.into(); let error_code: Status = status.into();
let error_code_string = format!("{:?}", error_code); let error_code_string = format!("{:?}", error_code);