Merge pull request #1395 from napi-rs/tweaks-code
chore(napi): reduce Mutex usage while loading addon
This commit is contained in:
commit
f8d1dcee5b
19 changed files with 165 additions and 344 deletions
|
@ -7,8 +7,6 @@ use std::sync::{
|
|||
Arc,
|
||||
};
|
||||
|
||||
#[cfg(feature = "napi4")]
|
||||
use crate::bindgen_prelude::{CUSTOM_GC_TSFN, CUSTOM_GC_TSFN_CLOSED, MAIN_THREAD_ID};
|
||||
pub use crate::js_values::TypedArrayType;
|
||||
use crate::{check_status, sys, Error, Result, Status};
|
||||
|
||||
|
@ -66,31 +64,6 @@ macro_rules! impl_typed_array {
|
|||
fn drop(&mut self) {
|
||||
if Arc::strong_count(&self.drop_in_vm) == 1 {
|
||||
if let Some((ref_, env)) = self.raw {
|
||||
#[cfg(feature = "napi4")]
|
||||
{
|
||||
if CUSTOM_GC_TSFN_CLOSED.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
if !MAIN_THREAD_ID
|
||||
.get()
|
||||
.map(|id| &std::thread::current().id() == id)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let status = unsafe {
|
||||
sys::napi_call_threadsafe_function(
|
||||
CUSTOM_GC_TSFN.load(std::sync::atomic::Ordering::SeqCst),
|
||||
ref_ as *mut c_void,
|
||||
1,
|
||||
)
|
||||
};
|
||||
assert!(
|
||||
status == sys::Status::napi_ok,
|
||||
"Call custom GC in ArrayBuffer::drop failed {:?}",
|
||||
Status::from(status)
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
crate::check_status_or_throw!(
|
||||
env,
|
||||
unsafe { sys::napi_reference_unref(env, ref_, &mut 0) },
|
||||
|
|
|
@ -52,7 +52,7 @@ impl FromNapiValue for BigInt {
|
|||
ptr::null_mut(),
|
||||
)
|
||||
})?;
|
||||
let mut words: Vec<u64> = Vec::with_capacity(word_count as usize);
|
||||
let mut words: Vec<u64> = Vec::with_capacity(word_count);
|
||||
let mut sign_bit = 0;
|
||||
|
||||
unsafe {
|
||||
|
@ -64,7 +64,7 @@ impl FromNapiValue for BigInt {
|
|||
words.as_mut_ptr(),
|
||||
))?;
|
||||
|
||||
words.set_len(word_count as usize);
|
||||
words.set_len(word_count);
|
||||
}
|
||||
if word_count == 0 {
|
||||
words = vec![0];
|
||||
|
@ -155,7 +155,7 @@ impl ToNapiValue for BigInt {
|
|||
impl ToNapiValue for i128 {
|
||||
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> crate::Result<sys::napi_value> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let sign_bit = if val > 0 { 0 } else { 1 };
|
||||
let sign_bit = i32::from(val <= 0);
|
||||
let words = &val as *const i128 as *const u64;
|
||||
check_status!(unsafe {
|
||||
sys::napi_create_bigint_words(env, sign_bit, 2, words, &mut raw_value)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#[cfg(debug_assertions)]
|
||||
#[cfg(all(debug_assertions, not(windows)))]
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::c_void;
|
||||
use std::mem;
|
||||
|
@ -6,12 +6,12 @@ use std::ops::{Deref, DerefMut};
|
|||
use std::ptr::{self, NonNull};
|
||||
use std::slice;
|
||||
use std::sync::Arc;
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(all(debug_assertions, not(windows)))]
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::{bindgen_prelude::*, check_status, sys, Result, ValueType};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(all(debug_assertions, not(windows)))]
|
||||
thread_local! {
|
||||
pub (crate) static BUFFER_DATA: Mutex<HashSet<*mut u8>> = Default::default();
|
||||
}
|
||||
|
@ -32,31 +32,6 @@ impl Drop for Buffer {
|
|||
fn drop(&mut self) {
|
||||
if Arc::strong_count(&self.ref_count) == 1 {
|
||||
if let Some((ref_, env)) = self.raw {
|
||||
#[cfg(feature = "napi4")]
|
||||
{
|
||||
if CUSTOM_GC_TSFN_CLOSED.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
if !MAIN_THREAD_ID
|
||||
.get()
|
||||
.map(|id| &std::thread::current().id() == id)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let status = unsafe {
|
||||
sys::napi_call_threadsafe_function(
|
||||
CUSTOM_GC_TSFN.load(std::sync::atomic::Ordering::SeqCst),
|
||||
ref_ as *mut c_void,
|
||||
1,
|
||||
)
|
||||
};
|
||||
assert!(
|
||||
status == sys::Status::napi_ok,
|
||||
"Call custom GC in Buffer::drop failed {:?}",
|
||||
Status::from(status)
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let mut ref_count = 0;
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
|
@ -94,7 +69,7 @@ impl Clone for Buffer {
|
|||
impl From<Vec<u8>> for Buffer {
|
||||
fn from(mut data: Vec<u8>) -> Self {
|
||||
let inner_ptr = data.as_mut_ptr();
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(all(debug_assertions, not(windows)))]
|
||||
{
|
||||
let is_existed = BUFFER_DATA.with(|buffer_data| {
|
||||
let buffer = buffer_data.lock().expect("Unlock buffer data failed");
|
||||
|
|
|
@ -77,7 +77,7 @@ impl<T: FromNapiValue> ValidateNapiValue for Promise<T> {
|
|||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: FromNapiValue> Send for Promise<T> {}
|
||||
unsafe impl<T: FromNapiValue + Send> Send for Promise<T> {}
|
||||
|
||||
impl<T: FromNapiValue> FromNapiValue for Promise<T> {
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> {
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::rc::{Rc, Weak};
|
|||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
bindgen_runtime::{PersistedSingleThreadHashMap, ToNapiValue},
|
||||
bindgen_runtime::{PersistedPerInstanceHashMap, ToNapiValue},
|
||||
check_status, Env, Error, Result, Status,
|
||||
};
|
||||
|
||||
|
@ -16,7 +16,7 @@ type RefInformation = (
|
|||
*const Cell<*mut dyn FnOnce()>,
|
||||
);
|
||||
|
||||
pub(crate) static REFERENCE_MAP: Lazy<PersistedSingleThreadHashMap<*mut c_void, RefInformation>> =
|
||||
pub(crate) static REFERENCE_MAP: Lazy<PersistedPerInstanceHashMap<*mut c_void, RefInformation>> =
|
||||
Lazy::new(Default::default);
|
||||
|
||||
/// ### Experimental feature
|
||||
|
@ -59,6 +59,7 @@ impl<T> Drop for Reference<T> {
|
|||
|
||||
impl<T: 'static> Reference<T> {
|
||||
#[doc(hidden)]
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
pub fn add_ref(env: crate::sys::napi_env, t: *mut c_void, value: RefInformation) {
|
||||
REFERENCE_MAP.borrow_mut(|map| {
|
||||
if let Some((_, previous_ref, previous_rc)) = map.insert(t, value) {
|
||||
|
|
|
@ -76,7 +76,7 @@ pub unsafe extern "C" fn drop_buffer(
|
|||
#[allow(unused)] finalize_data: *mut c_void,
|
||||
finalize_hint: *mut c_void,
|
||||
) {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(all(debug_assertions, not(windows)))]
|
||||
{
|
||||
js_values::BUFFER_DATA.with(|buffer_data| {
|
||||
let mut buffer = buffer_data.lock().expect("Unlock Buffer data failed");
|
||||
|
|
|
@ -1,21 +1,11 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
#[cfg(feature = "napi4")]
|
||||
use std::ffi::c_void;
|
||||
use std::ffi::CStr;
|
||||
use std::mem;
|
||||
#[cfg(feature = "napi4")]
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, AtomicPtr, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
#[cfg(feature = "napi4")]
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
|
||||
use std::thread::ThreadId;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
#[cfg(feature = "napi4")]
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use crate::{
|
||||
check_status, check_status_or_throw, sys, Env, JsError, JsFunction, Property, Result, Value,
|
||||
|
@ -26,120 +16,131 @@ pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result<sys::napi_v
|
|||
pub type ModuleExportsCallback =
|
||||
unsafe fn(env: sys::napi_env, exports: sys::napi_value) -> Result<()>;
|
||||
|
||||
#[cfg(feature = "napi4")]
|
||||
pub(crate) static CUSTOM_GC_TSFN: AtomicPtr<sys::napi_threadsafe_function__> =
|
||||
AtomicPtr::new(ptr::null_mut());
|
||||
#[cfg(feature = "napi4")]
|
||||
// CustomGC ThreadsafeFunction may be deleted during the process exit.
|
||||
// And there may still some Buffer alive after that.
|
||||
pub(crate) static CUSTOM_GC_TSFN_CLOSED: AtomicBool = AtomicBool::new(false);
|
||||
#[cfg(feature = "napi4")]
|
||||
pub(crate) static MAIN_THREAD_ID: OnceCell<ThreadId> = OnceCell::new();
|
||||
|
||||
struct PersistedSingleThreadVec<T> {
|
||||
inner: Mutex<Vec<T>>,
|
||||
struct PersistedPerInstanceVec<T> {
|
||||
inner: AtomicPtr<T>,
|
||||
length: AtomicUsize,
|
||||
}
|
||||
|
||||
impl<T> Default for PersistedSingleThreadVec<T> {
|
||||
impl<T> Default for PersistedPerInstanceVec<T> {
|
||||
fn default() -> Self {
|
||||
PersistedSingleThreadVec {
|
||||
inner: Mutex::new(Vec::new()),
|
||||
}
|
||||
let mut vec: Vec<T> = Vec::with_capacity(1);
|
||||
let ret = Self {
|
||||
inner: AtomicPtr::new(vec.as_mut_ptr()),
|
||||
length: AtomicUsize::new(0),
|
||||
};
|
||||
std::mem::forget(vec);
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PersistedSingleThreadVec<T> {
|
||||
impl<T> PersistedPerInstanceVec<T> {
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
fn borrow_mut<F>(&self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut [T]),
|
||||
{
|
||||
let mut locked = self
|
||||
.inner
|
||||
.lock()
|
||||
.expect("Acquire persisted thread vec lock failed");
|
||||
f(&mut locked);
|
||||
let length = self.length.load(Ordering::Relaxed);
|
||||
if length == 0 {
|
||||
f(&mut []);
|
||||
} else {
|
||||
let inner = self.inner.load(Ordering::Relaxed);
|
||||
let mut temp = unsafe { Vec::from_raw_parts(inner, length, length) };
|
||||
f(temp.as_mut_slice());
|
||||
// Inner Vec has been reallocated, so we need to update the pointer
|
||||
if temp.as_mut_ptr() != inner {
|
||||
self.inner.store(temp.as_mut_ptr(), Ordering::Relaxed);
|
||||
}
|
||||
self.length.store(temp.len(), Ordering::Relaxed);
|
||||
std::mem::forget(temp);
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&self, item: T) {
|
||||
let mut locked = self
|
||||
.inner
|
||||
.lock()
|
||||
.expect("Acquire persisted thread vec lock failed");
|
||||
locked.push(item);
|
||||
let length = self.length.load(Ordering::Relaxed);
|
||||
let inner = self.inner.load(Ordering::Relaxed);
|
||||
let mut temp = unsafe { Vec::from_raw_parts(inner, length, length) };
|
||||
temp.push(item);
|
||||
// Inner Vec has been reallocated, so we need to update the pointer
|
||||
if temp.as_mut_ptr() != inner {
|
||||
self.inner.store(temp.as_mut_ptr(), Ordering::Relaxed);
|
||||
}
|
||||
std::mem::forget(temp);
|
||||
|
||||
self.length.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for PersistedSingleThreadVec<T> {}
|
||||
unsafe impl<T> Sync for PersistedSingleThreadVec<T> {}
|
||||
unsafe impl<T: Send> Send for PersistedPerInstanceVec<T> {}
|
||||
unsafe impl<T: Sync> Sync for PersistedPerInstanceVec<T> {}
|
||||
|
||||
pub(crate) struct PersistedSingleThreadHashMap<K, V>(Mutex<HashMap<K, V>>);
|
||||
pub(crate) struct PersistedPerInstanceHashMap<K, V>(*mut HashMap<K, V>);
|
||||
|
||||
impl<K, V> PersistedPerInstanceHashMap<K, V> {
|
||||
pub(crate) fn from_hashmap(hashmap: HashMap<K, V>) -> Self {
|
||||
Self(Box::into_raw(Box::new(hashmap)))
|
||||
}
|
||||
|
||||
impl<K, V> PersistedSingleThreadHashMap<K, V> {
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub(crate) fn borrow_mut<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut HashMap<K, V>) -> R,
|
||||
{
|
||||
let mut lock = self
|
||||
.0
|
||||
.lock()
|
||||
.expect("Acquire persisted thread hash map lock failed");
|
||||
f(&mut *lock)
|
||||
f(unsafe { Box::leak(Box::from_raw(self.0)) })
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Default for PersistedSingleThreadHashMap<K, V> {
|
||||
impl<K, V> Default for PersistedPerInstanceHashMap<K, V> {
|
||||
fn default() -> Self {
|
||||
PersistedSingleThreadHashMap(Mutex::new(Default::default()))
|
||||
let map = Default::default();
|
||||
Self(Box::into_raw(Box::new(map)))
|
||||
}
|
||||
}
|
||||
|
||||
type ModuleRegisterCallback =
|
||||
PersistedSingleThreadVec<(Option<&'static str>, (&'static str, ExportRegisterCallback))>;
|
||||
PersistedPerInstanceVec<(Option<&'static str>, (&'static str, ExportRegisterCallback))>;
|
||||
|
||||
type ModuleClassProperty = PersistedSingleThreadHashMap<
|
||||
type ModuleClassProperty = PersistedPerInstanceHashMap<
|
||||
&'static str,
|
||||
HashMap<Option<&'static str>, (&'static str, Vec<Property>)>,
|
||||
>;
|
||||
|
||||
unsafe impl<K, V> Send for PersistedSingleThreadHashMap<K, V> {}
|
||||
unsafe impl<K, V> Sync for PersistedSingleThreadHashMap<K, V> {}
|
||||
unsafe impl<K, V> Send for PersistedPerInstanceHashMap<K, V> {}
|
||||
unsafe impl<K, V> Sync for PersistedPerInstanceHashMap<K, V> {}
|
||||
|
||||
type FnRegisterMap =
|
||||
PersistedSingleThreadHashMap<ExportRegisterCallback, (sys::napi_callback, &'static str)>;
|
||||
PersistedPerInstanceHashMap<ExportRegisterCallback, (sys::napi_callback, &'static str)>;
|
||||
type RegisteredClassesMap = PersistedPerInstanceHashMap<ThreadId, RegisteredClasses>;
|
||||
|
||||
static MODULE_REGISTER_CALLBACK: Lazy<ModuleRegisterCallback> = Lazy::new(Default::default);
|
||||
static MODULE_CLASS_PROPERTIES: Lazy<ModuleClassProperty> = Lazy::new(Default::default);
|
||||
static MODULE_REGISTER_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
|
||||
static REGISTERED: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
|
||||
static REGISTERED_CLASSES: Lazy<thread_local::ThreadLocal<AtomicPtr<RegisteredClasses>>> =
|
||||
Lazy::new(thread_local::ThreadLocal::new);
|
||||
static IS_FIRST_MODULE: AtomicBool = AtomicBool::new(true);
|
||||
static FIRST_MODULE_REGISTERED: AtomicBool = AtomicBool::new(false);
|
||||
static REGISTERED_CLASSES: Lazy<RegisteredClassesMap> = Lazy::new(Default::default);
|
||||
static FN_REGISTER_MAP: Lazy<FnRegisterMap> = Lazy::new(Default::default);
|
||||
|
||||
type RegisteredClasses =
|
||||
PersistedPerInstanceHashMap</* export name */ String, /* constructor */ sys::napi_ref>;
|
||||
|
||||
#[cfg(feature = "compat-mode")]
|
||||
// compatibility for #[module_exports]
|
||||
static MODULE_EXPORTS: Lazy<PersistedPerInstanceVec<ModuleExportsCallback>> =
|
||||
Lazy::new(Default::default);
|
||||
|
||||
#[inline]
|
||||
fn wait_first_thread_registered() {
|
||||
while !REGISTERED.load(Ordering::SeqCst) {
|
||||
while !FIRST_MODULE_REGISTERED.load(Ordering::SeqCst) {
|
||||
std::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
|
||||
type RegisteredClasses =
|
||||
HashMap</* export name */ String, /* constructor */ sys::napi_ref>;
|
||||
|
||||
#[cfg(feature = "compat-mode")]
|
||||
// compatibility for #[module_exports]
|
||||
|
||||
static MODULE_EXPORTS: Lazy<PersistedSingleThreadVec<ModuleExportsCallback>> =
|
||||
Lazy::new(Default::default);
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn get_class_constructor(js_name: &'static str) -> Option<sys::napi_ref> {
|
||||
wait_first_thread_registered();
|
||||
let registered_classes = REGISTERED_CLASSES.get().unwrap();
|
||||
let registered_classes =
|
||||
Box::leak(unsafe { Box::from_raw(registered_classes.load(Ordering::Relaxed)) });
|
||||
registered_classes.get(js_name).copied()
|
||||
let current_id = std::thread::current().id();
|
||||
REGISTERED_CLASSES.borrow_mut(|map| {
|
||||
map
|
||||
.get(¤t_id)
|
||||
.map(|m| m.borrow_mut(|map| map.get(js_name).copied()))
|
||||
})?
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -203,7 +204,6 @@ pub fn register_class(
|
|||
/// ```
|
||||
///
|
||||
pub fn get_js_function(env: &Env, raw_fn: ExportRegisterCallback) -> Result<JsFunction> {
|
||||
wait_first_thread_registered();
|
||||
FN_REGISTER_MAP.borrow_mut(|inner| {
|
||||
inner
|
||||
.get(&raw_fn)
|
||||
|
@ -257,7 +257,6 @@ pub fn get_js_function(env: &Env, raw_fn: ExportRegisterCallback) -> Result<JsFu
|
|||
/// ```
|
||||
///
|
||||
pub fn get_c_callback(raw_fn: ExportRegisterCallback) -> Result<crate::Callback> {
|
||||
wait_first_thread_registered();
|
||||
FN_REGISTER_MAP.borrow_mut(|inner| {
|
||||
inner
|
||||
.get(&raw_fn)
|
||||
|
@ -271,20 +270,25 @@ pub fn get_c_callback(raw_fn: ExportRegisterCallback) -> Result<crate::Callback>
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[ctor::ctor]
|
||||
fn load_host() {
|
||||
unsafe {
|
||||
sys::setup();
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn napi_register_module_v1(
|
||||
env: sys::napi_env,
|
||||
exports: sys::napi_value,
|
||||
) -> sys::napi_value {
|
||||
#[cfg(windows)]
|
||||
unsafe {
|
||||
sys::setup();
|
||||
if IS_FIRST_MODULE.load(Ordering::SeqCst) {
|
||||
IS_FIRST_MODULE.store(false, Ordering::SeqCst);
|
||||
} else {
|
||||
wait_first_thread_registered();
|
||||
}
|
||||
crate::__private::___CALL_FROM_FACTORY.get_or_default();
|
||||
let registered_classes_ptr = REGISTERED_CLASSES.get_or_default();
|
||||
let lock = MODULE_REGISTER_LOCK
|
||||
.lock()
|
||||
.expect("Failed to acquire module register lock");
|
||||
let mut exports_objects: HashSet<String> = HashSet::default();
|
||||
MODULE_REGISTER_CALLBACK.borrow_mut(|inner| {
|
||||
inner
|
||||
|
@ -360,8 +364,7 @@ unsafe extern "C" fn napi_register_module_v1(
|
|||
})
|
||||
});
|
||||
|
||||
let mut registered_classes: RegisteredClasses =
|
||||
HashMap::with_capacity(MODULE_CLASS_PROPERTIES.borrow_mut(|inner| inner.len()));
|
||||
let mut registered_classes = HashMap::new();
|
||||
|
||||
MODULE_CLASS_PROPERTIES.borrow_mut(|inner| {
|
||||
inner.iter().for_each(|(rust_name, js_mods)| {
|
||||
|
@ -447,10 +450,13 @@ unsafe extern "C" fn napi_register_module_v1(
|
|||
}
|
||||
}
|
||||
});
|
||||
registered_classes_ptr.store(
|
||||
Box::into_raw(Box::new(registered_classes)),
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
|
||||
REGISTERED_CLASSES.borrow_mut(|map| {
|
||||
map.insert(
|
||||
std::thread::current().id(),
|
||||
PersistedPerInstanceHashMap::from_hashmap(registered_classes),
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
#[cfg(feature = "compat-mode")]
|
||||
|
@ -462,26 +468,18 @@ unsafe extern "C" fn napi_register_module_v1(
|
|||
})
|
||||
});
|
||||
|
||||
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
|
||||
#[cfg(all(windows, feature = "napi4", feature = "tokio_rt"))]
|
||||
{
|
||||
let _ = crate::tokio_runtime::RT.clone();
|
||||
crate::tokio_runtime::TOKIO_RT_REF_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
sys::napi_add_env_cleanup_hook(
|
||||
env,
|
||||
Some(crate::shutdown_tokio_rt),
|
||||
env as *mut std::ffi::c_void,
|
||||
)
|
||||
},
|
||||
sys::Status::napi_ok
|
||||
);
|
||||
crate::tokio_runtime::RT_REFERENCE_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
unsafe {
|
||||
sys::napi_add_env_cleanup_hook(
|
||||
env,
|
||||
Some(crate::tokio_runtime::drop_runtime),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
}
|
||||
mem::drop(lock);
|
||||
|
||||
#[cfg(feature = "napi4")]
|
||||
create_custom_gc(env);
|
||||
REGISTERED.store(true, Ordering::SeqCst);
|
||||
FIRST_MODULE_REGISTERED.store(true, Ordering::SeqCst);
|
||||
exports
|
||||
}
|
||||
|
||||
|
@ -502,99 +500,3 @@ pub(crate) unsafe extern "C" fn noop(
|
|||
}
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi4")]
|
||||
fn create_custom_gc(env: sys::napi_env) {
|
||||
let mut custom_gc_fn = ptr::null_mut();
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
unsafe {
|
||||
sys::napi_create_function(
|
||||
env,
|
||||
"custom_gc".as_ptr() as *const c_char,
|
||||
9,
|
||||
Some(empty),
|
||||
ptr::null_mut(),
|
||||
&mut custom_gc_fn,
|
||||
)
|
||||
},
|
||||
"Create Custom GC Function in napi_register_module_v1 failed"
|
||||
);
|
||||
let mut async_resource_name = ptr::null_mut();
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
unsafe {
|
||||
sys::napi_create_string_utf8(
|
||||
env,
|
||||
"CustomGC".as_ptr() as *const c_char,
|
||||
8,
|
||||
&mut async_resource_name,
|
||||
)
|
||||
},
|
||||
"Create async resource string in napi_register_module_v1 napi_register_module_v1"
|
||||
);
|
||||
let mut custom_gc_tsfn = ptr::null_mut();
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
unsafe {
|
||||
sys::napi_create_threadsafe_function(
|
||||
env,
|
||||
custom_gc_fn,
|
||||
ptr::null_mut(),
|
||||
async_resource_name,
|
||||
0,
|
||||
1,
|
||||
ptr::null_mut(),
|
||||
Some(custom_gc_finalize),
|
||||
ptr::null_mut(),
|
||||
Some(custom_gc),
|
||||
&mut custom_gc_tsfn,
|
||||
)
|
||||
},
|
||||
"Create Custom GC ThreadsafeFunction in napi_register_module_v1 failed"
|
||||
);
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
unsafe { sys::napi_unref_threadsafe_function(env, custom_gc_tsfn) },
|
||||
"Unref Custom GC ThreadsafeFunction in napi_register_module_v1 failed"
|
||||
);
|
||||
CUSTOM_GC_TSFN.store(custom_gc_tsfn, Ordering::SeqCst);
|
||||
MAIN_THREAD_ID.get_or_init(|| std::thread::current().id());
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi4")]
|
||||
#[allow(unused)]
|
||||
unsafe extern "C" fn empty(env: sys::napi_env, info: sys::napi_callback_info) -> sys::napi_value {
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi4")]
|
||||
#[allow(unused)]
|
||||
unsafe extern "C" fn custom_gc_finalize(
|
||||
env: sys::napi_env,
|
||||
finalize_data: *mut c_void,
|
||||
finalize_hint: *mut c_void,
|
||||
) {
|
||||
CUSTOM_GC_TSFN_CLOSED.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi4")]
|
||||
// recycle the ArrayBuffer/Buffer Reference if the ArrayBuffer/Buffer is not dropped on the main thread
|
||||
extern "C" fn custom_gc(
|
||||
env: sys::napi_env,
|
||||
_js_callback: sys::napi_value,
|
||||
_context: *mut c_void,
|
||||
data: *mut c_void,
|
||||
) {
|
||||
let mut ref_count = 0;
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
unsafe { sys::napi_reference_unref(env, data as sys::napi_ref, &mut ref_count) },
|
||||
"Failed to unref Buffer reference in Custom GC"
|
||||
);
|
||||
check_status_or_throw!(
|
||||
env,
|
||||
unsafe { sys::napi_delete_reference(env, data as sys::napi_ref) },
|
||||
"Failed to delete Buffer reference in Custom GC"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ impl Env {
|
|||
#[cfg(feature = "napi6")]
|
||||
pub fn create_bigint_from_i128(&self, value: i128) -> Result<JsBigInt> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let sign_bit = if value > 0 { 0 } else { 1 };
|
||||
let sign_bit = i32::from(value <= 0);
|
||||
let words = &value as *const i128 as *const u64;
|
||||
check_status!(unsafe {
|
||||
sys::napi_create_bigint_words(self.0, sign_bit, 2, words, &mut raw_value)
|
||||
|
|
|
@ -204,7 +204,7 @@ impl TryFrom<JsBigInt> for u64 {
|
|||
impl JsBigInt {
|
||||
/// <https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_words>
|
||||
pub fn get_words(&mut self) -> Result<(bool, Vec<u64>)> {
|
||||
let mut words: Vec<u64> = Vec::with_capacity(self.word_count as usize);
|
||||
let mut words: Vec<u64> = Vec::with_capacity(self.word_count);
|
||||
let word_count = &mut self.word_count;
|
||||
let mut sign_bit = 0;
|
||||
check_status!(unsafe {
|
||||
|
@ -218,7 +218,7 @@ impl JsBigInt {
|
|||
})?;
|
||||
|
||||
unsafe {
|
||||
words.set_len(self.word_count as usize);
|
||||
words.set_len(self.word_count);
|
||||
};
|
||||
|
||||
Ok((sign_bit == 1, words))
|
||||
|
|
|
@ -35,7 +35,7 @@ impl JsString {
|
|||
check_status!(unsafe {
|
||||
sys::napi_get_value_string_utf8(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length)
|
||||
})?;
|
||||
Ok(length as usize)
|
||||
Ok(length)
|
||||
}
|
||||
|
||||
pub fn utf16_len(&self) -> Result<usize> {
|
||||
|
@ -43,7 +43,7 @@ impl JsString {
|
|||
check_status!(unsafe {
|
||||
sys::napi_get_value_string_utf16(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length)
|
||||
})?;
|
||||
Ok(length as usize)
|
||||
Ok(length)
|
||||
}
|
||||
|
||||
pub fn latin1_len(&self) -> Result<usize> {
|
||||
|
@ -51,7 +51,7 @@ impl JsString {
|
|||
check_status!(unsafe {
|
||||
sys::napi_get_value_string_latin1(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length)
|
||||
})?;
|
||||
Ok(length as usize)
|
||||
Ok(length)
|
||||
}
|
||||
|
||||
pub fn into_utf8(self) -> Result<JsStringUtf8> {
|
||||
|
|
|
@ -109,8 +109,6 @@ pub use error::*;
|
|||
pub use js_values::*;
|
||||
pub use status::Status;
|
||||
pub use task::Task;
|
||||
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
|
||||
pub use tokio_runtime::shutdown_tokio_rt;
|
||||
pub use value_type::*;
|
||||
pub use version::NodeVersion;
|
||||
#[cfg(feature = "serde-json")]
|
||||
|
|
|
@ -1,54 +1,33 @@
|
|||
use std::ffi::c_void;
|
||||
use std::future::Future;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::{
|
||||
runtime::Handle,
|
||||
sync::mpsc::{self, error::TrySendError},
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::{check_status, sys, JsDeferred, JsUnknown, NapiValue, Result};
|
||||
|
||||
pub(crate) static RT: Lazy<(Handle, mpsc::Sender<()>)> = Lazy::new(|| {
|
||||
let runtime = tokio::runtime::Runtime::new();
|
||||
let (sender, mut receiver) = mpsc::channel::<()>(1);
|
||||
runtime
|
||||
.map(|rt| {
|
||||
let h = rt.handle();
|
||||
let handle = h.clone();
|
||||
handle.spawn(async move {
|
||||
if receiver.recv().await.is_some() {
|
||||
rt.shutdown_background();
|
||||
}
|
||||
});
|
||||
|
||||
(handle, sender)
|
||||
})
|
||||
.expect("Create tokio runtime failed")
|
||||
pub(crate) static mut RT: Lazy<Option<Runtime>> = Lazy::new(|| {
|
||||
let runtime = tokio::runtime::Runtime::new().expect("Create tokio runtime failed");
|
||||
Some(runtime)
|
||||
});
|
||||
|
||||
pub(crate) static TOKIO_RT_REF_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
#[cfg(windows)]
|
||||
pub(crate) static RT_REFERENCE_COUNT: std::sync::atomic::AtomicUsize =
|
||||
std::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline(never)]
|
||||
pub unsafe extern "C" fn shutdown_tokio_rt(arg: *mut c_void) {
|
||||
if TOKIO_RT_REF_COUNT.fetch_sub(1, Ordering::SeqCst) == 0 {
|
||||
let sender = &RT.1;
|
||||
if let Err(e) = sender.clone().try_send(()) {
|
||||
match e {
|
||||
TrySendError::Closed(_) => {}
|
||||
TrySendError::Full(_) => {
|
||||
panic!("Send shutdown signal to tokio runtime failed, queue is full");
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
pub(crate) unsafe extern "C" fn drop_runtime(arg: *mut std::ffi::c_void) {
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
if RT_REFERENCE_COUNT.fetch_sub(1, Ordering::SeqCst) == 1 {
|
||||
if let Some(rt) = Lazy::get_mut(unsafe { &mut RT }) {
|
||||
rt.take();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let env: sys::napi_env = arg as *mut sys::napi_env__;
|
||||
sys::napi_remove_env_cleanup_hook(env, Some(shutdown_tokio_rt), arg);
|
||||
sys::napi_remove_env_cleanup_hook(env, Some(drop_runtime), arg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +39,7 @@ pub fn spawn<F>(fut: F) -> tokio::task::JoinHandle<F::Output>
|
|||
where
|
||||
F: 'static + Send + Future<Output = ()>,
|
||||
{
|
||||
RT.0.spawn(fut)
|
||||
unsafe { RT.as_ref() }.unwrap().spawn(fut)
|
||||
}
|
||||
|
||||
/// Runs a future to completion
|
||||
|
@ -70,7 +49,7 @@ pub fn block_on<F>(fut: F) -> F::Output
|
|||
where
|
||||
F: 'static + Send + Future<Output = ()>,
|
||||
{
|
||||
RT.0.block_on(fut)
|
||||
unsafe { RT.as_ref() }.unwrap().block_on(fut)
|
||||
}
|
||||
|
||||
// This function's signature must be kept in sync with the one in lib.rs, otherwise napi
|
||||
|
@ -78,8 +57,9 @@ where
|
|||
|
||||
/// If the feature `tokio_rt` has been enabled this will enter the runtime context and
|
||||
/// then call the provided closure. Otherwise it will just call the provided closure.
|
||||
#[inline]
|
||||
pub fn within_runtime_if_available<F: FnOnce() -> T, T>(f: F) -> T {
|
||||
let _rt_guard = RT.0.enter();
|
||||
let _rt_guard = unsafe { RT.as_ref() }.unwrap().enter();
|
||||
f()
|
||||
}
|
||||
|
||||
|
|
|
@ -738,7 +738,7 @@ pub use napi7::*;
|
|||
pub use napi8::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(super) unsafe fn load() -> Result<(), libloading::Error> {
|
||||
pub(super) unsafe fn load() -> Result<libloading::Library, libloading::Error> {
|
||||
let host = match libloading::os::windows::Library::this() {
|
||||
Ok(lib) => lib.into(),
|
||||
Err(err) => {
|
||||
|
@ -764,5 +764,5 @@ pub(super) unsafe fn load() -> Result<(), libloading::Error> {
|
|||
napi8::load(&host)?;
|
||||
#[cfg(feature = "experimental")]
|
||||
experimental::load(&host)?;
|
||||
Ok(())
|
||||
Ok(host)
|
||||
}
|
||||
|
|
|
@ -77,28 +77,21 @@ macro_rules! generate {
|
|||
};
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::sync::Once;
|
||||
|
||||
mod functions;
|
||||
mod types;
|
||||
|
||||
pub use functions::*;
|
||||
pub use types::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
static SETUP: Once = Once::new();
|
||||
|
||||
/// Loads N-API symbols from host process.
|
||||
/// Must be called at least once before using any functions in bindings or
|
||||
/// they will panic.
|
||||
/// Safety: `env` must be a valid `napi_env` for the current thread
|
||||
#[cfg(windows)]
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub unsafe fn setup() {
|
||||
SETUP.call_once(|| {
|
||||
if let Err(err) = load() {
|
||||
panic!("{}", err);
|
||||
}
|
||||
});
|
||||
pub unsafe fn setup() -> libloading::Library {
|
||||
match load() {
|
||||
Err(err) => panic!("{}", err),
|
||||
Ok(l) => l,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ fn test_delete_named_property(ctx: CallContext) -> Result<JsBoolean> {
|
|||
fn test_get_property(ctx: CallContext) -> Result<JsUnknown> {
|
||||
let obj = ctx.get::<JsObject>(0)?;
|
||||
let key = ctx.get::<JsUnknown>(1)?;
|
||||
obj.get_property(&key)
|
||||
obj.get_property(key)
|
||||
}
|
||||
|
||||
#[js_function(1)]
|
||||
|
|
11
examples/napi/__test__/unload.spec.js
Normal file
11
examples/napi/__test__/unload.spec.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
// use the commonjs syntax to prevent compiler from transpiling the module syntax
|
||||
|
||||
const test = require('ava').default
|
||||
|
||||
test('unload module', (t) => {
|
||||
const { add } = require('../index.node')
|
||||
t.is(add(1, 2), 3)
|
||||
delete require.cache[require.resolve('../index.node')]
|
||||
const { add: add2 } = require('../index.node')
|
||||
t.is(add2(1, 2), 3)
|
||||
})
|
|
@ -22,13 +22,7 @@ fn either3(input: Either3<String, u32, bool>) -> u32 {
|
|||
match input {
|
||||
Either3::A(s) => s.len() as u32,
|
||||
Either3::B(n) => n,
|
||||
Either3::C(b) => {
|
||||
if b {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Either3::C(b) => u32::from(b),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,13 +36,7 @@ fn either4(input: Either4<String, u32, bool, Obj>) -> u32 {
|
|||
match input {
|
||||
Either4::A(s) => s.len() as u32,
|
||||
Either4::B(n) => n,
|
||||
Either4::C(b) => {
|
||||
if b {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Either4::C(b) => u32::from(b),
|
||||
Either4::D(f) => match f.v {
|
||||
Either::A(s) => s.len() as u32,
|
||||
Either::B(n) => n,
|
||||
|
|
|
@ -69,5 +69,5 @@ impl Task for AsyncBuffer {
|
|||
|
||||
#[napi]
|
||||
fn async_reduce_buffer(buf: Buffer) -> Result<AsyncTask<AsyncBuffer>> {
|
||||
Ok(AsyncTask::new(AsyncBuffer { buf: buf.clone() }))
|
||||
Ok(AsyncTask::new(AsyncBuffer { buf }))
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ pub fn test_async(env: Env) -> napi::Result<napi::JsObject> {
|
|||
|
||||
#[napi]
|
||||
pub fn from_js(env: Env, input_object: Object) -> napi::Result<String> {
|
||||
let a: Welcome = env.from_js_value(&input_object)?;
|
||||
let a: Welcome = env.from_js_value(input_object)?;
|
||||
Ok(serde_json::to_string(&a)?)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue