Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

node::MakeCallback is dangerous, can you detect misuse and warn? #9245

Closed
metamatt opened this issue Feb 18, 2015 · 18 comments
Closed

node::MakeCallback is dangerous, can you detect misuse and warn? #9245

metamatt opened this issue Feb 18, 2015 · 18 comments
Assignees
Labels
Milestone

Comments

@metamatt
Copy link

This issue springs from TooTallNate/node-weak#35, but the root cause is that node::MakeCallback is dangerous if misused, and it would be nice to prevent its misuse.

By "misuse", I mean: if you have a Handle<Function> in a node addon and you want to call it, you have the choice of calling it directly (Handle<Function>::Call()) or via node::MakeCallback. You should do the former if your addon code was invoked from JS code; you should do the latter if your addon code was invoked directly from libuv as an event callback. This difference, while crucial, is not plainly obvious, and if you get it wrong, the results are really bad (preemption of Javascript code, that breaks Node's entire threadless/concurrency/consistency model).

Note comment TooTallNate/node-weak#36 (comment) from @trevnorris (and the surrounding discussion) saying that MakeCallback should be benign if called unnecessarily, but in my observation, that's not true. So I'm following up on this old discussion with an issue report and demo/repro case (for NodeJS 0.12.0) that shows it's actually (and still) dangerous if called this way.

The repro case is at https://gist.github.com/metamatt/4ce96adbf14de9a97d41.

@trevnorris
Copy link

@metamatt Thanks. There's a simple fix, but going to investigate to make sure it's also the best.

@trevnorris
Copy link

So, a conceptually simple fix. Working out the details.

@kkoopa
Copy link

kkoopa commented Feb 18, 2015

I hope the solution will make MakeCallback always work, as intended. Having to correctly decide between two different ways of calling javascript from C++ dependent on context is really not acceptable, as it will lead to much confusion and headache.

@trevnorris
Copy link

@kkoopa That's the plan. :)

@misterdjules misterdjules added this to the 0.12.1 milestone Feb 20, 2015
@misterdjules
Copy link

Adding to the 0.12.1 milestone, as discussed during the last core team meeting.

@johnhaley81
Copy link

Will this be back-ported to 0.10?

@kkoopa
Copy link

kkoopa commented Feb 24, 2015

NAN will most likely reimplement and backport all of it, as I don't imagine 0.8 getting updated, so 0.10 will at least be covered that way.

@misterdjules misterdjules modified the milestones: 0.12.3, 0.12.2 Apr 1, 2015
@misterdjules
Copy link

@trevnorris Any update on this?

@trevnorris
Copy link

@misterdjules No, sorry. I've taken several stabs at it but every time I've been thwarted by some edge scenario. I'll take another look this week and see if anything new pops to mind.

@misterdjules misterdjules modified the milestones: 0.12.3, 0.12.4 May 14, 2015
@misterdjules misterdjules modified the milestones: 0.12.4, 0.12.5 May 25, 2015
@kkoopa
Copy link

kkoopa commented May 26, 2015

What is the status on this? Is it doable or should it be resolved some other way?

@trevnorris
Copy link

@kkoopa Ooh. Just had an idea. Something like:

bool in_make_callback = false;
if (!env->in_make_callback()) {
  env->set_in_make_callback(true);
  in_make_callback = true;
}
// perform operations 'n stuff
if (in_make_callback) {
  env->set_in_make_callback(false);
}

So the call can keep track of whether it's in a MakeCallback sequence or not. Don't have the mental energy to do this today, but I'll give it a shot tomorrow.

@kkoopa
Copy link

kkoopa commented Jun 2, 2015

Cool. Hope it works.

@misterdjules misterdjules modified the milestones: 0.12.5, 0.12.6 Jun 22, 2015
@misterdjules misterdjules removed this from the 0.12.5 milestone Jun 22, 2015
wbyoung added a commit to wbyoung/node-sqlite3 that referenced this issue Jun 30, 2015
I believe this is the most appropriate way to handle exceptions. Using
v8::Function::Call rather than node::MakeCallback (via NanMakeCallback)
means that these functions will not work with domains, but that seems
appropriate.

I don't know if nodejs/node-v0.x-archive#9245 or nodejs/nan#284 should be a concern
here or not.
@misterdjules misterdjules modified the milestones: 0.12.6, 0.12.7, 0.12.8 Jul 6, 2015
@trevnorris
Copy link

I still haven't found a way to fix all the edge cases. The rules for calling it are pretty simple. It has to be the first entry into the JS stack. I'm going to keep thinking about it though. Feel like there's got to be a cleaner architecture here.

@metamatt
Copy link
Author

Thanks for sticking with this. To reiterate why it's important, I'll link to the nan documentation:

Because node::MakeCallback() now takes an Isolate, and because it doesn't exist in older versions of Node, we've introduced NanMakeCallback(). You should always use this when calling a JavaScript function from C++.

Having two different mechanisms ("synchronously invoke JS code" from C++ code invoked synchronously, which is Function::Call or NanCallback, and "invoke JS code plus signal end of tick" from C++ code invoked asynchronously, which is MakeCallback or NanMakeCallback) just doesn't seem that bad to me if documented appropriately.

@trevnorris
Copy link

Ah yeah. That documentation is worded poorly.

@kkoopa
Copy link

kkoopa commented Jul 12, 2015

If one of you were to submit a PR with better documentation, I'd be happy to merge it, assuming this is the official approach (for the time being).

@trevnorris
Copy link

@metamatt I have a partial fix for this issue. My final issue to resolve is the fact that the initial set of callback don't run in a MakeCallback. So it's no use in that case to determine whether they're already being run. Haven't found a solution to this, but still looking.

trevnorris added a commit to trevnorris/node that referenced this issue Feb 12, 2016
Add a scope that will allow MakeCallback to know whether or not it's
currently running. This will prevent nextTickQueue and the
MicrotaskQueue from being processed recursively. It is also required to
wrap the bootloading stage since it doesn't run within a MakeCallback.

Ref: nodejs/node-v0.x-archive#9245
PR-URL: nodejs#4507
Reviewed-By: Fedor Indutny <[email protected]>
@trevnorris
Copy link

This may finally be fixed by nodejs/node#4507

rvagg pushed a commit to nodejs/node that referenced this issue Feb 15, 2016
Add a scope that will allow MakeCallback to know whether or not it's
currently running. This will prevent nextTickQueue and the
MicrotaskQueue from being processed recursively. It is also required to
wrap the bootloading stage since it doesn't run within a MakeCallback.

Ref: nodejs/node-v0.x-archive#9245
PR-URL: #4507
Reviewed-By: Fedor Indutny <[email protected]>
stefanmb pushed a commit to stefanmb/node that referenced this issue Feb 23, 2016
Add a scope that will allow MakeCallback to know whether or not it's
currently running. This will prevent nextTickQueue and the
MicrotaskQueue from being processed recursively. It is also required to
wrap the bootloading stage since it doesn't run within a MakeCallback.

Ref: nodejs/node-v0.x-archive#9245
PR-URL: nodejs#4507
Reviewed-By: Fedor Indutny <[email protected]>
MylesBorins pushed a commit to nodejs/node that referenced this issue Jun 7, 2016
Add a scope that will allow MakeCallback to know whether or not it's
currently running. This will prevent nextTickQueue and the
MicrotaskQueue from being processed recursively. It is also required to
wrap the bootloading stage since it doesn't run within a MakeCallback.

Ref: nodejs/node-v0.x-archive#9245
Ref: #7048
PR-URL: #4507
Reviewed-By: Fedor Indutny <[email protected]>
MylesBorins pushed a commit to nodejs/node that referenced this issue Jun 24, 2016
Add a scope that will allow MakeCallback to know whether or not it's
currently running. This will prevent nextTickQueue and the
MicrotaskQueue from being processed recursively. It is also required to
wrap the bootloading stage since it doesn't run within a MakeCallback.

Ref: nodejs/node-v0.x-archive#9245
Ref: #7048
PR-URL: #4507
Reviewed-By: Fedor Indutny <[email protected]>
MylesBorins pushed a commit to nodejs/node that referenced this issue Jun 28, 2016
Add a scope that will allow MakeCallback to know whether or not it's
currently running. This will prevent nextTickQueue and the
MicrotaskQueue from being processed recursively. It is also required to
wrap the bootloading stage since it doesn't run within a MakeCallback.

Ref: nodejs/node-v0.x-archive#9245
Ref: #7048
PR-URL: #4507
Reviewed-By: Fedor Indutny <[email protected]>
MylesBorins pushed a commit to nodejs/node that referenced this issue Jul 12, 2016
Add a scope that will allow MakeCallback to know whether or not it's
currently running. This will prevent nextTickQueue and the
MicrotaskQueue from being processed recursively. It is also required to
wrap the bootloading stage since it doesn't run within a MakeCallback.

Ref: nodejs/node-v0.x-archive#9245
Ref: #7048
PR-URL: #4507
Reviewed-By: Fedor Indutny <[email protected]>
@Trott Trott closed this as completed Apr 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

6 participants