Skip to content

Commit

Permalink
Merge pull request #744 from indutny/fix/tsfn-crash
Browse files Browse the repository at this point in the history
Handle early finalization of TSFN by Node.js
  • Loading branch information
kjvalencik authored Jun 1, 2021
2 parents f3a96aa + b5a672e commit dd0af64
Showing 1 changed file with 40 additions and 4 deletions.
44 changes: 40 additions & 4 deletions crates/neon-runtime/src/napi/tsfn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use std::ffi::c_void;
use std::mem::MaybeUninit;
use std::sync::{Arc, Mutex};

use crate::napi::bindings as napi;
use crate::raw::{Env, Local};
Expand Down Expand Up @@ -34,6 +35,7 @@ unsafe impl Sync for Tsfn {}
/// function for scheduling tasks to execute on a JavaScript thread.
pub struct ThreadsafeFunction<T> {
tsfn: Tsfn,
is_finalized: Arc<Mutex<bool>>,
callback: fn(Option<Env>, T),
}

Expand Down Expand Up @@ -76,6 +78,7 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {
callback: fn(Option<Env>, T),
) -> Self {
let mut result = MaybeUninit::uninit();
let is_finalized = Arc::new(Mutex::new(false));

assert_eq!(
napi::create_threadsafe_function(
Expand All @@ -87,8 +90,8 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {
// Always set the reference count to 1. Prefer using
// Rust `Arc` to maintain the struct.
1,
std::ptr::null_mut(),
None,
Arc::into_raw(is_finalized.clone()) as *mut _,
Some(Self::finalize),
std::ptr::null_mut(),
Some(Self::callback),
result.as_mut_ptr(),
Expand All @@ -98,6 +101,7 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {

Self {
tsfn: Tsfn(result.assume_init()),
is_finalized: is_finalized,
callback,
}
}
Expand All @@ -115,12 +119,28 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {
data,
}));

let status =
unsafe { napi::call_threadsafe_function(self.tsfn.0, callback as *mut _, is_blocking) };
// Hold the lock before entering `call_threadsafe_function` so that
// `finalize_cb` would never complete.
let mut is_finalized = self.is_finalized.lock().unwrap();

let status = {
if *is_finalized {
napi::Status::Closing
} else {
unsafe {
napi::call_threadsafe_function(self.tsfn.0, callback as *mut _, is_blocking)
}
}
};

if status == napi::Status::Ok {
Ok(())
} else {
// Prevent further calls to `call_threadsafe_function`
if status == napi::Status::Closing {
*is_finalized = true;
}

// If the call failed, the callback won't execute
let callback = unsafe { Box::from_raw(callback) };

Expand Down Expand Up @@ -149,6 +169,14 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {
);
}

// Provides a C ABI wrapper for a napi callback notifying us about tsfn
// being finalized.
unsafe extern "C" fn finalize(_env: Env, data: *mut c_void, _hint: *mut c_void) {
let is_finalized = Arc::from_raw(data as *mut Mutex<bool>);

*is_finalized.lock().unwrap() = true;
}

// Provides a C ABI wrapper for invoking the user supplied function pointer
unsafe extern "C" fn callback(
env: Env,
Expand All @@ -167,6 +195,14 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {

impl<T> Drop for ThreadsafeFunction<T> {
fn drop(&mut self) {
let is_finalized = self.is_finalized.lock().unwrap();

// tsfn was already finalized by `Environment::CleanupHandles()` in
// Node.js
if *is_finalized {
return;
}

unsafe {
napi::release_threadsafe_function(
self.tsfn.0,
Expand Down

0 comments on commit dd0af64

Please sign in to comment.