Skip to content

Instantly share code, notes, and snippets.

@metamatt
Created February 18, 2015 17:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save metamatt/4ce96adbf14de9a97d41 to your computer and use it in GitHub Desktop.
Save metamatt/4ce96adbf14de9a97d41 to your computer and use it in GitHub Desktop.
Demo for NodeJS preemption bugs via node::MakeCallback.

Demo for NodeJS preemption bugs via node::MakeCallback.

Usage:

  • node 0.12.0 should be on your PATH
  • grab all the files from this gist into the same directory
  • node-gyp rebuild to compile preemptor.cc into build/Release/preemptor.node
  • node demo to run the demo

For me, the demo output is

initialize state to 1
benign callback that does not change state, invoked directly
State should be 1, is actually 1
benign callback that does not change state, invoked via MakeCallback
End of tick! Change state from 1 to 2
State should be 1, is actually 2
tick over, state should be 2, is actually 2

Note that "benign callback, invoked via MakeCallback" also caused the tick to end, and the other nextTick callback changes externally observable state at a bad time, corrupting the world from the point of view of the outer code that should not have been preempted but was.

{
"targets": [
{
"target_name": "preemptor",
"include_dirs" : [
],
"sources": [
"preemptor.cc",
],
}
]
}
'use strict';
var preemptor = require('./build/Release/preemptor.node');
var importantState = 1;
console.log('initialize state to', importantState);
// Register callback. Shouldn't be invoked yet.
process.nextTick(function() {
console.log('End of tick! Change state from 1 to 2');
importantState = 2;
});
// Invoke a different callback via node addon using MakeCallback.
preemptor.callDirectly(function() {
console.log('benign callback that does not change state, invoked directly');
});
// What's the state now?
console.log('State should be 1, is actually', importantState);
// Invoke a different callback via node addon using MakeCallback.
preemptor.callViaMakeCallback(function() {
console.log('benign callback that does not change state, invoked via MakeCallback');
});
// What's the state now?
console.log('State should be 1, is actually', importantState);
// Check it again after tick ends, and it should really be 2
// (because the earlier nextTick callback should have run)
process.nextTick(function() {
console.log('tick over, state should be 2, is actually', importantState);
});
#include <node.h>
using namespace v8;
void SafeMethod(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
Handle<Function> cb = Handle<Function>::Cast(args[0]);
cb->Call(isolate->GetCurrentContext()->Global(), 0, NULL);
}
void DangerousMethod(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
Handle<Function> cb = Handle<Function>::Cast(args[0]);
node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(), cb, 0, NULL);
}
void init(Handle<Object> exports) {
NODE_SET_METHOD(exports, "callDirectly", SafeMethod);
NODE_SET_METHOD(exports, "callViaMakeCallback", DangerousMethod);
}
NODE_MODULE(addon, init)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment