Skip to content

Commit

Permalink
[api test bin dist] Update to use daemon.node
Browse files Browse the repository at this point in the history
  • Loading branch information
indexzero committed Nov 15, 2010
1 parent 65a91fb commit 04705ed
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 50 deletions.
116 changes: 85 additions & 31 deletions bin/forever
Original file line number Diff line number Diff line change
@@ -1,61 +1,115 @@
#!/usr/bin/env node

var path = require('path'),
fs = require('fs'),
sys = require('sys'),
argv = require('optimist').argv;

eyes = require('eyes'),
daemon = require('daemon');

var accepts = ['start', 'stop', 'stopall', 'list'], action;
if (accepts.indexOf(process.argv[2]) !== -1) {
action = process.argv.splice(2,1)[0];
}

var argv = require('optimist').argv;

require.paths.unshift(path.join(__dirname, '..', 'lib'));

var forever = require('forever');

var help = [
"usage: forever [FILE, ...] [options]",
"usage: forever [start | stop | stopall | list] [options] SCRIPT [script options]",
"",
"options:",
" -m MAX Only run the specified script MAX times",
" -s, --silent Run the child script silencing stdout and stderr",
" -h, --help You're staring at it",
" -o, OUTFILE Logs stdout from child script to OUTFILE",
" -e, ERRFILE Logs stderr from child script to ERRFILE"
" start start SCRIPT as a daemon",
" stop stop the daemon SCRIPT",
" stopall stop all running forever scripts",
" list list all running forever scripts",
"",
" -m MAX Only run the specified script MAX times",
" -l LOGFILE Logs the forever output to LOGFILE",
" -o OUTFILE Logs stdout from child script to OUTFILE",
" -e ERRFILE Logs stderr from child script to ERRFILE",
" -p PATH Base path for all forever related files (pid files, etc.)",
" -s, --silent Run the child script silencing stdout and stderr",
" -h, --help You're staring at it",
"",
"[Long Running Process]",
" The forever process will continue to run outputting log messages to the console.",
" ex. forever -o out.log -e err.log my-script.js",
"",
"[Daemon]",
" The forever process will run as a daemon which will make the target process start",
" in the background. This is extremely useful for remote starting simple node.js scripts",
" without using nohup. It is recommended to run start with -o -l, & -e.",
" ex. forever start -l forever.log -o out.log -e err.log my-daemon.js",
" forever stop my-daemon.js",
""
].join('\n');

var mappings = {
'm': 'max',
's': 'silent',
'm': 'max',
'l': 'logfile',
'p': 'path',
's': 'silent',
'silent': 'silent',
'o': 'outfile',
'e': 'errfile'
'o': 'outfile',
'e': 'errfile'
};

// Show help prompt if requested
if (argv.h || argv.help) {
sys.puts(help);
process.exit(0);
return;
}

var options = {};
// If we are passed more than one non-hyphenated
// options, only use the first one. Assume the
// rest are pass-through for the child process
var file = argv._[0];

// Setup pass-thru options for child-process
options.options = [];

Object.keys(argv).forEach(function (key) {
if (key !== '_') {
if (typeof mappings[key] !== 'undefined') {
options[mappings[key]] = argv[key];
}
else {
options.options.push('-' + key);
options.options.push(argv[key]);
}
}
});
var options = {};
options.options = process.argv.splice(process.argv.indexOf(file) + 1);

// If max isn't specified set it to run forever
if (typeof options['max'] === 'undefined') {
options.forever = true;
}

// Run all of the files forever
argv._.forEach(function (file) {
forever.run(file, options);
});
// Setup configurations for forever
var config = {
root: argv.p
};

forever.load(config, function () {
// Run all of the files forever
if (action) {
switch (action) {
case 'start':
var uid = forever.randomString(16);
options.uid = uid;
options.pidFile = 'forever' + uid + '.pid';
options.logfile = argv.l || 'forever' + uid + '.log'
forever.startDaemon(file, options);
break;
case 'stop':
sys.puts('Remark: stop not implemented in 0.2.1');
sys.puts(' Try: killall -9 node');
sys.puts(' ps axl | grep node');
break;
case 'stopall':
sys.puts('Remark: stop not implemented in 0.2.1');
sys.puts(' Try: killall -9 node');
sys.puts(' ps axl | grep node');
break;
case 'list':
sys.puts('Remark: stop not implemented in 0.2.1');
sys.puts(' Try: ps axl | grep node');
break;
}
}
else {
forever.start(file, options);
}
});
133 changes: 121 additions & 12 deletions lib/forever.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,81 @@ var sys = require('sys'),
eyes = require('eyes'),
path = require('path'),
events = require('events'),
spawn = require('child_process').spawn;
spawn = require('child_process').spawn,
daemon = require('daemon');

var config,
forever = exports;

forever.load = function (options, callback) {
options.root = options.root || path.join('/tmp', 'forever'),
options.pidPath = options.pidPath || path.join(options.root, 'pids');
config = options;

// Create the two directories, ignoring errors
fs.mkdir(config.root, 0755, function (err) {
fs.mkdir(config.pidPath, 0755, function (err2) {
callback();
});
});
};

// Export the core 'start' method
forever.start = function (file, options) {
return new Forever(file, options).start();
};

forever.startDaemon = function (file, options) {
options.logfile = options.logfile || 'forever.log';
options.logfile = path.join(config.root, options.logfile);
var runner = new Forever(file, options);

fs.open(options.logfile, 'w+', function (err, fd) {
try {
daemon.start(fd);
daemon.lock(path.join(config.root, options.pidFile));
runner.start().save();
}
catch (ex) {
// Ignore errors
}
});
};

forever.stop = function (file, options) {
return new Forever(file, options).stop();
};

//
// function randomString (bits)
// randomString returns a pseude-random ASCII string which contains at least the specified number of bits of entropy
// the return value is a string of length ⌈bits/6⌉ of characters from the base64 alphabet
//
forever.randomString = function (bits) {
var chars, rand, i, ret;
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
ret = '';

//
// in v8, Math.random() yields 32 pseudo-random bits (in spidermonkey it gives 53)
//
while (bits > 0) {
rand = Math.floor(Math.random()*0x100000000) // 32-bit integer
// base 64 means 6 bits per character, so we use the top 30 bits from rand to give 30/6=5 characters.
for (i=26; i>0 && bits>0; i-=6, bits-=6) {
ret+=chars[0x3F & rand >>> i];
}
}
return ret;
};

var Forever = function (file, options) {
events.EventEmitter.call(this);

options.options.unshift(file);
options.silent = options.silent || false;
options.forever = options.forever || false;
options.logout = typeof options.logfile !== 'undefined';
options.stdout = typeof options.outfile !== 'undefined';
options.stderr = typeof options.errfile !== 'undefined';

Expand All @@ -38,15 +105,18 @@ var Forever = function (file, options) {

sys.inherits(Forever, events.EventEmitter);

Forever.prototype.run = function () {
Forever.prototype.start = function () {
var self = this, child = spawn('node', this.options.options);

this.child = child;
this.running = true;

// Hook all stream data and process it
function listenTo (stream) {
child[stream].on('data', function (data) {
// If we haven't been silenced, write to the process stdout stream
if (!self.options.silent) {
// If we haven't been silenced, and we don't have a file stream
// to output to write to the process stdout stream
if (!self.options.silent && !self.options[stream]) {
process.stdout.write(data);
}

Expand All @@ -55,23 +125,28 @@ Forever.prototype.run = function () {
self[stream].write(data);
}

self.emit(stream, null, data);
self.emit(stream, data);
});
}

// Listen to stdout and stderr
listenTo('stdout');
listenTo('stderr');

child.on('exit', function (code) {
self.log('Forever detected script exited with code: ' + code);
self.times++;

if (self.options.forever || self.times < self.options.max) {
self.emit('restart', null, self);
process.nextTick(function () {
self.run();
self.log('Forever restarting script for ' + self.times + ' time');
self.start();
});
}
else {
this.running = false;

// If had to write to an stdout file, close it
if (self.options.stdout) {
self.stdout.end();
Expand All @@ -90,10 +165,44 @@ Forever.prototype.run = function () {
return this;
};

// Export the Forever object
exports.Forever = Forever;
Forever.prototype.save = function () {
var self = this;
if (!this.running) {
process.nextTick(function () {
self.emit('error', new Error('Cannot save Forever instance that is not running'));
});
}

var childData = {
pid: this.child.pid,
foreverPid: process.pid,
options: this.options.options.slice(1),
file: this.options.options[0]
};

var childPath = path.join(config.pidPath, childData.pid + '.pid');
fs.writeFile(childPath, JSON.stringify(childData), function (err) {
// Ignore errors
});

// Chaining support
return this;
};

Forever.prototype.log = function (message) {
if (!this.options.silent) {
sys.puts(message);
}
}

// Export the core 'run' method
exports.run = function (file, options) {
return new Forever(file, options).run();
Forever.prototype.stop = function () {
//
// Remark: This is not implemented in 0.2.1
//

// Chaining support
return this;
};

// Export the Forever object
forever.Forever = Forever;
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
"version": "0.2.0",
"author": "Charlie Robbins <charlie.robbins@gmail.com>",
"contributors": [
{ "name": "Fedor Indutny", "email": "fedor.indutny@gmail.com" }
],
{ "name": "Fedor Indutny", "email": "fedor.indutny@gmail.com" }
],
"repository": {
"type": "git",
"url": "http://github.com/indexzero/forever.git"
},
"keywords": ["cli", "fault tolerant", "sysadmin", "tools"],
"dependencies": {
"daemon": ">= 0.1.0",
"optimist": ">= 0.0.3",
"vows": ">= 0.5.1"
"vows": ">= 0.5.1",
},
"bin": { "forever": "./bin/forever" },
"main": "./lib/forever",
Expand Down
4 changes: 2 additions & 2 deletions samples/spawn-and-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ var sys = require('sys'),
path = require('path'),
spawn = require('child_process').spawn;

var child = spawn('node', [path.join(__dirname, 'count-timer.js')]);
var child = spawn('node', [path.join(__dirname, 'count-timer.js')], { cwd: __dirname });

child.stdout.on('data', function (data) {
sys.puts(data);
throw new Error('User generated fault.');
//throw new Error('User generated fault.');
});

child.stderr.on('data', function (data) {
Expand Down
7 changes: 5 additions & 2 deletions test/forever-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var sys = require('sys'),
assert = require('assert'),
path = require('path'),
vows = require('vows'),
eyes = require('eyes'),
forever = require('forever');

vows.describe('forever').addBatch({
Expand All @@ -34,7 +35,9 @@ vows.describe('forever').addBatch({
assert.equal(child.options.max, 10);
assert.isTrue(child.options.silent);
assert.isArray(child.options.options);
assert.isFunction(child.run);
assert.isFunction(child.start);
assert.isFunction(child.save);
assert.isFunction(child.stop);
}
}
},
Expand All @@ -49,7 +52,7 @@ vows.describe('forever').addBatch({
});

child.on('exit', this.callback);
child.run();
child.start();
},
"should emit 'exit' when completed": function (err, child) {
assert.equal(child.times, 3);
Expand Down

0 comments on commit 04705ed

Please sign in to comment.