Skip to content

Commit 1608d90

Browse files
trivikraduh95
authored andcommitted
stream: reject pending reads on iterator throw
Settle reads that are already waiting when a push stream iterator is returned or thrown. This prevents next() promises from hanging after consumer cancellation. Fixes: #63554 Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: openai:gpt-5.5 PR-URL: #63555Fixes: #63554 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent fcccffc commit 1608d90

2 files changed

Lines changed: 49 additions & 0 deletions

File tree

‎lib/internal/streams/iter/push.js‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,7 @@ class PushQueue {
426426
if(this.#consumerState !=='active')return;
427427
this.#consumerState ='returned';
428428
this.#cleanup();
429+
this.#resolvePendingReads();
429430
this.#rejectPendingWrites(
430431
newERR_INVALID_STATE.TypeError('Stream closed by consumer'));
431432
// If closing, reject the pending end promise
@@ -443,7 +444,12 @@ class PushQueue {
443444
this.#consumerState ='thrown';
444445
this.#error =error;
445446
this.#cleanup();
447+
this.#rejectPendingReads(error);
446448
this.#rejectPendingWrites(error);
449+
if(this.#writerState ==='closing'&&this.#pendingEnd){
450+
this.#pendingEnd.reject(error);
451+
this.#pendingEnd =null;
452+
}
447453
// Reject pending drains - the consumer errored
448454
this.#rejectPendingDrains(error);
449455
}
@@ -485,6 +491,9 @@ class PushQueue {
485491
}elseif(this.#writerState ==='errored'&&this.#error){
486492
constpending=this.#pendingReads.shift();
487493
pending.reject(this.#error);
494+
}elseif(this.#consumerState ==='returned'){
495+
constpending=this.#pendingReads.shift();
496+
pending.resolve({__proto__: null,value: undefined,done: true});
488497
}else{
489498
break;
490499
}

‎test/parallel/test-stream-iter-push-writer.js‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,44 @@ async function testFailRejectsPendingRead() {
323323
);
324324
}
325325

326+
// iterator.return() resolves a pending read with done:true
327+
asyncfunctiontestConsumerReturnResolvesPendingRead(){
328+
const{ readable }=push();
329+
330+
constiter=readable[Symbol.asyncIterator]();
331+
constreadPromise=iter.next();
332+
333+
awaitnewPromise(setImmediate);
334+
335+
constreturnResult=awaititer.return();
336+
assert.strictEqual(returnResult.value,undefined);
337+
assert.strictEqual(returnResult.done,true);
338+
339+
constreadResult=awaitreadPromise;
340+
assert.strictEqual(readResult.value,undefined);
341+
assert.strictEqual(readResult.done,true);
342+
}
343+
344+
// iterator.throw() rejects a pending read with the thrown error
345+
asyncfunctiontestConsumerThrowRejectsPendingRead(){
346+
const{ readable }=push();
347+
348+
constiter=readable[Symbol.asyncIterator]();
349+
constreadPromise=iter.next();
350+
351+
awaitnewPromise(setImmediate);
352+
353+
consterr=newError('consumer read boom');
354+
constthrowResult=awaititer.throw(err);
355+
assert.strictEqual(throwResult.value,undefined);
356+
assert.strictEqual(throwResult.done,true);
357+
358+
awaitassert.rejects(
359+
()=>readPromise,
360+
(e)=>e===err,
361+
);
362+
}
363+
326364
// end() while writes are pending rejects those writes
327365
asyncfunctiontestEndRejectsPendingWrites(){
328366
const{ writer, readable }=push({highWaterMark: 1,backpressure: 'block'});
@@ -435,6 +473,8 @@ Promise.all([
435473
testConsumerThrowRejectsWrites(),
436474
testEndResolvesPendingRead(),
437475
testFailRejectsPendingRead(),
476+
testConsumerReturnResolvesPendingRead(),
477+
testConsumerThrowRejectsPendingRead(),
438478
testEndRejectsPendingWrites(),
439479
testEndIdempotentWhenClosed(),
440480
testEndRejectsWhenErrored(),

0 commit comments

Comments
 (0)