Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

events: optimize adding and removing of listeners more #785

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 82 additions & 54 deletions lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ EventEmitter.init = function() {
}
}

if (!this._events || this._events === Object.getPrototypeOf(this)._events)
if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
this._events = {};
this._eventsCount = 0;
}

this._maxListeners = this._maxListeners || undefined;
};
Expand Down Expand Up @@ -101,18 +103,32 @@ function emitThree(handler, isFn, self, arg1, arg2, arg3) {
}
}

function emitMany(handler, isFn, self, args) {
if (isFn)
handler.apply(self, args);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].apply(self, args);
}
}

EventEmitter.prototype.emit = function emit(type) {
var er, handler, len, args, i, listeners, events, domain;
var er, handler, len, args, i, events, domain;
var needDomainExit = false;
var doError = (type === 'error');

events = this._events;
if (!events)
events = this._events = {};
if (events)
doError = (doError && events.error == null);
else if (!doError)
return false;

domain = this.domain;

// If there is no 'error' event listener then throw.
if (type === 'error' && !events.error) {
if (doError) {
er = arguments[1];
if (domain) {
if (!er)
Expand Down Expand Up @@ -160,14 +176,7 @@ EventEmitter.prototype.emit = function emit(type) {
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];
if (isFn)
handler.apply(this, args);
else {
len = handler.length;
listeners = arrayClone(handler, len);
for (i = 0; i < len; ++i)
listeners[i].apply(this, args);
}
emitMany(handler, isFn, this, args);
}

if (needDomainExit)
Expand All @@ -185,39 +194,47 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
throw new TypeError('listener must be a function');

events = this._events;
if (!events)
if (!events) {
events = this._events = {};
else {
this._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener) {
this.emit('newListener', type,
typeof listener.listener === 'function' ?
listener.listener : listener);
listener.listener ? listener.listener : listener);

// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
events = this._events;
}
existing = events[type];
}

if (!existing)
if (!existing) {
// Optimize the case of one listener. Don't need the extra array object.
existing = events[type] = listener;
else if (typeof existing !== 'function')
// If we've already got an array, just append.
existing.push(listener);
else
// Adding the second element, need to change to array.
existing = events[type] = [existing, listener];

// Check for listener leak
if (typeof existing !== 'function' && !existing.warned) {
m = $getMaxListeners(this);
if (m && m > 0 && existing.length > m) {
existing.warned = true;
console.error('(node) warning: possible EventEmitter memory ' +
'leak detected. %d %s listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.',
existing.length, type);
console.trace();
++this._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] = [existing, listener];
} else {
// If we've already got an array, just append.
existing.push(listener);
}

// Check for listener leak
if (!existing.warned) {
m = $getMaxListeners(this);
if (m && m > 0 && existing.length > m) {
existing.warned = true;
console.error('(node) warning: possible EventEmitter memory ' +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be io.js instead of node now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shrug I didn't change anything in that whole block, just wrapped it all within an else {}.

'leak detected. %d %s listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.',
existing.length, type);
console.trace();
}
}
}

Expand Down Expand Up @@ -250,7 +267,7 @@ EventEmitter.prototype.once = function once(type, listener) {
// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
var list, events, position, length, i;
var list, events, position, i;

if (typeof listener !== 'function')
throw new TypeError('listener must be a function');
Expand All @@ -263,17 +280,18 @@ EventEmitter.prototype.removeListener =
if (!list)
return this;

length = list.length;
position = -1;

if (list === listener ||
(typeof list.listener === 'function' && list.listener === listener)) {
delete events[type];
if (events.removeListener)
this.emit('removeListener', type, listener);

if (list === listener || (list.listener && list.listener === listener)) {
if (--this._eventsCount === 0)
this._events = {};
else {
delete events[type];
if (events.removeListener)
this.emit('removeListener', type, listener);
}
} else if (typeof list !== 'function') {
for (i = length; i-- > 0;) {
position = -1;

for (i = list.length; i-- > 0;) {
if (list[i] === listener ||
(list[i].listener && list[i].listener === listener)) {
position = i;
Expand All @@ -285,8 +303,12 @@ EventEmitter.prototype.removeListener =
return this;

if (list.length === 1) {
list.length = 0;
delete events[type];
list[0] = undefined;
if (--this._eventsCount === 0) {
this._events = {};
return this;
} else
delete events[type];
} else {
spliceOne(list, position);
}
Expand All @@ -308,10 +330,15 @@ EventEmitter.prototype.removeAllListeners =

// not listening for removeListener, no need to emit
if (!events.removeListener) {
if (arguments.length === 0)
if (arguments.length === 0) {
this._events = {};
else if (events[type])
delete events[type];
this._eventsCount = 0;
} else if (events[type]) {
if (--this._eventsCount === 0)
this._events = {};
else
delete events[type];
}
return this;
}

Expand All @@ -325,19 +352,20 @@ EventEmitter.prototype.removeAllListeners =
}
this.removeAllListeners('removeListener');
this._events = {};
this._eventsCount = 0;
return this;
}

listeners = events[type];

if (typeof listeners === 'function') {
this.removeListener(type, listeners);
} else if (Array.isArray(listeners)) {
} else if (listeners) {
// LIFO order
while (listeners.length)
do {
this.removeListener(type, listeners[listeners.length - 1]);
} while (listeners[0]);
}
delete events[type];

return this;
};
Expand Down