fix(napi): asan caught memory safety issue
This commit is contained in:
parent
afd3395bb4
commit
0dc1ef738b
14 changed files with 205 additions and 261 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),*]);
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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<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 {
|
||||
|
|
|
@ -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<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;
|
||||
|
|
|
@ -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,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::<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"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use napi::bindgen_prelude::*;
|
||||
|
||||
/// default enum values are continuos i32s start from 0
|
||||
#[napi]
|
||||
pub enum Kind {
|
||||
|
|
Loading…
Reference in a new issue