fix(napi): thread safe issue while creating class instance (#1561)

This commit is contained in:
LongYinan 2023-04-15 15:37:01 +08:00 committed by GitHub
parent 77f53a8cc9
commit d14fdca242
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 42 additions and 19 deletions

View file

@ -1,14 +1,10 @@
use std::cell::Cell; use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::ffi::c_void; use std::ffi::c_void;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use once_cell::sync::Lazy; use crate::{bindgen_runtime::ToNapiValue, check_status, Env, Error, Result, Status};
use crate::{
bindgen_runtime::{PersistedPerInstanceHashMap, ToNapiValue},
check_status, Env, Error, Result, Status,
};
type RefInformation = ( type RefInformation = (
*mut c_void, *mut c_void,
@ -16,8 +12,9 @@ type RefInformation = (
*const Cell<*mut dyn FnOnce()>, *const Cell<*mut dyn FnOnce()>,
); );
pub(crate) static REFERENCE_MAP: Lazy<PersistedPerInstanceHashMap<*mut c_void, RefInformation>> = thread_local! {
Lazy::new(Default::default); pub(crate) static REFERENCE_MAP: RefCell<HashMap<*mut c_void, RefInformation>> = RefCell::new(HashMap::default());
}
/// ### Experimental feature /// ### Experimental feature
/// ///
@ -61,8 +58,8 @@ impl<T: 'static> Reference<T> {
#[doc(hidden)] #[doc(hidden)]
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn add_ref(env: crate::sys::napi_env, t: *mut c_void, value: RefInformation) { pub fn add_ref(env: crate::sys::napi_env, t: *mut c_void, value: RefInformation) {
REFERENCE_MAP.borrow_mut(|map| { REFERENCE_MAP.with(|map| {
if let Some((_, previous_ref, previous_rc)) = map.insert(t, value) { if let Some((_, previous_ref, previous_rc)) = map.borrow_mut().insert(t, value) {
unsafe { Rc::from_raw(previous_rc) }; unsafe { Rc::from_raw(previous_rc) };
unsafe { crate::sys::napi_delete_reference(env, previous_ref) }; unsafe { crate::sys::napi_delete_reference(env, previous_ref) };
} }
@ -72,7 +69,7 @@ impl<T: 'static> Reference<T> {
#[doc(hidden)] #[doc(hidden)]
pub unsafe fn from_value_ptr(t: *mut c_void, env: crate::sys::napi_env) -> Result<Self> { pub unsafe fn from_value_ptr(t: *mut c_void, env: crate::sys::napi_env) -> Result<Self> {
if let Some((wrapped_value, napi_ref, finalize_callbacks_ptr)) = if let Some((wrapped_value, napi_ref, finalize_callbacks_ptr)) =
REFERENCE_MAP.borrow_mut(|map| map.get(&t).cloned()) REFERENCE_MAP.with(|map| map.borrow().get(&t).cloned())
{ {
check_status!( check_status!(
unsafe { crate::sys::napi_reference_ref(env, napi_ref, &mut 0) }, unsafe { crate::sys::napi_reference_ref(env, napi_ref, &mut 0) },

View file

@ -40,7 +40,7 @@ pub unsafe extern "C" fn raw_finalize_unchecked<T: ObjectFinalize>(
unsafe { e.throw_into(env) }; unsafe { e.throw_into(env) };
} }
if let Some((_, ref_val, finalize_callbacks_ptr)) = if let Some((_, ref_val, finalize_callbacks_ptr)) =
REFERENCE_MAP.borrow_mut(|reference_map| reference_map.remove(&finalize_data)) REFERENCE_MAP.with(|reference_map| reference_map.borrow_mut().remove(&finalize_data))
{ {
let finalize_callbacks_rc = unsafe { Rc::from_raw(finalize_callbacks_ptr) }; let finalize_callbacks_rc = unsafe { Rc::from_raw(finalize_callbacks_ptr) };

View file

@ -5,10 +5,7 @@ import test from 'ava'
import { Animal, Kind, DEFAULT_COST } from '../index' import { Animal, Kind, DEFAULT_COST } from '../index'
const t = test('should be able to require in worker thread', async (t) => {
process.arch === 'arm64' && process.platform === 'linux' ? test.skip : test
t('should be able to require in worker thread', async (t) => {
await Promise.all( await Promise.all(
Array.from({ length: 100 }).map(() => { Array.from({ length: 100 }).map(() => {
const w = new Worker(join(__dirname, 'worker.js')) const w = new Worker(join(__dirname, 'worker.js'))
@ -30,7 +27,7 @@ t('should be able to require in worker thread', async (t) => {
) )
}) })
t('custom GC works on worker_threads', async (t) => { test('custom GC works on worker_threads', async (t) => {
await Promise.all( await Promise.all(
Array.from({ length: 50 }).map(() => Array.from({ length: 50 }).map(() =>
Promise.all([ Promise.all([
@ -68,3 +65,25 @@ t('custom GC works on worker_threads', async (t) => {
), ),
) )
}) })
test('should be able to new Class in worker thread concurrently', async (t) => {
await Promise.all(
Array.from({ length: 100 }).map(() => {
const w = new Worker(join(__dirname, 'worker.js'))
return new Promise<void>((resolve, reject) => {
w.postMessage({ type: 'constructor' })
w.on('message', (msg) => {
t.is(msg, 'Ellie')
resolve()
})
w.on('error', (err) => {
reject(err)
})
})
.then(() => w.terminate())
.then(() => {
t.pass()
})
}),
)
})

View file

@ -35,6 +35,13 @@ parentPort.on('message', ({ type }) => {
throw e throw e
}) })
break
case 'constructor':
let ellie
for (let i = 0; i < 10000; i++) {
ellie = new native.Animal(native.Kind.Cat, 'Ellie')
}
parentPort.postMessage(ellie.name)
break break
default: default:
throw new TypeError(`Unknown message type: ${type}`) throw new TypeError(`Unknown message type: ${type}`)

View file

@ -22,7 +22,7 @@
"build": "lerna run build --scope '@napi-rs/*'", "build": "lerna run build --scope '@napi-rs/*'",
"build:bench": "yarn workspace bench build", "build:bench": "yarn workspace bench build",
"build:memory": "yarn workspace memory-testing build", "build:memory": "yarn workspace memory-testing build",
"build:test": "lerna run build --scope '@examples/*'", "build:test": "lerna run build --stream --concurrency 1 --scope '@examples/*'",
"format": "run-p format:prettier format:rs format:toml", "format": "run-p format:prettier format:rs format:toml",
"format:prettier": "prettier . -w", "format:prettier": "prettier . -w",
"format:rs": "cargo fmt", "format:rs": "cargo fmt",