node-linux
Version:
Support daemon creation and management on Linux.
210 lines (179 loc) • 6.2 kB
JavaScript
// Handle input parameters
var optimist = require('optimist'),
fs = require('fs'),
max = 60,
p = require('path'),
argv = optimist
.demand('file')
.alias('f','file')
.describe('file','The absolute path of the script to be run as a process.')
.check(function(argv){
require('fs').existsSync(p.resolve(argv.f),function(exists){
return exists;
});
})
.demand('log')
.alias('l','log')
.describe('log','The absolute path of the log file.')
.demand('errorlog')
.alias('e','errorlog')
.describe('errorlog','The absolute path of the error log file.')
.demand('title')
.alias('t','title')
.describe('title','The name/title of the process.')
.default('maxretries',-1)
.alias('m','maxretries')
.describe('maxretries','The maximim number of times the process will be auto-restarted.')
.default('maxrestarts',5)
.alias('r','maxrestarts')
.describe('maxrestarts','The maximim number of times the process should be restarted within a '+max+' second period shutting down.')
.default('wait',1)
.alias('w','wait')
.describe('wait','The number of seconds between each restart attempt.')
.check(function(argv){
return argv.w >= 0;
})
.default('grow',0.25)
.alias('g','grow')
.describe('grow','A percentage growth rate at which the wait time is increased.')
.check(function(argv){
return (argv.g >= 0 && argv.g <= 1);
})
.default('abortonerror','no')
.alias('a','abortonerror')
.describe('abortonerror','Do not attempt to restart the process if it fails with an error,')
.check(function(argv){
return ['y','n','yes','no'].indexOf(argv.a.trim().toLowerCase()) >= 0;
})
.argv,
//log = new Logger(argv.e == undefined ? argv.l : {source:argv.l,eventlog:argv.e}),
fork = require('child_process').fork,
script = p.resolve(argv.f),
wait = argv.w*1000,
grow = argv.g+1,
attempts = 0,
startTime = null,
starts = 0,
child = null,
forcekill = false;
process.title = argv.t || 'Node.JS Script';
// Log Formatting - Standard Output Hook
process.stdout.write = (function(write) {
return function(logLine, encoding, fd) {
fs.appendFileSync(argv.log, new Date().toLocaleString()+' - SVCMGR - '+logLine);
};
})(process.stdout.write);
process.stderr.write = (function(write) {
return function(logLine, encoding, fd) {
fs.appendFileSync(argv.errorlog, new Date().toLocaleString()+' - SVCMGR - '+logLine);
};
})(process.stderr.write);
console.log(process.title + " start up");
if (argv.env){
if (Object.prototype.toString.call(argv.env) === '[object Array]'){
for(var i=0;i<argv.env.length;i++){
process.env[argv.env[i].split('=')[0]] = argv.env[i].split('=')[1];
}
} else {
process.env[argv.env.split('=')[0]] = argv.env.split('=')[1];
}
}
if (typeof argv.m === 'string'){
argv.m = parseInt(argv.m);
}
// Set the absolute path of the file
argv.f = p.resolve(argv.f);
// Hack to force the wrapper process to stay open by launching a ghost socket server
var server = require('net').createServer().listen(0, '127.0.0.1');
/**
* @method monitor
* Monitor the process to make sure it is running
*/
var monitor = function(){
if(!child.pid){
// If the number of periodic starts exceeds the max, kill the process
if (starts >= argv.r){
if (new Date().getTime()-(max*1000) <= startTime.getTime()){
console.error('Too many restarts within the last '+max+' seconds. Please check the script.');
process.exit();
}
}
setTimeout(function(){
wait = wait * grow;
attempts += 1;
if (attempts > argv.m && argv.m >= 0){
console.error('Too many restarts. '+argv.f+' will not be restarted because the maximum number of total restarts has been exceeded.');
process.exit();
} else {
launch();
}
},wait);
} else {
attempts = 0;
wait = argv.w * 1000;
}
};
/**
* @method launch
* A method to start a process.
*/
var launch = function(){
if (forcekill){
return;
}
console.log('Starting '+argv.f);
// Set the start time if it's null
if (startTime === null) {
startTime = startTime || new Date();
setTimeout(function(){
startTime = null;
starts = 0;
},(max*1000)+1);
}
starts += 1;
// Fork the child process piping stdin/out/err to the parent
child = fork(script, {env:process.env, silent:true});
child.stdout.on('data', function (data) {
fs.appendFileSync(argv.log, new Date().toLocaleString()+' - P.' + child.pid + ' - '+data);
});
child.stderr.on('data', function (data) {
fs.appendFileSync(argv.errorlog, new Date().toLocaleString()+' - P.' + child.pid + ' - '+ data);
});
// When the child dies, attempt to restart based on configuration
child.on('exit',function(code) {
console.warn(argv.f+' stopped running.');
// If an error is thrown and the process is configured to exit, then kill the parent.
if (code !== 0 && argv.a == "yes"){
console.error(argv.f+' exited with error code '+code);
process.exit();
server.unref();
}
delete child.pid;
// Monitor the process
monitor();
});
};
process.on('exit',function(){
console.log("Got exit signal, closing down");
forcekill = true;
child.kill();
process.exit();
});
// Killing the wrapper does not kill the child node process without this handler
process.on('SIGTERM',function(){
console.log("Got SIGTERM, closing down");
forcekill = true;
child.kill();
process.exit();
});
process.on('uncaughtException', function(err) {
console.error('Uncaught exception: ' + err.stack);
server.unref();
process.exit();
});
process.on('SIGHUP', function() {
console.log("SIGHUP received, restarting child");
child.kill();
});
// Launch the process
launch();