-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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
async_hooks: use resource objects for Promises #13452
Conversation
Thanks for looking at this. Do we not risk getting a very long chain of I was thinking it might be better to use the asyncId of the parent, rather than the There is also the problem of /cc @JiaLiPassion |
Also, cc @Fishrock123 who was the first to request this. |
@addaleax , thank you for implementing this. @AndreasMadsen , are you suggestion to pass the const parent_promise = new Promise((resolve, reject) => {resolve(5);});
const promise = parent_promise.then((val) => {return val;});
function init(id, type, triggerId, handle) {
process._rawDebug(`id: ${id}, type: ${type}, triggerId: ${triggerId},
handle.promise: ${handle.promise},
handle.parentid: ${handle.parentId}`);
} the output will look like
And if application want the promise object, they can keep a |
Yes. That is exactly what I mean. |
@AndreasMadsen , got it! I still try to understand the C++ code, |
@AndreasMadsen @JiaLiPassion Updated!
If you have any questions, please ask them, here or in the diff – whatever you want to know, a lot of other people will also like to know. |
@addaleax , got it! |
src/async-wrap.cc
Outdated
promise_wrap_template->SetAccessor( | ||
FIXED_ONE_BYTE_STRING(env->isolate(), "parentId"), | ||
PromiseWrap::GetParentId); | ||
env->SetProtoMethod(ctor, "getAsyncId", AsyncWrap::GetAsyncId); |
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.
What is the cost of this? If there is any I don't think we should add it. The result is given as the first parameter in init
anyway and we generally don't document that getAsyncId
exists.
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 thought it’s nice to have for consistency, but removing it is trivial. I’ve removed it for now, we can always revisit if necessary.
I haven’t measured, but I can’t really imagine it’s costly – it adds a method to the prototype, which should be practically free.
src/async-wrap.cc
Outdated
Local<Object> resource_object; | ||
PromiseWrap* wrap = nullptr; | ||
if (resource_object_value->IsObject()) { | ||
resource_object = resource_object_value.As<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.
My V8-C++ knowledge might be lacking, but why do we check that resource_object_value
is an Object
and then cast it to an 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.
.As<Object>()
is not a cast in the JS sense of the word; you can read it as telling the engine “I’ve made sure that this is an object, now can I please use it as one?” (i.e. calling handle.As<T>()
is undefined behaviour if handle
is not actually a 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.
Ah, similar to reinterpret_cast
.
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.
In addition a Debug build it asserts, so instead of simply crashing b/c it was cast to the wrong type it'll actually say you've screwed up.
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.
Code LGTM. Since zone.js
will be dependent on this, I think should document the PromiseWrap
resource object.
@AndreasMadsen I’ve added some documentation… PTAL |
@@ -203,13 +203,16 @@ UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST, | |||
RANDOMBYTESREQUEST, TLSWRAP | |||
``` | |||
|
|||
There is also the `PROMISE` resource type, which is used to track `Promise` |
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.
Maybe we should add PROMISE
to the provider list in async_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.
I’ve kept it out of it because it doesn’t quite qualify as “provided by the built-in Node.js modules”, but if you think it’s better, I can just drop this sentence and reword the existing text a bit
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.
No just leave it out for now. @trevnorris suggested that maybe we should add all types to the Providers
list. See #13287 (comment)
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.
Hm. Seems we don't even include Timer
or TickObject
in that list. Or even as a side comment.
Anyway, only argument I'd have for adding it to the above list is because it shows up in process.binding('async_wrap').Providers
. But that's not a strong argument. I'll leave this up to you.
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 we should delete the list. The user shouldn't really on a defined list of types because the Embedder API allows for extra types.
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.
@AndreasMadsen The reason I decided to pass the "type" (history note: using Provider
was not the name I wanted to use, but switched long ago b/c of pressure to change on the PR) was for to allow filtering resources. I'd like to have a list of all "core types" (which would include TickObject
and Timer
) and I'd expect module authors to document their own "types".
Though because this relies a bit on internals could we put a note like "subject to change in future major (or minor?) releases without deprecation"?
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 already have a note saying resource objects are not to be trusted:
... The API for getting this information is currently not considered public ...
I guess we can be more specific.
@@ -282,37 +285,87 @@ bool AsyncWrap::EmitAfter(Environment* env, double async_id) { | |||
class PromiseWrap : public AsyncWrap { | |||
public: | |||
PromiseWrap(Environment* env, Local<Object> object, bool silent) | |||
: AsyncWrap(env, object, PROVIDER_PROMISE, silent) {} | |||
: AsyncWrap(env, object, PROVIDER_PROMISE, silent) { | |||
MakeWeak(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.
@addaleax , sorry for very basic question, why we add
MakeWeak(this);
in this PR, since now we only keep the asyncId
of parent promise here, is there any GC issue if we don't call MakeWeak
?
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 question is not basic – it’s something I didn’t quite figure out until a couple days ago myself. And I’m not sure whether you’ve seen it, but the current MakeWeak
call is being removed here, too: https://github.com/nodejs/node/pull/13452/files#diff-5e552c79e1538215f1621d1774852e71L315
The thing is, MakeWeak
is a not the best choice of name. What it actually does is to set the 0th internal field of the associated object to the passed pointer, and then mark the handled contained in the Wrap object as weak and set a destroy callback for V8 to call.
And it needs to then make handle weak, because otherwise all V8 knows is that there is a persistent handle to the object; it doesn’t know how we use it, so it can’t garbage collect it. By making it weak, we tell V8 to destroy it once there are no other (non-weak) handles left, and to run our registered destroy callback (which will delete
the C++ instance of PromiseWrap
that we are using).
I agree, it’s all a bit tricky to see through. I’m mostly moving it here because that helps with avoiding duplication, and because that’s how we use it elsewhere in the code as well.
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.
@addaleax , thank you for the explanation. I just read MakeWeak
API and node_object_wrap.h
, and I will continue to learn about it.
And I am not quite understand is why we don't need the MakeWeak
call before this version?
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.
And I am not quite understand is why we don't need the
MakeWeak
call before this version?
It was there, it was just called after the constructor instead of being a part of it: https://github.com/nodejs/node/pull/13452/files#diff-5e552c79e1538215f1621d1774852e71L315
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.
@addaleax , oh, yes, it was there, thanks. Currently I have no further questions!
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 thing is, MakeWeak is a not the best choice of name. What it actually does is to set the 0th internal field of the associated object to the passed pointer, and then mark the handled contained in the Wrap object as weak and set a destroy callback for V8 to call.
And... I'm to blame for this one. In fe2df3b I carefully went through to make sure Wrap()
and ClearWrap()
were called for all applicable instances forgetting that 3.5 years ago for whatever forsaken reason when I implemented BaseObject
(d120d92) I made MakeWeak()
automatically Wrap()
the object. This was a horrible mistake on my part
Basically I would be happy if we removed the call to Wrap()
in BaseObject::MakeWeak()
.
I just read MakeWeak API and node_object_wrap.h, and I will continue to learn about it.
Be careful. AsyncWrap doesn't use node_object_wrap
. It uses base-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.
Be careful. AsyncWrap doesn't use node_object_wrap. It uses base-object
thank you for pointing out this one.
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.
I set it to semver-minor since this is an API change, although technically we make less than experimental guarantees about the resource object, so an argument could be made for semver-patch.
I think we should remove the type list. The user shouldn't really on a defined list of types because the Embedder API allows for extra types. However, it is not related to the PR.
👍 (@AndreasMadsen If you put comments in the "review summary" I can't 👍 them) |
I know. I prefer it this way :D evil laughter |
src/async-wrap.cc
Outdated
|
||
static void PromiseHook(PromiseHookType type, Local<Promise> promise, | ||
Local<Value> parent, void* arg) { | ||
Local<Context> context = promise->CreationContext(); | ||
Environment* env = Environment::GetCurrent(context); | ||
PromiseWrap* wrap = Unwrap<PromiseWrap>(promise); | ||
Local<Value> resource_object_value = promise->GetInternalField(0); | ||
Local<Object> resource_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.
nit: this is only used in the if statement below so mind just making it:
Local<Object> resource_object = resource_object_value.As<Object>();
Use `PromiseWrap` resource objects whose lifetimes are tied to the `Promise` instances themselves to track promises, and have a `.promise` getter that points to the `Promise` and a `.parent` property that points to the parent Promise’s resource object, if there is any. The properties are implemented as getters for internal fields rather than normal properties in the hope that it helps keep performance for the common case that async_hooks users will often not inspect them.
ec1c0d4
to
4acaed3
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.
Thank you much
Landed in 8f39881 |
Use `PromiseWrap` resource objects whose lifetimes are tied to the `Promise` instances themselves to track promises, and have a `.promise` getter that points to the `Promise` and a `.parent` property that points to the parent Promise’s resource object, if there is any. The properties are implemented as getters for internal fields rather than normal properties in the hope that it helps keep performance for the common case that async_hooks users will often not inspect them. PR-URL: #13452 Reviewed-By: Andreas Madsen <amwebdk@gmail.com> Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Use `PromiseWrap` resource objects whose lifetimes are tied to the `Promise` instances themselves to track promises, and have a `.promise` getter that points to the `Promise` and a `.parent` property that points to the parent Promise’s resource object, if there is any. The properties are implemented as getters for internal fields rather than normal properties in the hope that it helps keep performance for the common case that async_hooks users will often not inspect them. PR-URL: #13452 Reviewed-By: Andreas Madsen <amwebdk@gmail.com> Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Use `PromiseWrap` resource objects whose lifetimes are tied to the `Promise` instances themselves to track promises, and have a `.promise` getter that points to the `Promise` and a `.parent` property that points to the parent Promise’s resource object, if there is any. The properties are implemented as getters for internal fields rather than normal properties in the hope that it helps keep performance for the common case that async_hooks users will often not inspect them. PR-URL: #13452 Reviewed-By: Andreas Madsen <amwebdk@gmail.com> Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Use `PromiseWrap` resource objects whose lifetimes are tied to the `Promise` instances themselves to track promises, and have a `.promise` getter that points to the `Promise` and a `.parent` property that points to the parent Promise’s resource object, if there is any. The properties are implemented as getters for internal fields rather than normal properties in the hope that it helps keep performance for the common case that async_hooks users will often not inspect them. PR-URL: #13452 Reviewed-By: Andreas Madsen <amwebdk@gmail.com> Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Use `PromiseWrap` resource objects whose lifetimes are tied to the `Promise` instances themselves to track promises, and have a `.promise` getter that points to the `Promise` and a `.parent` property that points to the parent Promise’s resource object, if there is any. The properties are implemented as getters for internal fields rather than normal properties in the hope that it helps keep performance for the common case that async_hooks users will often not inspect them. PR-URL: #13452 Reviewed-By: Andreas Madsen <amwebdk@gmail.com> Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Use `PromiseWrap` resource objects whose lifetimes are tied to the `Promise` instances themselves to track promises, and have a `.promise` getter that points to the `Promise` and a `.parent` property that points to the parent Promise’s resource object, if there is any. The properties are implemented as getters for internal fields rather than normal properties in the hope that it helps keep performance for the common case that async_hooks users will often not inspect them. PR-URL: #13452 Reviewed-By: Andreas Madsen <amwebdk@gmail.com> Reviewed-By: Trevor Norris <trev.norris@gmail.com>
As discussed in #13367. /cc @nodejs/async_hooks
Use
PromiseWrap
resource objects whose lifetimes are tied tothe
Promise
instances themselves to track promises, and havea
.promise
getter that points to thePromise
and a.parent
property that points to the parent Promise’s resource object,
if there is any.
The properties are implemented as getters for internal fields
rather than normal properties in the hope that it helps keep
performance for the common case that async_hooks users will
often not inspect them.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
async_hooks