Skip to content

Instantly share code, notes, and snippets.

@metamatt
Created October 10, 2014 08:35
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/99ef2a5e12de2acd5e39 to your computer and use it in GitHub Desktop.
Save metamatt/99ef2a5e12de2acd5e39 to your computer and use it in GitHub Desktop.
Demo of undesirable preemption in NodeJS due to MakeCallback.
//
// preemption.js
//
// Demo of undesirable preemption in NodeJS 0.10 (at least) due to MakeCallback;
// outer JS code that's running along happily but transitions to C++ code and then
// some inner JS code and back can find that state has changed out from under it,
// in ways it cannot predict (or conversely, that the inner JS code can observe
// shared data structures in an inconsistent state because the outer code was in
// the process of making changes to them that were intended to be atomic, and are
// not atomic due to this preemption).
//
// This test doesn't actually involve "weak" per se; it's just a convenient way of
// forcing node.cc to execute node::MakeCallback() which calls process._tickCallback().
// So, you have to run this with "node --expose-gc".
//
// Something like https://github.com/joyent/node/issues/8231's addon.node
// would be easier way of provoking the same circumstances.
//
// To run this:
//
// npm install weak
// node --expose-gc preemption.js
//
'use strict';
var assert = require('assert');
var weak = require('weak');
if (typeof global.gc !== 'function') {
console.log('Must invoke node with --expose-gc');
process.exit(-1);
}
var __goodState = 'the cat is in a quantum superposition of states which you cannot observe';
var state = __goodState;
var didObserveBadState = false;
function checkState() {
if (state === __goodState) {
console.log('state check: ok');
} else {
console.log('STATE CHECK FAILED! (poor kitty)');
didObserveBadState = true;
}
}
function showTick(name) {
console.log('scheduling tick callback "%s"', name);
process.nextTick(function() {
console.log('in tick callback "%s"', name);
checkState();
console.trace();
});
}
function invokeMakeCallback() {
var o = { purpose: 'to be deleted' };
var w = weak(o, function() {
console.log('in JS from C++ from JS, about to return to C++ and invoke tick callbacks');
});
// Release our reference to o, and invoke the garbage collector,
// so that o gets garbage collected and node-weak invokes callbacks.
console.log('>>> enter explicit call to MakeCallback');
o = null;
global.gc();
console.log('<<< done with explicit call to MakeCallback');
}
function pleaseDoNotPreemptMe() {
assert.strictEqual(didObserveBadState, false);
// Tamper with state. Nobody should be able to observe this, right?
console.log('Beginning quantum experiments -- now would be a bad time to get preempted');
var originalState = state;
state = 'the cat is dead';
// Queue a tick callback.
showTick('observer');
// Cause MakeCallback to invoke that
invokeMakeCallback();
// Restore the state. (And we were just kidding about killing the cat.
// No cats were harmed during this thought experiment.)
state = originalState;
console.log('Exiting critical section. Normal quantum mechanic rules apply.');
// So, how'd that work out?
assert.strictEqual(didObserveBadState, false);
}
pleaseDoNotPreemptMe();
magi@ubuntu ~/s/d/experiments> node --expose-gc preemption.js
Beginning quantum experiments -- now would be a bad time to get preempted
scheduling tick callback "observer"
>>> enter explicit call to MakeCallback
in JS from C++ from JS, about to return to C++ and invoke tick callbacks
in tick callback "observer"
STATE CHECK FAILED! (poor kitty)
Trace
at /home/magi/src/demeterr/experiments/preemption.js:52:15
at process._tickCallback (node.js:415:13)
at invokeMakeCallback (/home/magi/src/demeterr/experiments/preemption.js:66:11)
at pleaseDoNotPreemptMe (/home/magi/src/demeterr/experiments/preemption.js:82:4)
at Object.<anonymous> (/home/magi/src/demeterr/experiments/preemption.js:94:1)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
<<< done with explicit call to MakeCallback
Exiting critical section. Normal quantum mechanic rules apply.
assert.js:93
throw new assert.AssertionError({
^
AssertionError: true === false
at pleaseDoNotPreemptMe (/home/magi/src/demeterr/experiments/preemption.js:90:11)
at Object.<anonymous> (/home/magi/src/demeterr/experiments/preemption.js:94:1)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:902:3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment