UNPKG

total4

Version:
505 lines (426 loc) 11.8 kB
const Fs = require('fs'); const Cluster = require('cluster'); const CLUSTER_REQ = { TYPE: 'req' }; const CLUSTER_RES = { TYPE: 'res' }; const CLUSTER_EMIT = { TYPE: 'emit' }; const CLUSTER_MASTER = { TYPE: 'master' }; const MAXTHREADLATENCY = 70; const FORKS = []; var OPERATIONS = {}; var CALLBACKS = {}; var OPTIONS = {}; var THREADS = 0; var MASTER = null; var CONTINUE = false; var STATS = []; var TIMEOUTS = {}; exports.on = function(name, callback) { !MASTER && (MASTER = {}); if (MASTER[name]) MASTER.push(callback); else MASTER[name] = [callback]; return F; }; exports.emit = function(name, a, b, c, d, e) { CLUSTER_EMIT.name = name; CLUSTER_EMIT.a = a; CLUSTER_EMIT.b = b; CLUSTER_EMIT.c = c; CLUSTER_EMIT.d = d; CLUSTER_EMIT.e = e; if (Cluster.isMaster) message(CLUSTER_EMIT); else if (F.isCluster) process.send(CLUSTER_EMIT); return F; }; exports.emit2 = function(msg) { if (Cluster.isMaster) message(msg); else if (F.isCluster) process.send(msg); return F; }; exports.master = function(name, a, b, c, d, e) { if (F.isCluster) { CLUSTER_MASTER.name = name; CLUSTER_MASTER.a = a; CLUSTER_MASTER.b = b; CLUSTER_MASTER.c = c; CLUSTER_MASTER.d = d; CLUSTER_MASTER.e = e; process.send(CLUSTER_MASTER); } return F; }; exports.request = function(name, data, callback, timeout) { if (typeof(data) === 'function') { timeout = callback; callback = data; data = null; } CLUSTER_REQ.name = name; CLUSTER_REQ.data = data; CLUSTER_REQ.callback = (Math.random()).toString(32).substring(3); var obj = CALLBACKS[CLUSTER_REQ.callback] = { fn: callback, response: [], id: CLUSTER_REQ.callback }; obj.timeout = setTimeout(function(obj) { delete CALLBACKS[obj.id]; obj.fn && obj.fn('Timeout', obj.response); }, timeout || 3000, obj); if (Cluster.isMaster) mastersend(CLUSTER_REQ); else process.send(CLUSTER_REQ); return F; }; exports.response = function(name, callback) { OPERATIONS[name] = callback; return F; }; exports.req = function(message) { if (!F.isCluster) return F; // message.id // message.name // message.data // message.callback if (OPERATIONS[message.name]) { OPERATIONS[message.name](message.data, function(response) { CLUSTER_RES.data = response; CLUSTER_RES.target = message.id; CLUSTER_RES.callback = message.callback; process.send(CLUSTER_RES); }, message.id); } else { CLUSTER_RES.target = message.id; CLUSTER_RES.data = undefined; CLUSTER_RES.callback = message.callback; process.send(CLUSTER_RES); } return F; }; exports.res = function(message) { var callback = CALLBACKS[message.callback]; callback.fn && callback.response.push(message.data); var count = message.target === 'master' ? THREADS : THREADS - 1; if (callback.response.length >= count) { delete CALLBACKS[callback.id]; clearTimeout(callback.timeout); callback.fn && callback.fn(null, callback.response); } }; exports.http = function(count, mode, options, callback) { process.env.TZ = (options ? options.tz : '') || 'utc'; // Fork will obtain options automatically via event if (Cluster.isMaster) { CLUSTER_REQ.id = 'master'; CLUSTER_RES.id = 'master'; CLUSTER_EMIT.id = 'master'; master(count, mode, options, callback); } else fork(); }; exports.https = function(count, mode, options, callback) { process.env.TZ = (options ? options.tz : '') || 'utc'; // Fork will obtain options automatically via event if (Cluster.isMaster) { CLUSTER_REQ.id = 'master'; CLUSTER_RES.id = 'master'; CLUSTER_EMIT.id = 'master'; master(count, mode, options, callback, true); } else fork(); }; exports.restart = function(index) { if (index == null) { for (var i = 0; i < THREADS; i++) setTimeout(index => exports.restart(index), i * 2000, i); } else { var fork = FORKS[index]; if (fork) { fork.$ready = false; fork.removeAllListeners(); fork.kill(0); exec(index); } else exec(index); RELEASE && console.log('======= ' + (new Date().format('yyyy-MM-dd HH:mm:ss')) + ': restarted thread with index "{0}"'.format(index)); } }; function master(count, mode, options, callback, https) { if (count == null) count = require('os').cpus().length; OPTIONS.auto = count === 'auto'; if (OPTIONS.auto) count = 1; if (typeof(options) === 'function') { callback = options; options = {}; } else if (!options) options = {}; OPTIONS.count = count; OPTIONS.mode = mode; OPTIONS.options = options; var Os = require('os'); require('./utils'); var memory = process.memoryUsage(); if (!process.connected || OPTIONS.options.logs === 'isolated') { console.log('==================== CLUSTER ======================='); console.log('PID : ' + process.pid); console.log('Node.js : ' + process.version); console.log('Total.js : v' + F.version_header); console.log('OS : ' + Os.platform() + ' ' + Os.release()); console.log('Memory : ' + memory.heapUsed.filesize(2) + ' / ' + memory.heapTotal.filesize(2)); console.log('User : ' + Os.userInfo().username); console.log('OS : ' + Os.platform() + ' ' + Os.release()); console.log('===================================================='); console.log('Thread count : {0}'.format(OPTIONS.auto ? 'auto' : count)); options.thread && console.log('Thread name : ' + options.thread); console.log('Date : ' + new Date().format('yyyy-MM-dd HH:mm:ss')); console.log('Mode : ' + mode); console.log('====================================================\n'); } if (options.thread) global.THREAD = options.thread; if (mode === 'debug') { require('./debug').watcher(function(changes) { var can = false; if (options.thread) { for (var i = 0; i < changes.length; i++) { var change = changes[i]; if (change.indexOf('/threads/') !== -1) { if (change.indexOf('/threads/' + options.thread + '/') !== -1) { can = true; break; } } else { can = true; break; } } } else can = true; can && exports.restart(); }); } THREADS = count; var can = function(cb) { if (CONTINUE) cb(); else setTimeout(can, 500, cb); }; count.async(function(i, next) { exec(Math.abs(i - THREADS), https); can(next); }, function() { callback && callback(FORKS); }); process.title = 'total: cluster'; var filename = require('path').join(process.cwd(), 'restart' + (options.thread ? ('_' + options.thread) : '')); var restartthreads = function(err) { if (!err) { Fs.unlink(filename, NOOP); if (!F.restarting) { exports.restart(); F.restarting = true; setTimeout(function() { F.restarting = false; }, 30000); } } }; var killme = function(fork) { fork.kill(); }; var counter = 0; var main = {}; main.pid = process.pid; main.version = {}; main.version.node = process.version; main.version.total = F.version_header; main.version.app = CONF.version; main.thread = options.thread; setInterval(function() { counter++; if (counter % 10 === 0) { main.date = new Date(); main.threads = THREADS; var memory = process.memoryUsage(); main.memory = (memory.heapUsed / 1024 / 1024).floor(2); main.stats = STATS; Fs.writeFile(process.mainModule.filename + '.json', JSON.stringify(main, null, ' '), NOOP); } Fs.stat(filename, restartthreads); // Ping if (!OPTIONS.auto) return; var isfree = false; var isempty = false; var sum = 0; // Auto-ping for (var i = 0; i < FORKS.length; i++) { var fork = FORKS[i]; if (fork) { if (fork.$pingoverload) fork.$pingoverload--; if (fork.$ping) { sum += fork.$ping; if (fork.$ping < MAXTHREADLATENCY) isfree = true; } else isempty = true; fork.$ping_beg = Date.now(); fork.send('total:ping'); } } if (isfree || isempty) { if (counter % 5 === 0 && !isempty && THREADS > 1) { // try to remove last var lastindex = FORKS.length - 1; var last = FORKS[lastindex]; if (last == null) { TIMEOUTS[lastindex] && clearTimeout(TIMEOUTS[lastindex]); FORKS.splice(lastindex, 1); STATS.splice(lastindex, 1); THREADS = FORKS.length; return; } for (var i = 0; i < STATS.length; i++) { if (STATS[i].id === last.$id) { if (STATS[i].pending < 2) { // nothing pending fork.$ready = false; fork.removeAllListeners(); fork.kill(0); setTimeout(killme, 1000, fork); FORKS.splice(lastindex, 1); STATS.splice(lastindex, 1); } break; } } THREADS = FORKS.length; } } else if (!options.cluster_limit || THREADS < options.cluster_limit) { var avg = (sum / FORKS.length).floor(3); if (avg > MAXTHREADLATENCY) exec(THREADS++, https); } }, 5000); } function message(m) { if (m === 'total:init') { OPTIONS.options.id = this.$id; this.send({ TYPE: 'init', bundling: !CONTINUE, id: this.$id, mode: OPTIONS.mode, options: OPTIONS.options, unixsocket: OPTIONS.unixsocket, threads: OPTIONS.count, index: this.$index, https: this.$https }); return; } if (m === 'total:ready') { CONTINUE = true; this.$ready = true; return; } if (m === 'total:ping') { this.$ping = Date.now() - this.$ping_beg; return; } if (m === 'total:update') { for (var i = 1; i < FORKS.length; i++) FORKS[i] && FORKS[i].$ready && FORKS[i].send(m); return; } if (m === 'total:overload') { if (this.$pingoverload) { this.$ping = MAXTHREADLATENCY + 1; this.$pingoverload = 3; // waits 15 seconds } return; } if (m.TYPE === 'master') { if (MASTER && MASTER[m.name]) { for (var i = 0, length = MASTER[m.name].length; i < length; i++) MASTER[m.name][i](m.a, m.b, m.c, m.d, m.e); } } else if (m.TYPE === 'snapshot') { var is = false; STATS[i]; for (var i = 0; i < STATS.length; i++) { if (STATS[i].id === m.data.id) { m.data.ping = this.$ping; STATS[i] = m.data; is = true; break; } } !is && STATS.push(m.data); } else { if (m.TYPE === 'total:emit') { if (process.send) process.send(m); else { m.TYPE = 'emit'; for (var i = 0; i < FORKS.length; i++) FORKS[i] && FORKS[i].$ready && FORKS[i].send(m); } return; } if (m.target === 'master') { exports.res(m); } else { for (var i = 0; i < FORKS.length; i++) FORKS[i] && FORKS[i].$ready && FORKS[i].send(m); } } } function mastersend(m) { for (var i = 0; i < FORKS.length; i++) FORKS[i] && FORKS[i].send(m); } function exec(index, https) { if (TIMEOUTS[index]) { clearTimeout(TIMEOUTS[index]); delete TIMEOUTS[index]; } var fork = Cluster.fork(); fork.$id = index.toString(); fork.$https = https; fork.$index = index; fork.on('message', message); fork.on('exit', function() { FORKS[index] = null; TIMEOUTS[index] = setTimeout(exports.restart, 1000, index); }); if (FORKS[index] === undefined) FORKS.push(fork); else FORKS[index] = fork; /* (function(fork) { setTimeout(function() { OPTIONS.options.id = fork.$id; fork.send({ TYPE: 'init', bundling: !CONTINUE, id: fork.$id, mode: OPTIONS.mode, options: OPTIONS.options, threads: OPTIONS.count, index: index, https: https }); }, fork.$id * 500); })(fork);*/ } function fork() { require('./index'); ON('message', on_init); } function on_init(msg) { switch (msg.TYPE) { case 'init': CLUSTER_EMIT.id = msg.id; CLUSTER_REQ.id = msg.id; CLUSTER_RES.id = msg.id; THREADS = msg.threads; msg.options.bundling = msg.bundling; if (msg.https) F.https(msg.mode, msg.options); else F.http(msg.mode, msg.options); F.isCluster = true; F.removeListener(msg.TYPE, on_init); break; } }