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

events: remove domain handling from events to domain #17403

Closed
wants to merge 1 commit into from
Closed

events: remove domain handling from events to domain #17403

wants to merge 1 commit into from

Conversation

vdeturckheim
Copy link
Member

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included (N/A)
  • documentation is changed or added (N/A)
  • commit message follows commit guidelines
Affected core subsystem(s)

Events and Domain I removed the domain handling in events to have domain doing that.
Relates to #17143

@nodejs-github-bot nodejs-github-bot added domain Issues and PRs related to the domain subsystem. events Issues and PRs related to the events subsystem / EventEmitter. labels Nov 30, 2017
Copy link
Member

@TimothyGu TimothyGu left a 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;
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

@addaleax addaleax left a 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 {
Copy link
Member Author

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

@addaleax
Copy link
Member

Have you benchmarked any potential performance change this could bring for use cases of EventEmitter with domain enabled?

I would be okay with a slight performance regression for a long-deprecated module like domain anyway.

@vdeturckheim
Copy link
Member Author

@TimothyGu I have no data regarding perf impact here.

Copy link
Member

@benjamingr benjamingr left a 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.

@benjamingr
Copy link
Member

Re: benchmarks. I don't think we have any for event emitters + domains.

@addaleax
Copy link
Member

lib/domain.js Outdated
@@ -387,3 +382,51 @@ Domain.prototype.bind = function(cb) {

return runBound;
};

// communicate with events module, but don't require that
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

Copy link
Member

@AndreasMadsen AndreasMadsen left a 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.

@TimothyGu
Copy link
Member

@addaleax

I would be okay with a slight performance regression for a long-deprecated module like domain anyway.

Yet, for a long time (years) domain was the only thing available that could do the things it could do. I don't foresee any significant performance problem that could stem from this PR, but would like to be safe.

@benjamingr

Re: benchmarks. I don't think we have any for event emitters + domains.

No need to even use domains in a benchmark, measuring the difference between EventEmitter usage with versus without require()ing it suffices.

Copy link
Member

@TimothyGu TimothyGu left a 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
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

Copy link
Member

@apapirovski apapirovski left a 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.

@addaleax
Copy link
Member

addaleax commented Dec 1, 2017

@addaleax addaleax added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Dec 1, 2017
Copy link
Member

@apapirovski apapirovski left a 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) {
Copy link
Member

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.

Copy link
Member

@benjamingr benjamingr Dec 4, 2017

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)

Copy link
Member

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).

Copy link
Member

@benjamingr benjamingr Dec 4, 2017

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

Copy link
Member

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.

Copy link
Member

@apapirovski apapirovski Dec 6, 2017

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 :)

Copy link
Member

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);
Copy link
Member

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?

Copy link
Member

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?

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

@apapirovski apapirovski Dec 4, 2017

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).

Copy link
Member

@apapirovski apapirovski Dec 4, 2017

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).

@vdeturckheim
Copy link
Member Author

@apapirovski since @addaleax placed the "ready" flag, I'm not certain I should still do changes :/

@apapirovski
Copy link
Member

@vdeturckheim Up to you. ready just means it can land but if you want to make changes, you always can. Otherwise I'll just open a PR after this lands.

@vdeturckheim
Copy link
Member Author

@apapirovski let's do that in two different PR then, I don't have access to my laptop atm.

@apapirovski
Copy link
Member

apapirovski commented Dec 4, 2017

Landing now. (Just running local tests one more time to make sure we're all good.)

@apapirovski
Copy link
Member

Landed in cf4448c

Thanks!!

@apapirovski apapirovski closed this Dec 4, 2017
apapirovski pushed a commit that referenced this pull request Dec 4, 2017
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>
@apapirovski
Copy link
Member

@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!

@novemberborn
Copy link

@addaleax I think this is a bug in Node.js actually. We emit error events with non-Error instances. There is also a domain active. Despite having an event handler, the emitted error is considered unhandled. I reckon it's this line in this PR that's causing problems.

See https://gist.github.com/novemberborn/2d6137cab8a57fdbafbccc5b33926585. I've tested with node-v10.0.0-nightly20171209e9d1e1265a-darwin-x64.

I would not be aware that they use the domain module

Neither am I, so either that comes from node-tap or there's a dependency that enters a domain.

apapirovski added a commit that referenced this pull request Dec 13, 2017
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>
apapirovski pushed a commit to apapirovski/node that referenced this pull request Jan 31, 2018
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>
apapirovski added a commit to apapirovski/node that referenced this pull request Jan 31, 2018
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>
@MylesBorins
Copy link
Contributor

MylesBorins commented Feb 20, 2018

the commit neccessary to land after this 04ae486 does not land cleanly.

Would someone be willing to manually backport?

edit: cf4448c does not land cleanly either

MylesBorins pushed a commit that referenced this pull request Feb 20, 2018
Backport-PR-URL: #18487
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>
MylesBorins pushed a commit that referenced this pull request Feb 20, 2018
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>
@MylesBorins MylesBorins mentioned this pull request Feb 21, 2018
@MylesBorins
Copy link
Contributor

@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?

@vdeturckheim
Copy link
Member Author

@MylesBorins LGTM. Should it wait until v10 or should we try to do that in a non breaking change way in v9?

@watson
Copy link
Member

watson commented Feb 22, 2018

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 require('assert')(require('events').usingDomains) 😜

MylesBorins added a commit to MylesBorins/node that referenced this pull request Feb 22, 2018
The line setting this was removed in a previous commit. This
potentially breaks code in the wild using this property.

Refs: nodejs#17403 (comment)
@gibfahn gibfahn added the semver-major PRs that contain breaking changes and should be released in the next major version. label Feb 22, 2018
MylesBorins added a commit that referenced this pull request Feb 23, 2018
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>
MylesBorins added a commit that referenced this pull request Feb 23, 2018
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>
MayaLekova pushed a commit to MayaLekova/node that referenced this pull request May 8, 2018
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain Issues and PRs related to the domain subsystem. events Issues and PRs related to the events subsystem / EventEmitter. semver-major PRs that contain breaking changes and should be released in the next major version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.