NodeJS lets you invoke a "callback" (really, invoke JS code from C++ code) 2 ways:
- calling the callback directly; if you have a
Handle<Function> foo you can just foo->Call()
- calling the callback via
MakeCallback (in src/node.cc)
The former ("direct") case just calls from C++ into JS synchronously, and returns back into C++ when the JS code returns.
The latter (MakeCallback) case calls the JS callback function, but then also ends the current tick, calling process._tickCallback() and any other callbacks registered via nextTick(). This form is intended only for use by C++ code that was dispatched from the libuv event loop, and not for C++ code that was invoked from JS code. Because in that case (JS code "A" running, calls into C++ code in a node addon, the addon's C++ code calls MakeCallback, MakeCallback dispatches the nextTick callbacks) the JS code marked "A" has been preempted, and state changed by the nextTick callbacks can be corrupted. See TooTallNate/node-weak#35 for an example of how dangerous this is.
Use cases for node addons (that would want to use nan) wanting to invoke JS code from C++ code that was invoked directly from JS (not from a libuv event callback) include
For these cases, use of MakeCallback is not only incorrect but extremely dangerous. The right answer is to call the callback directly. But nan doesn't expose a way of doing this; NanCallback::Call is actually wrapping MakeCallback. So is the right answer to avoid NanCallback altogether? I don't think so; it serves a very real need to deal more easily with V8's memory management; using Persistent<Function> got harder in newer V8 (node 0.11/0.12) and so it's much nicer to use NanCallback instead of raw Handle<Function>. So I think NanCallback should provide separate methods for calling a callback directly and calling a callback via MakeCallback, and make the difference really obvious so people don't accidentally do the latter. (Note in my patch for the node-weak instance of this, I do use NanCallback for memory management, but then have to go to some lengths to extract the real Handle<Function> to call directly.)
I think the problem here is exacerbated by (a) nebulous terminology like "callback" (is any function that gets called a "callback"? Not all callbacks are the same) and (b) the difficulty of using Persistent<Function> without NanCallback. But when you do use NanCallback and because of (a), it's just too tempting to accidentally call NanCallback::Call and thus MakeCallback and then preempt your JS code and then suffer all manner of heisenbugs.
So my suggestion and request is:
- add a new
NanCallback::CallDirect that wraps Handle<Function>::Call
- rename
NanCallback::Call to NanCallback::CallAndEndTick (or something similarly explicit that's difficult to confuse)
- in a perfect world, remove
NanCallback::Call. That will probably be too annoying to addon authors already using it correctly. Then, at least deprecate it (have it generate a compile-time warning, and then act as CallAndEndTick?)
NodeJS lets you invoke a "callback" (really, invoke JS code from C++ code) 2 ways:
Handle<Function> fooyou can justfoo->Call()MakeCallback(in src/node.cc)The former ("direct") case just calls from C++ into JS synchronously, and returns back into C++ when the JS code returns.
The latter (
MakeCallback) case calls the JS callback function, but then also ends the current tick, callingprocess._tickCallback()and any other callbacks registered vianextTick(). This form is intended only for use by C++ code that was dispatched from the libuv event loop, and not for C++ code that was invoked from JS code. Because in that case (JS code "A" running, calls into C++ code in a node addon, the addon's C++ code calls MakeCallback, MakeCallback dispatches the nextTick callbacks) the JS code marked "A" has been preempted, and state changed by the nextTick callbacks can be corrupted. See TooTallNate/node-weak#35 for an example of how dangerous this is.Use cases for node addons (that would want to use
nan) wanting to invoke JS code from C++ code that was invoked directly from JS (not from a libuv event callback) includeFor these cases, use of
MakeCallbackis not only incorrect but extremely dangerous. The right answer is to call the callback directly. Butnandoesn't expose a way of doing this;NanCallback::Callis actually wrappingMakeCallback. So is the right answer to avoidNanCallbackaltogether? I don't think so; it serves a very real need to deal more easily with V8's memory management; usingPersistent<Function>got harder in newer V8 (node 0.11/0.12) and so it's much nicer to useNanCallbackinstead of rawHandle<Function>. So I thinkNanCallbackshould provide separate methods for calling a callback directly and calling a callback viaMakeCallback, and make the difference really obvious so people don't accidentally do the latter. (Note in my patch for the node-weak instance of this, I do useNanCallbackfor memory management, but then have to go to some lengths to extract the realHandle<Function>to call directly.)I think the problem here is exacerbated by (a) nebulous terminology like "callback" (is any function that gets called a "callback"? Not all callbacks are the same) and (b) the difficulty of using
Persistent<Function>withoutNanCallback. But when you do useNanCallbackand because of (a), it's just too tempting to accidentally call NanCallback::Call and thus MakeCallback and then preempt your JS code and then suffer all manner of heisenbugs.So my suggestion and request is:
NanCallback::CallDirectthat wrapsHandle<Function>::CallNanCallback::CalltoNanCallback::CallAndEndTick(or something similarly explicit that's difficult to confuse)NanCallback::Call. That will probably be too annoying to addon authors already using it correctly. Then, at least deprecate it (have it generate a compile-time warning, and then act as CallAndEndTick?)