diff --git a/src/bun.js/webcore/Blob.zig b/src/bun.js/webcore/Blob.zig index 28056fd2e0..add5b8efe2 100644 --- a/src/bun.js/webcore/Blob.zig +++ b/src/bun.js/webcore/Blob.zig @@ -138,7 +138,7 @@ pub fn doReadFile(this: *Blob, comptime Function: anytype, global: *JSGlobalObje promise_value.ensureStillAlive(); handler.promise.strong.set(global, promise_value); - read_file.ReadFileUV.start(handler.globalThis.bunVM().uvLoop(), this.store.?, this.offset, this.size, Handler, handler); + read_file.ReadFileUV.start(handler.globalThis.bunVM().eventLoop(), this.store.?, this.offset, this.size, Handler, handler); return promise_value; } @@ -180,7 +180,7 @@ pub fn NewInternalReadFileHandler(comptime Context: type, comptime Function: any pub fn doReadFileInternal(this: *Blob, comptime Handler: type, ctx: Handler, comptime Function: anytype, global: *JSGlobalObject) void { if (Environment.isWindows) { const ReadFileHandler = NewInternalReadFileHandler(Handler, Function); - return read_file.ReadFileUV.start(libuv.Loop.get(), this.store.?, this.offset, this.size, ReadFileHandler, ctx); + return read_file.ReadFileUV.start(global.bunVM().eventLoop(), this.store.?, this.offset, this.size, ReadFileHandler, ctx); } const file_read = read_file.ReadFile.createWithCtx( bun.default_allocator, diff --git a/src/bun.js/webcore/blob/read_file.zig b/src/bun.js/webcore/blob/read_file.zig index 9b500b1d43..ba108ea6e9 100644 --- a/src/bun.js/webcore/blob/read_file.zig +++ b/src/bun.js/webcore/blob/read_file.zig @@ -523,6 +523,7 @@ pub const ReadFileUV = struct { pub const doClose = FileCloser(@This()).doClose; loop: *libuv.Loop, + event_loop: *jsc.EventLoop, file_store: FileStore, byte_store: ByteStore = ByteStore{ .allocator = bun.default_allocator }, store: *Store, @@ -543,10 +544,11 @@ pub const ReadFileUV = struct { req: libuv.fs_t = std.mem.zeroes(libuv.fs_t), - pub fn start(loop: *libuv.Loop, store: *Store, off: SizeType, max_len: SizeType, comptime Handler: type, handler: *anyopaque) void { + pub fn start(event_loop: *jsc.EventLoop, store: *Store, off: SizeType, max_len: SizeType, comptime Handler: type, handler: *anyopaque) void { log("ReadFileUV.start", .{}); var this = bun.new(ReadFileUV, .{ - .loop = loop, + .loop = event_loop.virtual_machine.uvLoop(), + .event_loop = event_loop, .file_store = store.data.file, .store = store, .offset = off, @@ -555,15 +557,20 @@ pub const ReadFileUV = struct { .on_complete_fn = @ptrCast(&Handler.run), }); store.ref(); + // Keep the event loop alive while the async operation is pending + event_loop.refConcurrently(); this.getFd(onFileOpen); } pub fn finalize(this: *ReadFileUV) void { log("ReadFileUV.finalize", .{}); + const event_loop = this.event_loop; defer { this.store.deref(); this.req.deinit(); bun.destroy(this); + // Release the event loop reference now that we're done + event_loop.unrefConcurrently(); log("ReadFileUV.finalize destroy", .{}); } diff --git a/test/regression/issue/26632.test.ts b/test/regression/issue/26632.test.ts new file mode 100644 index 0000000000..112643b412 --- /dev/null +++ b/test/regression/issue/26632.test.ts @@ -0,0 +1,58 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe, tempDir } from "harness"; + +// https://github.com/oven-sh/bun/issues/26632 +// Bun.file().text() on a non-existent file should throw ENOENT error, not silently exit +test("Bun.file().text() on nonexistent file throws ENOENT", async () => { + using dir = tempDir("26632", {}); + + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", `await Bun.file("nonexistent-file-that-does-not-exist.txt").text();`], + cwd: String(dir), + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout).toBe(""); + expect(stderr).toContain("ENOENT"); + expect(exitCode).not.toBe(0); +}); + +test("Bun.file().arrayBuffer() on nonexistent file throws ENOENT", async () => { + using dir = tempDir("26632", {}); + + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", `await Bun.file("nonexistent-file-that-does-not-exist.txt").arrayBuffer();`], + cwd: String(dir), + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout).toBe(""); + expect(stderr).toContain("ENOENT"); + expect(exitCode).not.toBe(0); +}); + +test("Bun.file().bytes() on nonexistent file throws ENOENT", async () => { + using dir = tempDir("26632", {}); + + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", `await Bun.file("nonexistent-file-that-does-not-exist.txt").bytes();`], + cwd: String(dir), + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout).toBe(""); + expect(stderr).toContain("ENOENT"); + expect(exitCode).not.toBe(0); +});