├── .gitignore ├── .npmignore ├── .travis.yml ├── History.md ├── Makefile ├── Readme.md ├── lib ├── index.js ├── master.js └── worker.js ├── package.json └── test ├── close.js ├── reload.js ├── server.js ├── test.js ├── timeout.js └── worker.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | script: make test 3 | node_js: 4 | - '0.10' -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.0.0 / 2014-08-14 3 | ================== 4 | 5 | * refactor: updating api, adding tests, simplifying architecture 6 | 7 | 0.3.1 / 2014-02-24 8 | ================== 9 | 10 | * republish 11 | 12 | 0.3.0 / 2014-01-06 13 | ================== 14 | 15 | * add SIGQUIT graceful shutdown support 16 | 17 | 0.2.0 / 2013-12-11 18 | ================== 19 | 20 | * Removing annoying initial healthcheck, simplifying code. 21 | 22 | 23 | 0.1.0 / 2013-09-27 24 | ================== 25 | 26 | * Allow herd to run directly from the function instead of `herd.run` 27 | 28 | 0.0.1 / 2010-01-03 29 | ================== 30 | 31 | * Initial release 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS= $(shell ls ./test) 2 | 3 | test: ${TESTS} 4 | 5 | ${TESTS}: 6 | @node ./test/$@ 7 | 8 | .PHONY: test -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # herd 2 | 3 | Herd your child processes! A simple wrapper over node cluster for zero-downtime reloads. 4 | 5 | ## Quickstart 6 | 7 | Just pass herd the function to run on the child-processes. 8 | 9 | ```javascript 10 | var http = require('http'); 11 | var herd = require('herd'); 12 | 13 | herd('server') 14 | .run(function (){ 15 | http.createServer.listen(8000); 16 | }); 17 | ``` 18 | 19 | From the terminal, send a `SIGHUP` signal to gracefully reload the process: 20 | 21 | ```shell 22 | $ kill -1 1922 23 | ``` 24 | 25 | We usually keep the master process really small, and never restart it. By default the process will exit after 1 second, but close handlers and custom timeouts can be specified as well. 26 | 27 | ## API 28 | 29 | #### Herd(name) 30 | 31 | Create a new herd with title `name` 32 | 33 | #### .timeout(ms) 34 | 35 | Set the timeout before killing a worker in ms. (defaults to 1 second if you don't specify a close handler, otherwise none). 36 | 37 | #### .server(isServer) 38 | 39 | By default, new processes will not be created until a `listening` event is emitted by the child process (which is the default for _servers_). Call this flag if you'd rather wait for `online` event, in the case of your workers which might not ever call `net.Server.listen`. 40 | 41 | #### .size(workers) 42 | 43 | Set the number of workers to spawn. Defaults to the number of cpus. 44 | 45 | #### .close(fn) 46 | 47 | Calls the `fn` as the worker is as part of the exit handler. Which can be used to perform some type of cleanup before exiting. If a timeout is also specified, the process will exit whenever the first condition is triggered. 48 | 49 | ```js 50 | 51 | /** 52 | * Flush our remaining messages to disk. 53 | */ 54 | 55 | function close(fn){ 56 | flush(msgs, fn); 57 | } 58 | 59 | 60 | herd() 61 | .close(close) 62 | .run(run); 63 | ``` 64 | 65 | ## License 66 | 67 | (The MIT License) 68 | 69 | Copyright (c) 2012 Segment.io <friends@segment.io> 70 | 71 | Permission is hereby granted, free of charge, to any person obtaining 72 | a copy of this software and associated documentation files (the 73 | 'Software'), to deal in the Software without restriction, including 74 | without limitation the rights to use, copy, modify, merge, publish, 75 | distribute, sublicense, and/or sell copies of the Software, and to 76 | permit persons to whom the Software is furnished to do so, subject to 77 | the following conditions: 78 | 79 | The above copyright notice and this permission notice shall be 80 | included in all copies or substantial portions of the Software. 81 | 82 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 83 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 84 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 85 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 86 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 87 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 88 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 89 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 2 | var Emitter = require('events').EventEmitter; 3 | var inherits = require('util').inherits; 4 | var cpus = require('os').cpus().length; 5 | var fwd = require('forward-events'); 6 | var cluster = require('cluster'); 7 | var Master = require('./master'); 8 | var Worker = require('./worker'); 9 | 10 | /** 11 | * Module `exports`. 12 | */ 13 | 14 | module.exports = Herd; 15 | 16 | /** 17 | * Create a new herd running `fn`. 18 | * 19 | */ 20 | 21 | function Herd(name){ 22 | if (!(this instanceof Herd)) return new Herd(name); 23 | this.size(cpus); 24 | this.server(true); 25 | process.title = cluster.isMaster ? name : name + '-worker'; 26 | } 27 | 28 | /** 29 | * Inherit from Emitter. 30 | */ 31 | 32 | inherits(Herd, Emitter); 33 | 34 | /** 35 | * Get/Set the `num` of child processes to spawn. 36 | * 37 | * @param {Number} num 38 | * @return {Number} num 39 | */ 40 | 41 | Herd.prototype.size = function(num){ 42 | if (!arguments.length) return this._size; 43 | this._size = num; 44 | return this; 45 | }; 46 | 47 | /** 48 | * Run the herd, sets up a master or worker depending on where 49 | * we are running. 50 | * 51 | */ 52 | 53 | Herd.prototype.run = function(fn){ 54 | var herd = cluster.isMaster 55 | ? new Master(this.size(), this.server()) 56 | : new Worker(fn, this.close(), this.timeout()); 57 | 58 | herd.run(); 59 | fwd(herd, this); 60 | return this; 61 | }; 62 | 63 | /** 64 | * Get/Set the graceful reload handler. 65 | * 66 | * @param {Function} fn 67 | * @return {Function} fn 68 | */ 69 | 70 | Herd.prototype.close = function(fn){ 71 | if (!arguments.length) return this._close; 72 | this._close = fn; 73 | return this; 74 | }; 75 | 76 | /** 77 | * Get/Set the `timeout` to wait before killing a process. 78 | * 79 | * @param {Number} timeout 80 | * @return {Number} timeout 81 | */ 82 | 83 | Herd.prototype.timeout = function(timeout){ 84 | if (!arguments.length) return this._timeout; 85 | this._timeout = timeout; 86 | return this; 87 | }; 88 | 89 | /** 90 | * Mark the herd as a herd of workers rather than servers. 91 | */ 92 | 93 | Herd.prototype.worker = function(){ 94 | this.server(false); 95 | return this; 96 | }; 97 | 98 | /** 99 | * Gets/sets whether the workers are `net` servers. 100 | * 101 | * @param {Boolean} isServer 102 | * @return {Boolean} 103 | */ 104 | 105 | Herd.prototype.server = function(isServer){ 106 | if (!arguments.length) return this._server; 107 | this._server = isServer; 108 | return this; 109 | }; -------------------------------------------------------------------------------- /lib/master.js: -------------------------------------------------------------------------------- 1 | 2 | var debug = require('debug')('herd:master:' + process.pid); 3 | var Emitter = require('events').EventEmitter; 4 | var inherits = require('util').inherits; 5 | var cluster = require('cluster'); 6 | 7 | /** 8 | * Module `exports`. 9 | */ 10 | 11 | module.exports = Master; 12 | 13 | /** 14 | * Create a new Master of the given `size`, and decide whether to 15 | * wait for `listening` or `online` events of the child processes 16 | * if `isServer` is true. 17 | * 18 | * @param {Number} size 19 | * @param {Boolean} isServer 20 | */ 21 | 22 | function Master(size, isServer){ 23 | this.size = size; 24 | this.server = isServer; 25 | 26 | /** 27 | * Set up reloading for crashed processes 28 | */ 29 | 30 | cluster.on('exit', function(worker, code, signal){ 31 | self.emit('worker:close', worker, code, signal); 32 | var pid = worker.process.pid; 33 | debug('worker %d exited with code: %d', pid, code); 34 | if (code !== 0) cluster.fork(); 35 | }); 36 | 37 | var self = this; 38 | cluster.on(this.signal(), function(worker){ 39 | self.emit('worker:ready', worker); 40 | }); 41 | 42 | /** 43 | * Set up handlers for graceful reloads 44 | */ 45 | 46 | process 47 | .on('SIGHUP', this.reload.bind(this)) 48 | .on('SIGTERM', this.close.bind(this)) 49 | .on('SIGINT', this.close.bind(this)) 50 | .on('SIGQUIT', this.close.bind(this)); 51 | } 52 | 53 | /** 54 | * Inherit from `Emitter`. 55 | */ 56 | 57 | inherits(Master, Emitter); 58 | 59 | /** 60 | * Fork the process for the first time 61 | */ 62 | 63 | Master.prototype.run = function(){ 64 | var signal = this.signal(); 65 | var waiting = this.size; 66 | var self = this; 67 | 68 | cluster.on(signal, ready); 69 | 70 | function ready(){ 71 | if (--waiting > 0) return; 72 | self.emit('ready'); 73 | cluster.removeListener(signal, ready); 74 | }; 75 | 76 | for (var i = 0; i < this.size; i++) cluster.fork(); 77 | }; 78 | 79 | /** 80 | * Reload all the workers 81 | */ 82 | 83 | Master.prototype.reload = function(){ 84 | if (this.closing) return debug('shutdown already in progress'); 85 | if (this.reloading) return debug('reload already in progress'); 86 | 87 | this.reloading = true; 88 | 89 | debug('reloading the workers'); 90 | var reloaded = 0; 91 | var size = this.size; 92 | var workers = currentWorkers(); 93 | 94 | var signal = this.signal(); 95 | cluster.on(signal, reload); 96 | for (var i = 0; i < size; i++) cluster.fork(); 97 | 98 | var self = this; 99 | function reload(worker){ 100 | if (++reloaded < size) return; 101 | self.reloading = false; 102 | debug('finished reloading, killing workers'); 103 | workers.forEach(kill); 104 | cluster.removeListener(signal, reload); 105 | } 106 | }; 107 | 108 | Master.prototype.signal = function(){ 109 | return this.server ? 'listening' : 'online'; 110 | } 111 | 112 | /** 113 | * Shut down the master gracefully. 114 | */ 115 | 116 | Master.prototype.close = function(){ 117 | if (this.closing) return debug('shutdown already in progress'); 118 | 119 | debug('shutting down!'); 120 | var alive = this.size; 121 | this.closing = true; 122 | 123 | cluster.on('exit', function(){ 124 | alive--; 125 | debug('%d workers remaining', alive); 126 | if (!alive) process.exit(0); 127 | }); 128 | 129 | currentWorkers().forEach(kill); 130 | }; 131 | 132 | /** 133 | * Returns an array of the current workers. 134 | * 135 | * @return {Array[Worker]} workers 136 | */ 137 | 138 | function currentWorkers(){ 139 | var ret = []; 140 | for (var id in cluster.workers) ret.push(cluster.workers[id]); 141 | return ret; 142 | } 143 | 144 | /** 145 | * Attempts to kill a worker, catches the exception in case 146 | * the worker is already dead. 147 | * 148 | * @param {Worker} worker 149 | */ 150 | 151 | function kill(worker){ 152 | debug('sending `close` message: %d', worker.id); 153 | try { 154 | worker.send('close'); 155 | } catch(err) { 156 | debug('worker %d is already dead', worker.id); 157 | } 158 | } -------------------------------------------------------------------------------- /lib/worker.js: -------------------------------------------------------------------------------- 1 | 2 | var debug = require('debug')('herd:worker:' + process.pid); 3 | var Emitter = require('events').EventEmitter; 4 | var inherits = require('util').inherits; 5 | 6 | /** 7 | * Noop. 8 | */ 9 | 10 | function noop(){} 11 | 12 | /** 13 | * Module `exports`. 14 | */ 15 | 16 | module.exports = Worker; 17 | 18 | /** 19 | * Create a worker to execute `fn`, exit gracefully with `exit` 20 | * or terminate after `timeout`. 21 | * 22 | * @param {Function} fn 23 | * @param {Function} exit 24 | * @param {Number} timeout 25 | */ 26 | 27 | function Worker(fn, exit, timeout){ 28 | // Add the default reload handler 29 | if (!exit && !timeout) exit = reload; 30 | if (!exit) exit = noop; 31 | this.fn = fn; 32 | this.exit = exit; 33 | this.timeout = timeout; 34 | process.on('message', this.handle.bind(this)); 35 | } 36 | 37 | /** 38 | * Inherit from `Emitter`. 39 | */ 40 | 41 | inherits(Worker, Emitter); 42 | 43 | /** 44 | * Handle the `msg` from the master process. 45 | * 46 | */ 47 | 48 | Worker.prototype.handle = function(msg){ 49 | debug('received message: %s', msg); 50 | if (msg === 'close') return this.close(); 51 | }; 52 | 53 | /** 54 | * Runs the worker. 55 | * 56 | */ 57 | 58 | Worker.prototype.run = function(){ 59 | debug('running'); 60 | this.fn(); 61 | this.emit('ready'); 62 | } 63 | 64 | /** 65 | * Gracefully shutdown the worker using the exit function 66 | * and timeout. 67 | * 68 | */ 69 | 70 | Worker.prototype.close = function(){ 71 | if (this.closing) return; 72 | 73 | this.closing = true; 74 | 75 | if (this.timeout) setTimeout(exit, this.timeout); 76 | var isAsync = this.exit.length > 0; 77 | if (isAsync) return this.exit(exit); 78 | this.exit(); 79 | exit(); 80 | } 81 | 82 | /** 83 | * Default gradeful reload handler, reloads after 1 second. 84 | * 85 | * @param {Function} done 86 | */ 87 | 88 | function reload(done){ 89 | setTimeout(done, 1000); 90 | } 91 | 92 | /** 93 | * Exits the process with code 0. 94 | * 95 | */ 96 | 97 | function exit(){ 98 | process.exit(0); 99 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "herd", 3 | "version": "1.0.0", 4 | "description": "A simple wrapper over node cluster for zero-downtime reloads", 5 | "repository": "https://github.com/segmentio/herd.git", 6 | "keywords": [ 7 | "herd", 8 | "cluster", 9 | "child_process", 10 | "fork", 11 | "downtime", 12 | "reload" 13 | ], 14 | "author": "Segment.io ", 15 | "dependencies": { 16 | "async": "~0.2.9", 17 | "debug": "~0.7.2", 18 | "forward-events": "0.0.1", 19 | "ms": "~0.6.1" 20 | }, 21 | "main": "lib/index", 22 | "devDependencies": { 23 | "ps-tree": "0.0.3", 24 | "superagent": "^0.18.2" 25 | } 26 | } -------------------------------------------------------------------------------- /test/close.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert'); 3 | var test = require('./test'); 4 | var Herd = require('../'); 5 | 6 | /** 7 | * Create the herd. 8 | */ 9 | 10 | var herd = Herd('close') 11 | .size(4) 12 | .close(function(fn){ setTimeout(fn, 10); }) 13 | .server(false) 14 | .run(test.run); 15 | 16 | /** 17 | * Run the tests. 18 | */ 19 | 20 | function boot(){ 21 | if (!test.master) return; 22 | 23 | console.log(' it should reload after closing...'); 24 | 25 | var workers = test.workers(); 26 | assert(workers.length === herd.size(), 'workers not fully spawned'); 27 | test.reload(); 28 | 29 | var reloaded = 0; 30 | herd.on('worker:close', function(){ 31 | if (++reloaded < herd.size()) return; 32 | test.online().forEach(function(worker){ 33 | var isNew = workers.indexOf(worker) == -1; 34 | assert(isNew, worker.state + ' worker found: ' + worker.id); 35 | }); 36 | test.quit(); 37 | }); 38 | 39 | process.on('exit', function(code){ 40 | console.log(' ' + (code === 0 ? 'passed' : 'failed')); 41 | }); 42 | } 43 | 44 | /** 45 | * Boot. 46 | */ 47 | 48 | herd.on('ready', boot); 49 | -------------------------------------------------------------------------------- /test/reload.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert'); 3 | var test = require('./test'); 4 | var Herd = require('../'); 5 | 6 | /** 7 | * Create the herd. 8 | */ 9 | 10 | var herd = Herd('reload') 11 | .size(4) 12 | .server(false) 13 | .run(run); 14 | 15 | /** 16 | * Die after a random interval. 17 | */ 18 | 19 | function run(){ 20 | var ttl = 400; 21 | setTimeout(function(){ 22 | process.exit(1); 23 | }, ttl); 24 | } 25 | 26 | /** 27 | * Verify that enough workers are being properly reloaded. 28 | */ 29 | 30 | function check(){ 31 | var online = test.online().length; 32 | var states = test.states(); 33 | var size = herd.size(); 34 | assert(online >= 1, 'not enough workers online: ' + states); 35 | assert(online <= size * 2, 'too many workers are spawned: ' + states); 36 | } 37 | 38 | /** 39 | * Run the tests. 40 | */ 41 | 42 | function boot(){ 43 | if (!test.master) return; 44 | 45 | console.log(' it should reload errored processes...'); 46 | 47 | herd.on('worker:ready', function(){ 48 | check(); 49 | test.quit(); 50 | }); 51 | 52 | process.on('exit', function(code){ 53 | console.log(' ' + (code === 0 ? 'passed' : 'failed')); 54 | }); 55 | } 56 | 57 | /** 58 | * Boot. 59 | */ 60 | 61 | herd.on('ready', boot); 62 | -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | 2 | var request = require('superagent'); 3 | var assert = require('assert'); 4 | var test = require('./test'); 5 | var http = require('http'); 6 | var Herd = require('../'); 7 | 8 | /** 9 | * Create the herd. 10 | */ 11 | 12 | var herd = Herd('server') 13 | .size(4) 14 | .timeout(200) 15 | .run(run); 16 | 17 | /** 18 | * Run each worker and create a server to listen with. 19 | */ 20 | 21 | function run(){ 22 | var server = http.createServer(function(req, res){ 23 | res.writeHead(200); 24 | res.end(); 25 | }); 26 | server.listen(8034); 27 | } 28 | 29 | /** 30 | * Verify that enough workers are being properly reloaded. 31 | */ 32 | 33 | function check(fn){ 34 | var alive = test.listening().length; 35 | var size = herd.size(); 36 | assert(alive >= size, 'not enough workers online: ' + alive); 37 | assert(alive <= size * 2, 'too many workers are spawned: ' + alive); 38 | request 39 | .get('localhost:8034') 40 | .end(function(err, res){ 41 | if (err) throw err; 42 | assert(res.ok); 43 | fn(); 44 | }); 45 | } 46 | 47 | /** 48 | * Run the tests. 49 | */ 50 | 51 | function boot(){ 52 | if (!test.master) return; 53 | 54 | console.log(' it should reload server processes...'); 55 | test.reload(); 56 | 57 | herd.on('worker:ready', function(){ 58 | check(test.quit); 59 | }); 60 | 61 | process.on('exit', function(code){ 62 | console.log(' ' + (!code ? 'passed' : 'failed')); 63 | }); 64 | } 65 | 66 | /** 67 | * Boot. 68 | */ 69 | 70 | herd.on('ready', boot); 71 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var cluster = require('cluster'); 2 | 3 | /** 4 | * Return whether we are running in the master. 5 | */ 6 | 7 | exports.master = cluster.isMaster; 8 | 9 | /** 10 | * Export `workers`. 11 | */ 12 | 13 | exports.workers = workers; 14 | 15 | /** 16 | * Export `state` filter. 17 | */ 18 | 19 | exports.state = state; 20 | 21 | /** 22 | * Returns the array of existing workers. 23 | * 24 | * @return {Array[Worker]} workers 25 | */ 26 | 27 | function workers(){ 28 | var workers = []; 29 | for (var id in cluster.workers){ 30 | workers.push(cluster.workers[id]); 31 | } 32 | return workers; 33 | }; 34 | 35 | /** 36 | * Returns all workers given the current `state`. 37 | * 38 | * @return {Array[Worker]} 39 | */ 40 | 41 | function state(state){ 42 | return workers().filter(function(worker){ return worker.state === state; }); 43 | } 44 | 45 | /** 46 | * Return a list of the current worker states. 47 | * 48 | * @return {Array[String]} 49 | */ 50 | 51 | exports.states = function(){ 52 | return workers().map(function(worker){ return worker.state; }); 53 | }; 54 | 55 | /** 56 | * Returns all 'online' workers. 57 | * 58 | * @return {Array[Worker]} 59 | */ 60 | 61 | exports.online = function(){ 62 | return state('online'); 63 | }; 64 | 65 | /** 66 | * Returns all 'listening' workers. 67 | * 68 | * @return {Array[Worker]} 69 | */ 70 | 71 | exports.listening = function(){ 72 | return state('listening'); 73 | }; 74 | 75 | /** 76 | * Return the dead workers. 77 | * 78 | * @return {Array[Worker]} 79 | */ 80 | 81 | exports.dead = function(){ 82 | return workers().filter(function(worker){ 83 | return worker.state !== 'online' && worker.state !== 'listening'; 84 | }); 85 | }; 86 | 87 | /** 88 | * Dummy runner function for our workers. 89 | */ 90 | 91 | exports.run = function(){ 92 | setInterval(function(){}, 1000); 93 | }; 94 | 95 | /** 96 | * Tell the master process to reload. 97 | */ 98 | 99 | exports.reload = function(){ 100 | process.kill(process.pid, 'SIGHUP'); 101 | } 102 | 103 | /** 104 | * Tell the master process to exit. 105 | */ 106 | 107 | exports.quit = function(){ 108 | process.kill(process.pid, 'SIGQUIT'); 109 | } -------------------------------------------------------------------------------- /test/timeout.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert'); 3 | var test = require('./test'); 4 | var Herd = require('../'); 5 | 6 | /** 7 | * Create the herd. 8 | */ 9 | 10 | var herd = Herd('timeout') 11 | .size(4) 12 | .server(false) 13 | .timeout(200) 14 | .run(test.run); 15 | 16 | /** 17 | * Run the tests. 18 | */ 19 | 20 | function boot(){ 21 | if (!test.master) return; 22 | 23 | console.log(' it should reload after a timeout...'); 24 | 25 | var workers = test.workers(); 26 | assert(workers.length === herd.size(), 'workers not fully spawned'); 27 | test.reload(); 28 | 29 | var reloaded = 0; 30 | herd.on('worker:close', function(){ 31 | if (++reloaded < herd.size()) return; 32 | test.online().forEach(function(worker){ 33 | var isNew = workers.indexOf(worker) == -1; 34 | assert(isNew, worker.state + ' worker found: ' + worker.id); 35 | }); 36 | test.quit(); 37 | }); 38 | 39 | process.on('exit', function(code){ 40 | console.log(' ' + (code === 0 ? 'passed' : 'failed')); 41 | }); 42 | } 43 | 44 | /** 45 | * Boot. 46 | */ 47 | 48 | herd.on('ready', boot); 49 | -------------------------------------------------------------------------------- /test/worker.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert'); 3 | var test = require('./test'); 4 | var Herd = require('../'); 5 | 6 | /** 7 | * Create the herd. 8 | */ 9 | 10 | var herd = Herd('worker') 11 | .size(4) 12 | .server(false) 13 | .timeout(200) 14 | .run(test.run); 15 | 16 | /** 17 | * Verify that enough workers are being properly reloaded. 18 | */ 19 | 20 | function check(){ 21 | var online = test.online().length; 22 | var size = herd.size(); 23 | assert(online >= size, 'not enough workers online: ' + online); 24 | assert(online <= size * 2, 'too many workers are spawned: ' + online); 25 | } 26 | 27 | /** 28 | * Run the tests. 29 | */ 30 | 31 | function boot(){ 32 | if (!test.master) return; 33 | 34 | console.log(' it should reload workers...'); 35 | check(); 36 | 37 | var reloaded = 0; 38 | var workers = test.online(); 39 | herd.on('worker:close', function(){ 40 | if (++reloaded < herd.size()) return; 41 | test.online().forEach(function(worker){ 42 | var isNew = workers.indexOf(worker) == -1; 43 | assert(isNew, worker.state + ' worker found: ' + worker.id); 44 | }); 45 | test.quit(); 46 | }); 47 | 48 | test.reload(); 49 | 50 | process.on('exit', function(code){ 51 | console.log(' ' + (code === 0 ? 'passed' : 'failed')); 52 | }); 53 | } 54 | 55 | /** 56 | * Boot. 57 | */ 58 | 59 | herd.on('ready', boot); 60 | --------------------------------------------------------------------------------