mirror of
https://github.com/Unitech/pm2.git
synced 2025-12-08 20:35:53 +00:00
318 lines
8.7 KiB
JavaScript
318 lines
8.7 KiB
JavaScript
|
|
var cluster = require('cluster');
|
|
var numCPUs = require('os').cpus() ? require('os').cpus().length : 1;
|
|
var path = require('path');
|
|
var util = require('util');
|
|
var EventEmitter2 = require('eventemitter2').EventEmitter2;
|
|
var fs = require('fs');
|
|
var p = path;
|
|
var Common = require('./Common');
|
|
var cst = require('../constants.js');
|
|
var pidusage = require('pidusage');
|
|
|
|
// require('webkit-devtools-agent').start({
|
|
// port: 9999,
|
|
// bind_to: '0.0.0.0',
|
|
// ipc_port: 3333,
|
|
// verbose: true
|
|
// });
|
|
|
|
/**
|
|
* Override cluster module configuration
|
|
*/
|
|
cluster.setupMaster({
|
|
exec : p.resolve(p.dirname(module.filename), 'ProcessContainer.js')
|
|
});
|
|
|
|
/**
|
|
* Expose God
|
|
*/
|
|
var God = module.exports = {
|
|
next_id : 0,
|
|
clusters_db : {},
|
|
bus : new EventEmitter2({
|
|
wildcard: false,
|
|
delimiter: ':',
|
|
maxListeners: 1000
|
|
})
|
|
};
|
|
|
|
/**
|
|
* Populate God namespace
|
|
*/
|
|
require('./God/Methods.js')(God);
|
|
require('./God/ForkMode.js')(God);
|
|
require('./God/ClusterMode.js')(God);
|
|
require('./God/Reload')(God);
|
|
require('./God/ActionMethods')(God);
|
|
|
|
/**
|
|
* Handle logic when a process exit (Node or Fork)
|
|
* @method handleExit
|
|
* @param {} clu
|
|
* @param {} exit_code
|
|
* @return
|
|
*/
|
|
God.handleExit = function handleExit(clu, exit_code) {
|
|
console.log('Script %s %s exit', clu.pm2_env.pm_exec_path, clu.pm2_env.pm_id);
|
|
|
|
var proc = this.clusters_db[clu.pm2_env.pm_id];
|
|
|
|
if (!proc) {
|
|
console.error('Process undefined ? with process id ', clu.pm2_env.pm_id);
|
|
return false;
|
|
}
|
|
|
|
pidusage.unmonitor(proc.process.pid);
|
|
|
|
var stopping = (proc.pm2_env.status == cst.STOPPING_STATUS || proc.pm2_env.status == cst.ERRORED_STATUS) ? true : false;
|
|
var overlimit = false;
|
|
|
|
if (stopping) proc.process.pid = 0;
|
|
|
|
if (proc.pm2_env.axm_actions) proc.pm2_env.axm_actions = [];
|
|
|
|
if (proc.pm2_env.status != cst.ERRORED_STATUS &&
|
|
proc.pm2_env.status != cst.STOPPING_STATUS)
|
|
proc.pm2_env.status = cst.STOPPED_STATUS;
|
|
|
|
try {
|
|
fs.unlinkSync(proc.pm2_env.pm_pid_path);
|
|
} catch (e) {
|
|
console.error('Error when unlinking PID file', e);
|
|
}
|
|
|
|
/**
|
|
* Avoid infinite reloop if an error is present
|
|
*/
|
|
// If the process has been created less than 15seconds ago
|
|
|
|
// And if the process has an uptime less than a second
|
|
var min_uptime = typeof(proc.pm2_env.min_uptime) !== 'undefined' ? proc.pm2_env.min_uptime : 1000;
|
|
var max_restarts = typeof(proc.pm2_env.max_restarts) !== 'undefined' ? proc.pm2_env.max_restarts : 15;
|
|
|
|
if ((Date.now() - proc.pm2_env.created_at) < (min_uptime * max_restarts)) {
|
|
if ((Date.now() - proc.pm2_env.pm_uptime) < min_uptime) {
|
|
// Increment unstable restart
|
|
proc.pm2_env.unstable_restarts += 1;
|
|
}
|
|
|
|
if (proc.pm2_env.unstable_restarts >= max_restarts) {
|
|
// Too many unstable restart in less than 15 seconds
|
|
// Set the process as 'ERRORED'
|
|
// And stop to restart it
|
|
proc.pm2_env.status = cst.ERRORED_STATUS;
|
|
|
|
console.log('Script %s had too many unstable restarts (%d). Stopped. %j',
|
|
proc.pm2_env.pm_exec_path,
|
|
proc.pm2_env.unstable_restarts,
|
|
proc.pm2_env.status);
|
|
this.bus.emit('process:exit:overlimit', { process : Common.serialize(proc) });
|
|
proc.pm2_env.unstable_restarts = 0;
|
|
proc.pm2_env.created_at = null;
|
|
overlimit = true;
|
|
}
|
|
}
|
|
|
|
this.bus.emit('process:exit', { process : Common.serialize(proc) });
|
|
|
|
if (!stopping)
|
|
proc.pm2_env.restart_time = proc.pm2_env.restart_time + 1;
|
|
|
|
if (!stopping && !overlimit)
|
|
this.executeApp(proc.pm2_env);
|
|
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Launch the specified script (present in env)
|
|
* @api private
|
|
* @method executeApp
|
|
* @param {Mixed} env
|
|
* @param {Function} cb
|
|
* @return Literal
|
|
*/
|
|
God.executeApp = function executeApp(env, cb) {
|
|
var env_copy = Common.serialize(env);
|
|
|
|
util._extend(env_copy, env_copy.env);
|
|
|
|
env_copy['axm_actions'] = [];
|
|
|
|
if (env_copy['pm_id'] === undefined) {
|
|
/**
|
|
* Enter here when it's the first time that the process is created
|
|
* 1 - Assign a new id
|
|
* 2 - Reset restart time and unstable_restarts
|
|
* 3 - Assign a log file name depending on the id
|
|
* 4 - If watch option is set, look for changes
|
|
*/
|
|
env_copy['pm_id'] = God.getNewId();
|
|
env_copy['restart_time'] = 0;
|
|
env_copy['unstable_restarts'] = 0;
|
|
|
|
// add -pm_id to pid file
|
|
env_copy.pm_pid_path = env_copy.pm_pid_path.replace(/-[0-9]+\.pid$|\.pid$/g, '-' + env_copy['pm_id'] + '.pid');
|
|
|
|
// If merge option, dont separate the logs
|
|
if (!env_copy['merge_logs']) {
|
|
env_copy.pm_out_log_path = env_copy.pm_out_log_path.replace(/-[0-9]+\.log$|\.log$/g, '-' + env_copy['pm_id'] + '.log');
|
|
env_copy.pm_err_log_path = env_copy.pm_err_log_path.replace(/-[0-9]+\.log$|\.log$/g, '-' + env_copy['pm_id'] + '.log');
|
|
}
|
|
|
|
if (env_copy['watch']) {
|
|
env_copy['watcher'] = require('./Watcher').watch(env_copy);
|
|
}
|
|
}
|
|
|
|
if (!env_copy.created_at)
|
|
env_copy['created_at'] = Date.now();
|
|
|
|
env_copy['pm_uptime'] = Date.now();
|
|
env_copy['status'] = cst.LAUNCHING_STATUS;
|
|
|
|
if (env_copy['exec_mode'] == 'fork_mode') {
|
|
/**
|
|
* Fork mode logic
|
|
*/
|
|
God.forkMode(env_copy, function forkMode(err, clu) {
|
|
if (cb && err) return cb(err);
|
|
if (err) return false;
|
|
|
|
var old_env = God.clusters_db[clu.pm2_env.pm_id];
|
|
if (old_env) old_env = null;
|
|
|
|
var proc = God.clusters_db[env_copy.pm_id] = clu;
|
|
|
|
clu.once('error', function cluError(err) {
|
|
proc.pm2_env.status = cst.ERRORED_STATUS;
|
|
return false;
|
|
});
|
|
|
|
clu.once('close', function cluClose(code) {
|
|
proc.removeAllListeners();
|
|
clu._reloadLogs = null;
|
|
God.handleExit(proc, code);
|
|
return false;
|
|
});
|
|
|
|
God.bus.emit('process:online', {process : Common.serialize(proc)});
|
|
|
|
if (cb) cb(null, clu);
|
|
return false;
|
|
});
|
|
}
|
|
else {
|
|
/**
|
|
* Cluster mode logic (for NodeJS apps)
|
|
*/
|
|
God.nodeApp(env_copy, function nodeApp(err, clu) {
|
|
if (cb && err) return cb(err);
|
|
if (err) return false;
|
|
|
|
var old_env = God.clusters_db[clu.pm2_env.pm_id];
|
|
|
|
if (old_env) {
|
|
old_env = null;
|
|
|
|
if (typeof(God.clusters_db[clu.pm2_env.pm_id].process._handle) !== 'undefined') {
|
|
if (God.clusters_db[clu.pm2_env.pm_id].process._handle)
|
|
God.clusters_db[clu.pm2_env.pm_id].process._handle.owner = null;
|
|
God.clusters_db[clu.pm2_env.pm_id].process._handle = null;
|
|
God.clusters_db[clu.pm2_env.pm_id].process = null;
|
|
}
|
|
God.clusters_db[clu.pm2_env.pm_id] = null;
|
|
}
|
|
|
|
var proc = God.clusters_db[clu.pm2_env.pm_id] = clu;
|
|
|
|
clu.once('online', function cluOnline() {
|
|
proc.pm2_env.status = cst.ONLINE_STATUS;
|
|
|
|
console.log('%s - id%d worker online', proc.pm2_env.pm_exec_path, proc.pm2_env.pm_id);
|
|
|
|
God.bus.emit('process:online', { process : Common.serialize(proc) });
|
|
|
|
if (cb) return cb(null, proc);
|
|
return false;
|
|
});
|
|
|
|
clu.once('exit', function cluExit(exited_clu, code) {
|
|
proc.removeAllListeners();
|
|
proc.process.removeAllListeners();
|
|
God.handleExit(proc, code);
|
|
return false;
|
|
});
|
|
|
|
return false;
|
|
});
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* First step before execution
|
|
* Check if the -i parameter has been passed
|
|
* so we execute the app multiple time
|
|
* @api public
|
|
* @method prepare
|
|
* @param {Mixed} env
|
|
* @param {} cb
|
|
* @return Literal
|
|
*/
|
|
God.prepare = function prepare(env, cb) {
|
|
// If instances option is set (-i [arg])
|
|
if (env.instances) {
|
|
if (env.instances == 'max') env.instances = numCPUs;
|
|
env.instances = parseInt(env.instances);
|
|
// multi fork depending on number of cpus
|
|
var arr = [];
|
|
|
|
(function ex(i) {
|
|
if (i <= 0) {
|
|
if (cb != null) return cb(null, arr);
|
|
return false;
|
|
}
|
|
|
|
return God.executeApp(Common.serialize(env), function(err, clu) {
|
|
if (err) return ex(i - 1);
|
|
arr.push(clu);
|
|
return ex(i - 1);
|
|
});
|
|
})(env.instances);
|
|
}
|
|
else {
|
|
return God.executeApp(env, function(err, dt) {
|
|
cb(err, [Common.serialize(dt)]);
|
|
});
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Allows an app to be prepared using the same json format as the CLI, instead
|
|
* of the internal PM2 format.
|
|
* An array of applications is not currently supported. Call this method
|
|
* multiple times with individual app objects if you have several to start.
|
|
* @method prepareJson
|
|
* @param app {Object}
|
|
* @param {} cwd
|
|
* @param cb {Function}
|
|
* @return CallExpression
|
|
*/
|
|
God.prepareJson = function prepareJson(app, cwd, cb) {
|
|
if (!cb) {
|
|
cb = cwd;
|
|
cwd = undefined;
|
|
}
|
|
|
|
app = Common.resolveAppPaths(app, cwd);
|
|
|
|
if (app instanceof Error)
|
|
return cb(app);
|
|
|
|
return God.prepare(app, cb);
|
|
};
|