Add no empty chunk to a byte-mode Readable's buffer (#2217)

This commit is contained in:
Chris Tate
2026-10-25 08:55:59 -05:00
committed by GitHub
4 changed files with 83 additions and 1 deletions
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/7516-readable-empty-push.ts"
],
"diags": []
}
+7 -1
View File
@@ -954,8 +954,14 @@ static bool scr_stream_add_chunk(ScrStream *s, ScrBytes *chunk, bool front) {
}
entry = dec; /* owned */
} else {
entry = scr_bytes_retain(chunk);
entry_len = chunk->len;
if (entry_len == 0) {
/* Node's readableAddChunk adds no empty chunk outside object mode:
* it clears reading and may read more */
if (!front) scr_stream_maybe_read_more(s);
return !st->r.ended && (st->r.length < st->r.hwm || st->r.length == 0);
}
entry = scr_bytes_retain(chunk);
}
if (entry_len > 0 &&
st->r.flowing == 1 && st->r.length == 0 && !st->r.in_read_sync && !front &&
+69
View File
@@ -0,0 +1,69 @@
// readable.push() of an empty Buffer outside object mode adds nothing, as
// in Node: the chunks pushed after it still reach 'data' listeners,
// readline and read(), and push(null) still ends the stream. In object
// mode an empty Buffer is a value like any other.
import { createInterface } from "node:readline";
import { Readable } from "node:stream";
function lines(chunks: string[]): Promise<void> {
return new Promise<void>((resolve) => {
const src = new Readable({ read() {} });
const rl = createInterface({ input: src, crlfDelay: Infinity });
rl.on("line", (line: string) => console.log("line", JSON.stringify(line)));
rl.on("close", () => {
console.log("readline closed");
resolve();
});
for (const chunk of chunks) src.push(Buffer.from(chunk, "utf8"));
src.push(null);
});
}
function data(): Promise<void> {
return new Promise<void>((resolve) => {
const src = new Readable({ read() {} });
src.on("data", (chunk: Buffer) => console.log("data", JSON.stringify(String(chunk))));
src.on("end", () => {
console.log("data end");
resolve();
});
console.log("push a", src.push(Buffer.from("a")));
console.log("push empty", src.push(Buffer.alloc(0)));
console.log("push b", src.push(Buffer.from("b")));
src.push(null);
});
}
function paused(): void {
const src = new Readable({ read() {} });
src.push(Buffer.from("x"));
src.push(Buffer.alloc(0));
src.push(Buffer.from("y"));
console.log("readableLength", src.readableLength);
console.log("read", JSON.stringify(String(src.read())));
}
function objectMode(): Promise<void> {
return new Promise<void>((resolve) => {
const src = new Readable({ objectMode: true, read() {} });
src.on("data", (chunk: Buffer) => console.log("object chunk length", chunk.length));
src.on("end", () => {
console.log("object end");
resolve();
});
src.push(Buffer.alloc(0));
src.push(Buffer.from("z"));
src.push(null);
});
}
async function main(): Promise<void> {
await lines(["a\nb", "", "c\n"]);
await lines(["", "", "only\n"]);
await data();
paused();
await objectMode();
console.log("done");
}
main();
@@ -0,0 +1 @@
{ "plain": 2200, "san": 3200 }