Skip to content

Commit c0fe52d

Browse files
stream: fix ERR_INVALID_STATE when cancelling Readable.toWeb()
When a web ReadableStream returned by Readable.toWeb() is cancelled while the underlying Readable is actively producing data, a pending onData callback can still fire after the controller has been closed and attempt to enqueue a chunk, throwing ERR_INVALID_STATE. Check wasCanceled in the onData handler and return early to avoid calling controller.enqueue() on a closed controller. Refs: #54205 Signed-off-by: Richard Scarrott <riscarrott@googlemail.com>
1 parent fd5b135 commit c0fe52d

2 files changed

Lines changed: 23 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);

‎test/parallel/test-stream-readable-to-web-termination.js‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,25 @@ const { setTimeout: delay } = require('timers/promises');
4242
assert.deepStrictEqual(closeResult, { value: undefined, done: true });
4343
})().then(common.mustCall());
4444
}
45+
46+
// Cancelling a web ReadableStream while the underlying Readable is actively
47+
// producing data should not throw ERR_INVALID_STATE. The 'data' handler in
48+
// newReadableStreamFromStreamReadable must check wasCanceled before calling
49+
// controller.enqueue(). See: https://github.com/nodejs/node/issues/54205
50+
{
51+
const readable = new Readable({
52+
read() {
53+
this.push(Buffer.alloc(1024));
54+
},
55+
});
56+
57+
const webStream = Readable.toWeb(readable);
58+
const reader = webStream.getReader();
59+
60+
(async () => {
61+
await reader.read();
62+
await reader.read();
63+
reader.releaseLock();
64+
await webStream.cancel();
65+
})().then(common.mustCall());
66+
}

0 commit comments

Comments
 (0)