egg-cluster
Version:
cluster manager for egg
151 lines (130 loc) • 3.71 kB
JavaScript
;
const cluster = require('cluster');
const cfork = require('cfork');
const sendmessage = require('sendmessage');
const gracefulExit = require('graceful-process');
const { BaseAppWorker, BaseAppUtils } = require('../../base/app');
const terminate = require('../../../terminate');
class AppWorker extends BaseAppWorker {
get id() {
return this.instance.id;
}
get workerId() {
return this.instance.process.pid;
}
get state() {
return this.instance.state;
}
get exitedAfterDisconnect() {
return this.instance.exitedAfterDisconnect;
}
get exitCode() {
return this.instance.exitCode;
}
send(...args) {
sendmessage(this.instance, ...args);
}
clean() {
this.instance.removeAllListeners();
}
static on(event, callback) {
process.on(event, callback);
}
static send(data) {
process.send(data);
}
static kill() {
process.exitCode = 1;
process.kill(process.pid);
}
static gracefulExit(options) {
gracefulExit(options);
}
}
class AppUtils extends BaseAppUtils {
fork() {
this.startTime = Date.now();
this.startSuccessCount = 0;
const args = [ JSON.stringify(this.options) ];
this.log('[master] start appWorker with args %j (process)', args);
cfork({
exec: this.getAppWorkerFile(),
args,
silent: false,
count: this.options.workers,
// don't refork in local env
refork: this.isProduction,
windowsHide: process.platform === 'win32',
});
let debugPort = process.debugPort;
cluster.on('fork', worker => {
const appWorker = new AppWorker(worker);
this.emit('worker_forked', appWorker);
appWorker.disableRefork = true;
worker.on('message', msg => {
if (typeof msg === 'string') {
msg = {
action: msg,
data: msg,
};
}
msg.from = 'app';
this.messenger.send(msg);
});
this.log('[master] app_worker#%s:%s start, state: %s, current workers: %j',
appWorker.id, appWorker.workerId, appWorker.state, Object.keys(cluster.workers));
// send debug message, due to `brk` scence, send here instead of app_worker.js
if (this.options.isDebug) {
debugPort++;
this.messenger.send({
to: 'parent',
from: 'app',
action: 'debug',
data: {
debugPort,
pid: appWorker.workerId,
},
});
}
});
cluster.on('disconnect', worker => {
const appWorker = new AppWorker(worker);
this.logger.info('[master] app_worker#%s:%s disconnect, suicide: %s, state: %s, current workers: %j',
appWorker.id, appWorker.workerId, appWorker.exitedAfterDisconnect, appWorker.state, Object.keys(cluster.workers));
});
cluster.on('exit', (worker, code, signal) => {
const appWorker = new AppWorker(worker);
this.messenger.send({
action: 'app-exit',
data: {
workerId: appWorker.workerId,
code,
signal,
},
to: 'master',
from: 'app',
});
});
cluster.on('listening', (worker, address) => {
const appWorker = new AppWorker(worker);
this.messenger.send({
action: 'app-start',
data: {
workerId: appWorker.workerId,
address,
},
to: 'master',
from: 'app',
});
});
return this;
}
async kill(timeout) {
await Promise.all(Object.keys(cluster.workers).map(id => {
const worker = cluster.workers[id];
worker.disableRefork = true;
return terminate(worker, timeout);
}));
}
}
module.exports = { AppWorker, AppUtils };