diff --git a/crates/rspack_binding_options/src/options/raw_module/js_loader.rs b/crates/rspack_binding_options/src/options/raw_module/js_loader.rs index d3a80eaa9bf..2618f679d55 100644 --- a/crates/rspack_binding_options/src/options/raw_module/js_loader.rs +++ b/crates/rspack_binding_options/src/options/raw_module/js_loader.rs @@ -1,8 +1,4 @@ -use std::{ - ops::Deref, - path::{Path, PathBuf}, - str::FromStr, -}; +use std::{ops::Deref, path::PathBuf, str::FromStr}; use napi_derive::napi; use rspack_core::{rspack_sources::SourceMap, Content, ResourceData}; @@ -15,9 +11,10 @@ use { rspack_error::error, rspack_identifier::{Identifiable, Identifier}, rspack_napi::threadsafe_function::ThreadsafeFunction, + rspack_napi::threadsafe_js_value_ref::ThreadsafeJsValueRef, }; -use crate::{get_builtin_loader, JsValueRef}; +use crate::get_builtin_loader; type ThreadsafeLoaderRunner = ThreadsafeFunction>; @@ -146,7 +143,7 @@ fn sync_loader_context( } else { loader_context .additional_data - .remove::>(); + .remove::>(); } loader_context.asset_filenames = loader_result.asset_filenames.into_iter().collect(); @@ -158,7 +155,7 @@ pub struct JsLoaderContext { /// Content maybe empty in pitching stage pub content: Either, #[napi(ts_type = "any")] - pub additional_data: Option>, + pub additional_data: Option>, pub source_map: Option, pub resource: String, pub resource_path: String, @@ -211,7 +208,10 @@ impl TryFrom<&mut rspack_core::LoaderContext<'_, rspack_core::LoaderRunnerContex Some(c) => Either::B(c.to_owned().into_bytes().into()), None => Either::A(Null), }, - additional_data: cx.additional_data.get::>().cloned(), + additional_data: cx + .additional_data + .get::>() + .cloned(), source_map: cx .source_map .clone() @@ -357,7 +357,7 @@ pub struct JsLoaderResult { pub build_dependencies: Vec, pub asset_filenames: Vec, pub source_map: Option, - pub additional_data: Option>, + pub additional_data: Option>, pub additional_data_external: External, pub cacheable: bool, /// Used to instruct how rust loaders should execute @@ -431,8 +431,8 @@ impl napi::bindgen_prelude::FromNapiValue for JsLoaderResult { ) })?; let source_map_: Option = obj.get("sourceMap")?; - let additional_data_: Option> = - obj.get::<_, JsValueRef>("additionalData")?; + let additional_data_: Option> = + obj.get::<_, ThreadsafeJsValueRef>("additionalData")?; // change: eagerly clone this field since `External` might be dropped. let additional_data_external_: External = obj diff --git a/crates/rspack_binding_options/src/options/raw_module/js_value_ref.rs b/crates/rspack_binding_options/src/options/raw_module/js_value_ref.rs deleted file mode 100644 index d47eb2b7b99..00000000000 --- a/crates/rspack_binding_options/src/options/raw_module/js_value_ref.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::{ - marker::PhantomData, - sync::{Arc, Mutex}, -}; - -use napi::bindgen_prelude::*; -use napi::{Env, NapiValue, Ref}; - -pub struct JsValueRef { - env: Env, - ref_: Arc>>, - _phantom: PhantomData, -} - -unsafe impl Send for JsValueRef {} -unsafe impl Sync for JsValueRef {} - -impl JsValueRef { - fn new(env: Env, value: T) -> Result { - let ref_ = env.create_reference(value)?; - - Ok(Self { - env, - ref_: Arc::new(Mutex::new(ref_)), - _phantom: PhantomData, - }) - } - - fn get(&self) -> Result { - let ref_ = &self.ref_.lock().map_err(|e| { - Error::new( - Status::GenericFailure, - format!("Failed to lock reference: {}", e), - ) - })?; - - self.env.get_reference_value(ref_) - } -} - -impl ToNapiValue for JsValueRef { - unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { - val.get().and_then(|v| T::to_napi_value(env, v)) - } -} - -impl FromNapiValue for JsValueRef { - unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { - JsValueRef::::new(Env::from_raw(env), T::from_napi_value(env, napi_val)?) - } -} - -impl Clone for JsValueRef { - fn clone(&self) -> Self { - Self { - env: self.env, - ref_: self.ref_.clone(), - _phantom: PhantomData, - } - } -} - -impl Drop for JsValueRef { - fn drop(&mut self) { - if Arc::strong_count(&self.ref_) == 1 { - self - .ref_ - .lock() - .expect("Failed to acquire JsValueRef lock in drop fn") - .unref(self.env) - .expect("Failed to release JsValueRef reference in drop fn"); - } - } -} diff --git a/crates/rspack_binding_options/src/options/raw_module/mod.rs b/crates/rspack_binding_options/src/options/raw_module/mod.rs index d94c74ca472..af06493500f 100644 --- a/crates/rspack_binding_options/src/options/raw_module/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_module/mod.rs @@ -1,5 +1,4 @@ mod js_loader; -mod js_value_ref; use std::fmt::Formatter; use std::{collections::HashMap, fmt::Debug, sync::Arc}; @@ -29,7 +28,6 @@ use tokio::runtime::Handle; pub use self::js_loader::JsLoaderAdapter; pub use self::js_loader::*; -pub use self::js_value_ref::JsValueRef; use crate::RawResolveOptions; pub fn get_builtin_loader(builtin: &str, options: Option<&str>) -> BoxLoader { diff --git a/crates/rspack_napi/src/js_values/js_value_ref.rs b/crates/rspack_napi/src/js_values/js_value_ref.rs new file mode 100644 index 00000000000..38423b4c01e --- /dev/null +++ b/crates/rspack_napi/src/js_values/js_value_ref.rs @@ -0,0 +1,44 @@ +use std::marker::PhantomData; + +use napi::bindgen_prelude::*; +use napi::{Env, NapiValue, Ref}; + +pub struct JsValueRef { + ref_: Ref<()>, + _phantom: PhantomData, +} + +impl JsValueRef { + pub fn new(env: Env, value: T) -> Result { + let ref_ = env.create_reference(value)?; + + Ok(Self { + ref_, + _phantom: PhantomData, + }) + } + + pub fn get(&self, env: Env) -> Result { + env.get_reference_value(&self.ref_) + } + + pub fn unref(&mut self, env: Env) -> Result { + self.ref_.unref(env) + } +} + +impl ToNapiValue for JsValueRef { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + val + .get(Env::from(env)) + .and_then(|v| unsafe { T::to_napi_value(env, v) }) + } +} + +impl FromNapiValue for JsValueRef { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + JsValueRef::::new(Env::from(env), unsafe { + T::from_napi_value(env, napi_val) + }?) + } +} diff --git a/crates/rspack_napi/src/js_values/mod.rs b/crates/rspack_napi/src/js_values/mod.rs index efb6ca18ab0..514aa9842ed 100644 --- a/crates/rspack_napi/src/js_values/mod.rs +++ b/crates/rspack_napi/src/js_values/mod.rs @@ -1 +1,2 @@ pub mod js_reg_exp; +pub mod js_value_ref; diff --git a/crates/rspack_napi/src/lib.rs b/crates/rspack_napi/src/lib.rs index 9276d891b31..a0bc191a853 100644 --- a/crates/rspack_napi/src/lib.rs +++ b/crates/rspack_napi/src/lib.rs @@ -12,6 +12,7 @@ mod callback; pub(crate) use callback::JsCallback; pub mod threadsafe_function; +pub mod threadsafe_js_value_ref; pub mod regexp { pub use crate::ext::js_reg_exp_ext::JsRegExpExt; diff --git a/crates/rspack_napi/src/threadsafe_js_value_ref.rs b/crates/rspack_napi/src/threadsafe_js_value_ref.rs new file mode 100644 index 00000000000..8ec1d98abce --- /dev/null +++ b/crates/rspack_napi/src/threadsafe_js_value_ref.rs @@ -0,0 +1,149 @@ +use std::ffi::{c_void, CString}; +use std::marker::PhantomData; +use std::ptr; +use std::sync::{Arc, Mutex}; + +use napi::bindgen_prelude::*; +use napi::sys::napi_threadsafe_function; +use napi::NapiValue; + +use crate::js_values::js_value_ref::JsValueRef; + +pub struct ThreadsafeJsValueRef { + inner: Arc<(Mutex>, DropJsValueRefFn)>, +} + +unsafe impl Send for ThreadsafeJsValueRef {} +unsafe impl Sync for ThreadsafeJsValueRef {} + +impl Clone for ThreadsafeJsValueRef { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl Drop for ThreadsafeJsValueRef { + fn drop(&mut self) { + if Arc::strong_count(&self.inner) == 1 { + let (_, drop_fn) = self.inner.as_ref(); + + drop_fn.call(self.inner.clone()); + } + } +} + +impl FromNapiValue for ThreadsafeJsValueRef { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + Self::new(Env::from(env), unsafe { + T::from_napi_value(env, napi_val) + }?) + } +} + +impl ToNapiValue for ThreadsafeJsValueRef { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + val + .get(Env::from(env)) + .and_then(|v| unsafe { T::to_napi_value(env, v) }) + } +} + +impl ThreadsafeJsValueRef { + pub fn new(env: Env, value: T) -> Result { + let js_ref = JsValueRef::new(env, value)?; + + Ok(Self { + inner: Arc::new((Mutex::new(js_ref), DropJsValueRefFn::new(env)?)), + }) + } + + pub fn get(&self, env: Env) -> Result { + let (ref_, _) = self.inner.as_ref(); + + ref_ + .lock() + .map_err(|e| { + Error::new( + Status::GenericFailure, + format!("Failed to lock mutex: {}", e.to_string()), + ) + })? + .get(env) + } +} + +struct DropJsValueRefFn { + inner: napi_threadsafe_function, + _phantom: PhantomData, +} + +impl DropJsValueRefFn { + pub fn new(env: Env) -> Result { + let mut raw_cb = std::ptr::null_mut(); + + let mut async_resource_name = ptr::null_mut(); + let s = "napi_rs_js_value_ref_drop"; + let len = s.len(); + let s = CString::new(s)?; + check_status!(unsafe { + sys::napi_create_string_utf8(env.raw(), s.as_ptr(), len, &mut async_resource_name) + })?; + + check_status! {unsafe { + sys::napi_create_threadsafe_function( + env.raw(), + ptr::null_mut(), + ptr::null_mut(), + async_resource_name, + 0, + 1, + ptr::null_mut(), + None, + ptr::null_mut(), + Some(call_js_cb::), + &mut raw_cb, + ) + }}?; + + Ok(Self { + inner: raw_cb, + _phantom: PhantomData, + }) + } + + pub fn call(&self, value: Arc<(Mutex>, DropJsValueRefFn)>) { + check_status! { + unsafe { + sys::napi_call_threadsafe_function(self.inner, Arc::into_raw(value) as *mut _, sys::ThreadsafeFunctionCallMode::nonblocking) + } + }.expect("Failed to call threadsafe function"); + } +} + +impl Drop for DropJsValueRefFn { + fn drop(&mut self) { + check_status! { + unsafe { + sys::napi_release_threadsafe_function(self.inner, sys::ThreadsafeFunctionReleaseMode::release) + } + }.expect("Failed to release threadsafe function"); + } +} + +unsafe extern "C" fn call_js_cb( + raw_env: sys::napi_env, + _: sys::napi_value, + _: *mut c_void, + data: *mut c_void, +) { + let arc = unsafe { Arc::<(Mutex>, DropJsValueRefFn)>::from_raw(data.cast()) }; + let (ref_, _) = arc.as_ref(); + + ref_ + .lock() + .expect("Failed to lock") + .unref(Env::from(raw_env)) + .expect("Failed to unref"); +}