diff --git a/lib/internal/streams/readable.js b/lib/internal/streams/readable.js index 25819d14b7e8..f46cb1a2b421 100644 --- a/lib/internal/streams/readable.js +++ b/lib/internal/streams/readable.js @@ -131,6 +131,7 @@ const kFlowing = 1 << 24; const kHasPaused = 1 << 25; const kPaused = 1 << 26; const kDataListening = 1 << 27; +const kEndScheduled = 1 << 28; // TODO(benjamingr) it is likely slower to do it this way than with free functions function makeBitMapDescriptor(bit) { @@ -1757,8 +1758,8 @@ function endReadable(stream) { const state = stream._readableState; debug('endReadable'); - if ((state[kState] & kEndEmitted) === 0) { - state[kState] |= kEnded; + if ((state[kState] & (kEndEmitted | kEndScheduled)) === 0) { + state[kState] |= kEnded | kEndScheduled; process.nextTick(endReadableNT, state, stream); } } @@ -1766,6 +1767,12 @@ function endReadable(stream) { function endReadableNT(state, stream) { debug('endReadableNT'); + // The scheduled tick is running; allow endReadable() to schedule again. + // This matters both when the 'end' emission is skipped below (e.g. after + // an unshift()) and when the stream is later reset for reuse + // (see undestroy()), which clears kEndEmitted but not this flag. + state[kState] &= ~kEndScheduled; + // Check that we didn't get one last unshift. if ((state[kState] & (kErrored | kCloseEmitted | kEndEmitted)) === 0 && state.length === 0) { state[kState] |= kEndEmitted;