2020-08-26 01:07:27 +09:00
|
|
|
use std::convert::Into;
|
2020-12-02 19:39:20 +09:00
|
|
|
use std::ffi::CString;
|
2020-10-04 18:10:52 +09:00
|
|
|
use std::marker::PhantomData;
|
2020-12-02 19:39:20 +09:00
|
|
|
use std::os::raw::c_void;
|
2020-06-21 20:10:06 +09:00
|
|
|
use std::ptr;
|
2020-11-11 13:16:19 +09:00
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
use std::sync::Arc;
|
2020-06-21 20:10:06 +09:00
|
|
|
|
2020-12-02 15:10:48 +09:00
|
|
|
use crate::{check_status, sys, Env, Error, JsError, JsFunction, NapiValue, Result, Status};
|
2020-06-19 21:42:18 +09:00
|
|
|
|
|
|
|
use sys::napi_threadsafe_function_call_mode;
|
|
|
|
|
2020-11-11 13:16:19 +09:00
|
|
|
/// ThreadSafeFunction Context object
|
|
|
|
/// the `value` is the value passed to `call` method
|
2020-10-04 18:10:52 +09:00
|
|
|
pub struct ThreadSafeCallContext<T: 'static> {
|
|
|
|
pub env: Env,
|
|
|
|
pub value: T,
|
|
|
|
}
|
|
|
|
|
2020-08-26 01:07:27 +09:00
|
|
|
#[repr(u8)]
|
|
|
|
pub enum ThreadsafeFunctionCallMode {
|
|
|
|
NonBlocking,
|
|
|
|
Blocking,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Into<napi_threadsafe_function_call_mode> for ThreadsafeFunctionCallMode {
|
|
|
|
fn into(self) -> napi_threadsafe_function_call_mode {
|
|
|
|
match self {
|
|
|
|
ThreadsafeFunctionCallMode::Blocking => {
|
|
|
|
napi_threadsafe_function_call_mode::napi_tsfn_blocking
|
|
|
|
}
|
|
|
|
ThreadsafeFunctionCallMode::NonBlocking => {
|
|
|
|
napi_threadsafe_function_call_mode::napi_tsfn_nonblocking
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-19 21:42:18 +09:00
|
|
|
/// Communicate with the addon's main thread by invoking a JavaScript function from other threads.
|
|
|
|
///
|
|
|
|
/// ## Example
|
|
|
|
/// An example of using `ThreadsafeFunction`:
|
|
|
|
///
|
2020-12-03 18:17:40 +09:00
|
|
|
/// ```rust
|
2020-06-19 21:42:18 +09:00
|
|
|
/// #[macro_use]
|
2020-07-19 18:29:47 +09:00
|
|
|
/// extern crate napi_derive;
|
2020-06-19 21:42:18 +09:00
|
|
|
///
|
|
|
|
/// use std::thread;
|
2020-10-04 18:10:52 +09:00
|
|
|
///
|
2020-07-19 18:29:47 +09:00
|
|
|
/// use napi::{
|
2020-11-11 13:16:19 +09:00
|
|
|
/// threadsafe_function::{
|
|
|
|
/// ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode,
|
|
|
|
/// },
|
|
|
|
/// CallContext, Error, JsFunction, JsNumber, JsUndefined, Result, Status,
|
2020-06-19 21:42:18 +09:00
|
|
|
/// };
|
2020-12-03 18:17:40 +09:00
|
|
|
///
|
2020-06-19 21:42:18 +09:00
|
|
|
/// #[js_function(1)]
|
2020-10-04 18:10:52 +09:00
|
|
|
/// pub fn test_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> {
|
2020-12-03 18:17:40 +09:00
|
|
|
/// let func = ctx.get::<JsFunction>(0)?;
|
|
|
|
///
|
|
|
|
/// let tsfn =
|
|
|
|
/// ctx
|
|
|
|
/// .env
|
|
|
|
/// .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext<Vec<u32>>| {
|
2020-11-11 13:16:19 +09:00
|
|
|
/// ctx.value
|
|
|
|
/// .iter()
|
2020-12-03 18:17:40 +09:00
|
|
|
/// .map(|v| ctx.env.create_uint32(*v))
|
2020-11-11 13:16:19 +09:00
|
|
|
/// .collect::<Result<Vec<JsNumber>>>()
|
2020-12-03 18:17:40 +09:00
|
|
|
/// })?;
|
|
|
|
///
|
|
|
|
/// let tsfn_cloned = tsfn.try_clone()?;
|
|
|
|
///
|
|
|
|
/// thread::spawn(move || {
|
|
|
|
/// let output: Vec<u32> = vec![0, 1, 2, 3];
|
|
|
|
/// // It's okay to call a threadsafe function multiple times.
|
|
|
|
/// tsfn.call(Ok(output.clone()), ThreadsafeFunctionCallMode::Blocking);
|
|
|
|
/// });
|
|
|
|
///
|
|
|
|
/// thread::spawn(move || {
|
|
|
|
/// let output: Vec<u32> = vec![3, 2, 1, 0];
|
|
|
|
/// // It's okay to call a threadsafe function multiple times.
|
|
|
|
/// tsfn_cloned.call(Ok(output.clone()), ThreadsafeFunctionCallMode::NonBlocking);
|
|
|
|
/// });
|
|
|
|
///
|
|
|
|
/// ctx.env.get_undefined()
|
2020-06-19 21:42:18 +09:00
|
|
|
/// }
|
|
|
|
/// ```
|
2020-10-04 18:10:52 +09:00
|
|
|
pub struct ThreadsafeFunction<T: 'static> {
|
|
|
|
raw_tsfn: sys::napi_threadsafe_function,
|
2020-11-11 13:16:19 +09:00
|
|
|
aborted: Arc<AtomicBool>,
|
2020-10-04 18:10:52 +09:00
|
|
|
_phantom: PhantomData<T>,
|
2020-06-19 21:42:18 +09:00
|
|
|
}
|
|
|
|
|
2020-10-04 18:10:52 +09:00
|
|
|
unsafe impl<T> Send for ThreadsafeFunction<T> {}
|
|
|
|
unsafe impl<T> Sync for ThreadsafeFunction<T> {}
|
2020-06-19 21:42:18 +09:00
|
|
|
|
2020-10-04 18:10:52 +09:00
|
|
|
struct ThreadSafeContext<T: 'static, V: NapiValue>(
|
|
|
|
Box<dyn FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>>,
|
|
|
|
);
|
|
|
|
|
|
|
|
impl<T: 'static> ThreadsafeFunction<T> {
|
2020-06-19 21:42:18 +09:00
|
|
|
/// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function)
|
|
|
|
/// for more information.
|
2020-11-25 18:42:14 +09:00
|
|
|
#[inline]
|
2020-10-04 18:10:52 +09:00
|
|
|
pub fn create<
|
|
|
|
V: NapiValue,
|
|
|
|
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>,
|
|
|
|
>(
|
|
|
|
env: sys::napi_env,
|
2020-11-11 13:16:19 +09:00
|
|
|
func: &JsFunction,
|
2020-11-04 23:52:13 +09:00
|
|
|
max_queue_size: usize,
|
2020-10-04 18:10:52 +09:00
|
|
|
callback: R,
|
|
|
|
) -> Result<Self> {
|
2020-06-19 21:42:18 +09:00
|
|
|
let mut async_resource_name = ptr::null_mut();
|
|
|
|
let s = "napi_rs_threadsafe_function";
|
2020-12-02 19:39:20 +09:00
|
|
|
let len = s.len();
|
|
|
|
let s = CString::new(s)?;
|
2020-11-25 18:42:14 +09:00
|
|
|
check_status!(unsafe {
|
2020-12-02 19:39:20 +09:00
|
|
|
sys::napi_create_string_utf8(env, s.as_ptr(), len, &mut async_resource_name)
|
2020-10-04 17:02:04 +09:00
|
|
|
})?;
|
2020-06-19 21:42:18 +09:00
|
|
|
|
2020-12-02 19:39:20 +09:00
|
|
|
let initial_thread_count = 1usize;
|
2020-10-04 18:10:52 +09:00
|
|
|
let mut raw_tsfn = ptr::null_mut();
|
|
|
|
let context = ThreadSafeContext(Box::from(callback));
|
|
|
|
let ptr = Box::into_raw(Box::new(context)) as *mut _;
|
2020-11-25 18:42:14 +09:00
|
|
|
check_status!(unsafe {
|
2020-06-19 21:42:18 +09:00
|
|
|
sys::napi_create_threadsafe_function(
|
2020-10-04 18:10:52 +09:00
|
|
|
env,
|
2020-06-21 20:10:06 +09:00
|
|
|
func.0.value,
|
2020-06-19 21:42:18 +09:00
|
|
|
ptr::null_mut(),
|
|
|
|
async_resource_name,
|
2020-12-02 19:39:20 +09:00
|
|
|
max_queue_size,
|
2020-06-19 21:42:18 +09:00
|
|
|
initial_thread_count,
|
|
|
|
ptr,
|
2020-10-04 18:10:52 +09:00
|
|
|
Some(thread_finalize_cb::<T, V>),
|
2020-06-19 21:42:18 +09:00
|
|
|
ptr,
|
2020-10-04 18:10:52 +09:00
|
|
|
Some(call_js_cb::<T, V>),
|
|
|
|
&mut raw_tsfn,
|
2020-06-19 21:42:18 +09:00
|
|
|
)
|
2020-10-04 18:10:52 +09:00
|
|
|
})?;
|
2020-06-19 21:42:18 +09:00
|
|
|
|
|
|
|
Ok(ThreadsafeFunction {
|
2020-10-04 18:10:52 +09:00
|
|
|
raw_tsfn,
|
2020-11-11 13:16:19 +09:00
|
|
|
aborted: Arc::new(AtomicBool::new(false)),
|
2020-10-04 18:10:52 +09:00
|
|
|
_phantom: PhantomData,
|
2020-06-19 21:42:18 +09:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function)
|
|
|
|
/// for more information.
|
2020-11-11 13:16:19 +09:00
|
|
|
pub fn call(&self, value: Result<T>, mode: ThreadsafeFunctionCallMode) -> Status {
|
|
|
|
if self.aborted.load(Ordering::Acquire) {
|
|
|
|
return Status::Closing;
|
|
|
|
}
|
|
|
|
unsafe {
|
2020-06-19 21:42:18 +09:00
|
|
|
sys::napi_call_threadsafe_function(
|
2020-10-04 18:10:52 +09:00
|
|
|
self.raw_tsfn,
|
|
|
|
Box::into_raw(Box::new(value)) as *mut _,
|
2020-08-26 01:07:27 +09:00
|
|
|
mode.into(),
|
2020-06-19 21:42:18 +09:00
|
|
|
)
|
2020-11-11 13:16:19 +09:00
|
|
|
}
|
|
|
|
.into()
|
2020-06-19 21:42:18 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
/// See [napi_ref_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_ref_threadsafe_function)
|
|
|
|
/// for more information.
|
|
|
|
///
|
|
|
|
/// "ref" is a keyword so that we use "refer" here.
|
2020-11-11 13:16:19 +09:00
|
|
|
pub fn refer(&mut self, env: &Env) -> Result<()> {
|
2020-11-12 11:47:33 +09:00
|
|
|
if self.aborted.load(Ordering::Acquire) {
|
|
|
|
return Err(Error::new(
|
|
|
|
Status::Closing,
|
|
|
|
format!("Can not ref, Thread safe function already aborted"),
|
|
|
|
));
|
|
|
|
}
|
2020-11-25 18:42:14 +09:00
|
|
|
check_status!(unsafe { sys::napi_ref_threadsafe_function(env.0, self.raw_tsfn) })
|
2020-06-19 21:42:18 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
/// See [napi_unref_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_unref_threadsafe_function)
|
|
|
|
/// for more information.
|
2020-11-11 13:16:19 +09:00
|
|
|
pub fn unref(&mut self, env: &Env) -> Result<()> {
|
2020-11-12 11:47:33 +09:00
|
|
|
if self.aborted.load(Ordering::Acquire) {
|
|
|
|
return Err(Error::new(
|
|
|
|
Status::Closing,
|
|
|
|
format!("Can not unref, Thread safe function already aborted"),
|
|
|
|
));
|
|
|
|
}
|
2020-11-25 18:42:14 +09:00
|
|
|
check_status!(unsafe { sys::napi_unref_threadsafe_function(env.0, self.raw_tsfn) })
|
2020-06-19 21:42:18 +09:00
|
|
|
}
|
2020-11-11 13:16:19 +09:00
|
|
|
|
|
|
|
pub fn aborted(&self) -> bool {
|
|
|
|
self.aborted.load(Ordering::Acquire)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn abort(self) -> Result<()> {
|
2020-11-25 18:42:14 +09:00
|
|
|
check_status!(unsafe {
|
2020-11-11 13:16:19 +09:00
|
|
|
sys::napi_release_threadsafe_function(
|
|
|
|
self.raw_tsfn,
|
|
|
|
sys::napi_threadsafe_function_release_mode::napi_tsfn_abort,
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
self.aborted.store(true, Ordering::Release);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_clone(&self) -> Result<Self> {
|
|
|
|
if self.aborted.load(Ordering::Acquire) {
|
|
|
|
return Err(Error::new(
|
|
|
|
Status::Closing,
|
2020-11-12 11:47:33 +09:00
|
|
|
format!("Can not clone, Thread safe function already aborted"),
|
2020-11-11 13:16:19 +09:00
|
|
|
));
|
|
|
|
}
|
2020-11-25 18:42:14 +09:00
|
|
|
check_status!(unsafe { sys::napi_acquire_threadsafe_function(self.raw_tsfn) })?;
|
2020-11-11 13:16:19 +09:00
|
|
|
Ok(Self {
|
|
|
|
raw_tsfn: self.raw_tsfn,
|
|
|
|
aborted: Arc::clone(&self.aborted),
|
|
|
|
_phantom: PhantomData,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the raw `ThreadSafeFunction` pointer
|
|
|
|
pub fn raw(&self) -> sys::napi_threadsafe_function {
|
|
|
|
self.raw_tsfn
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: 'static> Drop for ThreadsafeFunction<T> {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if !self.aborted.load(Ordering::Acquire) {
|
|
|
|
let release_status = unsafe {
|
|
|
|
sys::napi_release_threadsafe_function(
|
|
|
|
self.raw_tsfn,
|
|
|
|
sys::napi_threadsafe_function_release_mode::napi_tsfn_release,
|
|
|
|
)
|
|
|
|
};
|
|
|
|
assert!(
|
|
|
|
release_status == sys::Status::napi_ok,
|
|
|
|
"Threadsafe Function release failed"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-06-19 21:42:18 +09:00
|
|
|
}
|
|
|
|
|
2020-10-04 18:10:52 +09:00
|
|
|
unsafe extern "C" fn thread_finalize_cb<T: 'static, V: NapiValue>(
|
2020-06-19 21:42:18 +09:00
|
|
|
_raw_env: sys::napi_env,
|
|
|
|
finalize_data: *mut c_void,
|
|
|
|
_finalize_hint: *mut c_void,
|
|
|
|
) {
|
|
|
|
// cleanup
|
2020-10-04 18:10:52 +09:00
|
|
|
Box::from_raw(finalize_data as *mut ThreadSafeContext<T, V>);
|
2020-06-19 21:42:18 +09:00
|
|
|
}
|
|
|
|
|
2020-10-04 18:10:52 +09:00
|
|
|
unsafe extern "C" fn call_js_cb<T: 'static, V: NapiValue>(
|
2020-06-19 21:42:18 +09:00
|
|
|
raw_env: sys::napi_env,
|
|
|
|
js_callback: sys::napi_value,
|
|
|
|
context: *mut c_void,
|
|
|
|
data: *mut c_void,
|
|
|
|
) {
|
|
|
|
let mut recv = ptr::null_mut();
|
|
|
|
sys::napi_get_undefined(raw_env, &mut recv);
|
|
|
|
|
2020-10-04 18:10:52 +09:00
|
|
|
let ctx = Box::leak(Box::from_raw(context as *mut ThreadSafeContext<T, V>));
|
|
|
|
let val = Box::from_raw(data as *mut Result<T>);
|
2020-06-19 21:42:18 +09:00
|
|
|
|
2020-10-04 18:10:52 +09:00
|
|
|
let ret = val.and_then(|v| {
|
|
|
|
(ctx.0)(ThreadSafeCallContext {
|
|
|
|
env: Env::from_raw(raw_env),
|
|
|
|
value: v,
|
|
|
|
})
|
|
|
|
});
|
2020-06-19 21:42:18 +09:00
|
|
|
|
|
|
|
let status;
|
|
|
|
|
2020-07-26 17:56:42 +09:00
|
|
|
// Follow async callback conventions: https://nodejs.org/en/knowledge/errors/what-are-the-error-conventions/
|
|
|
|
// Check if the Result is okay, if so, pass a null as the first (error) argument automatically.
|
|
|
|
// If the Result is an error, pass that as the first argument.
|
2020-10-04 18:10:52 +09:00
|
|
|
match ret {
|
|
|
|
Ok(values) => {
|
|
|
|
let mut js_null = ptr::null_mut();
|
|
|
|
sys::napi_get_null(raw_env, &mut js_null);
|
|
|
|
let args_length = values.len() + 1;
|
|
|
|
let mut args: Vec<sys::napi_value> = Vec::with_capacity(args_length);
|
|
|
|
args.push(js_null);
|
|
|
|
args.extend(values.iter().map(|v| v.raw()));
|
|
|
|
status = sys::napi_call_function(
|
|
|
|
raw_env,
|
|
|
|
recv,
|
|
|
|
js_callback,
|
2020-12-02 19:39:20 +09:00
|
|
|
args_length,
|
2020-10-04 18:10:52 +09:00
|
|
|
args.as_ptr(),
|
|
|
|
ptr::null_mut(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
status = sys::napi_call_function(
|
|
|
|
raw_env,
|
|
|
|
recv,
|
|
|
|
js_callback,
|
|
|
|
1,
|
2020-12-02 15:10:48 +09:00
|
|
|
[JsError::from(e).into_value(raw_env)].as_mut_ptr(),
|
2020-10-04 18:10:52 +09:00
|
|
|
ptr::null_mut(),
|
|
|
|
);
|
2020-07-03 01:36:45 +09:00
|
|
|
}
|
2020-06-19 21:42:18 +09:00
|
|
|
}
|
2020-11-11 13:15:13 +09:00
|
|
|
debug_assert!(status == sys::Status::napi_ok, "CallJsCB failed");
|
2020-06-19 21:42:18 +09:00
|
|
|
}
|