From 2449435053b8eb4ab9d68ce76cddb1994cee74af Mon Sep 17 00:00:00 2001 From: marcopiraccini Date: Sat, 28 Feb 2026 14:09:57 +0100 Subject: [PATCH 1/2] stream: fix TransformStream race on cancel with pending write Signed-off-by: marcopiraccini --- lib/internal/webstreams/transformstream.js | 8 ++- ...hatwg-transformstream-cancel-write-race.js | 54 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-whatwg-transformstream-cancel-write-race.js diff --git a/lib/internal/webstreams/transformstream.js b/lib/internal/webstreams/transformstream.js index 371b597dc681d6..fa5e5803c159de 100644 --- a/lib/internal/webstreams/transformstream.js +++ b/lib/internal/webstreams/transformstream.js @@ -72,7 +72,6 @@ const { const assert = require('internal/assert'); const kSkipThrow = Symbol('kSkipThrow'); - const getNonWritablePropertyDescriptor = (value) => { return { __proto__: null, @@ -524,7 +523,12 @@ function transformStreamDefaultControllerError(controller, error) { async function transformStreamDefaultControllerPerformTransform(controller, chunk) { try { - return await controller[kState].transformAlgorithm(chunk, controller); + const transformAlgorithm = controller[kState].transformAlgorithm; + if (typeof transformAlgorithm !== 'function') { + // Algorithms were cleared by a concurrent cancel/abort/close. + return; + } + return await transformAlgorithm(chunk, controller); } catch (error) { transformStreamError(controller[kState].stream, error); throw error; diff --git a/test/parallel/test-whatwg-transformstream-cancel-write-race.js b/test/parallel/test-whatwg-transformstream-cancel-write-race.js new file mode 100644 index 00000000000000..eafd9aaacf99ed --- /dev/null +++ b/test/parallel/test-whatwg-transformstream-cancel-write-race.js @@ -0,0 +1,54 @@ +'use strict'; + +const common = require('../common'); +const { TransformStream } = require('stream/web'); +const { setTimeout } = require('timers/promises'); + +// Test for https://github.com/nodejs/node/issues/62036 +// A late write racing with reader.cancel() should not throw an internal "transformAlgorithm is not a function" TypeError. + +async function test() { + const stream = new TransformStream({ + transform(chunk, controller) { + controller.enqueue(chunk); + }, + }); + + await setTimeout(0); + + const reader = stream.readable.getReader(); + const writer = stream.writable.getWriter(); + + // Release backpressure. + const pendingRead = reader.read(); + + // Simulate client disconnect / shutdown. + const pendingCancel = reader.cancel(new Error('client disconnected')); + + // Late write racing with cancel. + const pendingLateWrite = writer.write('late-write'); + + const results = await Promise.allSettled([ + pendingRead, + pendingCancel, + pendingLateWrite, + ]); + + // pendingRead should fulfill (with done:true or a value). + // pendingCancel should fulfill. + // pendingLateWrite may reject, but must NOT reject with an internal + // TypeError about transformAlgorithm not being a function. + for (const result of results) { + if (result.status === 'rejected') { + const err = result.reason; + if (err instanceof TypeError && + /transformAlgorithm is not a function/.test(err.message)) { + throw new Error( + 'Internal implementation error leaked: ' + err.message + ); + } + } + } +} + +test().then(common.mustCall()); From 398e70014ef69e06e60870985b876c983f956d27 Mon Sep 17 00:00:00 2001 From: marcopiraccini Date: Sat, 28 Feb 2026 14:23:30 +0100 Subject: [PATCH 2/2] linting fixup Signed-off-by: marcopiraccini --- test/parallel/test-whatwg-transformstream-cancel-write-race.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-whatwg-transformstream-cancel-write-race.js b/test/parallel/test-whatwg-transformstream-cancel-write-race.js index eafd9aaacf99ed..49dcbf8fbfcdc5 100644 --- a/test/parallel/test-whatwg-transformstream-cancel-write-race.js +++ b/test/parallel/test-whatwg-transformstream-cancel-write-race.js @@ -5,7 +5,8 @@ const { TransformStream } = require('stream/web'); const { setTimeout } = require('timers/promises'); // Test for https://github.com/nodejs/node/issues/62036 -// A late write racing with reader.cancel() should not throw an internal "transformAlgorithm is not a function" TypeError. +// A late write racing with reader.cancel() should not throw an +// internal "transformAlgorithm is not a function" TypeError. async function test() { const stream = new TransformStream({