| 1 | use std::collections::HashMap; |
| 2 | #[cfg (not(feature = "noop" ))] |
| 3 | use std::collections::HashSet; |
| 4 | use std::ffi::CStr; |
| 5 | use std::ptr; |
| 6 | #[cfg (all(not(target_family = "wasm" ), feature = "napi4" , feature = "tokio_rt" ))] |
| 7 | use std::sync::atomic::AtomicUsize; |
| 8 | #[cfg (not(feature = "noop" ))] |
| 9 | use std::sync::atomic::{AtomicBool, Ordering}; |
| 10 | use std::sync::RwLock; |
| 11 | use std::thread::ThreadId; |
| 12 | |
| 13 | use once_cell::sync::Lazy; |
| 14 | |
| 15 | use crate::{check_status, sys, Env, JsFunction, Property, Result, Value, ValueType}; |
| 16 | #[cfg (not(feature = "noop" ))] |
| 17 | use crate::{check_status_or_throw, JsError}; |
| 18 | |
| 19 | pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result<sys::napi_value>; |
| 20 | pub type ModuleExportsCallback = |
| 21 | unsafe fn(env: sys::napi_env, exports: sys::napi_value) -> Result<()>; |
| 22 | |
| 23 | #[repr (transparent)] |
| 24 | pub(crate) struct PersistedPerInstanceHashMap<K, V>(RwLock<HashMap<K, V>>); |
| 25 | |
| 26 | impl<K, V> PersistedPerInstanceHashMap<K, V> { |
| 27 | #[cfg (not(feature = "noop" ))] |
| 28 | pub(crate) fn from_hashmap(hashmap: HashMap<K, V>) -> Self { |
| 29 | Self(RwLock::new(hashmap)) |
| 30 | } |
| 31 | |
| 32 | #[allow (clippy::mut_from_ref)] |
| 33 | pub(crate) fn borrow_mut<F, R>(&self, f: F) -> R |
| 34 | where |
| 35 | F: FnOnce(&mut HashMap<K, V>) -> R, |
| 36 | { |
| 37 | let mut write_lock: RwLockWriteGuard<'_, HashMap<…, …>> = self.0.write().unwrap(); |
| 38 | f(&mut *write_lock) |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | impl<K, V> Default for PersistedPerInstanceHashMap<K, V> { |
| 43 | fn default() -> Self { |
| 44 | Self(RwLock::new(HashMap::default())) |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | type ModuleRegisterCallback = |
| 49 | RwLock<Vec<(Option<&'static str>, (&'static str, ExportRegisterCallback))>>; |
| 50 | |
| 51 | type ModuleClassProperty = PersistedPerInstanceHashMap< |
| 52 | &'static str, |
| 53 | HashMap<Option<&'static str>, (&'static str, Vec<Property>)>, |
| 54 | >; |
| 55 | |
| 56 | unsafe impl<K, V> Send for PersistedPerInstanceHashMap<K, V> {} |
| 57 | unsafe impl<K, V> Sync for PersistedPerInstanceHashMap<K, V> {} |
| 58 | |
| 59 | type FnRegisterMap = |
| 60 | PersistedPerInstanceHashMap<ExportRegisterCallback, (sys::napi_callback, &'static str)>; |
| 61 | type RegisteredClassesMap = PersistedPerInstanceHashMap<ThreadId, RegisteredClasses>; |
| 62 | |
| 63 | static MODULE_REGISTER_CALLBACK: Lazy<ModuleRegisterCallback> = Lazy::new(Default::default); |
| 64 | static MODULE_CLASS_PROPERTIES: Lazy<ModuleClassProperty> = Lazy::new(Default::default); |
| 65 | #[cfg (not(feature = "noop" ))] |
| 66 | static IS_FIRST_MODULE: AtomicBool = AtomicBool::new(true); |
| 67 | #[cfg (not(feature = "noop" ))] |
| 68 | static FIRST_MODULE_REGISTERED: AtomicBool = AtomicBool::new(false); |
| 69 | static REGISTERED_CLASSES: Lazy<RegisteredClassesMap> = Lazy::new(Default::default); |
| 70 | static FN_REGISTER_MAP: Lazy<FnRegisterMap> = Lazy::new(Default::default); |
| 71 | #[cfg (all(feature = "napi4" , not(feature = "noop" ), not(target_family = "wasm" )))] |
| 72 | pub(crate) static CUSTOM_GC_TSFN: std::sync::atomic::AtomicPtr<sys::napi_threadsafe_function__> = |
| 73 | std::sync::atomic::AtomicPtr::new(ptr::null_mut()); |
| 74 | #[cfg (all(feature = "napi4" , not(feature = "noop" ), not(target_family = "wasm" )))] |
| 75 | pub(crate) static CUSTOM_GC_TSFN_DESTROYED: AtomicBool = AtomicBool::new(false); |
| 76 | #[cfg (all(feature = "napi4" , not(feature = "noop" ), not(target_family = "wasm" )))] |
| 77 | // Store thread id of the thread that created the CustomGC ThreadsafeFunction. |
| 78 | pub(crate) static THREADS_CAN_ACCESS_ENV: once_cell::sync::Lazy< |
| 79 | PersistedPerInstanceHashMap<ThreadId, bool>, |
| 80 | > = once_cell::sync::Lazy::new(Default::default); |
| 81 | |
| 82 | type RegisteredClasses = |
| 83 | PersistedPerInstanceHashMap</* export name */ String, /* constructor */ sys::napi_ref>; |
| 84 | |
| 85 | #[cfg (all(feature = "compat-mode" , not(feature = "noop" )))] |
| 86 | // compatibility for #[module_exports] |
| 87 | static MODULE_EXPORTS: Lazy<RwLock<Vec<ModuleExportsCallback>>> = Lazy::new(Default::default); |
| 88 | |
| 89 | #[cfg (not(feature = "noop" ))] |
| 90 | #[inline ] |
| 91 | fn wait_first_thread_registered() { |
| 92 | while !FIRST_MODULE_REGISTERED.load(order:Ordering::SeqCst) { |
| 93 | std::hint::spin_loop(); |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | #[doc (hidden)] |
| 98 | pub fn get_class_constructor(js_name: &'static str) -> Option<sys::napi_ref> { |
| 99 | let current_id: ThreadId = std::thread::current().id(); |
| 100 | REGISTERED_CLASSES.borrow_mut(|map: &mut HashMap>| { |
| 101 | mapOption<&PersistedPerInstanceHashMap<…, …>> |
| 102 | .get(¤t_id) |
| 103 | .map(|m: &PersistedPerInstanceHashMap<…, …>| m.borrow_mut(|map: &mut HashMap| map.get(js_name).copied())) |
| 104 | })? |
| 105 | } |
| 106 | |
| 107 | #[doc (hidden)] |
| 108 | #[cfg (all(feature = "compat-mode" , not(feature = "noop" )))] |
| 109 | // compatibility for #[module_exports] |
| 110 | pub fn register_module_exports(callback: ModuleExportsCallback) { |
| 111 | MODULE_EXPORTS |
| 112 | .write() |
| 113 | .expect("Register module exports failed" ) |
| 114 | .push(callback); |
| 115 | } |
| 116 | |
| 117 | #[doc (hidden)] |
| 118 | pub fn register_module_export( |
| 119 | js_mod: Option<&'static str>, |
| 120 | name: &'static str, |
| 121 | cb: ExportRegisterCallback, |
| 122 | ) { |
| 123 | MODULE_REGISTER_CALLBACKRwLockWriteGuard<'_, Vec<…>> |
| 124 | .write() |
| 125 | .expect(msg:"Register module export failed" ) |
| 126 | .push((js_mod, (name, cb))); |
| 127 | } |
| 128 | |
| 129 | #[doc (hidden)] |
| 130 | pub fn register_js_function( |
| 131 | name: &'static str, |
| 132 | cb: ExportRegisterCallback, |
| 133 | c_fn: sys::napi_callback, |
| 134 | ) { |
| 135 | FN_REGISTER_MAP.borrow_mut(|inner: &mut HashMap …, …>| { |
| 136 | inner.insert(k:cb, (c_fn, name)); |
| 137 | }); |
| 138 | } |
| 139 | |
| 140 | #[doc (hidden)] |
| 141 | pub fn register_class( |
| 142 | rust_name: &'static str, |
| 143 | js_mod: Option<&'static str>, |
| 144 | js_name: &'static str, |
| 145 | props: Vec<Property>, |
| 146 | ) { |
| 147 | MODULE_CLASS_PROPERTIES.borrow_mut(|inner: &mut HashMap<&'static str, HashMap<…, …>>| { |
| 148 | let val: &mut HashMap = inner.entry(key:rust_name).or_default(); |
| 149 | let val: &mut (&'static str, Vec) = val.entry(key:js_mod).or_default(); |
| 150 | val.0 = js_name; |
| 151 | val.1.extend(iter:props); |
| 152 | }); |
| 153 | } |
| 154 | |
| 155 | #[inline ] |
| 156 | /// Get `JsFunction` from defined Rust `fn` |
| 157 | /// ```rust |
| 158 | /// #[napi] |
| 159 | /// fn some_fn() -> u32 { |
| 160 | /// 1 |
| 161 | /// } |
| 162 | /// |
| 163 | /// #[napi] |
| 164 | /// fn return_some_fn() -> Result<JsFunction> { |
| 165 | /// get_js_function(some_fn_js_function) |
| 166 | /// } |
| 167 | /// ``` |
| 168 | /// |
| 169 | /// ```js |
| 170 | /// returnSomeFn()(); // 1 |
| 171 | /// ``` |
| 172 | /// |
| 173 | pub fn get_js_function(env: &Env, raw_fn: ExportRegisterCallback) -> Result<JsFunction> { |
| 174 | FN_REGISTER_MAP.borrow_mut(|inner| { |
| 175 | inner |
| 176 | .get(&raw_fn) |
| 177 | .and_then(|(cb, name)| { |
| 178 | let mut function = ptr::null_mut(); |
| 179 | let name_len = name.len() - 1; |
| 180 | let fn_name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) }; |
| 181 | check_status!(unsafe { |
| 182 | sys::napi_create_function( |
| 183 | env.0, |
| 184 | fn_name.as_ptr(), |
| 185 | name_len, |
| 186 | *cb, |
| 187 | ptr::null_mut(), |
| 188 | &mut function, |
| 189 | ) |
| 190 | }) |
| 191 | .ok()?; |
| 192 | Some(JsFunction(Value { |
| 193 | env: env.0, |
| 194 | value: function, |
| 195 | value_type: ValueType::Function, |
| 196 | })) |
| 197 | }) |
| 198 | .ok_or_else(|| { |
| 199 | crate::Error::new( |
| 200 | crate::Status::InvalidArg, |
| 201 | "JavaScript function does not exist" .to_owned(), |
| 202 | ) |
| 203 | }) |
| 204 | }) |
| 205 | } |
| 206 | |
| 207 | /// Get `C Callback` from defined Rust `fn` |
| 208 | /// ```rust |
| 209 | /// #[napi] |
| 210 | /// fn some_fn() -> u32 { |
| 211 | /// 1 |
| 212 | /// } |
| 213 | /// |
| 214 | /// #[napi] |
| 215 | /// fn create_obj(env: Env) -> Result<JsObject> { |
| 216 | /// let mut obj = env.create_object()?; |
| 217 | /// obj.define_property(&[Property::new("getter" )?.with_getter(get_c_callback(some_fn_js_function)?)])?; |
| 218 | /// Ok(obj) |
| 219 | /// } |
| 220 | /// ``` |
| 221 | /// |
| 222 | /// ```js |
| 223 | /// console.log(createObj().getter) // 1 |
| 224 | /// ``` |
| 225 | /// |
| 226 | pub fn get_c_callback(raw_fn: ExportRegisterCallback) -> Result<crate::Callback> { |
| 227 | FN_REGISTER_MAP.borrow_mut(|inner: &mut HashMap …, …>| { |
| 228 | inner |
| 229 | .get(&raw_fn) |
| 230 | .and_then(|(cb, _name)| *cb) |
| 231 | .ok_or_else(|| { |
| 232 | crate::Error::new( |
| 233 | crate::Status::InvalidArg, |
| 234 | reason:"JavaScript function does not exist" .to_owned(), |
| 235 | ) |
| 236 | }) |
| 237 | }) |
| 238 | } |
| 239 | |
| 240 | #[cfg (all(any(windows, feature = "dyn-symbols" ), not(feature = "noop" )))] |
| 241 | #[ctor::ctor ] |
| 242 | fn load_host() { |
| 243 | unsafe { |
| 244 | sys::setup(); |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | #[cfg (all(target_family = "wasm" , not(feature = "noop" )))] |
| 249 | #[no_mangle ] |
| 250 | unsafe extern "C" fn napi_register_wasm_v1( |
| 251 | env: sys::napi_env, |
| 252 | exports: sys::napi_value, |
| 253 | ) -> sys::napi_value { |
| 254 | unsafe { napi_register_module_v1(env, exports) } |
| 255 | } |
| 256 | |
| 257 | #[cfg (not(feature = "noop" ))] |
| 258 | #[no_mangle ] |
| 259 | /// Register the n-api module exports. |
| 260 | /// |
| 261 | /// # Safety |
| 262 | /// This method is meant to be called by Node.js while importing the n-api module. |
| 263 | /// Only call this method if the current module is **not** imported by a node-like runtime. |
| 264 | /// |
| 265 | /// Arguments `env` and `exports` must **not** be null. |
| 266 | pub unsafe extern "C" fn napi_register_module_v1( |
| 267 | env: sys::napi_env, |
| 268 | exports: sys::napi_value, |
| 269 | ) -> sys::napi_value { |
| 270 | #[cfg (all( |
| 271 | any(target_env = "msvc" , feature = "dyn-symbols" ), |
| 272 | not(feature = "noop" ) |
| 273 | ))] |
| 274 | unsafe { |
| 275 | sys::setup(); |
| 276 | } |
| 277 | if IS_FIRST_MODULE.load(Ordering::SeqCst) { |
| 278 | IS_FIRST_MODULE.store(false, Ordering::SeqCst); |
| 279 | } else { |
| 280 | wait_first_thread_registered(); |
| 281 | } |
| 282 | let mut exports_objects: HashSet<String> = HashSet::default(); |
| 283 | |
| 284 | { |
| 285 | let mut register_callback = MODULE_REGISTER_CALLBACK |
| 286 | .write() |
| 287 | .expect("Write MODULE_REGISTER_CALLBACK in napi_register_module_v1 failed" ); |
| 288 | register_callback |
| 289 | .iter_mut() |
| 290 | .fold( |
| 291 | HashMap::<Option<&'static str>, Vec<(&'static str, ExportRegisterCallback)>>::new(), |
| 292 | |mut acc, (js_mod, item)| { |
| 293 | if let Some(k) = acc.get_mut(js_mod) { |
| 294 | k.push(*item); |
| 295 | } else { |
| 296 | acc.insert(*js_mod, vec![*item]); |
| 297 | } |
| 298 | acc |
| 299 | }, |
| 300 | ) |
| 301 | .iter() |
| 302 | .for_each(|(js_mod, items)| { |
| 303 | let mut exports_js_mod = ptr::null_mut(); |
| 304 | if let Some(js_mod_str) = js_mod { |
| 305 | let mod_name_c_str = |
| 306 | unsafe { CStr::from_bytes_with_nul_unchecked(js_mod_str.as_bytes()) }; |
| 307 | if exports_objects.contains(*js_mod_str) { |
| 308 | check_status_or_throw!( |
| 309 | env, |
| 310 | unsafe { |
| 311 | sys::napi_get_named_property( |
| 312 | env, |
| 313 | exports, |
| 314 | mod_name_c_str.as_ptr(), |
| 315 | &mut exports_js_mod, |
| 316 | ) |
| 317 | }, |
| 318 | "Get mod {} from exports failed" , |
| 319 | js_mod_str, |
| 320 | ); |
| 321 | } else { |
| 322 | check_status_or_throw!( |
| 323 | env, |
| 324 | unsafe { sys::napi_create_object(env, &mut exports_js_mod) }, |
| 325 | "Create export JavaScript Object [ {}] failed" , |
| 326 | js_mod_str |
| 327 | ); |
| 328 | check_status_or_throw!( |
| 329 | env, |
| 330 | unsafe { |
| 331 | sys::napi_set_named_property(env, exports, mod_name_c_str.as_ptr(), exports_js_mod) |
| 332 | }, |
| 333 | "Set exports Object [ {}] into exports object failed" , |
| 334 | js_mod_str |
| 335 | ); |
| 336 | exports_objects.insert(js_mod_str.to_string()); |
| 337 | } |
| 338 | } |
| 339 | for (name, callback) in items { |
| 340 | unsafe { |
| 341 | let js_name = CStr::from_bytes_with_nul_unchecked(name.as_bytes()); |
| 342 | if let Err(e) = callback(env).and_then(|v| { |
| 343 | let exported_object = if exports_js_mod.is_null() { |
| 344 | exports |
| 345 | } else { |
| 346 | exports_js_mod |
| 347 | }; |
| 348 | check_status!( |
| 349 | sys::napi_set_named_property(env, exported_object, js_name.as_ptr(), v), |
| 350 | "Failed to register export ` {}`" , |
| 351 | name, |
| 352 | ) |
| 353 | }) { |
| 354 | JsError::from(e).throw_into(env) |
| 355 | } |
| 356 | } |
| 357 | } |
| 358 | }); |
| 359 | } |
| 360 | |
| 361 | let mut registered_classes = HashMap::new(); |
| 362 | |
| 363 | MODULE_CLASS_PROPERTIES.borrow_mut(|inner| { |
| 364 | inner.iter().for_each(|(rust_name, js_mods)| { |
| 365 | for (js_mod, (js_name, props)) in js_mods { |
| 366 | let mut exports_js_mod = ptr::null_mut(); |
| 367 | unsafe { |
| 368 | if let Some(js_mod_str) = js_mod { |
| 369 | let mod_name_c_str = CStr::from_bytes_with_nul_unchecked(js_mod_str.as_bytes()); |
| 370 | if exports_objects.contains(*js_mod_str) { |
| 371 | check_status_or_throw!( |
| 372 | env, |
| 373 | sys::napi_get_named_property( |
| 374 | env, |
| 375 | exports, |
| 376 | mod_name_c_str.as_ptr(), |
| 377 | &mut exports_js_mod, |
| 378 | ), |
| 379 | "Get mod {} from exports failed" , |
| 380 | js_mod_str, |
| 381 | ); |
| 382 | } else { |
| 383 | check_status_or_throw!( |
| 384 | env, |
| 385 | sys::napi_create_object(env, &mut exports_js_mod), |
| 386 | "Create export JavaScript Object [ {}] failed" , |
| 387 | js_mod_str |
| 388 | ); |
| 389 | check_status_or_throw!( |
| 390 | env, |
| 391 | sys::napi_set_named_property(env, exports, mod_name_c_str.as_ptr(), exports_js_mod), |
| 392 | "Set exports Object [ {}] into exports object failed" , |
| 393 | js_mod_str |
| 394 | ); |
| 395 | exports_objects.insert(js_mod_str.to_string()); |
| 396 | } |
| 397 | } |
| 398 | let (ctor, props): (Vec<_>, Vec<_>) = props.iter().partition(|prop| prop.is_ctor); |
| 399 | |
| 400 | let ctor = ctor |
| 401 | .first() |
| 402 | .map(|c| c.raw().method.unwrap()) |
| 403 | .unwrap_or(noop); |
| 404 | let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect(); |
| 405 | |
| 406 | let js_class_name = CStr::from_bytes_with_nul_unchecked(js_name.as_bytes()); |
| 407 | let mut class_ptr = ptr::null_mut(); |
| 408 | |
| 409 | check_status_or_throw!( |
| 410 | env, |
| 411 | sys::napi_define_class( |
| 412 | env, |
| 413 | js_class_name.as_ptr(), |
| 414 | js_name.len() - 1, |
| 415 | Some(ctor), |
| 416 | ptr::null_mut(), |
| 417 | raw_props.len(), |
| 418 | raw_props.as_ptr(), |
| 419 | &mut class_ptr, |
| 420 | ), |
| 421 | "Failed to register class ` {}` generate by struct ` {}`" , |
| 422 | &js_name, |
| 423 | &rust_name |
| 424 | ); |
| 425 | |
| 426 | let mut ctor_ref = ptr::null_mut(); |
| 427 | sys::napi_create_reference(env, class_ptr, 1, &mut ctor_ref); |
| 428 | |
| 429 | registered_classes.insert(js_name.to_string(), ctor_ref); |
| 430 | |
| 431 | check_status_or_throw!( |
| 432 | env, |
| 433 | sys::napi_set_named_property( |
| 434 | env, |
| 435 | if exports_js_mod.is_null() { |
| 436 | exports |
| 437 | } else { |
| 438 | exports_js_mod |
| 439 | }, |
| 440 | js_class_name.as_ptr(), |
| 441 | class_ptr |
| 442 | ), |
| 443 | "Failed to register class ` {}` generate by struct ` {}`" , |
| 444 | &js_name, |
| 445 | &rust_name |
| 446 | ); |
| 447 | } |
| 448 | } |
| 449 | }); |
| 450 | |
| 451 | REGISTERED_CLASSES.borrow_mut(|map| { |
| 452 | map.insert( |
| 453 | std::thread::current().id(), |
| 454 | PersistedPerInstanceHashMap::from_hashmap(registered_classes), |
| 455 | ) |
| 456 | }); |
| 457 | }); |
| 458 | |
| 459 | #[cfg (feature = "compat-mode" )] |
| 460 | { |
| 461 | let module_exports = MODULE_EXPORTS.read().expect("Read MODULE_EXPORTS failed" ); |
| 462 | module_exports.iter().for_each(|callback| unsafe { |
| 463 | if let Err(e) = callback(env, exports) { |
| 464 | JsError::from(e).throw_into(env); |
| 465 | } |
| 466 | }) |
| 467 | } |
| 468 | |
| 469 | #[cfg (all(not(target_family = "wasm" ), feature = "napi4" , feature = "tokio_rt" ))] |
| 470 | { |
| 471 | crate::tokio_runtime::ensure_runtime(); |
| 472 | |
| 473 | static init_counter: AtomicUsize = AtomicUsize::new(0); |
| 474 | let cleanup_hook_payload = |
| 475 | init_counter.fetch_add(1, Ordering::Relaxed) as *mut std::ffi::c_void; |
| 476 | |
| 477 | unsafe { |
| 478 | sys::napi_add_env_cleanup_hook( |
| 479 | env, |
| 480 | Some(crate::tokio_runtime::drop_runtime), |
| 481 | cleanup_hook_payload, |
| 482 | ) |
| 483 | }; |
| 484 | } |
| 485 | #[cfg (all(feature = "napi4" , not(target_family = "wasm" )))] |
| 486 | create_custom_gc(env); |
| 487 | FIRST_MODULE_REGISTERED.store(true, Ordering::SeqCst); |
| 488 | exports |
| 489 | } |
| 490 | |
| 491 | #[cfg (not(feature = "noop" ))] |
| 492 | pub(crate) unsafe extern "C" fn noop( |
| 493 | env: sys::napi_env, |
| 494 | _info: sys::napi_callback_info, |
| 495 | ) -> sys::napi_value { |
| 496 | if !crate::bindgen_runtime::___CALL_FROM_FACTORY.with(|s: &AtomicBool| s.load(order:Ordering::Relaxed)) { |
| 497 | unsafe { |
| 498 | sys::napi_throw_error( |
| 499 | env, |
| 500 | code:ptr::null_mut(), |
| 501 | msg:CStr::from_bytes_with_nul_unchecked(bytes:b"Class contains no `constructor`, can not new it! \0" ) |
| 502 | .as_ptr(), |
| 503 | ); |
| 504 | } |
| 505 | } |
| 506 | ptr::null_mut() |
| 507 | } |
| 508 | |
| 509 | #[cfg (all(feature = "napi4" , not(target_family = "wasm" ), not(feature = "noop" )))] |
| 510 | fn create_custom_gc(env: sys::napi_env) { |
| 511 | if !FIRST_MODULE_REGISTERED.load(Ordering::SeqCst) { |
| 512 | let mut custom_gc_fn = ptr::null_mut(); |
| 513 | check_status_or_throw!( |
| 514 | env, |
| 515 | unsafe { |
| 516 | sys::napi_create_function( |
| 517 | env, |
| 518 | "custom_gc" .as_ptr().cast(), |
| 519 | 9, |
| 520 | Some(empty), |
| 521 | ptr::null_mut(), |
| 522 | &mut custom_gc_fn, |
| 523 | ) |
| 524 | }, |
| 525 | "Create Custom GC Function in napi_register_module_v1 failed" |
| 526 | ); |
| 527 | let mut async_resource_name = ptr::null_mut(); |
| 528 | check_status_or_throw!( |
| 529 | env, |
| 530 | unsafe { |
| 531 | sys::napi_create_string_utf8(env, "CustomGC" .as_ptr().cast(), 8, &mut async_resource_name) |
| 532 | }, |
| 533 | "Create async resource string in napi_register_module_v1" |
| 534 | ); |
| 535 | let mut custom_gc_tsfn = ptr::null_mut(); |
| 536 | check_status_or_throw!( |
| 537 | env, |
| 538 | unsafe { |
| 539 | sys::napi_create_threadsafe_function( |
| 540 | env, |
| 541 | custom_gc_fn, |
| 542 | ptr::null_mut(), |
| 543 | async_resource_name, |
| 544 | 0, |
| 545 | 1, |
| 546 | ptr::null_mut(), |
| 547 | Some(custom_gc_finalize), |
| 548 | ptr::null_mut(), |
| 549 | Some(custom_gc), |
| 550 | &mut custom_gc_tsfn, |
| 551 | ) |
| 552 | }, |
| 553 | "Create Custom GC ThreadsafeFunction in napi_register_module_v1 failed" |
| 554 | ); |
| 555 | check_status_or_throw!( |
| 556 | env, |
| 557 | unsafe { sys::napi_unref_threadsafe_function(env, custom_gc_tsfn) }, |
| 558 | "Unref Custom GC ThreadsafeFunction in napi_register_module_v1 failed" |
| 559 | ); |
| 560 | CUSTOM_GC_TSFN.store(custom_gc_tsfn, Ordering::Relaxed); |
| 561 | } |
| 562 | |
| 563 | let current_thread_id = std::thread::current().id(); |
| 564 | THREADS_CAN_ACCESS_ENV.borrow_mut(|m| m.insert(current_thread_id, true)); |
| 565 | check_status_or_throw!( |
| 566 | env, |
| 567 | unsafe { |
| 568 | sys::napi_add_env_cleanup_hook( |
| 569 | env, |
| 570 | Some(remove_thread_id), |
| 571 | Box::into_raw(Box::new(current_thread_id)).cast(), |
| 572 | ) |
| 573 | }, |
| 574 | "Failed to add remove thread id cleanup hook" |
| 575 | ); |
| 576 | } |
| 577 | |
| 578 | #[cfg (all(feature = "napi4" , not(target_family = "wasm" ), not(feature = "noop" )))] |
| 579 | unsafe extern "C" fn remove_thread_id(id: *mut std::ffi::c_void) { |
| 580 | let thread_id: Box = unsafe { Box::from_raw(id.cast::<ThreadId>()) }; |
| 581 | THREADS_CAN_ACCESS_ENV.borrow_mut(|m: &mut HashMap| m.insert(*thread_id, v:false)); |
| 582 | } |
| 583 | |
| 584 | #[cfg (all(feature = "napi4" , not(target_family = "wasm" ), not(feature = "noop" )))] |
| 585 | #[allow (unused)] |
| 586 | unsafe extern "C" fn empty(env: sys::napi_env, info: sys::napi_callback_info) -> sys::napi_value { |
| 587 | ptr::null_mut() |
| 588 | } |
| 589 | |
| 590 | #[cfg (all(feature = "napi4" , not(target_family = "wasm" ), not(feature = "noop" )))] |
| 591 | #[allow (unused_variables)] |
| 592 | unsafe extern "C" fn custom_gc_finalize( |
| 593 | env: sys::napi_env, |
| 594 | finalize_data: *mut std::ffi::c_void, |
| 595 | finalize_hint: *mut std::ffi::c_void, |
| 596 | ) { |
| 597 | CUSTOM_GC_TSFN_DESTROYED.store(val:true, order:Ordering::SeqCst); |
| 598 | } |
| 599 | |
| 600 | #[cfg (all(feature = "napi4" , not(target_family = "wasm" ), not(feature = "noop" )))] |
| 601 | // recycle the ArrayBuffer/Buffer Reference if the ArrayBuffer/Buffer is not dropped on the main thread |
| 602 | extern "C" fn custom_gc( |
| 603 | env: sys::napi_env, |
| 604 | _js_callback: sys::napi_value, |
| 605 | _context: *mut std::ffi::c_void, |
| 606 | data: *mut std::ffi::c_void, |
| 607 | ) { |
| 608 | // current thread was destroyed |
| 609 | if THREADS_CAN_ACCESS_ENV.borrow_mut(|m: &mut HashMap| m.get(&std::thread::current().id()) == Some(&false)) |
| 610 | || data.is_null() |
| 611 | { |
| 612 | return; |
| 613 | } |
| 614 | let mut ref_count: u32 = 0; |
| 615 | check_status_or_throw!( |
| 616 | env, |
| 617 | unsafe { sys::napi_reference_unref(env, data.cast(), &mut ref_count) }, |
| 618 | "Failed to unref Buffer reference in Custom GC" |
| 619 | ); |
| 620 | debug_assert!( |
| 621 | ref_count == 0, |
| 622 | "Buffer reference count in Custom GC is not 0" |
| 623 | ); |
| 624 | check_status_or_throw!( |
| 625 | env, |
| 626 | unsafe { sys::napi_delete_reference(env, data.cast()) }, |
| 627 | "Failed to delete Buffer reference in Custom GC" |
| 628 | ); |
| 629 | } |
| 630 | |