diff --git a/src/io/PipeWriter.rs b/src/io/PipeWriter.rs index d8e7838e0ed1..7db331b5695c 100644 --- a/src/io/PipeWriter.rs +++ b/src/io/PipeWriter.rs @@ -99,6 +99,9 @@ pub trait PosixPipeWriter { write_fn: fn(Fd, &[u8]) -> sys::Result, ) -> WriteResult { let fd = self.get_fd(); + if fd == Fd::INVALID { + return WriteResult::Done(0); + } let mut offset: usize = 0; diff --git a/src/runtime/webcore/FileSink.rs b/src/runtime/webcore/FileSink.rs index 2c92dca2c921..a01d9a43a304 100644 --- a/src/runtime/webcore/FileSink.rs +++ b/src/runtime/webcore/FileSink.rs @@ -1240,7 +1240,8 @@ impl FileSink { sys::Result::Ok(()) } WriteResult::Err(e) => { - self.writer.with_mut(|w| w.close()); + self.done.set(true); + self.writer.with_mut(|w| w.end()); sys::Result::Err(e) } WriteResult::Pending(written) => { @@ -1354,7 +1355,8 @@ impl FileSink { sys::Result::Ok(JSValue::js_number(written as f64)) } WriteResult::Err(err) => { - self.writer.with_mut(|w| w.close()); + self.done.set(true); + self.writer.with_mut(|w| w.end()); sys::Result::Err(err) } WriteResult::Pending(pending_written) => { diff --git a/test/js/bun/util/filesink.test.ts b/test/js/bun/util/filesink.test.ts index 3b3f66c9dca4..f8e4d6123be5 100644 --- a/test/js/bun/util/filesink.test.ts +++ b/test/js/bun/util/filesink.test.ts @@ -278,3 +278,33 @@ it("start() without path/fd on an already-open writer does not crash", async () await writer.end(); expect(await Bun.file(path).text()).toBe("hello"); }); + +it.skipIf(!isPosix)("writing after end() fails during flush does not crash", async () => { + const dir = tmpdirSync(); + const target = join(dir, "ro.txt"); + fs.writeFileSync(target, ""); + const writer = Bun.file(target).writer(); + // Re-point the writer at a read-only fd so the buffered flush in end() fails. + const fd = fs.openSync(target, "r"); + try { + writer.start({ fd }); + } finally { + fs.closeSync(fd); + } + writer.write("x"); + let endErr: unknown; + try { + await writer.end(); + } catch (e) { + endErr = e; + } + expect(endErr).toBeDefined(); + // Previously this would attempt to write to an invalid fd and crash with a + // debug assertion; now it should behave as if the sink is closed. + expect(() => writer.write("y")).not.toThrow(); + expect(() => writer.start({})).not.toThrow(); + expect(() => writer.write("z")).not.toThrow(); + expect(() => writer.flush()).not.toThrow(); + await Promise.resolve(writer.end()).catch(() => {}); + await 1; +});