├── .DS_Store ├── jsconfig.json ├── index.js ├── test ├── file.js ├── text.js ├── error.js └── index.js ├── package.json ├── .gitignore ├── README.md ├── output ├── child.js └── child_manager.js └── src ├── child.js └── child_manager.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/advance-child-pool/master/.DS_Store -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "outDir": "./output" 5 | }, 6 | "include": [ 7 | "./src/*" 8 | ] 9 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | let childManager = require('./output/child_manager'); 2 | let childWorker = require('./output/child'); 3 | 4 | exports.childManager = childManager; 5 | exports.childWorker = childWorker; -------------------------------------------------------------------------------- /test/file.js: -------------------------------------------------------------------------------- 1 | let childTemplate = require('../output/child'); 2 | 3 | childTemplate((data, done) => { 4 | done({ 5 | type: 'file', 6 | msg: data.msg, 7 | index: data.index 8 | }) 9 | }); -------------------------------------------------------------------------------- /test/text.js: -------------------------------------------------------------------------------- 1 | let childTemplate = require('../output/child'); 2 | 3 | childTemplate((data, done) => { 4 | done({ 5 | type: 'text', 6 | msg: data.msg, 7 | index: data.index 8 | }); 9 | }); -------------------------------------------------------------------------------- /test/error.js: -------------------------------------------------------------------------------- 1 | let childTemplate = require('../output/child'); 2 | 3 | childTemplate((data, done) => { 4 | let index = data.index; 5 | 6 | if (index % 10 === 0) { 7 | done(new Error('got Unexpected error!!')); 8 | } 9 | 10 | done({ 11 | type: 'file', 12 | msg: data 13 | }); 14 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "advance-child-pool", 3 | "version": "0.1.0", 4 | "description": "Advance Node.js child process pool, support multitype child process, error handing", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "error": "^7.0.2", 11 | "lodash": "^4.17.4", 12 | "node-uuid": "^1.4.7" 13 | }, 14 | "devDependencies": { 15 | "mocha": "^3.2.0" 16 | }, 17 | "scripts": { 18 | "test": "./node_modules/.bin/mocha ./test/index.js -t 10000", 19 | "build": "./node_modules/.bin/tsc -p jsconfig.json", 20 | "dev": "./node_modules/.bin/tsc -p jsconfig.json --watch" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git@github.com:andycall/advance-child-pool.git" 25 | }, 26 | "keywords": [ 27 | "data-transform" 28 | ], 29 | "author": "dongtiangche@outlook.com", 30 | "license": "MIT" 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | 61 | .idea/ 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # advance-child-pool 2 | Advance Node.js child process pool, support multitype child process, error handing 3 | 4 | support features: 5 | + child process pool manage 6 | + recover dead child process 7 | + message queue 8 | + muilti task config 9 | 10 | ## Setup 11 | 12 | ### Config Master Process 13 | ```javascript 14 | let childProcess = require('child-process-manager').childManager; 15 | 16 | const textFilePath = path.join(__dirname, 'text.js'); 17 | const fileFilePath = path.join(__dirname, 'file.js'); 18 | const errorFilePath = path.join(__dirname, 'error.js'); 19 | 20 | // register child process 21 | childProcess.registerTask('text', textFilePath); 22 | childProcess.registerTask('file', fileFilePath); 23 | childProcess.registerTask('error', errorFilePath); 24 | 25 | // start pool 26 | childProcess.childStartUp(); 27 | 28 | // send data to child 29 | childProcess.sendData('text', { 30 | data: 'helloworld' 31 | }).then((res) => { 32 | // response from child_process 33 | console.log(res); 34 | }).catch(err => { 35 | // error from child_process 36 | console.log(err); 37 | }); 38 | ``` 39 | 40 | ### Config Child 41 | ```javascript 42 | let childTemplate = require('child-process-manager').childWorker; 43 | 44 | childTemplate((data, done) => { 45 | done({ 46 | type: 'file', 47 | msg: data.msg, 48 | index: data.index 49 | }) 50 | }); 51 | ``` 52 | 53 | ## Test 54 | run test 55 | ```javascript 56 | npm test 57 | ``` 58 | -------------------------------------------------------------------------------- /output/child.js: -------------------------------------------------------------------------------- 1 | let WrappedError = require('error/wrapped'); 2 | let childProcessErrorWrapper = new WrappedError({ 3 | message: '{message}', 4 | type: 'child-process-error', 5 | stack: null, 6 | statusCode: null 7 | }); 8 | module.exports = function (callback) { 9 | process.on('message', (msg) => { 10 | onMessageReceive(msg).then((data) => { 11 | process.send({ 12 | success: true, 13 | data: data 14 | }); 15 | }).catch(err => { 16 | process.send({ 17 | success: false, 18 | err: childProcessErrorWrapper(err, { 19 | stack: err.stack, 20 | message: err.message, 21 | statusCode: err.statusCode 22 | }) 23 | }); 24 | }); 25 | }); 26 | process.on('uncaughtException', function (err) { 27 | process.send({ 28 | success: false, 29 | err: err 30 | }, () => { 31 | process.exit(1); 32 | }); 33 | }); 34 | function onMessageReceive(data) { 35 | return new Promise((resolve, reject) => { 36 | callback(data, (trigger) => { 37 | if (trigger instanceof Error) { 38 | reject(trigger); 39 | } 40 | else { 41 | resolve(trigger); 42 | } 43 | }); 44 | }); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /src/child.js: -------------------------------------------------------------------------------- 1 | let WrappedError = require('error/wrapped'); 2 | 3 | let childProcessErrorWrapper = new WrappedError({ 4 | message: '{message}', 5 | type: 'child-process-error', 6 | stack: null, 7 | statusCode: null 8 | }); 9 | 10 | module.exports = function(callback) { 11 | process.on('message', (msg) => { 12 | onMessageReceive(msg).then((data) => { 13 | process.send({ 14 | success: true, 15 | data: data 16 | }); 17 | }).catch(err => { 18 | process.send({ 19 | success: false, 20 | err: childProcessErrorWrapper(err, { 21 | stack: err.stack, 22 | message: err.message, 23 | statusCode: err.statusCode 24 | }) 25 | }); 26 | }); 27 | }); 28 | 29 | process.on('uncaughtException', function (err) { 30 | process.send({ 31 | success: false, 32 | err: err 33 | }, () => { 34 | process.exit(1); 35 | }); 36 | }); 37 | 38 | function onMessageReceive(data) { 39 | return new Promise((resolve, reject) => { 40 | callback(data, (trigger) => { 41 | if (trigger instanceof Error) { 42 | reject(trigger); 43 | } 44 | else { 45 | resolve(trigger); 46 | } 47 | }) 48 | }); 49 | } 50 | }; -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | let childProcess = require('../output/child_manager'); 2 | let path = require('path'); 3 | let assert = require('assert'); 4 | 5 | const textFilePath = path.join(__dirname, 'text.js'); 6 | const fileFilePath = path.join(__dirname, 'file.js'); 7 | const errorFilePath = path.join(__dirname, 'error.js'); 8 | describe('Child Process Manager Test', () => { 9 | before(() => { 10 | childProcess.registerTask('text', textFilePath); 11 | childProcess.registerTask('file', fileFilePath); 12 | childProcess.registerTask('error', errorFilePath); 13 | childProcess.childStartUp(); 14 | }); 15 | 16 | it('create Text Job', (done) => { 17 | let data = { 18 | msg: 'helloworld' 19 | }; 20 | 21 | childProcess.sendData('text', data).then((res) => { 22 | assert.equal(res.type, 'text'); 23 | assert.equal(res.msg, 'helloworld'); 24 | 25 | done(); 26 | }).catch(err => { 27 | done(err); 28 | }) 29 | }); 30 | 31 | it('create Many Job', done => { 32 | let data = { 33 | msg: 'helloworld' 34 | }; 35 | 36 | let childArr = []; 37 | 38 | for (let i = 0; i < 100; i ++) { 39 | data.index = i; 40 | let promise = childProcess.sendData('text', Object.assign({}, data)); 41 | childArr.push(promise) 42 | } 43 | 44 | Promise.all(childArr).then(result => { 45 | result.forEach((item, index) => { 46 | assert.equal(item.type, 'text'); 47 | assert.equal(item.msg, 'helloworld'); 48 | assert.equal(item.index, index); 49 | }); 50 | done(); 51 | }).catch(err => { 52 | done(err); 53 | }) 54 | }); 55 | 56 | it('create multi many job', done => { 57 | let textData = { 58 | msg: 'Hello Text' 59 | }; 60 | 61 | let fileData = { 62 | msg: 'Hello File' 63 | }; 64 | 65 | let childArr = []; 66 | 67 | for (let i = 0; i < 500; i ++) { 68 | textData.index = i; 69 | fileData.index = i; 70 | let textPromise = childProcess.sendData('text', Object.assign({}, textData)); 71 | let filePromise = childProcess.sendData('file', Object.assign({}, fileData)); 72 | childArr.push(textPromise); 73 | childArr.push(filePromise); 74 | } 75 | 76 | Promise.all(childArr).then((result) => { 77 | result.forEach((item, index) => { 78 | if (index % 2 === 0) { 79 | assert.equal(item.type, 'text'); 80 | assert.equal(item.msg, 'Hello Text'); 81 | assert.equal(item.index, index / 2); 82 | } 83 | else { 84 | assert.equal(item.type, 'file'); 85 | assert.equal(item.msg, 'Hello File'); 86 | assert.equal(item.index, Math.floor(index / 2)); 87 | } 88 | }); 89 | 90 | done(); 91 | }).catch(err => { 92 | done(err); 93 | }) 94 | }); 95 | 96 | it('running error task', (done) => { 97 | let childArr = []; 98 | 99 | for (let i = 0; i < 100; i ++) { 100 | let errorData = { 101 | msg: 'error!!', 102 | index: i 103 | }; 104 | 105 | let errorPromise = childProcess.sendData('error', errorData); 106 | 107 | childArr.push(errorPromise); 108 | } 109 | 110 | Promise.all(childArr).then((result) => { 111 | console.log('1', result); 112 | }).catch(err => { 113 | assert.equal(err.message, 'got Unexpected error!!'); 114 | 115 | done(); 116 | }); 117 | }); 118 | }); -------------------------------------------------------------------------------- /output/child_manager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 子node进程管理器 3 | */ 4 | let fork = require('child_process').fork; 5 | let execSync = require('child_process').execSync; 6 | let path = require('path'); 7 | let _ = require('lodash'); 8 | let uuid = require('node-uuid'); 9 | let os = require('os'); 10 | class ProcessManager { 11 | constructor(poolSize) { 12 | // 进程池Hash 13 | this.childMap = new Map(); 14 | // 任务池Hasp 15 | this.taskMap = new Map(); 16 | // pid池Hash 17 | this.idMap = new Map(); 18 | this.poolSize = poolSize || 4; 19 | this.runningCount = {}; 20 | this._childCount = 0; 21 | this.jobQuene = {}; 22 | this.maxErrorCount = poolSize * 10; 23 | this.maxResponseTime = 1000 * 60; 24 | this.errCount = 0; 25 | } 26 | _getChildSize() { 27 | return this.childMap.size; 28 | } 29 | registerTask(task, entryPath) { 30 | if (this.taskMap.size > this.poolSize) { 31 | throw new Error('任务量不能超过进程池的数量'); 32 | } 33 | this.taskMap.set(task, entryPath); 34 | } 35 | handleTooMuchError(callback) { } 36 | kill() { 37 | for (let taskType of this.taskMap.keys()) { 38 | let taskPool = this.childMap.get(taskType); 39 | taskPool.forEach(info => { 40 | let instance = info.childInstance; 41 | let instancePid = instance.pid; 42 | execSync(`kill ${instancePid}`); 43 | }); 44 | } 45 | } 46 | /** 47 | * 正确应对进程异常退出 48 | */ 49 | handleChildExit(taskType, entry, childInstance, code, signal) { 50 | let childPool = this.childMap.get(taskType); 51 | setTimeout(() => { 52 | this.maxErrorCount = this.poolSize * 10; 53 | }, 5000); 54 | childPool.forEach(info => { 55 | if (info.childInstance === childInstance) { 56 | let newInstance = fork(entry); 57 | newInstance.on('close', this.handleChildExit.bind(this, taskType, entry, newInstance)); 58 | info.childInstance = newInstance; 59 | this.errCount++; 60 | // 过多错误直接报警 61 | if (this.errCount > this.maxErrorCount) { 62 | console.log('too many error!!'); 63 | this.handleTooMuchError(this.errCount); 64 | return; 65 | } 66 | // 恢复任务 67 | let taskId = info.id; 68 | let idInfo = this.idMap.get(taskId); 69 | if (idInfo) { 70 | // 给进程分配新的任务 71 | idInfo.childInstance = newInstance; 72 | this.idMap.set(taskId, idInfo); 73 | this.addMessageListener(taskId, idInfo.messageCallback); 74 | this.sendMessageToChild(taskId, idInfo.data); 75 | } 76 | } 77 | }); 78 | } 79 | /** 80 | * 根据task数和poolSize预先分配子进程 81 | */ 82 | childStartUp() { 83 | let taskCount = Math.floor(this.poolSize / this.taskMap.size); 84 | let taskKeys = this.taskMap.keys(); 85 | for (let taskType of taskKeys) { 86 | for (let i = 0; i < taskCount; i++) { 87 | let entry = this.taskMap.get(taskType); 88 | if (this._childCount >= this.poolSize) { 89 | return; 90 | } 91 | this._childCount++; 92 | let child = fork(entry); 93 | child.on('exit', this.handleChildExit.bind(this, taskType, entry, child)); 94 | let childTaskInfo; 95 | if (!this.childMap.has(taskType)) { 96 | childTaskInfo = []; 97 | } 98 | else { 99 | childTaskInfo = this.childMap.get(taskType); 100 | } 101 | childTaskInfo.push({ 102 | taskType: taskType, 103 | childInstance: child, 104 | available: true 105 | }); 106 | this.childMap.set(taskType, childTaskInfo); 107 | } 108 | this.runningCount[taskType] = 0; 109 | this.jobQuene[taskType] = []; 110 | } 111 | } 112 | /** 113 | * 在不退出进程的情况下回收进程 114 | * @param id 115 | */ 116 | detach(id) { 117 | if (!this.idMap.has(id)) { 118 | throw new Error('未知的process Id'); 119 | } 120 | let idInfo = this.idMap.get(id); 121 | let taskType = idInfo.type; 122 | let childInstance = idInfo.childInstance; 123 | this.runningCount[taskType]--; 124 | let childMapInfo = this.childMap.get(taskType); 125 | let targetChildInfo = null; 126 | childMapInfo.forEach(_info => { 127 | if (_info.id === id) { 128 | // 回收进程,设为可用状态 129 | _info.id = null; 130 | _info.available = true; 131 | targetChildInfo = _info; 132 | } 133 | }); 134 | // 消除旧的进程 135 | this.idMap.delete(id); 136 | if (this.jobQuene[taskType].length > 0) { 137 | let newTask = this.jobQuene[taskType].shift(); 138 | let newTaskId = newTask.id; 139 | targetChildInfo.id = newTaskId; 140 | let idInfo = this.idMap.get(newTaskId); 141 | // 给进程分配新的任务 142 | idInfo.childInstance = childInstance; 143 | this.idMap.set(newTaskId, idInfo); 144 | this.runningCount[taskType]++; 145 | this.addMessageListener(newTaskId, idInfo.messageCallback); 146 | this.sendMessageToChild(newTaskId, idInfo.data); 147 | } 148 | // 回收变量 149 | targetChildInfo = null; 150 | childMapInfo = null; 151 | } 152 | /** 153 | * 创建一个任务,用于发送给进程 154 | * @param type 155 | * @returns {*} 156 | */ 157 | createJob(type) { 158 | if (this._childCount === 0) { 159 | throw new Error('请预分配进程到进程池'); 160 | } 161 | if (!this.taskMap.has(type)) { 162 | throw new Error('未知的任务类型'); 163 | } 164 | let id = uuid.v1(); 165 | let taskCount = Math.floor(this.poolSize / this.taskMap.size); 166 | // 队列满了,等待 167 | if (this.runningCount[type] >= taskCount) { 168 | this.jobQuene[type].push({ 169 | id: id 170 | }); 171 | this.idMap.set(id, { 172 | messageCallback: null, 173 | childInstance: null, 174 | data: null, 175 | type: type 176 | }); 177 | return id; 178 | } 179 | this.runningCount[type]++; 180 | let taskChildArr = this.childMap.get(type); 181 | let readyChildInstance; 182 | taskChildArr.forEach(info => { 183 | if (info.available && !readyChildInstance) { 184 | readyChildInstance = info.childInstance; 185 | info.available = false; 186 | info.id = id; 187 | this.idMap.set(id, { 188 | messageCallback: null, 189 | childInstance: readyChildInstance, 190 | type: type, 191 | data: null 192 | }); 193 | } 194 | }); 195 | if (!readyChildInstance) { 196 | throw new Error('没有可用的进程分配'); 197 | } 198 | readyChildInstance = null; 199 | return id; 200 | } 201 | // 添加消息回调 202 | addMessageListener(id, callback = () => { }) { 203 | if (!this.idMap.has(id)) { 204 | throw new Error('未知的process Id'); 205 | } 206 | let idInfo = this.idMap.get(id); 207 | let childInstance = idInfo.childInstance; 208 | if (!childInstance) { 209 | idInfo.messageCallback = callback; 210 | return; 211 | } 212 | childInstance.once('message', (msg) => { 213 | callback(msg); 214 | clearTimeout(idInfo.timer); 215 | idInfo = null; 216 | }); 217 | } 218 | // 向进程发送数据 219 | sendMessageToChild(id, message) { 220 | if (!this.idMap.has(id)) { 221 | throw new Error('未知的process Id'); 222 | } 223 | let idInfo = this.idMap.get(id); 224 | let childInstance = idInfo.childInstance; 225 | if (!childInstance) { 226 | idInfo.data = message; 227 | return; 228 | } 229 | idInfo.timer = setTimeout(() => { 230 | console.error('child process is hang up!!', message); 231 | let instancePid = childInstance.pid; 232 | execSync(`kill ${instancePid}`); 233 | childInstance = null; 234 | }, this.maxResponseTime); 235 | childInstance.send(message); 236 | } 237 | // 封装更彻底的接口,调用数据直接返回Promise 238 | sendData(type, data) { 239 | return new Promise((resolve, reject) => { 240 | let job = this.createJob(type); 241 | this.addMessageListener(job, (msg) => { 242 | this.detach(job); 243 | if (msg.success) { 244 | resolve(msg.data); 245 | } 246 | else { 247 | reject(msg.err); 248 | } 249 | }); 250 | this.sendMessageToChild(job, data); 251 | }); 252 | } 253 | } 254 | const manager = new ProcessManager(); 255 | process.on('exit', function () { 256 | console.log('killing..'); 257 | manager.kill(); 258 | process.exit(0); 259 | }); 260 | process.on('SIGINT', function () { 261 | console.log('Ctrl-C'); 262 | manager.kill(); 263 | process.exit(0); 264 | }); 265 | module.exports = manager; 266 | -------------------------------------------------------------------------------- /src/child_manager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 子node进程管理器 3 | */ 4 | 5 | let fork = require('child_process').fork; 6 | let execSync = require('child_process').execSync; 7 | let path = require('path'); 8 | let _ = require('lodash'); 9 | let uuid = require('node-uuid'); 10 | let os = require('os'); 11 | 12 | class ProcessManager { 13 | constructor(poolSize) { 14 | // 进程池Hash 15 | this.childMap = new Map(); 16 | // 任务池Hasp 17 | this.taskMap = new Map(); 18 | // pid池Hash 19 | this.idMap = new Map(); 20 | this.poolSize = poolSize || 4; 21 | this.runningCount = {}; 22 | this._childCount = 0; 23 | this.jobQuene = {}; 24 | this.maxErrorCount = poolSize * 10; 25 | this.maxResponseTime = 1000 * 60; 26 | this.errCount = 0; 27 | } 28 | 29 | _getChildSize() { 30 | return this.childMap.size; 31 | } 32 | 33 | registerTask(task, entryPath) { 34 | if (this.taskMap.size > this.poolSize) { 35 | throw new Error('任务量不能超过进程池的数量'); 36 | } 37 | 38 | this.taskMap.set(task, entryPath); 39 | } 40 | 41 | handleTooMuchError(callback) { } 42 | 43 | kill() { 44 | for (let taskType of this.taskMap.keys()) { 45 | let taskPool = this.childMap.get(taskType); 46 | 47 | taskPool.forEach(info => { 48 | let instance = info.childInstance; 49 | let instancePid = instance.pid; 50 | 51 | execSync(`kill ${instancePid}`); 52 | }); 53 | } 54 | } 55 | 56 | /** 57 | * 正确应对进程异常退出 58 | */ 59 | handleChildExit(taskType, entry, childInstance, code, signal) { 60 | let childPool = this.childMap.get(taskType); 61 | 62 | setTimeout(() => { 63 | this.maxErrorCount = this.poolSize * 10; 64 | }, 5000); 65 | 66 | childPool.forEach(info => { 67 | if (info.childInstance === childInstance) { 68 | let newInstance = fork(entry); 69 | 70 | newInstance.on('close', this.handleChildExit.bind(this, taskType, entry, newInstance)); 71 | info.childInstance = newInstance; 72 | 73 | this.errCount++; 74 | 75 | // 过多错误直接报警 76 | if (this.errCount > this.maxErrorCount) { 77 | console.log('too many error!!'); 78 | this.handleTooMuchError(this.errCount); 79 | return; 80 | } 81 | 82 | // 恢复任务 83 | let taskId = info.id; 84 | let idInfo = this.idMap.get(taskId); 85 | 86 | if (idInfo) { 87 | // 给进程分配新的任务 88 | idInfo.childInstance = newInstance; 89 | this.idMap.set(taskId, idInfo); 90 | 91 | this.addMessageListener(taskId, idInfo.messageCallback); 92 | this.sendMessageToChild(taskId, idInfo.data); 93 | } 94 | } 95 | }); 96 | 97 | } 98 | 99 | /** 100 | * 根据task数和poolSize预先分配子进程 101 | */ 102 | childStartUp() { 103 | let taskCount = Math.floor(this.poolSize / this.taskMap.size); 104 | let taskKeys = this.taskMap.keys(); 105 | 106 | for (let taskType of taskKeys) { 107 | for (let i = 0; i < taskCount; i++) { 108 | let entry = this.taskMap.get(taskType); 109 | 110 | if (this._childCount >= this.poolSize) { 111 | return; 112 | } 113 | 114 | this._childCount++; 115 | 116 | let child = fork(entry); 117 | 118 | child.on('exit', this.handleChildExit.bind(this, taskType, entry, child)); 119 | 120 | let childTaskInfo; 121 | if (!this.childMap.has(taskType)) { 122 | childTaskInfo = []; 123 | } 124 | else { 125 | childTaskInfo = this.childMap.get(taskType); 126 | } 127 | 128 | childTaskInfo.push({ 129 | taskType: taskType, 130 | childInstance: child, 131 | available: true 132 | }); 133 | 134 | this.childMap.set(taskType, childTaskInfo); 135 | } 136 | this.runningCount[taskType] = 0; 137 | this.jobQuene[taskType] = []; 138 | } 139 | } 140 | 141 | /** 142 | * 在不退出进程的情况下回收进程 143 | * @param id 144 | */ 145 | detach(id) { 146 | if (!this.idMap.has(id)) { 147 | throw new Error('未知的process Id'); 148 | } 149 | 150 | let idInfo = this.idMap.get(id); 151 | let taskType = idInfo.type; 152 | let childInstance = idInfo.childInstance; 153 | 154 | this.runningCount[taskType]--; 155 | 156 | let childMapInfo = this.childMap.get(taskType); 157 | let targetChildInfo = null; 158 | 159 | childMapInfo.forEach(_info => { 160 | if (_info.id === id) { 161 | // 回收进程,设为可用状态 162 | _info.id = null; 163 | _info.available = true; 164 | 165 | targetChildInfo = _info; 166 | } 167 | }); 168 | 169 | // 消除旧的进程 170 | this.idMap.delete(id); 171 | 172 | if (this.jobQuene[taskType].length > 0) { 173 | let newTask = this.jobQuene[taskType].shift(); 174 | let newTaskId = newTask.id; 175 | 176 | targetChildInfo.id = newTaskId; 177 | 178 | let idInfo = this.idMap.get(newTaskId); 179 | 180 | // 给进程分配新的任务 181 | idInfo.childInstance = childInstance; 182 | this.idMap.set(newTaskId, idInfo); 183 | 184 | this.runningCount[taskType]++; 185 | 186 | this.addMessageListener(newTaskId, idInfo.messageCallback); 187 | this.sendMessageToChild(newTaskId, idInfo.data); 188 | } 189 | 190 | // 回收变量 191 | targetChildInfo = null; 192 | childMapInfo = null; 193 | } 194 | 195 | /** 196 | * 创建一个任务,用于发送给进程 197 | * @param type 198 | * @returns {*} 199 | */ 200 | createJob(type) { 201 | if (this._childCount === 0) { 202 | throw new Error('请预分配进程到进程池'); 203 | } 204 | 205 | if (!this.taskMap.has(type)) { 206 | throw new Error('未知的任务类型'); 207 | } 208 | 209 | let id = uuid.v1(); 210 | let taskCount = Math.floor(this.poolSize / this.taskMap.size); 211 | 212 | // 队列满了,等待 213 | if (this.runningCount[type] >= taskCount) { 214 | this.jobQuene[type].push({ 215 | id: id 216 | }); 217 | 218 | this.idMap.set(id, { 219 | messageCallback: null, 220 | childInstance: null, 221 | data: null, 222 | type: type 223 | }); 224 | 225 | return id; 226 | } 227 | 228 | this.runningCount[type]++; 229 | 230 | let taskChildArr = this.childMap.get(type); 231 | 232 | let readyChildInstance; 233 | 234 | taskChildArr.forEach(info => { 235 | if (info.available && !readyChildInstance) { 236 | readyChildInstance = info.childInstance; 237 | info.available = false; 238 | info.id = id; 239 | 240 | this.idMap.set(id, { 241 | messageCallback: null, 242 | childInstance: readyChildInstance, 243 | type: type, 244 | data: null 245 | }); 246 | } 247 | }); 248 | 249 | if (!readyChildInstance) { 250 | throw new Error('没有可用的进程分配'); 251 | } 252 | 253 | readyChildInstance = null; 254 | 255 | return id; 256 | } 257 | 258 | // 添加消息回调 259 | addMessageListener(id, callback = () => { }) { 260 | if (!this.idMap.has(id)) { 261 | throw new Error('未知的process Id'); 262 | } 263 | 264 | let idInfo = this.idMap.get(id); 265 | 266 | let childInstance = idInfo.childInstance; 267 | 268 | if (!childInstance) { 269 | idInfo.messageCallback = callback; 270 | return; 271 | } 272 | 273 | childInstance.once('message', (msg) => { 274 | callback(msg); 275 | 276 | clearTimeout(idInfo.timer); 277 | idInfo = null; 278 | }); 279 | } 280 | 281 | // 向进程发送数据 282 | sendMessageToChild(id, message) { 283 | if (!this.idMap.has(id)) { 284 | throw new Error('未知的process Id'); 285 | } 286 | 287 | let idInfo = this.idMap.get(id); 288 | 289 | let childInstance = idInfo.childInstance; 290 | 291 | if (!childInstance) { 292 | idInfo.data = message; 293 | return; 294 | } 295 | 296 | idInfo.timer = setTimeout(() => { 297 | console.error('child process is hang up!!', message); 298 | let instancePid = childInstance.pid; 299 | execSync(`kill ${instancePid}`); 300 | childInstance = null; 301 | }, this.maxResponseTime); 302 | childInstance.send(message); 303 | } 304 | 305 | // 封装更彻底的接口,调用数据直接返回Promise 306 | sendData(type, data) { 307 | return new Promise((resolve, reject) => { 308 | let job = this.createJob(type); 309 | 310 | this.addMessageListener(job, (msg) => { 311 | this.detach(job); 312 | 313 | if (msg.success) { 314 | resolve(msg.data); 315 | } 316 | else { 317 | reject(msg.err); 318 | } 319 | }); 320 | 321 | this.sendMessageToChild(job, data); 322 | }); 323 | } 324 | } 325 | 326 | const manager = new ProcessManager(); 327 | 328 | process.on('exit', function () { 329 | console.log('killing..'); 330 | manager.kill(); 331 | process.exit(0); 332 | }); 333 | process.on('SIGINT', function () { 334 | console.log('Ctrl-C'); 335 | manager.kill(); 336 | process.exit(0); 337 | }); 338 | 339 | 340 | module.exports = manager; --------------------------------------------------------------------------------