try to find a default port with runtime inspector in tests?

This commit is contained in:
Alistair Smith
2026-02-11 11:34:17 -08:00
parent 0d1a98b5e0
commit e5089fc1fd
8 changed files with 42 additions and 24 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -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=<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=<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,

View File

@@ -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;

View File

@@ -87,6 +87,7 @@ pub const runtime_params_ = [_]ParamType{
clap.parseParam("--inspect <STR>? Activate Bun's debugger") catch unreachable,
clap.parseParam("--inspect-wait <STR>? Activate Bun's debugger, wait for a connection before executing") catch unreachable,
clap.parseParam("--inspect-brk <STR>? Activate Bun's debugger, set breakpoint on first line of code and wait") catch unreachable,
clap.parseParam("--inspect-port <STR> 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 <STR> 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 {

View File

@@ -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;

View File

@@ -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"],

View File

@@ -24,9 +24,9 @@ async function waitForDebuggerListening(
// The banner format is:
// --------------------- Bun Inspector ---------------------
// Listening:
// ws://localhost:6499/...
// ws://localhost:<port>/...
// Inspect in browser:
// https://debug.bun.sh/#localhost:6499/...
// https://debug.bun.sh/#localhost:<port>/...
// --------------------- 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",