summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock47
-rw-r--r--Cargo.toml2
-rw-r--r--cli/Cargo.toml2
-rw-r--r--cli/build.rs2
-rw-r--r--cli/napi/generated_symbol_exports_list_linux.def2
-rw-r--r--cli/napi/generated_symbol_exports_list_macos.def9
-rw-r--r--cli/napi/generated_symbol_exports_list_windows.def9
-rw-r--r--cli/napi/mod.rs1
-rw-r--r--cli/napi/node_api.rs13
-rw-r--r--cli/napi/sym/symbol_exports.json9
-rw-r--r--cli/napi/uv.rs231
-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
15 files changed, 539 insertions, 16 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9bb75aac8..f264f41e3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -472,6 +472,26 @@ dependencies = [
]
[[package]]
+name = "bindgen"
+version = "0.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
+dependencies = [
+ "bitflags 2.6.0",
+ "cexpr",
+ "clang-sys",
+ "itertools 0.13.0",
+ "log",
+ "prettyplease 0.2.17",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash 1.1.0",
+ "shlex",
+ "syn 2.0.72",
+]
+
+[[package]]
name = "bit-set"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -688,7 +708,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
- "libloading 0.8.3",
+ "libloading 0.8.5",
]
[[package]]
@@ -1064,7 +1084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b28bfe653d79bd16c77f659305b195b82bb5ce0c0eb2a4846b82ddbd77586813"
dependencies = [
"bitflags 2.6.0",
- "libloading 0.8.3",
+ "libloading 0.8.5",
"winapi",
]
@@ -1205,6 +1225,7 @@ dependencies = [
"lazy-regex",
"libc",
"libsui",
+ "libuv-sys-lite",
"libz-sys",
"log",
"lsp-types",
@@ -1253,6 +1274,7 @@ dependencies = [
"walkdir",
"which 4.4.2",
"winapi",
+ "windows-sys 0.52.0",
"winres",
"yoke",
"zeromq",
@@ -4039,7 +4061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
dependencies = [
"libc",
- "libloading 0.8.3",
+ "libloading 0.8.5",
"pkg-config",
]
@@ -4144,9 +4166,9 @@ dependencies = [
[[package]]
name = "libloading"
-version = "0.8.3"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
+checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
"windows-targets 0.52.4",
@@ -4193,6 +4215,16 @@ dependencies = [
]
[[package]]
+name = "libuv-sys-lite"
+version = "1.48.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca8dfd1a173826d193e3b955e07c22765829890f62c677a59c4a410cb4f47c01"
+dependencies = [
+ "bindgen 0.70.1",
+ "libloading 0.8.5",
+]
+
+[[package]]
name = "libz-sys"
version = "1.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7205,6 +7237,7 @@ dependencies = [
name = "test_napi"
version = "0.1.0"
dependencies = [
+ "libuv-sys-lite",
"napi-build",
"napi-sys",
"test_server",
@@ -7891,7 +7924,7 @@ version = "0.106.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a381badc47c6f15acb5fe0b5b40234162349ed9d4e4fd7c83a7f5547c0fc69c5"
dependencies = [
- "bindgen",
+ "bindgen 0.69.4",
"bitflags 2.6.0",
"fslock",
"gzip-header",
@@ -8158,7 +8191,7 @@ dependencies = [
"js-sys",
"khronos-egl",
"libc",
- "libloading 0.8.3",
+ "libloading 0.8.5",
"log",
"metal",
"naga",
diff --git a/Cargo.toml b/Cargo.toml
index f60522907..22f3575b9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -225,7 +225,7 @@ nix = "=0.26.2"
# windows deps
junction = "=0.2.0"
winapi = "=0.3.9"
-windows-sys = { version = "0.52.0", features = ["Win32_Foundation", "Win32_Media", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_WindowsProgramming", "Wdk", "Wdk_System", "Wdk_System_SystemInformation", "Win32_System_Pipes", "Wdk_Storage_FileSystem", "Win32_System_Registry"] }
+windows-sys = { version = "0.52.0", features = ["Win32_Foundation", "Win32_Media", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_WindowsProgramming", "Wdk", "Wdk_System", "Wdk_System_SystemInformation", "Win32_System_Pipes", "Wdk_Storage_FileSystem", "Win32_System_Registry", "Win32_System_Kernel"] }
winres = "=0.1.12"
# NB: the `bench` and `release` profiles must remain EXACTLY the same.
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 41636c961..6167baa0b 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -170,12 +170,14 @@ zstd.workspace = true
[target.'cfg(windows)'.dependencies]
junction.workspace = true
winapi = { workspace = true, features = ["knownfolders", "mswsock", "objbase", "shlobj", "tlhelp32", "winbase", "winerror", "winsock2"] }
+windows-sys.workspace = true
[target.'cfg(unix)'.dependencies]
nix.workspace = true
[dev-dependencies]
deno_bench_util.workspace = true
+libuv-sys-lite = "=1.48.2"
pretty_assertions.workspace = true
test_util.workspace = true
diff --git a/cli/build.rs b/cli/build.rs
index c3b6f8b04..aa5d3d18c 100644
--- a/cli/build.rs
+++ b/cli/build.rs
@@ -387,6 +387,8 @@ fn main() {
"Missing symbols list! Generate using tools/napi/generate_symbols_lists.js",
);
+ println!("cargo:rustc-rerun-if-changed={}", symbols_path.display());
+
#[cfg(target_os = "windows")]
println!(
"cargo:rustc-link-arg-bin=deno=/DEF:{}",
diff --git a/cli/napi/generated_symbol_exports_list_linux.def b/cli/napi/generated_symbol_exports_list_linux.def
index 06e94f05b..614880ebf 100644
--- a/cli/napi/generated_symbol_exports_list_linux.def
+++ b/cli/napi/generated_symbol_exports_list_linux.def
@@ -1 +1 @@
-{ "node_api_create_syntax_error"; "napi_make_callback"; "napi_has_named_property"; "napi_async_destroy"; "napi_coerce_to_object"; "napi_get_arraybuffer_info"; "napi_detach_arraybuffer"; "napi_get_undefined"; "napi_reference_unref"; "napi_fatal_error"; "napi_open_callback_scope"; "napi_close_callback_scope"; "napi_get_value_uint32"; "napi_create_function"; "napi_create_arraybuffer"; "napi_get_value_int64"; "napi_get_all_property_names"; "napi_resolve_deferred"; "napi_is_detached_arraybuffer"; "napi_create_string_utf8"; "napi_create_threadsafe_function"; "node_api_throw_syntax_error"; "napi_create_bigint_int64"; "napi_wrap"; "napi_set_property"; "napi_get_value_bigint_int64"; "napi_open_handle_scope"; "napi_create_error"; "napi_create_buffer"; "napi_cancel_async_work"; "napi_is_exception_pending"; "napi_acquire_threadsafe_function"; "napi_create_external"; "napi_get_threadsafe_function_context"; "napi_get_null"; "napi_create_string_utf16"; "node_api_create_external_string_utf16"; "napi_get_value_bigint_uint64"; "napi_module_register"; "napi_is_typedarray"; "napi_create_external_buffer"; "napi_get_new_target"; "napi_get_instance_data"; "napi_close_handle_scope"; "napi_get_value_string_utf16"; "napi_get_property_names"; "napi_is_arraybuffer"; "napi_get_cb_info"; "napi_define_properties"; "napi_add_env_cleanup_hook"; "node_api_get_module_file_name"; "napi_get_node_version"; "napi_create_int64"; "napi_create_double"; "napi_get_and_clear_last_exception"; "napi_create_reference"; "napi_get_typedarray_info"; "napi_call_threadsafe_function"; "napi_get_last_error_info"; "napi_create_array_with_length"; "napi_coerce_to_number"; "napi_get_global"; "napi_is_error"; "napi_set_instance_data"; "napi_create_typedarray"; "napi_throw_type_error"; "napi_has_property"; "napi_get_value_external"; "napi_create_range_error"; "napi_typeof"; "napi_ref_threadsafe_function"; "napi_create_bigint_uint64"; "napi_get_prototype"; "napi_adjust_external_memory"; "napi_release_threadsafe_function"; "napi_delete_async_work"; "napi_create_string_latin1"; "node_api_create_external_string_latin1"; "napi_is_array"; "napi_unref_threadsafe_function"; "napi_throw_error"; "napi_has_own_property"; "napi_get_reference_value"; "napi_remove_env_cleanup_hook"; "napi_get_value_string_utf8"; "napi_is_promise"; "napi_get_boolean"; "napi_run_script"; "napi_get_element"; "napi_get_named_property"; "napi_get_buffer_info"; "napi_get_value_bool"; "napi_reference_ref"; "napi_create_object"; "napi_create_promise"; "napi_create_int32"; "napi_escape_handle"; "napi_open_escapable_handle_scope"; "napi_throw"; "napi_get_value_double"; "napi_set_named_property"; "napi_call_function"; "napi_create_date"; "napi_object_freeze"; "napi_get_uv_event_loop"; "napi_get_value_string_latin1"; "napi_reject_deferred"; "napi_add_finalizer"; "napi_create_array"; "napi_delete_reference"; "napi_get_date_value"; "napi_create_dataview"; "napi_get_version"; "napi_define_class"; "napi_is_date"; "napi_remove_wrap"; "napi_delete_property"; "napi_instanceof"; "napi_create_buffer_copy"; "napi_delete_element"; "napi_object_seal"; "napi_queue_async_work"; "napi_get_value_bigint_words"; "napi_is_buffer"; "napi_get_array_length"; "napi_get_property"; "napi_new_instance"; "napi_set_element"; "napi_create_bigint_words"; "napi_strict_equals"; "napi_is_dataview"; "napi_close_escapable_handle_scope"; "napi_get_dataview_info"; "napi_get_value_int32"; "napi_unwrap"; "napi_throw_range_error"; "napi_coerce_to_bool"; "napi_create_uint32"; "napi_has_element"; "napi_create_external_arraybuffer"; "napi_create_symbol"; "node_api_symbol_for"; "napi_coerce_to_string"; "napi_create_type_error"; "napi_fatal_exception"; "napi_create_async_work"; "napi_async_init"; "node_api_create_property_key_utf16"; "napi_type_tag_object"; "napi_check_object_type_tag"; "node_api_post_finalizer"; "napi_add_async_cleanup_hook"; "napi_remove_async_cleanup_hook"; }; \ No newline at end of file
+{ "node_api_create_syntax_error"; "napi_make_callback"; "napi_has_named_property"; "napi_async_destroy"; "napi_coerce_to_object"; "napi_get_arraybuffer_info"; "napi_detach_arraybuffer"; "napi_get_undefined"; "napi_reference_unref"; "napi_fatal_error"; "napi_open_callback_scope"; "napi_close_callback_scope"; "napi_get_value_uint32"; "napi_create_function"; "napi_create_arraybuffer"; "napi_get_value_int64"; "napi_get_all_property_names"; "napi_resolve_deferred"; "napi_is_detached_arraybuffer"; "napi_create_string_utf8"; "napi_create_threadsafe_function"; "node_api_throw_syntax_error"; "napi_create_bigint_int64"; "napi_wrap"; "napi_set_property"; "napi_get_value_bigint_int64"; "napi_open_handle_scope"; "napi_create_error"; "napi_create_buffer"; "napi_cancel_async_work"; "napi_is_exception_pending"; "napi_acquire_threadsafe_function"; "napi_create_external"; "napi_get_threadsafe_function_context"; "napi_get_null"; "napi_create_string_utf16"; "node_api_create_external_string_utf16"; "napi_get_value_bigint_uint64"; "napi_module_register"; "napi_is_typedarray"; "napi_create_external_buffer"; "napi_get_new_target"; "napi_get_instance_data"; "napi_close_handle_scope"; "napi_get_value_string_utf16"; "napi_get_property_names"; "napi_is_arraybuffer"; "napi_get_cb_info"; "napi_define_properties"; "napi_add_env_cleanup_hook"; "node_api_get_module_file_name"; "napi_get_node_version"; "napi_create_int64"; "napi_create_double"; "napi_get_and_clear_last_exception"; "napi_create_reference"; "napi_get_typedarray_info"; "napi_call_threadsafe_function"; "napi_get_last_error_info"; "napi_create_array_with_length"; "napi_coerce_to_number"; "napi_get_global"; "napi_is_error"; "napi_set_instance_data"; "napi_create_typedarray"; "napi_throw_type_error"; "napi_has_property"; "napi_get_value_external"; "napi_create_range_error"; "napi_typeof"; "napi_ref_threadsafe_function"; "napi_create_bigint_uint64"; "napi_get_prototype"; "napi_adjust_external_memory"; "napi_release_threadsafe_function"; "napi_delete_async_work"; "napi_create_string_latin1"; "node_api_create_external_string_latin1"; "napi_is_array"; "napi_unref_threadsafe_function"; "napi_throw_error"; "napi_has_own_property"; "napi_get_reference_value"; "napi_remove_env_cleanup_hook"; "napi_get_value_string_utf8"; "napi_is_promise"; "napi_get_boolean"; "napi_run_script"; "napi_get_element"; "napi_get_named_property"; "napi_get_buffer_info"; "napi_get_value_bool"; "napi_reference_ref"; "napi_create_object"; "napi_create_promise"; "napi_create_int32"; "napi_escape_handle"; "napi_open_escapable_handle_scope"; "napi_throw"; "napi_get_value_double"; "napi_set_named_property"; "napi_call_function"; "napi_create_date"; "napi_object_freeze"; "napi_get_uv_event_loop"; "napi_get_value_string_latin1"; "napi_reject_deferred"; "napi_add_finalizer"; "napi_create_array"; "napi_delete_reference"; "napi_get_date_value"; "napi_create_dataview"; "napi_get_version"; "napi_define_class"; "napi_is_date"; "napi_remove_wrap"; "napi_delete_property"; "napi_instanceof"; "napi_create_buffer_copy"; "napi_delete_element"; "napi_object_seal"; "napi_queue_async_work"; "napi_get_value_bigint_words"; "napi_is_buffer"; "napi_get_array_length"; "napi_get_property"; "napi_new_instance"; "napi_set_element"; "napi_create_bigint_words"; "napi_strict_equals"; "napi_is_dataview"; "napi_close_escapable_handle_scope"; "napi_get_dataview_info"; "napi_get_value_int32"; "napi_unwrap"; "napi_throw_range_error"; "napi_coerce_to_bool"; "napi_create_uint32"; "napi_has_element"; "napi_create_external_arraybuffer"; "napi_create_symbol"; "node_api_symbol_for"; "napi_coerce_to_string"; "napi_create_type_error"; "napi_fatal_exception"; "napi_create_async_work"; "napi_async_init"; "node_api_create_property_key_utf16"; "napi_type_tag_object"; "napi_check_object_type_tag"; "node_api_post_finalizer"; "napi_add_async_cleanup_hook"; "napi_remove_async_cleanup_hook"; "uv_mutex_init"; "uv_mutex_lock"; "uv_mutex_unlock"; "uv_mutex_destroy"; "uv_async_init"; "uv_async_send"; "uv_close"; }; \ No newline at end of file
diff --git a/cli/napi/generated_symbol_exports_list_macos.def b/cli/napi/generated_symbol_exports_list_macos.def
index cac7100c6..36b2f37fa 100644
--- a/cli/napi/generated_symbol_exports_list_macos.def
+++ b/cli/napi/generated_symbol_exports_list_macos.def
@@ -150,4 +150,11 @@ _napi_type_tag_object
_napi_check_object_type_tag
_node_api_post_finalizer
_napi_add_async_cleanup_hook
-_napi_remove_async_cleanup_hook \ No newline at end of file
+_napi_remove_async_cleanup_hook
+_uv_mutex_init
+_uv_mutex_lock
+_uv_mutex_unlock
+_uv_mutex_destroy
+_uv_async_init
+_uv_async_send
+_uv_close \ No newline at end of file
diff --git a/cli/napi/generated_symbol_exports_list_windows.def b/cli/napi/generated_symbol_exports_list_windows.def
index 5386b46e5..b7355112e 100644
--- a/cli/napi/generated_symbol_exports_list_windows.def
+++ b/cli/napi/generated_symbol_exports_list_windows.def
@@ -152,4 +152,11 @@ EXPORTS
napi_check_object_type_tag
node_api_post_finalizer
napi_add_async_cleanup_hook
- napi_remove_async_cleanup_hook \ No newline at end of file
+ napi_remove_async_cleanup_hook
+ uv_mutex_init
+ uv_mutex_lock
+ uv_mutex_unlock
+ uv_mutex_destroy
+ uv_async_init
+ uv_async_send
+ uv_close \ No newline at end of file
diff --git a/cli/napi/mod.rs b/cli/napi/mod.rs
index 122d2ff06..811efb1ec 100644
--- a/cli/napi/mod.rs
+++ b/cli/napi/mod.rs
@@ -18,3 +18,4 @@
pub mod js_native_api;
pub mod node_api;
pub mod util;
+pub mod uv;
diff --git a/cli/napi/node_api.rs b/cli/napi/node_api.rs
index 81cb800a7..2efb71c26 100644
--- a/cli/napi/node_api.rs
+++ b/cli/napi/node_api.rs
@@ -547,11 +547,16 @@ fn napi_delete_async_work(env: *mut Env, work: napi_async_work) -> napi_status {
}
#[napi_sym]
-fn napi_get_uv_event_loop(env: *mut Env, uv_loop: *mut *mut ()) -> napi_status {
- let env = check_env!(env);
+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);
- // There is no uv_loop in Deno
- napi_set_last_error(env, napi_generic_failure)
+ unsafe {
+ *uv_loop = env_ptr.cast();
+ }
+ 0
}
#[napi_sym]
diff --git a/cli/napi/sym/symbol_exports.json b/cli/napi/sym/symbol_exports.json
index 64b548d49..00946b8ed 100644
--- a/cli/napi/sym/symbol_exports.json
+++ b/cli/napi/sym/symbol_exports.json
@@ -152,6 +152,13 @@
"napi_check_object_type_tag",
"node_api_post_finalizer",
"napi_add_async_cleanup_hook",
- "napi_remove_async_cleanup_hook"
+ "napi_remove_async_cleanup_hook",
+ "uv_mutex_init",
+ "uv_mutex_lock",
+ "uv_mutex_unlock",
+ "uv_mutex_destroy",
+ "uv_async_init",
+ "uv_async_send",
+ "uv_close"
]
}
diff --git a/cli/napi/uv.rs b/cli/napi/uv.rs
new file mode 100644
index 000000000..d4cb5c0b3
--- /dev/null
+++ b/cli/napi/uv.rs
@@ -0,0 +1,231 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::parking_lot::Mutex;
+use deno_runtime::deno_napi::*;
+use std::mem::MaybeUninit;
+use std::ptr::addr_of_mut;
+
+#[allow(clippy::print_stderr)]
+fn assert_ok(res: c_int) -> c_int {
+ if res != 0 {
+ eprintln!("bad result in uv polyfill: {res}");
+ // don't panic because that might unwind into
+ // c/c++
+ std::process::abort();
+ }
+ res
+}
+
+use crate::napi::js_native_api::napi_create_string_utf8;
+use crate::napi::node_api::napi_create_async_work;
+use crate::napi::node_api::napi_delete_async_work;
+use crate::napi::node_api::napi_queue_async_work;
+use std::ffi::c_int;
+
+const UV_MUTEX_SIZE: usize = {
+ #[cfg(unix)]
+ {
+ std::mem::size_of::<libc::pthread_mutex_t>()
+ }
+ #[cfg(windows)]
+ {
+ std::mem::size_of::<windows_sys::Win32::System::Threading::CRITICAL_SECTION>(
+ )
+ }
+};
+
+#[repr(C)]
+struct uv_mutex_t {
+ mutex: Mutex<()>,
+ _padding: [MaybeUninit<usize>; const {
+ (UV_MUTEX_SIZE - size_of::<Mutex<()>>()) / size_of::<usize>()
+ }],
+}
+
+#[no_mangle]
+unsafe extern "C" fn uv_mutex_init(lock: *mut uv_mutex_t) -> c_int {
+ unsafe {
+ addr_of_mut!((*lock).mutex).write(Mutex::new(()));
+ 0
+ }
+}
+
+#[no_mangle]
+unsafe extern "C" fn uv_mutex_lock(lock: *mut uv_mutex_t) {
+ unsafe {
+ let guard = (*lock).mutex.lock();
+ // forget the guard so it doesn't unlock when it goes out of scope.
+ // we're going to unlock it manually
+ std::mem::forget(guard);
+ }
+}
+
+#[no_mangle]
+unsafe extern "C" fn uv_mutex_unlock(lock: *mut uv_mutex_t) {
+ unsafe {
+ (*lock).mutex.force_unlock();
+ }
+}
+
+#[no_mangle]
+unsafe extern "C" fn uv_mutex_destroy(_lock: *mut uv_mutex_t) {
+ // no cleanup required
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug)]
+#[allow(dead_code)]
+enum uv_handle_type {
+ UV_UNKNOWN_HANDLE = 0,
+ UV_ASYNC,
+ UV_CHECK,
+ UV_FS_EVENT,
+ UV_FS_POLL,
+ UV_HANDLE,
+ UV_IDLE,
+ UV_NAMED_PIPE,
+ UV_POLL,
+ UV_PREPARE,
+ UV_PROCESS,
+ UV_STREAM,
+ UV_TCP,
+ UV_TIMER,
+ UV_TTY,
+ UV_UDP,
+ UV_SIGNAL,
+ UV_FILE,
+ UV_HANDLE_TYPE_MAX,
+}
+
+const UV_HANDLE_SIZE: usize = 96;
+
+#[repr(C)]
+struct uv_handle_t {
+ // public members
+ pub data: *mut c_void,
+ pub r#loop: *mut uv_loop_t,
+ pub r#type: uv_handle_type,
+
+ _padding: [MaybeUninit<usize>; const {
+ (UV_HANDLE_SIZE
+ - size_of::<*mut c_void>()
+ - size_of::<*mut uv_loop_t>()
+ - size_of::<uv_handle_type>())
+ / size_of::<usize>()
+ }],
+}
+
+#[cfg(unix)]
+const UV_ASYNC_SIZE: usize = 128;
+
+#[cfg(windows)]
+const UV_ASYNC_SIZE: usize = 224;
+
+#[repr(C)]
+struct uv_async_t {
+ // public members
+ pub data: *mut c_void,
+ pub r#loop: *mut uv_loop_t,
+ pub r#type: uv_handle_type,
+ // private
+ async_cb: uv_async_cb,
+ work: napi_async_work,
+ _padding: [MaybeUninit<usize>; const {
+ (UV_ASYNC_SIZE
+ - size_of::<*mut c_void>()
+ - size_of::<*mut uv_loop_t>()
+ - size_of::<uv_handle_type>()
+ - size_of::<uv_async_cb>()
+ - size_of::<napi_async_work>())
+ / size_of::<usize>()
+ }],
+}
+
+type uv_loop_t = Env;
+type uv_async_cb = extern "C" fn(handle: *mut uv_async_t);
+#[no_mangle]
+unsafe extern "C" fn uv_async_init(
+ r#loop: *mut uv_loop_t,
+ // probably uninitialized
+ r#async: *mut uv_async_t,
+ async_cb: uv_async_cb,
+) -> c_int {
+ unsafe {
+ addr_of_mut!((*r#async).r#loop).write(r#loop);
+ addr_of_mut!((*r#async).r#type).write(uv_handle_type::UV_ASYNC);
+ addr_of_mut!((*r#async).async_cb).write(async_cb);
+
+ let mut resource_name: MaybeUninit<napi_value> = MaybeUninit::uninit();
+ assert_ok(napi_create_string_utf8(
+ r#loop,
+ c"uv_async".as_ptr(),
+ usize::MAX,
+ resource_name.as_mut_ptr(),
+ ));
+ let resource_name = resource_name.assume_init();
+
+ let res = napi_create_async_work(
+ r#loop,
+ None::<v8::Local<'static, v8::Value>>.into(),
+ resource_name,
+ Some(async_exec_wrap),
+ None,
+ r#async.cast(),
+ addr_of_mut!((*r#async).work),
+ );
+ -res
+ }
+}
+
+#[no_mangle]
+unsafe extern "C" fn uv_async_send(handle: *mut uv_async_t) -> c_int {
+ unsafe { -napi_queue_async_work((*handle).r#loop, (*handle).work) }
+}
+
+type uv_close_cb = unsafe extern "C" fn(*mut uv_handle_t);
+
+#[no_mangle]
+unsafe extern "C" fn uv_close(handle: *mut uv_handle_t, close: uv_close_cb) {
+ unsafe {
+ if handle.is_null() {
+ close(handle);
+ return;
+ }
+ if let uv_handle_type::UV_ASYNC = (*handle).r#type {
+ let handle: *mut uv_async_t = handle.cast();
+ napi_delete_async_work((*handle).r#loop, (*handle).work);
+ }
+ close(handle);
+ }
+}
+
+unsafe extern "C" fn async_exec_wrap(_env: napi_env, data: *mut c_void) {
+ let data: *mut uv_async_t = data.cast();
+ unsafe {
+ ((*data).async_cb)(data);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn sizes() {
+ assert_eq!(
+ std::mem::size_of::<libuv_sys_lite::uv_mutex_t>(),
+ UV_MUTEX_SIZE
+ );
+ assert_eq!(
+ std::mem::size_of::<libuv_sys_lite::uv_handle_t>(),
+ UV_HANDLE_SIZE
+ );
+ assert_eq!(
+ std::mem::size_of::<libuv_sys_lite::uv_async_t>(),
+ UV_ASYNC_SIZE
+ );
+ assert_eq!(std::mem::size_of::<uv_mutex_t>(), UV_MUTEX_SIZE);
+ assert_eq!(std::mem::size_of::<uv_handle_t>(), UV_HANDLE_SIZE);
+ assert_eq!(std::mem::size_of::<uv_async_t>(), UV_ASYNC_SIZE);
+ }
+}
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);
+});