From e5089fc1fd5b896cad5de6e97fa0a76ca2eae7f5 Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Wed, 11 Feb 2026 11:34:17 -0800 Subject: [PATCH] try to find a default port with runtime inspector in tests? --- src/bun.js.zig | 2 ++ src/bun.js/VirtualMachine.zig | 15 ++++++++++- src/bun.js/event_loop/RuntimeInspector.zig | 10 +++---- src/cli.zig | 2 ++ src/cli/Arguments.zig | 2 ++ src/cli/test_command.zig | 1 + .../runtime-inspector-windows.test.ts | 8 +++--- .../runtime-inspector.test.ts | 26 +++++++++---------- 8 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/bun.js.zig b/src/bun.js.zig index b0a382fa21..d21d014201 100644 --- a/src/bun.js.zig +++ b/src/bun.js.zig @@ -39,6 +39,7 @@ pub const Run = struct { .debugger = ctx.runtime_options.debugger, .dns_result_order = DNSResolver.Order.fromStringOrDie(ctx.runtime_options.dns_result_order), .disable_sigusr1 = ctx.runtime_options.disable_sigusr1, + .inspect_port = ctx.runtime_options.inspect_port, }), .arena = arena, .ctx = ctx, @@ -188,6 +189,7 @@ pub const Run = struct { .dns_result_order = DNSResolver.Order.fromStringOrDie(ctx.runtime_options.dns_result_order), .is_main_thread = true, .disable_sigusr1 = ctx.runtime_options.disable_sigusr1, + .inspect_port = ctx.runtime_options.inspect_port, }, ), .arena = arena, diff --git a/src/bun.js/VirtualMachine.zig b/src/bun.js/VirtualMachine.zig index 4b7d607ac1..d933ab8107 100644 --- a/src/bun.js/VirtualMachine.zig +++ b/src/bun.js/VirtualMachine.zig @@ -164,6 +164,9 @@ hot_reload_counter: u32 = 0, debugger: ?jsc.Debugger = null, has_started_debugger: bool = false, +/// Pre-configured inspector port for runtime activation (via --inspect-port). +/// Used by RuntimeInspector when SIGUSR1/process._debugProcess activates the inspector. +inspect_port: ?[]const u8 = null, has_terminated: bool = false, debug_thread_id: if (Environment.allow_assert) std.Thread.Id else void, @@ -1083,6 +1086,7 @@ pub fn initWithModuleGraph( uws.Loop.get().internal_loop_data.jsc_vm = vm.jsc_vm; vm.configureDebugger(opts.debugger); + vm.inspect_port = opts.inspect_port; vm.body_value_hive_allocator = Body.Value.HiveAllocator.init(bun.typedAllocator(jsc.WebCore.Body.Value)); configureSigusr1Handler(vm, opts); @@ -1115,6 +1119,8 @@ pub const Options = struct { destruct_main_thread_on_exit: bool = false, /// Disable SIGUSR1 handler for runtime debugger activation (matches Node.js). disable_sigusr1: bool = false, + /// Pre-configured inspector port for runtime activation (--inspect-port). + inspect_port: ?[]const u8 = null, }; /// Configure SIGUSR1 handling for runtime debugger activation (main thread only). @@ -1280,8 +1286,15 @@ fn configureDebugger(this: *VirtualMachine, cli_flag: bun.cli.Command.Debugger) } }, .enable => { + // If --inspect/--inspect-brk/--inspect-wait is used without an explicit port, + // use --inspect-port if provided. + const path_or_port = if (cli_flag.enable.path_or_port.len == 0) + this.inspect_port orelse cli_flag.enable.path_or_port + else + cli_flag.enable.path_or_port; + this.debugger = .{ - .path_or_port = cli_flag.enable.path_or_port, + .path_or_port = path_or_port, .from_environment_variable = unix, .wait_for_connection = if (cli_flag.enable.wait_for_connection) .forever else wait_for_connection, .set_breakpoint_on_first_line = set_breakpoint_on_first_line or cli_flag.enable.set_breakpoint_on_first_line, diff --git a/src/bun.js/event_loop/RuntimeInspector.zig b/src/bun.js/event_loop/RuntimeInspector.zig index 9eac5cf958..49efe36219 100644 --- a/src/bun.js/event_loop/RuntimeInspector.zig +++ b/src/bun.js/event_loop/RuntimeInspector.zig @@ -28,11 +28,9 @@ const RuntimeInspector = @This(); const log = Output.scoped(.RuntimeInspector, .hidden); /// Default port for runtime-activated inspector (via SIGUSR1/process._debugProcess). -/// Note: If this port is already in use, activation will fail with an error message. -/// This matches Node.js behavior where SIGUSR1-activated inspectors also use a fixed -/// port (9229). Users can pre-configure a different port using --inspect-port= -/// or --inspect=0 for automatic port selection when starting the process. -const inspector_port = "6499"; +/// If the user pre-configured a port via --inspect-port=, that port is used +/// instead. Use --inspect-port=0 for automatic port selection. +const default_inspector_port = "6499"; var installed: std.atomic.Value(bool) = std.atomic.Value(bool).init(false); var inspector_activation_requested: std.atomic.Value(bool) = std.atomic.Value(bool).init(false); @@ -104,7 +102,7 @@ fn activateInspector(vm: *VirtualMachine) !void { log("Activating inspector", .{}); vm.debugger = .{ - .path_or_port = inspector_port, + .path_or_port = vm.inspect_port orelse default_inspector_port, .from_environment_variable = "", .wait_for_connection = .off, .set_breakpoint_on_first_line = false, diff --git a/src/cli.zig b/src/cli.zig index 54abcc894f..59c6ddadbd 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -405,6 +405,8 @@ pub const Command = struct { } = .{}, /// Disable SIGUSR1 handler for runtime debugger activation disable_sigusr1: bool = false, + /// Pre-configure inspector port for runtime activation (SIGUSR1/process._debugProcess) + inspect_port: ?[]const u8 = null, }; var global_cli_ctx: Context = undefined; diff --git a/src/cli/Arguments.zig b/src/cli/Arguments.zig index e30345438e..392a5a2599 100644 --- a/src/cli/Arguments.zig +++ b/src/cli/Arguments.zig @@ -87,6 +87,7 @@ pub const runtime_params_ = [_]ParamType{ clap.parseParam("--inspect ? Activate Bun's debugger") catch unreachable, clap.parseParam("--inspect-wait ? Activate Bun's debugger, wait for a connection before executing") catch unreachable, clap.parseParam("--inspect-brk ? Activate Bun's debugger, set breakpoint on first line of code and wait") catch unreachable, + clap.parseParam("--inspect-port Set inspector port for runtime debugger activation (0 for random)") catch unreachable, clap.parseParam("--disable-sigusr1 Disable SIGUSR1 handler for runtime debugger activation") catch unreachable, clap.parseParam("--cpu-prof Start CPU profiler and write profile to disk on exit") catch unreachable, clap.parseParam("--cpu-prof-name Specify the name of the CPU profile file") catch unreachable, @@ -811,6 +812,7 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C ctx.runtime_options.preconnect = args.options("--fetch-preconnect"); ctx.runtime_options.expose_gc = args.flag("--expose-gc"); ctx.runtime_options.disable_sigusr1 = args.flag("--disable-sigusr1"); + ctx.runtime_options.inspect_port = args.option("--inspect-port"); if (args.option("--console-depth")) |depth_str| { const depth = std.fmt.parseInt(u16, depth_str, 10) catch { diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index fae5b88455..efc351fbe8 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -1399,6 +1399,7 @@ pub const TestCommand = struct { .debugger = ctx.runtime_options.debugger, .is_main_thread = true, .disable_sigusr1 = ctx.runtime_options.disable_sigusr1, + .inspect_port = ctx.runtime_options.inspect_port, }, ); vm.argv = ctx.passthrough; diff --git a/test/js/bun/runtime-inspector/runtime-inspector-windows.test.ts b/test/js/bun/runtime-inspector/runtime-inspector-windows.test.ts index 51ba01584b..bbdd1e4caf 100644 --- a/test/js/bun/runtime-inspector/runtime-inspector-windows.test.ts +++ b/test/js/bun/runtime-inspector/runtime-inspector-windows.test.ts @@ -223,9 +223,9 @@ describe.skipIf(!isWindows)("Runtime inspector Windows file mapping", () => { }); test("multiple Windows processes can have inspectors sequentially", async () => { - // Note: Runtime inspector uses hardcoded port 6499, so we must test - // sequential activation (activate first, shut down, then activate second) - // rather than concurrent activation. + // Test sequential activation: activate first, shut down, then activate second. + // Each process uses a random port, so concurrent would also work, but + // sequential tests the full lifecycle. using dir = tempDir("windows-multi-test", { "target.js": ` const fs = require("fs"); @@ -284,7 +284,7 @@ describe.skipIf(!isWindows)("Runtime inspector Windows file mapping", () => { await target1.exited; } - // Second process: now that first is shut down, port 6499 is free + // Second process { await using target2 = spawn({ cmd: [bunExe(), "target.js", "2"], diff --git a/test/js/bun/runtime-inspector/runtime-inspector.test.ts b/test/js/bun/runtime-inspector/runtime-inspector.test.ts index 5e3643bad1..50b33d7792 100644 --- a/test/js/bun/runtime-inspector/runtime-inspector.test.ts +++ b/test/js/bun/runtime-inspector/runtime-inspector.test.ts @@ -24,9 +24,9 @@ async function waitForDebuggerListening( // The banner format is: // --------------------- Bun Inspector --------------------- // Listening: - // ws://localhost:6499/... + // ws://localhost:/... // Inspect in browser: - // https://debug.bun.sh/#localhost:6499/... + // https://debug.bun.sh/#localhost:/... // --------------------- Bun Inspector --------------------- try { while ((stderr.match(/Bun Inspector/g) || []).length < 2) { @@ -54,7 +54,7 @@ describe("Runtime inspector activation", () => { test.skipIf(skipASAN)("activates inspector in target process", async () => { // Start target process - prints PID to stdout then stays alive await using targetProc = spawn({ - cmd: [bunExe(), "-e", `console.log(process.pid); setInterval(() => {}, 1000);`], + cmd: [bunExe(), "--inspect-port=0", "-e", `console.log(process.pid); setInterval(() => {}, 1000);`], env: bunEnv, stdout: "pipe", stderr: "pipe", @@ -108,7 +108,7 @@ describe("Runtime inspector activation", () => { test.skipIf(skipASAN)("inspector does not activate twice", async () => { // Start target process - prints PID to stdout then stays alive await using targetProc = spawn({ - cmd: [bunExe(), "-e", `console.log(process.pid); setInterval(() => {}, 1000);`], + cmd: [bunExe(), "--inspect-port=0", "-e", `console.log(process.pid); setInterval(() => {}, 1000);`], env: bunEnv, stdout: "pipe", stderr: "pipe", @@ -170,15 +170,15 @@ describe("Runtime inspector activation", () => { }); test.skipIf(skipASAN)("can activate inspector in multiple processes sequentially", async () => { - // Note: Runtime inspector uses hardcoded port 6499, so we must test - // sequential activation (activate first, shut down, then activate second) - // rather than concurrent activation. + // Test sequential activation: activate first, shut down, then activate second. + // Each process uses a random port, so concurrent would also work, but + // sequential tests the full lifecycle. const targetScript = `console.log(process.pid); setInterval(() => {}, 1000);`; // First process: activate inspector, verify, then shut down { await using target1 = spawn({ - cmd: [bunExe(), "-e", targetScript], + cmd: [bunExe(), "--inspect-port=0", "-e", targetScript], env: bunEnv, stdout: "pipe", stderr: "pipe", @@ -209,10 +209,10 @@ describe("Runtime inspector activation", () => { await target1.exited; } - // Second process: now that first is shut down, port 6499 is free + // Second process { await using target2 = spawn({ - cmd: [bunExe(), "-e", targetScript], + cmd: [bunExe(), "--inspect-port=0", "-e", targetScript], env: bunEnv, stdout: "pipe", stderr: "pipe", @@ -260,7 +260,7 @@ describe("Runtime inspector activation", () => { test.skipIf(skipASAN)("can interrupt an infinite loop", async () => { // Start target process with infinite loop await using targetProc = spawn({ - cmd: [bunExe(), "-e", `console.log(process.pid); while (true) {}`], + cmd: [bunExe(), "--inspect-port=0", "-e", `console.log(process.pid); while (true) {}`], env: bunEnv, stdout: "pipe", stderr: "pipe", @@ -299,7 +299,7 @@ describe("Runtime inspector activation", () => { test.skipIf(skipASAN)("can pause execution during while(true) via CDP", async () => { // Start target process with infinite loop await using targetProc = spawn({ - cmd: [bunExe(), "-e", `console.log(process.pid); while (true) {}`], + cmd: [bunExe(), "--inspect-port=0", "-e", `console.log(process.pid); while (true) {}`], env: bunEnv, stdout: "pipe", stderr: "pipe", @@ -387,7 +387,7 @@ describe("Runtime inspector activation", () => { test.skipIf(skipASAN)("CDP messages work after client reconnects", async () => { // Start target process - prints PID to stdout then stays alive await using targetProc = spawn({ - cmd: [bunExe(), "-e", `console.log(process.pid); setInterval(() => {}, 1000);`], + cmd: [bunExe(), "--inspect-port=0", "-e", `console.log(process.pid); setInterval(() => {}, 1000);`], env: bunEnv, stdout: "pipe", stderr: "pipe",