stream: ensure pre-existing writes drain before EOF and end() waits · nodejs/node@7308492 · GitHub
Skip to content

Commit 7308492

Browse files
jasnelladuh95
authored andcommitted
stream: ensure pre-existing writes drain before EOF and end() waits
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode PR-URL: #65658 Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
1 parent 7567734 commit 7308492

3 files changed

Lines changed: 65 additions & 37 deletions

File tree

‎doc/api/stream_iter.md‎

Lines changed: 5 additions & 1 deletion

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

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,10 @@ class PushQueue {
299299
const onAbort = () => {
300300
// Remove from queue so it doesn't occupy a slot
301301
const idx = this.#pendingWrites.indexOf(entry);
302-
if (idx !== -1) this.#pendingWrites.removeAt(idx);
302+
if (idx !== -1) {
303+
this.#pendingWrites.removeAt(idx);
304+
this.#resolvePendingReads();
305+
}
303306
reject(signal.reason ?? lazyDOMException('Aborted', 'AbortError'));
304307
};
305308

@@ -334,30 +337,31 @@ class PushQueue {
334337
return this.#bytesWritten; // Idempotent
335338
}
336339

337-
this.#rejectPendingWrites(
338-
new ERR_INVALID_STATE.TypeError('Writer closed'));
339340
this.#resolvePendingDrains(false);
340341

341-
// If buffer is empty, close immediately
342-
if (this.#slots.length === 0) {
342+
// If there is no accepted work to drain, close immediately.
343+
if (this.#slots.length === 0 && this.#pendingWrites.length === 0) {
343344
this.#writerState = 'closed';
344345
this.#cleanup();
345346
this.#resolvePendingReads();
346347
return this.#bytesWritten;
347348
}
348349

349-
// Buffer has data: transition to closing, defer completion until drained
350+
// Accepted work remains: close after it drains and the consumer pulls EOF.
350351
this.#writerState = 'closing';
351352
return -3; // Signal to PushWriter: create deferred end promise
352353
}
353354

354355
/**
355-
* Called by the read path when the consumer has drained all data while
356-
* the writer is in the 'closing' state. Transitions to 'closed' and
357-
* resolves the pending end promise.
356+
* Called when the consumer pulls past all accepted data while the writer is
357+
* closing. Transitions to 'closed' and resolves the pending end promise.
358358
*/
359359
endDrained() {
360-
if (this.#writerState !== 'closing') return;
360+
if (this.#writerState !== 'closing' ||
361+
this.#slots.length > 0 ||
362+
this.#pendingWrites.length > 0) {
363+
return;
364+
}
361365
this.#writerState = 'closed';
362366
this.#cleanup();
363367
if (this.#pendingEnd) {
@@ -384,15 +388,14 @@ class PushQueue {
384388
this.#cleanup();
385389
this.#rejectPendingReads(this.#error);
386390
this.#rejectPendingDrains(this.#error);
391+
this.#rejectPendingWrites(this.#error);
387392

388393
if (wasClosing) {
389394
// Short-circuit the graceful drain: reject the pending end promise
390395
if (this.#pendingEnd) {
391396
this.#pendingEnd.reject(this.#error);
392397
this.#pendingEnd = null;
393398
}
394-
} else {
395-
this.#rejectPendingWrites(this.#error);
396399
}
397400
}
398401

@@ -446,10 +449,6 @@ class PushQueue {
446449
if (this.#slots.length > 0) {
447450
const result = this.#drain();
448451
this.#resolvePendingWrites();
449-
// After draining, check if writer was closing and buffer is now empty
450-
if (this.#writerState === 'closing' && this.#slots.length === 0) {
451-
this.endDrained();
452-
}
453452
return { __proto__: null, done: false, value: result };
454453
}
455454

@@ -555,7 +554,9 @@ class PushQueue {
555554
} catch (error) {
556555
pending.reject(error);
557556
}
558-
} else if (this.#writerState === 'closing' && this.#slots.length === 0) {
557+
} else if (this.#writerState === 'closing' &&
558+
this.#slots.length === 0 &&
559+
this.#pendingWrites.length === 0) {
559560
this.endDrained();
560561
const pending = this.#pendingReads.shift();
561562
pending.resolve({ __proto__: null, done: true, value: undefined });

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

Lines changed: 42 additions & 19 deletions

0 commit comments

Comments
 (0)