Skip to content

Commit

Permalink
Merge pull request #42 from storify/respawn
Browse files Browse the repository at this point in the history
Respawn
  • Loading branch information
rauchg committed Sep 4, 2012
2 parents ea19cc7 + 41d6527 commit dc4e57e
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 1 deletion.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ The `up` command accepts the following options:
- Strings like `'10s'` are accepted.
- Defaults to `'10m'`, or `'500ms'` if `NODE_ENV` is `development`.

- `-k`/`--keepalive`

- start a new worker after one dies unexpectedly

- `-f`/`--pidfile`

- A filename to write the pid to
Expand Down Expand Up @@ -114,6 +118,10 @@ parameters:
- `workerTimeout`: (`Number`|`String`): see `--timeout` above.
- `title`: (`String`): see `--title` above.
- `assumeReady`: (`Boolean`): see Worker readiness below.
- `keepAlive`: (`Boolean`): see `--keepalive` above.
- `minExpectedLifetime`: (`Number`|`String`): Number of ms a worker is
expected to live. Don't auto-respawn if a worker dies earlier. Strings
like `'10s'` are accepted. Defaults to `'20s'`.

## Middleware

Expand Down
8 changes: 8 additions & 0 deletions bin/up
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ program
.option('-n, --number <workers>', 'Number of workers to spawn.'
, 'development' == process.env.NODE_ENV ? 1 : cpus)
.option('-t, --timeout [ms]', 'Worker timeout.')
.option('-k, --keepalive', 'Restart failed workers.')

/**
* Capture requires.
Expand Down Expand Up @@ -134,6 +135,12 @@ if (null != workerTimeout && isNaN(ms(workerTimeout))) {
, program.timeout, ms(workerTimeout));
}

/**
* Parse keepalive
*/

var keepAlive = program.keepalive;

/**
* Start!
*/
Expand All @@ -148,6 +155,7 @@ var httpServer = http.Server().listen(program.port)
, workerTimeout: workerTimeout
, requires: requires
, title: program.title
, keepAlive: keepAlive
})

/**
Expand Down
20 changes: 19 additions & 1 deletion lib/up.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ var workerTimeout = 'development' == env ? '500ms' : '10m';

var numWorkers = 'development' == env ? 1 : cpus;

/**
* Default minimum expected lifetime of a worker.
* If a worker dies younger, we don't respawn even if keepAlive == true.
* We want to prevent auto-respawning storms from overloading the system.
*/
var minExpectedLifetime = '20s';

/**
* UpServer factory/constructor.
*
Expand All @@ -75,6 +82,8 @@ function UpServer (server, file, opts) {
? opts.workerTimeout : workerTimeout);
this.requires = opts.requires || [];
this.assumeReady = opts.assumeReady === undefined ? true : !!opts.assumeReady;
this.keepAlive = opts.keepAlive || false;
this.minExpectedLifetime = ms(opts.minExpectedLifetime != null ? opts.minExpectedLifetime : minExpectedLifetime);
if (false !== opts.workerPingInterval) {
this.workerPingInterval = ms(opts.workerPingInterval || '1m');
}
Expand Down Expand Up @@ -202,7 +211,15 @@ UpServer.prototype.spawnWorker = function (fn) {
if (~self.workers.indexOf(w)) {
self.workers.splice(self.workers.indexOf(w), 1);
self.lastIndex = -1;
// @TODO: auto-add workers ?
if (self.keepAlive && (self.workers.length + self.spawning.length < self.numWorkers)) {
if (new Date().getTime() - w.birthtime < self.minExpectedLifetime) {
debug('worker %s found dead at a too young age. won\'t respawn new worker', w.pid);
}
else {
debug('worker %s found dead. spawning 1 new worker', w.pid);
self.spawnWorker();
}
}
}
self.emit('terminate', w)
break;
Expand Down Expand Up @@ -273,6 +290,7 @@ function Worker (server) {
this.proc.on('message', this.onMessage.bind(this));
this.proc.on('exit', this.onExit.bind(this));
this.pid = this.proc.pid;
this.birthtime = new Date().getTime();
debug('worker %s created', this.pid);
}

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@
, "express": "*"
, "superagent": "*"
}
, "scripts": {
"test": "./node_modules/mocha/bin/mocha test/*.test.js"
}
}
File renamed without changes.
18 changes: 18 additions & 0 deletions test/up.js → test/up.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,5 +212,23 @@ describe('up', function () {
testAssumeReady(done, false);
});

it('should respawn a worker when it dies', function (done) {
var httpServer = http.Server().listen()
, opts = { numWorkers: 1, keepAlive: true, minExpectedLifetime: '50' }
, srv = up(httpServer, __dirname + '/server', opts)
, orgPid = null;
srv.once('spawn', function () {
expect(srv.workers).to.have.length(1);
orgPid = srv.workers[0].pid
setTimeout(function () {
process.kill(orgPid, 'SIGKILL');
setTimeout(function () {
expect(srv.workers).to.have.length(1);
expect(srv.workers[0].pid).to.not.equal(orgPid);
done();
}, 300) // give it time to die and respawn
}, 75) // greater than minExpectedLifetime
});
});

});

0 comments on commit dc4e57e

Please sign in to comment.