diff --git a/.cirrus.yml b/.cirrus.yml index d8fe5484..ad1a653c 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -33,7 +33,7 @@ build_and_test: &BUILD_AND_TEST freebsd_task: name: FreeBSD freebsd_instance: - image: freebsd-13-1-release-amd64 + image: freebsd-13-2-release-amd64 env: RUSTUP_HOME: /usr/local/rustup CARGO_HOME: /usr/local/cargo diff --git a/crates/backend/src/codegen/const.rs b/crates/backend/src/codegen/const.rs index fe67f79e..8d217571 100644 --- a/crates/backend/src/codegen/const.rs +++ b/crates/backend/src/codegen/const.rs @@ -41,7 +41,7 @@ impl NapiConst { } #[allow(non_snake_case)] #[allow(clippy::all)] - #[cfg(all(not(test), not(feature = "noop"), not(target_arch = "wasm32")))] + #[cfg(all(not(test), not(feature = "noop"), not(target_os = "wasi")))] #[napi::bindgen_prelude::ctor] fn #register_name() { napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #cb_name); @@ -49,7 +49,7 @@ impl NapiConst { #[allow(non_snake_case)] #[allow(clippy::all)] - #[cfg(all(not(test), not(feature = "noop"), target_arch = "wasm32"))] + #[cfg(all(not(test), not(feature = "noop"), target_os = "wasi"))] #[no_mangle] unsafe extern "C" fn #register_name() { napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #cb_name); diff --git a/crates/backend/src/codegen/enum.rs b/crates/backend/src/codegen/enum.rs index 2c923f29..0be465d8 100644 --- a/crates/backend/src/codegen/enum.rs +++ b/crates/backend/src/codegen/enum.rs @@ -158,14 +158,14 @@ impl NapiEnum { } #[allow(non_snake_case)] #[allow(clippy::all)] - #[cfg(all(not(test), not(feature = "noop"), not(target_arch = "wasm32")))] + #[cfg(all(not(test), not(feature = "noop"), not(target_os = "wasi")))] #[napi::bindgen_prelude::ctor] fn #register_name() { napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #callback_name); } #[allow(non_snake_case)] #[allow(clippy::all)] - #[cfg(all(not(test), not(feature = "noop"), target_arch = "wasm32"))] + #[cfg(all(not(test), not(feature = "noop"), target_os = "wasi"))] #[no_mangle] extern "C" fn #register_name() { napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #callback_name); diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index 2c6eb00d..9bf200b4 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -589,7 +589,7 @@ impl NapiFn { #[allow(clippy::all)] #[allow(non_snake_case)] - #[cfg(all(not(test), not(feature = "noop"), not(target_arch = "wasm32")))] + #[cfg(all(not(test), not(feature = "noop"), not(target_os = "wasi")))] #[napi::bindgen_prelude::ctor] fn #module_register_name() { napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name); @@ -597,7 +597,7 @@ impl NapiFn { #[allow(clippy::all)] #[allow(non_snake_case)] - #[cfg(all(not(test), not(feature = "noop"), target_arch = "wasm32"))] + #[cfg(all(not(test), not(feature = "noop"), target_os = "wasi"))] #[no_mangle] extern "C" fn #module_register_name() { napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name); diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index 3a105acc..bdbd9304 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -778,7 +778,7 @@ impl NapiStruct { quote! { #[allow(non_snake_case)] #[allow(clippy::all)] - #[cfg(all(not(test), not(feature = "noop"), not(target_arch = "wasm32")))] + #[cfg(all(not(test), not(feature = "noop"), not(target_os = "wasi")))] #[napi::bindgen_prelude::ctor] fn #struct_register_name() { napi::__private::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]); @@ -786,7 +786,7 @@ impl NapiStruct { #[allow(non_snake_case)] #[allow(clippy::all)] - #[cfg(all(not(test), not(feature = "noop"), target_arch = "wasm32"))] + #[cfg(all(not(test), not(feature = "noop"), target_os = "wasi"))] #[no_mangle] extern "C" fn #struct_register_name() { napi::__private::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]); @@ -901,13 +901,13 @@ impl NapiImpl { use super::*; #(#methods)* - #[cfg(all(not(test), not(feature = "noop"), not(target_arch = "wasm32")))] + #[cfg(all(not(test), not(feature = "noop"), not(target_os = "wasi")))] #[napi::bindgen_prelude::ctor] fn #register_name() { napi::__private::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]); } - #[cfg(all(not(test), not(feature = "noop"), target_arch = "wasm32"))] + #[cfg(all(not(test), not(feature = "noop"), target_os = "wasi"))] #[no_mangle] extern "C" fn #register_name() { napi::__private::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props_wasm),*]); diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs index d72a8e6e..bd7c09bc 100644 --- a/crates/macro/src/lib.rs +++ b/crates/macro/src/lib.rs @@ -151,7 +151,7 @@ pub fn module_exports(_attr: TokenStream, input: TokenStream) -> TokenStream { }; let register = quote! { - #[cfg_attr(not(target_arch = "wasm32"), napi::bindgen_prelude::ctor)] + #[cfg_attr(not(target_os = "wasi"), napi::bindgen_prelude::ctor)] fn __napi__explicit_module_register() { unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> { use napi::{Env, JsObject, NapiValue}; diff --git a/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs b/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs index e479b269..ae2d4145 100644 --- a/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs +++ b/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs @@ -8,7 +8,7 @@ use std::sync::{ }; #[cfg(all(feature = "napi4", not(target_os = "wasi")))] -use crate::bindgen_prelude::{CUSTOM_GC_TSFN, THREADS_CAN_ACCESS_ENV, THREAD_DESTROYED}; +use crate::bindgen_prelude::{CUSTOM_GC_TSFN, CUSTOM_GC_TSFN_DESTROYED, THREADS_CAN_ACCESS_ENV}; pub use crate::js_values::TypedArrayType; use crate::{check_status, sys, Error, Result, Status}; @@ -77,14 +77,17 @@ 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(all(feature = "napi4", not(target_arch = "wasm32")))] + if ref_.is_null() { + return; + } + #[cfg(not(target_os = "wasi"))] + if CUSTOM_GC_TSFN_DESTROYED.load(Ordering::SeqCst) { + return; + } + #[cfg(all(feature = "napi4", not(target_os = "wasi")))] { - if THREAD_DESTROYED.with(|closed| closed.load(std::sync::atomic::Ordering::Relaxed)) { - return; - } if !THREADS_CAN_ACCESS_ENV - .get_or_init(Default::default) - .contains(&std::thread::current().id()) + .borrow_mut(|m| m.get(&std::thread::current().id()).is_some()) { let status = unsafe { sys::napi_call_threadsafe_function( @@ -94,18 +97,23 @@ macro_rules! impl_typed_array { ) }; assert!( - status == sys::Status::napi_ok, - "Call custom GC in ArrayBuffer::drop failed {:?}", + status == sys::Status::napi_ok || status == sys::Status::napi_closing, + "Call custom GC in ArrayBuffer::drop failed {}", Status::from(status) ); return; } } + let mut ref_count = 0; crate::check_status_or_throw!( env, - unsafe { sys::napi_reference_unref(env, ref_, &mut 0) }, + unsafe { sys::napi_reference_unref(env, ref_, &mut ref_count) }, "Failed to unref ArrayBuffer reference in drop" ); + debug_assert!( + ref_count == 0, + "ArrayBuffer reference count in ArrayBuffer::drop is not zero" + ); crate::check_status_or_throw!( env, unsafe { sys::napi_delete_reference(env, ref_) }, @@ -344,13 +352,21 @@ macro_rules! impl_typed_array { } impl ToNapiValue for $name { - unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + unsafe fn to_napi_value(env: sys::napi_env, mut val: Self) -> Result { if let Some((ref_, _)) = val.raw { let mut napi_value = std::ptr::null_mut(); check_status!( unsafe { sys::napi_get_reference_value(env, ref_, &mut napi_value) }, - "Failed to delete reference from Buffer" + "Failed to get reference from ArrayBuffer" )?; + // fast path for ArrayBuffer::drop + if Arc::strong_count(&val.drop_in_vm) == 1 { + check_status!( + unsafe { sys::napi_delete_reference(env, ref_) }, + "Failed to delete reference in ArrayBuffer::to_napi_value" + )?; + val.raw = Some((ptr::null_mut(), ptr::null_mut())); + } return Ok(napi_value); } let mut arraybuffer_value = ptr::null_mut(); @@ -422,7 +438,7 @@ macro_rules! impl_typed_array { let mut napi_value = std::ptr::null_mut(); check_status!( unsafe { sys::napi_get_reference_value(env, ref_, &mut napi_value) }, - "Failed to delete reference from Buffer" + "Failed to get reference from ArrayBuffer" )?; Ok(napi_value) } else { diff --git a/crates/napi/src/bindgen_runtime/js_values/buffer.rs b/crates/napi/src/bindgen_runtime/js_values/buffer.rs index 4c6c73f0..b710845d 100644 --- a/crates/napi/src/bindgen_runtime/js_values/buffer.rs +++ b/crates/napi/src/bindgen_runtime/js_values/buffer.rs @@ -9,8 +9,8 @@ use std::sync::Arc; #[cfg(all(debug_assertions, not(windows)))] use std::sync::Mutex; -#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] -use crate::bindgen_prelude::{CUSTOM_GC_TSFN, THREADS_CAN_ACCESS_ENV, THREAD_DESTROYED}; +#[cfg(all(feature = "napi4", not(target_os = "wasi")))] +use crate::bindgen_prelude::{CUSTOM_GC_TSFN, CUSTOM_GC_TSFN_DESTROYED, THREADS_CAN_ACCESS_ENV}; use crate::{bindgen_prelude::*, check_status, sys, Result, ValueType}; #[cfg(all(debug_assertions, not(windows)))] @@ -34,15 +34,16 @@ impl Drop for Buffer { fn drop(&mut self) { if Arc::strong_count(&self.ref_count) == 1 { if let Some((ref_, env)) = self.raw { - #[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] + if ref_.is_null() { + return; + } + #[cfg(not(target_os = "wasi"))] + if CUSTOM_GC_TSFN_DESTROYED.load(std::sync::atomic::Ordering::SeqCst) { + return; + } + #[cfg(all(feature = "napi4", not(target_os = "wasi")))] { - if THREAD_DESTROYED.with(|closed| closed.load(std::sync::atomic::Ordering::Relaxed)) { - return; - } - if !THREADS_CAN_ACCESS_ENV - .get_or_init(Default::default) - .contains(&std::thread::current().id()) - { + if !THREADS_CAN_ACCESS_ENV.borrow_mut(|m| m.get(&std::thread::current().id()).is_some()) { let status = unsafe { sys::napi_call_threadsafe_function( CUSTOM_GC_TSFN.load(std::sync::atomic::Ordering::SeqCst), @@ -51,8 +52,8 @@ impl Drop for Buffer { ) }; assert!( - status == sys::Status::napi_ok, - "Call custom GC in ArrayBuffer::drop failed {:?}", + status == sys::Status::napi_ok || status == sys::Status::napi_closing, + "Call custom GC in Buffer::drop failed {}", Status::from(status) ); return; @@ -64,6 +65,10 @@ impl Drop for Buffer { unsafe { sys::napi_reference_unref(env, ref_, &mut ref_count) }, "Failed to unref Buffer reference in drop" ); + debug_assert!( + ref_count == 0, + "Buffer reference count in Buffer::drop is not zero" + ); check_status_or_throw!( env, unsafe { sys::napi_delete_reference(env, ref_) }, @@ -217,7 +222,7 @@ impl FromNapiValue for Buffer { } impl ToNapiValue for Buffer { - unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + unsafe fn to_napi_value(env: sys::napi_env, mut val: Self) -> Result { // From Node.js value, not from `Vec` if let Some((ref_, _)) = val.raw { let mut buf = ptr::null_mut(); @@ -225,6 +230,14 @@ impl ToNapiValue for Buffer { unsafe { sys::napi_get_reference_value(env, ref_, &mut buf) }, "Failed to get Buffer value from reference" )?; + // fast path for Buffer::drop + if Arc::strong_count(&val.ref_count) == 1 { + check_status!( + unsafe { sys::napi_delete_reference(env, ref_) }, + "Failed to delete Buffer reference in Buffer::to_napi_value" + )?; + val.raw = Some((ptr::null_mut(), ptr::null_mut())); + } return Ok(buf); } let len = val.len; diff --git a/crates/napi/src/bindgen_runtime/module_register.rs b/crates/napi/src/bindgen_runtime/module_register.rs index 57c44861..cb4fc193 100644 --- a/crates/napi/src/bindgen_runtime/module_register.rs +++ b/crates/napi/src/bindgen_runtime/module_register.rs @@ -1,11 +1,14 @@ use std::collections::{HashMap, HashSet}; use std::ffi::CStr; -#[cfg(all(feature = "napi4", not(target_arch = "wasm32"), not(feature = "noop")))] -use std::hash::Hash; -#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] -use std::ops::Deref; use std::ptr; -use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering}; +#[cfg(all( + any(target_os = "windows", target_os = "freebsd"), + feature = "napi4", + feature = "tokio_rt" +))] +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; +use std::sync::RwLock; use std::thread::ThreadId; use once_cell::sync::Lazy; @@ -18,109 +21,13 @@ pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result Result<()>; -struct PersistedPerInstanceVec { - inner: AtomicPtr, - length: AtomicUsize, -} - -impl Default for PersistedPerInstanceVec { - fn default() -> Self { - let mut vec: Vec = Vec::with_capacity(1); - let ret = Self { - inner: AtomicPtr::new(vec.as_mut_ptr()), - length: AtomicUsize::new(0), - }; - std::mem::forget(vec); - ret - } -} - -impl PersistedPerInstanceVec { - #[cfg(not(feature = "noop"))] - #[allow(clippy::mut_from_ref)] - fn borrow_mut(&self, f: F) - where - F: FnOnce(&mut [T]), - { - let length = self.length.load(Ordering::Relaxed); - if length == 0 { - f(&mut []); - } else { - let inner = self.inner.load(Ordering::Relaxed); - let mut temp = - std::mem::ManuallyDrop::new(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); - } - } - - fn push(&self, item: T) { - let length = self.length.load(Ordering::Relaxed); - let inner = self.inner.load(Ordering::Relaxed); - let mut temp = - std::mem::ManuallyDrop::new(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); - } - self.length.fetch_add(1, Ordering::Relaxed); - } -} - -unsafe impl Send for PersistedPerInstanceVec {} -unsafe impl Sync for PersistedPerInstanceVec {} - -#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] -pub(crate) struct PersistedPerInstanceHashSet { - inner: *mut HashSet, -} - -#[cfg(not(feature = "noop"))] -#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] -impl PersistedPerInstanceHashSet { - pub(crate) fn insert(&self, item: T) { - Box::leak(unsafe { Box::from_raw(self.inner) }).insert(item); - } - - fn remove(&self, item: &T) { - Box::leak(unsafe { Box::from_raw(self.inner) }).remove(item); - } -} - -#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] -impl Deref for PersistedPerInstanceHashSet { - type Target = HashSet; - - fn deref(&self) -> &Self::Target { - Box::leak(unsafe { Box::from_raw(self.inner) }) - } -} - -#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] -impl Default for PersistedPerInstanceHashSet { - fn default() -> Self { - Self { - inner: Box::leak(Box::default()), - } - } -} - -#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] -unsafe impl Send for PersistedPerInstanceHashSet {} -#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] -unsafe impl Sync for PersistedPerInstanceHashSet {} - -pub(crate) struct PersistedPerInstanceHashMap(*mut HashMap); +#[repr(transparent)] +pub(crate) struct PersistedPerInstanceHashMap(RwLock>); impl PersistedPerInstanceHashMap { #[cfg(not(feature = "noop"))] pub(crate) fn from_hashmap(hashmap: HashMap) -> Self { - Self(Box::into_raw(Box::new(hashmap))) + Self(RwLock::new(hashmap)) } #[allow(clippy::mut_from_ref)] @@ -128,19 +35,19 @@ impl PersistedPerInstanceHashMap { where F: FnOnce(&mut HashMap) -> R, { - f(unsafe { Box::leak(Box::from_raw(self.0)) }) + let mut write_lock = self.0.write().unwrap(); + f(&mut *write_lock) } } impl Default for PersistedPerInstanceHashMap { fn default() -> Self { - let map = Default::default(); - Self(Box::into_raw(Box::new(map))) + Self(RwLock::new(HashMap::default())) } } type ModuleRegisterCallback = - PersistedPerInstanceVec<(Option<&'static str>, (&'static str, ExportRegisterCallback))>; + RwLock, (&'static str, ExportRegisterCallback))>>; type ModuleClassProperty = PersistedPerInstanceHashMap< &'static str, @@ -162,28 +69,23 @@ static IS_FIRST_MODULE: AtomicBool = AtomicBool::new(true); static FIRST_MODULE_REGISTERED: AtomicBool = AtomicBool::new(false); static REGISTERED_CLASSES: Lazy = Lazy::new(Default::default); static FN_REGISTER_MAP: Lazy = Lazy::new(Default::default); -#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] +#[cfg(all(feature = "napi4", not(target_os = "wasi")))] pub(crate) static CUSTOM_GC_TSFN: AtomicPtr = AtomicPtr::new(ptr::null_mut()); -#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] -thread_local! { - // CustomGC ThreadsafeFunction may be deleted during the process exit. - // And there may still some Buffer alive after that. - pub(crate) static THREAD_DESTROYED: AtomicBool = AtomicBool::new(false); -} -#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] +#[cfg(all(feature = "napi4", not(target_os = "wasi")))] +pub(crate) static CUSTOM_GC_TSFN_DESTROYED: AtomicBool = AtomicBool::new(false); +#[cfg(all(feature = "napi4", not(target_os = "wasi")))] // Store thread id of the thread that created the CustomGC ThreadsafeFunction. -pub(crate) static THREADS_CAN_ACCESS_ENV: once_cell::sync::OnceCell< - PersistedPerInstanceHashSet, -> = once_cell::sync::OnceCell::new(); +pub(crate) static THREADS_CAN_ACCESS_ENV: once_cell::sync::Lazy< + PersistedPerInstanceHashMap, +> = once_cell::sync::Lazy::new(Default::default); type RegisteredClasses = PersistedPerInstanceHashMap; #[cfg(all(feature = "compat-mode", not(feature = "noop")))] // compatibility for #[module_exports] -static MODULE_EXPORTS: Lazy> = - Lazy::new(Default::default); +static MODULE_EXPORTS: Lazy>> = Lazy::new(Default::default); #[cfg(not(feature = "noop"))] #[inline] @@ -207,7 +109,10 @@ pub fn get_class_constructor(js_name: &'static str) -> Option { #[cfg(all(feature = "compat-mode", not(feature = "noop")))] // compatibility for #[module_exports] pub fn register_module_exports(callback: ModuleExportsCallback) { - MODULE_EXPORTS.push(callback); + MODULE_EXPORTS + .write() + .expect("Register module exports failed") + .push(callback); } #[doc(hidden)] @@ -216,7 +121,10 @@ pub fn register_module_export( name: &'static str, cb: ExportRegisterCallback, ) { - MODULE_REGISTER_CALLBACK.push((js_mod, (name, cb))); + MODULE_REGISTER_CALLBACK + .write() + .expect("Register module export failed") + .push((js_mod, (name, cb))); } #[doc(hidden)] @@ -338,7 +246,7 @@ fn load_host() { } } -#[cfg(all(target_arch = "wasm32", not(feature = "noop")))] +#[cfg(all(target_os = "wasi", not(feature = "noop")))] #[no_mangle] unsafe extern "C" fn napi_register_wasm_v1( env: sys::napi_env, @@ -366,8 +274,12 @@ pub unsafe extern "C" fn napi_register_module_v1( wait_first_thread_registered(); } let mut exports_objects: HashSet = HashSet::default(); - MODULE_REGISTER_CALLBACK.borrow_mut(|inner| { - inner + + { + let mut register_callback = MODULE_REGISTER_CALLBACK + .write() + .expect("Write MODULE_REGISTER_CALLBACK in napi_register_module_v1 failed"); + register_callback .iter_mut() .fold( HashMap::, Vec<(&'static str, ExportRegisterCallback)>>::new(), @@ -437,8 +349,8 @@ pub unsafe extern "C" fn napi_register_module_v1( } } } - }) - }); + }); + } let mut registered_classes = HashMap::new(); @@ -536,15 +448,20 @@ pub unsafe extern "C" fn napi_register_module_v1( }); #[cfg(feature = "compat-mode")] - MODULE_EXPORTS.borrow_mut(|inner| { - inner.iter().for_each(|callback| unsafe { + { + let module_exports = MODULE_EXPORTS.read().expect("Read MODULE_EXPORTS failed"); + module_exports.iter().for_each(|callback| unsafe { if let Err(e) = callback(env, exports) { JsError::from(e).throw_into(env); } }) - }); + } - #[cfg(all(windows, feature = "napi4", feature = "tokio_rt"))] + #[cfg(all( + any(target_os = "windows", target_os = "freebsd"), + feature = "napi4", + feature = "tokio_rt" + ))] { crate::tokio_runtime::ensure_runtime(); @@ -560,7 +477,7 @@ pub unsafe extern "C" fn napi_register_module_v1( ) }; } - #[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] + #[cfg(all(feature = "napi4", not(target_os = "wasi")))] create_custom_gc(env); FIRST_MODULE_REGISTERED.store(true, Ordering::SeqCst); exports @@ -584,67 +501,69 @@ pub(crate) unsafe extern "C" fn noop( ptr::null_mut() } -#[cfg(all(feature = "napi4", not(target_arch = "wasm32"), not(feature = "noop")))] +#[cfg(all(feature = "napi4", not(target_os = "wasi"), not(feature = "noop")))] fn create_custom_gc(env: sys::napi_env) { use std::os::raw::c_char; - 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" - ); - 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::Relaxed); - let threads = THREADS_CAN_ACCESS_ENV.get_or_init(Default::default); + if !FIRST_MODULE_REGISTERED.load(Ordering::SeqCst) { + let mut custom_gc_fn = ptr::null_mut(); + check_status_or_throw!( + env, + unsafe { + sys::napi_create_function( + env, + "custom_gc".as_ptr().cast(), + 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" + ); + 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::Relaxed); + } + let current_thread_id = std::thread::current().id(); - threads.insert(current_thread_id); + THREADS_CAN_ACCESS_ENV.borrow_mut(|m| m.insert(current_thread_id, true)); check_status_or_throw!( env, unsafe { @@ -658,33 +577,29 @@ fn create_custom_gc(env: sys::napi_env) { ); } -#[cfg(all(feature = "napi4", not(target_arch = "wasm32"), not(feature = "noop")))] +#[cfg(all(feature = "napi4", not(target_os = "wasi"), not(feature = "noop")))] unsafe extern "C" fn remove_thread_id(id: *mut std::ffi::c_void) { let thread_id = unsafe { Box::from_raw(id.cast::()) }; - THREADS_CAN_ACCESS_ENV - .get_or_init(Default::default) - .remove(&*thread_id); + THREADS_CAN_ACCESS_ENV.borrow_mut(|m| m.insert(*thread_id, false)); } -#[cfg(all(feature = "napi4", not(target_arch = "wasm32"), not(feature = "noop")))] +#[cfg(all(feature = "napi4", not(target_os = "wasi"), not(feature = "noop")))] #[allow(unused)] unsafe extern "C" fn empty(env: sys::napi_env, info: sys::napi_callback_info) -> sys::napi_value { ptr::null_mut() } -#[cfg(all(feature = "napi4", not(target_arch = "wasm32"), not(feature = "noop")))] -#[allow(unused)] +#[cfg(all(feature = "napi4", not(target_os = "wasi"), not(feature = "noop")))] +#[allow(unused_variables)] unsafe extern "C" fn custom_gc_finalize( env: sys::napi_env, finalize_data: *mut std::ffi::c_void, finalize_hint: *mut std::ffi::c_void, ) { - THREAD_DESTROYED.with(|closed| { - closed.store(true, Ordering::Relaxed); - }); + CUSTOM_GC_TSFN_DESTROYED.store(true, Ordering::SeqCst); } -#[cfg(all(feature = "napi4", not(target_arch = "wasm32"), not(feature = "noop")))] +#[cfg(all(feature = "napi4", not(target_os = "wasi"), not(feature = "noop")))] // 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, @@ -692,9 +607,14 @@ extern "C" fn custom_gc( _context: *mut std::ffi::c_void, data: *mut std::ffi::c_void, ) { + // current thread was destroyed + if THREADS_CAN_ACCESS_ENV.borrow_mut(|m| m.get(&std::thread::current().id()) == Some(&false)) { + return; + } + let mut reference = 0; check_status_or_throw!( env, - unsafe { sys::napi_delete_reference(env, data as sys::napi_ref) }, + unsafe { sys::napi_reference_unref(env, data.cast(), &mut reference) }, "Failed to delete Buffer reference in Custom GC" ); } diff --git a/crates/napi/src/js_values/deferred.rs b/crates/napi/src/js_values/deferred.rs index c990fdee..8cde0dc9 100644 --- a/crates/napi/src/js_values/deferred.rs +++ b/crates/napi/src/js_values/deferred.rs @@ -3,8 +3,6 @@ use std::marker::PhantomData; use std::os::raw::c_void; use std::ptr; -#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] -use crate::bindgen_prelude::THREAD_DESTROYED; use crate::bindgen_runtime::ToNapiValue; use crate::{check_status, JsObject, Value}; use crate::{sys, Env, Error, Result}; @@ -208,12 +206,6 @@ extern "C" fn napi_resolve_deferred context: *mut c_void, data: *mut c_void, ) { - #[cfg(not(target_arch = "wasm32"))] - { - if THREAD_DESTROYED.with(|closed| closed.load(std::sync::atomic::Ordering::Relaxed)) { - return; - } - } let deferred = context.cast(); let deferred_data: Box> = unsafe { Box::from_raw(data.cast()) }; let result = deferred_data diff --git a/crates/napi/src/tokio_runtime.rs b/crates/napi/src/tokio_runtime.rs index eebc0d29..c56c1a69 100644 --- a/crates/napi/src/tokio_runtime.rs +++ b/crates/napi/src/tokio_runtime.rs @@ -23,14 +23,14 @@ fn create_runtime() -> Option { pub(crate) static RT: Lazy>> = Lazy::new(|| RwLock::new(create_runtime())); -#[cfg(windows)] +#[cfg(any(target_os = "windows", target_os = "freebsd"))] static RT_REFERENCE_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); /// Ensure that the Tokio runtime is initialized. /// In windows the Tokio runtime will be dropped when Node env exits. /// But in Electron renderer process, the Node env will exits and recreate when the window reloads. /// So we need to ensure that the Tokio runtime is initialized when the Node env is created. -#[cfg(windows)] +#[cfg(any(target_os = "windows", target_os = "freebsd"))] pub(crate) fn ensure_runtime() { use std::sync::atomic::Ordering; @@ -42,7 +42,7 @@ pub(crate) fn ensure_runtime() { RT_REFERENCE_COUNT.fetch_add(1, Ordering::Relaxed); } -#[cfg(windows)] +#[cfg(any(target_os = "windows", target_os = "freebsd"))] pub(crate) unsafe extern "C" fn drop_runtime(_arg: *mut std::ffi::c_void) { use std::sync::atomic::Ordering; @@ -104,10 +104,10 @@ pub fn execute_tokio_future< } }; - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_os = "wasi"))] spawn(inner); - #[cfg(target_arch = "wasm32")] + #[cfg(target_os = "wasi")] block_on(inner); Ok(promise.0.value) diff --git a/examples/napi/__tests__/worker-thread.spec.ts b/examples/napi/__tests__/worker-thread.spec.ts index 4bb1d09b..f5ec786d 100644 --- a/examples/napi/__tests__/worker-thread.spec.ts +++ b/examples/napi/__tests__/worker-thread.spec.ts @@ -8,14 +8,19 @@ const { Animal, Kind, DEFAULT_COST } = (await import('../index.js')).default const __dirname = join(fileURLToPath(import.meta.url), '..') -// aarch64-unknown-linux-gnu is extremely slow in CI, skip it or it will timeout const t = + // aarch64-unknown-linux-gnu is extremely slow in CI, skip it or it will timeout (process.arch === 'arm64' && process.platform === 'linux') || process.env.WASI_TEST ? test.skip : test -const concurrency = process.platform === 'win32' || process.platform === 'darwin' || (process.platform === 'linux' && process.arch === 'x64') ? 50 : 10 +const concurrency = + process.platform === 'win32' || + process.platform === 'darwin' || + (process.platform === 'linux' && process.arch === 'x64') + ? 50 + : 10 t('should be able to require in worker thread', async (t) => { await Promise.all( diff --git a/examples/napi/src/async.rs b/examples/napi/src/async.rs index 6d762107..724e3909 100644 --- a/examples/napi/src/async.rs +++ b/examples/napi/src/async.rs @@ -1,13 +1,13 @@ -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_os = "wasi"))] use futures::prelude::*; use napi::bindgen_prelude::*; use napi::tokio; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_os = "wasi"))] use napi::tokio::fs; #[napi] async fn read_file_async(path: String) -> Result { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_os = "wasi"))] { fs::read(path) .map(|r| match r { @@ -19,7 +19,7 @@ async fn read_file_async(path: String) -> Result { }) .await } - #[cfg(target_arch = "wasm32")] + #[cfg(target_os = "wasi")] { let conetent = std::fs::read(path)?; Ok(conetent.into()) diff --git a/examples/napi/src/enum.rs b/examples/napi/src/enum.rs index a2e72a18..73806eb7 100644 --- a/examples/napi/src/enum.rs +++ b/examples/napi/src/enum.rs @@ -1,5 +1,3 @@ -use napi::bindgen_prelude::*; - /// default enum values are continuos i32s start from 0 #[napi] pub enum Kind {