mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
fix(windows): keep event loop alive during Bun.file() async reads
ReadFileUV was not calling refConcurrently()/unrefConcurrently() on the event loop, causing the process to exit prematurely before the async libuv file read operations completed. This resulted in Bun.file().text() silently failing (not throwing ENOENT) when reading non-existent files on Windows. Fixes #26632 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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", .{});
|
||||
}
|
||||
|
||||
|
||||
58
test/regression/issue/26632.test.ts
Normal file
58
test/regression/issue/26632.test.ts
Normal file
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user