,
) {
let mut counters = self.tsfn_ref_counters.lock();
assert!(!counters.iter().any(|(i, _)| *i == id));
counters.push((id, counter));
}
pub fn remove_threadsafe_function_ref_counter(&mut self, id: usize) {
let mut counters = self.tsfn_ref_counters.lock();
let index = counters.iter().position(|(i, _)| *i == id).unwrap();
counters.remove(index);
}
}
deno_core::extension!(deno_napi,
parameters = [P: NapiPermissions],
ops = [
op_napi_open
],
state = |state| {
let (async_work_sender, async_work_receiver) =
mpsc::unbounded::();
let (threadsafe_function_sender, threadsafe_function_receiver) =
mpsc::unbounded::();
state.put(NapiState {
pending_async_work: Vec::new(),
async_work_sender,
async_work_receiver,
threadsafe_function_sender,
threadsafe_function_receiver,
active_threadsafe_functions: 0,
env_cleanup_hooks: Rc::new(RefCell::new(vec![])),
tsfn_ref_counters: Arc::new(Mutex::new(vec![])),
});
},
event_loop_middleware = event_loop_middleware,
);
fn event_loop_middleware(
op_state_rc: Rc>,
cx: &mut std::task::Context,
) -> bool {
// `work` can call back into the runtime. It can also schedule an async task
// but we don't know that now. We need to make the runtime re-poll to make
// sure no pending NAPI tasks exist.
let mut maybe_scheduling = false;
{
let mut op_state = op_state_rc.borrow_mut();
let napi_state = op_state.borrow_mut::();
while let Poll::Ready(Some(async_work_fut)) =
napi_state.async_work_receiver.poll_next_unpin(cx)
{
napi_state.pending_async_work.push(async_work_fut);
}
if napi_state.active_threadsafe_functions > 0 {
maybe_scheduling = true;
}
let tsfn_ref_counters = napi_state.tsfn_ref_counters.lock().clone();
for (_id, counter) in tsfn_ref_counters.iter() {
if counter.load(std::sync::atomic::Ordering::SeqCst) > 0 {
maybe_scheduling = true;
break;
}
}
}
loop {
let maybe_work = {
let mut op_state = op_state_rc.borrow_mut();
let napi_state = op_state.borrow_mut::();
napi_state.pending_async_work.pop()
};
if let Some(work) = maybe_work {
work();
maybe_scheduling = true;
} else {
break;
}
}
maybe_scheduling
}
pub trait NapiPermissions {
fn check(&mut self, path: Option<&Path>)
-> std::result::Result<(), AnyError>;
}
/// # Safety
///
/// This function is unsafe because it dereferences raw pointer Env.
/// - The caller must ensure that the pointer is valid.
/// - The caller must ensure that the pointer is not freed.
pub unsafe fn weak_local(
env_ptr: *mut Env,
value: v8::Local,
data: *mut c_void,
finalize_cb: napi_finalize,
finalize_hint: *mut c_void,
) -> Option> {
use std::cell::Cell;
let env = &mut *env_ptr;
let weak_ptr = Rc::new(Cell::new(None));
let scope = &mut env.scope();
let weak = v8::Weak::with_finalizer(
scope,
value,
Box::new({
let weak_ptr = weak_ptr.clone();
move |isolate| {
finalize_cb(env_ptr as _, data as _, finalize_hint as _);
// Self-deleting weak.
if let Some(weak_ptr) = weak_ptr.get() {
let weak: v8::Weak =
unsafe { v8::Weak::from_raw(isolate, Some(weak_ptr)) };
drop(weak);
}
}
}),
);
let value = weak.to_local(scope);
let raw = weak.into_raw();
weak_ptr.set(raw);
value
}
#[op(v8)]
fn op_napi_open(
scope: &mut v8::HandleScope<'scope>,
op_state: &mut OpState,
path: String,
global: serde_v8::Value,
) -> std::result::Result, AnyError>
where
NP: NapiPermissions + 'static,
{
let permissions = op_state.borrow_mut::();
permissions.check(Some(&PathBuf::from(&path)))?;
let (
async_work_sender,
tsfn_sender,
isolate_ptr,
cleanup_hooks,
tsfn_ref_counters,
) = {
let napi_state = op_state.borrow::();
let isolate_ptr = op_state.borrow::<*mut v8::OwnedIsolate>();
(
napi_state.async_work_sender.clone(),
napi_state.threadsafe_function_sender.clone(),
*isolate_ptr,
napi_state.env_cleanup_hooks.clone(),
napi_state.tsfn_ref_counters.clone(),
)
};
let napi_wrap_name = v8::String::new(scope, "napi_wrap").unwrap();
let napi_wrap = v8::Private::new(scope, Some(napi_wrap_name));
let napi_wrap = v8::Global::new(scope, napi_wrap);
// The `module.exports` object.
let exports = v8::Object::new(scope);
let mut env_shared = EnvShared::new(napi_wrap);
let cstr = CString::new(&*path).unwrap();
env_shared.filename = cstr.as_ptr();
std::mem::forget(cstr);
let ctx = scope.get_current_context();
let mut env = Env::new(
isolate_ptr,
v8::Global::new(scope, ctx),
v8::Global::new(scope, global.v8_value),
async_work_sender,
tsfn_sender,
cleanup_hooks,
tsfn_ref_counters,
);
env.shared = Box::into_raw(Box::new(env_shared));
let env_ptr = Box::into_raw(Box::new(env)) as _;
#[cfg(unix)]
let flags = RTLD_LAZY;
#[cfg(not(unix))]
let flags = 0x00000008;
// SAFETY: opening a DLL calls dlopen
#[cfg(unix)]
let library = match unsafe { Library::open(Some(&path), flags) } {
Ok(lib) => lib,
Err(e) => return Err(type_error(e.to_string())),
};
// SAFETY: opening a DLL calls dlopen
#[cfg(not(unix))]
let library = match unsafe { Library::load_with_flags(&path, flags) } {
Ok(lib) => lib,
Err(e) => return Err(type_error(e.to_string())),
};
MODULE.with(|cell| {
let slot = *cell.borrow();
let obj = match slot {
Some(nm) => {
// SAFETY: napi_register_module guarantees that `nm` is valid.
let nm = unsafe { &*nm };
assert_eq!(nm.nm_version, 1);
// SAFETY: we are going blind, calling the register function on the other side.
let maybe_exports = unsafe {
(nm.nm_register_func)(
env_ptr,
std::mem::transmute::, napi_value>(
exports.into(),
),
)
};
let exports = maybe_exports
.as_ref()
.map(|_| unsafe {
// SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer
// to a value, they have the same layout
std::mem::transmute::>(
maybe_exports,
)
})
.unwrap_or_else(|| {
// If the module didn't return anything, we use the exports object.
exports.into()
});
Ok(serde_v8::Value { v8_value: exports })
}
None => {
// Initializer callback.
// SAFETY: we are going blind, calling the register function on the other side.
unsafe {
let init = library
.get:: napi_value>(b"napi_register_module_v1")
.expect("napi_register_module_v1 not found");
let maybe_exports = init(
env_ptr,
std::mem::transmute::, napi_value>(
exports.into(),
),
);
let exports = maybe_exports
.as_ref()
.map(|_| {
// SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer
// to a value, they have the same layout
std::mem::transmute::>(
maybe_exports,
)
})
.unwrap_or_else(|| {
// If the module didn't return anything, we use the exports object.
exports.into()
});
Ok(serde_v8::Value { v8_value: exports })
}
}
};
// NAPI addons can't be unloaded, so we're going to "forget" the library
// object so it lives till the program exit.
std::mem::forget(library);
obj
})
}