mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -05:00
e1c893fab8
exposes node-api symbols in denort so that `deno compile` can run native addons.
1010 lines
22 KiB
Rust
1010 lines
22 KiB
Rust
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
#![deny(unsafe_op_in_unsafe_fn)]
|
|
|
|
use super::util::get_array_buffer_ptr;
|
|
use super::util::make_external_backing_store;
|
|
use super::util::napi_clear_last_error;
|
|
use super::util::napi_set_last_error;
|
|
use super::util::SendPtr;
|
|
use crate::check_arg;
|
|
use crate::check_env;
|
|
use crate::*;
|
|
use deno_core::parking_lot::Condvar;
|
|
use deno_core::parking_lot::Mutex;
|
|
use deno_core::V8CrossThreadTaskSpawner;
|
|
use napi_sym::napi_sym;
|
|
use std::sync::atomic::AtomicBool;
|
|
use std::sync::atomic::AtomicU8;
|
|
use std::sync::atomic::AtomicUsize;
|
|
use std::sync::atomic::Ordering;
|
|
use std::sync::Arc;
|
|
|
|
#[napi_sym]
|
|
fn napi_module_register(module: *const NapiModule) -> napi_status {
|
|
MODULE_TO_REGISTER.with(|cell| {
|
|
let mut slot = cell.borrow_mut();
|
|
let prev = slot.replace(module);
|
|
assert!(prev.is_none());
|
|
});
|
|
napi_ok
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_add_env_cleanup_hook(
|
|
env: *mut Env,
|
|
fun: Option<napi_cleanup_hook>,
|
|
arg: *mut c_void,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
check_arg!(env, fun);
|
|
|
|
let fun = fun.unwrap();
|
|
|
|
env.add_cleanup_hook(fun, arg);
|
|
|
|
napi_ok
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_remove_env_cleanup_hook(
|
|
env: *mut Env,
|
|
fun: Option<napi_cleanup_hook>,
|
|
arg: *mut c_void,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
check_arg!(env, fun);
|
|
|
|
let fun = fun.unwrap();
|
|
|
|
env.remove_cleanup_hook(fun, arg);
|
|
|
|
napi_ok
|
|
}
|
|
|
|
struct AsyncCleanupHandle {
|
|
env: *mut Env,
|
|
hook: napi_async_cleanup_hook,
|
|
data: *mut c_void,
|
|
}
|
|
|
|
unsafe extern "C" fn async_cleanup_handler(arg: *mut c_void) {
|
|
unsafe {
|
|
let handle = Box::<AsyncCleanupHandle>::from_raw(arg as _);
|
|
(handle.hook)(arg, handle.data);
|
|
}
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_add_async_cleanup_hook(
|
|
env: *mut Env,
|
|
hook: Option<napi_async_cleanup_hook>,
|
|
arg: *mut c_void,
|
|
remove_handle: *mut napi_async_cleanup_hook_handle,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
check_arg!(env, hook);
|
|
|
|
let hook = hook.unwrap();
|
|
|
|
let handle = Box::into_raw(Box::new(AsyncCleanupHandle {
|
|
env,
|
|
hook,
|
|
data: arg,
|
|
})) as *mut c_void;
|
|
|
|
env.add_cleanup_hook(async_cleanup_handler, handle);
|
|
|
|
if !remove_handle.is_null() {
|
|
unsafe {
|
|
*remove_handle = handle;
|
|
}
|
|
}
|
|
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_remove_async_cleanup_hook(
|
|
remove_handle: napi_async_cleanup_hook_handle,
|
|
) -> napi_status {
|
|
if remove_handle.is_null() {
|
|
return napi_invalid_arg;
|
|
}
|
|
|
|
let handle =
|
|
unsafe { Box::<AsyncCleanupHandle>::from_raw(remove_handle as _) };
|
|
|
|
let env = unsafe { &mut *handle.env };
|
|
|
|
env.remove_cleanup_hook(async_cleanup_handler, remove_handle);
|
|
|
|
napi_ok
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_fatal_exception(env: &mut Env, err: napi_value) -> napi_status {
|
|
check_arg!(env, err);
|
|
|
|
let report_error = v8::Local::new(&mut env.scope(), &env.report_error);
|
|
|
|
let this = v8::undefined(&mut env.scope());
|
|
if report_error
|
|
.call(&mut env.scope(), this.into(), &[err.unwrap()])
|
|
.is_none()
|
|
{
|
|
return napi_generic_failure;
|
|
}
|
|
|
|
napi_ok
|
|
}
|
|
|
|
#[napi_sym]
|
|
#[allow(clippy::print_stderr)]
|
|
fn napi_fatal_error(
|
|
location: *const c_char,
|
|
location_len: usize,
|
|
message: *const c_char,
|
|
message_len: usize,
|
|
) -> napi_status {
|
|
let location = if location.is_null() {
|
|
None
|
|
} else {
|
|
unsafe {
|
|
Some(if location_len == NAPI_AUTO_LENGTH {
|
|
std::ffi::CStr::from_ptr(location).to_str().unwrap()
|
|
} else {
|
|
let slice = std::slice::from_raw_parts(
|
|
location as *const u8,
|
|
location_len as usize,
|
|
);
|
|
std::str::from_utf8(slice).unwrap()
|
|
})
|
|
}
|
|
};
|
|
|
|
let message = if message_len == NAPI_AUTO_LENGTH {
|
|
unsafe { std::ffi::CStr::from_ptr(message).to_str().unwrap() }
|
|
} else {
|
|
let slice = unsafe {
|
|
std::slice::from_raw_parts(message as *const u8, message_len as usize)
|
|
};
|
|
std::str::from_utf8(slice).unwrap()
|
|
};
|
|
|
|
if let Some(location) = location {
|
|
eprintln!("NODE API FATAL ERROR: {} {}", location, message);
|
|
} else {
|
|
eprintln!("NODE API FATAL ERROR: {}", message);
|
|
}
|
|
|
|
std::process::abort();
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_open_callback_scope(
|
|
env: *mut Env,
|
|
_resource_object: napi_value,
|
|
_context: napi_value,
|
|
result: *mut napi_callback_scope,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
check_arg!(env, result);
|
|
|
|
// we open scope automatically when it's needed
|
|
unsafe {
|
|
*result = std::ptr::null_mut();
|
|
}
|
|
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_close_callback_scope(
|
|
env: *mut Env,
|
|
scope: napi_callback_scope,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
// we close scope automatically when it's needed
|
|
assert!(scope.is_null());
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
// NOTE: we don't support "async_hooks::AsyncContext" so these APIs are noops.
|
|
#[napi_sym]
|
|
fn napi_async_init(
|
|
env: *mut Env,
|
|
_async_resource: napi_value,
|
|
_async_resource_name: napi_value,
|
|
result: *mut napi_async_context,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
unsafe {
|
|
*result = ptr::null_mut();
|
|
}
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_async_destroy(
|
|
env: *mut Env,
|
|
async_context: napi_async_context,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
assert!(async_context.is_null());
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_make_callback<'s>(
|
|
env: &'s mut Env,
|
|
_async_context: napi_async_context,
|
|
recv: napi_value,
|
|
func: napi_value,
|
|
argc: usize,
|
|
argv: *const napi_value<'s>,
|
|
result: *mut napi_value<'s>,
|
|
) -> napi_status {
|
|
check_arg!(env, recv);
|
|
if argc > 0 {
|
|
check_arg!(env, argv);
|
|
}
|
|
|
|
let Some(recv) = recv.and_then(|v| v.to_object(&mut env.scope())) else {
|
|
return napi_object_expected;
|
|
};
|
|
|
|
let Some(func) =
|
|
func.and_then(|v| v8::Local::<v8::Function>::try_from(v).ok())
|
|
else {
|
|
return napi_function_expected;
|
|
};
|
|
|
|
let args = if argc > 0 {
|
|
unsafe {
|
|
std::slice::from_raw_parts(argv as *mut v8::Local<v8::Value>, argc)
|
|
}
|
|
} else {
|
|
&[]
|
|
};
|
|
|
|
// TODO: async_context
|
|
|
|
let Some(v) = func.call(&mut env.scope(), recv.into(), args) else {
|
|
return napi_generic_failure;
|
|
};
|
|
|
|
unsafe {
|
|
*result = v.into();
|
|
}
|
|
|
|
napi_ok
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_create_buffer<'s>(
|
|
env: &'s mut Env,
|
|
length: usize,
|
|
data: *mut *mut c_void,
|
|
result: *mut napi_value<'s>,
|
|
) -> napi_status {
|
|
check_arg!(env, result);
|
|
|
|
let ab = v8::ArrayBuffer::new(&mut env.scope(), length);
|
|
|
|
let buffer_constructor =
|
|
v8::Local::new(&mut env.scope(), &env.buffer_constructor);
|
|
let Some(buffer) =
|
|
buffer_constructor.new_instance(&mut env.scope(), &[ab.into()])
|
|
else {
|
|
return napi_generic_failure;
|
|
};
|
|
|
|
if !data.is_null() {
|
|
unsafe {
|
|
*data = get_array_buffer_ptr(ab);
|
|
}
|
|
}
|
|
|
|
unsafe {
|
|
*result = buffer.into();
|
|
}
|
|
|
|
napi_ok
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_create_external_buffer<'s>(
|
|
env: &'s mut Env,
|
|
length: usize,
|
|
data: *mut c_void,
|
|
finalize_cb: napi_finalize,
|
|
finalize_hint: *mut c_void,
|
|
result: *mut napi_value<'s>,
|
|
) -> napi_status {
|
|
check_arg!(env, result);
|
|
|
|
let store = make_external_backing_store(
|
|
env,
|
|
data,
|
|
length,
|
|
ptr::null_mut(),
|
|
finalize_cb,
|
|
finalize_hint,
|
|
);
|
|
|
|
let ab =
|
|
v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared());
|
|
|
|
let buffer_constructor =
|
|
v8::Local::new(&mut env.scope(), &env.buffer_constructor);
|
|
let Some(buffer) =
|
|
buffer_constructor.new_instance(&mut env.scope(), &[ab.into()])
|
|
else {
|
|
return napi_generic_failure;
|
|
};
|
|
|
|
unsafe {
|
|
*result = buffer.into();
|
|
}
|
|
|
|
napi_ok
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_create_buffer_copy<'s>(
|
|
env: &'s mut Env,
|
|
length: usize,
|
|
data: *mut c_void,
|
|
result_data: *mut *mut c_void,
|
|
result: *mut napi_value<'s>,
|
|
) -> napi_status {
|
|
check_arg!(env, result);
|
|
|
|
let ab = v8::ArrayBuffer::new(&mut env.scope(), length);
|
|
|
|
let buffer_constructor =
|
|
v8::Local::new(&mut env.scope(), &env.buffer_constructor);
|
|
let Some(buffer) =
|
|
buffer_constructor.new_instance(&mut env.scope(), &[ab.into()])
|
|
else {
|
|
return napi_generic_failure;
|
|
};
|
|
|
|
let ptr = get_array_buffer_ptr(ab);
|
|
unsafe {
|
|
std::ptr::copy(data, ptr, length);
|
|
}
|
|
|
|
if !result_data.is_null() {
|
|
unsafe {
|
|
*result_data = ptr;
|
|
}
|
|
}
|
|
|
|
unsafe {
|
|
*result = buffer.into();
|
|
}
|
|
|
|
napi_ok
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_is_buffer(
|
|
env: *mut Env,
|
|
value: napi_value,
|
|
result: *mut bool,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
check_arg!(env, value);
|
|
check_arg!(env, result);
|
|
|
|
let buffer_constructor =
|
|
v8::Local::new(&mut env.scope(), &env.buffer_constructor);
|
|
|
|
let Some(is_buffer) = value
|
|
.unwrap()
|
|
.instance_of(&mut env.scope(), buffer_constructor.into())
|
|
else {
|
|
return napi_set_last_error(env, napi_generic_failure);
|
|
};
|
|
|
|
unsafe {
|
|
*result = is_buffer;
|
|
}
|
|
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_get_buffer_info(
|
|
env: *mut Env,
|
|
value: napi_value,
|
|
data: *mut *mut c_void,
|
|
length: *mut usize,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
check_arg!(env, value);
|
|
|
|
// NB: Any TypedArray instance seems to be accepted by this function
|
|
// in Node.js.
|
|
let Some(ta) =
|
|
value.and_then(|v| v8::Local::<v8::TypedArray>::try_from(v).ok())
|
|
else {
|
|
return napi_set_last_error(env, napi_invalid_arg);
|
|
};
|
|
|
|
if !data.is_null() {
|
|
unsafe {
|
|
*data = ta.data();
|
|
}
|
|
}
|
|
|
|
if !length.is_null() {
|
|
unsafe {
|
|
*length = ta.byte_length();
|
|
}
|
|
}
|
|
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_get_node_version(
|
|
env: *mut Env,
|
|
result: *mut *const napi_node_version,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
check_arg!(env, result);
|
|
|
|
const NODE_VERSION: napi_node_version = napi_node_version {
|
|
major: 20,
|
|
minor: 11,
|
|
patch: 1,
|
|
release: c"Deno".as_ptr(),
|
|
};
|
|
|
|
unsafe {
|
|
*result = &NODE_VERSION as *const napi_node_version;
|
|
}
|
|
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
struct AsyncWork {
|
|
state: AtomicU8,
|
|
env: *mut Env,
|
|
_async_resource: v8::Global<v8::Object>,
|
|
_async_resource_name: String,
|
|
execute: napi_async_execute_callback,
|
|
complete: Option<napi_async_complete_callback>,
|
|
data: *mut c_void,
|
|
}
|
|
|
|
impl AsyncWork {
|
|
const IDLE: u8 = 0;
|
|
const QUEUED: u8 = 1;
|
|
const RUNNING: u8 = 2;
|
|
}
|
|
|
|
#[napi_sym]
|
|
pub(crate) fn napi_create_async_work(
|
|
env: *mut Env,
|
|
async_resource: napi_value,
|
|
async_resource_name: napi_value,
|
|
execute: Option<napi_async_execute_callback>,
|
|
complete: Option<napi_async_complete_callback>,
|
|
data: *mut c_void,
|
|
result: *mut napi_async_work,
|
|
) -> napi_status {
|
|
let env_ptr = env;
|
|
let env = check_env!(env);
|
|
check_arg!(env, execute);
|
|
check_arg!(env, result);
|
|
|
|
let resource = if let Some(v) = *async_resource {
|
|
let Some(resource) = v.to_object(&mut env.scope()) else {
|
|
return napi_set_last_error(env, napi_object_expected);
|
|
};
|
|
resource
|
|
} else {
|
|
v8::Object::new(&mut env.scope())
|
|
};
|
|
|
|
let Some(resource_name) =
|
|
async_resource_name.and_then(|v| v.to_string(&mut env.scope()))
|
|
else {
|
|
return napi_set_last_error(env, napi_string_expected);
|
|
};
|
|
|
|
let resource_name = resource_name.to_rust_string_lossy(&mut env.scope());
|
|
|
|
let work = Box::new(AsyncWork {
|
|
state: AtomicU8::new(AsyncWork::IDLE),
|
|
env: env_ptr,
|
|
_async_resource: v8::Global::new(&mut env.scope(), resource),
|
|
_async_resource_name: resource_name,
|
|
execute: execute.unwrap(),
|
|
complete,
|
|
data,
|
|
});
|
|
|
|
unsafe {
|
|
*result = Box::into_raw(work) as _;
|
|
}
|
|
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
#[napi_sym]
|
|
pub(crate) fn napi_delete_async_work(
|
|
env: *mut Env,
|
|
work: napi_async_work,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
check_arg!(env, work);
|
|
|
|
drop(unsafe { Box::<AsyncWork>::from_raw(work as _) });
|
|
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_get_uv_event_loop(
|
|
env_ptr: *mut Env,
|
|
uv_loop: *mut *mut (),
|
|
) -> napi_status {
|
|
let env = check_env!(env_ptr);
|
|
check_arg!(env, uv_loop);
|
|
unsafe {
|
|
*uv_loop = env_ptr.cast();
|
|
}
|
|
0
|
|
}
|
|
|
|
#[napi_sym]
|
|
pub(crate) fn napi_queue_async_work(
|
|
env: *mut Env,
|
|
work: napi_async_work,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
check_arg!(env, work);
|
|
|
|
let work = unsafe { &*(work as *mut AsyncWork) };
|
|
|
|
let result =
|
|
work
|
|
.state
|
|
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
|
|
// allow queue if idle or if running, but not if already queued.
|
|
if state == AsyncWork::IDLE || state == AsyncWork::RUNNING {
|
|
Some(AsyncWork::QUEUED)
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
|
|
if result.is_err() {
|
|
return napi_clear_last_error(env);
|
|
}
|
|
|
|
let work = SendPtr(work);
|
|
|
|
env.add_async_work(move || {
|
|
let work = work.take();
|
|
let work = unsafe { &*work };
|
|
|
|
let state = work.state.compare_exchange(
|
|
AsyncWork::QUEUED,
|
|
AsyncWork::RUNNING,
|
|
Ordering::SeqCst,
|
|
Ordering::SeqCst,
|
|
);
|
|
|
|
if state.is_ok() {
|
|
unsafe {
|
|
(work.execute)(work.env as _, work.data);
|
|
}
|
|
|
|
// reset back to idle if its still marked as running
|
|
let _ = work.state.compare_exchange(
|
|
AsyncWork::RUNNING,
|
|
AsyncWork::IDLE,
|
|
Ordering::SeqCst,
|
|
Ordering::Relaxed,
|
|
);
|
|
}
|
|
|
|
if let Some(complete) = work.complete {
|
|
let status = if state.is_ok() {
|
|
napi_ok
|
|
} else if state == Err(AsyncWork::IDLE) {
|
|
napi_cancelled
|
|
} else {
|
|
napi_generic_failure
|
|
};
|
|
|
|
unsafe {
|
|
complete(work.env as _, status, work.data);
|
|
}
|
|
}
|
|
|
|
// `complete` probably deletes this `work`, so don't use it here.
|
|
});
|
|
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_cancel_async_work(env: *mut Env, work: napi_async_work) -> napi_status {
|
|
let env = check_env!(env);
|
|
check_arg!(env, work);
|
|
|
|
let work = unsafe { &*(work as *mut AsyncWork) };
|
|
|
|
let _ = work.state.compare_exchange(
|
|
AsyncWork::QUEUED,
|
|
AsyncWork::IDLE,
|
|
Ordering::SeqCst,
|
|
Ordering::Relaxed,
|
|
);
|
|
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
extern "C" fn default_call_js_cb(
|
|
env: napi_env,
|
|
js_callback: napi_value,
|
|
_context: *mut c_void,
|
|
_data: *mut c_void,
|
|
) {
|
|
if let Some(js_callback) = *js_callback {
|
|
if let Ok(js_callback) = v8::Local::<v8::Function>::try_from(js_callback) {
|
|
let env = unsafe { &mut *(env as *mut Env) };
|
|
let scope = &mut env.scope();
|
|
let recv = v8::undefined(scope);
|
|
js_callback.call(scope, recv.into(), &[]);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct TsFn {
|
|
env: *mut Env,
|
|
func: Option<v8::Global<v8::Function>>,
|
|
max_queue_size: usize,
|
|
queue_size: Mutex<usize>,
|
|
queue_cond: Condvar,
|
|
thread_count: AtomicUsize,
|
|
thread_finalize_data: *mut c_void,
|
|
thread_finalize_cb: Option<napi_finalize>,
|
|
context: *mut c_void,
|
|
call_js_cb: napi_threadsafe_function_call_js,
|
|
_resource: v8::Global<v8::Object>,
|
|
_resource_name: String,
|
|
is_closing: AtomicBool,
|
|
is_closed: Arc<AtomicBool>,
|
|
sender: V8CrossThreadTaskSpawner,
|
|
is_ref: AtomicBool,
|
|
}
|
|
|
|
impl Drop for TsFn {
|
|
fn drop(&mut self) {
|
|
assert!(self
|
|
.is_closed
|
|
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
|
|
.is_ok());
|
|
|
|
self.unref();
|
|
|
|
if let Some(finalizer) = self.thread_finalize_cb {
|
|
unsafe {
|
|
(finalizer)(self.env as _, self.thread_finalize_data, self.context);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TsFn {
|
|
pub fn acquire(&self) -> napi_status {
|
|
if self.is_closing.load(Ordering::SeqCst) {
|
|
return napi_closing;
|
|
}
|
|
self.thread_count.fetch_add(1, Ordering::Relaxed);
|
|
napi_ok
|
|
}
|
|
|
|
pub fn release(
|
|
tsfn: *mut TsFn,
|
|
mode: napi_threadsafe_function_release_mode,
|
|
) -> napi_status {
|
|
let tsfn = unsafe { &mut *tsfn };
|
|
|
|
let result = tsfn.thread_count.fetch_update(
|
|
Ordering::Relaxed,
|
|
Ordering::Relaxed,
|
|
|x| {
|
|
if x == 0 {
|
|
None
|
|
} else {
|
|
Some(x - 1)
|
|
}
|
|
},
|
|
);
|
|
|
|
if result.is_err() {
|
|
return napi_invalid_arg;
|
|
}
|
|
|
|
if (result == Ok(1) || mode == napi_tsfn_abort)
|
|
&& tsfn
|
|
.is_closing
|
|
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
|
|
.is_ok()
|
|
{
|
|
tsfn.queue_cond.notify_all();
|
|
let tsfnptr = SendPtr(tsfn);
|
|
// drop must be queued in order to preserve ordering consistent
|
|
// with Node.js and so that the finalizer runs on the main thread.
|
|
tsfn.sender.spawn(move |_| {
|
|
let tsfn = unsafe { Box::from_raw(tsfnptr.take() as *mut TsFn) };
|
|
drop(tsfn);
|
|
});
|
|
}
|
|
|
|
napi_ok
|
|
}
|
|
|
|
pub fn ref_(&self) -> napi_status {
|
|
if self
|
|
.is_ref
|
|
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
|
|
.is_ok()
|
|
{
|
|
let env = unsafe { &mut *self.env };
|
|
env.threadsafe_function_ref();
|
|
}
|
|
napi_ok
|
|
}
|
|
|
|
pub fn unref(&self) -> napi_status {
|
|
if self
|
|
.is_ref
|
|
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
|
|
.is_ok()
|
|
{
|
|
let env = unsafe { &mut *self.env };
|
|
env.threadsafe_function_unref();
|
|
}
|
|
|
|
napi_ok
|
|
}
|
|
|
|
pub fn call(
|
|
&self,
|
|
data: *mut c_void,
|
|
mode: napi_threadsafe_function_call_mode,
|
|
) -> napi_status {
|
|
if self.is_closing.load(Ordering::SeqCst) {
|
|
return napi_closing;
|
|
}
|
|
|
|
if self.max_queue_size > 0 {
|
|
let mut queue_size = self.queue_size.lock();
|
|
while *queue_size >= self.max_queue_size {
|
|
if mode == napi_tsfn_blocking {
|
|
self.queue_cond.wait(&mut queue_size);
|
|
|
|
if self.is_closing.load(Ordering::SeqCst) {
|
|
return napi_closing;
|
|
}
|
|
} else {
|
|
return napi_queue_full;
|
|
}
|
|
}
|
|
*queue_size += 1;
|
|
}
|
|
|
|
let is_closed = self.is_closed.clone();
|
|
let tsfn = SendPtr(self);
|
|
let data = SendPtr(data);
|
|
let context = SendPtr(self.context);
|
|
let call_js_cb = self.call_js_cb;
|
|
|
|
self.sender.spawn(move |scope: &mut v8::HandleScope| {
|
|
let data = data.take();
|
|
|
|
// if is_closed then tsfn is freed, don't read from it.
|
|
if is_closed.load(Ordering::Relaxed) {
|
|
unsafe {
|
|
call_js_cb(
|
|
std::ptr::null_mut(),
|
|
None::<v8::Local<v8::Value>>.into(),
|
|
context.take() as _,
|
|
data as _,
|
|
);
|
|
}
|
|
} else {
|
|
let tsfn = tsfn.take();
|
|
|
|
let tsfn = unsafe { &*tsfn };
|
|
|
|
if tsfn.max_queue_size > 0 {
|
|
let mut queue_size = tsfn.queue_size.lock();
|
|
let size = *queue_size;
|
|
*queue_size -= 1;
|
|
if size == tsfn.max_queue_size {
|
|
tsfn.queue_cond.notify_one();
|
|
}
|
|
}
|
|
|
|
let func = tsfn.func.as_ref().map(|f| v8::Local::new(scope, f));
|
|
|
|
unsafe {
|
|
(tsfn.call_js_cb)(
|
|
tsfn.env as _,
|
|
func.into(),
|
|
tsfn.context,
|
|
data as _,
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
napi_ok
|
|
}
|
|
}
|
|
|
|
#[napi_sym]
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn napi_create_threadsafe_function(
|
|
env: *mut Env,
|
|
func: napi_value,
|
|
async_resource: napi_value,
|
|
async_resource_name: napi_value,
|
|
max_queue_size: usize,
|
|
initial_thread_count: usize,
|
|
thread_finalize_data: *mut c_void,
|
|
thread_finalize_cb: Option<napi_finalize>,
|
|
context: *mut c_void,
|
|
call_js_cb: Option<napi_threadsafe_function_call_js>,
|
|
result: *mut napi_threadsafe_function,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
check_arg!(env, async_resource_name);
|
|
if initial_thread_count == 0 {
|
|
return napi_set_last_error(env, napi_invalid_arg);
|
|
}
|
|
check_arg!(env, result);
|
|
|
|
let func = if let Some(value) = *func {
|
|
let Ok(func) = v8::Local::<v8::Function>::try_from(value) else {
|
|
return napi_set_last_error(env, napi_function_expected);
|
|
};
|
|
Some(v8::Global::new(&mut env.scope(), func))
|
|
} else {
|
|
check_arg!(env, call_js_cb);
|
|
None
|
|
};
|
|
|
|
let resource = if let Some(v) = *async_resource {
|
|
let Some(resource) = v.to_object(&mut env.scope()) else {
|
|
return napi_set_last_error(env, napi_object_expected);
|
|
};
|
|
resource
|
|
} else {
|
|
v8::Object::new(&mut env.scope())
|
|
};
|
|
let resource = v8::Global::new(&mut env.scope(), resource);
|
|
|
|
let Some(resource_name) =
|
|
async_resource_name.and_then(|v| v.to_string(&mut env.scope()))
|
|
else {
|
|
return napi_set_last_error(env, napi_string_expected);
|
|
};
|
|
let resource_name = resource_name.to_rust_string_lossy(&mut env.scope());
|
|
|
|
let tsfn = Box::new(TsFn {
|
|
env,
|
|
func,
|
|
max_queue_size,
|
|
queue_size: Mutex::new(0),
|
|
queue_cond: Condvar::new(),
|
|
thread_count: AtomicUsize::new(initial_thread_count),
|
|
thread_finalize_data,
|
|
thread_finalize_cb,
|
|
context,
|
|
call_js_cb: call_js_cb.unwrap_or(default_call_js_cb),
|
|
_resource: resource,
|
|
_resource_name: resource_name,
|
|
is_closing: AtomicBool::new(false),
|
|
is_closed: Arc::new(AtomicBool::new(false)),
|
|
is_ref: AtomicBool::new(false),
|
|
sender: env.async_work_sender.clone(),
|
|
});
|
|
|
|
tsfn.ref_();
|
|
|
|
unsafe {
|
|
*result = Box::into_raw(tsfn) as _;
|
|
}
|
|
|
|
napi_clear_last_error(env)
|
|
}
|
|
|
|
/// Maybe called from any thread.
|
|
#[napi_sym]
|
|
fn napi_get_threadsafe_function_context(
|
|
func: napi_threadsafe_function,
|
|
result: *mut *const c_void,
|
|
) -> napi_status {
|
|
assert!(!func.is_null());
|
|
let tsfn = unsafe { &*(func as *const TsFn) };
|
|
unsafe {
|
|
*result = tsfn.context;
|
|
}
|
|
napi_ok
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_call_threadsafe_function(
|
|
func: napi_threadsafe_function,
|
|
data: *mut c_void,
|
|
is_blocking: napi_threadsafe_function_call_mode,
|
|
) -> napi_status {
|
|
assert!(!func.is_null());
|
|
let tsfn = unsafe { &*(func as *mut TsFn) };
|
|
tsfn.call(data, is_blocking)
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_acquire_threadsafe_function(
|
|
tsfn: napi_threadsafe_function,
|
|
) -> napi_status {
|
|
assert!(!tsfn.is_null());
|
|
let tsfn = unsafe { &*(tsfn as *mut TsFn) };
|
|
tsfn.acquire()
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_release_threadsafe_function(
|
|
tsfn: napi_threadsafe_function,
|
|
mode: napi_threadsafe_function_release_mode,
|
|
) -> napi_status {
|
|
assert!(!tsfn.is_null());
|
|
TsFn::release(tsfn as _, mode)
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_unref_threadsafe_function(
|
|
_env: &mut Env,
|
|
func: napi_threadsafe_function,
|
|
) -> napi_status {
|
|
assert!(!func.is_null());
|
|
let tsfn = unsafe { &*(func as *mut TsFn) };
|
|
tsfn.unref()
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn napi_ref_threadsafe_function(
|
|
_env: &mut Env,
|
|
func: napi_threadsafe_function,
|
|
) -> napi_status {
|
|
assert!(!func.is_null());
|
|
let tsfn = unsafe { &*(func as *mut TsFn) };
|
|
tsfn.ref_()
|
|
}
|
|
|
|
#[napi_sym]
|
|
fn node_api_get_module_file_name(
|
|
env: *mut Env,
|
|
result: *mut *const c_char,
|
|
) -> napi_status {
|
|
let env = check_env!(env);
|
|
check_arg!(env, result);
|
|
|
|
unsafe {
|
|
*result = env.shared().filename.as_ptr() as _;
|
|
}
|
|
|
|
napi_clear_last_error(env)
|
|
}
|