summaryrefslogtreecommitdiff
path: root/tests/napi
diff options
context:
space:
mode:
authorNathan Whitaker <17734409+nathanwhit@users.noreply.github.com>2024-10-02 10:43:42 -0700
committerGitHub <noreply@github.com>2024-10-02 10:43:42 -0700
commitbbd4ae1bc12dc6b34d4a455015096b7113a5cec5 (patch)
tree1d91babfa3fdfe47d0a3b01809081150ede27628 /tests/napi
parent1837aed79b77b3137563d4730d02e466c85b2b87 (diff)
fix(node): implement libuv APIs needed to support `npm:sqlite3` (#25893)
Fixes #24740. Implements the `uv_mutex_*` and `uv_async_*` APIs. The mutex API is implemented exactly as libuv, a thin wrapper over the OS's native mutex. The async API is implemented in terms of napi_async_work. As documented in the napi docs, you really shouldn't call `napi_queue_async_work` multiple times (it is documented as undefined behavior). However, our implementation doesn't have any issue with this, so I believe it suits our purpose here.
Diffstat (limited to 'tests/napi')
-rw-r--r--tests/napi/Cargo.toml1
-rw-r--r--tests/napi/src/lib.rs3
-rw-r--r--tests/napi/src/uv.rs206
-rw-r--r--tests/napi/uv_test.js18
4 files changed, 228 insertions, 0 deletions
diff --git a/tests/napi/Cargo.toml b/tests/napi/Cargo.toml
index 611d6d550..e3de25368 100644
--- a/tests/napi/Cargo.toml
+++ b/tests/napi/Cargo.toml
@@ -13,6 +13,7 @@ repository.workspace = true
crate-type = ["cdylib"]
[dependencies]
+libuv-sys-lite = "=1.48.2"
napi-sys = { version = "=2.2.2", default-features = false, features = ["napi7"] }
[dev-dependencies]
diff --git a/tests/napi/src/lib.rs b/tests/napi/src/lib.rs
index f6fe6e189..8c6190ad3 100644
--- a/tests/napi/src/lib.rs
+++ b/tests/napi/src/lib.rs
@@ -31,6 +31,7 @@ pub mod strings;
pub mod symbol;
pub mod tsfn;
pub mod typedarray;
+pub mod uv;
#[macro_export]
macro_rules! cstr {
@@ -138,6 +139,7 @@ unsafe extern "C" fn napi_register_module_v1(
#[cfg(windows)]
{
napi_sys::setup();
+ libuv_sys_lite::setup();
}
// We create a fresh exports object and leave the passed
@@ -169,6 +171,7 @@ unsafe extern "C" fn napi_register_module_v1(
symbol::init(env, exports);
make_callback::init(env, exports);
object::init(env, exports);
+ uv::init(env, exports);
init_cleanup_hook(env, exports);
diff --git a/tests/napi/src/uv.rs b/tests/napi/src/uv.rs
new file mode 100644
index 000000000..555470c00
--- /dev/null
+++ b/tests/napi/src/uv.rs
@@ -0,0 +1,206 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::assert_napi_ok;
+use crate::napi_get_callback_info;
+use crate::napi_new_property;
+use libuv_sys_lite::uv_async_init;
+use libuv_sys_lite::uv_async_t;
+use libuv_sys_lite::uv_close;
+use libuv_sys_lite::uv_handle_t;
+use libuv_sys_lite::uv_mutex_destroy;
+use libuv_sys_lite::uv_mutex_lock;
+use libuv_sys_lite::uv_mutex_t;
+use libuv_sys_lite::uv_mutex_unlock;
+use napi_sys::*;
+use std::mem::MaybeUninit;
+use std::ptr;
+use std::ptr::addr_of_mut;
+use std::ptr::null_mut;
+use std::time::Duration;
+
+struct KeepAlive {
+ tsfn: napi_threadsafe_function,
+}
+
+impl KeepAlive {
+ fn new(env: napi_env) -> Self {
+ let mut name = null_mut();
+ assert_napi_ok!(napi_create_string_utf8(
+ env,
+ c"test_uv_async".as_ptr(),
+ 13,
+ &mut name
+ ));
+
+ unsafe extern "C" fn dummy(
+ _env: napi_env,
+ _cb: napi_callback_info,
+ ) -> napi_value {
+ ptr::null_mut()
+ }
+
+ let mut func = null_mut();
+ assert_napi_ok!(napi_create_function(
+ env,
+ c"dummy".as_ptr(),
+ usize::MAX,
+ Some(dummy),
+ null_mut(),
+ &mut func,
+ ));
+
+ let mut tsfn = null_mut();
+ assert_napi_ok!(napi_create_threadsafe_function(
+ env,
+ func,
+ null_mut(),
+ name,
+ 0,
+ 1,
+ null_mut(),
+ None,
+ null_mut(),
+ None,
+ &mut tsfn,
+ ));
+ assert_napi_ok!(napi_ref_threadsafe_function(env, tsfn));
+ Self { tsfn }
+ }
+}
+
+impl Drop for KeepAlive {
+ fn drop(&mut self) {
+ assert_napi_ok!(napi_release_threadsafe_function(
+ self.tsfn,
+ ThreadsafeFunctionReleaseMode::release,
+ ));
+ }
+}
+
+struct Async {
+ mutex: *mut uv_mutex_t,
+ env: napi_env,
+ value: u32,
+ callback: napi_ref,
+ _keep_alive: KeepAlive,
+}
+
+#[derive(Clone, Copy)]
+struct UvAsyncPtr(*mut uv_async_t);
+
+unsafe impl Send for UvAsyncPtr {}
+
+fn new_raw<T>(t: T) -> *mut T {
+ Box::into_raw(Box::new(t))
+}
+
+unsafe extern "C" fn close_cb(handle: *mut uv_handle_t) {
+ let handle = handle.cast::<uv_async_t>();
+ let async_ = (*handle).data as *mut Async;
+ let env = (*async_).env;
+ assert_napi_ok!(napi_delete_reference(env, (*async_).callback));
+
+ uv_mutex_destroy((*async_).mutex);
+ let _ = Box::from_raw((*async_).mutex);
+ let _ = Box::from_raw(async_);
+ let _ = Box::from_raw(handle);
+}
+
+unsafe extern "C" fn callback(handle: *mut uv_async_t) {
+ eprintln!("callback");
+ let async_ = (*handle).data as *mut Async;
+ uv_mutex_lock((*async_).mutex);
+ let env = (*async_).env;
+ let mut js_cb = null_mut();
+ assert_napi_ok!(napi_get_reference_value(
+ env,
+ (*async_).callback,
+ &mut js_cb
+ ));
+ let mut global: napi_value = ptr::null_mut();
+ assert_napi_ok!(napi_get_global(env, &mut global));
+
+ let mut result: napi_value = ptr::null_mut();
+ let value = (*async_).value;
+ eprintln!("value is {value}");
+ let mut value_js = ptr::null_mut();
+ assert_napi_ok!(napi_create_uint32(env, value, &mut value_js));
+ let args = &[value_js];
+ assert_napi_ok!(napi_call_function(
+ env,
+ global,
+ js_cb,
+ 1,
+ args.as_ptr(),
+ &mut result,
+ ));
+ uv_mutex_unlock((*async_).mutex);
+ if value == 5 {
+ uv_close(handle.cast(), Some(close_cb));
+ }
+}
+
+unsafe fn uv_async_send(ptr: UvAsyncPtr) {
+ assert_napi_ok!(libuv_sys_lite::uv_async_send(ptr.0));
+}
+
+fn make_uv_mutex() -> *mut uv_mutex_t {
+ let mutex = new_raw(MaybeUninit::<uv_mutex_t>::uninit());
+ assert_napi_ok!(libuv_sys_lite::uv_mutex_init(mutex.cast()));
+ mutex.cast()
+}
+
+#[allow(unused_unsafe)]
+extern "C" fn test_uv_async(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = napi_get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut loop_ = null_mut();
+ assert_napi_ok!(napi_get_uv_event_loop(env, &mut loop_));
+ let uv_async = new_raw(MaybeUninit::<uv_async_t>::uninit());
+ let uv_async = uv_async.cast::<uv_async_t>();
+ let mut js_cb = null_mut();
+ assert_napi_ok!(napi_create_reference(env, args[0], 1, &mut js_cb));
+ // let mut tsfn = null_mut();
+
+ let data = new_raw(Async {
+ env,
+ callback: js_cb,
+ mutex: make_uv_mutex(),
+ value: 0,
+ _keep_alive: KeepAlive::new(env),
+ });
+ unsafe {
+ addr_of_mut!((*uv_async).data).write(data.cast());
+ assert_napi_ok!(uv_async_init(loop_.cast(), uv_async, Some(callback)));
+ let uv_async = UvAsyncPtr(uv_async);
+ std::thread::spawn({
+ move || {
+ let data = (*uv_async.0).data as *mut Async;
+ for _ in 0..5 {
+ uv_mutex_lock((*data).mutex);
+ (*data).value += 1;
+ uv_mutex_unlock((*data).mutex);
+ std::thread::sleep(Duration::from_millis(10));
+ uv_async_send(uv_async);
+ }
+ }
+ });
+ }
+
+ ptr::null_mut()
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[napi_new_property!(env, "test_uv_async", test_uv_async)];
+
+ assert_napi_ok!(napi_define_properties(
+ env,
+ exports,
+ properties.len(),
+ properties.as_ptr()
+ ));
+}
diff --git a/tests/napi/uv_test.js b/tests/napi/uv_test.js
new file mode 100644
index 000000000..6d8ee2671
--- /dev/null
+++ b/tests/napi/uv_test.js
@@ -0,0 +1,18 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { assertEquals, loadTestLibrary } from "./common.js";
+
+const uv = loadTestLibrary();
+
+Deno.test("napi uv async", async () => {
+ let called = false;
+ await new Promise((resolve) => {
+ uv.test_uv_async((value) => {
+ called = true;
+ if (value === 5) {
+ resolve();
+ }
+ });
+ });
+ assertEquals(called, true);
+});