stream: fixup writer to terminate on consumer return/throw

Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode
PR-URL: https://github.com/nodejs/node/pull/65652
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
James M Snell committed 2026-09-02 13:14:30 +00:00
1 parent ae168eac5b
commit 535f934000
2 files changed
+47 -19

No files matched your search

+32 -19
View File
@@ -425,6 +425,13 @@ class PushQueue {
// ===========================================================================
async read() {
if (this.#consumerState === 'returned') {
return { __proto__: null, done: true, value: undefined };
}
if (this.#consumerState === 'thrown') {
throw this.#error;
}
// If there's data in the buffer, return it immediately
if (this.#slots.length > 0) {
const result = this.#drain();
@@ -458,16 +465,9 @@ class PushQueue {
consumerReturn() {
if (this.#consumerState !== 'active') return;
this.#consumerState = 'returned';
this.#cleanup();
const error = new ERR_INVALID_STATE.TypeError('Stream closed by consumer');
this.#terminateWriterFromConsumer(error);
this.#resolvePendingReads();
this.#rejectPendingWrites(
new ERR_INVALID_STATE.TypeError('Stream closed by consumer'));
// If closing, reject the pending end promise
if (this.#writerState === 'closing' && this.#pendingEnd) {
this.#pendingEnd.reject(
new ERR_INVALID_STATE.TypeError('Stream closed by consumer'));
this.#pendingEnd = null;
}
// Resolve pending drains with false - no more data will be consumed
this.#resolvePendingDrains(false);
}
@@ -476,13 +476,8 @@ class PushQueue {
if (this.#consumerState !== 'active') return;
this.#consumerState = 'thrown';
this.#error = error;
this.#cleanup();
this.#terminateWriterFromConsumer(error);
this.#rejectPendingReads(error);
this.#rejectPendingWrites(error);
if (this.#writerState === 'closing' && this.#pendingEnd) {
this.#pendingEnd.reject(error);
this.#pendingEnd = null;
}
// Reject pending drains - the consumer errored
this.#rejectPendingDrains(error);
}
@@ -516,9 +511,30 @@ class PushQueue {
return size;
}
#terminateWriterFromConsumer(error) {
this.#slots.clear();
this.#bufferedBytes = 0;
if (this.#writerState === 'open' || this.#writerState === 'closing') {
this.#writerState = 'errored';
this.#error = error;
}
this.#cleanup();
this.#rejectPendingWrites(error);
if (this.#pendingEnd) {
this.#pendingEnd.reject(error);
this.#pendingEnd = null;
}
}
#resolvePendingReads() {
while (this.#pendingReads.length > 0) {
if (this.#slots.length > 0) {
if (this.#consumerState === 'returned') {
const pending = this.#pendingReads.shift();
pending.resolve({ __proto__: null, done: true, value: undefined });
} else if (this.#consumerState === 'thrown') {
const pending = this.#pendingReads.shift();
pending.reject(this.#error);
} else if (this.#slots.length > 0) {
const pending = this.#pendingReads.shift();
const result = this.#drain();
this.#resolvePendingWrites();
@@ -533,9 +549,6 @@ class PushQueue {
} else if (this.#writerState === 'errored') {
const pending = this.#pendingReads.shift();
pending.reject(this.#error);
} else if (this.#consumerState === 'returned') {
const pending = this.#pendingReads.shift();
pending.resolve({ __proto__: null, done: true, value: undefined });
} else {
break;
}