-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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 reaches into _events internals #17440
Conversation
|
||
return unwrapListeners(evlistener); | ||
EventEmitter.prototype.rawListeners = function rawListeners(type) { |
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.
Is there a way to not make this public? I don't like to expose internal details of the EventEmitter
.
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 guess the question is whether it's beneficial to hide these internals or not? I've already seen lots of user-code reaching into _events
for the wrappers. We do it ourselves even. That seems to indicate that we probably need a way to get those once
wrappers.
Having this be behind a method at least gives us some control over the internals that users accessing _events[type]
directly doesn't.
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.
FWIW there's good discussion on this in #6881 where the change listeners
was made. It seems like @addaleax, @ChALkeR & @Fishrock123 wanted to add an ability to return the wrapped listeners anyway but then that fell by the wayside.
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've already seen lots of user-code reaching into
_events
for the wrappers
Can you link some examples which is not readable-stream
? We added eventNames()
to prevent people from using _events
and there weren't a lot of modules using _events
at the time.
We do it ourselves even. That seems to indicate that we probably need a way to get those once wrappers.
Agreed but I would prefer to have it private.
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've seen it in private codebases for this particular use case, described by @ChALkeR in the PR linked above:
It looks like there will be no way to re-attach the events then, which could be useful for cloning, intercepting, or temporary disabling events. Perhaps some API should be added that allows that?
As an example out in the wild, this module broke after the change to listeners
and would need to use _events
now. (Of course, as far as I can tell, prependListener
just solves the same problem anyway but that's a different story.)
https://github.com/timoxley/overshadow-listeners/blob/master/index.js
Also, Ultron is subtly broken if one adds the same handler as a once
using Ultron and then as a real event without Ultron. Ultron will then inadvertently remove the second version. (Although that's also partially a general issue with storing the ID directly on the function.)
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 think this should be exposed for API completeness (for the reason in the @ChALkeR quote above); and that there is code in Node core that uses this should be a good indicator that there are valid use cases.
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 don't see it coming, but changes to how once events are handled can potentially translate to breaking changes if this api is exposed.
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.
@lpinca can you please articulate this more? What would change on how once
events are handled?
I think we will have breaking changes anyway if people used _events
, just outside of the public API.
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.
This will probably never happen but imagine that we will switch to an object to handle events, for example:
{ once: false, listener }
rawListeners
can no longer return a function.
I think we will have breaking changes anyway if people used _events, just outside of the public API.
Yes, the difference is that _events
is "private" so you know beforehand that if you use it your code can break at any time.
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.
However we can't currently touch _events
because a good chunk of the ecosystem uses it.
c71d935
to
0c953ec
Compare
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.
Didn’t realize this was a separate PR when I commented
LGTM :)
lib/events.js
Outdated
@@ -36,6 +36,7 @@ EventEmitter.usingDomains = false; | |||
|
|||
EventEmitter.prototype.domain = undefined; | |||
EventEmitter.prototype._events = undefined; | |||
EventEmitter.prototype._eventsCount = 0; |
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 is this necessary? It seems to me that it used to be necessary for process
in bootstrap_node.js
since C++ pre-sets _events
, which prevents the EventEmitter
constructor from setting _eventsCount
. But now that you've removed the C++ part of this, merely removing _eventsCount
in bootstrap_node.js
should suffice. I could well be mistaken though.
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.
Can we use a private Symbol
if it is necessary?
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.
The issue is that this has been public for a while anyway. I'm just adding it onto the prototype since there are packages out there that use EventEmitter without using EventEmitter.prototype.init
which means that they end up changing the hidden class when they first call emit
or addListener
.
The reason it wasn't there before — at least as far as I understand — is that there were issues with performance around 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.
Oh ok then.
lib/events.js
Outdated
@@ -36,6 +36,7 @@ EventEmitter.usingDomains = false; | |||
|
|||
EventEmitter.prototype.domain = undefined; | |||
EventEmitter.prototype._events = undefined; | |||
EventEmitter.prototype._eventsCount = 0; |
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.
Can we use a private Symbol
if it is necessary?
Local<Object> events_obj = Object::New(env->isolate()); | ||
CHECK(events_obj->SetPrototype(env->context(), | ||
Null(env->isolate())).FromJust()); | ||
process->Set(env->events_string(), events_obj); |
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.
does this cause a perf regression?
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.
Not that I can tell, since we create it in the init
call anyway.
lib/vm.js
Outdated
@@ -43,15 +43,15 @@ const realRunInThisContext = Script.prototype.runInThisContext; | |||
const realRunInContext = Script.prototype.runInContext; | |||
|
|||
Script.prototype.runInThisContext = function(options) { | |||
if (options && options.breakOnSigint && process._events.SIGINT) { | |||
if (options && options.breakOnSigint && process.listenerCount('SIGINT')) { |
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.
can you make listenerCount('SIGINT') > 0
explicit?
lib/vm.js
Outdated
return sigintHandlersWrap(realRunInThisContext, this, [options]); | ||
} else { | ||
return realRunInThisContext.call(this, options); | ||
} | ||
}; | ||
|
||
Script.prototype.runInContext = function(contextifiedSandbox, options) { | ||
if (options && options.breakOnSigint && process._events.SIGINT) { | ||
if (options && options.breakOnSigint && process.listenerCount('SIGINT')) { |
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.
same here.
|
||
return unwrapListeners(evlistener); | ||
EventEmitter.prototype.rawListeners = function rawListeners(type) { |
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.
@lpinca can you please articulate this more? What would change on how once
events are handled?
I think we will have breaking changes anyway if people used _events
, just outside of the public API.
assert.strictEqual(wrappedListeners[0], listener); | ||
assert.notStrictEqual(wrappedListeners[1], listener); | ||
assert.strictEqual(wrappedListeners[1].listener, listener); | ||
} |
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.
Can you add some more tests?
- check if it is a new copy every time
- check that after firing an event the
once
listener is not removed from thewrappedListeners
array (so it's not the internal copy)
- `eventName` {any} | ||
|
||
Returns a copy of the array of listeners for the event named `eventName`, | ||
including any wrappers (such as those created by `.once`). |
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.
The doc does not explain what a wrapper is in this context, and how it is wrapped.
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.
Any thoughts on how we should document this or whether we even should? I'm not certain we want to necessarily encourage the usage. If someone needs it they'll know and otherwise we might want it to be a bit obscure, haha? Not sure...
If anyone has any thoughts, please chime in.
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 it's documented, it needs to be documented in full. Otherwise it doesn't. I'm fine either way.
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.
Do we have precedent for including a publicly exposed function that's not documented? Just trying to figure out the direction we would best go here. This isn't something we want to encourage being used but it's a step better than the current situation of reaching into _events
.
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'd rather this get documented fully.
CI: https://ci.nodejs.org/job/node-test-commit/14721/ Are there any issues with landing this PR as it is? |
I'm guessing that documenting wrappers might be a good idea (for the purposes of |
@mcollina @TimothyGu do you have any suggestions for a good way to document the |
I would just write there an example output of the |
@mcollina PTAL. Thank you! |
// Returns a new Array with a function `onceWrapper` which has a property | ||
// `listener` which contains the original listener bound above | ||
const listeners = emitter.rawListeners('log'); | ||
const logFnWrapper = listeners[0]; |
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.
can you add an example also with on()
?
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. Let me know if this isn't what you had in mind.
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.
LGTM with my nit.
<!-- YAML | ||
added: REPLACEME | ||
--> | ||
- `eventName` {any} |
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.
Hmm, shouldn't this be {string|symbol}
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 matches the rest of the docs and it is at least partially correct. We don't validate the input so if they pass in a number, it'll get automatically converted to a string.
Either way, if we're changing this then it should be changed in all of the docs.
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've filed #17657 to track that. This again LGTM.
Refactor lib & src code to eliminate all deep reaches into the internal _events dictionary object, instead use available APIs and add an extra method to EventEmitter: wrappedListeners.
1b0d9cd
to
53aab29
Compare
Refactor lib & src code to eliminate all deep reaches into the internal _events dictionary object, instead use available APIs and add an extra method to EventEmitter: rawListeners. PR-URL: #17440 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
Landed in decab71 |
Change type annotations for eventName in events API doc from {any} to {string|symbol} to be consistent with doc changes made in nodejs#17440. Fixes: nodejs#17657
Refactor lib & src code to eliminate all deep reaches into the internal _events dictionary object, instead use available APIs and add an extra method to EventEmitter: rawListeners. PR-URL: #17440 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
Notable change: * async_hooks: - deprecate AsyncHooks Sensitive API and runInAsyncIdScope. Neither API were documented. (Andreas Madsen) #16972 * deps: - update nghttp2 to 1.29.0 (James M Snell) #17908 - upgrade npm to 5.6.0 (Kat Marchán) #17535 - cherry-pick 50f7455 from upstream V8 (Michaël Zasso) #16591 * events: - remove reaches into _events internals (Anatoli Papirovski) #17440 * http: - add rawPacket in err of `clientError` event (XadillaX) #17672 * http2: - implement maxSessionMemory (James M Snell) #17967 - add initial support for originSet (James M Snell) #17935 - add altsvc support (James M Snell) #17917 - perf_hooks integration (James M Snell) #17906 * net: - remove Socket.prototype.write (Anna Henningsen) #17644 - remove Socket.prototype.listen (Ruben Bridgewater) #13735 * repl: - show lexically scoped vars in tab completion (Michaël Zasso) #16591 * stream: - rm {writeable/readable}State.length (Calvin Metcalf) #12857 - add flow and buffer properties to streams (Calvin Metcalf) #12855 * util: - allow wildcards in NODE_DEBUG variable (Tyler) #17609 * zlib: - add ArrayBuffer support (Jem Bezooyen) #16042 * Addedew collaborator** - [starkwang](https://github.com/starkwang) Weijia Wang * Addedew TSC member** - [danbev](https://github.com/danbev) Daniel Bevenius PR-URL: #18069
Notable change: * async_hooks: - deprecate AsyncHooks Sensitive API and runInAsyncIdScope. Neither API were documented. (Andreas Madsen) #16972 * deps: - update nghttp2 to 1.29.0 (James M Snell) #17908 - upgrade npm to 5.6.0 (Kat Marchán) #17535 - cherry-pick 50f7455 from upstream V8 (Michaël Zasso) #16591 * events: - remove reaches into _events internals (Anatoli Papirovski) #17440 * http: - add rawPacket in err of `clientError` event (XadillaX) #17672 * http2: - implement maxSessionMemory (James M Snell) #17967 - add initial support for originSet (James M Snell) #17935 - add altsvc support (James M Snell) #17917 - perf_hooks integration (James M Snell) #17906 - Refactoring and cleanup of Http2Session and Http2Stream destroy (James M Snell) #17406 * net: - remove Socket.prototype.write (Anna Henningsen) #17644 - remove Socket.prototype.listen (Ruben Bridgewater) #13735 * repl: - show lexically scoped vars in tab completion (Michaël Zasso) #16591 * stream: - rm {writeable/readable}State.length (Calvin Metcalf) #12857 - add flow and buffer properties to streams (Calvin Metcalf) #12855 * util: - allow wildcards in NODE_DEBUG variable (Tyler) #17609 * zlib: - add ArrayBuffer support (Jem Bezooyen) #16042 * Addedew collaborator** - [starkwang](https://github.com/starkwang) Weijia Wang * Addedew TSC member** - [danbev](https://github.com/danbev) Daniel Bevenius PR-URL: #18069
Notable change: * async_hooks: - deprecate AsyncHooks Sensitive API and runInAsyncIdScope. Neither API were documented. (Andreas Madsen) #16972 * deps: - update nghttp2 to 1.29.0 (James M Snell) #17908 - upgrade npm to 5.6.0 (Kat Marchán) #17535 - cherry-pick 50f7455 from upstream V8 (Michaël Zasso) #16591 * events: - remove reaches into _events internals (Anatoli Papirovski) #17440 * http: - add rawPacket in err of `clientError` event (XadillaX) #17672 * http2: - implement maxSessionMemory (James M Snell) #17967 - add initial support for originSet (James M Snell) #17935 - add altsvc support (James M Snell) #17917 - perf_hooks integration (James M Snell) #17906 - Refactoring and cleanup of Http2Session and Http2Stream destroy (James M Snell) #17406 * net: - remove Socket.prototype.write (Anna Henningsen) #17644 - remove Socket.prototype.listen (Ruben Bridgewater) #13735 * repl: - show lexically scoped vars in tab completion (Michaël Zasso) #16591 * stream: - rm {writeable/readable}State.length (Calvin Metcalf) #12857 - add flow and buffer properties to streams (Calvin Metcalf) #12855 * util: - allow wildcards in NODE_DEBUG variable (Tyler) #17609 * zlib: - add ArrayBuffer support (Jem Bezooyen) #16042 * Addedew collaborator** - [starkwang](https://github.com/starkwang) Weijia Wang * Addedew TSC member** - [danbev](https://github.com/danbev) Daniel Bevenius PR-URL: #18069
This strips out the harmless bits of #17324 into this standalone PR so they can actually land.
Refactoring of the lib & src code to eliminate all deep reaches into the internal
_events
dictionary object, instead use available APIs and add an extra method to EventEmitter:rawListeners
(this seems to be a common reason to reach into_events
in user-code as well).Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
events