Skip to content

fix(v8): export node::EmitAsyncInit and related async hooks C++ API symbols#28132

Open
robobun wants to merge 1 commit intomainfrom
claude/fix-node-async-hooks-symbols-28131
Open

fix(v8): export node::EmitAsyncInit and related async hooks C++ API symbols#28132
robobun wants to merge 1 commit intomainfrom
claude/fix-node-async-hooks-symbols-28131

Conversation

@robobun
Copy link
Collaborator

@robobun robobun commented Mar 15, 2026

Summary

  • Add stub implementations for node::EmitAsyncInit, node::EmitAsyncDestroy, node::AsyncHooksGetExecutionAsyncId, and node::AsyncHooksGetTriggerAsyncId in Bun's V8 compatibility layer
  • Export the mangled symbols on all platforms (Linux, macOS, Windows)
  • Native addons compiled against Node.js headers (like libpq used by pg-native) reference these C++ API functions and fail to load without them

Closes #28131

Test plan

  • Added test_v8_async_hooks test in the V8 test module (test/v8/v8-module/main.cpp) that calls all new functions
  • Test passes with checkSameOutput (identical behavior under both Bun and Node.js)
  • Verified symbols are exported from bun-debug binary using nm -D
  • Full V8 test suite passes (only pre-existing test_handle_scope_gc failure unrelated to this change)

🤖 Generated with Claude Code

…ymbols

Native addons compiled against Node.js headers (like libpq used by pg-native)
reference node::EmitAsyncInit and related async hooks C++ API functions. Bun
was missing these symbols, causing ERR_DLOPEN_FAILED when loading such addons.

Add stub implementations for EmitAsyncInit, EmitAsyncDestroy,
AsyncHooksGetExecutionAsyncId, and AsyncHooksGetTriggerAsyncId in the V8
compatibility layer.

Closes #28131

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@robobun
Copy link
Collaborator Author

robobun commented Mar 15, 2026

Updated 1:36 AM PT - Mar 15th, 2026

❌ Your commit de9875bd has 2 failures in Build #39702 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 28132

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

bun-28132 --bun

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a0d008bf-5978-497c-8f19-8be81b415e11

📥 Commits

Reviewing files that changed from the base of the PR and between d50ab98 and de9875b.

📒 Files selected for processing (7)
  • src/bun.js/bindings/v8/node.cpp
  • src/bun.js/bindings/v8/node.h
  • src/symbols.def
  • src/symbols.dyn
  • src/symbols.txt
  • test/v8/v8-module/main.cpp
  • test/v8/v8.test.ts

Walkthrough

Introduces async hooks API support by adding five new functions to the node bindings: AsyncHooksGetExecutionAsyncId, AsyncHooksGetTriggerAsyncId, and overloaded EmitAsyncInit variants, along with EmitAsyncDestroy. Includes type definitions for async_id and async_context, exports these symbols across multiple symbol tables, and adds corresponding test coverage.

Changes

Cohort / File(s) Summary
Node Async Hooks API
src/bun.js/bindings/v8/node.h, src/bun.js/bindings/v8/node.cpp
Declares and implements async hooks API with new async_id type and async_context struct; adds five functions: AsyncHooksGetExecutionAsyncId, AsyncHooksGetTriggerAsyncId, two EmitAsyncInit overloads, and EmitAsyncDestroy.
Symbol Exports
src/symbols.def, src/symbols.dyn, src/symbols.txt
Exports five mangled symbols for the new async hooks functions across Windows (.def), dynamic (.dyn), and symbol list (.txt) files to expose them in the binary's public API.
Async Hooks Tests
test/v8/v8-module/main.cpp, test/v8/v8.test.ts
Adds C++ test function exercising all new async hooks APIs and corresponding TypeScript test case verifying the functions execute without crashes.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: exporting Node.js async hooks C++ API symbols for V8 compatibility.
Description check ✅ Passed The description covers what the PR does, how it was verified, and includes test plan details exceeding the minimal template requirements.
Linked Issues check ✅ Passed All coding requirements from issue #28131 are met: stub implementations added, symbols exported on all platforms, and test coverage provided.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing and exporting the required async hooks API symbols; no unrelated modifications present.

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

📝 Coding Plan
  • Generate coding plan for human review comments

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

@github-actions
Copy link
Contributor

Found 0 issues this PR may fix beyond #28131.

After searching through the repository for open issues related to:

  • ERR_DLOPEN_FAILED errors with native addons
  • Missing async hooks C++ API symbols
  • V8/Node.js C++ API compatibility gaps
  • PostgreSQL native driver issues
  • Symbol export and dynamic linking problems

No additional open issues were found that would be directly fixed by this PR's implementation of async hooks C++ API stub functions (EmitAsyncInit, EmitAsyncDestroy, AsyncHooksGetExecutionAsyncId, AsyncHooksGetTriggerAsyncId).

This PR specifically addresses issue #28131 as stated in the description.

🤖 Generated with Claude Code

Copy link
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 — straightforward stub implementations for missing V8 async hooks symbols, with proper symbol exports on all platforms and tests.

Extended reasoning...

Overview

This PR adds stub implementations for five node:: C++ API functions (AsyncHooksGetExecutionAsyncId, AsyncHooksGetTriggerAsyncId, two overloads of EmitAsyncInit, and EmitAsyncDestroy) to Bun's V8 compatibility layer. These are no-op stubs returning sentinel values (-1), matching the pattern of the existing AddEnvironmentCleanupHook/RemoveEnvironmentCleanupHook stubs. The mangled symbols are correctly added to all three platform-specific export files (symbols.txt for Linux, symbols.dyn for macOS, symbols.def for Windows). A test is added that exercises all new functions under both Bun and Node.js.

Security risks

None. The new functions are no-op stubs that return hardcoded values. They don't process user input, access the filesystem, or interact with the network. The async_context struct and async_id typedef are simple numeric types matching Node.js's definitions.

Level of scrutiny

This is a low-risk, mechanical change. The stub implementations follow the exact same pattern as existing stubs in the same file. The symbol export additions are straightforward name mangling entries. The test is a simple smoke test confirming the functions don't crash. This warrants minimal scrutiny.

Other factors

The one nit found (missing V8API entries in napi.zig) is a valid observation about internal consistency, but as the bug report itself acknowledges, the practical impact is negligible — the platform-specific export files are the primary mechanism ensuring symbol visibility, and the doNotOptimizeAway loop in napi.zig is a secondary belt-and-suspenders measure. The symbols will be exported correctly on all platforms without the napi.zig change. The PR includes a proper test that validates behavior matches Node.js, and it addresses a real user-facing issue (pg-native failing to load).

Comment on lines +39 to +68
async_id AsyncHooksGetExecutionAsyncId(v8::Isolate* isolate)
{
return -1;
}

async_id AsyncHooksGetTriggerAsyncId(v8::Isolate* isolate)
{
return -1;
}

async_context EmitAsyncInit(v8::Isolate* isolate,
v8::Local<v8::Object> resource,
const char* name,
async_id trigger_async_id)
{
return async_context { -1, -1 };
}

async_context EmitAsyncInit(v8::Isolate* isolate,
v8::Local<v8::Object> resource,
v8::Local<v8::String> name,
async_id trigger_async_id)
{
return async_context { -1, -1 };
}

void EmitAsyncDestroy(v8::Isolate* isolate,
async_context asyncContext)
{
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 Nit: The new async hooks functions (AsyncHooksGetExecutionAsyncId, AsyncHooksGetTriggerAsyncId, EmitAsyncInit x2, EmitAsyncDestroy) are added to the symbol export files but not to the V8API struct in src/napi/napi.zig. The existing node:: functions like AddEnvironmentCleanupHook (line 1839) and RemoveEnvironmentCleanupHook (line 1840) have entries in both Unix and Windows branches of this struct, and the project's own src/bun.js/bindings/v8/CLAUDE.md documents this as a required step. Adding the corresponding mangled symbol entries to the V8API struct would maintain consistency with the established pattern.

Extended reasoning...

What the bug is

The PR adds five new node:: C++ API functions (AsyncHooksGetExecutionAsyncId, AsyncHooksGetTriggerAsyncId, two overloads of EmitAsyncInit, and EmitAsyncDestroy) and correctly adds their mangled symbols to all three platform-specific export files (symbols.txt, symbols.dyn, symbols.def). However, it does not add corresponding entries to the V8API struct in src/napi/napi.zig.

The established pattern

The existing node::AddEnvironmentCleanupHook and node::RemoveEnvironmentCleanupHook functions follow a two-part pattern: their mangled symbols appear in (1) the platform export files AND (2) the V8API struct in napi.zig (lines 1839-1840 for Unix, lines 1926-1927 for Windows). The V8API struct is iterated at lines 2487-2488 with doNotOptimizeAway to prevent dead code elimination from stripping these symbols.

Documentation requirement

The project's own src/bun.js/bindings/v8/CLAUDE.md explicitly documents this as a required step (section 2a: "Add to src/napi/napi.zig") when implementing new V8/node C++ API functions.

Practical impact

In practice, the risk is low. The platform-specific symbol export files (symbols.txt for Linux, symbols.dyn for macOS, symbols.def for Windows) are the primary mechanism that ensures the linker retains these symbols. The doNotOptimizeAway loop in napi.zig is a secondary belt-and-suspenders mechanism. Since the symbols are correctly listed in all three export files and the functions are declared with BUN_EXPORT, they will almost certainly be exported correctly on all platforms.

Step-by-step proof

  1. src/napi/napi.zig line 1835 defines const V8API = if (!bun.Environment.isWindows) struct { ... } else struct { ... }
  2. In the Unix branch, lines 1839-1840 declare AddEnvironmentCleanupHook and RemoveEnvironmentCleanupHook with their Itanium-mangled names
  3. In the Windows branch, lines 1926-1927 declare the same functions with their MSVC-mangled names
  4. At lines 2487-2488, inline for (comptime std.meta.declarations(V8API)) |decl| { std.mem.doNotOptimizeAway(&@field(V8API, decl.name)); } ensures all V8API symbols are retained
  5. The five new async hooks symbols (e.g., _ZN4node29AsyncHooksGetExecutionAsyncIdEPN2v87IsolateE for Unix) have no entries in either branch of the V8API struct
  6. Searching napi.zig for AsyncHooks or EmitAsync returns zero matches, confirming the omission

How to fix

Add the five mangled symbol names to both the Unix and Windows branches of the V8API struct in src/napi/napi.zig, following the same pattern as the existing AddEnvironmentCleanupHook/RemoveEnvironmentCleanupHook entries. The Unix (Itanium) mangled names are in symbols.txt and the Windows (MSVC) mangled names are in symbols.def.

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.

pg-native - libpq/build/Release/addon.node: undefined symbol ERR_DLOPEN_FAILED

1 participant