-
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
handle_wrap: expose {un}refed check to JS #5834
Conversation
@@ -69,6 +81,7 @@ HandleWrap::HandleWrap(Environment* env, | |||
HandleScope scope(env->isolate()); | |||
Wrap(object, this); | |||
env->handle_wrap_queue()->PushBack(this); | |||
env->SetMethod(object, "isRefed", HandleWrap::IsRefed); |
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.
idk if it's better to have this be isUnrefed
?
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 probably should be isUnrefed()
, but isRefed()
seems a lot simpler to think about.
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 think isRefed()
sounds better ("is this holding the event loop open?").
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 is not the way it's done. This has to be declared in every child class: https://github.com/nodejs/node/blob/v5.9.0/src/tcp_wrap.cc#L87
Otherwise you're setting an object property on an already constructed object.
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 that kinda sucks, alright.
(does this needs tests..?) |
bool refed = (wrap->flags_ & kUnref) == 0; | ||
args.GetReturnValue().Set(Boolean::New(env->isolate(), refed)); | ||
} | ||
} |
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: is it ok to return nothing of the handle is not alive?
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.
returning nothing == returning undefined. but the JS signature returns bool. The question this is trying to answer is "Can this handle hold open the event loop?" So if !IsAlive()
means should 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.
Noted, thanks.
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.
So basically this can be boiled down to:
bool refed = IsAlive(wrap) && (wrap->flags_ & kUnref) == 0;
args.GetReturnValue().Set(Boolean::New(env->isolate(), refed);
Yes, I think this needs tests. Ideally you'd pass tests similar to the ones in the PRs you referenced. |
Yes, tests are needed. |
Does anyone have advice on how I can make a more generic test for this rather than testing everywhere that we expose a handle? |
@Fishrock123 I guess you could test on C++ level (do we have C++ level tests?) and just create a handle and interact with it directly. |
Does it have to be a method? Is it possible to have it as a read-only property? A quick test case would be worthwhile also. |
@jasnell I guess this makes it explicit that you cannot just set it, but I could possibly change it otherwise. |
@Fishrock123 ... that makes sense. The additional |
The implementation is incomplete. As noted in #5834 (comment) the method needs to be placed on the The test is simple enough: const assert = require('assert');
const net = require('net');
const server = net.createServer(() => {}).listen(common.PORT);
server.unref();
assert.equal(false, server._handle.isRefed()); Remember this won't cover timer cases. To make the API parallel may need to implement a JS version that can be called on the JS timer handle instances. |
@Fishrock123 thanks for doing this. It would also be useful for just checking what keeps a process alive at any given time. |
502fe6d
to
a55ceff
Compare
Updated with a test, some things: I realized that some I'm not sure if it is possible to test spawn a TTY's cannot be re-refed for some reason. The method does not exist in c++, and does not work if it is added. |
Hmm, getting this on Windows vs2015 and vcbt2015:
Anyone know how to fix this test for that? const assert = makeAssert('isRefed() not working on tty_wrap');
const ReadStream = require('tty').ReadStream;
const tty = new ReadStream();
assert(Object.getPrototypeOf(tty._handle).hasOwnProperty('isRefed'), true);
assert(tty._handle.isRefed(), true);
tty.unref();
assert(tty._handle.isRefed(), false); |
Do you need to pass a fd to the Once the CI is happy, LGTM. |
assert(timer._handle.isRefed(), false); | ||
timer.ref(); | ||
assert(timer._handle.isRefed(), true); | ||
timer.unref(); |
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 depends on some non-obvious implementation details. If the timer is unref'd then the JS object will be moved to a TimerWrap
that also isn't ref'd, correct?
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.
See
Lines 460 to 485 in d3a7534
Timeout.prototype.unref = function() { | |
if (this._handle) { | |
this._handle.unref(); | |
} else if (typeof this._onTimeout === 'function') { | |
var now = TimerWrap.now(); | |
if (!this._idleStart) this._idleStart = now; | |
var delay = this._idleStart + this._idleTimeout - now; | |
if (delay < 0) delay = 0; | |
// Prevent running cb again when unref() is called during the same cb | |
if (this._called && !this._repeat) { | |
exports.unenroll(this); | |
return; | |
} | |
var handle = reuse(this); | |
this._handle = handle || new TimerWrap(); | |
this._handle.owner = this; | |
this._handle[kOnTimeout] = unrefdHandle; | |
this._handle.start(delay, 0); | |
this._handle.domain = this.domain; | |
this._handle.unref(); | |
} | |
return this; | |
}; |
timer.unref()
assigns a ._handle
for that specific timer, and is unlikely to change in the short term.
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 realize that. What I failed to say is that a note of this should be placed in timer_wrap.cc
where the method is added so future generations can immediately see why this non-obvious implementation detail actually works.
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.
Where exactly do you mean? Isn't the same thing for the un(ref)
methods on timer_wrap?
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.
My point is it may be non-obvious why setTimeout(()=>{}, 100)._handle === undefined
but setTimeout(()=>{}, 100).unref()._handle
links to a Timer
instance. So using isRefed()
is conditional to the state of timer. So not having a uniform way to access that data from the public object instance could be confusing.
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.
Sure, but the regular timers implementation will also benefit from this. e.g.
Lines 210 to 214 in cf94929
if (list._unrefed === true) { | |
delete unrefedLists[msecs]; | |
} else { | |
delete refedLists[msecs]; | |
} |
So I'm not sure what I would say in handle_wrap
a55ceff
to
40c81d2
Compare
Ci with fd 0 for TTY: https://ci.nodejs.org/job/node-test-pull-request/2030/ |
@cjihrig still the same thing on those windows builds/platforms.. |
Crap. The only real use of |
maybe cc @bnoordhuis |
40c81d2
to
abd0daf
Compare
@nodejs/lts think we can get this in the next minor LTS release? |
Should be possible. It's just not yet clear when that next minor LTS release will be. |
OK in hindsight this behavior regarding This needs to be accurate enough for timers to use too, and currently it isn't with either the following patches: diff --git a/lib/timers.js b/lib/timers.js
index dc2506e..3a57d86 100644
--- a/lib/timers.js
+++ b/lib/timers.js
@@ -133,8 +133,6 @@ function insert(item, unrefed) {
list = new TimersList(msecs, unrefed);
L.init(list);
list._timer._list = list;
-
- if (unrefed === true) list._timer.unref();
list._timer.start(msecs, 0);
lists[msecs] = list;
@@ -149,7 +147,7 @@ function TimersList(msecs, unrefed) {
this._idleNext = null; // Create the list with the linkedlist properties to
this._idlePrev = null; // prevent any unnecessary hidden class changes.
this._timer = new TimerWrap();
- this._unrefed = unrefed;
+ if (unrefed === true) this._timer.unref();
this.msecs = msecs;
}
@@ -207,7 +205,7 @@ function listOnTimeout() {
debug('%d list empty', msecs);
assert(L.isEmpty(list));
this.close();
- if (list._unrefed === true) {
+ if (list._timer.isRefed() === false) {
delete unrefedLists[msecs];
} else {
delete refedLists[msecs];
diff --git a/lib/timers.js b/lib/timers.js
index dc2506e..cdc0d1c 100644
--- a/lib/timers.js
+++ b/lib/timers.js
@@ -133,8 +133,6 @@ function insert(item, unrefed) {
list = new TimersList(msecs, unrefed);
L.init(list);
list._timer._list = list;
-
- if (unrefed === true) list._timer.unref();
list._timer.start(msecs, 0);
lists[msecs] = list;
@@ -149,7 +147,7 @@ function TimersList(msecs, unrefed) {
this._idleNext = null; // Create the list with the linkedlist properties to
this._idlePrev = null; // prevent any unnecessary hidden class changes.
this._timer = new TimerWrap();
- this._unrefed = unrefed;
+ if (unrefed === true) this._timer.unref();
this.msecs = msecs;
}
@@ -207,10 +205,10 @@ function listOnTimeout() {
debug('%d list empty', msecs);
assert(L.isEmpty(list));
this.close();
- if (list._unrefed === true) {
- delete unrefedLists[msecs];
- } else {
+ if (list._timer.isRefed() === true) {
delete refedLists[msecs];
+ } else {
+ delete unrefedLists[msecs];
}
} |
So... it does work if I close after the delete (duh), but the behavior is still pretty confusing.. Will probably remove the |
@Fishrock123 you may want to chime into the v6 thread before the rc is cut if you don't think this should land |
This fixes my perceived usability issues with 7d8882b. Which, at the time of writing, has not landed in any release except v6 RCs. This should not be considered a breaking change due to that. It is useful if you have a handle, even if it has been closed, to be able to inspect whether that handle was unrefed or not. As such, this renames the method accordingly. If people need to check a handle's aliveness, that is a separate API we should consider exposing. Refs: nodejs#5834 PR-URL: nodejs#6204 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trevor Norris <[email protected]>
This reverts commit 9bb5a5e. Refs: nodejs#6382 Refs: nodejs#6204 Refs: nodejs#5834
This reverts commit f938ef7. Refs: nodejs#6382 Refs: nodejs#6204 Refs: nodejs#5834
This reverts commit 7d8882b. Refs: nodejs#6382 Refs: nodejs#6204 Refs: nodejs#5834
This allows third-party tools to check whether or not a handle that can be unreferenced is unreferenced at a particular time. Notably, this should be helpful for inspection via AsyncWrap. Also, this is useful even to node's internals, particularly timers. Refs: #5828 Refs: #5827 PR-URL: #5834 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Trevor Norris <[email protected]>
This fixes my perceived usability issues with 7d8882b. Which, at the time of writing, has not landed in any release except v6 RCs. This should not be considered a breaking change due to that. It is useful if you have a handle, even if it has been closed, to be able to inspect whether that handle was unrefed or not. As such, this renames the method accordingly. If people need to check a handle's aliveness, that is a separate API we should consider exposing. Refs: #5834 PR-URL: #6204 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trevor Norris <[email protected]>
Pull Request check-list
make -j8 test
(UNIX) orvcbuild test nosign
(Windows) pass withthis change (including linting)?
Affected core subsystem(s)
handle_wrap
Description of change
Give tools a consistent way to check if a handle is unrefed that works for all of our APIs that expose handles. E.g. would be very helpful to see which handles are umrefed in @AndreasMadsen's https://github.com/AndreasMadsen/dprof
(Some APIs, like timers, do not normally expose handles and are not addressed at that level here)
Refs: #5828
Refs: #5827
R= @trevnorris
cc @thlorenz