Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 0 additions & 9 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,6 @@ pub struct InitOptions {
/// reuses the caller's env loader.
pub env_loader: Option<NonNull<bun_dotenv::Loader>>,
pub graph: Option<&'static dyn bun_resolver::StandaloneModuleGraph>,
/// Must be applied to
/// `transpiler.resolver.store_fd` BEFORE `configure_linker()` reads
/// `top_level_dir`, so it threads through `init_runtime_state`.
pub store_fd: bool,
pub smol: bool,
pub eval_mode: bool,
pub is_main_thread: bool,
Expand All @@ -122,7 +118,6 @@ impl Default for InitOptions {
log: None,
env_loader: None,
graph: None,
store_fd: false,
smol: false,
eval_mode: false,
is_main_thread: false,
Expand Down Expand Up @@ -3382,7 +3377,6 @@ pub struct Options {
// BORROW_PARAM (`&'a mut bun_dotenv::Loader`) — caller-owned; the loader
// outlives the VM, so the inner lifetime is erased to `'static`.
pub env_loader: Option<NonNull<bun_dotenv::Loader>>,
pub store_fd: bool,
pub smol: bool,
// LAYERING: real type is `bun_runtime::dns_jsc::Order` (forward
// dep); stored as its `u8` repr.
Expand Down Expand Up @@ -4173,7 +4167,6 @@ impl VirtualMachine {
vm_ref.let_heap_take_initial_module_graph(graph);
// Avoid reading from tsconfig.json & package.json when in standalone mode
vm_ref.transpiler.configure_linker_with_auto_jsx(false);
vm_ref.transpiler.resolver.store_fd = false;
IS_SMOL_MODE.store(opts.smol, core::sync::atomic::Ordering::Relaxed);
Ok(vm)
}
Expand All @@ -4191,7 +4184,6 @@ impl VirtualMachine {
graph: opts.graph,
log: opts.log,
env_loader: opts.env_loader,
store_fd: opts.store_fd,
smol: opts.smol,
eval_mode: opts.eval,
is_main_thread: false,
Expand Down Expand Up @@ -4219,7 +4211,6 @@ impl VirtualMachine {
vm_ref.transpiler.resolver.standalone_module_graph = opts.graph;
vm_ref.hot_reload = worker.hot_reload();
vm_ref.initial_script_execution_context_identifier = worker.execution_context_id() as i32;
vm_ref.transpiler.resolver.store_fd = opts.store_fd;
if opts.graph.is_none() {
vm_ref.transpiler.configure_linker();
} else {
Expand Down
4 changes: 0 additions & 4 deletions src/jsc/web_worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ pub struct WebWorker {
/// Created by `node:worker_threads`' `Worker` (as opposed to the Web `Worker`
/// constructor): loads `node:worker_threads` before preloads and the entry point.
is_node_worker: bool,
store_fd: bool,
/// Borrowed from the proxy's `WorkerOptions` (alive as long as the proxy).
argv_ptr: *const WTFStringImpl,
argv_len: usize,
Expand Down Expand Up @@ -351,7 +350,6 @@ impl WebWorker {
// its own thread; the worker never dereferences `parent`.
// SAFETY: `parent` is the calling thread's live VM.
let parent_ref = unsafe { &*parent };
let store_fd = parent_ref.transpiler.resolver.store_fd;
let mut transform_options = (*parent_ref.transpiler.options.transform_options).clone();
if !inherit_exec_argv {
let hooks = runtime_hooks().expect("RuntimeHooks not installed");
Expand Down Expand Up @@ -406,7 +404,6 @@ impl WebWorker {
mini,
eval_mode,
is_node_worker,
store_fd,
argv_ptr,
argv_len,
exec_argv_ptr,
Expand Down Expand Up @@ -692,7 +689,6 @@ impl WebWorker {
virtual_machine::Options {
args: transform_options,
env_loader: NonNull::new(loader_ptr),
store_fd: self.store_fd,
graph: crate::virtual_machine::standalone_module_graph(),
..Default::default()
},
Expand Down
2 changes: 0 additions & 2 deletions src/runtime/cli/run_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -972,8 +972,6 @@ Full documentation is available at <magenta>https://bun.com/docs/cli/run<r>
// hand the CLI's vectors over wholesale (process-lifetime, never freed).
vm.preload = std::mem::take(&mut ctx.preloads);
vm.argv = std::mem::take(&mut ctx.passthrough);
// `InitOptions` has no `store_fd` field, so set it on the resolver directly.
vm.transpiler.resolver.store_fd = ctx.debug.hot_reload != cli::command::HotReload::None;
// `vm.dns_result_order` is a `u8` until the b2-cycle widens
// it to `bun_dns::Order`; the enum is `#[repr(u8)]` so `as u8` is exact.
vm.dns_result_order =
Expand Down
1 change: 0 additions & 1 deletion src/runtime/cli/test_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1925,7 +1925,6 @@ impl TestCommand {
debugger: core::mem::take(&mut ctx.runtime_options.debugger),
log: core::ptr::NonNull::new(ctx.log),
env_loader: core::ptr::NonNull::new(&raw mut *env_loader),
store_fd: ctx.debug.hot_reload != jsc::virtual_machine::HotReload::None,
smol: ctx.runtime_options.smol,
Comment thread
robobun marked this conversation as resolved.
is_main_thread: true,
..Default::default()
Expand Down
1 change: 0 additions & 1 deletion src/runtime/jsc_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,6 @@ unsafe fn init_runtime_state(
unsafe {
let t = &mut (*vm).transpiler;
t.options.emit_dce_annotations = false;
t.resolver.store_fd = opts.store_fd;
t.resolver.prefer_module_field = false;
// Propagate `--preserve-symlinks`
// from CLI args to the resolver so symlinked node_modules
Expand Down
91 changes: 89 additions & 2 deletions test/cli/watch/watch.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Subprocess } from "bun";
import { spawn } from "bun";
import { afterEach, expect, it } from "bun:test";
import { bunEnv, bunExe, isBroken, isLinux, isWindows, tempDir, tmpdirSync } from "harness";
import { readdirSync, rmSync } from "node:fs";
import { bunEnv, bunExe, isBroken, isLinux, isMacOS, isWindows, tempDir, tmpdirSync } from "harness";
import { readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync } from "node:fs";
import { join } from "node:path";

let watchee: Subprocess;
Expand Down Expand Up @@ -465,3 +465,90 @@ it.skipIf(isWindows)(
},
30000,
);

// Paths of the files and directories a process holds open. Linux reads
// /proc, macOS asks lsof (the kernel has no per-process fd listing there).
function openPaths(pid: number): string[] {
if (isLinux) {
const fdDir = `/proc/${pid}/fd`;
return readdirSync(fdDir).flatMap(fd => {
// The process is live, so an fd listed a moment ago can be closed by
// the time it is read. A closed fd is not a held path.
try {
return [readlinkSync(join(fdDir, fd))];
} catch {
return [];
}
});
}
const out = Bun.spawnSync({ cmd: ["lsof", "-Fn", "-p", String(pid)], stdout: "pipe", stderr: "ignore" });
return out.stdout
.toString()
.split("\n")
.filter(line => line.startsWith("n/"))
.map(line => line.slice(1));
}

// Watch mode used to make the resolver keep every directory and symlink
// descriptor it opened, most of them under node_modules, which the watcher
// never registers. On macOS each one is an fd for the life of the dev
// server, and a monorepo with the isolated linker reached tens of thousands
// of them (#42702).
for (const [mode, args] of [
["--watch", ["--watch", "--no-clear-screen", "main.ts"]],
["--hot", ["--hot", "--no-clear-screen", "main.ts"]],
["test --watch", ["test", "--watch", "main.test.ts"]],
] as const) {
it.skipIf(isWindows || (isMacOS && !Bun.which("lsof")))(
`${mode} holds no file descriptors for paths under node_modules`,
async () => {
using dir = tempDir("watch-node-modules-fds", {
"main.ts": `import { x } from "dep";
import { y } from "dep2";
console.log("ready", x, y);
setInterval(() => {}, 1e6);
`,
"main.test.ts": `import { test } from "bun:test";
import { x } from "dep";
import { y } from "dep2";
test("ready", () => console.log("ready", x, y));
`,
"node_modules/dep/package.json": JSON.stringify({
name: "dep",
type: "module",
exports: { ".": { import: "./dist/esm/index.js" } },
}),
"node_modules/dep/dist/esm/index.js": "export const x = 1;",
// The layout of the isolated linker: the real package lives under
// node_modules/.bun and node_modules/dep2 is a symlink to it.
"node_modules/.bun/dep2@1.0.0/node_modules/dep2/package.json": JSON.stringify({
name: "dep2",
type: "module",
main: "./dist/index.js",
}),
"node_modules/.bun/dep2@1.0.0/node_modules/dep2/dist/index.js": "export const y = 2;",
});
const cwd = realpathSync(String(dir));
symlinkSync(join(".bun", "dep2@1.0.0", "node_modules", "dep2"), join(cwd, "node_modules", "dep2"));

watchee = spawn({
cwd,
cmd: [bunExe(), ...args],
env: bunEnv,
stdout: "pipe",
stderr: "inherit",
stdin: "ignore",
});
const waiter = stdoutWaiter(watchee);
await waiter.waitFor("ready 1 2");

const held = openPaths(watchee.pid);
waiter.release();

// stdin, stdout and stderr are always there, so an empty list means the
// listing failed rather than that nothing is held.
expect(held.length).toBeGreaterThan(0);
expect(held.filter(p => p.startsWith(cwd) && p.includes("node_modules"))).toEqual([]);
},
);
}
Loading