Skip to content

Commit 5632446

Browse files
marcopiracciniaduh95
authored andcommitted
stream: fix TransformStream race on cancel with pending write
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com> PR-URL: #62040Fixes: #62036 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Mattias Buelens <mattias@buelens.com>
1 parent db150b2 commit 5632446

2 files changed

Lines changed: 57 additions & 2 deletions

File tree

‎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
constassert=require('internal/assert');
7373

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

525524
asyncfunctiontransformStreamDefaultControllerPerformTransform(controller,chunk){
526525
try{
527-
returnawaitcontroller[kState].transformAlgorithm(chunk,controller);
526+
consttransformAlgorithm=controller[kState].transformAlgorithm;
527+
if(transformAlgorithm===undefined){
528+
// Algorithms were cleared by a concurrent cancel/abort/close.
529+
return;
530+
}
531+
returnawaittransformAlgorithm(chunk,controller);
528532
}catch(error){
529533
transformStreamError(controller[kState].stream,error);
530534
throwerror;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use strict';
2+
3+
require('../common');
4+
const{ test }=require('node:test');
5+
constassert=require('node:assert');
6+
const{ TransformStream }=require('stream/web');
7+
const{ setTimeout }=require('timers/promises');
8+
9+
// https://github.com/nodejs/node/issues/62036
10+
11+
test('Late write racing with reader.cancel() should not throw an internal TypeError',async()=>{
12+
conststream=newTransformStream({
13+
transform(chunk,controller){
14+
controller.enqueue(chunk);
15+
},
16+
});
17+
18+
awaitsetTimeout(0);
19+
20+
constreader=stream.readable.getReader();
21+
constwriter=stream.writable.getWriter();
22+
23+
// Release backpressure.
24+
constpendingRead=reader.read();
25+
26+
// Simulate client disconnect / shutdown.
27+
constpendingCancel=reader.cancel(newError('client disconnected'));
28+
29+
// Late write racing with cancel.
30+
constpendingLateWrite=writer.write('late-write');
31+
32+
const[
33+
readResult,
34+
cancelResult,
35+
lateWriteResult,
36+
]=awaitPromise.allSettled([
37+
pendingRead,
38+
pendingCancel,
39+
pendingLateWrite,
40+
]);
41+
42+
assert.strictEqual(readResult.status,'fulfilled');
43+
assert.strictEqual(cancelResult.status,'fulfilled');
44+
if(lateWriteResult.status==='rejected'){
45+
consterr=lateWriteResult.reason;
46+
constisNotAFunction=errinstanceofTypeError&&
47+
/transformAlgorithmisnotafunction/.test(err.message);
48+
assert.ok(!isNotAFunction,
49+
`Internal implementation error leaked: ${err.message}`);
50+
}
51+
});

0 commit comments

Comments
 (0)