diff options
author | Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> | 2024-10-02 10:43:42 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-10-02 10:43:42 -0700 |
commit | bbd4ae1bc12dc6b34d4a455015096b7113a5cec5 (patch) | |
tree | 1d91babfa3fdfe47d0a3b01809081150ede27628 /tests/napi | |
parent | 1837aed79b77b3137563d4730d02e466c85b2b87 (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.toml | 1 | ||||
-rw-r--r-- | tests/napi/src/lib.rs | 3 | ||||
-rw-r--r-- | tests/napi/src/uv.rs | 206 | ||||
-rw-r--r-- | tests/napi/uv_test.js | 18 |
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); +}); |