mirror of
https://github.com/oven-sh/bun
synced 2026-02-24 18:47:18 +01:00
fix(router): don't cache file descriptors in Route.parse to prevent stale fd reuse (#27164)
## Summary - `FileSystemRouter.Route.parse()` was caching file descriptors in the global entry cache (`entry.cache.fd`). When `Bun.build()` later closed these fds during `ParseTask`, the cache still referenced them. Subsequent `Bun.build()` calls would find these stale fds, pass them to `readFileWithAllocator`, and `seekTo(0)` would fail with EBADF (errno 9). - The fix ensures `Route.parse` always closes the file it opens for `getFdPath` instead of caching it in the shared entry. The fd was only used to resolve the absolute path via `getFdPath`, so caching was unnecessary and harmful. Closes #18242 ## Test plan - [x] Added regression test `test/regression/issue/18242.test.ts` that creates a `FileSystemRouter` and runs `Bun.build()` three times sequentially - [x] Test passes with `bun bd test test/regression/issue/18242.test.ts` - [x] Test fails with `USE_SYSTEM_BUN=1 bun test test/regression/issue/18242.test.ts` (system bun v1.3.9) - [x] Verified 5 sequential builds work correctly after the fix 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
@@ -731,23 +731,22 @@ pub const Route = struct {
|
||||
|
||||
if (abs_path_str.len == 0) {
|
||||
var file: std.fs.File = undefined;
|
||||
var needs_close = false;
|
||||
var needs_close = true;
|
||||
defer if (needs_close) file.close();
|
||||
if (entry.cache.fd.unwrapValid()) |valid| {
|
||||
file = valid.stdFile();
|
||||
needs_close = false;
|
||||
} else {
|
||||
var parts = [_]string{ entry.dir, entry.base() };
|
||||
abs_path_str = FileSystem.instance.absBuf(&parts, &route_file_buf);
|
||||
route_file_buf[abs_path_str.len] = 0;
|
||||
const buf = route_file_buf[0..abs_path_str.len :0];
|
||||
file = std.fs.openFileAbsoluteZ(buf, .{ .mode = .read_only }) catch |err| {
|
||||
needs_close = false;
|
||||
log.addErrorFmt(null, Logger.Loc.Empty, allocator, "{s} opening route: {s}", .{ @errorName(err), abs_path_str }) catch unreachable;
|
||||
return null;
|
||||
};
|
||||
FileSystem.setMaxFd(file.handle);
|
||||
|
||||
needs_close = FileSystem.instance.fs.needToCloseFiles();
|
||||
if (!needs_close) entry.cache.fd = .fromStdFile(file);
|
||||
}
|
||||
|
||||
const _abs = bun.getFdPath(.fromStdFile(file), &route_file_buf) catch |err| {
|
||||
|
||||
62
test/regression/issue/18242.test.ts
Normal file
62
test/regression/issue/18242.test.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDir } from "harness";
|
||||
|
||||
test("Bun.build works multiple times after FileSystemRouter is created", async () => {
|
||||
using dir = tempDir("issue-18242", {
|
||||
"pages/index.ts": `console.log("Hello via Bun!");`,
|
||||
"build.ts": `
|
||||
import path from "path";
|
||||
|
||||
const PAGES_DIR = path.resolve(process.cwd(), "pages");
|
||||
|
||||
const srcRouter = new Bun.FileSystemRouter({
|
||||
dir: PAGES_DIR,
|
||||
style: "nextjs",
|
||||
});
|
||||
|
||||
const entrypoints = Object.values(srcRouter.routes);
|
||||
|
||||
const result1 = await Bun.build({
|
||||
entrypoints,
|
||||
outdir: "dist/browser",
|
||||
});
|
||||
|
||||
const result2 = await Bun.build({
|
||||
entrypoints,
|
||||
outdir: "dist/bun",
|
||||
target: "bun",
|
||||
});
|
||||
|
||||
const result3 = await Bun.build({
|
||||
entrypoints,
|
||||
outdir: "dist/third",
|
||||
});
|
||||
|
||||
console.log(JSON.stringify({
|
||||
build1: result1.success,
|
||||
build2: result2.success,
|
||||
build3: result3.success,
|
||||
build2Logs: result2.logs.map(String),
|
||||
build3Logs: result3.logs.map(String),
|
||||
}));
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "build.ts"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
const result = JSON.parse(stdout.trim());
|
||||
|
||||
expect(result.build1).toBe(true);
|
||||
expect(result.build2).toBe(true);
|
||||
expect(result.build3).toBe(true);
|
||||
expect(result.build2Logs).toEqual([]);
|
||||
expect(result.build3Logs).toEqual([]);
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user