Skip to content

Commit 2449435

Browse files
stream: fix TransformStream race on cancel with pending write
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
1 parent c9d0ef8 commit 2449435

File tree

2 files changed

+60
-2
lines changed

2 files changed

+60
-2
lines changed

lib/internal/webstreams/transformstream.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ const {
7272
const assert = require('internal/assert');
7373

7474
const kSkipThrow = Symbol('kSkipThrow');
75-
7675
const getNonWritablePropertyDescriptor = (value) => {
7776
return {
7877
__proto__: null,
@@ -524,7 +523,12 @@ function transformStreamDefaultControllerError(controller, error) {
524523

525524
async function transformStreamDefaultControllerPerformTransform(controller, chunk) {
526525
try {
527-
return await controller[kState].transformAlgorithm(chunk, controller);
526+
const transformAlgorithm = controller[kState].transformAlgorithm;
527+
if (typeof transformAlgorithm !== 'function') {
528+
// Algorithms were cleared by a concurrent cancel/abort/close.
529+
return;
530+
}
531+
return await transformAlgorithm(chunk, controller);
528532
} catch (error) {
529533
transformStreamError(controller[kState].stream, error);
530534
throw error;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const { TransformStream } = require('stream/web');
5+
const { setTimeout } = require('timers/promises');
6+
7+
// Test for https://github.com/nodejs/node/issues/62036
8+
// A late write racing with reader.cancel() should not throw an internal "transformAlgorithm is not a function" TypeError.
9+
10+
async function test() {
11+
const stream = new TransformStream({
12+
transform(chunk, controller) {
13+
controller.enqueue(chunk);
14+
},
15+
});
16+
17+
await setTimeout(0);
18+
19+
const reader = stream.readable.getReader();
20+
const writer = stream.writable.getWriter();
21+
22+
// Release backpressure.
23+
const pendingRead = reader.read();
24+
25+
// Simulate client disconnect / shutdown.
26+
const pendingCancel = reader.cancel(new Error('client disconnected'));
27+
28+
// Late write racing with cancel.
29+
const pendingLateWrite = writer.write('late-write');
30+
31+
const results = await Promise.allSettled([
32+
pendingRead,
33+
pendingCancel,
34+
pendingLateWrite,
35+
]);
36+
37+
// pendingRead should fulfill (with done:true or a value).
38+
// pendingCancel should fulfill.
39+
// pendingLateWrite may reject, but must NOT reject with an internal
40+
// TypeError about transformAlgorithm not being a function.
41+
for (const result of results) {
42+
if (result.status === 'rejected') {
43+
const err = result.reason;
44+
if (err instanceof TypeError &&
45+
/transformAlgorithm is not a function/.test(err.message)) {
46+
throw new Error(
47+
'Internal implementation error leaked: ' + err.message
48+
);
49+
}
50+
}
51+
}
52+
}
53+
54+
test().then(common.mustCall());

0 commit comments

Comments
 (0)