Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
f62d869
events: add fast and slow path
rluvaton Apr 28, 2024
a7b0563
events: remove duplicate functions
rluvaton Apr 28, 2024
5c7a50d
events: remove debugger and cleanup
rluvaton Apr 28, 2024
e633fd8
events: fix missing handler this reference
rluvaton Apr 28, 2024
9a5fc65
events: fix some more things
rluvaton Apr 28, 2024
afd6515
events: remvoe unused
rluvaton Apr 28, 2024
569ef1e
events: fix tests
rluvaton Apr 28, 2024
3d25c6c
events: remove cmments
rluvaton Apr 28, 2024
864c23c
events: pass kShape to impl
rluvaton Apr 28, 2024
00e599b
events: remove comments
rluvaton Apr 28, 2024
c297ca4
events: improve performance of stream creation
rluvaton Apr 28, 2024
6d57e14
events: fix switch to slow path when no impl exists
rluvaton Apr 28, 2024
c035878
events: try to improve stream creation performance
rluvaton Apr 28, 2024
de6dfd7
events: fix lint errors
rluvaton Apr 28, 2024
99b9a1d
events: add comments and remove custom handling for newListener and l…
rluvaton Apr 28, 2024
245172d
events: remove comments in code
rluvaton Apr 28, 2024
33d5894
events: init when calling on before event initialized
rluvaton Apr 29, 2024
c25e14a
events: remove kCapture value
rluvaton Apr 29, 2024
4d74562
events: remove double init of kCapture
rluvaton Apr 29, 2024
d8cd35d
events: fix the performance penalty of dict object
rluvaton May 1, 2024
bb34b26
events: moved slow event emitter to own file
rluvaton May 1, 2024
ae87cd9
events: moved fast event emitter to own file
rluvaton May 1, 2024
682b820
events: remove split comments
rluvaton May 1, 2024
fe43317
events: extract EventEmitter to own file
rluvaton May 1, 2024
63fc389
events: remove temp code
rluvaton May 1, 2024
8d7ad6b
events: remove eslint ignore comments
rluvaton May 1, 2024
b260568
events: update comment and use original code
rluvaton May 1, 2024
731599d
events: convert FastEventEmitter class to function
rluvaton May 1, 2024
603f59a
events: improve stream creation performance
rluvaton May 1, 2024
53fddf2
events: replace check for kImpl
rluvaton May 1, 2024
5e77743
events: remove check assert not object
rluvaton May 1, 2024
3055bca
events: initiate impl if missing inside emit
rluvaton May 1, 2024
23ac15c
events: fixed bug for shared _events
rluvaton May 1, 2024
f766752
events: fixed emit and improved perf
rluvaton May 1, 2024
badf1e5
events: remove todos
rluvaton May 1, 2024
60d522c
events: format
rluvaton May 1, 2024
ddfeb85
events: replace SlowEventEmitter class with function
rluvaton May 1, 2024
7132c44
events: switch to slow path immediately if setting the _events property
rluvaton May 1, 2024
dd03904
events: return 0 in _eventsCount when no implementation exists
rluvaton May 1, 2024
46004a2
events: remove todos
rluvaton May 1, 2024
7b82c66
events: remove todos
rluvaton May 1, 2024
c073179
events: remove todos
rluvaton May 1, 2024
70692bf
events: fix not returning this
rluvaton May 1, 2024
6422d22
events: add internal event emitter to bootstrap module
rluvaton May 1, 2024
d880794
events: fix lint issues
rluvaton May 1, 2024
c7444a5
events: make initial events be preferred over missing events
rluvaton May 1, 2024
01f2e88
events: fix fixture after files move
rluvaton May 1, 2024
09b92d5
events: allow calling using different instance
rluvaton May 1, 2024
3db33d6
events: fix missing symbol
rluvaton May 1, 2024
fb9f74c
events: fix not using the right target
rluvaton May 1, 2024
d395895
events: fix fail to spread
rluvaton May 1, 2024
d08b36c
events: update
rluvaton May 2, 2024
3a88c16
events: fix failing to remove all listeners
rluvaton May 2, 2024
eb9784f
events: add support for object prototype keys as event names
rluvaton May 2, 2024
3928044
events: remove check for events should not be an object
rluvaton May 2, 2024
0632bd7
events: add support for object prototype keys as event names
rluvaton May 2, 2024
486e899
events: fix emit event name from object prototype keys
rluvaton May 2, 2024
4d5ee43
events: format
rluvaton May 2, 2024
62c7a01
events: move isEventUnsupportedForFastPath to fast event emitter file
rluvaton May 2, 2024
d3f8345
events: fix test-disable-proto-delete.js test
rluvaton May 2, 2024
94447a4
events: update jsdoc
rluvaton May 2, 2024
0a9177e
events: add support for --disable-proto=throw and --disable-proto=delete
rluvaton May 2, 2024
0fe1254
events: try to fix throw not working in the tests
rluvaton May 2, 2024
b59447c
events: fix events count
rluvaton May 2, 2024
e787353
events: try to improve stream creation perf
rluvaton May 12, 2024
acf96af
events: add back changes from c0a6320
rluvaton May 12, 2024
8758ff2
events: remove event emitter rebase issue
rluvaton May 12, 2024
190cb0d
events: set initial events count to 0
rluvaton May 13, 2024
1d27fc5
events: try to improve perf by checking if impl already exists
rluvaton May 13, 2024
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
859 changes: 8 additions & 851 deletions lib/events.js

Large diffs are not rendered by default.

700 changes: 700 additions & 0 deletions lib/internal/events/event_emitter.js

Large diffs are not rendered by default.

169 changes: 169 additions & 0 deletions lib/internal/events/fast_event_emitter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
'use strict';

const { kShapeMode, kErrorMonitor } = require('internal/events/symbols');
const {
throwErrorOnMissingErrorHandler,
addCatch,
} = require('internal/events/shared_internal_event_emitter');

/**
* This class is optimized for the case where there is only a single listener for each event.
* it supports kCapture
* but does not support event types that {@link isEventUnsupportedForFastPath} returns false for
*/
function FastEventEmitter(eventEmitterTranslationLayer, _events) {
this.eventEmitterTranslationLayer = eventEmitterTranslationLayer;
this._events = _events;
}

/**
* The events are stored here as Record<string, function | undefined>
*/
FastEventEmitter.prototype._events = undefined;
FastEventEmitter.prototype[kShapeMode] = undefined;
FastEventEmitter.prototype._eventsCount = 0;
FastEventEmitter.prototype.eventEmitterTranslationLayer = undefined;

/**
* Synchronously calls each of the listeners registered
* for the event.
* @param {string | symbol} type
* @param {...any} [args]
* @returns {boolean}
*/
FastEventEmitter.prototype.emit = function emit(type, ...args) {
const events = this._events;

if (type === 'error' && events?.error === undefined) {
throwErrorOnMissingErrorHandler.apply(this.eventEmitterTranslationLayer, args);
}

const handler = events[type];

if (handler === undefined) {
return false;
}

const result = handler.apply(this.eventEmitterTranslationLayer, args);

// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty
if (result !== undefined && result !== null) {
addCatch(this.eventEmitterTranslationLayer, result, type, args);
}

return true;
};

FastEventEmitter.prototype.isListenerAlreadyExists = function isListenerAlreadyExists(type) {
return this._events?.[type] !== undefined;
};

/**
* Adds a listener to the event emitter.
* @param {string | symbol} type
* @param {Function} listener
* @param {boolean} prepend not used here as we are in fast mode and only have single listener
*/
FastEventEmitter.prototype.addListener = function addListener(type, listener, prepend = undefined) {
let events;

events = this._events;
if (events === undefined) {
events = this._events = {};
this._eventsCount = 0;

// Not emitting `newListener` here as in fast path we don't have it
}

// Optimize the case of one listener. Don't need the extra array object.
events[type] = listener;
++this._eventsCount;
};

/**
* Removes the specified `listener`.
* @param {string | symbol} type
* @param {Function} listener
*/
FastEventEmitter.prototype.removeListener = function removeListener(type, listener) {
const events = this._events;
if (events === undefined)
return undefined;

const list = events[type];
if (list === undefined || (list !== listener && list.listener !== listener))
return undefined;

this._eventsCount -= 1;

if (this[kShapeMode]) {
events[type] = undefined;
} else if (this._eventsCount === 0) {
this._events = { };
} else {
delete events[type];
// Not emitting `removeListener` here as in fast path we don't have it
}
};


/**
* Removes all listeners from the event emitter. (Only
* removes listeners for a specific event name if specified
* as `type`).
* @param {string | symbol} [type]
*/
FastEventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
const events = this._events;
if (events === undefined)
return undefined;

if (arguments.length === 0) {
this._events = { };
this._eventsCount = 0;
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0)
this._events = { };
else
delete events[type];
}

this[kShapeMode] = false;

return undefined;
};


function isEventUnsupportedForFastPath(type) {
return (
// Not supporting newListener and removeListener events in fast path
// as they can add new listeners in the middle of the process
type === 'newListener' ||
type === 'removeListener' ||

// Not supporting errorMonitor event as it has special handling
type === kErrorMonitor ||

// Not supporting Object prototype keys as fast path object is not an object with null prototype
type === 'constructor' ||
type === '__defineGetter__' ||
type === '__defineSetter__' ||
type === 'hasOwnProperty' ||
type === '__lookupGetter__' ||
type === '__lookupSetter__' ||
type === 'isPrototypeOf' ||
type === 'propertyIsEnumerable' ||
type === 'toString' ||
type === 'valueOf' ||
type === '__proto__' ||
type === 'toLocaleString'
);
}


module.exports = {
FastEventEmitter,
isEventUnsupportedForFastPath,
};
151 changes: 151 additions & 0 deletions lib/internal/events/shared_internal_event_emitter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
'use strict';

const {
ArrayPrototypeJoin,
ArrayPrototypeSlice,
ArrayPrototypeSplice,
Error,
ErrorCaptureStackTrace,
FunctionPrototypeBind,
ObjectDefineProperty,
StringPrototypeSplit,
} = primordials;

const { kCapture, kRejection } = require('internal/events/symbols');
const { inspect, identicalSequenceRange } = require('internal/util/inspect');

const {
codes: {
ERR_UNHANDLED_ERROR,
},
kEnhanceStackBeforeInspector,
} = require('internal/errors');

let EventEmitter;

function arrayClone(arr) {
// At least since V8 8.3, this implementation is faster than the previous
// which always used a simple for-loop
switch (arr.length) {
case 2: return [arr[0], arr[1]];
case 3: return [arr[0], arr[1], arr[2]];
case 4: return [arr[0], arr[1], arr[2], arr[3]];
case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]];
case 6: return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]];
}
return ArrayPrototypeSlice(arr);
}


function addCatch(that, promise, type, args) {
if (!that[kCapture]) {
return;
}

// Handle Promises/A+ spec, then could be a getter
// that throws on second use.
try {
const then = promise.then;

if (typeof then === 'function') {
then.call(promise, undefined, function(err) {
// The callback is called with nextTick to avoid a follow-up
// rejection from this promise.
process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args);
});
}
} catch (err) {
that.emit('error', err);
}
}

function emitUnhandledRejectionOrErr(ee, err, type, args) {
if (typeof ee[kRejection] === 'function') {
ee[kRejection](err, type, ...args);
} else {
// We have to disable the capture rejections mechanism, otherwise
// we might end up in an infinite loop.
const prev = ee[kCapture];

// If the error handler throws, it is not catchable and it
// will end up in 'uncaughtException'. We restore the previous
// value of kCapture in case the uncaughtException is present
// and the exception is handled.
try {
ee[kCapture] = false;
ee.emit('error', err);
} finally {
ee[kCapture] = prev;
}
}
}


function throwErrorOnMissingErrorHandler(...args) {
let er;
if (args.length > 0)
er = args[0];

if (er instanceof Error) {
try {
const capture = {};
EventEmitter ??= require('internal/events/event_emitter').EventEmitter;
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, this, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.
}

// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
throw err; // Unhandled 'error' event
}


function enhanceStackTrace(err, own) {
let ctorInfo = '';
try {
const { name } = this.constructor;
if (name !== 'EventEmitter')
ctorInfo = ` on ${name} instance`;
} catch {
// Continue regardless of error.
}
const sep = `\nEmitted 'error' event${ctorInfo} at:\n`;

const errStack = ArrayPrototypeSlice(
StringPrototypeSplit(err.stack, '\n'), 1);
const ownStack = ArrayPrototypeSlice(
StringPrototypeSplit(own.stack, '\n'), 1);

const { len, offset } = identicalSequenceRange(ownStack, errStack);
if (len > 0) {
ArrayPrototypeSplice(ownStack, offset + 1, len - 2,
' [... lines matching original stack trace ...]');
}

return err.stack + sep + ArrayPrototypeJoin(ownStack, '\n');
}

module.exports = {
arrayClone,
addCatch,
throwErrorOnMissingErrorHandler,
};
Loading