You can embed "script breaker" into UNKNOWN_CODE. Something like:
;setTimeout(function() { throw new Error("Execution time limit reached!") }, 2000);
So, the whole thing would look like this:
var UNKNOWN_CODE = "while(true){}";
var scriptBreaker = ';setTimeout(function() { throw new Error("Execution time limit reached!") }, 2000);';
var vm = require("vm");
var obj = {};
var ctx = vm.createContext(obj);
var script = vm.createScript(scriptBreaker + UNKNOWN_CODE);
try {
script.runInNewContext(ctx);
console.log("Finished");
}
catch (err) {
console.log("Timeout!");
// Handle Timeout Error...
}
UPDATE:
After more tests I've come to conclusion that reliable approach would be to use process as pointed Esailija. However, I'm doing it a bit differently.
In main app I have a code like this:
var cp = require('child_process');
function runUnsafeScript(script, callback) {
var worker = cp.fork('./script-runner', [script]);
worker.on('message', function(data) {
worker.kill();
callback(false, data);
});
worker.on('exit', function (code, signal) {
callback(new Error(code), false);
});
worker.on('error', function (err) {
callback(err, false);
});
setTimeout(function killOnTimeOut() {
worker.kill();
callback(new Error("Timeout"), false);
}, 5000);
}
In script-runner.js it looks like following:
var vm = require("vm");
var script = vm.createScript( process.argv[2] );
var obj = { sendResult:function (result) { process.send(result); process.exit(0); } };
var context = vm.createContext(obj);
script.runInNewContext(context);
process.on('uncaughtException', function(err) {
process.exit(1);
});
This approach allowed to achieve following goals:
- run script in a limited context
- avoid problems with dead loops and exceptions
- run many (limited by hardware) unsafe scripts simultaneously so they don't interrupt each other
- pass script execution results to the main app for further processing