From 96e546b1c0ca29618f7ac2d8bece9055f1331b01 Mon Sep 17 00:00:00 2001 From: Richard Scarrott Date: Thu, 16 Apr 2026 11:16:10 +0100 Subject: [PATCH] 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: https://github.com/nodejs/node/issues/54205 --- lib/internal/webstreams/adapters.js | 1 + ...test-stream-readable-to-web-termination.js | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/internal/webstreams/adapters.js b/lib/internal/webstreams/adapters.js index 8befa6bbbafd72..06418853292928 100644 --- a/lib/internal/webstreams/adapters.js +++ b/lib/internal/webstreams/adapters.js @@ -507,6 +507,7 @@ function newReadableStreamFromStreamReadable(streamReadable, options = kEmptyObj let wasCanceled = false; function onData(chunk) { + if (wasCanceled) return; // Copy the Buffer to detach it from the pool. if (Buffer.isBuffer(chunk) && !objectMode) chunk = new Uint8Array(chunk); diff --git a/test/parallel/test-stream-readable-to-web-termination.js b/test/parallel/test-stream-readable-to-web-termination.js index 13fce9bc715e1e..df18d95d57c3ee 100644 --- a/test/parallel/test-stream-readable-to-web-termination.js +++ b/test/parallel/test-stream-readable-to-web-termination.js @@ -10,3 +10,25 @@ const { Readable } = require('stream'); const reader = Readable.toWeb(r).getReader(); reader.read(); } + +// Cancelling a web ReadableStream while the underlying Readable is actively +// producing data should not throw ERR_INVALID_STATE. The onData handler in +// newReadableStreamFromStreamReadable must check wasCanceled before calling +// controller.enqueue(). See: https://github.com/nodejs/node/issues/54205 +{ + const readable = new Readable({ + read() { + this.push(Buffer.alloc(1024)); + }, + }); + + const webStream = Readable.toWeb(readable); + const reader = webStream.getReader(); + + (async () => { + await reader.read(); + await reader.read(); + reader.releaseLock(); + await webStream.cancel(); + })(); +}