-
-
Notifications
You must be signed in to change notification settings - Fork 31.6k
domain: allow concurrent user-land impl #26326
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
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.