Skip to content

Commit 58eb15c

Browse files
colinaaaaduh95
authored andcommitted
stream: fix pipeTo assert when writer is released during microtask
pipeTo defers each slow-path write to a microtask so it does not run synchronously during enqueue(). A race allows the pipe to shut down and release its writer after the write is scheduled but before it runs. The deferred write then hits an ERR_INTERNAL_ASSERTION because writer[[stream]] is undefined. Skip the deferred write when shutdown has already started. Fixes: #63732 Signed-off-by: Qingyu Wang <wangqingyu.c0l1n@bytedance.com> Assisted-by: Codex PR-URL: #63733 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 8163152 commit 58eb15c

2 files changed

Lines changed: 64 additions & 0 deletions

File tree

‎lib/internal/webstreams/readablestream.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1792,6 +1792,7 @@ function readableStreamPipeTo(
17921792
function forwardChunk() {
17931793
const chunk = pendingChunk;
17941794
pendingChunk = undefined;
1795+
if (writer[kState].stream === undefined) return;
17951796
writableStreamDefaultWriterWriteWithRequest(writer, chunk, writeTracker);
17961797
pump();
17971798
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { ReadableStream, WritableStream } = require('stream/web');
6+
7+
{
8+
let sourceController;
9+
let destController;
10+
11+
const source = new ReadableStream({
12+
start(controller) {
13+
sourceController = controller;
14+
},
15+
});
16+
17+
const dest = new WritableStream({
18+
start(controller) {
19+
destController = controller;
20+
},
21+
write() {},
22+
});
23+
24+
assert.rejects(
25+
source.pipeTo(dest, { preventCancel: true }),
26+
{ message: 'destination errored' },
27+
).then(common.mustCall());
28+
29+
setImmediate(common.mustCall(() => {
30+
destController.error(new Error('destination errored'));
31+
sourceController.enqueue('chunk');
32+
}));
33+
}
34+
35+
{
36+
const ac = new AbortController();
37+
let sourceController;
38+
const chunks = [];
39+
40+
const source = new ReadableStream({
41+
start(controller) {
42+
sourceController = controller;
43+
},
44+
}, { highWaterMark: 0 });
45+
46+
const dest = new WritableStream({
47+
write: common.mustCall((chunk) => {
48+
chunks.push(chunk);
49+
}),
50+
}, { highWaterMark: 1 });
51+
52+
assert.rejects(
53+
source.pipeTo(dest, { signal: ac.signal }),
54+
{ name: 'AbortError' },
55+
).then(common.mustCall(() => {
56+
assert.deepStrictEqual(chunks, ['chunk']);
57+
}));
58+
59+
setImmediate(common.mustCall(() => {
60+
sourceController.enqueue('chunk');
61+
ac.abort();
62+
}));
63+
}

0 commit comments

Comments
 (0)