Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 10 additions & 0 deletions src/js/internal/fs/streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,16 @@
const buf = Buffer.allocUnsafeSlow(n);

this[kFs].read(this.fd, buf, 0, n, this.pos, (er, bytesRead, buf) => {
// If the stream was destroyed while the read was in flight, ignore the
// result. The fd has been (or is about to be) closed by _destroy, and
// pushing more data or EOF here would clobber the destroyed state —
// in particular, push(null) would mark the readable side as ended and
// suppress the ERR_STREAM_PREMATURE_CLOSE that end-of-stream/finished/
// async iteration relies on. Matches Node.js behavior.
if (this.destroyed) {
return;
}

Check notice on line 320 in src/js/internal/fs/streams.ts

View check run for this annotation

Claude / Claude Code Review

Pre-existing: _destroy hangs and leaks fd when kReadStreamFastPath is true

Pre-existing (not introduced by this PR), but adjacent to your change: `_destroy` at L354-355 does `this.once(kReadStreamFastPath, ...)` when `this[kReadStreamFastPath]` is truthy, yet that symbol is never emitted anywhere — so for `createReadStream(path, { start: 0, autoClose: true })` the destroy callback never fires, `'close'` is never emitted, and the fd leaks. Worth noting because (a) the new comment here claims "the fd has been (or is about to be) closed by `_destroy`", which is false in t
Comment thread
robobun marked this conversation as resolved.

if (er) {
require("internal/streams/destroy").errorOrDestroy(this, er);
} else if (bytesRead > 0) {
Expand Down
20 changes: 20 additions & 0 deletions test/js/node/fs/fs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2221,6 +2221,26 @@ describe("createReadStream", () => {
},
{ timeout: 100 },
);

// https://github.com/oven-sh/bun/issues/30919
it("async iterator rejects with ERR_STREAM_PREMATURE_CLOSE when destroy() is called during iteration", async () => {
const stream = createReadStream(join(import.meta.dir, "readFileSync.txt"));

let chunks = 0;
let caught: any = undefined;
try {
for await (const _ of stream) {
chunks++;
stream.destroy();
}
} catch (err) {
caught = err;
}

expect(chunks).toBe(1);
expect(caught).toBeDefined();
expect(caught?.code).toBe("ERR_STREAM_PREMATURE_CLOSE");
});
});

describe("fs.WriteStream", () => {
Expand Down
Loading