Skip to content

napi: polyfill uv_thread_* against pthread#29261

Open
robobun wants to merge 6 commits intomainfrom
farm/c3c42f78/uv-thread-polyfills
Open

napi: polyfill uv_thread_* against pthread#29261
robobun wants to merge 6 commits intomainfrom
farm/c3c42f78/uv-thread-polyfills

Conversation

@robobun
Copy link
Copy Markdown
Collaborator

@robobun robobun commented Apr 13, 2026

What

Polyfill the POSIX libuv thread primitives that NAPI modules (notably ffi-napi) call during module init. They currently hit the uv-posix-stubs.c "unsupported uv function" panic before users require() even returns.

Polyfilled (all trivial pthread wrappers):

  • uv_thread_self
  • uv_thread_equal
  • uv_thread_join
  • uv_thread_detach
  • uv_thread_create
  • uv_thread_create_ex

Placed alongside the existing uv_mutex_* / uv_once / uv_os_getpid polyfills in src/bun.js/bindings/uv-posix-polyfills.c.

Why this works

uv_thread_t is typedef pthread_t on POSIX (vendor/libuv/include/uv/unix.h:130), and libuvs own src/unix/thread.c implementations are 1:1 pthread calls. We just copy them into the polyfills file and remove the corresponding stubs from the generated uv-posix-stubs.c.

uv_thread_create_ex honors UV_THREAD_HAS_STACK_SIZE but omits libuvs page-rounding / minimum-stack clamping — simpler, and pthread_attr_setstacksize errors abort(), matching the other primitives already in the file.

Scope

This fixes the specific crash in #29260 (the one the reporters stack trace points at). ffi-napi still hits a separate wall later — it also uses uv_async_init + uv_queue_work + uv_default_loop, which require a real Bun-integrated libuv event loop. That wider surface tracks in #18546.

Reproduction

// test/repro.mjs
import ffi from "ffi-napi";

Before: panic: unsupported uv function: uv_thread_self (crash during require).
After: require proceeds past the thread-init path; ffi-napi fails later on uv_async_init, which is part of #18546.

Test

test/regression/issue/29260.test.ts — spawns a NAPI addon (test/napi/napi-app/uv_thread_addon.c) whose NAPI_MODULE_INIT calls every polyfilled symbol and returns true only if they all succeed.

Gate verified locally:

  • USE_SYSTEM_BUN=1 bun test test/regression/issue/29260.test.tsfail (panic uv_thread_self)
  • bun bd test test/regression/issue/29260.test.tspass

Closes #29260.

NAPI modules like ffi-napi call uv_thread_self and friends during module
init. They hit the uv-posix-stubs.c "unsupported uv function" panic
before the users require() even returned.

The uv_thread_* family maps 1:1 onto pthread on POSIX (uv_thread_t is
typedef pthread_t, per vendor/libuv/include/uv/unix.h:130), so they can
be polyfilled directly. Matches the existing uv_mutex_* / uv_once /
uv_os_getpid pattern in uv-posix-polyfills.c.

Polyfilled:
  uv_thread_self, uv_thread_equal, uv_thread_join, uv_thread_detach,
  uv_thread_create, uv_thread_create_ex

Note: uv_thread_create_ex honors UV_THREAD_HAS_STACK_SIZE but omits
libuvs page-rounding / minimum-stack clamping. pthread_attr_setstacksize
errors propagate via abort(), matching other thread primitives already
in this file.

ffi-napi still crashes later - it also calls uv_async_init and the
libuv event-loop surface - but uv_thread_self is no longer the blocker.
Broader libuv support tracks in #18546.

Test: test/regression/issue/29260.test.ts spins up a NAPI addon
(test/napi/napi-app/uv_thread_addon.c) whose init exercises every
polyfilled symbol and returns true only if all succeed. Gate verified:
passes with the fix, panics uv_thread_self without it.
@robobun
Copy link
Copy Markdown
Collaborator Author

robobun commented Apr 13, 2026

Updated 10:13 AM PT - Apr 13th, 2026

@robobun, your commit 8509520 has 10 failures in Build #45515 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 29261

That installs a local version of the PR into your bun-29261 executable, so you can run:

bun-29261 --bun

@github-actions
Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Crash: ffi-napi call triggers unsupported libuv function (uv_thread_self) and aborts Bun #29223 - Duplicate report of the same uv_thread_self panic when loading ffi-napi, directly fixed by these thread polyfills

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #29223

Generated with Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Implement uv_thread_self on POSIX #29228 - Implements uv_thread_self on POSIX via pthread_self(), a direct subset of this PR's six uv_thread_* polyfills
  2. napi: implement simple libuv functions on posix #28900 - Polyfills uv_thread_self, uv_thread_join, and uv_thread_equal (among other libuv functions), overlapping three of the six thread functions in this PR

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds real POSIX pthread-based implementations for several libuv thread APIs, removes their stubbed not-implemented variants and generated export entries, and introduces/updates N-API tests and build targets to validate the new thread APIs and prevent runtime panics.

Changes

Cohort / File(s) Summary
POSIX thread polyfills
src/bun.js/bindings/uv-posix-polyfills.c
Added exported pthread-based implementations: uv_thread_self, uv_thread_equal, uv_thread_join, uv_thread_detach, uv_thread_create_ex, and uv_thread_create. Implements stack-size calculation, conditional pthread_attr use, error mapping via UV__ERR, and related includes.
Removed stubs & generated constants
src/bun.js/bindings/uv-posix-stubs.c, src/bun.js/bindings/libuv/generate_uv_posix_stubs_constants.ts
Removed stub implementations that previously threw for thread APIs from uv-posix-stubs.c. Updated generate_uv_posix_stubs_constants.ts to stop treating those thread APIs as generated symbol constants (entries commented/marked as defined in uv-posix-polyfills.c).
N-API test build target
test/napi/napi-app/binding.gyp
Added uv_thread_addon build target that compiles uv_thread_addon.c with the same include/define settings as existing tests.
N-API addon & plugin tests
test/napi/napi-app/uv_thread_addon.c, test/napi/uv-stub-stuff/good_plugin.c, test/napi/uv-stub-stuff/plugin.c
Added uv_thread_addon.c N-API module exercising thread APIs on init. Updated good_plugin.c to include <uv.h> and run thread lifecycle checks. Removed string-dispatch cases for the previously stubbed uv_thread_* functions from plugin.c.
Test updates & regression
test/napi/uv_stub.test.ts, test/regression/issue/29260.test.ts
Updated test to assert THREAD_OK in stdout. Added regression test issue/29260.test.ts (non-Windows) that builds the addon if missing and verifies requiring the built N-API module returns successfully ("true" and exit code 0).
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding POSIX polyfills for libuv thread functions using pthread implementations.
Description check ✅ Passed The description provides comprehensive coverage with 'What' and 'Why this works' sections explaining the polyfills, plus scope clarification, reproduction steps, and test verification—exceeding the template requirements.
Linked Issues check ✅ Passed The PR fully addresses issue #29260 by implementing all six required uv_thread_* polyfills (uv_thread_self, uv_thread_equal, uv_thread_join, uv_thread_detach, uv_thread_create, uv_thread_create_ex) to prevent the panic during ffi-napi module initialization.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the uv_thread_* polyfills and their tests; no unrelated modifications or scope creep detected beyond the stated objective of fixing #29260.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/napi/napi-app/uv_thread_addon.c`:
- Around line 56-63: The detached thread currently writes to the stack-local
variable counter via thread_entry after NAPI_MODULE_INIT may return, causing a
use-after-return; modify the detach test so the detached thread does not access
a stack-local: either make counter static (e.g. static int counter) or allocate
the counter on the heap and pass its pointer, or change thread_entry to a no-op
that doesn't touch the passed data; update the uv_thread_create/uv_thread_detach
invocation to pass the chosen safe storage and ensure any heap allocation is
freed by the thread if used.

In `@test/napi/uv_stub.test.ts`:
- Around line 89-97: In the test "should not crash when calling supported uv
functions" swap the order of the assertions so you assert on stdout (the
variable stdout / out and expect(out).toContain(...)) before asserting on
exitCode (expect(exitCode).toBe(0)); locate the test block and move the exitCode
assertion after the stdout/assertions (including the two
expect(out).toContain(...) checks) so failures report stdout content first for
better diagnostics.

In `@test/napi/uv-stub-stuff/good_plugin.c`:
- Around line 60-69: The detach test currently passes a stack-local variable
`counter` to the detached thread (`uv_thread_create` -> `thread_entry`) which
risks a use-after-return if `Init` returns before the thread finishes; fix by
either making the counter heap/static-scoped or by turning the detached thread
into a no-op: allocate `counter` with malloc (and have the thread free it) or
call `uv_thread_create(&tid, thread_entry, NULL)` and update `thread_entry` to
handle a NULL argument (ignoring any counter) so `uv_thread_detach` is still
exercised without referencing stack memory.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f4c4cb3b-8f63-412a-8e68-6439ccedf81f

📥 Commits

Reviewing files that changed from the base of the PR and between d35774f and 4447621.

📒 Files selected for processing (9)
  • src/bun.js/bindings/libuv/generate_uv_posix_stubs_constants.ts
  • src/bun.js/bindings/uv-posix-polyfills.c
  • src/bun.js/bindings/uv-posix-stubs.c
  • test/napi/napi-app/binding.gyp
  • test/napi/napi-app/uv_thread_addon.c
  • test/napi/uv-stub-stuff/good_plugin.c
  • test/napi/uv-stub-stuff/plugin.c
  • test/napi/uv_stub.test.ts
  • test/regression/issue/29260.test.ts
💤 Files with no reviewable changes (2)
  • test/napi/uv-stub-stuff/plugin.c
  • src/bun.js/bindings/uv-posix-stubs.c

Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — clean pthread wrappers following the existing polyfill pattern, with the detach-test use-after-stack nit already flagged inline.

Extended reasoning...

Overview

The PR adds 6 libuv thread primitives (, , , , , ) as thin pthread wrappers in , removes the corresponding panic stubs from and the stub generator constants, and adds regression tests in and .

Security risks

None. These are trivial wrappers around standard POSIX pthread calls, copy-pasted from libuv's own . They don't touch auth, crypto, IPC, or any security boundary.

Level of scrutiny

Low. The implementation follows the exact same pattern as the already-present and polyfills in the same file. is on POSIX, so the mapping is 1:1. The one deliberate simplification ( omits page-rounding/clamping of the stack size) is documented in a comment and is acceptable for this use case.

Other factors

The only issue flagged by the bug hunter is a use-after-stack in the detach sub-tests in and — both files pass a pointer to a local to a detached thread without joining. This is formally undefined behavior in test-only code. In practice the race window is a single store instruction wide and has negligible impact. It is already captured as an inline comment on the PR. The production polyfill code is not affected.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/regression/issue/29260.test.ts`:
- Around line 61-63: Remove the negative panic-string assertion by deleting the
expect(stderr).not.toContain("unsupported uv function"); line in the test (leave
expect(stdout).toContain("boolean"); intact); this eliminates the discouraged
check for absence of panic-like text and relies on existing assertions that
already detect crashes/non-zero exits and successful initialization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4e401b70-50f2-45f2-b79d-fe13c7b8e2eb

📥 Commits

Reviewing files that changed from the base of the PR and between 4447621 and 7b7e6ad.

📒 Files selected for processing (1)
  • test/regression/issue/29260.test.ts

- uv_thread_addon.c + good_plugin.c: use a dedicated no-op thread_entry
  for the detach sub-test. The previous version passed a stack-local
  counter to the detached thread, which can be a use-after-return if
  the caller returns before the thread scheduler starts the new thread
  (flagged by coderabbit/claude bots).

- good_plugin.c: also drop the 128KB explicit stack_size from the _ex
  test. Under ASAN the minimum viable stack is much larger, and the
  no-flags path still exercises uv_thread_create_ex end-to-end.

- 29260.test.ts: drop the expect(stderr).not.toContain("unsupported uv
  function") assertion. CLAUDE.md forbids negative-panic-string checks
  (they pass vacuously on different failure modes). expect(stdout
  .toContain("boolean")) + exitCode already cover every regression
  path that matters.

- uv_stub.test.ts: reorder assertions so stdout checks come before
  exitCode, per CLAUDE.md.

- 29260.test.ts: bump per-test timeout to 30s. The 5s default races
  the first NAPI module load under ASAN.
…ne stale skip

- uv-posix-polyfills.c: add uv__min_stack_size / uv__default_stack_size /
  uv__thread_stack_size helpers and the full page-rounding + min-clamp
  logic from libuv src/unix/thread.c. With these in place, the existing
  abort() on pthread_attr_setstacksize failure is safe — every value
  passed to setstacksize is guaranteed page-aligned and >= PTHREAD_STACK_MIN,
  matching libuvs own behavior byte-for-byte. Includes adjusted to add
  sys/resource.h for getrlimit and limits.h for PTHREAD_STACK_MIN.

- 29260.test.ts: drop the { timeout: 30_000 } from test(). CLAUDE.md
  forbids per-test timeouts; the slow node-gyp build is already
  covered by the beforeAll timeout and the test body itself finishes
  in milliseconds.

- generate_uv_posix_stubs_constants.ts: remove stale uv_thread_detach
  from test_skipped. That symbol is no longer in the generated symbols
  list (it is polyfilled), so the skip entry is dead code and masks a
  latent regression if the symbol is ever re-added.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/regression/issue/29260.test.ts`:
- Around line 45-66: The test currently runs bun with "-p typeof
require(addonPath)" which only checks the type and would pass if the addon
returned false; change the spawned command so it prints the addon's actual
return value (e.g., evaluate and print require(addonPath) instead of typeof
require(...)) and update assertions to assert the addon returned true (check
stdout for "true" or assert stdout === "true") in addition to exitCode === 0;
locate the Bun.spawn call that uses bunExe(), addonPath and napiAppDir and
modify the cmd and the expect() checks accordingly.
- Around line 1-12: Trim the file header in test/regression/issue/29260.test.ts
to a single-line GitHub issue URL comment and remove the long preamble; keep
only minimal inline comments necessary to explain the test structure or why
certain assertions exist. Specifically, replace the multi-line comment block
describing bug history with the standard one-line
"https://github.com/oven-sh/bun/issues/29260" and move any essential rationale
into short inline comments next to the test code (no extra narrative at file
top).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e1f415c9-e055-485b-8def-bd8d7079207b

📥 Commits

Reviewing files that changed from the base of the PR and between f7d0afc and 5985ac9.

📒 Files selected for processing (3)
  • src/bun.js/bindings/libuv/generate_uv_posix_stubs_constants.ts
  • src/bun.js/bindings/uv-posix-polyfills.c
  • test/regression/issue/29260.test.ts

- Drop the multi-line bug preamble per test/regression/issue/* convention;
  keep only the standard one-line issue URL.
- Change the spawned command from -p typeof require(...) to -p require(...)
  and assert stdout.trim() === "true". The old assertion would pass if the
  addon ever returned false (both print "boolean").
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 13, 2026

No merge conflicts detected when merging into main.

Your branch is good to go!

Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all previously raised issues have been addressed.

Extended reasoning...

Overview

This PR adds POSIX pthread-backed polyfills for six libuv thread APIs (uv_thread_self, uv_thread_equal, uv_thread_join, uv_thread_detach, uv_thread_create, uv_thread_create_ex) in uv-posix-polyfills.c, removing the panic stubs from uv-posix-stubs.c. It also adds NAPI test fixtures (uv_thread_addon.c, updates to good_plugin.c and uv_stub.test.ts) and a regression test for issue #29260.

Security risks

No security-sensitive code paths (auth, crypto, permissions) are touched. The implementation copies libuv stack-size logic verbatim, ensuring pthread_attr_setstacksize is only called with page-aligned values ≥ PTHREAD_STACK_MIN. No injection or data-exposure concerns.

Level of scrutiny

Moderate: the core implementation in uv-posix-polyfills.c is a faithful copy of upstream libuv src/unix/thread.c with no novel logic. The test changes are mechanical. Multiple rounds of automated review caught and fixed real issues (stack use-after-return, assertion ordering, abort() on invalid sizes, test timeout, stale skip entry, negative panic assertion) — all resolved in commits f7d0afc and 5985ac9.

Other factors

The one remaining nit (potential integer overflow in the page-rounding expression when stack_size is near SIZE_MAX) is a pre-existing upstream libuv limitation, not introduced by this PR, and is already captured as an inline comment. No outstanding unaddressed reviewer concerns remain.

Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all review feedback addressed, implementation faithfully mirrors libuv thread.c.

Extended reasoning...

Overview

Adds pthread-based polyfills for six uv_thread_* functions in uv-posix-polyfills.c, removes the corresponding panic stubs from uv-posix-stubs.c, and adds a new NAPI addon (uv_thread_addon.c) plus a regression test (test/regression/issue/29260.test.ts).

Security risks

No security-sensitive code paths. The polyfills are thin pthread wrappers with no privilege escalation or data exposure risk. The uv_thread_create_ex stack-size path applies page-rounding and minimum-stack clamping before calling pthread_attr_setstacksize, matching libuv byte-for-byte.

Level of scrutiny

Moderate: NAPI/C code changes are not trivial, but the approach is conservative — copy-paste from libuv own src/unix/thread.c rather than a novel implementation. Multiple rounds of bot review caught and fixed real issues (use-after-stack in detach test, wrong typeof-vs-value assertion, stale test_skipped entry, test timeout guideline violation, negative panic assertion). All are resolved.

Other factors

All inline comments are resolved. The implementation follows the same pattern as the existing uv_mutex_* / uv_once polyfills in the same file. uv_thread_t is typedef pthread_t on POSIX so the mapping is 1:1 with no semantic gap. No bugs were found by the bug hunting system on the final revision.

test/regression/issue/29260.test.ts trips the ASAN validateExceptionChecks
path through Process_functionDlopen → BunProcess.cpp:379 (thrown from
JSObject.cpp:835), the same pre-existing throw-scope issue already
handled for other NAPI tests (test/napi/uv.test.ts, uv_stub.test.ts).
Add the regression test under the existing "3rd party napi" section.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bun crashes when loading ffi-napi due to missing libuv support (uv_thread_self)

1 participant