Skip to content

Commit 4ccbe5e

Browse files
committed
stream: avoid duplicated endReadableNT scheduling
Calling read() on an ended stream multiple times before the microtask queue drains scheduled one endReadableNT tick per call, as the only guard was endEmitted, which is set inside the tick itself. A hello-world HTTP server was scheduling it four times per request while dumping the unread request body. Introduce a kEndScheduled flag armed when the tick is scheduled and cleared when it runs. Clearing it unconditionally matters for reused sockets: undestroy() resets endEmitted through the state descriptors but cannot reach this flag, and a stale value would block the 'end' event after a net.Socket reconnect. Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent 347e266 commit 4ccbe5e

1 file changed

Lines changed: 9 additions & 2 deletions

File tree

‎lib/internal/streams/readable.js‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ const kFlowing = 1 << 24;
131131
const kHasPaused = 1 << 25;
132132
const kPaused = 1 << 26;
133133
const kDataListening = 1 << 27;
134+
const kEndScheduled = 1 << 28;
134135

135136
// TODO(benjamingr) it is likely slower to do it this way than with free functions
136137
function makeBitMapDescriptor(bit) {
@@ -1757,15 +1758,21 @@ function endReadable(stream) {
17571758
const state = stream._readableState;
17581759

17591760
debug('endReadable');
1760-
if ((state[kState] & kEndEmitted) === 0) {
1761-
state[kState] |= kEnded;
1761+
if ((state[kState] & (kEndEmitted | kEndScheduled)) === 0) {
1762+
state[kState] |= kEnded | kEndScheduled;
17621763
process.nextTick(endReadableNT, state, stream);
17631764
}
17641765
}
17651766

17661767
function endReadableNT(state, stream) {
17671768
debug('endReadableNT');
17681769

1770+
// The scheduled tick is running; allow endReadable() to schedule again.
1771+
// This matters both when the 'end' emission is skipped below (e.g. after
1772+
// an unshift()) and when the stream is later reset for reuse
1773+
// (see undestroy()), which clears kEndEmitted but not this flag.
1774+
state[kState] &= ~kEndScheduled;
1775+
17691776
// Check that we didn't get one last unshift.
17701777
if ((state[kState] & (kErrored | kCloseEmitted | kEndEmitted)) === 0 && state.length === 0) {
17711778
state[kState] |= kEndEmitted;

0 commit comments

Comments
 (0)