Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
15 changes: 8 additions & 7 deletions src/bun.js/bindings/libuv/generate_uv_posix_stubs_constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export const test_skipped = [
"uv_getrusage_thread",
"uv_thread_detach",
"uv_thread_getname",
"uv_thread_getpriority",
"uv_thread_setname",
Expand Down Expand Up @@ -271,16 +270,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
151 changes: 150 additions & 1 deletion src/bun.js/bindings/uv-posix-polyfills.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

#if OS(LINUX) || OS(DARWIN)

#include <limits.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <unistd.h>

// libuv does the annoying thing of #undef'ing these
#include <errno.h>
Expand Down Expand Up @@ -138,4 +140,151 @@ 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): minimum stack size a thread
* may be created with. A new thread needs to allocate, among other things,
* a TLS block AND pthread's internal bookkeeping. The exact size is
* arch-dependent. */
static size_t uv__min_stack_size(void)
{
static const size_t min = 8192;

#ifdef PTHREAD_STACK_MIN /* Not defined on NetBSD. */
if (min < (size_t)PTHREAD_STACK_MIN)
return PTHREAD_STACK_MIN;
#endif

return min;
}

/* Copy-pasted from libuv (src/unix/thread.c): on Linux, threads created by
* musl have a much smaller stack than threads created by glibc (80 vs.
* 2048 or 4096 kB). Follow glibc for consistency. */
static size_t uv__default_stack_size(void)
{
#if !defined(__linux__)
return 0;
#elif defined(__PPC__) || defined(__ppc__) || defined(__powerpc__)
return 4 << 20; /* glibc default. */
#else
return 2 << 20; /* glibc default. */
#endif
}

/* Copy-pasted from libuv (src/unix/thread.c): on MacOS, threads other than
* the main thread are created with a reduced stack size by default. Adjust
* to RLIMIT_STACK aligned to the page size. */
static size_t uv__thread_stack_size(void)
{
#if defined(__APPLE__) || defined(__linux__)
struct rlimit lim;

/* getrlimit() can fail on some aarch64 systems due to a glibc bug
* where the system call wrapper invokes the wrong system call. Don't
* treat that as fatal, just use the default stack size instead. */
if (getrlimit(RLIMIT_STACK, &lim))
return uv__default_stack_size();

if (lim.rlim_cur == RLIM_INFINITY)
return uv__default_stack_size();

/* pthread_attr_setstacksize() expects page-aligned values. */
lim.rlim_cur -= lim.rlim_cur % (rlim_t)getpagesize();

if (lim.rlim_cur >= (rlim_t)uv__min_stack_size())
return lim.rlim_cur;
#endif

return uv__default_stack_size();
}

// Copy-pasted from libuv (src/unix/thread.c). The page-rounding and
// min-stack-size clamping is what makes the two abort() calls below safe:
// without them, pthread_attr_setstacksize could legitimately fail with
// EINVAL on a caller-supplied stack_size that is too small or not
// page-aligned, and abort() would be wrong.
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 pagesize;
size_t stack_size;
size_t min_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) {
stack_size = uv__thread_stack_size();
} else {
pagesize = (size_t)getpagesize();
/* Round up to the nearest page boundary. */
stack_size = (stack_size + pagesize - 1) & ~(pagesize - 1);
min_stack_size = uv__min_stack_size();
if (stack_size < min_stack_size)
stack_size = min_stack_size;
}

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",
],
},
]
}
73 changes: 73 additions & 0 deletions test/napi/napi-app/uv_thread_addon.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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;
}

// For the detach sub-test: the caller returns before this runs, so we
// must NOT touch anything from the caller's stack. No-op instead.
static void thread_entry_detach(void *arg) { (void)arg; }

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.
if (uv_thread_create(&tid, thread_entry_detach, NULL) != 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;
}
Loading
Loading