Skip to content

Commit 04705ed

Browse files
committed
[api test bin dist] Update to use daemon.node
1 parent 65a91fb commit 04705ed

File tree

5 files changed

+217
-50
lines changed

5 files changed

+217
-50
lines changed

bin/forever

+85-31
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,115 @@
11
#!/usr/bin/env node
22

33
var path = require('path'),
4+
fs = require('fs'),
45
sys = require('sys'),
5-
argv = require('optimist').argv;
6-
6+
eyes = require('eyes'),
7+
daemon = require('daemon');
8+
9+
var accepts = ['start', 'stop', 'stopall', 'list'], action;
10+
if (accepts.indexOf(process.argv[2]) !== -1) {
11+
action = process.argv.splice(2,1)[0];
12+
}
13+
14+
var argv = require('optimist').argv;
15+
716
require.paths.unshift(path.join(__dirname, '..', 'lib'));
817

918
var forever = require('forever');
1019

1120
var help = [
12-
"usage: forever [FILE, ...] [options]",
21+
"usage: forever [start | stop | stopall | list] [options] SCRIPT [script options]",
1322
"",
1423
"options:",
15-
" -m MAX Only run the specified script MAX times",
16-
" -s, --silent Run the child script silencing stdout and stderr",
17-
" -h, --help You're staring at it",
18-
" -o, OUTFILE Logs stdout from child script to OUTFILE",
19-
" -e, ERRFILE Logs stderr from child script to ERRFILE"
24+
" start start SCRIPT as a daemon",
25+
" stop stop the daemon SCRIPT",
26+
" stopall stop all running forever scripts",
27+
" list list all running forever scripts",
28+
"",
29+
" -m MAX Only run the specified script MAX times",
30+
" -l LOGFILE Logs the forever output to LOGFILE",
31+
" -o OUTFILE Logs stdout from child script to OUTFILE",
32+
" -e ERRFILE Logs stderr from child script to ERRFILE",
33+
" -p PATH Base path for all forever related files (pid files, etc.)",
34+
" -s, --silent Run the child script silencing stdout and stderr",
35+
" -h, --help You're staring at it",
36+
"",
37+
"[Long Running Process]",
38+
" The forever process will continue to run outputting log messages to the console.",
39+
" ex. forever -o out.log -e err.log my-script.js",
40+
"",
41+
"[Daemon]",
42+
" The forever process will run as a daemon which will make the target process start",
43+
" in the background. This is extremely useful for remote starting simple node.js scripts",
44+
" without using nohup. It is recommended to run start with -o -l, & -e.",
45+
" ex. forever start -l forever.log -o out.log -e err.log my-daemon.js",
46+
" forever stop my-daemon.js",
47+
""
2048
].join('\n');
2149

2250
var mappings = {
23-
'm': 'max',
24-
's': 'silent',
51+
'm': 'max',
52+
'l': 'logfile',
53+
'p': 'path',
54+
's': 'silent',
2555
'silent': 'silent',
26-
'o': 'outfile',
27-
'e': 'errfile'
56+
'o': 'outfile',
57+
'e': 'errfile'
2858
};
2959

3060
// Show help prompt if requested
3161
if (argv.h || argv.help) {
3262
sys.puts(help);
33-
process.exit(0);
63+
return;
3464
}
3565

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

3871
// Setup pass-thru options for child-process
39-
options.options = [];
40-
41-
Object.keys(argv).forEach(function (key) {
42-
if (key !== '_') {
43-
if (typeof mappings[key] !== 'undefined') {
44-
options[mappings[key]] = argv[key];
45-
}
46-
else {
47-
options.options.push('-' + key);
48-
options.options.push(argv[key]);
49-
}
50-
}
51-
});
72+
var options = {};
73+
options.options = process.argv.splice(process.argv.indexOf(file) + 1);
5274

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

58-
// Run all of the files forever
59-
argv._.forEach(function (file) {
60-
forever.run(file, options);
61-
});
80+
// Setup configurations for forever
81+
var config = {
82+
root: argv.p
83+
};
84+
85+
forever.load(config, function () {
86+
// Run all of the files forever
87+
if (action) {
88+
switch (action) {
89+
case 'start':
90+
var uid = forever.randomString(16);
91+
options.uid = uid;
92+
options.pidFile = 'forever' + uid + '.pid';
93+
options.logfile = argv.l || 'forever' + uid + '.log'
94+
forever.startDaemon(file, options);
95+
break;
96+
case 'stop':
97+
sys.puts('Remark: stop not implemented in 0.2.1');
98+
sys.puts(' Try: killall -9 node');
99+
sys.puts(' ps axl | grep node');
100+
break;
101+
case 'stopall':
102+
sys.puts('Remark: stop not implemented in 0.2.1');
103+
sys.puts(' Try: killall -9 node');
104+
sys.puts(' ps axl | grep node');
105+
break;
106+
case 'list':
107+
sys.puts('Remark: stop not implemented in 0.2.1');
108+
sys.puts(' Try: ps axl | grep node');
109+
break;
110+
}
111+
}
112+
else {
113+
forever.start(file, options);
114+
}
115+
});

lib/forever.js

+121-12
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,81 @@ var sys = require('sys'),
1111
eyes = require('eyes'),
1212
path = require('path'),
1313
events = require('events'),
14-
spawn = require('child_process').spawn;
14+
spawn = require('child_process').spawn,
15+
daemon = require('daemon');
16+
17+
var config,
18+
forever = exports;
19+
20+
forever.load = function (options, callback) {
21+
options.root = options.root || path.join('/tmp', 'forever'),
22+
options.pidPath = options.pidPath || path.join(options.root, 'pids');
23+
config = options;
24+
25+
// Create the two directories, ignoring errors
26+
fs.mkdir(config.root, 0755, function (err) {
27+
fs.mkdir(config.pidPath, 0755, function (err2) {
28+
callback();
29+
});
30+
});
31+
};
32+
33+
// Export the core 'start' method
34+
forever.start = function (file, options) {
35+
return new Forever(file, options).start();
36+
};
37+
38+
forever.startDaemon = function (file, options) {
39+
options.logfile = options.logfile || 'forever.log';
40+
options.logfile = path.join(config.root, options.logfile);
41+
var runner = new Forever(file, options);
42+
43+
fs.open(options.logfile, 'w+', function (err, fd) {
44+
try {
45+
daemon.start(fd);
46+
daemon.lock(path.join(config.root, options.pidFile));
47+
runner.start().save();
48+
}
49+
catch (ex) {
50+
// Ignore errors
51+
}
52+
});
53+
};
54+
55+
forever.stop = function (file, options) {
56+
return new Forever(file, options).stop();
57+
};
58+
59+
//
60+
// function randomString (bits)
61+
// randomString returns a pseude-random ASCII string which contains at least the specified number of bits of entropy
62+
// the return value is a string of length ⌈bits/6⌉ of characters from the base64 alphabet
63+
//
64+
forever.randomString = function (bits) {
65+
var chars, rand, i, ret;
66+
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
67+
ret = '';
68+
69+
//
70+
// in v8, Math.random() yields 32 pseudo-random bits (in spidermonkey it gives 53)
71+
//
72+
while (bits > 0) {
73+
rand = Math.floor(Math.random()*0x100000000) // 32-bit integer
74+
// base 64 means 6 bits per character, so we use the top 30 bits from rand to give 30/6=5 characters.
75+
for (i=26; i>0 && bits>0; i-=6, bits-=6) {
76+
ret+=chars[0x3F & rand >>> i];
77+
}
78+
}
79+
return ret;
80+
};
1581

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

1985
options.options.unshift(file);
2086
options.silent = options.silent || false;
2187
options.forever = options.forever || false;
88+
options.logout = typeof options.logfile !== 'undefined';
2289
options.stdout = typeof options.outfile !== 'undefined';
2390
options.stderr = typeof options.errfile !== 'undefined';
2491

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

39106
sys.inherits(Forever, events.EventEmitter);
40107

41-
Forever.prototype.run = function () {
108+
Forever.prototype.start = function () {
42109
var self = this, child = spawn('node', this.options.options);
110+
43111
this.child = child;
112+
this.running = true;
44113

45114
// Hook all stream data and process it
46115
function listenTo (stream) {
47116
child[stream].on('data', function (data) {
48-
// If we haven't been silenced, write to the process stdout stream
49-
if (!self.options.silent) {
117+
// If we haven't been silenced, and we don't have a file stream
118+
// to output to write to the process stdout stream
119+
if (!self.options.silent && !self.options[stream]) {
50120
process.stdout.write(data);
51121
}
52122

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

58-
self.emit(stream, null, data);
128+
self.emit(stream, data);
59129
});
60130
}
61131

62132
// Listen to stdout and stderr
63133
listenTo('stdout');
64134
listenTo('stderr');
65-
135+
66136
child.on('exit', function (code) {
137+
self.log('Forever detected script exited with code: ' + code);
67138
self.times++;
139+
68140
if (self.options.forever || self.times < self.options.max) {
69141
self.emit('restart', null, self);
70142
process.nextTick(function () {
71-
self.run();
143+
self.log('Forever restarting script for ' + self.times + ' time');
144+
self.start();
72145
});
73146
}
74147
else {
148+
this.running = false;
149+
75150
// If had to write to an stdout file, close it
76151
if (self.options.stdout) {
77152
self.stdout.end();
@@ -90,10 +165,44 @@ Forever.prototype.run = function () {
90165
return this;
91166
};
92167

93-
// Export the Forever object
94-
exports.Forever = Forever;
168+
Forever.prototype.save = function () {
169+
var self = this;
170+
if (!this.running) {
171+
process.nextTick(function () {
172+
self.emit('error', new Error('Cannot save Forever instance that is not running'));
173+
});
174+
}
175+
176+
var childData = {
177+
pid: this.child.pid,
178+
foreverPid: process.pid,
179+
options: this.options.options.slice(1),
180+
file: this.options.options[0]
181+
};
182+
183+
var childPath = path.join(config.pidPath, childData.pid + '.pid');
184+
fs.writeFile(childPath, JSON.stringify(childData), function (err) {
185+
// Ignore errors
186+
});
187+
188+
// Chaining support
189+
return this;
190+
};
191+
192+
Forever.prototype.log = function (message) {
193+
if (!this.options.silent) {
194+
sys.puts(message);
195+
}
196+
}
95197

96-
// Export the core 'run' method
97-
exports.run = function (file, options) {
98-
return new Forever(file, options).run();
198+
Forever.prototype.stop = function () {
199+
//
200+
// Remark: This is not implemented in 0.2.1
201+
//
202+
203+
// Chaining support
204+
return this;
99205
};
206+
207+
// Export the Forever object
208+
forever.Forever = Forever;

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44
"version": "0.2.0",
55
"author": "Charlie Robbins <[email protected]>",
66
"contributors": [
7-
{ "name": "Fedor Indutny", "email": "[email protected]" }
8-
],
7+
{ "name": "Fedor Indutny", "email": "[email protected]" }
8+
],
99
"repository": {
1010
"type": "git",
1111
"url": "http://github.com/indexzero/forever.git"
1212
},
1313
"keywords": ["cli", "fault tolerant", "sysadmin", "tools"],
1414
"dependencies": {
15+
"daemon": ">= 0.1.0",
1516
"optimist": ">= 0.0.3",
16-
"vows": ">= 0.5.1"
17+
"vows": ">= 0.5.1",
1718
},
1819
"bin": { "forever": "./bin/forever" },
1920
"main": "./lib/forever",

samples/spawn-and-error.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ var sys = require('sys'),
1010
path = require('path'),
1111
spawn = require('child_process').spawn;
1212

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

1515
child.stdout.on('data', function (data) {
1616
sys.puts(data);
17-
throw new Error('User generated fault.');
17+
//throw new Error('User generated fault.');
1818
});
1919

2020
child.stderr.on('data', function (data) {

test/forever-test.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ var sys = require('sys'),
1212
assert = require('assert'),
1313
path = require('path'),
1414
vows = require('vows'),
15+
eyes = require('eyes'),
1516
forever = require('forever');
1617

1718
vows.describe('forever').addBatch({
@@ -34,7 +35,9 @@ vows.describe('forever').addBatch({
3435
assert.equal(child.options.max, 10);
3536
assert.isTrue(child.options.silent);
3637
assert.isArray(child.options.options);
37-
assert.isFunction(child.run);
38+
assert.isFunction(child.start);
39+
assert.isFunction(child.save);
40+
assert.isFunction(child.stop);
3841
}
3942
}
4043
},
@@ -49,7 +52,7 @@ vows.describe('forever').addBatch({
4952
});
5053

5154
child.on('exit', this.callback);
52-
child.run();
55+
child.start();
5356
},
5457
"should emit 'exit' when completed": function (err, child) {
5558
assert.equal(child.times, 3);

0 commit comments

Comments
 (0)