mirror of
https://github.com/Unitech/pm2.git
synced 2025-12-08 20:35:53 +00:00
261 lines
6.9 KiB
JavaScript
261 lines
6.9 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Module dependencies
|
|
*/
|
|
|
|
var cluster = require('cluster');
|
|
var numCPUs = require('os').cpus() ? require('os').cpus().length : 1;
|
|
var path = require('path');
|
|
var util = require('util');
|
|
var log = require('debug')('pm2:god');
|
|
var async = require('async');
|
|
var EventEmitter2 = require('eventemitter2').EventEmitter2;
|
|
var fs = require('fs');
|
|
var os = require('os');
|
|
var p = path;
|
|
var Common = require('./Common');
|
|
var cst = require('../constants.js');
|
|
|
|
require('./WatchDog').connect();
|
|
|
|
/**
|
|
* 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: true,
|
|
delimiter: ':',
|
|
newListener: false,
|
|
maxListeners: 20
|
|
})
|
|
};
|
|
|
|
/**
|
|
* 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);
|
|
|
|
/**
|
|
* Forced entry to initialize cluster monitoring
|
|
*/
|
|
|
|
(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;
|
|
God.bus.emit('process:online', clu);
|
|
});
|
|
|
|
cluster.on('exit', function(clu, code, signal) {
|
|
handleExit(clu, code);
|
|
});
|
|
})();
|
|
|
|
/**
|
|
* Handle logic when a process exit (Node or Fork)
|
|
*/
|
|
function handleExit(clu, exit_code) {
|
|
console.log('Script %s %s exited code %d', clu.pm2_env.pm_exec_path, clu.pm2_env.pm_id, exit_code);
|
|
|
|
var stopping = (clu.pm2_env.status == cst.STOPPING_STATUS || clu.pm2_env.status == cst.ERRORED_STATUS) ? true : false;
|
|
var overlimit = false;
|
|
var pidFile = [clu.pm2_env.pm_pid_path, clu.pm2_env.pm_id, '.pid'].join('');
|
|
|
|
if (stopping) clu.process.pid = 0;
|
|
|
|
if (clu.pm2_env.status != cst.ERRORED_STATUS &&
|
|
clu.pm2_env.status != cst.STOPPING_STATUS)
|
|
clu.pm2_env.status = cst.STOPPED_STATUS;
|
|
|
|
try {
|
|
fs.unlinkSync(pidFile);
|
|
}catch(e) {}
|
|
|
|
/**
|
|
* 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) < (clu.pm2_env.min_uptime || 1000)) {
|
|
// Increment unstable restart
|
|
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'
|
|
// And stop to restart it
|
|
clu.pm2_env.status = cst.ERRORED_STATUS;
|
|
console.log('Script %s had too many unstable restarts (%d). Stopped.',
|
|
clu.pm2_env.pm_exec_path,
|
|
clu.pm2_env.unstable_restarts);
|
|
God.bus.emit('process:exit:overlimit', clu);
|
|
clu.pm2_env.unstable_restarts = 0;
|
|
clu.pm2_env.created_at = null;
|
|
overlimit = true;
|
|
}
|
|
}
|
|
|
|
God.bus.emit('process:exit', clu);
|
|
|
|
if (!stopping)
|
|
clu.pm2_env.restart_time = clu.pm2_env.restart_time + 1;
|
|
|
|
if (!stopping && !overlimit) God.executeApp(clu.pm2_env);
|
|
};
|
|
|
|
|
|
/**
|
|
* Launch the specified script (present in env)
|
|
*
|
|
* @param {Mixed} env
|
|
* @param {Function} cb
|
|
* @api private
|
|
*/
|
|
|
|
God.executeApp = function(env, cb) {
|
|
var env_copy = JSON.parse(JSON.stringify(env));
|
|
|
|
util._extend(env_copy, env.env);
|
|
|
|
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;
|
|
|
|
// 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(err, clu) {
|
|
if (cb && err) return cb(err);
|
|
|
|
God.clusters_db[env_copy.pm_id] = clu;
|
|
|
|
clu.once('error', function(err) {
|
|
console.log(err);
|
|
clu.pm2_env_copy.status = cst.ERRORED_STATUS;
|
|
});
|
|
|
|
clu.once('close', function(code) {
|
|
handleExit(clu, code);
|
|
});
|
|
|
|
God.bus.emit('process:online', clu);
|
|
if (cb) cb(null, clu);
|
|
return false;
|
|
});
|
|
}
|
|
else {
|
|
/**
|
|
* Cluster mode logic (for NodeJS apps)
|
|
*/
|
|
God.nodeApp(env_copy, function(err, clu) {
|
|
if (cb && err) return cb(err);
|
|
if (err) return false;
|
|
God.clusters_db[clu.pm2_env.pm_id] = clu;
|
|
if (cb) cb(null, clu);
|
|
return false;
|
|
});
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* First step before execution
|
|
* Check if the -i parameter has been passed
|
|
* so we execute the app multiple time
|
|
*
|
|
* @param {Mixed} env
|
|
* @api public
|
|
*/
|
|
God.prepare = function(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(JSON.parse(JSON.stringify(env)), function(err, clu) { // deep copy
|
|
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, 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.
|
|
* @param app {Object}
|
|
* @param [cwd] {string} Optional string to specify the cwd for the script.
|
|
* @param cb {Function}
|
|
* @returns {*}
|
|
*/
|
|
God.prepareJson = function (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);
|
|
};
|