Skip to content

Commit be40feb

Browse files
committed
stream: prevent enqueue after cancel in Readable.toWeb()
Signed-off-by: Alessio Attilio <alessio.attilio@protonmail.com>
1 parent 54a5095 commit be40feb

2 files changed

Lines changed: 53 additions & 0 deletions

File tree

lib/internal/webstreams/adapters.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ function newReadableStreamFromStreamReadable(streamReadable, options = kEmptyObj
541541
streamReadable.pause();
542542

543543
streamReadable.on('data', function onData(chunk) {
544+
if (wasCanceled) return;
544545
// Copy the Buffer to detach it from the pool.
545546
if (Buffer.isBuffer(chunk) && !objectMode)
546547
chunk = new Uint8Array(chunk);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use strict';
2+
const common = require('../common');
3+
const { PassThrough, Readable, pipeline } = require('node:stream');
4+
const { WritableStream } = require('node:stream/web');
5+
const { setTimeout } = require('node:timers/promises');
6+
7+
// Regression test for https://github.com/nodejs/node/issues/64529
8+
// Readable.toWeb() uncaughtException when the stream is canceled during backpressure resume
9+
10+
process.on('uncaughtException', (err) => {
11+
console.error('Uncaught exception!', err);
12+
process.exit(1);
13+
});
14+
15+
async function run() {
16+
for (let i = 0; i < 50; i++) {
17+
const src = new Readable({
18+
read() {
19+
this.push(Buffer.alloc(16 * 1024, 1));
20+
if ((this.bytes = (this.bytes || 0) + 16384) > 512 * 1024) this.push(null);
21+
},
22+
});
23+
const pt = new PassThrough({ highWaterMark: 16384 });
24+
pipeline(src, pt, () => {});
25+
const web = Readable.toWeb(pt);
26+
27+
const ac = new AbortController();
28+
const writer = new WritableStream(
29+
{
30+
async write() {
31+
await setTimeout(1);
32+
}
33+
},
34+
{ highWaterMark: 1 },
35+
);
36+
37+
// Disconnect randomly to catch the exact tick window
38+
setTimeout(i % 10).then(() => ac.abort()).then(common.mustCall());
39+
40+
try {
41+
await web.pipeTo(writer, { signal: ac.signal });
42+
} catch {
43+
// Ignore abort errors
44+
}
45+
46+
await new Promise((r) => setImmediate(r));
47+
}
48+
}
49+
50+
run().then(common.mustCall(() => {
51+
process.exit(0);
52+
}));

0 commit comments

Comments
 (0)