Skip to content

diagnostics_channel: publish tracing:module.require and tracing:module.import - #34212

Open
robobun wants to merge 5 commits into
mainfrom
farm/486f58c7/diagnostics-channel-module-tracing
Open

diagnostics_channel: publish tracing:module.require and tracing:module.import#34212
robobun wants to merge 5 commits into
mainfrom
farm/486f58c7/diagnostics-channel-module-tracing

Conversation

@robobun

@robobun robobun commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Node.js publishes on the built-in TracingChannels tracing:module.require:{start,end,error} around every CJS require() and tracing:module.import:{start,end,asyncStart,asyncEnd,error} around every dynamic import(). Instrumentation loaders (OpenTelemetry, APM module patchers) subscribe to these to hook module loads. Bun never published on any of the eight channels.

Repro

import dc from 'node:diagnostics_channel';
import { createRequire } from 'node:module';
import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path';

const NAMES = [
  'tracing:module.require:start', 'tracing:module.require:end', 'tracing:module.require:error',
  'tracing:module.import:start', 'tracing:module.import:end',
  'tracing:module.import:asyncStart', 'tracing:module.import:asyncEnd', 'tracing:module.import:error',
];
const hits = new Map();
for (const n of NAMES) dc.subscribe(n, () => hits.set(n, (hits.get(n) ?? 0) + 1));

const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dch-mod-'));
fs.writeFileSync(path.join(dir, 'a.cjs'), 'module.exports=1;\n');
fs.writeFileSync(path.join(dir, 'b.mjs'), 'export default 2;\n');
const req = createRequire(import.meta.url);
req(path.join(dir, 'a.cjs'));
try { req(path.join(dir, 'missing.cjs')); } catch {}
await import(path.join(dir, 'b.mjs'));
try { await import(path.join(dir, 'missing.mjs')); } catch {}

for (const n of NAMES) console.log(`${hits.get(n) ? ' ok' : 'BUG'} ${n}: x${hits.get(n) ?? 0}`);

Node v26: all eight channels fire (require start/end x2, error x1; import start/end/asyncStart/asyncEnd x2, error x1). Bun before this change fires zero.

Fix

require(): the bound require() wrapper in src/js/builtins/CommonJS.ts now consults a lazily-created tracingChannel('module.require') (cached in the new internal/module_tracing module) and runs the load through its start/end/error channels when subscribed, matching Node's wrapModuleLoad. The context is {id, parentFilename} with result/error added. The unsubscribed fast path adds only a cached internal-module field read plus a hasSubscribers getter call.

import(): GlobalObject::moduleLoaderImportModule is native C++ with no JS layer. Subscribing to any tracing:module.* channel now flips a one-way hasModuleTracingSubscribers de-opt flag on the global (set from markActive() via a new host function, same pattern as hasOverriddenModuleResolveFilenameFunction). When set, each return site routes its result promise through a JS traceDynamicImport helper which wraps it with tracePromise, matching Node's ModuleLoader#import. The context is {parentURL, url}. No subscriber means one extra boolean read per import().

Also adds the TracingChannel.prototype.hasSubscribers getter (Node v22.0.0), which the module loader uses to gate the slow path. This overlaps with #33449; whichever lands second just drops the duplicate getter.

Verification

bun bd test test/js/node/diagnostics_channel/diagnostics_channel.test.ts covers the full event sequence for successful and failing require()/import() (all eight channels, context shape, result/error propagation), the identity between the :end context's result and the require() return value, the unsubscribed path, and the new TracingChannel.hasSubscribers getter. All new tests fail on the release build and pass with the fix. Verified clean under BUN_JSC_validateExceptionChecks=1.


[review] gate passed · iteration 2 · 10 files touched

fails on main (without fix)
ASAN without fix: BUILD FAILED (no junit output)
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/diagnostics_channel/diagnostics_channel.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
ninja: Entering directory `/workspace/bun/build/debug'
[1/160] gen cpp.rs (cppbind)
[2/160] gen JS modules (bundle-modules)
Preprocess modules (7580ms)
Bundle modules (33ms)
Postprocesss modules (28ms)
Bundle Functions (740ms)
Generate Code (8ms)

[8.41s] Bundled "src/js" for development
  2072 kb
  163 internal modules
  12 native modules
  90 internal functions across 19 files
[2/160] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-05-06-x86_64-unknown-linux-gnu unchanged - rustc 1.97.0-nightly (e95e73209 2026-05-05)

[4/160] pch pch/root-pch.h.hxx.pch
[5/160] cxx obj/unified/UnifiedSource-packages_bun_usockets_src_crypto-0.cpp.o
[6/160] cxx obj/unified/UnifiedSource-src_jsc_bindings-0
... (truncated)

release without fix: 3 skipped
bun test v1.4.0-canary.1 (5907d47d8)

test/js/node/diagnostics_channel/diagnostics_channel.test.ts:
(pass) Channel > can have subscribers [0.23ms]
(pass) Channel > can have symbol as name [0.25ms]
(pass) Channel > does not throw when unsubscribed [0.16ms]
(pass) Channel > can publish and subscribe [0.21ms]
(pass) Channel > can publish and subscribe using object [0.14ms]
(todo) Channel > can handle subscriber errors
(todo) Channel > can use bind store
(pass) Channel > references are not leaked [3.85ms]
(todo) TracingChannel > TODO
(pass) TracingChannel > hasSubscribers reflects sub-channel state [0.12ms]
(pass) module tracing channels > tracing:module.require result matches require() return value [25.35ms]
(pass) module tracing channels > require() still works when nothing is subscribed [25.19ms]
(pass) module tracing channels > tracing:module.require publishes on every require() [29.29ms]
(pass) module tracing channels > tracing:module.import publishes on every dynamic import() [28.04ms]

 11 pass
 3 todo
 0 fail
 49 expect() calls
Ran 14 tests across 1 file. [183.00ms]
__F:0:S:3
passes on PR (with fix)
ASAN with fix: 3 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/diagnostics_channel/diagnostics_channel.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (bdbd15181)

test/js/node/diagnostics_channel/diagnostics_channel.test.ts:
(pass) Channel > can have subscribers [15.47ms]
(pass) Channel > can have symbol as name [14.31ms]
(pass) Channel > does not throw when unsubscribed [10.32ms]
(pass) Channel > can publish and subscribe [14.65ms]
(pass) Channel > can publish and subscribe using object [12.27ms]
(todo) Channel > can handle subscriber errors
(todo) Channel > can use bind store
(pass) Channel > references are not leaked [321.99ms]
(todo) TracingChannel > TODO
(pass) TracingChannel > hasSubscribers reflects sub-channel state [6.89ms]
(pass) module tracing channels > tracing:module.require result matches require() return value [1359.01ms]
(pass) module tra
... (truncated)

release with fix: 3 skipped
$ bun scripts/build.ts --profile=release
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
[configured] bun-profile → bun (stripped) in 718ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/119] gen cpp.rs (cppbind)
[2/119] gen JS modules (bundle-modules)
Preprocess modules (6690ms)
Bundle modules (36ms)
Postprocesss modules (20ms)
Bundle Functions (650ms)
Generate Code (115ms)

[7.53s] Bundled "src/js" for production
  1913 kb
  163 internal modules
  12 native modules
  90 internal functions across 19 files
[2/119] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: component rust-std is up to date

  nightly-2026-05-06-x86_64-unknown-linux-gnu unchanged - rustc 1.97.0-nightly (e95e73209 2026-05-05)

info: checking 
... (truncated)
diff hotspot
src/js/builtins/CommonJS.ts                        |  26 ++-
 src/js/internal/module_tracing.ts                  |  15 ++
 src/js/node/diagnostics_channel.ts                 |  17 ++
 src/jsc/bindings/NodeDiagnosticsChannel.cpp        |  19 ++
 src/jsc/bindings/NodeDiagnosticsChannel.h          |   9 +
 src/jsc/bindings/ZigGlobalObject.cpp               |  64 +++++-
 src/jsc/bindings/ZigGlobalObject.h                 |   4 +
 .../diagnostics_channel.test.ts                    | 221 ++++++++++++++++++++-
 .../test/js-native-api/test_function/test.js       |  13 +-
 .../test/js-native-api/test_instance_data/test.js  |   9 +-
 10 files changed, 384 insertions(+), 13 deletions(-)

gate history · 2 passed · 1 rejected · iteration 2

evidence per changed file
file                                                      reads  edits  tests
src/js/builtins/CommonJS.ts                                   2      3      0
src/js/internal/module_tracing.ts                             0      1      0
src/js/node/diagnostics_channel.ts                            2      6      0
src/jsc/bindings/NodeDiagnosticsChannel.cpp                   0      1      0
src/jsc/bindings/NodeDiagnosticsChannel.h                     1      2      0
src/jsc/bindings/ZigGlobalObject.cpp                          6     12      0
src/jsc/bindings/ZigGlobalObject.h                            2      3      0
…js/node/diagnostics_channel/diagnostics_channel.test.ts      2      8      0
…ode-napi-tests/test/js-native-api/test_function/test.js      1      1      0
…api-tests/test/js-native-api/test_instance_data/test.js      1      1      0

…e.import

Node.js publishes on the built-in TracingChannels tracing:module.require:*
around every CJS require() (context {id, parentFilename}) and
tracing:module.import:* around every dynamic import() (context {parentURL,
url}). Instrumentation loaders (OpenTelemetry, APM module patchers) subscribe
to these to hook module loads. Bun never published on any of the eight
channels.

For require(): the bound require() wrapper in builtins/CommonJS.ts now
consults a lazily-created tracingChannel('module.require') and runs the load
through traceSync when subscribed, matching Node's wrapModuleLoad. The
unsubscribed fast path adds only a cached internal-module read plus a
hasSubscribers getter call.

For dynamic import(): moduleLoaderImportModule is native, so subscribing to
any tracing:module.* channel now flips a one-way de-opt flag on the global
(set from markActive() via a new host function). When set, the hook routes
its result promise through a JS traceDynamicImport helper which wraps it with
tracePromise. No subscriber means one extra boolean read.

Also adds the TracingChannel.prototype.hasSubscribers getter (Node v22.0.0),
which the module loader uses to gate the slow path.
@robobun

robobun commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 12:57 AM PT - Jul 15th, 2026

@robobun, your commit bdbd151 has 2 failures in Build #73197 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 34212

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

bun-34212 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. tracingChannel().hasSubscribers is undefined #27805 - PR adds TracingChannel.prototype.hasSubscribers getter, which is exactly what this issue reports as missing/undefined

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

Fixes #27805

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. node:diagnostics_channel: sync with Node 26 + subsystem channels #32628 - Full Node 26 diagnostics_channel sync that implements the same tracing:module.require and tracing:module.import channels, plus hasSubscribers, as a superset of this PR
  2. diagnostics_channel: add hasSubscribers getter to TracingChannel #33449 - Adds the same TracingChannel.prototype.hasSubscribers getter implemented in this PR
  3. fix: add hasSubscribers property to TracingChannel #27881 - Older PR also adding the same TracingChannel.prototype.hasSubscribers property

🤖 Generated with Claude Code

@robobun

robobun commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

#32628 is a draft superset of this: it implements module.require and module.import tracing (same src/js/internal/module_tracing.ts filename, so these hard-conflict) as part of a full Node 26 diagnostics_channel sync with many more channels. Its approach also avoids loading node:diagnostics_channel from require() until something has actually subscribed, and wraps overridableRequire so module.require() is covered too.

This PR is the standalone slice (just the two module channels, plus hasSubscribers) in case that's useful to land ahead of the larger sync. Happy to close in favor of #32628 if preferred.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

require() and dynamic import() now integrate with module tracing diagnostics channels. Subscriber activation connects JavaScript channels to native global state, while tests cover event sequences, payloads, results, errors, and non-traced execution.

Module tracing

Layer / File(s) Summary
Tracing channels and subscriber state
src/js/internal/module_tracing.ts, src/js/node/diagnostics_channel.ts
Defines module require/import tracing channels and exposes aggregate subscriber state.
Native subscriber activation
src/js/node/diagnostics_channel.ts, src/jsc/bindings/NodeDiagnosticsChannel.*, src/jsc/bindings/ZigGlobalObject.h
Activating a tracing:module.* channel invokes a native callback and sets the global module-tracing flag.
CommonJS require tracing
src/js/builtins/CommonJS.ts
Publishes start, error, and completion events around require(), while retaining the direct path without subscribers.
Dynamic import tracing
src/jsc/bindings/ZigGlobalObject.*
Resolves the internal import tracer and routes successful or rejected dynamic-import promises through it when tracing is enabled.
Tracing validation
test/js/node/diagnostics_channel/diagnostics_channel.test.ts
Tests subscriber state, event ordering, payloads, results, errors, and unsubscribed require() behavior.

N-API garbage-collection test reliability

Layer / File(s) Summary
Scoped object-lifetime tests
test/napi/node-napi-tests/test/js-native-api/test_function/test.js, test/napi/node-napi-tests/test/js-native-api/test_instance_data/test.js
Scopes tracked objects in IIFEs and repeats garbage collection for finalizer-related assertions.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding tracing channel publication for module require/import.
Description check ✅ Passed It covers what the PR does and how it was verified, though it uses custom headings instead of the template.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/jsc/bindings/ZigGlobalObject.cpp (1)

3572-3579: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Route NodeVM imports through the tracer before returning.

NodeVM::importModule returns its promise directly, while its exception path exits through RETURN_IF_EXCEPTION. These imports therefore emit no start/end/async*/error events despite tracing being active. Convert any pending exception to a rejected promise and pass every non-null result through traceDynamicImport.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/jsc/bindings/ZigGlobalObject.cpp` around lines 3572 - 3579, Update the
NodeVM::importModule handling in the needsTracing flow to convert any pending
exception into a rejected promise instead of returning through
RETURN_IF_EXCEPTION, then pass every non-null result to traceDynamicImport
before returning it. Preserve the existing direct-return behavior when tracing
is not active.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/js/node/diagnostics_channel.ts`:
- Around line 68-69: In the ActiveChannel activation logic, replace the userland
`channel.name.startsWith` call with the primordial
`StringPrototypeStartsWith.$call` using the same channel name and prefix.
Preserve the existing conditional and module-tracing initialization behavior
while ensuring the prefix check cannot invoke mutable userland code.

In `@test/js/node/diagnostics_channel/diagnostics_channel.test.ts`:
- Around line 412-414: Reorder assertions in the affected subprocess test blocks
around the stdout parsing and event expectations: validate parsed stdout and its
expected result first, then assert stderr, and check exitCode last. Apply this
consistently to the blocks near the existing Promise.all assertions, including
the additionally referenced cases.

---

Outside diff comments:
In `@src/jsc/bindings/ZigGlobalObject.cpp`:
- Around line 3572-3579: Update the NodeVM::importModule handling in the
needsTracing flow to convert any pending exception into a rejected promise
instead of returning through RETURN_IF_EXCEPTION, then pass every non-null
result to traceDynamicImport before returning it. Preserve the existing
direct-return behavior when tracing is not active.
🪄 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: 6b759b5d-93bf-4a2b-a33e-92b37e984ff2

📥 Commits

Reviewing files that changed from the base of the PR and between 6b2c888 and 1742c30.

📒 Files selected for processing (8)
  • src/js/builtins/CommonJS.ts
  • src/js/internal/module_tracing.ts
  • src/js/node/diagnostics_channel.ts
  • src/jsc/bindings/NodeDiagnosticsChannel.cpp
  • src/jsc/bindings/NodeDiagnosticsChannel.h
  • src/jsc/bindings/ZigGlobalObject.cpp
  • src/jsc/bindings/ZigGlobalObject.h
  • test/js/node/diagnostics_channel/diagnostics_channel.test.ts

Comment thread src/js/node/diagnostics_channel.ts Outdated
Comment thread test/js/node/diagnostics_channel/diagnostics_channel.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/js/node/diagnostics_channel/diagnostics_channel.test.ts (1)

348-361: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise every subchannel and overlapping subscriptions.

Checking only asyncEnd and error independently would miss a getter that omits start, end, or asyncStart, or incorrectly becomes false when one of several active subscriptions is removed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/js/node/diagnostics_channel/diagnostics_channel.test.ts` around lines
348 - 361, Expand the “hasSubscribers reflects sub-channel state” test to cover
subscriptions on every tracing subchannel: start, end, asyncStart, asyncEnd, and
error. Add overlapping-subscription assertions so hasSubscribers remains true
while any subscription is still active, including after unsubscribing one of
multiple subscribed handlers, and returns false only after all are removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/js/node/diagnostics_channel/diagnostics_channel.test.ts`:
- Around line 469-522: Update the expected payloads in the diagnostics channel
test to assert the Node shape: use the emitted `id` field instead of `url`, and
expect `parentURL` as a URL-valued value rather than a plain string. Apply this
consistently to every `tracing:module.import:*` event in the expectation while
preserving the existing event ordering and result/error flags.

---

Outside diff comments:
In `@test/js/node/diagnostics_channel/diagnostics_channel.test.ts`:
- Around line 348-361: Expand the “hasSubscribers reflects sub-channel state”
test to cover subscriptions on every tracing subchannel: start, end, asyncStart,
asyncEnd, and error. Add overlapping-subscription assertions so hasSubscribers
remains true while any subscription is still active, including after
unsubscribing one of multiple subscribed handlers, and returns false only after
all are removed.
🪄 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: 052c30d7-9395-4cde-851a-fe5652ebcbaf

📥 Commits

Reviewing files that changed from the base of the PR and between 1742c30 and 9142a5b.

📒 Files selected for processing (2)
  • src/js/node/diagnostics_channel.ts
  • test/js/node/diagnostics_channel/diagnostics_channel.test.ts

Comment thread test/js/node/diagnostics_channel/diagnostics_channel.test.ts
Comment thread src/js/node/diagnostics_channel.ts Outdated
robobun and others added 2 commits July 15, 2026 05:28
The require() wrapper added in this PR changes the CJS module load call depth,
which on release-ASAN layouts can leave a stale pointer to the finalizer target
in an unscrubbed stack slot that JSC's conservative stack scan keeps alive past
a single gc(). Create the target inside an IIFE so its frame is gone before
gc() runs, and retry gc() a bounded number of times, matching the pattern
already used by test_finalizer/test.js for the same reason.

Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
@robobun

robobun commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

CI at bdbd151 (build #73197): the two napi finalizer tests that failed in the previous build now pass. test/js/node/diagnostics_channel/diagnostics_channel.test.ts is green everywhere it ran. Remaining failures are all unrelated to this diff:

test lane tag note
test-worker-message-port-transfer-terminate.js x64-asan pre-existing SIGABRT in JSValue::get, known on main
es-module-lexer.test.ts Windows x64 flaky 90s timeout
test-repl-close.js Windows x64-baseline flaky EPIPE
no-orphans.test.ts darwin aarch64 flaky perl reaper timeout
require-cache.test.ts darwin aarch64 flaky 74 MB vs 64 MB threshold on import() loop (my import() path adds only a bool read when unsubscribed)
serve-direct-readable-stream.test.ts darwin aarch64 flaky port collision with local Verdaccio registry
bun-serve-routes.test.ts darwin x64 flaky port collision (404s)

Ready for review.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No bugs found, and the earlier primordial-startsWith nit is addressed in 9142a5b — but this touches the per-call require() builtin wrapper and the native moduleLoaderImportModule hook plus new ZigGlobalObject state, so it's worth a human look for the hot-path cost and the #32628 overlap decision.

Checked: exception-scope handling in the new traceDynamicImport C++ helper and its three call sites (rejected-promise paths covered); the LazyProperty initializer for m_traceDynamicImportFunction (visited via FOR_EACH_GLOBALOBJECT_GC_MEMBER); the require() fast path still forwards arguments unchanged when unsubscribed; hasSubscribers getter matches Node's five-channel OR. The NAPI test IIFE/gc-retry changes look like standard conservative-stack-scan hardening.

Extended reasoning...

Overview

This PR wires Bun's CJS require() and dynamic import() into Node's tracing:module.require / tracing:module.import diagnostics channels. It touches: the require() builtin wrapper in src/js/builtins/CommonJS.ts (runs on every require), GlobalObject::moduleLoaderImportModule in ZigGlobalObject.cpp (native dynamic-import hook), a new LazyProperty<JSFunction> and bool de-opt flag on ZigGlobalObject, a new C++ host function, a new internal/module_tracing JS module, and the TracingChannel.prototype.hasSubscribers getter. It also bundles unrelated NAPI test hardening (IIFE-scoping finalizer targets + gc retry loop) in bdbd151.

Security risks

None identified. The change is observability-only; the new code paths run user-provided subscriber callbacks, but that is the existing diagnostics_channel contract and errors are already caught by ActiveChannel.publish. No new external input parsing, auth, or filesystem/permission surface.

Level of scrutiny

High. src/js/builtins/CommonJS.ts:require() is bound to every CommonJSModule instance and runs on every require() call — the unsubscribed path now adds an internal-module-registry lookup plus a getter call per invocation. moduleLoaderImportModule is the sole native entry point for dynamic import() and the new code adds a JS re-entry (traceDynamicImport) at three return sites with fresh throw scopes. ZigGlobalObject.h is explicitly flagged in CLAUDE.md as a place to avoid new fields. These are not mechanical changes; a maintainer should sign off on the per-require overhead being acceptable and confirm the C++ exception-scope discipline.

Other factors

  • The author explicitly flags overlap with draft #32628 (a superset that also covers module.require via overridableRequire so Module.prototype.require is traced) and #33449 (hasSubscribers), and asks whether to close in favor of the larger PR — that's a maintainer call, not something to auto-approve past.
  • My earlier inline nit (primordial startsWith) was addressed; the current markActive uses StringPrototypeStartsWith.$call.
  • Tests are thorough (event ordering, context shape, result identity, error paths, unsubscribed path) and were verified to fail on release / pass with fix under ASAN and BUN_JSC_validateExceptionChecks=1.
  • The bundled NAPI test edits are logically separate from the diagnostics_channel change; they look correct but widen the PR's scope.

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.

1 participant