diff --git a/.github/workflows/test-release.yaml b/.github/workflows/test-release.yaml index 62753e1d..c0817162 100644 --- a/.github/workflows/test-release.yaml +++ b/.github/workflows/test-release.yaml @@ -260,7 +260,7 @@ jobs: - name: Setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: 'yarn' - name: Install @@ -475,7 +475,7 @@ jobs: run: | node -e "console.info('docker-image=${{ matrix.settings.image }}'.replace('{:version}', ${{ matrix.node }}))" >> "$GITHUB_OUTPUT" - name: Run test - run: docker run --rm ${{ matrix.settings.args }} -v ${{ github.workspace }}:/build -w /build ${{ steps.image-name.outputs.docker-image }} sh -c "DISABLE_V8_COMPILE_CACHE=1 yarn workspace @examples/napi test -s" + run: docker run --rm ${{ matrix.settings.args }} -v ${{ github.workspace }}:/build -w /build ${{ steps.image-name.outputs.docker-image }} sh -c "NODE_OPTIONS=--max-old-space-size=6000 DISABLE_V8_COMPILE_CACHE=1 yarn workspace @examples/napi test -s" build-and-test-linux-armv7: name: stable - armv7-unknown-linux-gnueabihf - node@20 diff --git a/bench/Cargo.toml b/bench/Cargo.toml index 8c25a97b..e9f825a1 100644 --- a/bench/Cargo.toml +++ b/bench/Cargo.toml @@ -9,7 +9,6 @@ version = "0.1.0" crate-type = ["cdylib"] [dependencies] -ctor = "0.3" napi = { path = "../crates/napi", features = [ "tokio_rt", "serde-json", diff --git a/crates/backend/src/codegen/const.rs b/crates/backend/src/codegen/const.rs index 482eb2fc..6a1980c1 100644 --- a/crates/backend/src/codegen/const.rs +++ b/crates/backend/src/codegen/const.rs @@ -36,7 +36,7 @@ impl NapiConst { #[allow(non_snake_case)] #[allow(clippy::all)] #[cfg(all(not(test), not(target_family = "wasm")))] - #[napi::bindgen_prelude::ctor] + #[napi::ctor::ctor(crate_path=::napi::ctor)] 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 69b1acea..d971e658 100644 --- a/crates/backend/src/codegen/enum.rs +++ b/crates/backend/src/codegen/enum.rs @@ -254,7 +254,7 @@ impl NapiEnum { #[allow(non_snake_case)] #[allow(clippy::all)] #[cfg(all(not(test), not(target_family = "wasm")))] - #[napi::bindgen_prelude::ctor] + #[napi::ctor::ctor(crate_path=napi::ctor)] 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 2474096f..0d307843 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -702,7 +702,7 @@ impl NapiFn { #[allow(clippy::all)] #[allow(non_snake_case)] #[cfg(all(not(test), not(target_family = "wasm")))] - #[napi::bindgen_prelude::ctor] + #[napi::ctor::ctor(crate_path=::napi::ctor)] 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 4c663641..b95ec93e 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -869,7 +869,7 @@ impl NapiStruct { #[allow(non_snake_case)] #[allow(clippy::all)] #[cfg(all(not(test), not(target_family = "wasm")))] - #[napi::bindgen_prelude::ctor] + #[napi::ctor::ctor(crate_path=napi::ctor)] fn #struct_register_name() { napi::__private::register_class(std::any::TypeId::of::<#name>(), #js_mod_ident, #js_name, vec![#(#props),*]); } @@ -1402,7 +1402,7 @@ impl NapiImpl { #(#methods)* #[cfg(all(not(test), not(target_family = "wasm")))] - #[napi::bindgen_prelude::ctor] + #[napi::ctor::ctor(crate_path=napi::ctor)] fn #register_name() { napi::__private::register_class(std::any::TypeId::of::<#name>(), #js_mod_ident, #js_name, vec![#(#props),*]); } diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs index ef34952b..17a2a06e 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_family = "wasm"), napi::bindgen_prelude::ctor)] + #[cfg_attr(not(target_family = "wasm"), napi::ctor::ctor(crate_path=napi::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}; @@ -174,3 +174,13 @@ pub fn module_exports(_attr: TokenStream, input: TokenStream) -> TokenStream { }) .into() } + +#[proc_macro_attribute] +pub fn module_init(_: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemFn); + quote! { + #[napi::ctor::ctor(crate_path=napi::ctor)] + #input + } + .into() +} diff --git a/crates/napi/Cargo.toml b/crates/napi/Cargo.toml index 1b59688a..0b88e91f 100644 --- a/crates/napi/Cargo.toml +++ b/crates/napi/Cargo.toml @@ -62,7 +62,7 @@ node_version_detect = [] [dependencies] bitflags = "2" -ctor = "0.3" +ctor = "0.3.6" [dependencies.anyhow] optional = true diff --git a/crates/napi/src/bindgen_runtime/module_register.rs b/crates/napi/src/bindgen_runtime/module_register.rs index 136663e5..353b202d 100644 --- a/crates/napi/src/bindgen_runtime/module_register.rs +++ b/crates/napi/src/bindgen_runtime/module_register.rs @@ -7,7 +7,7 @@ use std::mem::MaybeUninit; #[cfg(not(feature = "noop"))] use std::ptr; #[cfg(not(feature = "noop"))] -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{LazyLock, RwLock}; use std::thread::ThreadId; use std::{any::TypeId, collections::HashMap}; @@ -52,6 +52,7 @@ impl Default for PersistedPerInstanceHashMap { } } +#[cfg(not(feature = "noop"))] type ModuleRegisterCallback = RwLock, (&'static str, ExportRegisterCallback))>>; @@ -65,10 +66,12 @@ type FnRegisterMap = PersistedPerInstanceHashMap; type RegisteredClassesMap = PersistedPerInstanceHashMap; +#[cfg(not(feature = "noop"))] static MODULE_REGISTER_CALLBACK: LazyLock = LazyLock::new(Default::default); +#[cfg(not(feature = "noop"))] static MODULE_CLASS_PROPERTIES: LazyLock = LazyLock::new(Default::default); #[cfg(not(feature = "noop"))] -static IS_FIRST_MODULE: AtomicBool = AtomicBool::new(true); +static MODULE_COUNT: AtomicUsize = AtomicUsize::new(0); #[cfg(not(feature = "noop"))] static FIRST_MODULE_REGISTERED: AtomicBool = AtomicBool::new(false); static REGISTERED_CLASSES: LazyLock = LazyLock::new(Default::default); @@ -109,6 +112,7 @@ pub fn register_module_exports(callback: ModuleExportsCallback) { .push(callback); } +#[cfg(not(feature = "noop"))] #[doc(hidden)] pub fn register_module_export( js_mod: Option<&'static str>, @@ -142,6 +146,7 @@ pub fn get_class_constructor(js_name: &'static str) -> Option { })? } +#[cfg(not(feature = "noop"))] #[doc(hidden)] pub fn register_class( rust_type_id: TypeId, @@ -212,10 +217,7 @@ pub unsafe extern "C" fn napi_register_module_v1( env: sys::napi_env, exports: sys::napi_value, ) -> sys::napi_value { - #[cfg(all( - any(target_env = "msvc", feature = "dyn-symbols"), - not(feature = "noop") - ))] + #[cfg(any(target_env = "msvc", feature = "dyn-symbols"))] unsafe { sys::setup(); } @@ -234,11 +236,11 @@ pub unsafe extern "C" fn napi_register_module_v1( NODE_VERSION_PATCH = node_version.patch; }; } - if IS_FIRST_MODULE.load(Ordering::SeqCst) { - IS_FIRST_MODULE.store(false, Ordering::SeqCst); - } else { + + if MODULE_COUNT.fetch_add(1, Ordering::SeqCst) != 0 { wait_first_thread_registered(); } + let mut exports_objects: HashSet = HashSet::default(); { @@ -424,29 +426,21 @@ pub unsafe extern "C" fn napi_register_module_v1( }) } + #[cfg(feature = "napi4")] let current_thread_id = std::thread::current().id(); - #[cfg(all(not(target_family = "wasm"), feature = "napi3"))] + // attach cleanup hook to the `module` object + // we don't use the `napi_add_env_cleanup_hook` because it's required napi3 check_status_or_throw!( env, unsafe { - sys::napi_add_env_cleanup_hook( + sys::napi_wrap( env, - Some(thread_cleanup), + exports, Box::into_raw(Box::new(current_thread_id)).cast(), - ) - }, - "Failed to add remove thread id cleanup hook" - ); - - #[cfg(all(target_family = "wasm", feature = "napi3"))] - check_status_or_throw!( - env, - unsafe { - crate::napi_add_env_cleanup_hook( - env, Some(thread_cleanup), - Box::into_raw(Box::new(current_thread_id)).cast(), + ptr::null_mut(), + ptr::null_mut(), ) }, "Failed to add remove thread id cleanup hook" @@ -538,19 +532,24 @@ fn create_custom_gc(env: sys::napi_env, current_thread_id: ThreadId) { THREADS_CAN_ACCESS_ENV.borrow_mut(|m| m.insert(current_thread_id, true)); } -#[cfg(all(feature = "napi3", not(feature = "noop")))] -unsafe extern "C" fn thread_cleanup(id: *mut std::ffi::c_void) { - let thread_id = unsafe { Box::from_raw(id.cast::()) }; - REGISTERED_CLASSES.borrow_mut(|m| { - m.remove(&thread_id); - }); - #[cfg(feature = "napi4")] - { - THREADS_CAN_ACCESS_ENV.borrow_mut(|m| m.remove(&thread_id)); - #[cfg(feature = "tokio_rt")] +#[cfg(not(feature = "noop"))] +unsafe extern "C" fn thread_cleanup( + _: sys::napi_env, + #[allow(unused_variables)] id: *mut std::ffi::c_void, + _data: *mut std::ffi::c_void, +) { + if MODULE_COUNT.fetch_sub(1, Ordering::Relaxed) == 1 { + #[cfg(all(feature = "tokio_rt", feature = "napi4"))] { crate::tokio_runtime::drop_runtime(); } + crate::bindgen_runtime::REFERENCE_MAP.borrow_mut(|m| m.clear()); + return; + } + #[cfg(feature = "napi4")] + { + let thread_id = unsafe { Box::from_raw(id.cast::()) }; + THREADS_CAN_ACCESS_ENV.borrow_mut(|m| m.remove(&thread_id)); } } diff --git a/crates/napi/src/lib.rs b/crates/napi/src/lib.rs index dab1041c..23f1f3a2 100644 --- a/crates/napi/src/lib.rs +++ b/crates/napi/src/lib.rs @@ -7,7 +7,7 @@ //! //! ## Feature flags //! -//! ### napi1 ~ napi8 +//! ### napi1 ~ napi9 //! //! Because `Node.js` N-API has versions. So there are feature flags to choose what version of `N-API` you want to build for. //! For example, if you want build a library which can be used by `node@10.17.0`, you should choose the `napi5` or lower. @@ -149,8 +149,6 @@ macro_rules! assert_type_of { }; } -pub use crate::bindgen_runtime::ctor as module_init; - pub mod bindgen_prelude { #[cfg(all(feature = "compat-mode", not(feature = "noop")))] pub use crate::bindgen_runtime::register_module_exports; @@ -213,6 +211,8 @@ pub mod __private { } } +pub extern crate ctor; + #[cfg(feature = "tokio_rt")] pub extern crate tokio; diff --git a/crates/napi/src/tokio_runtime.rs b/crates/napi/src/tokio_runtime.rs index 8ad00fe1..f60c2fff 100644 --- a/crates/napi/src/tokio_runtime.rs +++ b/crates/napi/src/tokio_runtime.rs @@ -62,33 +62,22 @@ pub fn create_custom_tokio_runtime(rt: Runtime) { USER_DEFINED_RT.get_or_init(move || Mutex::new(Some(rt))); } -#[cfg(not(feature = "noop"))] -static RT_REFERENCE_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); - #[cfg(not(feature = "noop"))] /// 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. pub(crate) fn ensure_runtime() { - use std::sync::atomic::Ordering; - let mut rt = RT.write().unwrap(); if rt.is_none() { *rt = create_runtime(); } - - RT_REFERENCE_COUNT.fetch_add(1, Ordering::Relaxed); } #[cfg(not(feature = "noop"))] pub(crate) fn drop_runtime() { - use std::sync::atomic::Ordering; - - if RT_REFERENCE_COUNT.fetch_sub(1, Ordering::AcqRel) == 1 { - if let Some(rt) = RT.write().unwrap().take() { - rt.shutdown_background(); - } + if let Some(rt) = RT.write().unwrap().take() { + rt.shutdown_background(); } } diff --git a/examples/napi-compat-mode/Cargo.toml b/examples/napi-compat-mode/Cargo.toml index b656d9ae..9f756def 100644 --- a/examples/napi-compat-mode/Cargo.toml +++ b/examples/napi-compat-mode/Cargo.toml @@ -14,7 +14,6 @@ napi3 = ["napi/napi3"] dyn-symbols = ["napi/dyn-symbols"] [dependencies] -ctor = "0.3" futures = "0.3" napi = { path = "../../crates/napi", features = [ "tokio_rt", diff --git a/examples/napi/Cargo.toml b/examples/napi/Cargo.toml index 1b64b999..664331fe 100644 --- a/examples/napi/Cargo.toml +++ b/examples/napi/Cargo.toml @@ -15,7 +15,6 @@ error_try_builds = [] [dependencies] chrono = "0.4" -ctor = "0.3" futures = "0.3" bytes = "1" napi-derive = { path = "../../crates/macro", features = ["type-def"] } diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index a89b2ce2..da1cfba9 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -18,7 +18,7 @@ extern crate serde_derive; static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc; #[cfg(not(target_family = "wasm"))] -#[napi::module_init] +#[napi_derive::module_init] fn init() { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() diff --git a/memory-testing/Cargo.toml b/memory-testing/Cargo.toml index 7e3a9f60..26839a62 100644 --- a/memory-testing/Cargo.toml +++ b/memory-testing/Cargo.toml @@ -9,7 +9,6 @@ version = "0.1.0" crate-type = ["cdylib"] [dependencies] -ctor = "0.3" futures = "0.3" napi = { path = "../crates/napi", features = [ "tokio_rt",