events: avoid cloning listeners array on every emit

PR-URL: https://github.com/nodejs/node/pull/62261
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
Gürgün Dayıoğlu authored and GitHub committed 2026-03-29 14:53:52 +00:00
1 parent adac077484
commit 7ffbb768fe
2 files changed
+62 -17

No files matched your search

+44 -17
View File
@@ -87,6 +87,7 @@ const { addAbortListener } = require('internal/events/abort_listener');
const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kShapeMode = Symbol('shapeMode');
const kEmitting = Symbol('events.emitting');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
const kMaxEventTargetListenersWarned =
Symbol('events.maxEventTargetListenersWarned');
@@ -514,19 +515,22 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
addCatch(this, result, type, args);
}
} else {
const len = handler.length;
const listeners = arrayClone(handler);
for (let i = 0; i < len; ++i) {
const result = ReflectApply(listeners[i], this, args);
handler[kEmitting]++;
try {
for (let i = 0; i < handler.length; ++i) {
const result = ReflectApply(handler[i], this, args);
// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty.
// This code is duplicated because extracting it away
// would make it non-inlineable.
if (result !== undefined && result !== null) {
addCatch(this, result, type, args);
// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty.
// This code is duplicated because extracting it away
// would make it non-inlineable.
if (result !== undefined && result !== null) {
addCatch(this, result, type, args);
}
}
} finally {
handler[kEmitting]--;
}
}
@@ -565,13 +569,17 @@ function _addListener(target, type, listener, prepend) {
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] =
prepend ? [listener, existing] : [existing, listener];
existing = prepend ? [listener, existing] : [existing, listener];
existing[kEmitting] = 0;
events[type] = existing;
// If we've already got an array, just append.
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
existing = ensureMutableListenerArray(events, type, existing);
if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
}
// Check for listener leak
@@ -674,7 +682,7 @@ EventEmitter.prototype.removeListener =
if (events === undefined)
return this;
const list = events[type];
let list = events[type];
if (list === undefined)
return this;
@@ -692,6 +700,7 @@ EventEmitter.prototype.removeListener =
if (events.removeListener !== undefined)
this.emit('removeListener', type, list.listener || listener);
} else if (typeof list !== 'function') {
list = ensureMutableListenerArray(events, type, list);
let position = -1;
for (let i = list.length - 1; i >= 0; i--) {
@@ -875,6 +884,24 @@ function arrayClone(arr) {
return ArrayPrototypeSlice(arr);
}
function cloneEventListenerArray(arr) {
const copy = arrayClone(arr);
copy[kEmitting] = 0;
if (arr.warned) {
copy.warned = true;
}
return copy;
}
function ensureMutableListenerArray(events, type, handler) {
if (handler[kEmitting] > 0) {
const copy = cloneEventListenerArray(handler);
events[type] = copy;
return copy;
}
return handler;
}
function unwrapListeners(arr) {
const ret = arrayClone(arr);
for (let i = 0; i < ret.length; ++i) {
@@ -78,3 +78,21 @@ assert.strictEqual(e.listeners('foo').length, 2);
e.emit('foo');
assert.deepStrictEqual(['callback2', 'callback3'], callbacks_called);
assert.strictEqual(e.listeners('foo').length, 0);
// Verify that removing all callbacks while in emit allows the current emit to
// propagate to all listeners.
callbacks_called = [];
function callback4() {
callbacks_called.push('callback4');
e.removeAllListeners('foo');
}
e.on('foo', callback4);
e.on('foo', callback2);
e.on('foo', callback3);
assert.strictEqual(e.listeners('foo').length, 3);
e.emit('foo');
assert.deepStrictEqual(['callback4', 'callback2', 'callback3'],
callbacks_called);
assert.strictEqual(e.listeners('foo').length, 0);