auto detective interpreter in fork mode; add corresponding testcase

This commit is contained in:
sailxjx 2013-10-28 11:39:45 +08:00
parent 2128843740
commit 5bce6bba94
6 changed files with 90 additions and 71 deletions

View File

@ -12,6 +12,7 @@ var Log = require('./Log');
var Satan = require('./Satan');
var cst = require('../constants.js');
var pkg = require('../package.json');
var extItps = require('./interpreter.json')
var CLI = module.exports = {};
require('colors');
@ -25,7 +26,7 @@ CLI.startFile = function(script) {
if (commander.name)
appConf['name'] = commander.name;
if (commander.instances)
appConf['instances'] = commander.instances;
appConf['instances'] = commander.instances;
if (commander.error)
appConf['error_file'] = commander.error;
if (commander.output)
@ -34,17 +35,19 @@ CLI.startFile = function(script) {
appConf['pid_file'] = commander.pid;
if (commander.cron)
appConf['cron_restart'] = commander.cron;
if (commander.interpreter)
appConf['exec_interpreter'] = commander.interpreter;
else if (extItps[path.extname(commander.args[0])])
appConf['exec_interpreter'] = extItps[path.extname(commander.args[0])]
else
appConf['exec_interpreter'] = 'node';
if (commander.executeCommand)
appConf['exec_mode'] = 'fork_mode';
else
appConf['exec_mode'] = 'cluster_mode';
if (commander.startOneTime)
appConf['one_launch_only'] = cst.ONE_LAUNCH_STATUS;
@ -52,7 +55,7 @@ CLI.startFile = function(script) {
process.version.match(/0.10/)) {
console.log(cst.PREFIX_MSG_ERR + ' [Warning], you\'re using the 0.10.x node version, it\'s prefered that you switch to fork mode by adding the -x parameter.');
}
// Script arguments
var env = commander.rawArgs.indexOf('--') + 1;
if (env > 1)
@ -63,7 +66,7 @@ CLI.startFile = function(script) {
console.log(cst.PREFIX_MSG + 'Writing configuration to ', dst_path);
fs.writeFileSync(dst_path, JSON.stringify(appConf));
}
Satan.executeRemote('findByFullPath', path.resolve(process.cwd(), script), function(err, exec) {
if (exec && exec[0].pm2_env.status == cst.STOPPED_STATUS) {
var app_name = exec[0].pm2_env.name;
@ -169,23 +172,23 @@ CLI.startup = function(platform) {
});
return;
}
var INIT_SCRIPT = "/etc/init.d/pm2-init.sh";
var script = fs.readFileSync(path.join(__dirname, cst.STARTUP_SCRIPT));
script = script.toString().replace(/%PM2_PATH%/g, process.mainModule.filename);
script = script.toString().replace(/%HOME_PATH%/g, process.env.HOME);
script = script.toString().replace(/%NODE_PATH%/g, process.execPath);
script = script.toString().replace(/%USER%/g, commander.user || 'root');
fs.writeFileSync(INIT_SCRIPT, script);
if (fs.existsSync(INIT_SCRIPT) == false) {
if (fs.existsSync(INIT_SCRIPT) == false) {
console.log(script);
console.log(cst.PREFIX_MSG_ERR + ' There is a problem when trying to write file : ' + INIT_SCRIPT);
process.exit(cst.ERROR_EXIT);
}
var cmd;
if (platform == 'centos')
@ -281,7 +284,7 @@ CLI.restartAll = function() {
console.error('Error retrieving process list: ' + err);
process.exit(cst.ERROR_EXIT);
}
list.forEach(function(l) {
Satan.executeRemote('restartProcessId', l.pm2_env.pm_id, function(err, res) {
if (err) {
@ -289,9 +292,9 @@ CLI.restartAll = function() {
process.exit(cst.ERROR_EXIT);
}
console.log(cst.PREFIX_MSG + 'Process ' + l.pm2_env.name + ' restarted');
});
});
setTimeout(function() {
});
});
setTimeout(function() {
console.log('\n' + cst.PREFIX_MSG + 'Process restarted');
speedList();
}, 1000);
@ -317,7 +320,7 @@ CLI.deleteProcess = function(process_name) {
process.exit(cst.ERROR_EXIT);
}
UX.processing.stop();
speedList();
speedList();
});
}
else if (!isNaN(parseInt(process_name))) {
@ -361,7 +364,7 @@ CLI.stopId = function(pm2_id) {
console.error(cst.PREFIX_MSG_ERR + pm2_id + ' : pm2 id not found');
process.exit(cst.ERROR_EXIT);
}
console.log(cst.PREFIX_MSG + ' Process stopped');
console.log(cst.PREFIX_MSG + ' Process stopped');
UX.processing.stop();
speedList();
});
@ -437,7 +440,7 @@ CLI.monit = function() {
console.log(cst.PREFIX_MSG + 'No online process to monitor');
process.exit(cst.ERROR_EXIT);
}
Monit.init(list);
function refresh(cb) {
@ -458,7 +461,7 @@ CLI.monit = function() {
CLI.streamLogs = function(id) {
var tdb = {};
Satan.executeRemote('getMonitorData', {}, function(err, list) {
if (err) {
console.error('Error retrieving process list: ' + err);
@ -467,7 +470,7 @@ CLI.streamLogs = function(id) {
list.forEach(function(l) {
tdb[l.pm2_env.pm_exec_path] = l;
});
console.log('########### Starting streaming logs for [%s] process', id || 'all');
for (var k in tdb) {
if (((!id || (id && !isNaN(parseInt(id)) && tdb[k].pm2_env.pm_id == id)) ||
@ -517,7 +520,7 @@ function validate(appConf) {
var instances = appConf['instances'];
var script = appConf['script'];
var cron_pattern = appConf['cron_restart'];
if (instances && isNaN(parseInt(instances)) && instances != 'max') {
console.error(cst.PREFIX_MSG_ERR + 'Instance option must be an integer or the "max" string');
process.exit(cst.ERROR_EXIT);
@ -543,12 +546,12 @@ function validate(appConf) {
function resolvePaths(app) {
validate(app);
app.env = {
pm_cwd : process.cwd(),
NODE_ENV : "production" // set default node_env as production
NODE_ENV : "production" // set default node_env as production
};
if (!('exec_mode' in app)) app['exec_mode'] = 'cluster_mode';
app["pm_exec_path"] = path.resolve(process.cwd(), app.script);
@ -558,12 +561,12 @@ function resolvePaths(app) {
app["name"] = p.basename(app["pm_exec_path"]);
}
if (fs.existsSync(app.pm_exec_path) == false) {
console.error(cst.PREFIX_MSG_ERR + 'script not found : ' + app.pm_exec_path);
process.exit(cst.ERROR_EXIT);
}
if (app.out_file)
app["pm_out_log_path"] = path.resolve(process.cwd(), app.out_file);
else {
@ -574,7 +577,7 @@ function resolvePaths(app) {
app["pm_out_log_path"] = path.resolve(cst.DEFAULT_LOG_PATH, [app.name, '-out.log'].join(''));
app.out_file = app["pm_out_log_path"];
}
delete app.out_file;
delete app.out_file;
if (app.error_file)
app["pm_err_log_path"] = path.resolve(process.cwd(), app.error_file);

View File

@ -48,7 +48,7 @@ function handleExit(clu, exit_code) {
var stopping = (clu.pm2_env.status == 'stopping' || clu.pm2_env.status == cst.ERRORED_STATUS) ? true : false;
var overlimit = false;
if (stopping) clu.process.pid = 0;
if (clu.pm2_env.status != cst.ERRORED_STATUS)
@ -57,15 +57,15 @@ function handleExit(clu, exit_code) {
/**
* Avoid infinite reloop if an error is present
*/
*/
// If the process has been created less than 15seconds ago
if ((Date.now() - clu.pm2_env.created_at) < 15000) {
// And if the process has an uptime less than a second
if ((Date.now() - clu.pm2_env.pm_uptime) < (1000 || clu.pm2_env.min_uptime)) {
// Increment unstable restart
clu.pm2_env.unstable_restarts += 1;
clu.pm2_env.unstable_restarts += 1;
}
if (clu.pm2_env.unstable_restarts >= 15) {
// Too many unstable restart in less than 15 seconds
// Set the process as "ERRORED"
@ -80,19 +80,19 @@ function handleExit(clu, exit_code) {
overlimit = true;
}
}
God.bus.emit('process:exit', clu);
if (!stopping)
clu.pm2_env.restart_time = clu.pm2_env.restart_time + 1;
if (!stopping && !overlimit) executeApp(clu.pm2_env);
};
/**
* For Node apps - Cluster mode
* It will wrap the code and enable load-balancing mode
* It will wrap the code and enable load-balancing mode
*/
function nodeApp(pm2_env, cb){
@ -107,7 +107,7 @@ function nodeApp(pm2_env, cb){
try {
clu = cluster.fork(pm2_env);
} catch(e) { console.error(e); }
// Receive message from child
clu.on('message', function(msg) {
switch (msg.type) {
@ -121,12 +121,12 @@ function nodeApp(pm2_env, cb){
// Avoid circular dependency
delete clu.process._handle.owner;
clu.once('online', function() {
if (cb) return cb(null, clu);
return false;
});
}
/**
@ -137,10 +137,11 @@ function nodeApp(pm2_env, cb){
function forkMode(pm2_env, cb) {
log('Entering in fork mode');
var spawn = require('child_process').spawn;
var interpreter = pm2_env.exec_interpreter || 'node';
var script = [pm2_env.pm_exec_path];
var out = fs.openSync(pm2_env.pm_out_log_path, 'a');
var err = fs.openSync(pm2_env.pm_err_log_path, 'a');
@ -149,7 +150,7 @@ function forkMode(pm2_env, cb) {
// Concat args if present
if (pm2_env.args)
script = script.concat(eval((pm2_env.args)));
var cspr = spawn(interpreter, script, {
env : pm2_env,
cwd : pm2_env.pm_cwd || process.cwd(),
@ -159,7 +160,7 @@ function forkMode(pm2_env, cb) {
cspr.unref();
fs.writeFileSync(pidFile, cspr.pid);
cspr.once('close', function(status) {
fs.close(out);
fs.close(err);
@ -167,10 +168,10 @@ function forkMode(pm2_env, cb) {
fs.unlinkSync(pidFile);
}catch(e) {}
});
// Avoid circular dependency
delete cspr._handle.owner;
cspr.process = {};
@ -179,7 +180,7 @@ function forkMode(pm2_env, cb) {
if (cb) return cb(null, cspr);
return false;
}
}
/**
* Forced entry to initialize cluster monitoring
@ -187,8 +188,8 @@ function forkMode(pm2_env, cb) {
(function initEngine() {
cluster.on('online', function(clu) {
console.log("%s - id%d worker online", clu.pm2_env.pm_exec_path, clu.pm2_env.pm_id);
clu.pm2_env.status = cst.ONLINE_STATUS;
console.log("%s - id%d worker online", clu.pm2_env.pm_exec_path, clu.pm2_env.pm_id);
clu.pm2_env.status = cst.ONLINE_STATUS;
God.bus.emit('process:online', clu);
});
@ -201,7 +202,7 @@ function forkMode(pm2_env, cb) {
* Launch the specified script (present in env)
*
* @param {Mixed} env
* @param {Function} cb
* @param {Function} cb
* @api private
*/
@ -221,27 +222,27 @@ function executeApp(env, cb) {
if (!env.created_at)
env['created_at'] = Date.now();
env['pm_uptime'] = Date.now();
env['status'] = 'launching';
// Raw env copy
var post_env = JSON.parse(JSON.stringify(env));
util._extend(post_env, env.env);
if (env['exec_mode'] == 'fork_mode') {
// If fork mode enabled
forkMode(post_env, function(err, clu) {
clu['pm2_env'] = env;
clu.pm2_env.status = cst.ONLINE_STATUS;
God.clusters_db[env.pm_id] = clu;
clu.once('error', function(err) {
console.log(err);
clu.pm2_env.status = cst.ERRORED_STATUS;
});
clu.once('close', function(code) {
handleExit(clu, code);
});
@ -293,7 +294,7 @@ God.prepare = function(env, cb) {
});
})(env.instances);
}
else {
else {
return executeApp(env, function(err, dt) {
cb(err, dt);
});
@ -360,7 +361,7 @@ God.getMonitorData = function(env, cb) {
usage.lookup(pro.pid, { keepHistory : true }, function(err, res) {
if (err)
return cb(new Error('Looks like a process in on infinite loop, wait 10s to get more informations'));
pro['monit'] = res;
arr.push(pro);
return ex(i - 1);
@ -465,7 +466,7 @@ God.startProcessId = function(id, cb) {
* Stop a process and set it on state "stopped"
*/
God.stopProcessId = function(id, cb) {
God.stopProcessId = function(id, cb) {
if (!(id in God.clusters_db))
return cb(new Error({msg : "PM ID unknown"}), {});
God.clusters_db[id].pm2_env.status = 'stopping';
@ -491,7 +492,7 @@ God.deleteProcessId = function(id, cb) {
delete God.clusters_db[id];
setTimeout(function() {
cb(null, God.getFormatedProcesses());
}, 200);
}, 200);
});
};
/**
@ -522,13 +523,13 @@ God.restartProcessId = function(id, cb) {
God.restartProcessName = function(name, cb) {
var arr = Object.keys(God.clusters_db);
(function ex(arr) {
if (arr[0] == null) return cb(null, God.getFormatedProcesses());
var key = arr[0];
var proc_env = God.clusters_db[key].pm2_env;
if (p.basename(proc_env.pm_exec_path) == name || proc_env.name == name) {
if (proc_env.status == cst.ONLINE_STATUS) {
process.kill(God.clusters_db[key].process.pid);
@ -549,7 +550,7 @@ God.restartProcessName = function(name, cb) {
return ex(arr);
}
return false;
})(arr);
})(arr);
};
/**
@ -559,7 +560,7 @@ God.restartProcessName = function(name, cb) {
God.stopProcessName = function(name, cb) {
var arr = Object.keys(God.clusters_db);
var stopped_proc = 0;
(function ex(arr) {
if (arr[0] == null) {
if (stopped_proc == 0)
@ -592,7 +593,7 @@ God.stopProcessName = function(name, cb) {
God.deleteProcessName = function(name, cb) {
var arr = Object.keys(God.clusters_db);
(function ex(arr) {
if (arr[0] == null) return cb(null, God.getFormatedProcesses());
var key = arr[0];
@ -620,7 +621,7 @@ God.deleteProcessName = function(name, cb) {
God.deleteAll = function(opts, cb) {
var arr = Object.keys(God.clusters_db);
(function ex(arr) {
if (arr[0] == null) return cb(null, God.getFormatedProcesses());
var key = arr[0];
@ -640,7 +641,7 @@ God.deleteAll = function(opts, cb) {
God.killMe = function(env, cb) {
for (var id in God.clusters_db) {
God.stopProcessId(id, function() {});
};
};
setTimeout(function() {
cb(null, {msg : 'pm2 killed'});
process.exit(cst.SUCCESS_EXIT);

9
lib/interpreter.json Normal file
View File

@ -0,0 +1,9 @@
{
".js": "node",
".coffee": "coffee",
".sh": "bash",
".py": "python",
".rb": "ruby",
".php": "php",
".go": "go"
}

View File

@ -49,7 +49,6 @@ function ispec {
success "$1"
}
function should {
OUT=`$pm2 prettylist | grep -o "$2" | wc -l`
[ $OUT -eq $3 ] || fail "$1"

3
test/fixtures/echo.coffee vendored Normal file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env coffee
setInterval (-> console.log 'ok'), 500

View File

@ -49,13 +49,10 @@ function ispec {
success "$1"
}
function should()
{
function should {
OUT=`$pm2 prettylist | grep -o "$2" | wc -l`
[ $OUT -eq $3 ] || fail "$1"
success "$1"
}
cd $file_path
@ -75,6 +72,13 @@ $pm2 kill
$pm2 start bashscript.sh -x --interpreter bash
should 'should has forked app' 'fork' 1
########### Auto Detective Interpreter In Fork mode
$pm2 kill
$pm2 start echo.coffee -x
should 'should has forked app' 'fork' 1
### Dump resurect should be ok
$pm2 dump