Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions src/bun.js/bindings/libuv/generate_uv_posix_stubs_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,16 +271,18 @@ export const symbols = [
"uv_tcp_nodelay",
"uv_tcp_open",
"uv_tcp_simultaneous_accepts",
"uv_thread_create",
"uv_thread_create_ex",
"uv_thread_detach",
"uv_thread_equal",
// Defined in uv-posix-polyfills.c
// "uv_thread_create",
// "uv_thread_create_ex",
// "uv_thread_detach",
// "uv_thread_equal",
"uv_thread_getaffinity",
"uv_thread_getcpu",
"uv_thread_getname",
"uv_thread_getpriority",
"uv_thread_join",
"uv_thread_self",
// Defined in uv-posix-polyfills.c
// "uv_thread_join",
// "uv_thread_self",
"uv_thread_setaffinity",
"uv_thread_setname",
"uv_thread_setpriority",
Expand Down
76 changes: 76 additions & 0 deletions src/bun.js/bindings/uv-posix-polyfills.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,80 @@ UV_EXTERN void uv_mutex_unlock(uv_mutex_t* mutex)
abort();
}

// Copy-pasted from libuv (src/unix/thread.c)
UV_EXTERN uv_thread_t uv_thread_self(void)
{
return pthread_self();
}

// Copy-pasted from libuv (src/unix/thread.c)
UV_EXTERN int uv_thread_equal(const uv_thread_t* t1, const uv_thread_t* t2)
{
return pthread_equal(*t1, *t2);
}

// Copy-pasted from libuv (src/unix/thread.c)
UV_EXTERN int uv_thread_join(uv_thread_t* tid)
{
return UV__ERR(pthread_join(*tid, NULL));
}

// Copy-pasted from libuv (src/unix/thread.c)
UV_EXTERN int uv_thread_detach(uv_thread_t* tid)
{
return UV__ERR(pthread_detach(*tid));
}

// Copy-pasted from libuv (src/unix/thread.c), with stack size tuning omitted.
// The libuv version rounds a requested stack size up to a page boundary and
// clamps it to a minimum; here we fall through to pthread defaults when no
// size is requested and just honor the requested size otherwise.
UV_EXTERN int uv_thread_create_ex(uv_thread_t* tid,
const uv_thread_options_t* params,
uv_thread_cb entry,
void* arg)
{
int err;
pthread_attr_t* attr;
pthread_attr_t attr_storage;
size_t stack_size;

/* Used to squelch a -Wcast-function-type warning. */
union {
void (*in)(void*);
void* (*out)(void*);
} f;

stack_size = (params != NULL && (params->flags & UV_THREAD_HAS_STACK_SIZE))
? params->stack_size
: 0;

attr = NULL;
if (stack_size > 0) {
attr = &attr_storage;

if (pthread_attr_init(attr))
abort();

if (pthread_attr_setstacksize(attr, stack_size))
abort();
}

f.in = entry;
err = pthread_create(tid, attr, f.out, arg);

if (attr != NULL)
pthread_attr_destroy(attr);

return UV__ERR(err);
}

// Copy-pasted from libuv (src/unix/thread.c)
UV_EXTERN int uv_thread_create(uv_thread_t* tid, uv_thread_cb entry, void* arg)
{
uv_thread_options_t params;
params.flags = UV_THREAD_NO_FLAGS;
return uv_thread_create_ex(tid, &params, entry, arg);
}

#endif
39 changes: 0 additions & 39 deletions src/bun.js/bindings/uv-posix-stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1686,33 +1686,6 @@ UV_EXTERN int uv_tcp_simultaneous_accepts(uv_tcp_t* handle, int enable)
__builtin_unreachable();
}

UV_EXTERN int uv_thread_create(uv_thread_t* tid, uv_thread_cb entry, void* arg)
{
__bun_throw_not_implemented("uv_thread_create");
__builtin_unreachable();
}

UV_EXTERN int uv_thread_create_ex(uv_thread_t* tid,
const uv_thread_options_t* params,
uv_thread_cb entry,
void* arg)
{
__bun_throw_not_implemented("uv_thread_create_ex");
__builtin_unreachable();
}

UV_EXTERN int uv_thread_detach(uv_thread_t* tid)
{
__bun_throw_not_implemented("uv_thread_detach");
__builtin_unreachable();
}

UV_EXTERN int uv_thread_equal(const uv_thread_t* t1, const uv_thread_t* t2)
{
__bun_throw_not_implemented("uv_thread_equal");
__builtin_unreachable();
}

UV_EXTERN int uv_thread_getaffinity(uv_thread_t* tid,
char* cpumask,
size_t mask_size)
Expand All @@ -1739,18 +1712,6 @@ UV_EXTERN int uv_thread_getpriority(uv_thread_t tid, int* priority)
__builtin_unreachable();
}

UV_EXTERN int uv_thread_join(uv_thread_t* tid)
{
__bun_throw_not_implemented("uv_thread_join");
__builtin_unreachable();
}

UV_EXTERN uv_thread_t uv_thread_self(void)
{
__bun_throw_not_implemented("uv_thread_self");
__builtin_unreachable();
}

UV_EXTERN int uv_thread_setaffinity(uv_thread_t* tid,
char* cpumask,
char* oldmask,
Expand Down
11 changes: 11 additions & 0 deletions test/napi/napi-app/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -232,5 +232,16 @@
"NAPI_VERSION_EXPERIMENTAL=1",
],
},
{
"target_name": "uv_thread_addon",
"sources": ["uv_thread_addon.c"],
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
"libraries": [],
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
"defines": [
"NAPI_DISABLE_CPP_EXCEPTIONS",
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
],
},
]
}
70 changes: 70 additions & 0 deletions test/napi/napi-app/uv_thread_addon.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Regression test for https://github.com/oven-sh/bun/issues/29260:
// NAPI modules (e.g. ffi-napi) call uv_thread_self at module init. This
// addon exercises uv_thread_self + uv_thread_equal + uv_thread_create +
// uv_thread_join + uv_thread_detach + uv_thread_create_ex. If any of them
// is not polyfilled, Bun panics with "unsupported uv function".

#include <node_api.h>
#include <stdio.h>
#include <uv.h>

static void thread_entry(void *arg) {
int *counter = (int *)arg;
*counter = 42;
}

static napi_value fail(napi_env env, const char *msg) {
napi_throw_error(env, NULL, msg);
return NULL;
}

NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
// uv_thread_self + uv_thread_equal: self must equal self.
uv_thread_t self1 = uv_thread_self();
uv_thread_t self2 = uv_thread_self();
if (!uv_thread_equal(&self1, &self2)) {
return fail(env, "uv_thread_equal(self, self) returned false");
}

// uv_thread_create + uv_thread_join: spawn, join, verify the thread ran.
int counter = 0;
uv_thread_t tid;
if (uv_thread_create(&tid, thread_entry, &counter) != 0) {
return fail(env, "uv_thread_create failed");
}
if (uv_thread_join(&tid) != 0) {
return fail(env, "uv_thread_join failed");
}
if (counter != 42) {
return fail(env, "uv_thread_create: thread did not run");
}

// uv_thread_create_ex: no flags → default pthread stack size.
counter = 0;
uv_thread_options_t opts;
opts.flags = UV_THREAD_NO_FLAGS;
if (uv_thread_create_ex(&tid, &opts, thread_entry, &counter) != 0) {
return fail(env, "uv_thread_create_ex failed");
}
if (uv_thread_join(&tid) != 0) {
return fail(env, "uv_thread_join (after _ex) failed");
}
if (counter != 42) {
return fail(env, "uv_thread_create_ex: thread did not run");
}

// uv_thread_detach: spawn, detach, the thread cleans up on its own.
counter = 0;
if (uv_thread_create(&tid, thread_entry, &counter) != 0) {
return fail(env, "uv_thread_create (detach) failed");
}
if (uv_thread_detach(&tid) != 0) {
return fail(env, "uv_thread_detach failed");
}

napi_value result;
if (napi_get_boolean(env, true, &result) != napi_ok) {
return fail(env, "napi_get_boolean failed");
}
return result;
}
63 changes: 60 additions & 3 deletions test/napi/uv-stub-stuff/good_plugin.c
Original file line number Diff line number Diff line change
@@ -1,17 +1,74 @@
#include <node_api.h>
#include <sys/types.h>

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <uv.h>

typedef pid_t uv_pid_t;
uv_pid_t uv_os_getpid();
static void thread_entry(void *arg) {
int *counter = (int *)arg;
*counter = 42;
}

napi_value Init(napi_env env, napi_value exports) {
uv_pid_t pid = uv_os_getpid();
printf("%d\n", pid);

// uv_thread_self + uv_thread_equal: self must equal self.
uv_thread_t self1 = uv_thread_self();
uv_thread_t self2 = uv_thread_self();
if (!uv_thread_equal(&self1, &self2)) {
printf("FAIL: uv_thread_equal(self, self)\n");
return NULL;
}

// uv_thread_create + uv_thread_join: spawn a thread, join, verify it ran.
int counter = 0;
uv_thread_t tid;
if (uv_thread_create(&tid, thread_entry, &counter) != 0) {
printf("FAIL: uv_thread_create\n");
return NULL;
}
if (uv_thread_join(&tid) != 0) {
printf("FAIL: uv_thread_join\n");
return NULL;
}
if (counter != 42) {
printf("FAIL: thread did not run (counter=%d)\n", counter);
return NULL;
}

// uv_thread_create_ex: with a custom stack size.
counter = 0;
uv_thread_options_t opts;
opts.flags = UV_THREAD_HAS_STACK_SIZE;
opts.stack_size = 128 * 1024;
if (uv_thread_create_ex(&tid, &opts, thread_entry, &counter) != 0) {
printf("FAIL: uv_thread_create_ex\n");
return NULL;
}
if (uv_thread_join(&tid) != 0) {
printf("FAIL: uv_thread_join (ex)\n");
return NULL;
}
if (counter != 42) {
printf("FAIL: uv_thread_create_ex thread did not run (counter=%d)\n",
counter);
return NULL;
}

// uv_thread_detach: spawn, detach, let it run to completion on its own.
counter = 0;
if (uv_thread_create(&tid, thread_entry, &counter) != 0) {
printf("FAIL: uv_thread_create (detach)\n");
return NULL;
}
if (uv_thread_detach(&tid) != 0) {
printf("FAIL: uv_thread_detach\n");
return NULL;
}

printf("THREAD_OK\n");
return NULL;
}

Expand Down
40 changes: 0 additions & 40 deletions test/napi/uv-stub-stuff/plugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -2040,33 +2040,6 @@ napi_value call_uv_func(napi_env env, napi_callback_info info) {
return NULL;
}

if (strcmp(buffer, "uv_thread_create") == 0) {
uv_thread_t *arg0 = {0};
uv_thread_cb arg1 = NULL;
void *arg2 = {0};

uv_thread_create(arg0, arg1, arg2);
return NULL;
}

if (strcmp(buffer, "uv_thread_create_ex") == 0) {
uv_thread_t *arg0 = {0};
const uv_thread_options_t *arg1 = {0};
uv_thread_cb arg2 = NULL;
void *arg3 = {0};

uv_thread_create_ex(arg0, arg1, arg2, arg3);
return NULL;
}

if (strcmp(buffer, "uv_thread_equal") == 0) {
const uv_thread_t *arg0 = {0};
const uv_thread_t *arg1 = {0};

uv_thread_equal(arg0, arg1);
return NULL;
}

if (strcmp(buffer, "uv_thread_getaffinity") == 0) {
uv_thread_t *arg0 = {0};
char *arg1 = {0};
Expand All @@ -2082,19 +2055,6 @@ napi_value call_uv_func(napi_env env, napi_callback_info info) {
return NULL;
}

if (strcmp(buffer, "uv_thread_join") == 0) {
uv_thread_t *arg0 = {0};

uv_thread_join(arg0);
return NULL;
}

if (strcmp(buffer, "uv_thread_self") == 0) {

uv_thread_self();
return NULL;
}

if (strcmp(buffer, "uv_thread_setaffinity") == 0) {
uv_thread_t *arg0 = {0};
char *arg1 = {0};
Expand Down
6 changes: 5 additions & 1 deletion test/napi/uv_stub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,13 @@
});
}

test("should not crash when calling supported uv functions", async () => {
const { stdout, exitCode } = await Bun.$`${bunExe()} run nocrash.ts`.cwd(tempdir).throws(false).quiet();
expect(exitCode).toBe(0);
expect(stdout.toString()).toContain("HI!");
const out = stdout.toString();
expect(out).toContain("HI!");
// good_plugin.c exercises uv_thread_self/_equal/_join/_detach/_create/_create_ex
// — regression for #29260 (uv_thread_self panic loading ffi-napi).
expect(out).toContain("THREAD_OK");
});

Check warning on line 97 in test/napi/uv_stub.test.ts

View check run for this annotation

Claude / Claude Code Review

exitCode asserted before stdout assertions

In the 'should not crash when calling supported uv functions' test, `expect(exitCode).toBe(0)` is checked before the stdout assertions, violating the CLAUDE.md rule that stdout assertions must precede exit-code assertions. Move `expect(exitCode).toBe(0)` to after both `expect(out).toContain("HI\!")` and `expect(out).toContain("THREAD_OK")` so that a failure shows what stdout contained instead of just reporting a non-zero exit code.
});
Loading
Loading