events: remove domain handling from events to domain#17403
events: remove domain handling from events to domain#17403vdeturckheim wants to merge 1 commit intonodejs:masterfrom vdeturckheim:remove_domains_from_events
Conversation
TimothyGu
left a comment
There was a problem hiding this comment.
Have you benchmarked any potential performance change this could bring for use cases of EventEmitter with domain enabled?
lib/events.js
Outdated
There was a problem hiding this comment.
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.
lib/events.js
Outdated
There was a problem hiding this comment.
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. |
benjamingr
left a comment
There was a problem hiding this comment.
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
There was a problem hiding this comment.
We should change this comment. It serves no purpose now, other than backward compatibility.
AndreasMadsen
left a comment
There was a problem hiding this comment.
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 |
lib/domain.js
Outdated
There was a problem hiding this comment.
Nit: typos
// Override EventEmitter methods to make it domain-aware.
apapirovski
left a comment
There was a problem hiding this comment.
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.
apapirovski
left a comment
There was a problem hiding this comment.
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.
@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.
@apapirovski are you sure it's faster? (In recent V8)
There was a problem hiding this comment.
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.
@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.
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.195msThe 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.
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.
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.
Could you use Reflect.apply here (and everywhere else) to avoid any potentially overridden apply methods?
There was a problem hiding this comment.
Why would we use Reflect.apply here but not in the regular event emitter code?
There was a problem hiding this comment.
There's a PR introducing it there too, it's just currently blocked on some other stuff.
There was a problem hiding this comment.
@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.
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.
Doesn't Reflect.apply have the same "issue"? It is writable too.
There was a problem hiding this comment.
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.
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 <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Andreas Madsen <amwebdk@gmail.com> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
|
@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 <ruben@bridgewater.de> Reviewed-By: James M Snell <jasnell@gmail.com>
PR-URL: nodejs#17403 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Andreas Madsen <amwebdk@gmail.com> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
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 <ruben@bridgewater.de> Reviewed-By: James M Snell <jasnell@gmail.com>
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 <ruben@bridgewater.de> Reviewed-By: James M Snell <jasnell@gmail.com>
|
@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 bluebird@2.9.24 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 <vlad2t@hotmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
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 <vlad2t@hotmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
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 <vlad2t@hotmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
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