├── .gitignore ├── .travis.yml ├── AUTHORS ├── LICENSE ├── Makefile ├── README.md ├── demo ├── graceful_exit │ ├── app.js │ ├── dispatch.js │ └── worker.js ├── master.js └── worker │ ├── daemon.js │ ├── exception.js │ ├── http.js │ └── serial.js ├── index.js ├── lib ├── child.js ├── common.js ├── master.js ├── os.js └── worker.js ├── package.json └── test ├── child.test.js ├── common.test.js ├── master.test.js ├── mock.js ├── os.test.js └── worker.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage* 3 | *.pid 4 | *.log 5 | *.swp 6 | .DS_Store 7 | .idea/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4.0 4 | - 4.2 5 | - 5.0 6 | - 5.2 7 | 8 | script: make coveralls 9 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Total 7 contributors. 2 | # Ordered by date of first contribution. 3 | # Auto-generated (https://github.com/fengmk2/node-authors) on Tue Aug 14 2012 03:30:18 GMT+0800 (CST). 4 | 5 | aleafs (https://github.com/aleafs) 6 | Jackson Tian (https://github.com/JacksonTian) 7 | fengmk2 (https://github.com/fengmk2) 8 | Will Wen Gunn (https://github.com/iwillwen) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012 aleafs and other pm contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = test/*.test.js 2 | REPORTER = spec 3 | TIMEOUT = 5000 4 | MOCHA_OPTS = 5 | 6 | install: 7 | @npm install 8 | 9 | test: install 10 | @NODE_ENV=test ./node_modules/mocha/bin/mocha \ 11 | --reporter $(REPORTER) \ 12 | --timeout $(TIMEOUT) \ 13 | $(MOCHA_OPTS) \ 14 | $(TESTS) 15 | 16 | cov: install 17 | @NODE_ENV=test ./node_modules/.bin/istanbul cover \ 18 | ./node_modules/mocha/bin/_mocha -- -R spec 19 | 20 | coveralls: install 21 | @NODE_ENV=test ./node_modules/.bin/istanbul cover \ 22 | ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && \ 23 | cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js --verbose 24 | 25 | .PHONY: test 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](http://img.shields.io/npm/v/pm.svg)](https://github.com/aleafs/pm) 2 | [![Build Status](https://secure.travis-ci.org/aleafs/pm.png?branch=master)](http://travis-ci.org/aleafs/pm) 3 | [![Coverage Status](https://coveralls.io/repos/aleafs/pm/badge.svg?branch=master&service=github)](https://coveralls.io/r/aleafs/pm?branch=master) 4 | [![npm download](https://img.shields.io/npm/dm/pm.svg)](https://npmjs.org/package/pm) 5 | 6 | ## About 7 | 8 | `pm` 是一个轻量级的Node.js多进程管理器,基于之前的`node-cluster`重构而来,在淘宝内部的生产系统中得到了广泛的应用. 9 | 10 | * 基于 master + worker 模式,master负责进程管理,worker 处理业务逻辑,有效利用现代服务器的多CPU; 11 | * 同一 master 可管理多种类型的worker, 并且支持在不同类型的 worker 之间进行轻量的消息传递; 12 | * 同一类型的 worker ,对于TCP请求,采用抢占式的方式进行负载均衡; 13 | * 平滑退出和 不退出前提下的 worker 进程重载 (reload). 14 | 15 | ## Api 16 | 17 | * Visit the [wiki page](https://github.com/aleafs/pm/wiki) to get more information about `pm`. 18 | * Also, we supply demo scripts in the code directory [demo](demo). 19 | 20 | ## Install 21 | 22 | ```bash 23 | $ npm install pm 24 | ``` 25 | 26 | ## Benchmark 27 | 28 | ```bash 29 | 30 | $ siege -b -c100 -t 60S http://172.0.0.2:33749/ 31 | ``` 32 | 33 | * QPS (only one child, http server, response req.url) (node0.6.17): 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
CASEClosedKeepAlive
pm2.0560010553
pm1.0523110388
node548110126
49 | 50 | ## Usage 51 | 52 | * in `master.js`, run as master: 53 | 54 | ```javascript 55 | var app = require('pm').createMaster({ 56 | 'pidfile' : '/tmp/demo.pid', 57 | }); 58 | 59 | app.register('group1', __dirname + '/http.js', { 60 | 'listen' : [8080, 8081], 61 | 'addr': '127.0.0.1' // it uses '0.0.0.0' by default 62 | }); 63 | 64 | app.on('giveup', function (name, num, pause) { 65 | // YOU SHOULD ALERT HERE! 66 | }); 67 | app.dispatch(); 68 | 69 | ``` 70 | 71 | * in `http.js`, run as worker: 72 | 73 | ```javascript 74 | var http = require('http').createServer(function (req, res) { 75 | res.end('hello world'); 76 | }); 77 | 78 | require('pm').createWorker().ready(function (socket, port) { 79 | http.emit('connection', socket); 80 | }); 81 | ``` 82 | 83 | ## Contributors 84 | 85 | Thanks goes to the people who have contributed code to this module, see the [GitHub Contributors page](https://github.com/aleafs/pm/graphs/contributors). 86 | 87 | Below is the output from `git-summary` 88 | 89 | ``` 90 | project: pm 91 | commits: 91 92 | files : 27 93 | authors: 94 | 86 aleafs 94.5% 95 | 4 wanglang 4.4% 96 | 1 fengmk2 1.1% 97 | 98 | ``` 99 | 100 | ## License 101 | 102 | `pm` is published under MIT license. 103 | See license text in [LICENSE](https://github.com/aleafs/pm/blob/master/LICENSE) file. 104 | 105 | -------------------------------------------------------------------------------- /demo/graceful_exit/app.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * pm - demo/graceful_exit/app.js 3 | * Copyright(c) 2013 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var http = require('http'); 14 | 15 | var server = http.createServer(function (req, res) { 16 | if (req.url === '/asyncerror') { 17 | setTimeout(function () { 18 | asyncError(); 19 | }, 10); 20 | return; 21 | } 22 | res.end(JSON.stringify({ 23 | url: req.url, 24 | pid: process.pid, 25 | })); 26 | }); 27 | 28 | module.exports = server; 29 | -------------------------------------------------------------------------------- /demo/graceful_exit/dispatch.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * pm - demo/graceful_exit/dispatch.js 3 | * Copyright(c) 2013 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var pm = require('../../'); 14 | 15 | var master = pm.createMaster(); 16 | 17 | master.on('giveup', function (name, fatals, pause) { 18 | console.log('[%s] [master:%s] giveup to restart "%s" process after %d times. pm will try after %d ms.', 19 | new Date(), process.pid, name, fatals, pause); 20 | }); 21 | 22 | master.on('disconnect', function (name, pid) { 23 | // console.log('%s %s disconnect', name, pid) 24 | var w = master.fork(name); 25 | console.error('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', 26 | new Date(), process.pid, pid, w.process.pid); 27 | }); 28 | 29 | master.on('fork', function (name, pid) { 30 | console.log('[%s] [master:%s] new %s:worker:%s fork', 31 | new Date(), process.pid, name, pid); 32 | }); 33 | 34 | master.on('quit', function (name, pid, code, signal) { 35 | console.log('[%s] [master:%s] %s:worker:%s quit, code: %s, signal: %s', 36 | new Date(), process.pid, name, pid, code, signal); 37 | }); 38 | 39 | master.register('web', __dirname + '/worker.js', { 40 | listen: 1984, 41 | children: 2 42 | }); 43 | 44 | master.dispatch(); 45 | -------------------------------------------------------------------------------- /demo/graceful_exit/worker.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * pm - demo/graceful_exit/worker.js 3 | * Copyright(c) 2013 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var graceful = require('graceful'); 14 | var worker = require('../../').createWorker(); 15 | var server = require('./app'); 16 | 17 | // hack for pm, because server._handle is empty. 18 | server.close = function () {}; 19 | 20 | graceful({ 21 | server: server, 22 | worker: worker, 23 | error: function (err) { 24 | console.log('[%s] [worker:%s] error: %s', new Date(), process.pid, err.stack); 25 | }, 26 | killTimeout: 10000, 27 | }); 28 | 29 | worker.ready(function (socket, port) { 30 | server.emit('connection', socket); 31 | }); 32 | -------------------------------------------------------------------------------- /demo/master.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | var app = require(__dirname + '/../').createMaster({ 4 | 'pidfile' : __dirname + '/bench.pid', 5 | 'statusfile' : __dirname + '/status.log', 6 | }); 7 | 8 | app.on('giveup', function (name, fatals, pause) { 9 | console.log('Master giveup to restart "%s" process after %d times. pm will try after %d ms.', name, fatals, pause); 10 | }); 11 | 12 | app.on('disconnect', function (worker, pid) { 13 | // var w = cluster.fork(); 14 | console.error('[%s] [master:%s] wroker:%s disconnect! new worker:%s fork', 15 | new Date(), process.pid, worker.process.pid); //, w.process.pid); 16 | }); 17 | 18 | app.on('fork', function () { 19 | console.log('fork', arguments); 20 | }); 21 | 22 | app.on('quit', function () { 23 | console.log('quit', arguments); 24 | }); 25 | 26 | /** 27 | * @ test fork error protect 28 | */ 29 | app.register('error', __dirname + '/worker/exception.js', { 30 | 'children' : -1 31 | }); 32 | 33 | /** 34 | * daemon process, log analysist or something else 35 | */ 36 | app.register('daemon', __dirname + '/worker/daemon.js', { 37 | 'trace_gc': true, 38 | 'children': 2 39 | }); 40 | 41 | /** 42 | * A http service 43 | */ 44 | app.register('http', __dirname + '/worker/http.js', { 45 | 'listen' : [ 33749, __dirname + '/http.socket', 33750 ], 46 | 'children' : 1 47 | }); 48 | 49 | /** 50 | * process serial start 51 | */ 52 | app.register('serial', __dirname + '/worker/serial.js', { 53 | 'children': 2, 54 | 'use_serial_mode': true 55 | }); 56 | 57 | app.dispatch(); 58 | -------------------------------------------------------------------------------- /demo/worker/daemon.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | var worker = require(__dirname + '/../../').createWorker(); 4 | worker.on('message', function (msg, from, pid) { 5 | console.log('[%d]: Got message "%s" from %s by %d', process.pid, msg, from, pid); 6 | switch (msg.toString()) { 7 | case 'fatal': 8 | process.exit(127); 9 | break; 10 | 11 | default: 12 | break; 13 | } 14 | }); 15 | 16 | worker.ready(); 17 | -------------------------------------------------------------------------------- /demo/worker/exception.js: -------------------------------------------------------------------------------- 1 | setTimeout(function () { 2 | throw new Error('test'); 3 | }, 1000); 4 | -------------------------------------------------------------------------------- /demo/worker/http.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | var worker = require(__dirname + '/../../').createWorker(); 4 | 5 | var s1 = require('http').createServer(function (req, res) { 6 | res.end(req.url); 7 | }); 8 | 9 | var s2 = require('http').createServer(function (req, res) { 10 | 11 | var url = req.url.split('?').shift().split('/').filter(function (x) { 12 | return x.length; 13 | }); 14 | var act = url.shift().toLowerCase(); 15 | 16 | switch (act) { 17 | case 'cleancache': 18 | worker.broadcast('daemon', 'Please clean your cache', url.shift()); 19 | break; 20 | 21 | case 'fatal': 22 | worker.broadcast('daemon', 'fatal'); 23 | break; 24 | 25 | case 'disconnect': 26 | console.log('[worker:%s] disconnected', process.pid); 27 | worker.disconnect(); 28 | setTimeout(function () { 29 | process.exit(1); 30 | }, 3000); 31 | break; 32 | 33 | /* 34 | case 'reload': 35 | worker.reload('daemon'); 36 | break; 37 | */ 38 | 39 | default: 40 | break; 41 | } 42 | res.end(JSON.stringify({ 43 | 'act' : url, 44 | 'url' : req.url, 45 | pid: process.pid, 46 | })); 47 | }); 48 | 49 | worker.ready(function(socket, which) { 50 | if (33749 === which) { 51 | s1.emit('connection', socket); 52 | } else { 53 | s2.emit('connection', socket); 54 | } 55 | }); 56 | 57 | worker.on('suicide', function (by) { 58 | console.log('suicide by ' + by); 59 | }); 60 | 61 | -------------------------------------------------------------------------------- /demo/worker/serial.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | var worker = require(__dirname + '/../../').createWorker().serialStart(function (done) { 4 | console.log('child start!'); 5 | done(); 6 | }, 100).ready(); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | var master = require('./lib/master.js'); 4 | var worker = require('./lib/worker.js'); 5 | 6 | /** 7 | * Create a `Master`. 8 | * @param {Object} options 9 | * - {String} [pidfile] 10 | * - {String} [statusfile] 11 | * @return {Master} the master instance. 12 | * @api public 13 | */ 14 | exports.createMaster = function (options) { 15 | return master.create(options); 16 | }; 17 | 18 | /** 19 | * Create a `Worker`. 20 | * @param {Object} options 21 | * - {Number} [heartbeat_interval], heartbeat interval ms, default is `2000` ms. 22 | * - {Number} [terminate_timeout], terminate timeout ms, default is `1000` ms. 23 | * @return {Worker} this worker instance. 24 | * @api public 25 | */ 26 | exports.createWorker = function (options) { 27 | return worker.create(options); 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /lib/child.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | "use strict"; 4 | 5 | var util = require('util'); 6 | var child_process = require('child_process'); 7 | var CPUS = require(__dirname + '/os.js').cpusnum(); 8 | 9 | var Handle = require(__dirname + '/common.js').getHandle; 10 | var Emitter = require('events').EventEmitter; 11 | 12 | /** 13 | * @ 重新定义全局变量,便于单元测试mock 14 | */ 15 | var PROCESS = process; 16 | 17 | /* {{{ private function _extend() */ 18 | var _extend = function (a, b) { 19 | a = a || {}; 20 | for (var i in b) { 21 | a[i] = b[i]; 22 | } 23 | return a; 24 | }; 25 | /* }}} */ 26 | 27 | var MessageHandle = {}; 28 | MessageHandle.heartbeat = function (owner, pid, data) { 29 | owner.pstatus[pid] = _extend(_extend(owner.pstatus[pid], data), { 30 | '_time' : Date.now() 31 | }); 32 | }; 33 | 34 | MessageHandle.broadcast = function (owner, pid, data) { 35 | if (!data || !data.msg) { 36 | return; 37 | } 38 | owner.emit('broadcast', data.who, data.msg, pid, data.pid); 39 | }; 40 | 41 | MessageHandle.disconnect = function (owner, pid, data) { 42 | owner.emit('disconnect', pid, data); 43 | }; 44 | 45 | exports.create = function (argv, options) { 46 | 47 | /* {{{ _options */ 48 | var _options = _extend({ 49 | 'listen' : [], 50 | 'children' : CPUS, 51 | 'max_fatal_restart' : 5, 52 | 'pause_after_fatal' : 60000, 53 | 'max_heartbeat_lost' : -1, 54 | 'use_serial_mode' : false 55 | }, options); 56 | 57 | if (!_options.children) { 58 | _options.children = CPUS; 59 | } 60 | if (!_options.listen) { 61 | _options.listen = []; 62 | } else if (!Array.isArray(_options.listen)) { 63 | _options.listen = _options.listen.toString().split(',').filter(function (a) { return a; }); 64 | } 65 | _options.listen = _options.listen.map(function (i) { 66 | return /^\d+$/.test(i) ? Number(i) : i; 67 | }); 68 | /* }}} */ 69 | 70 | var Child = function () { 71 | Emitter.call(this); 72 | 73 | /** 74 | * @ 运行状态 75 | */ 76 | this.running = 1; 77 | 78 | /** 79 | * @ 进程状态表 80 | */ 81 | this.pstatus = {}; 82 | 83 | /** 84 | * @ 正常进程数 85 | */ 86 | this.okcount = 0; 87 | 88 | /** 89 | * @ 即将消亡的进程列表 90 | */ 91 | this.dielist = {}; 92 | 93 | /** 94 | * @ 异常退出 95 | */ 96 | this.pfatals = []; 97 | 98 | /** 99 | * @ 串行启动模式下的获得令牌的进程id及分配过的令牌次数 100 | */ 101 | if (_options.use_serial_mode) { 102 | this.tokenPid = -1; 103 | this.tokenCount = 0; 104 | } 105 | 106 | /** 107 | * 总共 fork 过的进程数 108 | * @type {Number} 109 | */ 110 | this._forkCount = 0; 111 | 112 | }; 113 | util.inherits(Child, Emitter); 114 | 115 | /** 116 | * @ 监听句柄 117 | */ 118 | var handles = {}; 119 | var getHandle = function (i, addr) { 120 | if (!handles[i]) { 121 | handles[i] = Handle(i, addr); 122 | } 123 | return handles[i]; 124 | }; 125 | 126 | /** 127 | * @ childress list 128 | */ 129 | var workers = {}; 130 | 131 | var command = argv.join(' '); 132 | var exepath = argv.shift(); 133 | 134 | Child.prototype.fork = function() { 135 | var sub = this._fork(); 136 | return { 137 | process: sub 138 | }; 139 | }; 140 | 141 | /* {{{ private prototype _fork() */ 142 | Child.prototype._fork = function () { 143 | var _self = this; 144 | var sub = child_process.fork(exepath, argv, { 145 | 'cwd' : PROCESS.cwd(), 146 | 'env' : _extend({ 147 | PM_WORKER_INDEX: _self._forkCount++ 148 | }, PROCESS.env) 149 | }); 150 | 151 | var pid = sub.pid; 152 | workers[pid] = sub; 153 | _self.pstatus[pid] = { 154 | 'uptime' : Date.now(), 155 | }; 156 | _self.emit('fork', pid); 157 | 158 | sub.on('exit', function (code, signal) { 159 | delete workers[pid]; 160 | delete _self.pstatus[pid]; 161 | delete _self.dielist[pid]; 162 | 163 | _self.okcount = Math.max(0, _self.okcount - 1); 164 | _self.emit('exit', pid, code, signal); 165 | 166 | if (!_self.running) { 167 | return; 168 | } 169 | 170 | if (code || 'SIGKILL' === signal) { 171 | var t = Date.now(); 172 | var n = _self.pfatals.unshift(t); 173 | var p = _options.pause_after_fatal; 174 | if (n > _options.max_fatal_restart) { 175 | _self.pfatals = _self.pfatals.slice(0, _options.max_fatal_restart); 176 | } 177 | 178 | n = _self.pfatals.length; 179 | if (n >= _options.max_fatal_restart && _self.pfatals[n - 1] + ~~(0.9 * p) >= t) { 180 | _self.emit('giveup', n, p); 181 | setTimeout(function () { 182 | _self.start(); 183 | }, p); 184 | return; 185 | } 186 | } 187 | 188 | // 串行启动模式下发送令牌后,子进程异常退出的情况 189 | if (_options.use_serial_mode && _self.tokenPid === pid) { 190 | _self.tokenPid = -1; 191 | } 192 | 193 | _self.start(); 194 | }); 195 | 196 | sub.on('message', function (msg) { 197 | //并行启动的令牌发放及回收操作 198 | if (_options.use_serial_mode) { 199 | if (msg.cmd === 'token_get') { 200 | try{ 201 | if (_self.tokenPid === -1) { 202 | sub.send({ token : ++_self.tokenCount }); 203 | _self.tokenPid = msg.pid; 204 | } else { 205 | sub.send({ token : -1 }); 206 | } 207 | } catch (e) { 208 | } 209 | } else if (msg.cmd === 'token_release') { 210 | _self.tokenPid = -1; 211 | } 212 | } 213 | 214 | if (!msg || !msg.type) { 215 | return; 216 | } 217 | 218 | if ('ready' === msg.type) { 219 | _options.listen.forEach(function (i) { 220 | if (!i) { 221 | return; 222 | } 223 | try { 224 | sub.send({'type' : 'listen', 'data' : i}, getHandle(i, _options['addr'])); 225 | } catch (e) { 226 | } 227 | }); 228 | _self.okcount++; 229 | if (_self.okcount >= _options.children / 2) { 230 | for (var i in _self.dielist) { 231 | if (workers[i]) { 232 | workers[i].kill('SIGTERM'); 233 | } 234 | } 235 | } 236 | } else if ('function' === (typeof MessageHandle[msg.type])) { 237 | (MessageHandle[msg.type])(_self, pid, msg.data); 238 | } 239 | }); 240 | 241 | return sub; 242 | }; 243 | /* }}} */ 244 | 245 | /* {{{ public prototype start() */ 246 | Child.prototype.start = function () { 247 | var n = 0; 248 | 249 | var _self = this; 250 | Object.keys(workers).forEach(function (i) { 251 | if (!_self.dielist[i]) { 252 | n++; 253 | } 254 | }); 255 | _self.okcount = n; 256 | 257 | while (n < _options.children) { 258 | _self._fork(); 259 | n++; 260 | } 261 | }; 262 | /* }}} */ 263 | 264 | /* {{{ public prototype stop() */ 265 | Child.prototype.stop = function (signal) { 266 | var _self = this; 267 | _self.running = 0; 268 | _self.okcount = 0; 269 | 270 | Object.keys(handles).forEach(function (i) { 271 | handles[i].close(); 272 | delete handles[i]; 273 | }); 274 | 275 | Object.keys(workers).forEach(function (i) { 276 | workers[i].kill(signal || 'SIGTERM'); 277 | }); 278 | 279 | Object.keys(_self.dielist).forEach(function (i) { 280 | if (workers[i]) { 281 | workers[i].kill(signal || 'SIGTERM'); 282 | } 283 | }); 284 | }; 285 | /* }}} */ 286 | 287 | /* {{{ public prototype reload() */ 288 | Child.prototype.reload = function () { 289 | var _self = this; 290 | _self.okcount = 0; 291 | Object.keys(workers).forEach(function (i) { 292 | _self.dielist[i] = true; 293 | }); 294 | _self.start(); 295 | }; 296 | /* }}} */ 297 | 298 | /* {{{ public prototype broadcast() */ 299 | Child.prototype.broadcast = function (msg, from, pid, tid) { 300 | Object.keys(workers).forEach(function (i) { 301 | if (tid && tid != i) { 302 | return; 303 | } 304 | try { 305 | workers[i].send({ 306 | 'type' : 'hello', 307 | 'data' : msg, 308 | 'from' : from, 309 | '_pid' : pid 310 | }); 311 | } catch (e) { 312 | } 313 | }); 314 | }; 315 | /* }}} */ 316 | 317 | return new Child(); 318 | 319 | }; 320 | 321 | if (process.env.NODE_ENV === 'test') { 322 | exports.mock = function (p) { 323 | PROCESS = p; 324 | }; 325 | } 326 | -------------------------------------------------------------------------------- /lib/common.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | "use strict"; 4 | 5 | var net = require('net'); 6 | var TCP = process.binding('tcp_wrap').TCP; 7 | var Pipe = process.binding('pipe_wrap').Pipe; 8 | 9 | exports.getHandle = function (idx, addr) { 10 | var h = null; 11 | var r = 0; 12 | 13 | if (!addr) { 14 | addr = '0.0.0.0'; 15 | } 16 | 17 | idx = Number(idx) || idx; 18 | if ('number' === (typeof idx)) { 19 | h = new TCP(); 20 | r = h.bind(addr, idx); 21 | } else { 22 | h = new Pipe(); 23 | r = h.bind(idx); 24 | } 25 | 26 | if (0 !== r) { 27 | h.close(); 28 | h = null; 29 | } 30 | 31 | return h; 32 | }; 33 | 34 | exports.listen2 = function (handle, connect, backlog) { 35 | handle.onconnection = function () { 36 | var c = null; 37 | for (var i in arguments) { 38 | if (arguments[i].fd) { 39 | c = arguments[i]; 40 | break; 41 | } 42 | } 43 | var s = new net.Socket({'handle' : c}); 44 | s.readable = true; 45 | s.writable = true; 46 | s.resume(); 47 | s.emit('connect'); 48 | connect(s); 49 | }; 50 | handle.listen(backlog || 1023); 51 | return handle; 52 | }; 53 | 54 | -------------------------------------------------------------------------------- /lib/master.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | "use strict"; 4 | 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var util = require('util'); 8 | var Emitter = require('events').EventEmitter; 9 | 10 | var PROCESS = process; 11 | var child = require(__dirname + '/child.js'); 12 | 13 | /* {{{ private function _normalize() */ 14 | var _normalize = function (name) { 15 | return name.toString().trim().toLowerCase(); 16 | }; 17 | /* }}} */ 18 | 19 | /* {{{ private function mkdir() */ 20 | var mkdir = function (dir, mode) { 21 | if ((fs.existsSync || path.existsSync)(dir)) { 22 | return; 23 | } 24 | 25 | var p = path.dirname(dir); 26 | if (p && p != dir) { 27 | mkdir(p, mode); 28 | } 29 | fs.mkdirSync(dir, mode || 493/**< 0755*/); 30 | }; 31 | /* }}} */ 32 | 33 | /* {{{ private function _writePidFile() */ 34 | var _writePidFile = function (fn) { 35 | mkdir(path.dirname(fn)); 36 | fs.writeFileSync(fn, PROCESS.pid); 37 | PROCESS.on('exit', function () { 38 | try { 39 | var pid = fs.readFileSync(fn, 'utf8'); 40 | if (Number(pid) === PROCESS.pid) { 41 | fs.unlinkSync(fn); 42 | } 43 | } catch (e) { 44 | } 45 | }); 46 | }; 47 | /* }}} */ 48 | 49 | exports.create = function (options) { 50 | 51 | var _options = { 52 | 'terminate_timeout' : 1000, 53 | 'statusflush_interval' : 1000, 54 | 'addr': '0.0.0.0' 55 | }; 56 | for (var i in options) { 57 | _options[i] = options[i]; 58 | } 59 | 60 | /** 61 | * @ worker对象列表 62 | */ 63 | var _workers = {}; 64 | 65 | if (_options.pidfile) { 66 | _writePidFile(_options.pidfile); 67 | } 68 | 69 | if (_options.statusfile) { 70 | mkdir(path.dirname(_options.statusfile)); 71 | (function statusfile() { 72 | var _stream = fs.createWriteStream(_options.statusfile, { 73 | 'flags': 'a+', 'encoding': 'utf8', 'mode': 420 // 0644 74 | }); 75 | Object.keys(_workers).forEach(function (i) { 76 | Object.keys(_workers[i].pstatus).forEach(function (p) { 77 | _stream.write(util.format('%d:\t%s\t%s\t%j\n', 78 | process.pid, i, p, _workers[i].pstatus[p])); 79 | }); 80 | }); 81 | _stream.end(); 82 | setTimeout(statusfile, _options.statusflush_interval); 83 | })(); 84 | } 85 | 86 | var Master = function () { 87 | Emitter.call(this); 88 | 89 | var _self = this; 90 | 91 | PROCESS.on('SIGHUB', function () { 92 | _self.emit('signal', 1, 'SIGHUB'); 93 | }); 94 | PROCESS.on('exit', function () { 95 | _self.shutdown('SIGKILL'); 96 | }); 97 | 98 | PROCESS.on('SIGTERM', function (signal, code) { 99 | _self.emit('signal', code || 15, signal || 'SIGTERM'); 100 | _self.shutdown('SIGTERM'); 101 | setTimeout(function () { 102 | PROCESS.exit(0); 103 | }, _options.terminate_timeout); 104 | }); 105 | 106 | PROCESS.on('SIGINT', function () { 107 | PROCESS.emit('SIGTERM', 'SIGINT', 2); 108 | }); 109 | 110 | PROCESS.on('SIGUSR1', function () { 111 | _self.emit('signal', 30, 'SIGUSR1'); 112 | _self.reload(); 113 | }); 114 | }; 115 | util.inherits(Master, Emitter); 116 | 117 | /* {{{ public prototype register() */ 118 | Master.prototype.register = function (name, file, options, argv) { 119 | name = _normalize(name); 120 | if (_workers[name]) { 121 | _workers[name].removeAllListeners(); 122 | _workers[name].stop('SIGKILL'); 123 | } 124 | 125 | argv = Array.isArray(argv) ? argv : []; 126 | argv.unshift(file); 127 | 128 | if (options && options.trace_gc) { 129 | argv.unshift('--trace_gc'); 130 | delete options.trace_gc; 131 | } 132 | 133 | var _self = this; 134 | var _pair = child.create(argv, options); 135 | _pair.on('fork', function (pid) { 136 | _self.emit('fork', name, pid); 137 | }); 138 | _pair.on('exit', function (pid, code, signal) { 139 | _self.emit('quit', name, pid, code, signal); 140 | }); 141 | _pair.on('giveup', function (n, p) { 142 | _self.emit('giveup', name, n, p); 143 | }); 144 | _pair.on('broadcast', function (to, msg, pid, xxx) { 145 | to = _normalize(to); 146 | if (_workers[to]) { 147 | _workers[to].broadcast(msg, name, pid, xxx); 148 | } 149 | }); 150 | _pair.on('disconnect', function (pid, worker) { 151 | _self.emit('disconnect', name, pid, worker); 152 | }); 153 | _workers[name] = _pair; 154 | 155 | return _pair; 156 | }; 157 | /* }}} */ 158 | 159 | Master.prototype.dispatch = function () { 160 | Object.keys(_workers).forEach(function (i) { 161 | _workers[i].start(); 162 | }); 163 | }; 164 | 165 | Master.prototype.reload = function () { 166 | Object.keys(_workers).forEach(function (i) { 167 | _workers[i].reload(); 168 | }); 169 | }; 170 | 171 | Master.prototype.shutdown = function (signal) { 172 | Object.keys(_workers).forEach(function (i) { 173 | _workers[i].stop(signal || 'SIGTERM'); 174 | }); 175 | }; 176 | 177 | Master.prototype.fork = function (name) { 178 | var _pair = _workers[name]; 179 | return _pair.fork(); 180 | }; 181 | 182 | return new Master(); 183 | }; 184 | 185 | if (process.env.NODE_ENV === 'test') { 186 | exports.mock = function (p) { 187 | PROCESS = p; 188 | }; 189 | } 190 | -------------------------------------------------------------------------------- /lib/os.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | "use strict"; 4 | 5 | /** 6 | * XXX: 7 | * 8 | * node (>=0.8) crash on linux platform when cpu number are not continuous 9 | * 10 | * cpu 1075827 37 353258 631990752 220302 11 60263 22716756 0 11 | * cpu0 504931 33 161329 100616782 159668 9 23452 7626460 0 12 | * cpu1 503828 3 173795 107652876 20966 0 26767 607430 0 13 | * cpu8 31654 0 7789 98402179 26194 1 8495 11865758 0 14 | * cpu9 16218 0 4293 107873312 3666 0 1041 1339345 0 15 | * cpu10 7757 0 3439 108714234 3819 0 311 731368 0 16 | * cpu11 11436 0 2611 108731366 5987 0 194 546393 0 17 | */ 18 | 19 | var os = require('os'); 20 | var fs = require('fs'); 21 | 22 | os.cpusnum = function () { 23 | if ('linux' !== os.platform()) { 24 | return os.cpus().length; 25 | } 26 | 27 | var num = 0; 28 | try { 29 | fs.readFileSync('/proc/stat', 'utf8').split('\n').forEach(function (row) { 30 | var w = row.split(' '); 31 | if (w.shift().match(/^cpu\d+$/) && w.length > 4) { 32 | num++; 33 | } 34 | }); 35 | } catch (e) { 36 | num = os.cpus().length; 37 | } 38 | 39 | return num; 40 | }; 41 | 42 | module.exports = os; 43 | 44 | -------------------------------------------------------------------------------- /lib/worker.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | "use strict"; 4 | 5 | var net = require('net'); 6 | var util = require('util'); 7 | var Emitter = require('events').EventEmitter; 8 | var listen2 = require(__dirname + '/common.js').listen2; 9 | 10 | exports.create = function (options, PROCESS) { 11 | 12 | /** 13 | * @ 配置参数 14 | */ 15 | var _options = { 16 | 'heartbeat_interval' : 2000, 17 | 'terminate_timeout' : 1000, 18 | }; 19 | for (var i in options) { 20 | _options[i] = ~~Number(options[i]); 21 | } 22 | 23 | /** 24 | * @ 方便单元测试 25 | */ 26 | PROCESS = PROCESS || process; 27 | 28 | /** 29 | * @ send message to master 30 | */ 31 | var askmaster = function () {}; 32 | if ('function' === (typeof PROCESS.send)) { 33 | askmaster = function (type, data) { 34 | PROCESS.send({'type' : type, 'data' : data}); 35 | }; 36 | } 37 | 38 | /** 39 | * @ 连接处理 40 | */ 41 | var onconnect = function (socket, which) { 42 | socket.end(); 43 | }; 44 | 45 | /** 46 | * @ 监听句柄 47 | */ 48 | var _listener = {}; 49 | 50 | /** 51 | * @ 记分板 52 | */ 53 | var _scores = {}; 54 | 55 | /** 56 | * @ 心跳函数 57 | */ 58 | var heartbeat = function () { 59 | askmaster('heartbeat', { 60 | 'hbterm' : _options.heartbeat_interval, 61 | 'scores' : _scores, 62 | 'memory' : PROCESS.memoryUsage(), 63 | }); 64 | }; 65 | PROCESS.send && setInterval(heartbeat, _options.heartbeat_interval); 66 | 67 | /** 68 | * Close all open lsitening handles. 69 | */ 70 | var closeListeners = function () { 71 | Object.keys(_listener).forEach(function (i) { 72 | try { 73 | _listener[i].close(); 74 | delete _listener[i]; 75 | } catch (e) { 76 | } 77 | }); 78 | }; 79 | 80 | /* {{{ private function suicide() */ 81 | var suicide = function () { 82 | closeListeners(); 83 | setTimeout(function () { 84 | PROCESS.exit(0); 85 | }, _options.terminate_timeout); 86 | }; 87 | /* }}} */ 88 | 89 | /* {{{ public Worker constructor */ 90 | var Worker = function () { 91 | Emitter.call(this); 92 | 93 | var _self = this; 94 | PROCESS.on('message', function (msg, handle) { 95 | if (!msg || !msg.type) { 96 | return; 97 | } 98 | 99 | if (handle && 'listen' === msg.type) { 100 | var idx = msg.data; // port or sock 101 | if (_listener[idx]) { 102 | _listener[idx].close(); 103 | delete _listener[idx]; 104 | } 105 | _listener[idx] = listen2(handle, function (socket) { 106 | onconnect(socket, idx); 107 | }); 108 | process.nextTick(function () { 109 | _self.emit('listen', idx); 110 | }); 111 | return; 112 | } 113 | 114 | switch (msg.type) { 115 | case 'suicide': 116 | suicide(); 117 | _self.emit('suicide', 'message'); 118 | break; 119 | 120 | case 'hello': 121 | _self.emit('message', msg.data, msg.from, msg._pid); 122 | break; 123 | 124 | default: 125 | break; 126 | } 127 | }); 128 | }; 129 | util.inherits(Worker, Emitter); 130 | /* }}} */ 131 | 132 | /* {{{ public prototype ready() */ 133 | Worker.prototype.ready = function (callback) { 134 | /** 135 | * @ 找master要监听handle 136 | */ 137 | askmaster('ready'); 138 | if ('function' === (typeof callback)) { 139 | onconnect = callback; 140 | } 141 | }; 142 | /* }}} */ 143 | 144 | /* {{{ public prototype broadcast() */ 145 | /** 146 | * Broadcast message around workers 147 | */ 148 | Worker.prototype.broadcast = function (who, msg, pid) { 149 | askmaster('broadcast', { 150 | 'who' : who, 151 | 'pid' : pid || 0, 152 | 'msg' : msg, 153 | }); 154 | }; 155 | /* }}} */ 156 | 157 | Worker.prototype.disconnect = function () { 158 | var worker = { 159 | suicide: true, 160 | process: { 161 | pid: PROCESS.pid, 162 | } 163 | }; 164 | askmaster('disconnect', worker); 165 | // close listeners, do not accept new connection. 166 | closeListeners(); 167 | }; 168 | 169 | /* {{{ public prototype serialStart() */ 170 | /** 171 | * serial start child Process 172 | */ 173 | Worker.prototype.serialStart = function (cb, serialModeGetTokenTimeout) { 174 | var timeout = serialModeGetTokenTimeout || 100; 175 | var ctimer; 176 | var childPid = PROCESS.pid; 177 | var _self = this; 178 | 179 | if (!cb || typeof cb !== 'function') { 180 | return; 181 | } 182 | 183 | PROCESS.on('message', function (tokenRes) { 184 | if (!tokenRes.token) { 185 | return; 186 | } 187 | 188 | if (tokenRes.token >= 0) { 189 | clearTimeout(ctimer); 190 | try{ 191 | cb.call(_self, releaseToken); 192 | } catch (e) { 193 | } 194 | } else { 195 | ctimer = setTimeout(function () { 196 | try{ 197 | PROCESS.send({ 198 | cmd : 'token_get', 199 | pid : childPid 200 | }); 201 | } catch (e) { 202 | } 203 | }, timeout); 204 | } 205 | }); 206 | try{ 207 | PROCESS.send({ 208 | cmd : 'token_get', 209 | pid : childPid 210 | }); 211 | } catch (e) { 212 | } 213 | return _self; 214 | }; 215 | /* }}} */ 216 | 217 | var releaseToken = function () { 218 | PROCESS.send({cmd : 'token_release'}); 219 | }; 220 | 221 | var _me = new Worker(); 222 | 223 | PROCESS.on('SIGHUB', function () {}); 224 | PROCESS.on('SIGTERM', function () { 225 | suicide(); 226 | _me.emit('suicide', 'SIGTERM'); 227 | }); 228 | PROCESS.once('exit', function () { 229 | _me.emit('exit'); 230 | }); 231 | 232 | return _me; 233 | }; 234 | 235 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pm", 3 | "version": "2.2.6", 4 | "author": "Aleafs Zhang (zhangxc83@gmail.com)", 5 | "license" : "MIT", 6 | "contributors": [ 7 | {"name": "fengmk2", "email": "fengmk2@gmail.com", "web": "http://fengmk2.github.com"} 8 | ], 9 | "homepage": "https://github.com/aleafs/pm", 10 | "repository": "git://github.com/aleafs/pm.git", 11 | "description": "Process Manager for Node.js, branched from node-cluster.", 12 | "keywords": [ "process manager", "cluster", "child-process", "master-worker" ], 13 | "dependencies": {}, 14 | "engines": { 15 | "node": ">=0.9" 16 | }, 17 | "devDependencies": { 18 | "graceful": ">=0.0.3", 19 | "should": ">=3.0.1", 20 | "mocha": "=1.21.*", 21 | "rewire": "*", 22 | "mm": "*", 23 | "istanbul": "*", 24 | "coveralls" : "*", 25 | "travis-cov": "*", 26 | "mocha-lcov-reporter": "*" 27 | }, 28 | "main" : "./index.js", 29 | "scripts": { 30 | "test": "make test" 31 | }, 32 | "config" : { 33 | "travis-cov": { 34 | "threshold": 98 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/child.test.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | "use strict"; 4 | 5 | var mm = require('mm'); 6 | var child_process = require('child_process'); 7 | var fs = require('fs'); 8 | var path = require('path'); 9 | fs.existsSync = fs.existsSync || path.existsSync; 10 | var should = require('should'); 11 | var common = require(__dirname + '/mock.js'); 12 | var Child = require('../lib/child.js'); 13 | 14 | describe('child manager', function () { 15 | 16 | var PROCESS; 17 | beforeEach(function () { 18 | 19 | common.resetAllStatic(); 20 | 21 | PROCESS = common.mockProcess(); 22 | PROCESS.makesureCleanAllMessage(); 23 | PROCESS.__getOutMessage().should.eql([]); 24 | PROCESS.__getEvents().should.eql([]); 25 | 26 | var sub = common.mockFork(); 27 | sub.makesureCleanAllMessage; 28 | Child.mock(PROCESS); 29 | mm(child_process, 'fork', common.mockFork); 30 | }); 31 | 32 | afterEach(function () { 33 | mm.restore(); 34 | }); 35 | 36 | /* {{{ should_public_method_works_fine() */ 37 | it('should_public_method_works_fine', function (_done) { 38 | var sock = __dirname + '/' + process.version + '.a.socket'; 39 | if (fs.existsSync(sock)) { 40 | fs.unlinkSync(sock); 41 | } 42 | var _me = Child.create(['filename.js', 'b'], { 43 | 'listen' : sock + ',,33047', 44 | 'children' : 3, 45 | }); 46 | 47 | var _messages = []; 48 | _me.on('fork', function (pid) { 49 | _messages.push(['fork', pid]); 50 | }); 51 | _me.on('broadcast', function (who, msg, by) { 52 | _messages.push(['broadcast', who, JSON.stringify(msg), by]); 53 | }); 54 | 55 | var done = function () { 56 | _messages.should.eql([ 57 | ['fork', 1], 58 | ['fork', 2], 59 | ['fork', 3], 60 | ['fork', 4], 61 | ['fork', 5], 62 | ['fork', 6], 63 | ['broadcast', 'FBX', '"fuck"', 1], 64 | ]); 65 | _done(); 66 | }; 67 | 68 | common.resetAllStatic(); 69 | 70 | var one = _me._fork(); 71 | one.__getArguments().should.eql([ 72 | 'filename.js', 73 | ['b'], 74 | { 75 | 'cwd' : __dirname, 76 | 'env' : { 77 | 'test-env' : 'lalal', 78 | PM_WORKER_INDEX: 0 79 | } 80 | } 81 | ]); 82 | 83 | _me.start(); 84 | _me.running.should.eql(1); 85 | Object.keys(_me.pstatus).should.eql(['1', '2', '3']); 86 | 87 | _me.reload(); 88 | _me.start(); 89 | Object.keys(_me.dielist).should.eql(['1', '2', '3']); 90 | Object.keys(_me.pstatus).should.eql(['1', '2', '3', '4', '5', '6']); 91 | 92 | _me.broadcast(['world'], 'Obama', -1); 93 | 94 | var expects = []; 95 | for (var i = 1; i < 7; i++) { 96 | expects.push(JSON.stringify([i, { 97 | 'type' : 'hello', 98 | 'data' : ['world'], 99 | 'from' : 'Obama', 100 | '_pid' : -1, 101 | }, null])); 102 | } 103 | one.__getGlobalMessage().should.eql(expects); 104 | 105 | one.emit('message'); 106 | one.emit('message', {'data' : 1}); 107 | 108 | /** 109 | * @ 心跳信息 110 | */ 111 | one.emit('message', {'type' : 'heartbeat', 'data' : {'aa' : 1}}); 112 | _me.pstatus['1'].should.have.property('aa', 1); 113 | _me.pstatus['1'].should.have.property('_time'); 114 | 115 | /** 116 | * @ 请求广播 117 | */ 118 | one.emit('message', {'type' : 'broadcast', 'data' : {'who' : 'FBX'}}); 119 | one.emit('message', {'type' : 'broadcast', 'data' : {'who' : 'FBX', 'msg' : 'fuck'}}); 120 | 121 | one.__getOutMessage().pop(); 122 | 123 | /** 124 | * @ 请求句柄 125 | */ 126 | one.emit('message', {'type' : 'ready'}); 127 | one.__getOutMessage().pop()[0].should.eql({'type' : 'listen', 'data' : 33047}); 128 | 129 | /** 130 | * @ 模拟两个进程ready,dielist应该至少杀掉2个 131 | */ 132 | one.emit('message', {'type' : 'ready'}); 133 | setTimeout(function () { 134 | Object.keys(_me.dielist).length.should.below(2); 135 | Object.keys(_me.pstatus).should.eql(['4', '5', '6']); 136 | _me.stop(); 137 | _me.running.should.eql(0); 138 | done(); 139 | }, 10); 140 | }); 141 | /* }}} */ 142 | 143 | /* {{{ should_killall_child_when_exit() */ 144 | it('should_killall_child_when_exit', function (done) { 145 | var _me = Child.create(['filename.js', 'b'], { 146 | 'children' : 3, 147 | }); 148 | 149 | common.resetAllStatic(); 150 | 151 | _me.start(); 152 | _me.reload(); 153 | Object.keys(_me.dielist).should.eql(['1','2','3']); 154 | 155 | var one = _me._fork(); 156 | one.emit('ready'); 157 | setTimeout(function () { 158 | Object.keys(_me.dielist).should.eql(['1','2','3']); 159 | _me.stop(); 160 | 161 | setTimeout(function () { 162 | Object.keys(_me.dielist).should.eql([]); 163 | done(); 164 | }, 10); 165 | }, 10); 166 | }); 167 | /* }}} */ 168 | 169 | /** 170 | * XXX: 这个case逻辑上还有点问题 171 | */ 172 | /* {{{ should_max_fatals_works_fine() */ 173 | it('should_max_fatals_works_fine', function (done) { 174 | 175 | common.resetAllStatic(); 176 | 177 | var _me = Child.create(['a'], { 178 | 'listen' : null, 'max_fatal_restart' : 2, 'pause_after_fatal' : 10, 179 | 'children' : 0 180 | }); 181 | 182 | var _messages = []; 183 | _me.on('giveup', function (n, p) { 184 | n.should.eql(2); 185 | p.should.eql(10); 186 | _messages.push(['giveup', n, p]); 187 | }); 188 | _me.on('fork', function (pid) { 189 | _messages.push(['fork', pid]); 190 | }); 191 | 192 | var one = _me._fork(); 193 | _messages.should.eql([['fork', 1]]); 194 | 195 | _me.start(); 196 | 197 | var cpu = require(__dirname + '/../lib/os.js').cpusnum(); 198 | Object.keys(_me.pstatus).should.have.property('length', cpu); 199 | 200 | /** 201 | * exit cases: 202 | * 0, sigterm : [-] fork 0 203 | * 0, sigkill : [!] fork 1 204 | * 1, sigterm : [!] fork 2 205 | * 1, sigkill : [!] pause 206 | */ 207 | for (var i = 0; i < 2; i++) { 208 | ['SIGTERM', 'SIGKILL'].forEach(function (s) { 209 | one.emit('exit', i, s); 210 | }); 211 | } 212 | 213 | var expects = []; 214 | for (var i = 0; i < cpu; i++) { 215 | expects.push(['fork', 1 + i]); 216 | } 217 | 218 | expects.push(['fork', i++]); 219 | 220 | setTimeout(function () { 221 | //_messages.should.eql(expects); 222 | done(); 223 | }, 50); 224 | }); 225 | /* }}} */ 226 | 227 | /* {{{ should_serial_mode_works_fine() */ 228 | it('should_serial_mode_works_fine', function (done) { 229 | var _me = Child.create(['filename.js', 'b'], { 230 | 'children' : 3, 231 | 'use_serial_mode': true 232 | }); 233 | 234 | common.resetAllStatic(); 235 | 236 | var one = _me._fork(); 237 | one.__getArguments().should.eql([ 238 | 'filename.js', 239 | ['b'], 240 | { 241 | 'cwd' : __dirname, 'env' : {'test-env' : 'lalal', PM_WORKER_INDEX: 0} 242 | } 243 | ]); 244 | 245 | _me.start(); 246 | _me.running.should.eql(1); 247 | Object.keys(_me.pstatus).should.eql(['1', '2', '3']); 248 | 249 | // 测试串行启动模式,主进程的两个初始值 250 | _me.tokenPid.should.eql(-1); 251 | _me.tokenCount.should.eql(0); 252 | 253 | one.emit('message', { 254 | cmd : 'token_get', 255 | pid : one.pid 256 | }); 257 | 258 | _me.tokenPid.should.eql(one.pid); 259 | _me.tokenCount.should.eql(1); 260 | 261 | one.emit('message', { 262 | cmd : 'token_get', 263 | pid : one.pid 264 | }); 265 | 266 | one.emit('message', { 267 | cmd : 'token_release', 268 | pid : one.pid 269 | }); 270 | 271 | _me.tokenPid.should.eql(-1); 272 | _me.tokenCount.should.eql(1); 273 | 274 | // 测试发送令牌后,子进程异常退出的情况 275 | one.emit('message', { 276 | cmd : 'token_get', 277 | pid : one.pid 278 | }); 279 | 280 | _me.tokenPid.should.eql(one.pid); 281 | _me.tokenCount.should.eql(2); 282 | 283 | one.emit('exit'); 284 | 285 | _me.tokenPid.should.eql(-1); 286 | 287 | done(); 288 | }); 289 | /* }}} */ 290 | 291 | }); 292 | 293 | -------------------------------------------------------------------------------- /test/common.test.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | "use strict"; 4 | 5 | var fs = require('fs'); 6 | var http = require('http'); 7 | var net = require('net'); 8 | var should = require('should'); 9 | var common = require('../lib/common.js'); 10 | 11 | var cleanSocketFiles = function (path, callback) { 12 | fs.readdir(path, function (err, res) { 13 | (res || []).forEach(function (fn) { 14 | if (String(fn).match(/\.socket$/)) { 15 | fs.unlinkSync(path + '/' + fn); 16 | } 17 | }); 18 | callback(err); 19 | }); 20 | }; 21 | 22 | beforeEach(function (done) { 23 | cleanSocketFiles(__dirname, done); 24 | }); 25 | 26 | afterEach(function (done) { 27 | cleanSocketFiles(__dirname, done); 28 | }); 29 | 30 | describe('common functions', function () { 31 | 32 | it('handle_close_after_bind_failed', function () { 33 | should.ok(!common.getHandle('/i/am/denied.socket')); 34 | }); 35 | 36 | var sock = __dirname + '/' + process.version + '.a.socket'; 37 | 38 | [33046, '33046', sock].forEach(function (idx) { 39 | it('listen at: ' + idx, function (done) { 40 | var _me = http.createServer(function (req, res) { 41 | res.end(req.url); 42 | }); 43 | 44 | var s = common.listen2(common.getHandle(idx), function (socket) { 45 | _me.emit('connection', socket); 46 | }); 47 | 48 | var options = { 49 | 'path' : '/' + idx, 50 | }; 51 | if ('number' === (typeof idx) || idx.match(/^\d+$/)) { 52 | options.host = 'localhost'; 53 | options.port = Number(idx); 54 | } else { 55 | options.socketPath = idx; 56 | } 57 | http.get(options, function (res) { 58 | s.close(); 59 | done(); 60 | }); 61 | }); 62 | }); 63 | 64 | }); 65 | 66 | -------------------------------------------------------------------------------- /test/master.test.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | "use strict"; 4 | 5 | var mm = require('mm'); 6 | var fs = require('fs'); 7 | var should = require('should'); 8 | var common = require(__dirname + '/mock.js'); 9 | var child = require('../lib/child.js'); 10 | var master = require('../lib/master.js'); 11 | 12 | var PROCESS; 13 | 14 | describe('master', function () { 15 | 16 | beforeEach(function (done) { 17 | common.resetAllStatic(); 18 | PROCESS = common.mockProcess(); 19 | PROCESS.makesureCleanAllMessage(); 20 | PROCESS.__getOutMessage().should.eql([]); 21 | PROCESS.__getEvents().should.eql([]); 22 | 23 | master.mock(PROCESS); 24 | mm(child, 'create', common.mockChild); 25 | 26 | var cmd = require('util').format('rm -rf "%s/tmp"', __dirname); 27 | require('child_process').exec(cmd, function (e) { 28 | should.ok(!e); 29 | done(); 30 | }); 31 | }); 32 | 33 | afterEach(function () { 34 | mm.restore(); 35 | }); 36 | 37 | var pidfile = __dirname + '/tmp/a.pid'; 38 | 39 | /* {{{ should_write_pidfile_works_fine() */ 40 | it('should_write_pidfile_works_fine', function (done) { 41 | var _me = master.create({ 42 | 'pidfile' : pidfile 43 | }); 44 | fs.readFile(pidfile, 'utf8', function (e, d) { 45 | should.ok(!e); 46 | d.should.eql('' + PROCESS.pid); 47 | 48 | fs.writeFileSync(pidfile, '-1'); 49 | PROCESS.emit('exit'); 50 | fs.readFile(pidfile, 'utf8', function (e, d) { 51 | should.ok(!e); 52 | fs.writeFileSync(pidfile, PROCESS.pid); 53 | PROCESS.emit('exit'); 54 | fs.readFile(pidfile, 'utf8', function (e, d) { 55 | e.message.should.containEql('ENOENT'); 56 | done(); 57 | }); 58 | }); 59 | }); 60 | }); 61 | /* }}} */ 62 | 63 | /* {{{ should_public_api_works_fine() */ 64 | it('should_public_api_works_fine', function (done) { 65 | var _me = master.create({'terminate_timeout' : 10}); 66 | 67 | var _messages = []; 68 | ['fork', 'quit', 'giveup', 'signal'].forEach(function (i) { 69 | _me.on(i, function () { 70 | _messages.push([i, Array.prototype.slice.call(arguments)]); 71 | }); 72 | }); 73 | 74 | var _p1 = _me.register('A1', 'a1.js'); 75 | var _p2 = _me.register('A1', 'a2.js', {'trace_gc' : true}); 76 | 77 | _p1.__getMessages().pop().should.eql(['stop', ['SIGKILL']]); 78 | 79 | /** 80 | * @ 实际上需要remove掉这个listener 81 | */ 82 | _p1.emit('fork', 1); 83 | _p2.emit('fork', 1); 84 | _p2.emit('exit', 1, 23, 'SIGTERM'); 85 | _p2.emit('giveup', 10, 600); 86 | 87 | _p2.emit('broadcast', 'A1', 'hello', 3); 88 | _p2.__getMessages().pop().should.eql(['broadcast', ['hello', 'a1', 3, undefined]]); 89 | 90 | _me.dispatch(); 91 | _p2.__getMessages().pop().should.eql(['start', []]); 92 | _me.shutdown(); 93 | _p2.__getMessages().pop().should.eql(['stop', ['SIGTERM']]); 94 | 95 | PROCESS.emit('SIGHUB'); 96 | PROCESS.emit('SIGUSR1'); 97 | _p2.__getMessages().pop().should.eql(['reload', []]); 98 | 99 | PROCESS.emit('SIGTERM'); 100 | _p2.__getMessages().pop().should.eql(['stop', ['SIGTERM']]); 101 | 102 | PROCESS.emit('SIGINT'); 103 | _p2.__getMessages().pop().should.eql(['stop', ['SIGTERM']]); 104 | 105 | setTimeout(function () { 106 | _messages.should.eql([ 107 | ['fork', ['a1',1]], 108 | ['quit', ['a1',1,23,'SIGTERM']], 109 | ['giveup', ['a1',10,600]], 110 | ['signal', [1, 'SIGHUB']], 111 | ['signal', [30,'SIGUSR1']], 112 | ['signal', [15,'SIGTERM']], 113 | ['signal', [2, 'SIGINT']], 114 | ]); 115 | done(); 116 | }, 20); 117 | }); 118 | /* }}} */ 119 | 120 | /* {{{ should_statusfile_works_fine() */ 121 | it('should_statusfile_works_fine', function (done) { 122 | var _fn = __dirname + '/tmp/status.log'; 123 | var _me = master.create({ 124 | 'statusfile' : _fn, 'statusflush_interval' : 20 125 | }); 126 | _me.register('group1', './group1.js'); 127 | _me.register('group2', './group1.js'); 128 | setTimeout(function () { 129 | fs.readFile(_fn, 'utf8', function (e,d) { 130 | should.ok(!e); 131 | var d = d.split('\n'); 132 | d.length.should.above(2); 133 | d.should.containEql(process.pid + ':\tgroup1\tpid\t{"k1":"aaa"}'); 134 | d.should.containEql(process.pid + ':\tgroup2\tpid\t{"k1":"aaa"}'); 135 | done(); 136 | }); 137 | }, 70); 138 | }); 139 | /* }}} */ 140 | 141 | }); 142 | -------------------------------------------------------------------------------- /test/mock.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | "use strict"; 4 | 5 | var util = require('util'); 6 | var path = require('path'); 7 | var Emitter = require('events').EventEmitter; 8 | 9 | var _PIDCOUNTER = 1; 10 | var globalMessage = []; 11 | 12 | exports.resetAllStatic = function () { 13 | _PIDCOUNTER = 1; 14 | globalMessage = []; 15 | }; 16 | 17 | exports.mockProcess = function () { 18 | 19 | /** 20 | * @ 发出的消息 21 | */ 22 | var msgsout = []; 23 | 24 | /** 25 | * @ 被触发的消息 26 | */ 27 | var _events = []; 28 | 29 | var Process = function () { 30 | Emitter.call(this); 31 | this.env = {'test-env' : 'lalal'}; 32 | this.pid = 0; 33 | }; 34 | util.inherits(Process, Emitter); 35 | 36 | Process.prototype.cwd = function () { 37 | return path.normalize(__dirname); 38 | }; 39 | 40 | Process.prototype.makesureCleanAllMessage = function () { 41 | msgsout = []; 42 | _events = []; 43 | }; 44 | 45 | Process.prototype.send = function (msg, handle) { 46 | msgsout.push([msg, handle]); 47 | }; 48 | 49 | Process.prototype.memoryUsage = function () { 50 | return {'rss' : 2, 'heapTotal' : 1, 'heapUsed' : 1}; 51 | }; 52 | 53 | Process.prototype.__getOutMessage = function () { 54 | return msgsout; 55 | }; 56 | 57 | Process.prototype.__getEvents = function () { 58 | return _events; 59 | }; 60 | 61 | Process.prototype.exit = function (code) { 62 | _events.push(['exit', code || 0]); 63 | this.emit('exit', code); 64 | }; 65 | 66 | Process.prototype.kill = function (pid, signal) { 67 | _events.push(['kill', pid, signal]); 68 | }; 69 | 70 | return new Process(); 71 | }; 72 | 73 | exports.mockFork = function () { 74 | 75 | var _arguments = arguments; 76 | 77 | var Sub = function () { 78 | Emitter.call(this); 79 | this.pid = _PIDCOUNTER++; 80 | }; 81 | util.inherits(Sub, Emitter); 82 | 83 | Sub.prototype.__getArguments = function () { 84 | return Array.prototype.slice.call(_arguments); 85 | }; 86 | 87 | /** 88 | * @ 发出的消息 89 | */ 90 | var msgsout = []; 91 | 92 | Sub.prototype.send = function (msg, handle) { 93 | msgsout.push([msg, handle]); 94 | globalMessage.push(JSON.stringify([this.pid, msg, handle])); 95 | }; 96 | 97 | Sub.prototype.kill = function (signal) { 98 | var _self = this; 99 | setTimeout(function () { 100 | _self.emit('exit', 0, signal); 101 | }, 1); 102 | }; 103 | 104 | Sub.prototype.__getOutMessage = function () { 105 | return msgsout; 106 | }; 107 | 108 | Sub.prototype.__getGlobalMessage = function () { 109 | return globalMessage; 110 | }; 111 | 112 | return new Sub(); 113 | }; 114 | 115 | exports.mockChild = function () { 116 | 117 | var Child = function () { 118 | Emitter.call(this); 119 | this.pstatus = { 120 | 'pid' : {'k1' : 'aaa'} 121 | }; 122 | }; 123 | util.inherits(Child, Emitter); 124 | 125 | /** 126 | * @ 收到的消息 127 | */ 128 | var _messages = []; 129 | 130 | ['start', 'stop', 'reload', 'broadcast'].forEach(function (i) { 131 | Child.prototype[i] = function () { 132 | _messages.push([i, Array.prototype.slice.call(arguments)]); 133 | }; 134 | }); 135 | 136 | Child.prototype.__getMessages = function () { 137 | return _messages; 138 | }; 139 | 140 | return new Child(); 141 | }; 142 | 143 | -------------------------------------------------------------------------------- /test/os.test.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | "use strict"; 4 | 5 | var fs = require('fs'); 6 | var mm = require('mm'); 7 | var should = require('should'); 8 | var os = require('../lib/os.js'); 9 | 10 | describe('os patch for linux', function () { 11 | 12 | afterEach(function () { 13 | mm.restore(); 14 | }); 15 | 16 | it('should_cpusnum_works_fine', function () { 17 | var _os = require('os'); 18 | mm(_os, 'platform', function () { return 'linux'; }); 19 | mm(_os, 'cpus', function () { return [0, 1]; }); 20 | mm(fs, 'readFileSync', function () { 21 | return ['cpu 1075827 37 353258 631990752 220302 11 60263 22716756 0', 22 | 'cpu0 504931 33 161329 100616782 159668 9 23452 7626460 0', 23 | 'cpu1 503828 3 173795 107652876 20966 0 26767 607430 0', 24 | 'cpu8 31654 0 7789 98402179 26194 1 8495 11865758 0', 25 | 'cpu9 16218 0 4293 107873312 3666 0 1041 1339345 0', 26 | 'cpu10 7757 0 3439 108714234 3819 0 311 731368 0', 27 | 'cpu11 11436 0 2611 108731366 5987 0 194 546393 0', 28 | 'intr ...' 29 | ].join('\n'); 30 | }); 31 | os.cpusnum().should.eql(6); 32 | 33 | mm(fs, 'readFileSync', function () { 34 | throw new Error('test error'); 35 | }); 36 | os.cpusnum().should.eql(2); 37 | 38 | mm(_os, 'platform', function () { return 'darwin'; }); 39 | os.cpusnum().should.eql(2); 40 | }); 41 | 42 | }); 43 | 44 | -------------------------------------------------------------------------------- /test/worker.test.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: */ 2 | 3 | "use strict"; 4 | 5 | var fs = require('fs'); 6 | var net = require('net'); 7 | var http = require('http'); 8 | var should = require('should'); 9 | var Common = require(__dirname + '/mock.js'); 10 | var worker = require('../lib/worker.js'); 11 | var _Handle = require('../lib/common.js').getHandle; 12 | 13 | var cleanSocketFiles = function (path, callback) { 14 | fs.readdir(path, function (err, res) { 15 | (res || []).forEach(function (fn) { 16 | if (String(fn).match(/\.socket$/)) { 17 | fs.unlinkSync(path + '/' + fn); 18 | } 19 | }); 20 | callback(err); 21 | }); 22 | }; 23 | 24 | var PROCESS; 25 | beforeEach(function (done) { 26 | 27 | Common.resetAllStatic(); 28 | PROCESS = Common.mockProcess(); 29 | PROCESS.makesureCleanAllMessage(); 30 | PROCESS.__getOutMessage().should.eql([]); 31 | PROCESS.__getEvents().should.eql([]); 32 | 33 | cleanSocketFiles(__dirname, done); 34 | }); 35 | 36 | afterEach(function (done) { 37 | cleanSocketFiles(__dirname, done); 38 | }); 39 | 40 | describe('worker process', function () { 41 | 42 | /* {{{ should_hearbeat_works_fine() */ 43 | it('should_hearbeat_works_fine', function (done) { 44 | 45 | var _me = worker.create({ 46 | 'heartbeat_interval' : 10, 47 | 'terminate_timeout' : 200, 48 | }, PROCESS); 49 | 50 | _me.broadcast('who', 'test msg'); 51 | PROCESS.__getOutMessage().pop().should.eql([{ 52 | 'type' : 'broadcast', 53 | 'data' : {'who' : 'who', 'pid' : 0, 'msg' : 'test msg'} 54 | }, undefined]); 55 | 56 | _me.ready(function (client, which) { 57 | client.end(); 58 | }); 59 | 60 | PROCESS.__getOutMessage().pop().should.eql([{ 61 | 'type' : 'ready', 62 | 'data' : undefined 63 | }, undefined]); 64 | 65 | setTimeout(function () { 66 | var msg = PROCESS.__getOutMessage(); 67 | for (var i = 0; i < msg.length; i++) { 68 | var s = msg[i].shift(); 69 | if ('heartbeat' === s.type) { 70 | JSON.stringify(s.data).should.eql(JSON.stringify({ 71 | 'hbterm' : 10, 72 | 'scores' : {}, 73 | 'memory' : {'rss': 2, 'heapTotal': 1, 'heapUsed': 1} 74 | })); 75 | done(); 76 | return; 77 | } 78 | } 79 | (true).should.eql(false); 80 | }, 15); 81 | }); 82 | /* }}} */ 83 | 84 | /* {{{ should_messages_works_fine() */ 85 | it('should_messages_works_fine', function (_done) { 86 | 87 | var _me = worker.create({ 88 | 'heartbeat_interval' : 1000, 89 | 'terminate_timeout' : 20, 90 | }, PROCESS); 91 | 92 | var msg = []; 93 | _me.on('message', function (txt, from, pid) { 94 | msg.push(JSON.stringify([txt, from, pid])); 95 | }); 96 | _me.on('suicide', function (from) { 97 | msg.push(JSON.stringify(['suicide', from])); 98 | }); 99 | _me.on('exit', function () { 100 | msg.push('exit'); 101 | }); 102 | _me.on('listen', function (which) { 103 | msg.push(JSON.stringify(['listen', which])); 104 | }); 105 | 106 | var done = function () { 107 | msg.should.containEql(JSON.stringify(['Fuck GFW', 'FBX', -1])); 108 | msg.should.containEql(JSON.stringify(['suicide', 'SIGTERM'])); 109 | msg.should.containEql(JSON.stringify(['suicide', 'message'])); 110 | msg.should.containEql(JSON.stringify(['listen', 'a'])); 111 | msg.should.containEql('exit'); 112 | _done(); 113 | }; 114 | 115 | PROCESS.emit('message'); 116 | PROCESS.emit('message', {'data' : 'blabla'}); 117 | PROCESS.emit('message', {'type' : 'undefined'}); 118 | PROCESS.emit('message', {'type' : 'hello', 'data' : 'Fuck GFW', 'from' : 'FBX', '_pid' : -1}); 119 | 120 | var _s1 = http.createServer(function (req, res) { 121 | res.writeHeader(200, {'x-lalla' : req.url}); 122 | res.end(req.url); 123 | }); 124 | 125 | PROCESS.emit('message', {'type' : 'listen'}); 126 | PROCESS.emit('message', {'type' : 'listen', 'data' : 'a'}, _Handle('33046')); 127 | 128 | /** 129 | * @ 默认处理,连接就断掉 130 | */ 131 | _me.ready(function (socket, which) { 132 | if ('a' === which) { 133 | _s1.emit('connection', socket); 134 | } 135 | }); 136 | 137 | var options = { 138 | 'host' : 'localhost', 'port' : 33046, 'path' : '/aabbce' 139 | }; 140 | 141 | _me.once('listen', function (which) { 142 | which.should.eql('a'); 143 | 144 | var n = 2; 145 | for (var i = 0; i < n; i++) { 146 | http.get(options, function (res) { 147 | res.headers.should.have.property('x-lalla', '/aabbce'); 148 | PROCESS.emit('message', {'type' : 'listen', 'data' : 'a'}, _Handle('33046')); 149 | if (0 === (--n)) { 150 | PROCESS.emit('message', {'type' : 'suicide'}); 151 | PROCESS.emit('SIGTERM'); 152 | setTimeout(done, 25); 153 | } 154 | }); 155 | } 156 | }); 157 | }); 158 | /* }}} */ 159 | 160 | /* {{{ should_default_onconnect_works_fine() */ 161 | it('should_default_onconnect_works_fine', function (done) { 162 | var _me = worker.create({ 163 | 'heartbeat_interval' : 1000, 164 | 'terminate_timeout' : 20, 165 | }, PROCESS); 166 | 167 | var _fn = __dirname + '/' + process.version + '.a.socket'; 168 | PROCESS.emit('message', {'type' : 'listen', 'data' : 'a'}, _Handle(_fn)); 169 | _me.ready(); 170 | 171 | var clt = net.connect(_fn, function () { 172 | }); 173 | clt.on('data', function (d) { 174 | (true).should.eql(false); 175 | }); 176 | clt.on('end', function () { 177 | done(); 178 | }); 179 | }); 180 | /* }}} */ 181 | 182 | /* {{{ should_serialStart_works_fine() */ 183 | it('should_serialStart_works_fine', function (_done) { 184 | 185 | var _me = worker.create({ 186 | 'heartbeat_interval' : 1000, 187 | 'terminate_timeout' : 20, 188 | }, PROCESS); 189 | 190 | var txt = ''; 191 | var GET_TOKEN_TIMEOUT = 100; 192 | 193 | _me.serialStart(txt, GET_TOKEN_TIMEOUT); 194 | 195 | _me.serialStart(function (done) { 196 | txt = 'child start!'; 197 | done(); 198 | }, GET_TOKEN_TIMEOUT); 199 | 200 | PROCESS.emit('message', {'token' : -1}); 201 | 202 | setTimeout(function () { 203 | txt.should.eql(''); 204 | 205 | PROCESS.emit('message', {'token' : 1}); 206 | 207 | setTimeout(function () { 208 | txt.should.eql('child start!'); 209 | 210 | PROCESS.emit('message', {'test' : ''}); 211 | _done(); 212 | }, 10); 213 | }, GET_TOKEN_TIMEOUT + 1); 214 | }); 215 | /* }}} */ 216 | 217 | describe('disconnect()', function () { 218 | it('should disconnect and close all listeners', function (done) { 219 | var w = worker.create({ 220 | heartbeat_interval: 1000, 221 | terminate_timeout: 20, 222 | }, PROCESS); 223 | 224 | var web = http.createServer(function (req, res) { 225 | if (req.url === '/disconnect') { 226 | w.disconnect(); 227 | } 228 | res.setHeader('x-url', req.url); 229 | res.end(req.url); 230 | }); 231 | 232 | PROCESS.emit('message', {'type' : 'listen', 'data' : 'a'}, _Handle('43119')); 233 | 234 | /** 235 | * @ 默认处理,连接就断掉 236 | */ 237 | w.ready(function (socket, which) { 238 | if ('a' === which) { 239 | web.emit('connection', socket); 240 | } 241 | }); 242 | 243 | var options = { 244 | 'host' : 'localhost', 'port' : 43119, 'path' : '/normal-request' 245 | }; 246 | 247 | http.get(options, function (res) { 248 | res.headers.should.have.property('x-url', '/normal-request'); 249 | }); 250 | 251 | w.once('listen', function (which) { 252 | http.get(options, function (res) { 253 | res.headers.should.have.property('x-url', '/normal-request'); 254 | }); 255 | 256 | options.path = '/disconnect'; 257 | http.get(options, function (res) { 258 | res.headers.should.have.property('x-url', '/disconnect'); 259 | setTimeout(function () { 260 | var msgs = PROCESS.__getOutMessage(); 261 | var disconnectMsg = msgs[1][0]; 262 | disconnectMsg.type.should.equal('disconnect'); 263 | disconnectMsg.data.suicide.should.equal(true); 264 | done(); 265 | }, 10); 266 | }); 267 | 268 | }); 269 | }); 270 | 271 | }); 272 | 273 | }); 274 | --------------------------------------------------------------------------------