fix(napi): asan caught memory safety issue

This commit is contained in:
LongYinan 2023-11-03 12:09:06 +08:00
parent afd3395bb4
commit 0dc1ef738b
14 changed files with 205 additions and 261 deletions

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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),*]);

View file

@ -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};

View file

@ -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 THREAD_DESTROYED.with(|closed| closed.load(std::sync::atomic::Ordering::Relaxed)) {
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 !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<sys::napi_value> {
unsafe fn to_napi_value(env: sys::napi_env, mut val: Self) -> Result<sys::napi_value> {
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 {

View file

@ -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 THREAD_DESTROYED.with(|closed| closed.load(std::sync::atomic::Ordering::Relaxed)) {
if ref_.is_null() {
return;
}
if !THREADS_CAN_ACCESS_ENV
.get_or_init(Default::default)
.contains(&std::thread::current().id())
#[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 !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<sys::napi_value> {
unsafe fn to_napi_value(env: sys::napi_env, mut val: Self) -> Result<sys::napi_value> {
// From Node.js value, not from `Vec<u8>`
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;

View file

@ -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<sys::napi_v
pub type ModuleExportsCallback =
unsafe fn(env: sys::napi_env, exports: sys::napi_value) -> Result<()>;
struct PersistedPerInstanceVec<T> {
inner: AtomicPtr<T>,
length: AtomicUsize,
}
impl<T> Default for PersistedPerInstanceVec<T> {
fn default() -> Self {
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> PersistedPerInstanceVec<T> {
#[cfg(not(feature = "noop"))]
#[allow(clippy::mut_from_ref)]
fn borrow_mut<F>(&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<T: Send> Send for PersistedPerInstanceVec<T> {}
unsafe impl<T: Sync> Sync for PersistedPerInstanceVec<T> {}
#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))]
pub(crate) struct PersistedPerInstanceHashSet<T: 'static> {
inner: *mut HashSet<T>,
}
#[cfg(not(feature = "noop"))]
#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))]
impl<T: 'static + PartialEq + Eq + Hash> PersistedPerInstanceHashSet<T> {
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<T: 'static> Deref for PersistedPerInstanceHashSet<T> {
type Target = HashSet<T>;
fn deref(&self) -> &Self::Target {
Box::leak(unsafe { Box::from_raw(self.inner) })
}
}
#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))]
impl<T: 'static> Default for PersistedPerInstanceHashSet<T> {
fn default() -> Self {
Self {
inner: Box::leak(Box::default()),
}
}
}
#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))]
unsafe impl<T: Send> Send for PersistedPerInstanceHashSet<T> {}
#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))]
unsafe impl<T: Sync> Sync for PersistedPerInstanceHashSet<T> {}
pub(crate) struct PersistedPerInstanceHashMap<K, V>(*mut HashMap<K, V>);
#[repr(transparent)]
pub(crate) struct PersistedPerInstanceHashMap<K, V>(RwLock<HashMap<K, V>>);
impl<K, V> PersistedPerInstanceHashMap<K, V> {
#[cfg(not(feature = "noop"))]
pub(crate) fn from_hashmap(hashmap: HashMap<K, V>) -> Self {
Self(Box::into_raw(Box::new(hashmap)))
Self(RwLock::new(hashmap))
}
#[allow(clippy::mut_from_ref)]
@ -128,19 +35,19 @@ impl<K, V> PersistedPerInstanceHashMap<K, V> {
where
F: FnOnce(&mut HashMap<K, V>) -> R,
{
f(unsafe { Box::leak(Box::from_raw(self.0)) })
let mut write_lock = self.0.write().unwrap();
f(&mut *write_lock)
}
}
impl<K, V> Default for PersistedPerInstanceHashMap<K, V> {
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<Vec<(Option<&'static str>, (&'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<RegisteredClassesMap> = Lazy::new(Default::default);
static FN_REGISTER_MAP: Lazy<FnRegisterMap> = 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<sys::napi_threadsafe_function__> =
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<ThreadId>,
> = once_cell::sync::OnceCell::new();
pub(crate) static THREADS_CAN_ACCESS_ENV: once_cell::sync::Lazy<
PersistedPerInstanceHashMap<ThreadId, bool>,
> = once_cell::sync::Lazy::new(Default::default);
type RegisteredClasses =
PersistedPerInstanceHashMap</* export name */ String, /* constructor */ sys::napi_ref>;
#[cfg(all(feature = "compat-mode", not(feature = "noop")))]
// compatibility for #[module_exports]
static MODULE_EXPORTS: Lazy<PersistedPerInstanceVec<ModuleExportsCallback>> =
Lazy::new(Default::default);
static MODULE_EXPORTS: Lazy<RwLock<Vec<ModuleExportsCallback>>> = Lazy::new(Default::default);
#[cfg(not(feature = "noop"))]
#[inline]
@ -207,7 +109,10 @@ pub fn get_class_constructor(js_name: &'static str) -> Option<sys::napi_ref> {
#[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<String> = 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::<Option<&'static str>, 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,17 +501,18 @@ 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;
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() as *const c_char,
"custom_gc".as_ptr().cast(),
9,
Some(empty),
ptr::null_mut(),
@ -642,9 +560,10 @@ fn create_custom_gc(env: sys::napi_env) {
"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);
}
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::<ThreadId>()) };
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"
);
}

View file

@ -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<Data: ToNapiValue, Resolver: FnOnce(Env) ->
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<DeferredData<Data, Resolver>> = unsafe { Box::from_raw(data.cast()) };
let result = deferred_data

View file

@ -23,14 +23,14 @@ fn create_runtime() -> Option<Runtime> {
pub(crate) static RT: Lazy<RwLock<Option<Runtime>>> = 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)

View file

@ -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(

View file

@ -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<Buffer> {
#[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<Buffer> {
})
.await
}
#[cfg(target_arch = "wasm32")]
#[cfg(target_os = "wasi")]
{
let conetent = std::fs::read(path)?;
Ok(conetent.into())

View file

@ -1,5 +1,3 @@
use napi::bindgen_prelude::*;
/// default enum values are continuos i32s start from 0
#[napi]
pub enum Kind {