From 38a776a40443f677e3c38f276b94e585c6604a77 Mon Sep 17 00:00:00 2001 From: Zack Radisic <56137411+zackradisic@users.noreply.github.com> Date: Tue, 1 Apr 2025 19:08:32 -0700 Subject: [PATCH] Implement `uv_mutex_*` fns and others (#18555) --- cmake/CompilerFlags.cmake | 8 + cmake/targets/BuildBun.cmake | 2 + .../bindings/libuv/generate_uv_posix_stubs.ts | 4 +- .../generate_uv_posix_stubs_constants.ts | 19 ++- src/bun.js/bindings/root.h | 4 + .../bindings/uv-posix-polyfills-darwin.c | 20 +++ .../bindings/uv-posix-polyfills-linux.c | 42 +++++ .../bindings/uv-posix-polyfills-posix.c | 15 ++ src/bun.js/bindings/uv-posix-polyfills.c | 141 ++++++++++++++++ src/bun.js/bindings/uv-posix-polyfills.cpp | 20 --- src/bun.js/bindings/uv-posix-polyfills.h | 9 +- .../{uv-posix-stubs.cpp => uv-posix-stubs.c} | 48 ------ src/bun_js.zig | 1 + src/crash_handler.zig | 13 +- test/napi/uv-stub-stuff/uv_impl.c | 159 ++++++++++++++++++ test/napi/uv.test.ts | 114 +++++++++++++ 16 files changed, 535 insertions(+), 84 deletions(-) create mode 100644 src/bun.js/bindings/uv-posix-polyfills-darwin.c create mode 100644 src/bun.js/bindings/uv-posix-polyfills-linux.c create mode 100644 src/bun.js/bindings/uv-posix-polyfills-posix.c create mode 100644 src/bun.js/bindings/uv-posix-polyfills.c delete mode 100644 src/bun.js/bindings/uv-posix-polyfills.cpp rename src/bun.js/bindings/{uv-posix-stubs.cpp => uv-posix-stubs.c} (97%) create mode 100644 test/napi/uv-stub-stuff/uv_impl.c create mode 100644 test/napi/uv.test.ts diff --git a/cmake/CompilerFlags.cmake b/cmake/CompilerFlags.cmake index 9f61034af8..c321c758be 100644 --- a/cmake/CompilerFlags.cmake +++ b/cmake/CompilerFlags.cmake @@ -142,6 +142,14 @@ if(UNIX) -fno-unwind-tables -fno-asynchronous-unwind-tables ) + + # needed for libuv stubs because they use + # C23 feature which lets you define parameter without + # name + register_compiler_flags( + DESCRIPTION "Allow C23 extensions" + -Wno-c23-extensions + ) endif() register_compiler_flags( diff --git a/cmake/targets/BuildBun.cmake b/cmake/targets/BuildBun.cmake index 084dcc9054..0a1e0e64df 100644 --- a/cmake/targets/BuildBun.cmake +++ b/cmake/targets/BuildBun.cmake @@ -635,6 +635,8 @@ file(GLOB BUN_C_SOURCES ${CONFIGURE_DEPENDS} ${BUN_USOCKETS_SOURCE}/src/eventing/*.c ${BUN_USOCKETS_SOURCE}/src/internal/*.c ${BUN_USOCKETS_SOURCE}/src/crypto/*.c + ${CWD}/src/bun.js/bindings/uv-posix-polyfills.c + ${CWD}/src/bun.js/bindings/uv-posix-stubs.c ) if(WIN32) diff --git a/src/bun.js/bindings/libuv/generate_uv_posix_stubs.ts b/src/bun.js/bindings/libuv/generate_uv_posix_stubs.ts index 8ceee0fd62..f2e875edbc 100644 --- a/src/bun.js/bindings/libuv/generate_uv_posix_stubs.ts +++ b/src/bun.js/bindings/libuv/generate_uv_posix_stubs.ts @@ -274,9 +274,9 @@ ${parts.map(([stub, _]) => stub).join("\n\n")} `; -await Bun.write(join(import.meta.dir, "../", "uv-posix-stubs.cpp"), final_contents); +await Bun.write(join(import.meta.dir, "../", "uv-posix-stubs.c"), final_contents); if (Bun.which("clang-format")) { - await Bun.$`clang-format -i ${join(import.meta.dir, "../", "uv-posix-stubs.cpp")}`; + await Bun.$`clang-format -i ${join(import.meta.dir, "../", "uv-posix-stubs.c")}`; } const test_plugin_contents = ` // GENERATED CODE ... NO TOUCHY!! diff --git a/src/bun.js/bindings/libuv/generate_uv_posix_stubs_constants.ts b/src/bun.js/bindings/libuv/generate_uv_posix_stubs_constants.ts index 966f50afea..b28f652ed9 100644 --- a/src/bun.js/bindings/libuv/generate_uv_posix_stubs_constants.ts +++ b/src/bun.js/bindings/libuv/generate_uv_posix_stubs_constants.ts @@ -123,7 +123,8 @@ export const symbols = [ "uv_handle_size", "uv_handle_type_name", "uv_has_ref", - "uv_hrtime", + // Defined in uv-posix-polyfills.cpp + // "uv_hrtime", "uv_idle_init", "uv_idle_start", "uv_idle_stop", @@ -161,14 +162,16 @@ export const symbols = [ "uv_loop_size", "uv_metrics_idle_time", "uv_metrics_info", - "uv_mutex_destroy", - "uv_mutex_init", - "uv_mutex_init_recursive", - "uv_mutex_lock", - "uv_mutex_trylock", - "uv_mutex_unlock", + // Defined in uv-posix-polyfills.cpp + // "uv_mutex_destroy", + // "uv_mutex_init", + // "uv_mutex_init_recursive", + // "uv_mutex_lock", + // "uv_mutex_trylock", + // "uv_mutex_unlock", "uv_now", - "uv_once", + // Defined in uv-posix-polyfills.cpp + // "uv_once", "uv_open_osfhandle", "uv_os_environ", "uv_os_free_environ", diff --git a/src/bun.js/bindings/root.h b/src/bun.js/bindings/root.h index de68e2fec8..ba06c1e7d2 100644 --- a/src/bun.js/bindings/root.h +++ b/src/bun.js/bindings/root.h @@ -71,6 +71,9 @@ #define WEBCORE_EXPORT JS_EXPORT_PRIVATE #endif +#include + +#ifdef __cplusplus #include #include #include @@ -78,6 +81,7 @@ #include #include #include +#endif #define ENABLE_WEB_CRYPTO 1 #define USE_OPENSSL 1 diff --git a/src/bun.js/bindings/uv-posix-polyfills-darwin.c b/src/bun.js/bindings/uv-posix-polyfills-darwin.c new file mode 100644 index 0000000000..7497ae74bd --- /dev/null +++ b/src/bun.js/bindings/uv-posix-polyfills-darwin.c @@ -0,0 +1,20 @@ + +#include "uv-posix-polyfills.h" +#include +#include +#include + +static uv_once_t once = UV_ONCE_INIT; +static mach_timebase_info_data_t timebase; + +static void uv__hrtime_init_once(void) +{ + if (KERN_SUCCESS != mach_timebase_info(&timebase)) + abort(); +} + +uint64_t uv__hrtime(uv_clocktype_t type) +{ + uv_once(&once, uv__hrtime_init_once); + return mach_continuous_time() * timebase.numer / timebase.denom; +} diff --git a/src/bun.js/bindings/uv-posix-polyfills-linux.c b/src/bun.js/bindings/uv-posix-polyfills-linux.c new file mode 100644 index 0000000000..690c4a8439 --- /dev/null +++ b/src/bun.js/bindings/uv-posix-polyfills-linux.c @@ -0,0 +1,42 @@ + +#include "uv-posix-polyfills.h" +#include +#include +#include + +uint64_t uv__hrtime(uv_clocktype_t type) +{ + static _Atomic clock_t fast_clock_id = -1; + struct timespec t; + clock_t clock_id; + + /* Prefer CLOCK_MONOTONIC_COARSE if available but only when it has + * millisecond granularity or better. CLOCK_MONOTONIC_COARSE is + * serviced entirely from the vDSO, whereas CLOCK_MONOTONIC may + * decide to make a costly system call. + */ + /* TODO(bnoordhuis) Use CLOCK_MONOTONIC_COARSE for UV_CLOCK_PRECISE + * when it has microsecond granularity or better (unlikely). + */ + clock_id = CLOCK_MONOTONIC; + if (type != UV_CLOCK_FAST) + goto done; + + clock_id = atomic_load_explicit(&fast_clock_id, memory_order_relaxed); + if (clock_id != -1) + goto done; + + clock_id = CLOCK_MONOTONIC; + if (0 == clock_getres(CLOCK_MONOTONIC_COARSE, &t)) + if (t.tv_nsec <= 1 * 1000 * 1000) + clock_id = CLOCK_MONOTONIC_COARSE; + + atomic_store_explicit(&fast_clock_id, clock_id, memory_order_relaxed); + +done: + + if (clock_gettime(clock_id, &t)) + return 0; /* Not really possible. */ + + return t.tv_sec * (uint64_t)1e9 + t.tv_nsec; +} diff --git a/src/bun.js/bindings/uv-posix-polyfills-posix.c b/src/bun.js/bindings/uv-posix-polyfills-posix.c new file mode 100644 index 0000000000..afaaf396df --- /dev/null +++ b/src/bun.js/bindings/uv-posix-polyfills-posix.c @@ -0,0 +1,15 @@ +#include "uv-posix-polyfills.h" + +#include +#include +#include + +uint64_t uv__hrtime(uv_clocktype_t type) +{ + struct timespec t; + + if (clock_gettime(CLOCK_MONOTONIC, &t)) + abort(); + + return t.tv_sec * (uint64_t)1e9 + t.tv_nsec; +} diff --git a/src/bun.js/bindings/uv-posix-polyfills.c b/src/bun.js/bindings/uv-posix-polyfills.c new file mode 100644 index 0000000000..b36cd5c6e4 --- /dev/null +++ b/src/bun.js/bindings/uv-posix-polyfills.c @@ -0,0 +1,141 @@ +#include "uv-posix-polyfills.h" + +#if OS(LINUX) || OS(DARWIN) + +#include +#include +#include + +// libuv does the annoying thing of #undef'ing these +#include +#if EDOM > 0 +#define UV__ERR(x) (-(x)) +#else +#define UV__ERR(x) (x) +#endif + +void __bun_throw_not_implemented(const char* symbol_name) +{ + CrashHandler__unsupportedUVFunction(symbol_name); +} + +// Internals + +uint64_t uv__hrtime(uv_clocktype_t type); + +#if defined(__linux__) +#include "uv-posix-polyfills-linux.c" +// #elif defined(__MVS__) +// #include "uv/os390.h" +// #elif defined(__PASE__) /* __PASE__ and _AIX are both defined on IBM i */ +// #include "uv/posix.h" /* IBM i needs uv/posix.h, not uv/aix.h */ +// #elif defined(_AIX) +// #include "uv/aix.h" +// #elif defined(__sun) +// #include "uv/sunos.h" +#elif defined(__APPLE__) +#include "uv-posix-polyfills-darwin.c" +// #elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +// #include "uv/bsd.h" +#elif defined(__CYGWIN__) || defined(__MSYS__) || defined(__HAIKU__) || defined(__QNX__) || defined(__GNU__) +#include "uv-posix-polyfills-posix.c" +#endif + +uv_pid_t uv_os_getpid() +{ + return getpid(); +} + +uv_pid_t uv_os_getppid() +{ + return getppid(); +} + +UV_EXTERN void uv_once(uv_once_t* guard, void (*callback)(void)) +{ + if (pthread_once(guard, callback)) + abort(); +} + +UV_EXTERN uint64_t uv_hrtime(void) +{ + return uv__hrtime(UV_CLOCK_PRECISE); +} + +// Copy-pasted from libuv +UV_EXTERN void uv_mutex_destroy(uv_mutex_t* mutex) +{ + if (pthread_mutex_destroy(mutex)) + abort(); +} + +// Copy-pasted from libuv +UV_EXTERN int uv_mutex_init(uv_mutex_t* mutex) +{ + pthread_mutexattr_t attr; + int err; + + if (pthread_mutexattr_init(&attr)) + abort(); + + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK)) + abort(); + + err = pthread_mutex_init(mutex, &attr); + + if (pthread_mutexattr_destroy(&attr)) + abort(); + + return UV__ERR(err); +} + +// Copy-pasted from libuv +UV_EXTERN int uv_mutex_init_recursive(uv_mutex_t* mutex) +{ + pthread_mutexattr_t attr; + int err; + + if (pthread_mutexattr_init(&attr)) + abort(); + + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) + abort(); + + err = pthread_mutex_init(mutex, &attr); + + if (pthread_mutexattr_destroy(&attr)) + abort(); + + return UV__ERR(err); +} + +// Copy-pasted from libuv +UV_EXTERN void uv_mutex_lock(uv_mutex_t* mutex) +{ + if (pthread_mutex_lock(mutex)) + abort(); +} + +// Copy-pasted from libuv +UV_EXTERN int uv_mutex_trylock(uv_mutex_t* mutex) +{ + int err; + + err = pthread_mutex_trylock(mutex); + if (err) { + if (err != EBUSY && err != EAGAIN) + abort(); + return UV_EBUSY; + } + + return 0; +} + +// Copy-pasted from libuv +UV_EXTERN void uv_mutex_unlock(uv_mutex_t* mutex) +{ + if (pthread_mutex_unlock(mutex)) + abort(); +} + +#endif diff --git a/src/bun.js/bindings/uv-posix-polyfills.cpp b/src/bun.js/bindings/uv-posix-polyfills.cpp deleted file mode 100644 index ed09f32d74..0000000000 --- a/src/bun.js/bindings/uv-posix-polyfills.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "uv-posix-polyfills.h" - -#if OS(LINUX) || OS(DARWIN) - -void __bun_throw_not_implemented(const char* symbol_name) -{ - CrashHandler__unsupportedUVFunction(symbol_name); -} - -uv_pid_t uv_os_getpid() -{ - return getpid(); -} - -uv_pid_t uv_os_getppid() -{ - return getppid(); -} - -#endif diff --git a/src/bun.js/bindings/uv-posix-polyfills.h b/src/bun.js/bindings/uv-posix-polyfills.h index bcdac5b312..de88022f2d 100644 --- a/src/bun.js/bindings/uv-posix-polyfills.h +++ b/src/bun.js/bindings/uv-posix-polyfills.h @@ -8,12 +8,17 @@ // These functions are called by the stubs to crash with a nice error message // when accessing a libuv functin which we do not support on posix -extern "C" void CrashHandler__unsupportedUVFunction(const char* function_name); +void CrashHandler__unsupportedUVFunction(const char* function_name); void __bun_throw_not_implemented(const char* symbol_name); // libuv headers will use UV_EXTERN -#define UV_EXTERN extern "C" __attribute__((visibility("default"))) __attribute__((used)) +#define UV_EXTERN __attribute__((visibility("default"))) __attribute__((used)) #include +typedef enum { + UV_CLOCK_PRECISE = 0, /* Use the highest resolution clock available. */ + UV_CLOCK_FAST = 1 /* Use the fastest clock with <= 1ms granularity. */ +} uv_clocktype_t; + #endif diff --git a/src/bun.js/bindings/uv-posix-stubs.cpp b/src/bun.js/bindings/uv-posix-stubs.c similarity index 97% rename from src/bun.js/bindings/uv-posix-stubs.cpp rename to src/bun.js/bindings/uv-posix-stubs.c index 1da812e570..2ed27895e7 100644 --- a/src/bun.js/bindings/uv-posix-stubs.cpp +++ b/src/bun.js/bindings/uv-posix-stubs.c @@ -830,12 +830,6 @@ UV_EXTERN int uv_has_ref(const uv_handle_t*) __builtin_unreachable(); } -UV_EXTERN uint64_t uv_hrtime(void) -{ - __bun_throw_not_implemented("uv_hrtime"); - __builtin_unreachable(); -} - UV_EXTERN int uv_idle_init(uv_loop_t*, uv_idle_t* idle) { __bun_throw_not_implemented("uv_idle_init"); @@ -1063,54 +1057,12 @@ UV_EXTERN int uv_metrics_info(uv_loop_t* loop, uv_metrics_t* metrics) __builtin_unreachable(); } -UV_EXTERN void uv_mutex_destroy(uv_mutex_t* handle) -{ - __bun_throw_not_implemented("uv_mutex_destroy"); - __builtin_unreachable(); -} - -UV_EXTERN int uv_mutex_init(uv_mutex_t* handle) -{ - __bun_throw_not_implemented("uv_mutex_init"); - __builtin_unreachable(); -} - -UV_EXTERN int uv_mutex_init_recursive(uv_mutex_t* handle) -{ - __bun_throw_not_implemented("uv_mutex_init_recursive"); - __builtin_unreachable(); -} - -UV_EXTERN void uv_mutex_lock(uv_mutex_t* handle) -{ - __bun_throw_not_implemented("uv_mutex_lock"); - __builtin_unreachable(); -} - -UV_EXTERN int uv_mutex_trylock(uv_mutex_t* handle) -{ - __bun_throw_not_implemented("uv_mutex_trylock"); - __builtin_unreachable(); -} - -UV_EXTERN void uv_mutex_unlock(uv_mutex_t* handle) -{ - __bun_throw_not_implemented("uv_mutex_unlock"); - __builtin_unreachable(); -} - UV_EXTERN uint64_t uv_now(const uv_loop_t*) { __bun_throw_not_implemented("uv_now"); __builtin_unreachable(); } -UV_EXTERN void uv_once(uv_once_t* guard, void (*callback)(void)) -{ - __bun_throw_not_implemented("uv_once"); - __builtin_unreachable(); -} - UV_EXTERN int uv_open_osfhandle(uv_os_fd_t os_fd) { __bun_throw_not_implemented("uv_open_osfhandle"); diff --git a/src/bun_js.zig b/src/bun_js.zig index 4ff0ac93de..cc8c570148 100644 --- a/src/bun_js.zig +++ b/src/bun_js.zig @@ -449,6 +449,7 @@ pub const Run = struct { } JSC.napi.fixDeadCodeElimination(); + bun.crash_handler.fixDeadCodeElimination(); vm.globalExit(); } diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 16dbdb1d5f..ae3c734a40 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -255,11 +255,12 @@ pub fn crashHandler( \\Bun is actively working on supporting all libuv functions for POSIX \\systems, please see this issue to track our progress: \\ - \\https://github.com/oven-sh/bun/issues/4290 + \\https://github.com/oven-sh/bun/issues/18546 \\ \\ ; writer.print(Output.prettyFmt(fmt, true), .{name}) catch std.posix.abort(); + has_printed_message = true; } } else { if (Output.enable_ansi_colors) { @@ -368,7 +369,7 @@ pub fn crashHandler( \\Bun is actively working on supporting all libuv functions for POSIX \\systems, please see this issue to track our progress: \\ - \\https://github.com/oven-sh/bun/issues/4290 + \\https://github.com/oven-sh/bun/issues/18546 \\ \\ ; @@ -1862,7 +1863,7 @@ export fn CrashHandler__setInsideNativePlugin(name: ?[*:0]const u8) callconv(.C) inside_native_plugin = name; } -fn unsupportUVFunction(name: ?[*:0]const u8) callconv(.C) void { +export fn CrashHandler__unsupportedUVFunction(name: ?[*:0]const u8) callconv(.C) void { bun.analytics.Features.unsupported_uv_function += 1; unsupported_uv_function = name; std.debug.panic("unsupported uv function: {s}", .{name.?}); @@ -1882,9 +1883,13 @@ export fn CrashHandler__setDlOpenAction(action: ?[*:0]const u8) void { } } +pub fn fixDeadCodeElimination() void { + std.mem.doNotOptimizeAway(&CrashHandler__unsupportedUVFunction); +} comptime { _ = &Bun__crashHandler; if (!bun.Environment.isWindows) { - @export(&unsupportUVFunction, .{ .name = "CrashHandler__unsupportedUVFunction" }); + std.mem.doNotOptimizeAway(&CrashHandler__unsupportedUVFunction); + // @export(&unsupportUVFunction, .{ .name = "CrashHandler__unsupportedUVFunction", .linkage = .strong }); } } diff --git a/test/napi/uv-stub-stuff/uv_impl.c b/test/napi/uv-stub-stuff/uv_impl.c new file mode 100644 index 0000000000..ca3b4358c5 --- /dev/null +++ b/test/napi/uv-stub-stuff/uv_impl.c @@ -0,0 +1,159 @@ +#include + +#include +#include +#include +#include +#include + +// Test mutex initialization and destruction +static napi_value test_mutex_init_destroy(napi_env env, + napi_callback_info info) { + uv_mutex_t mutex; + int result = uv_mutex_init(&mutex); + if (result != 0) { + napi_throw_error(env, NULL, "Failed to initialize mutex"); + return NULL; + } + + uv_mutex_destroy(&mutex); + + napi_value ret; + napi_get_boolean(env, true, &ret); + return ret; +} + +// Test recursive mutex +static napi_value test_mutex_recursive(napi_env env, napi_callback_info info) { + uv_mutex_t mutex; + int result = uv_mutex_init_recursive(&mutex); + if (result != 0) { + napi_throw_error(env, NULL, "Failed to initialize recursive mutex"); + return NULL; + } + + // Try locking multiple times + uv_mutex_lock(&mutex); + uv_mutex_lock(&mutex); + + // Unlock the same number of times + uv_mutex_unlock(&mutex); + uv_mutex_unlock(&mutex); + + uv_mutex_destroy(&mutex); + + napi_value ret; + napi_get_boolean(env, true, &ret); + return ret; +} + +// Test mutex trylock +static napi_value test_mutex_trylock(napi_env env, napi_callback_info info) { + uv_mutex_t mutex; + uv_mutex_init(&mutex); + + int result = uv_mutex_trylock(&mutex); + if (result != 0) { + uv_mutex_destroy(&mutex); + napi_throw_error(env, NULL, "Failed to trylock mutex"); + return NULL; + } + + uv_mutex_unlock(&mutex); + uv_mutex_destroy(&mutex); + + napi_value ret; + napi_get_boolean(env, true, &ret); + return ret; +} + +// Test getpid and getppid +static napi_value test_process_ids(napi_env env, napi_callback_info info) { + uv_pid_t pid = uv_os_getpid(); + uv_pid_t ppid = uv_os_getppid(); + + // Create return object with pid and ppid + napi_value obj; + napi_create_object(env, &obj); + + napi_value pid_value, ppid_value; + napi_create_int32(env, pid, &pid_value); + napi_create_int32(env, ppid, &ppid_value); + + napi_set_named_property(env, obj, "pid", pid_value); + napi_set_named_property(env, obj, "ppid", ppid_value); + + return obj; +} + +int count = 0; +// Test uv_once +static void once_callback(void) { + // Just a dummy callback + count++; +} +uv_once_t guard = UV_ONCE_INIT; + +static napi_value test_uv_once(napi_env env, napi_callback_info info) { + uv_once(&guard, once_callback); + + napi_value ret; + napi_create_int32(env, count, &ret); + return ret; +} + +// Test uv_hrtime +static napi_value test_hrtime(napi_env env, napi_callback_info info) { + uint64_t time1 = uv_hrtime(); + + // Sleep for a tiny bit to ensure time passes + usleep(1000); // Sleep for 1ms + + uint64_t time2 = uv_hrtime(); + + // Create return object with both timestamps + napi_value obj; + napi_create_object(env, &obj); + + // Convert uint64_t to two int32 values (high and low bits) + // because JavaScript numbers can't safely handle 64-bit integers + napi_value time1_low, time1_high, time2_low, time2_high; + napi_create_int32(env, (int32_t)(time1 & 0xFFFFFFFF), &time1_low); + napi_create_int32(env, (int32_t)(time1 >> 32), &time1_high); + napi_create_int32(env, (int32_t)(time2 & 0xFFFFFFFF), &time2_low); + napi_create_int32(env, (int32_t)(time2 >> 32), &time2_high); + + napi_set_named_property(env, obj, "time1Low", time1_low); + napi_set_named_property(env, obj, "time1High", time1_high); + napi_set_named_property(env, obj, "time2Low", time2_low); + napi_set_named_property(env, obj, "time2High", time2_high); + + return obj; +} + +napi_value Init(napi_env env, napi_value exports) { + // Register all test functions + napi_value fn; + + napi_create_function(env, NULL, 0, test_mutex_init_destroy, NULL, &fn); + napi_set_named_property(env, exports, "testMutexInitDestroy", fn); + + napi_create_function(env, NULL, 0, test_mutex_recursive, NULL, &fn); + napi_set_named_property(env, exports, "testMutexRecursive", fn); + + napi_create_function(env, NULL, 0, test_mutex_trylock, NULL, &fn); + napi_set_named_property(env, exports, "testMutexTrylock", fn); + + napi_create_function(env, NULL, 0, test_process_ids, NULL, &fn); + napi_set_named_property(env, exports, "testProcessIds", fn); + + napi_create_function(env, NULL, 0, test_uv_once, NULL, &fn); + napi_set_named_property(env, exports, "testUvOnce", fn); + + napi_create_function(env, NULL, 0, test_hrtime, NULL, &fn); + napi_set_named_property(env, exports, "testHrtime", fn); + + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/napi/uv.test.ts b/test/napi/uv.test.ts new file mode 100644 index 0000000000..52f9a9c4e7 --- /dev/null +++ b/test/napi/uv.test.ts @@ -0,0 +1,114 @@ +import { beforeAll, describe, expect, afterEach, test } from "bun:test"; +import path from "node:path"; +import { bunEnv, bunExe, makeTree, tempDirWithFiles, isWindows } from "harness"; +import source from "./uv-stub-stuff/uv_impl.c"; +import { symbols, test_skipped } from "../../src/bun.js/bindings/libuv/generate_uv_posix_stubs_constants"; + +const symbols_to_test = symbols.filter(s => !test_skipped.includes(s)); + +// We use libuv on Windows +describe.if(!isWindows)("uv stubs", () => { + const cwd = process.cwd(); + let tempdir: string = ""; + let outdir: string = ""; + let nativeModule: any; + + beforeAll(async () => { + const files = { + "uv_impl.c": await Bun.file(source).text(), + "package.json": JSON.stringify({ + "name": "fake-plugin", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5.0.0", + }, + "scripts": { + "build:napi": "node-gyp configure && node-gyp build", + }, + "dependencies": { + "node-gyp": "10.2.0", + }, + }), + "binding.gyp": `{ + "targets": [ + { + "target_name": "uv_test", + "sources": [ "uv_impl.c" ], + "include_dirs": [ ".", "./libuv" ], + "cflags": ["-fPIC"], + "ldflags": ["-Wl,--export-dynamic"] + }, + ] + }`, + }; + + tempdir = tempDirWithFiles("uv-tests", files); + await makeTree(tempdir, files); + outdir = path.join(tempdir, "dist"); + + process.chdir(tempdir); + + const libuvDir = path.join(__dirname, "../../src/bun.js/bindings/libuv"); + await Bun.$`cp -R ${libuvDir} ${path.join(tempdir, "libuv")}`; + await Bun.$`${bunExe()} i && ${bunExe()} build:napi`.env(bunEnv).cwd(tempdir); + + nativeModule = require(path.join(tempdir, "./build/Release/uv_test.node")); + }); + + afterEach(() => { + process.chdir(cwd); + }); + + test("mutex init and destroy", () => { + expect(() => nativeModule.testMutexInitDestroy()).not.toThrow(); + }); + + test("recursive mutex", () => { + expect(() => nativeModule.testMutexRecursive()).not.toThrow(); + }); + + test("mutex trylock", () => { + expect(() => nativeModule.testMutexTrylock()).not.toThrow(); + }); + + test("process IDs", () => { + const result = nativeModule.testProcessIds(); + expect(result).toHaveProperty("pid"); + expect(result).toHaveProperty("ppid"); + expect(result.pid).toBeGreaterThan(0); + expect(result.ppid).toBeGreaterThan(0); + // The process ID should match Node's process.pid + expect(result.pid).toBe(process.pid); + }); + + test("uv_once", () => { + expect(nativeModule.testUvOnce()).toBe(1); + expect(nativeModule.testUvOnce()).toBe(1); + expect(nativeModule.testUvOnce()).toBe(1); + }); + + test("hrtime", () => { + const result = nativeModule.testHrtime(); + + // Reconstruct the 64-bit values + const time1 = (BigInt(result.time1High) << 32n) | BigInt(result.time1Low >>> 0); + const time2 = (BigInt(result.time2High) << 32n) | BigInt(result.time2Low >>> 0); + + // Verify that: + // 1. time2 is greater than time1 (time passed) + expect(time2 > time1).toBe(true); + + // 2. The difference should be at least 1ms (we slept for 1ms) + // hrtime is in nanoseconds, so 1ms = 1,000,000 ns + const diff = time2 - time1; + expect(diff >= 1_000_000n).toBe(true); + + // 3. The difference shouldn't be unreasonably large + // Let's say not more than 100ms (100,000,000 ns) + expect(diff <= 100_000_000n).toBe(true); + }); +});