2020-08-26 01:07:27 +09:00
|
|
|
use std::convert::Into;
|
2020-10-04 18:10:52 +09:00
|
|
|
use std::marker::PhantomData;
|
2020-06-19 21:42:18 +09:00
|
|
|
use std::os::raw::{c_char, c_void};
|
2020-06-21 20:10:06 +09:00
|
|
|
use std::ptr;
|
|
|
|
|
|
|
|
use crate::error::check_status;
|
2020-10-04 18:10:52 +09:00
|
|
|
use crate::{sys, Env, JsFunction, NapiValue, Result};
|
2020-06-19 21:42:18 +09:00
|
|
|
|
|
|
|
use sys::napi_threadsafe_function_call_mode;
|
|
|
|
use sys::napi_threadsafe_function_release_mode;
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[repr(u8)]
|
|
|
|
pub enum ThreadsafeFunctionReleaseMode {
|
|
|
|
Release,
|
|
|
|
Abort,
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Into<napi_threadsafe_function_release_mode> for ThreadsafeFunctionReleaseMode {
|
|
|
|
fn into(self) -> napi_threadsafe_function_release_mode {
|
|
|
|
match self {
|
|
|
|
ThreadsafeFunctionReleaseMode::Release => {
|
|
|
|
napi_threadsafe_function_release_mode::napi_tsfn_release
|
|
|
|
}
|
|
|
|
ThreadsafeFunctionReleaseMode::Abort => {
|
|
|
|
napi_threadsafe_function_release_mode::napi_tsfn_abort
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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`:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// #[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-10-04 18:10:52 +09:00
|
|
|
/// threadsafe_function::{
|
|
|
|
/// ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode,
|
|
|
|
/// },
|
|
|
|
/// CallContext, Error, JsFunction, JsNumber, JsUndefined, Result, Status,
|
2020-06-19 21:42:18 +09:00
|
|
|
/// };
|
2020-10-04 18:10:52 +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-06-21 20:10:06 +09:00
|
|
|
/// let func = ctx.get::<JsFunction>(0)?;
|
2020-10-04 18:10:52 +09:00
|
|
|
|
|
|
|
/// let tsfn =
|
|
|
|
/// ctx
|
|
|
|
/// .env
|
|
|
|
/// .create_threadsafe_function(func, 0, |ctx: ThreadSafeCallContext<Vec<u32>>| {
|
|
|
|
/// ctx
|
|
|
|
/// .value
|
|
|
|
/// .iter()
|
|
|
|
/// .map(|v| ctx.env.create_uint32(*v))
|
|
|
|
/// .collect::<Result<Vec<JsNumber>>>()
|
|
|
|
/// })?;
|
|
|
|
|
2020-06-19 21:42:18 +09:00
|
|
|
/// thread::spawn(move || {
|
2020-10-04 18:10:52 +09:00
|
|
|
/// let output: Vec<u32> = vec![42, 1, 2, 3];
|
|
|
|
/// /// It's okay to call a threadsafe function multiple times.
|
|
|
|
/// tsfn.call(Ok(output.clone()), ThreadsafeFunctionCallMode::Blocking);
|
|
|
|
/// tsfn.call(Ok(output.clone()), ThreadsafeFunctionCallMode::NonBlocking);
|
|
|
|
/// tsfn.release(ThreadsafeFunctionReleaseMode::Release);
|
2020-06-19 21:42:18 +09:00
|
|
|
/// });
|
2020-10-04 18:10:52 +09:00
|
|
|
|
2020-06-21 20:10:06 +09:00
|
|
|
/// 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,
|
|
|
|
_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
|
|
|
#[repr(transparent)]
|
|
|
|
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-10-04 18:10:52 +09:00
|
|
|
#[inline(always)]
|
|
|
|
pub fn create<
|
|
|
|
V: NapiValue,
|
|
|
|
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>,
|
|
|
|
>(
|
|
|
|
env: sys::napi_env,
|
|
|
|
func: JsFunction,
|
|
|
|
max_queue_size: u64,
|
|
|
|
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-10-04 17:02:04 +09:00
|
|
|
check_status(unsafe {
|
2020-06-19 21:42:18 +09:00
|
|
|
sys::napi_create_string_utf8(
|
2020-10-04 18:10:52 +09:00
|
|
|
env,
|
2020-06-19 21:42:18 +09:00
|
|
|
s.as_ptr() as *const c_char,
|
|
|
|
s.len() as u64,
|
|
|
|
&mut async_resource_name,
|
|
|
|
)
|
2020-10-04 17:02:04 +09:00
|
|
|
})?;
|
2020-06-19 21:42:18 +09:00
|
|
|
|
|
|
|
let initial_thread_count: u64 = 1;
|
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 _;
|
|
|
|
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,
|
|
|
|
max_queue_size,
|
|
|
|
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,
|
|
|
|
_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-10-04 18:10:52 +09:00
|
|
|
pub fn call(&self, value: Result<T>, mode: ThreadsafeFunctionCallMode) {
|
|
|
|
let status = 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-10-04 18:10:52 +09:00
|
|
|
};
|
|
|
|
debug_assert!(
|
|
|
|
status == sys::napi_status::napi_ok,
|
|
|
|
"Threadsafe Function call failed"
|
|
|
|
);
|
2020-06-19 21:42:18 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
/// See [napi_acquire_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_acquire_threadsafe_function)
|
|
|
|
/// for more information.
|
2020-10-04 18:10:52 +09:00
|
|
|
pub fn acquire(&self) {
|
|
|
|
let status = unsafe { sys::napi_acquire_threadsafe_function(self.raw_tsfn) };
|
|
|
|
debug_assert!(
|
|
|
|
status == sys::napi_status::napi_ok,
|
|
|
|
"Threadsafe Function acquire failed"
|
|
|
|
);
|
2020-06-19 21:42:18 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
/// See [napi_release_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_release_threadsafe_function)
|
|
|
|
/// for more information.
|
2020-10-04 18:10:52 +09:00
|
|
|
pub fn release(self, mode: ThreadsafeFunctionReleaseMode) {
|
|
|
|
let status = unsafe { sys::napi_release_threadsafe_function(self.raw_tsfn, mode.into()) };
|
|
|
|
debug_assert!(
|
|
|
|
status == sys::napi_status::napi_ok,
|
|
|
|
"Threadsafe Function call failed"
|
|
|
|
);
|
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.
|
|
|
|
pub fn refer(&self, env: &Env) -> Result<()> {
|
2020-10-04 18:10:52 +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.
|
|
|
|
pub fn unref(&self, env: &Env) -> Result<()> {
|
2020-10-04 18:10:52 +09:00
|
|
|
check_status(unsafe { sys::napi_unref_threadsafe_function(env.0, self.raw_tsfn) })
|
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,
|
|
|
|
args_length as _,
|
|
|
|
args.as_ptr(),
|
|
|
|
ptr::null_mut(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
status = sys::napi_call_function(
|
|
|
|
raw_env,
|
|
|
|
recv,
|
|
|
|
js_callback,
|
|
|
|
1,
|
|
|
|
[e.into_raw(raw_env)].as_mut_ptr(),
|
|
|
|
ptr::null_mut(),
|
|
|
|
);
|
2020-07-03 01:36:45 +09:00
|
|
|
}
|
2020-06-19 21:42:18 +09:00
|
|
|
}
|
|
|
|
debug_assert!(status == sys::napi_status::napi_ok, "CallJsCB failed");
|
|
|
|
}
|