Skip to content

Commit 5341887

Browse files
committed
Call callback directly to avoid unwanted preemption.
Nan's NanCallback::Call goes through Node's MakeCallback, which is designed to be used only by event callbacks (i.e. functions called from the libuv event loop), not invoked synchronously from a call stack where JS code was already running. This is because when MakeCallback returns it assumes it's the end of the current "tick", so it calls not only the callback you told it to, but also all tick callbacks registered for the current tick. Since node-weak's TargetCallback is invoked from GC, which could be invoked from anywhere at any time, the code it calls into (the callback attached to each weakref object) needs to be designed and written carefully, essentially to the standards of signal or interrupt handlers, since they preempt any JS code that was already running when GC kicked in. This is fine as long as the weakref callbacks are written to this standard, but it also means TargetCallback needs to avoid accidentally calling other callbacks that weren't written to this standard. The fix is to call the callback directly, instead of via MakeCallback. Fixes TooTallNate#35.
1 parent 98622b9 commit 5341887

File tree

2 files changed

+30
-2
lines changed

2 files changed

+30
-2
lines changed

src/weakref.cc

+7-2
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ NAN_PROPERTY_ENUMERATOR(WeakPropertyEnumerator) {
136136
}
137137

138138
/**
139-
* Weakref callback function. Invokes the "global" callback function.
139+
* Weakref callback function. Invokes the "global" callback function,
140+
* which emits the _CB event on the per-object EventEmitter.
140141
*/
141142

142143
NAN_WEAK_CALLBACK(TargetCallback) {
@@ -150,7 +151,11 @@ NAN_WEAK_CALLBACK(TargetCallback) {
150151
NanNew<Object>(cont->cbinfo->persistent),
151152
NanNew<Object>(cont->emitter)
152153
};
153-
globalCallback->Call(2, argv);
154+
// Invoke callback directly, not via NanCallback->Call() which uses
155+
// node::MakeCallback() which calls into process._tickCallback()
156+
// too. Those other callbacks are not safe to run from here.
157+
v8::Local<v8::Function> globalCallbackDirect = globalCallback->GetFunction();
158+
globalCallbackDirect->Call(NanGetCurrentContext()->Global(), 2, argv);
154159

155160
// clean everything up
156161
Local<Object> proxy = NanNew<Object>(cont->proxy);

test/callback.js

+23
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,29 @@ describe('weak()', function () {
4545
assert(called2)
4646
})
4747

48+
it('should preempt code for GC callback but not nextTick callbacks'
49+
, function(done) {
50+
var calledGcCallback = false
51+
, calledTickCallback = false
52+
weak({}, function() {
53+
calledGcCallback = true
54+
})
55+
56+
process.nextTick(function() {
57+
calledTickCallback = true
58+
});
59+
60+
assert(!calledGcCallback)
61+
assert(!calledTickCallback)
62+
gc()
63+
assert(calledGcCallback)
64+
assert(!calledTickCallback)
65+
setTimeout(function() {
66+
assert(calledTickCallback);
67+
done();
68+
}, 0)
69+
})
70+
4871
})
4972
})
5073

0 commit comments

Comments
 (0)