From 59f7316b8f24fd7d0553a8fe6653e446a11f3384 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Thu, 26 Oct 2023 08:36:55 +0200 Subject: [PATCH] stream: avoid calls to listenerCount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/50357 Reviewed-By: Raz Luvaton Reviewed-By: Vinícius Lourenço Claro Cardoso Reviewed-By: Yagiz Nizipli Reviewed-By: Matteo Collina Reviewed-By: Benjamin Gruenbaum --- lib/internal/streams/readable.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/internal/streams/readable.js b/lib/internal/streams/readable.js index a18c6d32d7b92c..80798a35dcb34f 100644 --- a/lib/internal/streams/readable.js +++ b/lib/internal/streams/readable.js @@ -118,6 +118,7 @@ const kHasFlowing = 1 << 23; const kFlowing = 1 << 24; const kHasPaused = 1 << 25; const kPaused = 1 << 26; +const kDataListening = 1 << 27; // TODO(benjamingr) it is likely slower to do it this way than with free functions function makeBitMapDescriptor(bit) { @@ -527,8 +528,7 @@ function canPushMore(state) { } function addChunk(stream, state, chunk, addToFront) { - if ((state[kState] & (kFlowing | kSync)) === kFlowing && state.length === 0 && - stream.listenerCount('data') > 0) { + if ((state[kState] & (kFlowing | kSync | kDataListening)) === (kFlowing | kDataListening) && state.length === 0) { // Use the guard to avoid creating `Set()` repeatedly // when we have multiple pipes. if ((state[kState] & kMultiAwaitDrain) !== 0) { @@ -1062,7 +1062,7 @@ function pipeOnDrain(src, dest) { } if ((!state.awaitDrainWriters || state.awaitDrainWriters.size === 0) && - src.listenerCount('data')) { + (state[kState] & kDataListening) !== 0) { src.resume(); } }; @@ -1109,6 +1109,8 @@ Readable.prototype.on = function(ev, fn) { const state = this._readableState; if (ev === 'data') { + state[kState] |= kDataListening; + // Update readableListening so that resume() may be a no-op // a few lines down. This is needed to support once('readable'). state[kState] |= this.listenerCount('readable') > 0 ? kReadableListening : 0; @@ -1135,6 +1137,8 @@ Readable.prototype.on = function(ev, fn) { Readable.prototype.addListener = Readable.prototype.on; Readable.prototype.removeListener = function(ev, fn) { + const state = this._readableState; + const res = Stream.prototype.removeListener.call(this, ev, fn); @@ -1146,6 +1150,8 @@ Readable.prototype.removeListener = function(ev, fn) { // resume within the same tick will have no // effect. process.nextTick(updateReadableListening, this); + } else if (ev === 'data' && this.listenerCount('data') === 0) { + state[kState] &= ~kDataListening; } return res; @@ -1184,7 +1190,7 @@ function updateReadableListening(self) { state[kState] |= kHasFlowing | kFlowing; // Crude way to check if we should resume. - } else if (self.listenerCount('data') > 0) { + } else if ((state[kState] & kDataListening) !== 0) { self.resume(); } else if ((state[kState] & kReadableListening) === 0) { state[kState] &= ~(kHasFlowing | kFlowing);