-
-
Notifications
You must be signed in to change notification settings - Fork 31.4k
domain: allow concurrent user-land impl #26326
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,17 +28,17 @@ | |
|
||
const util = require('util'); | ||
const EventEmitter = require('events'); | ||
const { | ||
ERR_DOMAIN_CALLBACK_NOT_AVAILABLE, | ||
ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE, | ||
ERR_UNHANDLED_ERROR | ||
} = require('internal/errors').codes; | ||
const { createHook } = require('async_hooks'); | ||
|
||
// TODO(addaleax): Use a non-internal solution for this. | ||
const kWeak = Symbol('kWeak'); | ||
const { WeakReference } = internalBinding('util'); | ||
|
||
// Communicate with events module, but don't require that | ||
// module to have to load this one, since this module has | ||
// a few side effects. | ||
EventEmitter.usingDomains = true; | ||
|
||
// Overwrite process.domain with a getter/setter that will allow for more | ||
// effective optimizations | ||
var _domain = [null]; | ||
|
@@ -80,23 +80,7 @@ const asyncHook = createHook({ | |
} | ||
}); | ||
|
||
// When domains are in use, they claim full ownership of the | ||
// uncaught exception capture callback. | ||
if (process.hasUncaughtExceptionCaptureCallback()) { | ||
throw new ERR_DOMAIN_CALLBACK_NOT_AVAILABLE(); | ||
} | ||
|
||
// Get the stack trace at the point where `domain` was required. | ||
// eslint-disable-next-line no-restricted-syntax | ||
const domainRequireStack = new Error('require(`domain`) at this point').stack; | ||
|
||
const { setUncaughtExceptionCaptureCallback } = process; | ||
process.setUncaughtExceptionCaptureCallback = function(fn) { | ||
const err = new ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE(); | ||
err.stack = err.stack + '\n' + '-'.repeat(40) + '\n' + domainRequireStack; | ||
throw err; | ||
}; | ||
|
||
|
||
let sendMakeCallbackDeprecation = false; | ||
function emitMakeCallbackDeprecation() { | ||
|
@@ -123,10 +107,10 @@ function topLevelDomainCallback(cb, ...args) { | |
return ret; | ||
} | ||
|
||
// It's possible to enter one domain while already inside | ||
// another one. The stack is each entered domain. | ||
const stack = []; | ||
exports._stack = stack; | ||
|
||
// It's possible to enter one domain while already inside another one. The stack | ||
// is each entered domain. | ||
const stack = exports._stack = process._domainsStack; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you revert the change to this line? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean? How would There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The general goal is to avoid doing this would work: const stack = process._domainStack;
exports._stack = stack; // no change to this line or better yet, remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good, will do! |
||
internalBinding('domain').enable(topLevelDomainCallback); | ||
|
||
function updateExceptionCapture() { | ||
|
@@ -181,6 +165,8 @@ class Domain extends EventEmitter { | |
} | ||
} | ||
|
||
Domain[EventEmitter.domainSym] = true; | ||
|
||
exports.Domain = Domain; | ||
|
||
exports.create = exports.createDomain = function createDomain() { | ||
|
@@ -307,7 +293,7 @@ Domain.prototype.add = function(ee) { | |
// d.add(e); | ||
// e.add(d); | ||
// e.emit('error', er); // RangeError, stack overflow! | ||
if (this.domain && (ee instanceof Domain)) { | ||
if (this.domain && ee[EventEmitter.domainSym]) { | ||
for (var d = this.domain; d; d = d.domain) { | ||
if (ee === d) return; | ||
} | ||
|
@@ -410,52 +396,3 @@ Domain.prototype.bind = function(cb) { | |
|
||
return runBound; | ||
}; | ||
|
||
// Override EventEmitter methods to make it domain-aware. | ||
EventEmitter.usingDomains = true; | ||
|
||
const eventInit = EventEmitter.init; | ||
EventEmitter.init = function() { | ||
this.domain = null; | ||
if (exports.active && !(this instanceof exports.Domain)) { | ||
this.domain = exports.active; | ||
} | ||
|
||
return eventInit.call(this); | ||
}; | ||
|
||
const eventEmit = EventEmitter.prototype.emit; | ||
EventEmitter.prototype.emit = function(...args) { | ||
const domain = this.domain; | ||
|
||
const type = args[0]; | ||
const shouldEmitError = type === 'error' && | ||
this.listenerCount(type) > 0; | ||
|
||
// Just call original `emit` if current EE instance has `error` | ||
// handler, there's no active domain or this is process | ||
if (shouldEmitError || domain === null || domain === undefined || | ||
this === process) { | ||
return Reflect.apply(eventEmit, this, args); | ||
} | ||
|
||
if (type === 'error') { | ||
const er = args.length > 1 && args[1] ? | ||
args[1] : new ERR_UNHANDLED_ERROR(); | ||
|
||
if (typeof er === 'object') { | ||
er.domainEmitter = this; | ||
er.domain = domain; | ||
er.domainThrown = false; | ||
} | ||
|
||
domain.emit('error', er); | ||
return false; | ||
} | ||
|
||
domain.enter(); | ||
const ret = Reflect.apply(eventEmit, this, args); | ||
domain.exit(); | ||
|
||
return ret; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,7 +32,9 @@ module.exports = EventEmitter; | |
EventEmitter.EventEmitter = EventEmitter; | ||
|
||
EventEmitter.usingDomains = false; | ||
EventEmitter.domainSym = Symbol('isDomainLike'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. symbol descriptions should match how they are accessed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good, will fix! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’d prefer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sounds good, given there might be a lot of different opinions on the naming of this property, let's leave that to the final stages of this review :)
Yes, the idea is to make it available to other implementations. You can take a look at how it's used in the proof-of-concept that I wrote to test this. |
||
|
||
EventEmitter.prototype.domain = undefined; | ||
EventEmitter.prototype._events = undefined; | ||
EventEmitter.prototype._eventsCount = 0; | ||
EventEmitter.prototype._maxListeners = undefined; | ||
|
@@ -72,6 +74,12 @@ Object.defineProperty(EventEmitter, 'defaultMaxListeners', { | |
}); | ||
|
||
EventEmitter.init = function() { | ||
this.domain = null; | ||
// If there is an active domain, then attach to it, except if the event | ||
// emitter being constructed is itself an instance of a domain-like class. | ||
if (EventEmitter.usingDomains && !this.constructor[EventEmitter.domainSym]) { | ||
this.domain = process.domain; | ||
} | ||
|
||
if (this._events === undefined || | ||
this._events === Object.getPrototypeOf(this)._events) { | ||
|
@@ -152,12 +160,25 @@ EventEmitter.prototype.emit = function emit(type, ...args) { | |
else if (!doError) | ||
return false; | ||
|
||
const domain = this.domain; | ||
|
||
// If there is no 'error' event listener then throw. | ||
if (doError) { | ||
let er; | ||
if (args.length > 0) | ||
er = args[0]; | ||
if (er instanceof Error) { | ||
if (domain !== null && domain !== undefined) { | ||
if (!er) { | ||
const errors = lazyErrors(); | ||
er = new errors.ERR_UNHANDLED_ERROR(); | ||
} | ||
if (typeof er === 'object' && er !== null) { | ||
er.domainEmitter = this; | ||
er.domain = domain; | ||
er.domainThrown = false; | ||
} | ||
domain.emit('error', er); | ||
} else if (er instanceof Error) { | ||
try { | ||
const { kExpandStackSymbol } = require('internal/util'); | ||
const capture = {}; | ||
|
@@ -171,28 +192,36 @@ EventEmitter.prototype.emit = function emit(type, ...args) { | |
// 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 | ||
} | ||
} else { | ||
let stringifiedEr; | ||
const { inspect } = require('internal/util/inspect'); | ||
try { | ||
stringifiedEr = inspect(er); | ||
} catch { | ||
stringifiedEr = er; | ||
} | ||
|
||
let stringifiedEr; | ||
const { inspect } = require('internal/util/inspect'); | ||
try { | ||
stringifiedEr = inspect(er); | ||
} catch { | ||
stringifiedEr = er; | ||
// At least give some kind of context to the user | ||
const errors = lazyErrors(); | ||
const err = new errors.ERR_UNHANDLED_ERROR(stringifiedEr); | ||
err.context = er; | ||
throw err; // Unhandled 'error' event | ||
} | ||
|
||
// At least give some kind of context to the user | ||
const errors = lazyErrors(); | ||
const err = new errors.ERR_UNHANDLED_ERROR(stringifiedEr); | ||
err.context = er; | ||
throw err; // Unhandled 'error' event | ||
return false; | ||
} | ||
|
||
const handler = events[type]; | ||
|
||
if (handler === undefined) | ||
return false; | ||
|
||
let needDomainExit = false; | ||
if (domain !== null && domain !== undefined && this !== process) { | ||
domain.enter(); | ||
needDomainExit = true; | ||
} | ||
|
||
if (typeof handler === 'function') { | ||
Reflect.apply(handler, this, args); | ||
} else { | ||
|
@@ -202,6 +231,9 @@ EventEmitter.prototype.emit = function emit(type, ...args) { | |
Reflect.apply(listeners[i], this, args); | ||
} | ||
|
||
if (needDomainExit) | ||
domain.exit(); | ||
|
||
return true; | ||
}; | ||
|
||
|
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a special section for error codes that were removed, could you move these there (and add a
removed: REPLACEME
YAML tag)?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes 👍 I keep forgetting about that, my apologies.