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

[WIP] events: use Map for events and limit copying #17074

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
18 changes: 15 additions & 3 deletions benchmark/events/ee-add-remove.js
Original file line number Diff line number Diff line change
@@ -2,18 +2,30 @@
const common = require('../common.js');
const events = require('events');

const bench = common.createBenchmark(main, { n: [25e4] });
const bench = common.createBenchmark(main, {
n: [5e6],
events: [0, 5],
listeners: [1, 5]
});

function main(conf) {
const n = conf.n | 0;
let n = conf.n | 0;
const eventsCOunt = conf.events | 0;
const listenersCount = conf.listeners | 0;

const ee = new events.EventEmitter();
const listeners = [];

if (listenersCount === 1)
n *= 2;

var k;
for (k = 0; k < 10; k += 1)
for (k = 0; k < listenersCount; k += 1)
listeners.push(function() {});

for (k = 0; k < eventsCOunt; k++)
ee.on(`dummyunused${k}`, () => {});

bench.start();
for (var i = 0; i < n; i += 1) {
const dummy = (i % 2 === 0) ? 'dummy0' : 'dummy1';
36 changes: 36 additions & 0 deletions benchmark/events/ee-emit-multi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';
const common = require('../common.js');
const EventEmitter = require('events').EventEmitter;

const bench = common.createBenchmark(main, {
n: [2e7],
listeners: [1, 5, 10],
});

function main(conf) {
var n = conf.n | 0;
const listeners = Math.max(conf.listeners | 0, 1);

const ee = new EventEmitter();

if (listeners === 1)
n *= 5;
else if (listeners === 5)
n *= 2;

for (var k = 0; k < listeners; k += 1) {
ee.on('dummy', function() {});
ee.on(`dummy${k}`, function() {});
}

bench.start();
for (var i = 0; i < n; i += 1) {
if (i % 3 === 0)
ee.emit('dummy', true, 5);
else if (i % 2 === 0)
ee.emit('dummy', true, 5, 10, false);
else
ee.emit('dummy');
}
bench.end(n);
}
13 changes: 10 additions & 3 deletions benchmark/events/ee-emit.js
Original file line number Diff line number Diff line change
@@ -3,20 +3,27 @@ const common = require('../common.js');
const EventEmitter = require('events').EventEmitter;

const bench = common.createBenchmark(main, {
n: [2e6],
n: [2e7],
argc: [0, 2, 4, 10],
listeners: [1, 5, 10],
});

function main(conf) {
const n = conf.n | 0;
var n = conf.n | 0;
const argc = conf.argc | 0;
const listeners = Math.max(conf.listeners | 0, 1);

const ee = new EventEmitter();

for (var k = 0; k < listeners; k += 1)
if (listeners === 1)
n *= 5;
else if (listeners === 5)
n *= 2;

for (var k = 0; k < listeners; k += 1) {
ee.on('dummy', function() {});
ee.on(`dummy${k}`, function() {});
}

var i;
switch (argc) {
30 changes: 30 additions & 0 deletions benchmark/events/ee-event-names.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';
const common = require('../common.js');
const EventEmitter = require('events').EventEmitter;

const bench = common.createBenchmark(main, { n: [1e6] });

function main(conf) {
const n = conf.n | 0;

const ee = new EventEmitter();

for (var k = 0; k < 9; k += 1) {
ee.on(`dummy0${k}`, function() {});
ee.on(`dummy1${k}`, function() {});
ee.on(`dummy2${k}`, function() {});
}

ee.removeAllListeners('dummy01');
ee.removeAllListeners('dummy11');
ee.removeAllListeners('dummy21');
ee.removeAllListeners('dummy06');
ee.removeAllListeners('dummy16');
ee.removeAllListeners('dummy26');

bench.start();
for (var i = 0; i < n; i += 1) {
ee.eventNames();
}
bench.end(n);
}
2 changes: 1 addition & 1 deletion benchmark/events/ee-listeners-many.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
const common = require('../common.js');
const EventEmitter = require('events').EventEmitter;

const bench = common.createBenchmark(main, { n: [5e6] });
const bench = common.createBenchmark(main, { n: [1e7] });

function main(conf) {
const n = conf.n | 0;
2 changes: 1 addition & 1 deletion benchmark/events/ee-listeners.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
const common = require('../common.js');
const EventEmitter = require('events').EventEmitter;

const bench = common.createBenchmark(main, { n: [5e6] });
const bench = common.createBenchmark(main, { n: [5e7] });

function main(conf) {
const n = conf.n | 0;
16 changes: 12 additions & 4 deletions benchmark/events/ee-once.js
Original file line number Diff line number Diff line change
@@ -2,19 +2,27 @@
const common = require('../common.js');
const EventEmitter = require('events').EventEmitter;

const bench = common.createBenchmark(main, { n: [2e7] });
const bench = common.createBenchmark(main, {
n: [5e6],
listeners: [1, 5]
});

function main(conf) {
const n = conf.n | 0;
let n = conf.n | 0;
const listeners = conf.listeners | 0;

if (listeners === 1)
n *= 2;

const ee = new EventEmitter();

function listener() {}

bench.start();
for (var i = 0; i < n; i += 1) {
for (var i = 0; i < n; ++i) {
const dummy = (i % 2 === 0) ? 'dummy0' : 'dummy1';
ee.once(dummy, listener);
for (var j = 0; j < listeners; ++j)
ee.once(dummy, listener);
ee.emit(dummy);
}
bench.end(n);
11 changes: 9 additions & 2 deletions doc/api/events.md
Original file line number Diff line number Diff line change
@@ -344,16 +344,23 @@ added: v3.2.0

Returns the number of listeners listening to the event named `eventName`.

### emitter.listeners(eventName)
### emitter.listeners(eventName[, unwrap])
<!-- YAML
added: v0.1.26
changes:
- version: v7.0.0
pr-url: https://github.com/nodejs/node/pull/6881
description: For listeners attached using `.once()` this returns the
original listeners instead of wrapper functions now.
- version: REPLACEME
pr-url: REPLACEME
description: Second optional argument `unwrap` allows the wrapped
listeners to be returned (such as ones created with
`.once()`)
-->
- `eventName` {any}
* `eventName` {any} The name of the event.
* `unwrap` {boolean} When set to true, will return the original listeners
instead of the wrapper functions created by `.once()`. **Default:** `true`

Returns a copy of the array of listeners for the event named `eventName`.

16 changes: 16 additions & 0 deletions lib/domain.js
Original file line number Diff line number Diff line change
@@ -94,6 +94,9 @@ class Domain extends EventEmitter {

this.members = [];
asyncHook.enable();

this.on('newListener', setHasErrorListener);
this.on('removeListener', removeHasErrorListener);
}
}

@@ -105,6 +108,19 @@ exports.create = exports.createDomain = function() {

// the active domain is always the one that we're currently in.
exports.active = null;

function setHasErrorListener(type) {
if (type === 'error')
this._hasErrorListener = true;
}

function removeHasErrorListener(type) {
if (type === 'error' && !this.listenerCount('error'))
this._hasErrorListener = false;
}

Domain.prototype._hasErrorListener = false;

Domain.prototype.members = undefined;

// Called by process._fatalException in case an error was thrown.
203 changes: 122 additions & 81 deletions lib/events.js
Original file line number Diff line number Diff line change
@@ -36,6 +36,7 @@ EventEmitter.usingDomains = false;

EventEmitter.prototype.domain = undefined;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined;

// By default EventEmitters will print a warning if more than 10 listeners are
@@ -81,7 +82,8 @@ EventEmitter.init = function() {
this._eventsCount = 0;
}

this._maxListeners = this._maxListeners || undefined;
if (!this._maxListeners)
this._maxListeners = undefined;
};

// Obviously not all Emitters should be limited to 10. This function allows
@@ -156,12 +158,13 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
}

if (typeof handler === 'function') {
handler.apply(this, args);
Reflect.apply(handler, this, args);
} else {
const len = handler.length;
const listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].apply(this, args);
const prevEmitting = handler.emitting;
handler.emitting = true;
for (var i = 0; i < handler.length; ++i)
Reflect.apply(handler[i], this, args);
handler.emitting = prevEmitting;
}

if (needDomainExit)
@@ -171,66 +174,67 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
};

function _addListener(target, type, listener, prepend) {
var m;
var events;
var existing;

if (typeof listener !== 'function') {
const errors = lazyErrors();
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'listener', 'Function');
}

events = target._events;
var events = target._events;
var list;
if (events === undefined) {
events = target._events = Object.create(null);
target._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener !== undefined) {
target.emit('newListener', type,
listener.listener ? listener.listener : listener);
target.emit('newListener', type, listener.listener || listener);

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

if (existing === undefined) {
if (list === undefined) {
// Optimize the case of one listener. Don't need the extra array object.
existing = events[type] = listener;
events[type] = listener;
++target._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] =
prepend ? [listener, existing] : [existing, listener];
// If we've already got an array, just append.
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
return target;
}

// Check for listener leak
if (!existing.warned) {
m = $getMaxListeners(target);
if (m && m > 0 && existing.length > m) {
existing.warned = true;
// No error code for this since it is a Warning
const w = new Error('Possible EventEmitter memory leak detected. ' +
`${existing.length} ${String(type)} listeners ` +
'added. Use emitter.setMaxListeners() to ' +
'increase limit');
w.name = 'MaxListenersExceededWarning';
w.emitter = target;
w.type = type;
w.count = existing.length;
process.emitWarning(w);
}
if (typeof list === 'function') {
// Adding the second element, need to change to array.
list = events[type] = prepend ? [listener, list] : [list, listener];
} else if (list.emitting) {
const { warned } = list;
list = events[type] =
arrayCloneWithElement(list, listener, prepend ? 1 : 0);
if (warned) {
list.warned = true;
return target;
}
} else if (prepend) {
list.unshift(listener);
} else {
list.push(listener);
}

// Check for listener leak
const m = $getMaxListeners(target);
if (m > 0 && list.length > m && !list.warned) {
list.warned = true;
// No error code for this since it is a Warning
const w = new Error('Possible EventEmitter memory leak detected. ' +
`${list.length} ${String(type)} listeners ` +
'added. Use emitter.setMaxListeners() to ' +
'increase limit');
w.name = 'MaxListenersExceededWarning';
w.emitter = target;
w.type = type;
w.count = list.length;
process.emitWarning(w);
}

return target;
@@ -247,11 +251,11 @@ EventEmitter.prototype.prependListener =
return _addListener(this, type, listener, true);
};

function onceWrapper(...args) {
function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
this.listener.apply(this.target, args);
Reflect.apply(this.listener, this.target, arguments);
}
}

@@ -286,36 +290,36 @@ EventEmitter.prototype.prependOnceListener =
// Emits a 'removeListener' event if and only if the listener was removed.
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
var list, events, position, i, originalListener;

if (typeof listener !== 'function') {
const errors = lazyErrors();
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'listener',
'Function');
}

events = this._events;
const events = this._events;
if (events === undefined)
return this;

list = events[type];
const list = events[type];
if (list === undefined)
return this;

if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0)
this._events = Object.create(null);
else {
delete events[type];
if (events.removeListener)
this.emit('removeListener', type, list.listener || listener);
}
} else if (typeof list !== 'function') {
position = -1;
events[type] = undefined;
--this._eventsCount;
if (events.removeListener !== undefined)
this.emit('removeListener', type, list.listener || listener);
return this;
}

for (i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || list[i].listener === listener) {
originalListener = list[i].listener;
if (typeof list !== 'function') {
let position = -1;

for (var i = list.length - 1; i >= 0; --i) {
const l = list[i];
if (l === listener || l.listener === listener) {
if (l.listener)
listener = l.listener;
position = i;
break;
}
@@ -324,19 +328,25 @@ EventEmitter.prototype.removeListener =
if (position < 0)
return this;

if (position === 0)
if (list.length === 2)
events[type] = list[position ? 0 : 1];
else if (list.emitting) {
const { warned } = list;
events[type] = sliceOne(list, position);
if (warned)
events[type].warned = true;
} else if (position === 0)
list.shift();
else if (position === list.length - 1)
list.pop();
else {
if (spliceOne === undefined)
spliceOne = require('internal/util').spliceOne;
spliceOne(list, position);
}

if (list.length === 1)
events[type] = list[0];

if (events.removeListener !== undefined)
this.emit('removeListener', type, originalListener || listener);
this.emit('removeListener', type, listener);
}

return this;
@@ -356,26 +366,23 @@ EventEmitter.prototype.removeAllListeners =
this._events = Object.create(null);
this._eventsCount = 0;
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0)
this._events = Object.create(null);
else
delete events[type];
events[type] = undefined;
--this._eventsCount;
}
return this;
}

// emit removeListener for all listeners on all events
if (arguments.length === 0) {
var keys = Object.keys(events);
const keys = Reflect.ownKeys(events);
var key;
for (i = 0; i < keys.length; ++i) {
key = keys[i];
if (key === 'removeListener') continue;
if (key === 'removeListener' || events[key] === undefined)
continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = Object.create(null);
this._eventsCount = 0;
return this;
}

@@ -385,15 +392,15 @@ EventEmitter.prototype.removeAllListeners =
this.removeListener(type, listeners);
} else if (listeners !== undefined) {
// LIFO order
for (i = listeners.length - 1; i >= 0; i--) {
for (i = listeners.length - 1; i >= 0; --i) {
this.removeListener(type, listeners[i]);
}
}

return this;
};

EventEmitter.prototype.listeners = function listeners(type) {
EventEmitter.prototype.listeners = function listeners(type, unwrap = true) {
const events = this._events;

if (events === undefined)
@@ -406,7 +413,7 @@ EventEmitter.prototype.listeners = function listeners(type) {
if (typeof evlistener === 'function')
return [evlistener.listener || evlistener];

return unwrapListeners(evlistener);
return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener);
};

EventEmitter.listenerCount = function(emitter, type) {
@@ -435,16 +442,50 @@ function listenerCount(type) {
}

EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
if (this._eventsCount === 0)
return [];

const events = this._events;
const keys = Reflect.ownKeys(events);
const actualEventNames = new Array(this._eventsCount);

for (var i = 0, j = 0; i < keys.length; ++i) {
if (events[keys[i]] !== undefined) {
actualEventNames[j] = keys[i];
j++;
}
}

return actualEventNames;
};

function arrayClone(arr, n) {
var copy = new Array(n);
for (var i = 0; i < n; ++i)
function arrayClone(arr) {
const copy = new Array(arr.length);
for (var i = 0; i < arr.length; ++i)
copy[i] = arr[i];
return copy;
}

function arrayCloneWithElement(arr, element, prepend) {
const len = arr.length;
const copy = new Array(len + 1);
for (var i = 0 + prepend; i < len + prepend; ++i)
copy[i] = arr[i - prepend];
copy[prepend ? 0 : len] = element;
return copy;
}

function sliceOne(arr, index) {
const len = arr.length - 1;
const copy = new Array(len);
for (var i = 0, offset = 0; i < len; ++i) {
if (i === index)
offset = 1;
copy[i] = arr[i + offset];
}
return copy;
}

function unwrapListeners(arr) {
const ret = new Array(arr.length);
for (var i = 0; i < ret.length; ++i) {
7 changes: 3 additions & 4 deletions lib/internal/bootstrap_node.js
Original file line number Diff line number Diff line change
@@ -12,7 +12,6 @@

function startup() {
const EventEmitter = NativeModule.require('events');
process._eventsCount = 0;

const origProcProto = Object.getPrototypeOf(process);
Object.setPrototypeOf(origProcProto, EventEmitter.prototype);
@@ -370,17 +369,17 @@
const { kAfter, kExecutionAsyncId,
kInitTriggerAsyncId } = async_wrap.constants;

process._fatalException = function(er) {
process._fatalException = function(er, shouldCatch = true) {
var caught;

// It's possible that kInitTriggerAsyncId was set for a constructor call
// that threw and was never cleared. So clear it now.
async_id_fields[kInitTriggerAsyncId] = 0;

if (process.domain && process.domain._errorHandler)
if (shouldCatch && process.domain && process.domain._errorHandler)
caught = process.domain._errorHandler(er);

if (!caught)
if (shouldCatch && !caught)
caught = process.emit('uncaughtException', er);

// If someone handled it, then great. otherwise, die in C++ land
13 changes: 3 additions & 10 deletions lib/vm.js
Original file line number Diff line number Diff line change
@@ -43,15 +43,15 @@ const realRunInThisContext = Script.prototype.runInThisContext;
const realRunInContext = Script.prototype.runInContext;

Script.prototype.runInThisContext = function(options) {
if (options && options.breakOnSigint && process._events.SIGINT) {
if (options && options.breakOnSigint && process.listenerCount('SIGINT')) {
return sigintHandlersWrap(realRunInThisContext, this, [options]);
} else {
return realRunInThisContext.call(this, options);
}
};

Script.prototype.runInContext = function(contextifiedSandbox, options) {
if (options && options.breakOnSigint && process._events.SIGINT) {
if (options && options.breakOnSigint && process.listenerCount('SIGINT')) {
return sigintHandlersWrap(realRunInContext, this,
[contextifiedSandbox, options]);
} else {
@@ -82,14 +82,7 @@ function createScript(code, options) {
// Remove all SIGINT listeners and re-attach them after the wrapped function
// has executed, so that caught SIGINT are handled by the listeners again.
function sigintHandlersWrap(fn, thisArg, argsArray) {
// Using the internal list here to make sure `.once()` wrappers are used,
// not the original ones.
let sigintListeners = process._events.SIGINT;

if (Array.isArray(sigintListeners))
sigintListeners = sigintListeners.slice();
else
sigintListeners = [sigintListeners];
const sigintListeners = process.listeners('SIGINT', false);

process.removeAllListeners('SIGINT');

15 changes: 5 additions & 10 deletions src/async_wrap.cc
Original file line number Diff line number Diff line change
@@ -154,8 +154,7 @@ static void DestroyAsyncIdsCallback(Environment* env, void* data) {
env->context(), Undefined(env->isolate()), 1, &async_id_value);

if (ret.IsEmpty()) {
ClearFatalExceptionHandlers(env);
FatalException(env->isolate(), try_catch);
FatalException(env->isolate(), try_catch, false);
UNREACHABLE();
}
}
@@ -175,8 +174,7 @@ void AsyncWrap::EmitPromiseResolve(Environment* env, double async_id) {
MaybeLocal<Value> ar = fn->Call(
env->context(), Undefined(env->isolate()), 1, &async_id_value);
if (ar.IsEmpty()) {
ClearFatalExceptionHandlers(env);
FatalException(env->isolate(), try_catch);
FatalException(env->isolate(), try_catch, false);
UNREACHABLE();
}
}
@@ -209,8 +207,7 @@ void AsyncWrap::EmitBefore(Environment* env, double async_id) {
MaybeLocal<Value> ar = fn->Call(
env->context(), Undefined(env->isolate()), 1, &async_id_value);
if (ar.IsEmpty()) {
ClearFatalExceptionHandlers(env);
FatalException(env->isolate(), try_catch);
FatalException(env->isolate(), try_catch, false);
UNREACHABLE();
}
}
@@ -245,8 +242,7 @@ void AsyncWrap::EmitAfter(Environment* env, double async_id) {
MaybeLocal<Value> ar = fn->Call(
env->context(), Undefined(env->isolate()), 1, &async_id_value);
if (ar.IsEmpty()) {
ClearFatalExceptionHandlers(env);
FatalException(env->isolate(), try_catch);
FatalException(env->isolate(), try_catch, false);
UNREACHABLE();
}
}
@@ -753,8 +749,7 @@ void AsyncWrap::EmitAsyncInit(Environment* env,
env->context(), object, arraysize(argv), argv);

if (ret.IsEmpty()) {
ClearFatalExceptionHandlers(env);
FatalException(env->isolate(), try_catch);
FatalException(env->isolate(), try_catch, false);
}
}

1 change: 0 additions & 1 deletion src/env.h
Original file line number Diff line number Diff line change
@@ -146,7 +146,6 @@ class ModuleWrap;
V(env_pairs_string, "envPairs") \
V(errno_string, "errno") \
V(error_string, "error") \
V(events_string, "_events") \
V(exiting_string, "_exiting") \
V(exit_code_string, "exitCode") \
V(exit_string, "exit") \
88 changes: 35 additions & 53 deletions src/node.cc
Original file line number Diff line number Diff line change
@@ -779,28 +779,6 @@ void* ArrayBufferAllocator::Allocate(size_t size) {

namespace {

bool DomainHasErrorHandler(const Environment* env,
const Local<Object>& domain) {
HandleScope scope(env->isolate());

Local<Value> domain_event_listeners_v = domain->Get(env->events_string());
if (!domain_event_listeners_v->IsObject())
return false;

Local<Object> domain_event_listeners_o =
domain_event_listeners_v.As<Object>();

Local<Value> domain_error_listeners_v =
domain_event_listeners_o->Get(env->error_string());

if (domain_error_listeners_v->IsFunction() ||
(domain_error_listeners_v->IsArray() &&
domain_error_listeners_v.As<Array>()->Length() > 0))
return true;

return false;
}

bool DomainsStackHasErrorHandler(const Environment* env) {
HandleScope scope(env->isolate());

@@ -818,7 +796,12 @@ bool DomainsStackHasErrorHandler(const Environment* env) {
return false;

Local<Object> domain = domain_v.As<Object>();
if (DomainHasErrorHandler(env, domain))

Local<Value> has_error_handler = domain->Get(
env->context(), OneByteString(env->isolate(),
"_hasErrorListener")).ToLocalChecked();

if (has_error_handler->IsTrue())
return true;
}

@@ -2401,7 +2384,8 @@ NO_RETURN void FatalError(const char* location, const char* message) {

void FatalException(Isolate* isolate,
Local<Value> error,
Local<Message> message) {
Local<Message> message,
bool should_catch) {
HandleScope scope(isolate);

Environment* env = Environment::GetCurrent(isolate);
@@ -2424,9 +2408,14 @@ void FatalException(Isolate* isolate,
// Do not call FatalException when _fatalException handler throws
fatal_try_catch.SetVerbose(false);

Local<Value> argv[2] = {
error,
Boolean::New(env->isolate(), should_catch)
};

// this will return true if the JS layer handled it, false otherwise
Local<Value> caught =
fatal_exception_function->Call(process_object, 1, &error);
Local<Value> caught = fatal_exception_function->Call(
env->context(), process_object, 2, argv).FromMaybe(Local<Value>());

if (fatal_try_catch.HasCaught()) {
// the fatal exception function threw, so we must exit
@@ -2449,39 +2438,38 @@ void FatalException(Isolate* isolate,
}


void FatalException(Isolate* isolate, const TryCatch& try_catch) {
void FatalException(Isolate* isolate,
const TryCatch& try_catch,
bool should_catch) {
HandleScope scope(isolate);
if (!try_catch.IsVerbose()) {
FatalException(isolate,
try_catch.Exception(),
try_catch.Message(),
should_catch);
}
}


void FatalException(Isolate* isolate,
const TryCatch& try_catch) {
HandleScope scope(isolate);
if (!try_catch.IsVerbose()) {
FatalException(isolate, try_catch.Exception(), try_catch.Message());
FatalException(isolate,
try_catch.Exception(),
try_catch.Message(),
true);
}
}


static void OnMessage(Local<Message> message, Local<Value> error) {
// The current version of V8 sends messages for errors only
// (thus `error` is always set).
FatalException(Isolate::GetCurrent(), error, message);
FatalException(Isolate::GetCurrent(), error, message, true);
}


void ClearFatalExceptionHandlers(Environment* env) {
Local<Object> process = env->process_object();
Local<Value> events =
process->Get(env->context(), env->events_string()).ToLocalChecked();

if (events->IsObject()) {
events.As<Object>()->Set(
env->context(),
OneByteString(env->isolate(), "uncaughtException"),
Undefined(env->isolate())).FromJust();
}

process->Set(
env->context(),
env->domain_string(),
Undefined(env->isolate())).FromJust();
}

// Call process.emitWarning(str), fmt is a snprintf() format string
void ProcessEmitWarning(Environment* env, const char* fmt, ...) {
char warning[1024];
@@ -3348,12 +3336,6 @@ void SetupProcessObject(Environment* env,
env->SetMethod(process, "_setupNextTick", SetupNextTick);
env->SetMethod(process, "_setupPromises", SetupPromises);
env->SetMethod(process, "_setupDomainUse", SetupDomainUse);

// pre-set _events object for faster emit checks
Local<Object> events_obj = Object::New(env->isolate());
CHECK(events_obj->SetPrototype(env->context(),
Null(env->isolate())).FromJust());
process->Set(env->events_string(), events_obj);
}


4 changes: 4 additions & 0 deletions src/node.h
Original file line number Diff line number Diff line change
@@ -366,6 +366,10 @@ NODE_DEPRECATED("Use ParseEncoding(isolate, ...)",
NODE_EXTERN void FatalException(v8::Isolate* isolate,
const v8::TryCatch& try_catch);

void FatalException(v8::Isolate* isolate,
const v8::TryCatch& try_catch,
bool should_catch);

NODE_DEPRECATED("Use FatalException(isolate, ...)",
inline void FatalException(const v8::TryCatch& try_catch) {
return FatalException(v8::Isolate::GetCurrent(), try_catch);
5 changes: 0 additions & 5 deletions src/node_internals.h
Original file line number Diff line number Diff line change
@@ -330,11 +330,6 @@ class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
uint32_t zero_fill_field_ = 1; // Boolean but exposed as uint32 to JS land.
};

// Clear any domain and/or uncaughtException handlers to force the error's
// propagation and shutdown the process. Use this to force the process to exit
// by clearing all callbacks that could handle the error.
void ClearFatalExceptionHandlers(Environment* env);

namespace Buffer {
v8::MaybeLocal<v8::Object> Copy(Environment* env, const char* data, size_t len);
v8::MaybeLocal<v8::Object> New(Environment* env, size_t size);
3 changes: 1 addition & 2 deletions src/node_url.cc
Original file line number Diff line number Diff line change
@@ -2183,8 +2183,7 @@ const Local<Value> URL::ToObject(Environment* env) const {
->Call(env->context(), undef, 9, argv);

if (ret.IsEmpty()) {
ClearFatalExceptionHandlers(env);
FatalException(isolate, try_catch);
FatalException(isolate, try_catch, false);
}

return ret.ToLocalChecked();
3 changes: 2 additions & 1 deletion test/parallel/test-process-emit.js
Original file line number Diff line number Diff line change
@@ -19,4 +19,5 @@ process.emit('normal', 'normalData');
process.emit(sym, 'symbolData');
process.emit('SIGPIPE', 'signalData');

assert.strictEqual(isNaN(process._eventsCount), false);
assert.strictEqual(process._eventsCount,
Reflect.ownKeys(process._events).length);
3 changes: 2 additions & 1 deletion test/parallel/test-readline-interface.js
Original file line number Diff line number Diff line change
@@ -39,7 +39,8 @@ class FakeInput extends EventEmitter {
function isWarned(emitter) {
for (const name in emitter) {
const listeners = emitter[name];
if (listeners.warned) return true;
if (listeners && listeners.warned)
return true;
}
return false;
}