Created
October 10, 2014 08:35
-
-
Save metamatt/99ef2a5e12de2acd5e39 to your computer and use it in GitHub Desktop.
Demo of undesirable preemption in NodeJS due to MakeCallback.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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