diff --git a/napi/src/async_work.rs b/napi/src/async_work.rs index 6c306e50..2bddc8e7 100644 --- a/napi/src/async_work.rs +++ b/napi/src/async_work.rs @@ -107,16 +107,16 @@ unsafe extern "C" fn complete( match check_status(status).and_then(move |_| value) { Ok(v) => { let status = sys::napi_resolve_deferred(env, deferred, v.raw()); - debug_assert!(status == sys::napi_status::napi_ok, "Reject promise failed"); + debug_assert!(status == sys::Status::napi_ok, "Reject promise failed"); } Err(e) => { let status = sys::napi_reject_deferred(env, deferred, e.into_raw(env)); - debug_assert!(status == sys::napi_status::napi_ok, "Reject promise failed"); + debug_assert!(status == sys::Status::napi_ok, "Reject promise failed"); } }; let delete_status = sys::napi_delete_async_work(env, napi_async_work); debug_assert!( - delete_status == sys::napi_status::napi_ok, + delete_status == sys::Status::napi_ok, "Delete async work failed" ); } diff --git a/napi/src/env.rs b/napi/src/env.rs index cde2109b..9438f35a 100644 --- a/napi/src/env.rs +++ b/napi/src/env.rs @@ -597,7 +597,7 @@ impl Env { R: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, >( &self, - func: JsFunction, + func: &JsFunction, max_queue_size: usize, callback: R, ) -> Result> { diff --git a/napi/src/error.rs b/napi/src/error.rs index 1140b3bb..facb38cc 100644 --- a/napi/src/error.rs +++ b/napi/src/error.rs @@ -71,12 +71,9 @@ impl Error { s.len() as _, &mut err_reason, ); - debug_assert!( - status == sys::napi_status::napi_ok, - "Create error reason failed" - ); + debug_assert!(status == sys::Status::napi_ok, "Create error reason failed"); let status = sys::napi_create_error(env, ptr::null_mut(), err_reason, &mut err); - debug_assert!(status == sys::napi_status::napi_ok, "Create error failed"); + debug_assert!(status == sys::Status::napi_ok, "Create error failed"); }; err } diff --git a/napi/src/js_values/arraybuffer.rs b/napi/src/js_values/arraybuffer.rs index 7d73bf4d..5573d508 100644 --- a/napi/src/js_values/arraybuffer.rs +++ b/napi/src/js_values/arraybuffer.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; -use std::convert::TryInto; use std::mem; use std::ops::Deref; use std::os::raw::c_void; @@ -7,7 +5,7 @@ use std::ptr; use super::{Value, ValueType}; use crate::error::check_status; -use crate::{sys, Error, JsUnknown, NapiValue, Ref, Result}; +use crate::{sys, JsUnknown, NapiValue, Ref, Result}; #[repr(transparent)] #[derive(Debug)] @@ -44,10 +42,10 @@ pub struct JsDataViewValue { pub length: u64, } -#[repr(u8)] -#[derive(Debug)] +#[repr(i32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum TypedArrayType { - Int8, + Int8 = 0, Uint8, Uint8Clamped, Int16, @@ -56,51 +54,35 @@ pub enum TypedArrayType { Uint32, Float32, Float64, - #[cfg(feature = "napi6")] BigInt64, - #[cfg(feature = "napi6")] BigUint64, + + /// compatible with higher versions + Unknown = 1024, } -impl TryFrom for TypedArrayType { - type Error = Error; - - fn try_from(value: sys::napi_typedarray_type) -> Result { +impl From for TypedArrayType { + fn from(value: sys::napi_typedarray_type) -> Self { match value { - sys::napi_typedarray_type::napi_int8_array => Ok(Self::Int8), - sys::napi_typedarray_type::napi_uint8_array => Ok(Self::Uint8), - sys::napi_typedarray_type::napi_uint8_clamped_array => Ok(Self::Uint8Clamped), - sys::napi_typedarray_type::napi_int16_array => Ok(Self::Int16), - sys::napi_typedarray_type::napi_uint16_array => Ok(Self::Uint16), - sys::napi_typedarray_type::napi_int32_array => Ok(Self::Int32), - sys::napi_typedarray_type::napi_uint32_array => Ok(Self::Uint32), - sys::napi_typedarray_type::napi_float32_array => Ok(Self::Float32), - sys::napi_typedarray_type::napi_float64_array => Ok(Self::Float64), - #[cfg(feature = "napi6")] - sys::napi_typedarray_type::napi_bigint64_array => Ok(Self::BigInt64), - #[cfg(feature = "napi6")] - sys::napi_typedarray_type::napi_biguint64_array => Ok(Self::BigUint64), + sys::TypedarrayType::napi_int8_array => Self::Int8, + sys::TypedarrayType::napi_uint8_array => Self::Uint8, + sys::TypedarrayType::napi_uint8_clamped_array => Self::Uint8Clamped, + sys::TypedarrayType::napi_int16_array => Self::Int16, + sys::TypedarrayType::napi_uint16_array => Self::Uint16, + sys::TypedarrayType::napi_int32_array => Self::Int32, + sys::TypedarrayType::napi_uint32_array => Self::Uint32, + sys::TypedarrayType::napi_float32_array => Self::Float32, + sys::TypedarrayType::napi_float64_array => Self::Float64, + sys::TypedarrayType::napi_bigint64_array => Self::BigInt64, + sys::TypedarrayType::napi_biguint64_array => Self::BigUint64, + _ => Self::Unknown, } } } impl From for sys::napi_typedarray_type { - fn from(value: TypedArrayType) -> Self { - match value { - TypedArrayType::Int8 => sys::napi_typedarray_type::napi_int8_array, - TypedArrayType::Uint8 => sys::napi_typedarray_type::napi_uint8_array, - TypedArrayType::Uint8Clamped => sys::napi_typedarray_type::napi_uint8_clamped_array, - TypedArrayType::Int16 => sys::napi_typedarray_type::napi_int16_array, - TypedArrayType::Uint16 => sys::napi_typedarray_type::napi_uint16_array, - TypedArrayType::Int32 => sys::napi_typedarray_type::napi_int32_array, - TypedArrayType::Uint32 => sys::napi_typedarray_type::napi_uint32_array, - TypedArrayType::Float32 => sys::napi_typedarray_type::napi_float32_array, - TypedArrayType::Float64 => sys::napi_typedarray_type::napi_float64_array, - #[cfg(feature = "napi6")] - TypedArrayType::BigInt64 => sys::napi_typedarray_type::napi_bigint64_array, - #[cfg(feature = "napi6")] - TypedArrayType::BigUint64 => sys::napi_typedarray_type::napi_biguint64_array, - } + fn from(value: TypedArrayType) -> sys::napi_typedarray_type { + value as _ } } @@ -221,7 +203,7 @@ impl JsTypedArray { /// /// ***Warning***: Use caution while using this API since the underlying data buffer is managed by the VM. pub fn into_value(self) -> Result { - let mut typedarray_type = sys::napi_typedarray_type::napi_int8_array; + let mut typedarray_type = 0; let mut len = 0u64; let mut data = ptr::null_mut(); let mut arraybuffer_value = ptr::null_mut(); @@ -242,7 +224,7 @@ impl JsTypedArray { data, length: len, byte_offset, - typedarray_type: typedarray_type.try_into()?, + typedarray_type: typedarray_type.into(), arraybuffer: JsArrayBuffer::from_raw_unchecked(self.0.env, arraybuffer_value), }) } diff --git a/napi/src/js_values/function.rs b/napi/src/js_values/function.rs index 377df1e2..a1d38b6f 100644 --- a/napi/src/js_values/function.rs +++ b/napi/src/js_values/function.rs @@ -34,7 +34,7 @@ impl JsFunction { .map(|u| u.raw()) }) .ok_or(Error::new( - Status::Unknown, + Status::GenericFailure, "Get raw this failed".to_owned(), ))?; let raw_args = args diff --git a/napi/src/js_values/mod.rs b/napi/src/js_values/mod.rs index a615604d..227fc9e1 100644 --- a/napi/src/js_values/mod.rs +++ b/napi/src/js_values/mod.rs @@ -76,9 +76,9 @@ pub struct JsExternal(pub(crate) Value); #[inline] pub(crate) fn type_of(env: sys::napi_env, raw_value: sys::napi_value) -> Result { unsafe { - let mut value_type = sys::napi_valuetype::napi_undefined; + let mut value_type = 0; check_status(sys::napi_typeof(env, raw_value, &mut value_type))?; - Ok(ValueType::from(value_type)) + ValueType::try_from(value_type).or_else(|_| Ok(ValueType::Unknown)) } } diff --git a/napi/src/js_values/value_type.rs b/napi/src/js_values/value_type.rs index 1c58f26e..5320522a 100644 --- a/napi/src/js_values/value_type.rs +++ b/napi/src/js_values/value_type.rs @@ -1,8 +1,8 @@ -use std::convert::TryInto; +use std::fmt::{Display, Formatter, Result}; -use crate::{sys, Error, Result, Status}; +use crate::sys; -#[repr(u8)] +#[repr(i32)] #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] pub enum ValueType { Undefined = 0, @@ -16,44 +16,31 @@ pub enum ValueType { External = 8, #[cfg(feature = "napi6")] Bigint = 9, - Unknown = 255, + Unknown = 1024, } -impl TryInto for ValueType { - type Error = Error; - - fn try_into(self) -> Result { - match self { - ValueType::Unknown => Err(Error::from_status(Status::Unknown)), - #[cfg(feature = "napi6")] - ValueType::Bigint => Ok(sys::napi_valuetype::napi_bigint), - ValueType::Boolean => Ok(sys::napi_valuetype::napi_boolean), - ValueType::External => Ok(sys::napi_valuetype::napi_external), - ValueType::Function => Ok(sys::napi_valuetype::napi_function), - ValueType::Null => Ok(sys::napi_valuetype::napi_null), - ValueType::Number => Ok(sys::napi_valuetype::napi_number), - ValueType::Object => Ok(sys::napi_valuetype::napi_object), - ValueType::String => Ok(sys::napi_valuetype::napi_string), - ValueType::Symbol => Ok(sys::napi_valuetype::napi_symbol), - ValueType::Undefined => Ok(sys::napi_valuetype::napi_undefined), - } +impl Display for ValueType { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let status_string = format!("{:?}", self); + write!(f, "{}", status_string) } } -impl From for ValueType { - fn from(value: sys::napi_valuetype) -> Self { +impl From for ValueType { + fn from(value: i32) -> ValueType { match value { #[cfg(feature = "napi6")] - sys::napi_valuetype::napi_bigint => ValueType::Bigint, - sys::napi_valuetype::napi_boolean => ValueType::Boolean, - sys::napi_valuetype::napi_external => ValueType::External, - sys::napi_valuetype::napi_function => ValueType::Function, - sys::napi_valuetype::napi_null => ValueType::Null, - sys::napi_valuetype::napi_number => ValueType::Number, - sys::napi_valuetype::napi_object => ValueType::Object, - sys::napi_valuetype::napi_string => ValueType::String, - sys::napi_valuetype::napi_symbol => ValueType::Symbol, - sys::napi_valuetype::napi_undefined => ValueType::Undefined, + sys::ValueType::napi_bigint => ValueType::Bigint, + sys::ValueType::napi_boolean => ValueType::Boolean, + sys::ValueType::napi_external => ValueType::External, + sys::ValueType::napi_function => ValueType::Function, + sys::ValueType::napi_null => ValueType::Null, + sys::ValueType::napi_number => ValueType::Number, + sys::ValueType::napi_object => ValueType::Object, + sys::ValueType::napi_string => ValueType::String, + sys::ValueType::napi_symbol => ValueType::Symbol, + sys::ValueType::napi_undefined => ValueType::Undefined, + _ => ValueType::Unknown, } } } diff --git a/napi/src/promise.rs b/napi/src/promise.rs index a8a6ab79..9483a05a 100644 --- a/napi/src/promise.rs +++ b/napi/src/promise.rs @@ -105,14 +105,11 @@ unsafe extern "C" fn call_js_cb( match js_value_to_resolve { Ok(v) => { let status = sys::napi_resolve_deferred(raw_env, deferred, v.raw()); - debug_assert!( - status == sys::napi_status::napi_ok, - "Resolve promise failed" - ); + debug_assert!(status == sys::Status::napi_ok, "Resolve promise failed"); } Err(e) => { let status = sys::napi_reject_deferred(raw_env, deferred, e.into_raw(raw_env)); - debug_assert!(status == sys::napi_status::napi_ok, "Reject promise failed"); + debug_assert!(status == sys::Status::napi_ok, "Reject promise failed"); } }; check_status(sys::napi_release_threadsafe_function( diff --git a/napi/src/status.rs b/napi/src/status.rs index 31fbbee8..bf9c5933 100644 --- a/napi/src/status.rs +++ b/napi/src/status.rs @@ -1,8 +1,11 @@ -use crate::sys::napi_status; +use std::fmt::{Display, Formatter, Result}; +use crate::sys; + +#[repr(i32)] #[derive(Eq, PartialEq, Debug, Clone, Copy)] pub enum Status { - Ok, + Ok = 0, InvalidArg, ObjectExpected, StringExpected, @@ -17,71 +20,51 @@ pub enum Status { EscapeCalledTwice, HandleScopeMismatch, CallbackScopeMismatch, - #[cfg(feature = "napi4")] + /// ThreadSafeFunction queue is full QueueFull, - #[cfg(feature = "napi4")] + /// ThreadSafeFunction closed Closing, - #[cfg(feature = "napi6")] BigintExpected, - Unknown, + DateExpected, + ArrayBufferExpected, + DetachableArraybufferExpected, + WouldDeadlock, + Unknown = 1024, // unknown status. for example, using napi3 module in napi7 NodeJS, and generate an invalid napi3 status } -impl From for Status { - fn from(code: napi_status) -> Self { - use Status::*; +impl Display for Status { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let status_string = format!("{:?}", self); + write!(f, "{}", status_string) + } +} +impl From for Status { + fn from(code: i32) -> Self { match code { - napi_status::napi_ok => Ok, - napi_status::napi_invalid_arg => InvalidArg, - napi_status::napi_object_expected => ObjectExpected, - napi_status::napi_string_expected => StringExpected, - napi_status::napi_name_expected => NameExpected, - napi_status::napi_function_expected => FunctionExpected, - napi_status::napi_number_expected => NumberExpected, - napi_status::napi_boolean_expected => BooleanExpected, - napi_status::napi_array_expected => ArrayExpected, - napi_status::napi_generic_failure => GenericFailure, - napi_status::napi_pending_exception => PendingException, - napi_status::napi_cancelled => Cancelled, - napi_status::napi_escape_called_twice => EscapeCalledTwice, - napi_status::napi_handle_scope_mismatch => HandleScopeMismatch, - napi_status::napi_callback_scope_mismatch => CallbackScopeMismatch, - #[cfg(feature = "napi4")] - napi_status::napi_queue_full => QueueFull, - #[cfg(feature = "napi4")] - napi_status::napi_closing => Closing, - #[cfg(feature = "napi6")] - napi_status::napi_bigint_expected => BigintExpected, - _ => Unknown, - } - } -} - -impl Into for Status { - fn into(self) -> napi_status { - match self { - Self::Ok => napi_status::napi_ok, - Self::InvalidArg => napi_status::napi_invalid_arg, - Self::ObjectExpected => napi_status::napi_object_expected, - Self::StringExpected => napi_status::napi_string_expected, - Self::NameExpected => napi_status::napi_name_expected, - Self::FunctionExpected => napi_status::napi_function_expected, - Self::NumberExpected => napi_status::napi_number_expected, - Self::BooleanExpected => napi_status::napi_boolean_expected, - Self::ArrayExpected => napi_status::napi_array_expected, - Self::GenericFailure => napi_status::napi_generic_failure, - Self::PendingException => napi_status::napi_pending_exception, - Self::Cancelled => napi_status::napi_cancelled, - Self::EscapeCalledTwice => napi_status::napi_escape_called_twice, - Self::HandleScopeMismatch => napi_status::napi_handle_scope_mismatch, - Self::CallbackScopeMismatch => napi_status::napi_callback_scope_mismatch, - #[cfg(feature = "napi4")] - Self::QueueFull => napi_status::napi_queue_full, - #[cfg(feature = "napi4")] - Self::Closing => napi_status::napi_closing, - #[cfg(feature = "napi6")] - Self::BigintExpected => napi_status::napi_bigint_expected, - Self::Unknown => napi_status::napi_generic_failure, + sys::Status::napi_ok => Status::Ok, + sys::Status::napi_invalid_arg => Status::InvalidArg, + sys::Status::napi_object_expected => Status::ObjectExpected, + sys::Status::napi_string_expected => Status::StringExpected, + sys::Status::napi_name_expected => Status::NameExpected, + sys::Status::napi_function_expected => Status::FunctionExpected, + sys::Status::napi_number_expected => Status::NumberExpected, + sys::Status::napi_boolean_expected => Status::BooleanExpected, + sys::Status::napi_array_expected => Status::ArrayExpected, + sys::Status::napi_generic_failure => Status::GenericFailure, + sys::Status::napi_pending_exception => Status::PendingException, + sys::Status::napi_cancelled => Status::Cancelled, + sys::Status::napi_escape_called_twice => Status::EscapeCalledTwice, + sys::Status::napi_handle_scope_mismatch => Status::HandleScopeMismatch, + sys::Status::napi_callback_scope_mismatch => Status::CallbackScopeMismatch, + sys::Status::napi_queue_full => Status::QueueFull, + sys::Status::napi_closing => Status::Closing, + sys::Status::napi_bigint_expected => Status::BigintExpected, + sys::Status::napi_date_expected => Status::DateExpected, + sys::Status::napi_arraybuffer_expected => Status::ArrayBufferExpected, + sys::Status::napi_detachable_arraybuffer_expected => Status::DetachableArraybufferExpected, + sys::Status::napi_would_deadlock => Status::WouldDeadlock, + _ => Status::Unknown, } } } diff --git a/napi/src/threadsafe_function.rs b/napi/src/threadsafe_function.rs index 936fa8ae..0de8bc22 100644 --- a/napi/src/threadsafe_function.rs +++ b/napi/src/threadsafe_function.rs @@ -2,13 +2,16 @@ use std::convert::Into; use std::marker::PhantomData; use std::os::raw::{c_char, c_void}; use std::ptr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use crate::error::check_status; -use crate::{sys, Env, JsFunction, NapiValue, Result}; +use crate::{sys, Env, Error, JsFunction, NapiValue, Result, Status}; use sys::napi_threadsafe_function_call_mode; -use sys::napi_threadsafe_function_release_mode; +/// ThreadSafeFunction Context object +/// the `value` is the value passed to `call` method pub struct ThreadSafeCallContext { pub env: Env, pub value: T, @@ -20,12 +23,6 @@ pub enum ThreadsafeFunctionCallMode { Blocking, } -#[repr(u8)] -pub enum ThreadsafeFunctionReleaseMode { - Release, - Abort, -} - impl Into for ThreadsafeFunctionCallMode { fn into(self) -> napi_threadsafe_function_call_mode { match self { @@ -39,19 +36,6 @@ impl Into for ThreadsafeFunctionCallMode { } } -impl Into 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 - } - } - } -} - /// Communicate with the addon's main thread by invoking a JavaScript function from other threads. /// /// ## Example @@ -64,40 +48,38 @@ impl Into for ThreadsafeFunctionReleaseMo /// use std::thread; /// /// use napi::{ -/// threadsafe_function::{ -/// ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode, -/// }, -/// CallContext, Error, JsFunction, JsNumber, JsUndefined, Result, Status, +/// threadsafe_function::{ +/// ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode, +/// }, +/// CallContext, Error, JsFunction, JsNumber, JsUndefined, Result, Status, /// }; /// #[js_function(1)] /// pub fn test_threadsafe_function(ctx: CallContext) -> Result { -/// let func = ctx.get::(0)?; +/// let func = ctx.get::(0)?; -/// let tsfn = -/// ctx -/// .env -/// .create_threadsafe_function(func, 0, |ctx: ThreadSafeCallContext>| { -/// ctx -/// .value -/// .iter() -/// .map(|v| ctx.env.create_uint32(*v)) -/// .collect::>>() -/// })?; +/// let tsfn = ctx.env +/// .create_threadsafe_function(func, 0, |ctx: ThreadSafeCallContext>| { +/// ctx.value +/// .iter() +/// .map(|v| ctx.env.create_uint32(*v))] +/// .collect::>>() +/// })?; -/// thread::spawn(move || { -/// let output: Vec = 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); -/// }); +/// thread::spawn(move || { +/// let output: Vec = 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); +/// }); -/// ctx.env.get_undefined() +/// ctx.env.get_undefined() /// } /// ``` pub struct ThreadsafeFunction { raw_tsfn: sys::napi_threadsafe_function, + aborted: Arc, _phantom: PhantomData, } @@ -118,7 +100,7 @@ impl ThreadsafeFunction { R: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, >( env: sys::napi_env, - func: JsFunction, + func: &JsFunction, max_queue_size: usize, callback: R, ) -> Result { @@ -155,59 +137,92 @@ impl ThreadsafeFunction { Ok(ThreadsafeFunction { raw_tsfn, + aborted: Arc::new(AtomicBool::new(false)), _phantom: PhantomData, }) } /// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function) /// for more information. - pub fn call(&self, value: Result, mode: ThreadsafeFunctionCallMode) { - let status = unsafe { + pub fn call(&self, value: Result, mode: ThreadsafeFunctionCallMode) -> Status { + if self.aborted.load(Ordering::Acquire) { + return Status::Closing; + } + unsafe { sys::napi_call_threadsafe_function( self.raw_tsfn, Box::into_raw(Box::new(value)) as *mut _, mode.into(), ) - }; - debug_assert!( - status == sys::napi_status::napi_ok, - "Threadsafe Function call failed" - ); - } - - /// See [napi_acquire_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_acquire_threadsafe_function) - /// for more information. - 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" - ); - } - - /// See [napi_release_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_release_threadsafe_function) - /// for more information. - 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" - ); + } + .into() } /// 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<()> { + pub fn refer(&mut self, env: &Env) -> Result<()> { check_status(unsafe { sys::napi_ref_threadsafe_function(env.0, self.raw_tsfn) }) } /// 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<()> { + pub fn unref(&mut self, env: &Env) -> Result<()> { check_status(unsafe { sys::napi_unref_threadsafe_function(env.0, self.raw_tsfn) }) } + + pub fn aborted(&self) -> bool { + self.aborted.load(Ordering::Acquire) + } + + pub fn abort(self) -> Result<()> { + check_status(unsafe { + 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 { + if self.aborted.load(Ordering::Acquire) { + return Err(Error::new( + Status::Closing, + format!("Thread safe function already aborted"), + )); + } + check_status(unsafe { sys::napi_acquire_threadsafe_function(self.raw_tsfn) })?; + 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 Drop for ThreadsafeFunction { + 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" + ); + } + } } unsafe extern "C" fn thread_finalize_cb( @@ -271,5 +286,5 @@ unsafe extern "C" fn call_js_cb( ); } } - debug_assert!(status == sys::napi_status::napi_ok, "CallJsCB failed"); + debug_assert!(status == sys::Status::napi_ok, "CallJsCB failed"); } diff --git a/sys/src/lib.rs b/sys/src/lib.rs index a90c2bbb..321d51f0 100644 --- a/sys/src/lib.rs +++ b/sys/src/lib.rs @@ -68,71 +68,63 @@ pub enum napi_property_attributes { napi_static = 1 << 10, } -#[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum napi_valuetype { - napi_undefined, - napi_null, - napi_boolean, - napi_number, - napi_string, - napi_symbol, - napi_object, - napi_function, - napi_external, +pub type napi_valuetype = i32; + +pub mod ValueType { + pub const napi_undefined: i32 = 0; + pub const napi_null: i32 = 1; + pub const napi_boolean: i32 = 2; + pub const napi_number: i32 = 3; + pub const napi_string: i32 = 4; + pub const napi_symbol: i32 = 5; + pub const napi_object: i32 = 6; + pub const napi_function: i32 = 7; + pub const napi_external: i32 = 8; #[cfg(feature = "napi6")] - napi_bigint, + pub const napi_bigint: i32 = 9; } -#[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum napi_typedarray_type { - napi_int8_array, - napi_uint8_array, - napi_uint8_clamped_array, - napi_int16_array, - napi_uint16_array, - napi_int32_array, - napi_uint32_array, - napi_float32_array, - napi_float64_array, - #[cfg(feature = "napi6")] - napi_bigint64_array, - #[cfg(feature = "napi6")] - napi_biguint64_array, +pub type napi_typedarray_type = i32; + +pub mod TypedarrayType { + pub const napi_int8_array: i32 = 0; + pub const napi_uint8_array: i32 = 1; + pub const napi_uint8_clamped_array: i32 = 2; + pub const napi_int16_array: i32 = 3; + pub const napi_uint16_array: i32 = 4; + pub const napi_int32_array: i32 = 5; + pub const napi_uint32_array: i32 = 6; + pub const napi_float32_array: i32 = 7; + pub const napi_float64_array: i32 = 8; + pub const napi_bigint64_array: i32 = 9; + pub const napi_biguint64_array: i32 = 10; } -#[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum napi_status { - napi_ok, - napi_invalid_arg, - napi_object_expected, - napi_string_expected, - napi_name_expected, - napi_function_expected, - napi_number_expected, - napi_boolean_expected, - napi_array_expected, - napi_generic_failure, - napi_pending_exception, - napi_cancelled, - napi_escape_called_twice, - napi_handle_scope_mismatch, - napi_callback_scope_mismatch, - #[cfg(feature = "napi4")] - napi_queue_full, - #[cfg(feature = "napi4")] - napi_closing, - #[cfg(feature = "napi6")] - napi_bigint_expected, - #[cfg(feature = "napi6")] - napi_date_expected, - #[cfg(feature = "napi7")] - napi_arraybuffer_expected, - #[cfg(feature = "napi7")] - napi_detachable_arraybuffer_expected, - napi_would_deadlock, // unused +pub type napi_status = i32; + +pub mod Status { + pub const napi_ok: i32 = 0; + pub const napi_invalid_arg: i32 = 1; + pub const napi_object_expected: i32 = 2; + pub const napi_string_expected: i32 = 3; + pub const napi_name_expected: i32 = 4; + pub const napi_function_expected: i32 = 5; + pub const napi_number_expected: i32 = 6; + pub const napi_boolean_expected: i32 = 7; + pub const napi_array_expected: i32 = 8; + pub const napi_generic_failure: i32 = 9; + pub const napi_pending_exception: i32 = 10; + pub const napi_cancelled: i32 = 11; + pub const napi_escape_called_twice: i32 = 12; + pub const napi_handle_scope_mismatch: i32 = 13; + pub const napi_callback_scope_mismatch: i32 = 14; + pub const napi_queue_full: i32 = 15; + pub const napi_closing: i32 = 16; + pub const napi_bigint_expected: i32 = 17; + pub const napi_date_expected: i32 = 18; + pub const napi_arraybuffer_expected: i32 = 19; + pub const napi_detachable_arraybuffer_expected: i32 = 20; + pub const napi_would_deadlock: i32 = 21; // unused } pub type napi_callback = ::std::option::Option< diff --git a/test_module/__test__/napi4/threadsafe_function.spec.ts b/test_module/__test__/napi4/threadsafe_function.spec.ts index da44da31..6f9c2858 100644 --- a/test_module/__test__/napi4/threadsafe_function.spec.ts +++ b/test_module/__test__/napi4/threadsafe_function.spec.ts @@ -16,7 +16,11 @@ test('should get js function called from a thread', async (t) => { bindings.testThreadsafeFunction((...args: any[]) => { called += 1 try { - t.deepEqual(args, [null, 42, 1, 2, 3]) + if (args[1] === 0) { + t.deepEqual(args, [null, 0, 1, 2, 3]) + } else { + t.deepEqual(args, [null, 3, 2, 1, 0]) + } } catch (err) { reject(err) } @@ -27,3 +31,27 @@ test('should get js function called from a thread', async (t) => { }) }) }) + +test('should be able to abort tsfn', (t) => { + if (napiVersion < 4) { + t.is(bindings.testAbortThreadsafeFunction, undefined) + return + } + t.true(bindings.testAbortThreadsafeFunction(() => {})) +}) + +test('should be able to abort independent tsfn', (t) => { + if (napiVersion < 4) { + t.is(bindings.testAbortIndependentThreadsafeFunction, undefined) + return + } + t.false(bindings.testAbortIndependentThreadsafeFunction(() => {})) +}) + +test('should return Closing while calling aborted tsfn', (t) => { + if (napiVersion < 4) { + t.is(bindings.testCallAbortedThreadsafeFunction, undefined) + return + } + t.notThrows(() => bindings.testCallAbortedThreadsafeFunction(() => {})) +}) diff --git a/test_module/src/napi4/mod.rs b/test_module/src/napi4/mod.rs index fd78fc40..5a451c97 100644 --- a/test_module/src/napi4/mod.rs +++ b/test_module/src/napi4/mod.rs @@ -8,5 +8,17 @@ pub fn register_js(module: &mut Module) -> Result<()> { module.create_named_method("testThreadsafeFunction", test_threadsafe_function)?; module.create_named_method("testTsfnError", test_tsfn_error)?; module.create_named_method("testTokioReadfile", test_tokio_readfile)?; + module.create_named_method( + "testAbortThreadsafeFunction", + test_abort_threadsafe_function, + )?; + module.create_named_method( + "testAbortIndependentThreadsafeFunction", + test_abort_independent_threadsafe_function, + )?; + module.create_named_method( + "testCallAbortedThreadsafeFunction", + test_call_aborted_threadsafe_function, + )?; Ok(()) } diff --git a/test_module/src/napi4/tsfn.rs b/test_module/src/napi4/tsfn.rs index c456118d..5508f0fd 100644 --- a/test_module/src/napi4/tsfn.rs +++ b/test_module/src/napi4/tsfn.rs @@ -2,10 +2,8 @@ use std::path::Path; use std::thread; use napi::{ - threadsafe_function::{ - ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode, - }, - CallContext, Error, JsFunction, JsNumber, JsString, JsUndefined, Result, Status, + threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunctionCallMode}, + CallContext, Error, JsBoolean, JsFunction, JsNumber, JsString, JsUndefined, Result, Status, }; use tokio; @@ -16,7 +14,7 @@ pub fn test_threadsafe_function(ctx: CallContext) -> Result { let tsfn = ctx .env - .create_threadsafe_function(func, 0, |ctx: ThreadSafeCallContext>| { + .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext>| { ctx .value .iter() @@ -24,14 +22,80 @@ pub fn test_threadsafe_function(ctx: CallContext) -> Result { .collect::>>() })?; + let tsfn_cloned = tsfn.try_clone()?; + thread::spawn(move || { - let output: Vec = vec![42, 1, 2, 3]; + let output: Vec = vec![0, 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); }); + thread::spawn(move || { + let output: Vec = 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() +} + +#[js_function(1)] +pub fn test_abort_threadsafe_function(ctx: CallContext) -> Result { + let func = ctx.get::(0)?; + + let tsfn = + ctx + .env + .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext>| { + ctx + .value + .iter() + .map(|v| ctx.env.create_uint32(*v)) + .collect::>>() + })?; + + let tsfn_cloned = tsfn.try_clone()?; + + tsfn_cloned.abort()?; + ctx.env.get_boolean(tsfn.aborted()) +} + +#[js_function(1)] +pub fn test_abort_independent_threadsafe_function(ctx: CallContext) -> Result { + let func = ctx.get::(0)?; + + let tsfn = ctx + .env + .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext| { + ctx.env.create_uint32(ctx.value).map(|v| vec![v]) + })?; + + let tsfn_other = + ctx + .env + .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext| { + ctx.env.create_uint32(ctx.value).map(|v| vec![v]) + })?; + + tsfn_other.abort()?; + ctx.env.get_boolean(tsfn.aborted()) +} + +#[js_function(1)] +pub fn test_call_aborted_threadsafe_function(ctx: CallContext) -> Result { + let func = ctx.get::(0)?; + + let tsfn = ctx + .env + .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext| { + ctx.env.create_uint32(ctx.value).map(|v| vec![v]) + })?; + + let tsfn_clone = tsfn.try_clone()?; + tsfn_clone.abort()?; + + let call_status = tsfn.call(Ok(1), ThreadsafeFunctionCallMode::NonBlocking); + assert!(call_status == Status::Closing); ctx.env.get_undefined() } @@ -40,15 +104,14 @@ pub fn test_tsfn_error(ctx: CallContext) -> Result { let func = ctx.get::(0)?; let tsfn = ctx .env - .create_threadsafe_function(func, 0, |ctx: ThreadSafeCallContext<()>| { + .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext<()>| { ctx.env.get_undefined().map(|v| vec![v]) })?; thread::spawn(move || { tsfn.call( - Err(Error::new(Status::Unknown, "invalid".to_owned())), + Err(Error::new(Status::GenericFailure, "invalid".to_owned())), ThreadsafeFunctionCallMode::Blocking, ); - tsfn.release(ThreadsafeFunctionReleaseMode::Release); }); ctx.env.get_undefined() @@ -57,7 +120,7 @@ pub fn test_tsfn_error(ctx: CallContext) -> Result { async fn read_file_content(filepath: &Path) -> Result> { tokio::fs::read(filepath) .await - .map_err(|e| Error::new(Status::Unknown, format!("{}", e))) + .map_err(|e| Error::new(Status::GenericFailure, format!("{}", e))) } #[js_function(2)] @@ -69,7 +132,7 @@ pub fn test_tokio_readfile(ctx: CallContext) -> Result { let tsfn = ctx .env - .create_threadsafe_function(js_func, 0, |ctx: ThreadSafeCallContext>| { + .create_threadsafe_function(&js_func, 0, |ctx: ThreadSafeCallContext>| { ctx .env .create_buffer_with_data(ctx.value) @@ -81,7 +144,6 @@ pub fn test_tokio_readfile(ctx: CallContext) -> Result { rt.block_on(async move { let ret = read_file_content(&Path::new(&path_str)).await; tsfn.call(ret, ThreadsafeFunctionCallMode::Blocking); - tsfn.release(ThreadsafeFunctionReleaseMode::Release); }); ctx.env.get_undefined() diff --git a/test_module/src/tokio_rt/read_file.rs b/test_module/src/tokio_rt/read_file.rs index bd013f23..4a943e57 100644 --- a/test_module/src/tokio_rt/read_file.rs +++ b/test_module/src/tokio_rt/read_file.rs @@ -7,8 +7,14 @@ pub fn test_execute_tokio_readfile(ctx: CallContext) -> Result { let js_filepath = ctx.get::(0)?; let path_str = js_filepath.into_utf8()?.to_owned()?; ctx.env.execute_tokio_future( - tokio::fs::read(path_str) - .map(|v| v.map_err(|e| Error::new(Status::Unknown, format!("failed to read file, {}", e)))), + tokio::fs::read(path_str).map(|v| { + v.map_err(|e| { + Error::new( + Status::GenericFailure, + format!("failed to read file, {}", e), + ) + }) + }), |&mut env, data| env.create_buffer_with_data(data).map(|v| v.into_raw()), ) } @@ -22,7 +28,7 @@ pub fn error_from_tokio_future(ctx: CallContext) -> Result { .map_err(Error::from) .and_then(|_| async move { Err::, Error>(Error::new( - Status::Unknown, + Status::GenericFailure, "Error from tokio future".to_owned(), )) }),