Skip to content
Open
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
64a7c4e
node:diagnostics_channel: sync with Node 26 and add subsystem channels
cirospaciari Jun 23, 2026
dc25df3
require: publish the module.require diagnostics tracing channel
cirospaciari Jun 23, 2026
e6751a9
node:v8: implement queryObjects()
cirospaciari Jun 23, 2026
82b4d13
module loader: publish the module.import diagnostics tracing channel
cirospaciari Jun 23, 2026
4fbcc5c
Fix unchecked exception in the module.import wrapper
cirospaciari Jun 23, 2026
55bc058
net: create the diagnostics channels at module scope
cirospaciari Jun 23, 2026
e6ec58c
expectations: quarantine test-net-connect-memleak.js on musl runners
cirospaciari Jun 23, 2026
7e07ce4
expectations: quarantine two node-api suites on the ASAN runners
cirospaciari Jun 23, 2026
ee6a68d
napi tests: make test_function/test_instance_data robust under conser…
robobun Jun 23, 2026
4c17997
Merge remote-tracking branch 'origin/main' into claude/diagnostics-ch…
cirospaciari Jul 14, 2026
c0f91f7
diagnostics_channel: drop expectations entries and cover http.server.…
cirospaciari Jul 14, 2026
864835d
Remove stray empty file committed during the merge
cirospaciari Jul 14, 2026
39d39c1
worker_threads/child_process: create the diagnostics channels at modu…
cirospaciari Jul 14, 2026
9d460cf
Merge branch 'main' into claude/diagnostics-channel-node26
cirospaciari Jul 16, 2026
0baaa6d
Address review nits: fix always-true dc guard, capture DisposableStac…
robobun Jul 16, 2026
18bf408
diagnostics_channel: drop upstream TODO comments from tracePromise re…
robobun Jul 16, 2026
b3d66cd
http.server.* channels: decide upgrade before constructing the response
robobun Jul 16, 2026
075d209
moduleLoaderImportModule: root the captured module-name string with a…
robobun Jul 16, 2026
16ae013
NodeV8Module.h: add #pragma once and use root.h like sibling headers
robobun Jul 16, 2026
3134afa
Merge remote-tracking branch 'origin/main' into claude/diagnostics-ch…
robobun Jul 16, 2026
d28684e
Merge branch 'main' into claude/diagnostics-channel-node26
robobun Jul 16, 2026
6f4bd8b
ci: retrigger
robobun Jul 16, 2026
0247903
net.server.listen: publish asyncEnd before unref, strengthen upgrade …
robobun Jul 16, 2026
a79591c
http.server.response.created: publish from dispatch path, restore sho…
robobun Jul 16, 2026
4751648
Merge remote-tracking branch 'origin/main' into claude/diagnostics-ch…
robobun Jul 17, 2026
e25d142
cleanupAsyncHooksData: drain the nextTick queue when it exists
robobun Jul 17, 2026
76f7eed
test: match file convention for subprocess stderr assertion
robobun Jul 17, 2026
895dc9d
Merge branch 'main' into claude/diagnostics-channel-node26
cirospaciari Jul 17, 2026
b9d94a8
http.server.response.finish: attach a shared listener instead of per-…
robobun Jul 17, 2026
34febeb
napi harness: capture stdout in runAsync too
robobun Jul 17, 2026
fc583af
Merge branch 'main' into claude/diagnostics-channel-node26
cirospaciari Jul 19, 2026
cf1178b
test: widen mimalloc page-count bound for JSC heap noise, matching main
cirospaciari Jul 20, 2026
b885814
Merge remote-tracking branch 'origin/main' into claude/diagnostics-ch…
robobun Jul 20, 2026
152ef93
Merge remote-tracking branch 'origin/main' into claude/diagnostics-ch…
robobun Jul 21, 2026
ba6586a
test(async_hooks): give the four http/http2 frame-clear subprocess te…
robobun Jul 21, 2026
3fbc875
expectations: quarantine test-net-connect-memleak.js on linux-x64
robobun Jul 21, 2026
c47b0af
Merge remote-tracking branch 'origin/main' into claude/diagnostics-ch…
robobun Jul 23, 2026
6d4f0b2
test(async_hooks): document why the four frame-clear subprocess tests…
robobun Jul 23, 2026
c5313fb
diagnostics_channel: vendor the two http channel tests (+2) (#34641)
cirospaciari Jul 23, 2026
c732c17
Merge remote-tracking branch 'origin/main' into claude/diagnostics-ch…
robobun Aug 3, 2026
078e4bb
Address review: swap require() on subscribe, lazy dc load in subsyste…
robobun Aug 3, 2026
f4383c0
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 3, 2026
0ee0735
Address three nits: capture-once baseRequire, fix stale test comment
robobun Aug 3, 2026
63640b2
Remove stray untracked Rust files accidentally added in 0ee07357cd
robobun Aug 3, 2026
b879723
http.Server.listen() publishes on net.server.listen like Node
robobun Aug 4, 2026
a5311d4
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 4, 2026
43449e1
Merge remote-tracking branch 'origin/main' into claude/diagnostics-ch…
robobun Aug 4, 2026
1f00d24
ci: retrigger
robobun Aug 4, 2026
a1d5c11
Merge branch 'main' into claude/diagnostics-channel-node26
dylan-conway Aug 4, 2026
e1ed259
Merge branch 'main' into diagnostics-channel-node26
cirospaciari Aug 5, 2026
cc52550
tracingChannel(null): throw ERR_INVALID_ARG_TYPE like Node
robobun Aug 5, 2026
f17681e
Merge remote-tracking branch 'origin/main' into claude/diagnostics-ch…
robobun Aug 7, 2026
4eb58ac
v8.queryObjects: read options.format with ?? like Node
robobun Aug 7, 2026
1c8fc53
Revert "v8.queryObjects: read options.format with ?? like Node"
robobun Aug 7, 2026
3b8b93a
Merge branch 'main' into diagnostics-channel-node26
cirospaciari Aug 7, 2026
b7ac433
Trim comments to node-source/spec references
robobun Aug 7, 2026
0ea7f36
require: forward the two named params with $call instead of $apply(ar…
robobun Aug 7, 2026
6ef2005
Address review nits: 1-arg require fast path, stale expectations entr…
robobun Aug 8, 2026
416dd52
http1 fallback path publishes the http.server.* channels
robobun Aug 8, 2026
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
6 changes: 5 additions & 1 deletion src/js/builtins/CommonJS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ export function require(this: JSCommonJSModule, _: string) {
return $overridableRequire.$apply(this, arguments);
}

// overridableRequire can be overridden by setting `Module.prototype.require`
// overridableRequire can be overridden by setting `Module.prototype.require`.
// When the "module.require" diagnostics tracing channel gains subscribers,
// node:diagnostics_channel swaps this out for a tracing wrapper via that
// setter (see internal/module_tracing), so the no-subscriber path has no
// per-call overhead here.
$overriddenName = "require";
$visibility = "Private";
export function overridableRequire(this: JSCommonJSModule, originalId: string, options: { paths?: string[] } = {}) {
Expand Down
79 changes: 79 additions & 0 deletions src/js/internal/module_tracing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Wires the "module.require" / "module.import" diagnostics tracing channels
// into the CommonJS require path and dynamic import(). node:diagnostics_channel
// calls install() on load; nothing here runs until it does, and the loaders
// themselves carry no per-call check.

const setHasModuleImportSubscribers = $newCppFunction(
"NodeDiagnosticsChannel.cpp",
"jsSetHasModuleImportSubscribers",
1,
);

let requireChannel;
let importChannel;
// The unwrapped overridableRequire (captured on first install so a user's own
// Module.prototype.require override is what we wrap and restore).
let baseRequire;
let requireWrapped = false;

function tracingRequire(this: any, originalId: string, options?: { paths?: string[] }) {
if (requireChannel !== undefined && requireChannel.hasSubscribers) {
return requireChannel.traceSync(
baseRequire,
{ __proto__: null, parentFilename: this.filename, id: originalId },
this,
originalId,
options,
);
}
return baseRequire.$call(this, originalId, options);
}
Object.defineProperty(tracingRequire, "name", { value: "require" });

function onRequireSubscribersChanged() {
const has = requireChannel !== undefined && requireChannel.hasSubscribers;
if (has === requireWrapped) return;
const Module = require("node:module");
if (has) {
// Capture once: a user wrapper installed between subscribe cycles may
// delegate to tracingRequire, so re-capturing it here would recurse.
baseRequire ??= Module.prototype.require;
Module.prototype.require = tracingRequire;
requireWrapped = true;
} else {
// Only unwrap what we wrapped; a user override installed while tracing was
// active stays.
if (Module.prototype.require === tracingRequire) {
Module.prototype.require = baseRequire;
}
requireWrapped = false;
}
}
Comment thread
robobun marked this conversation as resolved.

function onImportSubscribersChanged() {
setHasModuleImportSubscribers(importChannel !== undefined && importChannel.hasSubscribers);
}

const moduleTracing = {
// Called from tryTraceModuleImport (C++) only when the import flag is set;
// `doImport` performs the dynamic import and returns its promise.
traceImport(doImport, parentURL, url) {
if (importChannel === undefined || !importChannel.hasSubscribers) {
return doImport();
}
return importChannel.tracePromise(doImport, { __proto__: null, parentURL, url });
},
// Called once from node:diagnostics_channel on load. Hooks every sub-channel
// so gaining/losing a subscriber on any of them re-evaluates the swap.
install(requireCh, importCh, hookSubscriberChange) {
requireChannel = requireCh;
importChannel = importCh;
hookSubscriberChange(requireCh, onRequireSubscribersChanged);
hookSubscriberChange(importCh, onImportSubscribersChanged);
// In case something subscribed before install() ran.
onRequireSubscribersChanged();
onImportSubscribersChanged();
},
};

export default moduleTracing;
66 changes: 66 additions & 0 deletions src/js/node/_http_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ const {
const { ConnResetException, hasObserver, startPerf, stopPerf } = require("internal/shared");
const kServerResponseStatistics = Symbol("ServerResponseStatistics");

let onServerRequestStartChannel, onServerResponseCreatedChannel, onServerResponseFinishChannel;
// In Node http.Server inherits listen() from net.Server, so http servers
// publish on the net.server.listen tracing channel too; Bun's http.Server is
// backed by Bun.serve and has its own listen(), so it publishes here directly.
let netServerListenChannel;
function initHttpServerChannels() {
const dc = require("node:diagnostics_channel");
onServerRequestStartChannel = dc.channel("http.server.request.start");
onServerResponseCreatedChannel = dc.channel("http.server.response.created");
onServerResponseFinishChannel = dc.channel("http.server.response.finish");
netServerListenChannel = dc.tracingChannel("net.server.listen");
}

const { isPrimary } = require("internal/cluster/isPrimary");
const {
throwOnInvalidTLSArray,
Expand Down Expand Up @@ -631,6 +644,14 @@ Server.prototype.listen = function () {
onListen = lastArg;
}

if (!netServerListenChannel) initHttpServerChannels();
if (netServerListenChannel.hasSubscribers) {
const arg0 = arguments[0];
const options =
typeof arg0 === "object" && arg0 !== null ? arg0 : socketPath != null ? { path: socketPath } : { port, host };
netServerListenChannel.asyncStart.publish({ server: this, options });
}

try {
// listenInCluster

Expand Down Expand Up @@ -677,6 +698,9 @@ Server.prototype.listen = function () {

server[kRealListen](tls, port, host, socketPath, true, onListen);
} catch (err) {
if (netServerListenChannel.hasSubscribers) {
netServerListenChannel.error.publish({ server: this, error: err });
}
setTimeout(() => server.emit("error", err), 1);
}

Expand Down Expand Up @@ -943,6 +967,31 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort
// 'upgrade' listener and false for a declined upgrade that falls
// through to 'request'.
http_req.upgrade = is_upgrade;

if (!is_upgrade) {
if (!onServerResponseCreatedChannel) initHttpServerChannels();
// Node publishes response.created from the ServerResponse constructor,
// which it only reaches after the upgrade check; Bun constructs the
// response unconditionally, so publish here instead.
if (onServerResponseCreatedChannel.hasSubscribers) {
onServerResponseCreatedChannel.publish({
request: http_req,
response: http_res,
});
}
if (onServerRequestStartChannel.hasSubscribers) {
onServerRequestStartChannel.publish({
request: http_req,
response: http_res,
socket,
server,
});
}
// Node's resOnFinish is always attached and checks hasSubscribers at
// 'finish' time, so a subscriber added mid-request still observes it.
http_res.on("finish", publishServerResponseFinish);
}
Comment thread
robobun marked this conversation as resolved.

if (isPipelined) {
// A previous response on this connection has not finished yet: like
// Node.js, this response is queued (res.socket === null) and its
Expand Down Expand Up @@ -1129,6 +1178,10 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort
isHTTPS = this[serverSymbol].protocol === "https";
applyServerCustomOptions(this);

if (netServerListenChannel.hasSubscribers) {
netServerListenChannel.asyncEnd.publish({ server: this });
}

if (this?._unref) {
this[serverSymbol]?.unref?.();
}
Expand Down Expand Up @@ -2158,6 +2211,19 @@ function _writeHead(statusCode, reason, obj, response) {

Object.defineProperty(NodeHTTPServerSocket, "name", { value: "Socket" });

function publishServerResponseFinish(this: any) {
if (!onServerResponseFinishChannel.hasSubscribers) return;
// Same socket lookup as emitResponseFinishHandleSocket (req.socket may be
// nulled by the stream destroyer; this.socket is the assignSocket fallback).
const socket = this.req?.socket ?? this.socket;
onServerResponseFinishChannel.publish({
request: this.req,
response: this,
socket,
server: socket?.server,
});
}

function ServerResponse(req, options): void {
if (!(this instanceof ServerResponse)) return new ServerResponse(req, options);
OutgoingMessage.$call(this, options);
Expand Down
31 changes: 31 additions & 0 deletions src/js/node/child_process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ var BufferIsEncoding = Buffer.isEncoding;
var kEmptyObject = ObjectCreate(null);
var signals = OsModule.constants.signals;

let childProcessChannel, childProcessSpawn;
function initChildProcessChannels() {
const dc = require("node:diagnostics_channel");
childProcessChannel = dc.channel("child_process");
childProcessSpawn = dc.tracingChannel("child_process.spawn");
}

var ArrayPrototypeJoin = Array.prototype.join;
var ArrayPrototypeIncludes = Array.prototype.includes;
var ArrayPrototypeSlice = Array.prototype.slice;
Expand Down Expand Up @@ -1107,6 +1114,16 @@ class ChildProcess extends EventEmitter {
channel;
killed = false;

constructor() {
super();
if (!childProcessChannel) initChildProcessChannels();
if (childProcessChannel.hasSubscribers) {
childProcessChannel.publish({
process: this,
});
}
}

[Symbol.dispose]() {
if (!this.killed) {
this.kill();
Expand Down Expand Up @@ -1401,6 +1418,10 @@ class ChildProcess extends EventEmitter {
// Bun.spawn() expects cmd[0] to be the command to run, and argv0 to replace the first arg when running the command,
// so we have to set argv0 to spawnargs[0] and cmd[0] to file

if (childProcessSpawn.hasSubscribers) {
childProcessSpawn.start.publish({ process: this, options });
}

try {
this.#handle = Bun.spawn({
cmd: [file, ...Array.prototype.slice.$call(spawnargs, 1)],
Expand Down Expand Up @@ -1441,6 +1462,10 @@ class ChildProcess extends EventEmitter {

$debug("ChildProcess: spawn", this.pid, spawnargs);

if (childProcessSpawn.hasSubscribers) {
childProcessSpawn.end.publish({ process: this });
}

process.nextTick(() => {
this.emit("spawn");
});
Expand Down Expand Up @@ -1478,6 +1503,9 @@ class ChildProcess extends EventEmitter {
this.#handle = null;
ex.syscall = "spawn " + this.spawnfile;
ex.spawnargs = Array.prototype.slice.$call(this.spawnargs, 1);
if (childProcessSpawn.hasSubscribers) {
childProcessSpawn.error.publish({ process: this, error: ex });
}
process.nextTick(() => {
this.emit("error", ex);
this.emit("close", (ex as SystemError).errno ?? -1);
Expand All @@ -1494,6 +1522,9 @@ class ChildProcess extends EventEmitter {
// synchronously, with `syscall: "spawn"` (no file appended).
ex.syscall = "spawn";
}
if (childProcessSpawn.hasSubscribers) {
childProcessSpawn.error.publish({ process: this, error: ex });
}
throw ex;
}
}
Expand Down
Loading