-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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: remove domain handling from events to domain #17403
events: remove domain handling from events to domain #17403
Conversation
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.
Have you benchmarked any potential performance change this could bring for use cases of EventEmitter with domain enabled?
lib/events.js
Outdated
@@ -141,20 +116,13 @@ EventEmitter.prototype.emit = function emit(type, ...args) { | |||
err.context = er; | |||
throw err; | |||
} | |||
return false; |
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.
Why did this get removed?
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.
It has moved here https://github.com/nodejs/node/pull/17403/files/426afc70832c47f18f9afd9f90f35c1e554c8d3a#diff-0b93f5d72b0d34f5b972b661935344c3R415
The only case when this code was reached was when a domain was present, other situation throw.
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.
Nice!
lib/events.js
Outdated
} | ||
domain.emit('error', er); | ||
} else if (er instanceof Error) { | ||
if (er instanceof Error) { | ||
throw er; // Unhandled 'error' event | ||
} else { |
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.
I should remove the else
here
I would be okay with a slight performance regression for a long-deprecated module like |
@TimothyGu I have no data regarding perf impact here. |
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.
I'm +1 on the cleanup and not too concerned about performance implications.
Re: benchmarks. I don't think we have any for event emitters + domains. |
lib/domain.js
Outdated
@@ -387,3 +382,51 @@ Domain.prototype.bind = function(cb) { | |||
|
|||
return runBound; | |||
}; | |||
|
|||
// communicate with events module, but don't require that |
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 should change this comment. It serves no purpose now, other than backward compatibility.
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.
Updated!
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.
Excellent. Only one minor nit.
Yet, for a long time (years)
No need to even use domains in a benchmark, measuring the difference between EventEmitter usage with versus without |
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.
Change itself LGTM.
lib/domain.js
Outdated
@@ -387,3 +382,50 @@ Domain.prototype.bind = function(cb) { | |||
|
|||
return runBound; | |||
}; | |||
|
|||
// override methods on event to have | |||
// EventEmitters going allong with domains |
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.
Nit: typos
// Override EventEmitter methods to make it domain-aware.
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.
Updated!
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.
If this is only meant for 9.x & on, then instead of ...args
I would recommend using arguments
. There's no risk in V8 6.2 and it's faster, since we're just forwarding the full argument list with no slicing.
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.
Making my requests more explicit. Would like these changes to get made before it lands but if not, will open a PR afterwards.
}; | ||
|
||
const eventEmit = EventEmitter.prototype.emit; | ||
EventEmitter.prototype.emit = function emit(...args) { |
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.
@vdeturckheim Would you mind changing this to emit()
and then using arguments
everywhere? I realize performance is not that big of a deal but this will actually make a substantial difference.
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.
@apapirovski are you sure it's faster? (In recent V8)
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.
Yeah, I'm certain because I checked it very recently. ...args
is only faster when arguments requires slicing or manually copying in situations like emit(type, ...args)
.
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.
@apapirovski very interesting can you share a benchmark showing that? I'm pretty sure ...args
is optimized.
cc @bmeurer
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.
It has been optimized but in practical usage it still seems to perform worse. I've run into this working on nextTick
, events
and timers
.
> function fn(...args) {
... return Reflect.apply(Math.max, Math, args);
... }
>
> function fn2() {
... return Reflect.apply(Math.max, Math, arguments);
... }
> console.time('test'); for (var i = 0; i < 1e8; i++) { fn(1, 2, 3, 4); } console.timeEnd('test');
test: 279.502ms
> console.time('test'); for (var i = 0; i < 1e8; i++) { fn2(1, 2, 3, 4); } console.timeEnd('test');
test: 284.195ms
The basic benchmark shows it being optimized. As soon as I've tried using it in practice where there's more than one layer to the usage, arguments seems to win out.
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.
btw @benjamingr I decided to finally split test all the possible variations. Here are my findings:
function fn(...args) {
cb(...args);
}
is the fastest for that particular purpose, faster than using arguments
(about 3-5% faster). But then there's:
function fn(something, ...args) {
cb(...args);
}
seems to depend on specific usage of args
within fn
. For example, I've found that in setTimeout
and setImmediate
we incur about 20-25% performance penalty compared to using manual copying of arguments
.
On the flipside, it performs as expected within EventEmitter.prototype.emit
... and is, in fact, yet again faster than using arguments
. There definitely seem to be some edge cases around this behaviour that I don't quite understand.
I don't know if this helps with your plan to bug V8 devs regarding this :)
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.
It's possible this might be related to whether args
is being stored somewhere or just passed through to another function, or whether args
is accessed at all in the body?
EventEmitter.prototype.emit = function emit(...args) { | ||
const domain = this.domain; | ||
if (domain === null || domain === undefined || this === process) { | ||
return eventEmit.apply(this, args); |
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.
Could you use Reflect.apply
here (and everywhere else) to avoid any potentially overridden apply
methods?
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.
Why would we use Reflect.apply
here but not in the regular event emitter code?
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.
There's a PR introducing it there too, it's just currently blocked on some other stuff.
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.
@apapirovski sounds good - although a little defensive (if someone overrides Function.prototype.apply
or apply on a function object - they other didn't mean to or they know exactly what they're doing - in both cases I'm not sure Node.js should guard against it.
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 definitely should. The usage of .apply
is an implementation detail and should not be affected by monkey-patching.
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.
Doesn't Reflect.apply
have the same "issue"? It is writable too.
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.
Of course. But it's a step better because we're no longer relying on a method of a user provided function. At least Reflect.apply
allows us consistent behaviour between all the different functions that are going to be called, instead of relying on the potentially unique apply of each specific callback.
Strictly speaking, using some sort of an internal way of invoking functions brings us closer to the spec too (e.g. for timers).
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.
Also, to elaborate, this has become less of an issue lately but it was potentially even more problematic in the past where certain functions were called as callback(arguments[0])
and others callback.apply(undefined, arguments)
.
@apapirovski since @addaleax placed the "ready" flag, I'm not certain I should still do changes :/ |
@vdeturckheim Up to you. |
@apapirovski let's do that in two different PR then, I don't have access to my laptop atm. |
Landing now. (Just running local tests one more time to make sure we're all good.) |
Landed in cf4448c Thanks!! |
PR-URL: #17403 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Andreas Madsen <[email protected]> Reviewed-By: Timothy Gu <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]>
@addaleax or @AndreasMadsen do we land these changes to domain on 6.x or 8.x? Not sure what the situation is given the deprecation. Want to make sure we get the right labels on this, if necessary. Thanks! |
@addaleax I think this is a bug in Node.js actually. We emit See https://gist.github.com/novemberborn/2d6137cab8a57fdbafbccc5b33926585. I've tested with
Neither am I, so either that comes from |
Fix an issue where error is never emitted on the original EventEmitter in situations where a listener for error does exist. Refactor to eliminate unnecessary try/catch/finally block. PR-URL: #17588 Refs: #17403 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]>
PR-URL: nodejs#17403 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Andreas Madsen <[email protected]> Reviewed-By: Timothy Gu <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]>
Fix an issue where error is never emitted on the original EventEmitter in situations where a listener for error does exist. Refactor to eliminate unnecessary try/catch/finally block. PR-URL: nodejs#17588 Refs: nodejs#17403 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]>
Backport-PR-URL: #18487 PR-URL: #17403 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Andreas Madsen <[email protected]> Reviewed-By: Timothy Gu <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]>
Fix an issue where error is never emitted on the original EventEmitter in situations where a listener for error does exist. Refactor to eliminate unnecessary try/catch/finally block. Backport-PR-URL: #18487 PR-URL: #17588 Refs: #17403 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]>
@watson managed to track down this commit as breaking, potentially we should have considered it Semver-Major Here is the reproducible case they provided https://gist.github.com/watson/d67b60629026fdb7d081be7477c040e5 The below code will pass on 9.5.0 and fail on 9.6.0 var assert = require('assert')
var EventsModule = require('events')
var descriptor = Object.getOwnPropertyDescriptor(EventsModule, 'usingDomains')
assert(descriptor) I'm going to open a revert against the 9.x branch and potentially release another version later today. Thoughts? |
@MylesBorins LGTM. Should it wait until v10 or should we try to do that in a non breaking change way in v9? |
Thanks for the quick action @MylesBorins 😃 The test-case was written before I knew what was causing the problem. It's code lifted from [email protected] but could be simplified to just |
The line setting this was removed in a previous commit. This potentially breaks code in the wild using this property. Refs: nodejs#17403 (comment)
The line setting this was removed in a previous commit. This potentially breaks code in the wild using this property. Refs: #17403 (comment) PR-URL: #18944 Reviewed-By: Vladimir de Turckheim <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
The line setting this was removed in a previous commit. This potentially breaks code in the wild using this property. Refs: #17403 (comment) PR-URL: #18944 Reviewed-By: Vladimir de Turckheim <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
The line setting this was removed in a previous commit. This potentially breaks code in the wild using this property. Refs: nodejs#17403 (comment) PR-URL: nodejs#18944 Reviewed-By: Vladimir de Turckheim <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
Events and Domain I removed the domain handling in events to have domain doing that.
Relates to #17143