kibana-123
Version:
Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elastic
167 lines (134 loc) • 4.48 kB
JavaScript
import _ from 'lodash';
import cluster from 'cluster';
import { resolve } from 'path';
import { EventEmitter } from 'events';
import { BinderFor, fromRoot } from '../../utils';
let cliPath = fromRoot('src/cli');
let baseArgs = _.difference(process.argv.slice(2), ['--no-watch']);
let baseArgv = [process.execPath, cliPath].concat(baseArgs);
cluster.setupMaster({
exec: cliPath,
silent: false
});
let dead = fork => {
return fork.isDead() || fork.killed;
};
module.exports = class Worker extends EventEmitter {
constructor(opts) {
opts = opts || {};
super();
this.log = opts.log;
this.type = opts.type;
this.title = opts.title || opts.type;
this.watch = (opts.watch !== false);
this.startCount = 0;
// status flags
this.online = false; // the fork can accept messages
this.listening = false; // the fork is listening for connections
this.crashed = false; // the fork crashed
this.changes = [];
this.forkBinder = null; // defined when the fork is
this.clusterBinder = new BinderFor(cluster);
this.processBinder = new BinderFor(process);
let argv = _.union(baseArgv, opts.argv || []);
this.env = {
kbnWorkerType: this.type,
kbnWorkerArgv: JSON.stringify(argv)
};
}
onExit(fork, code) {
if (this.fork !== fork) return;
// we have our fork's exit, so stop listening for others
this.clusterBinder.destroy();
// our fork is gone, clear our ref so we don't try to talk to it anymore
this.fork = null;
this.forkBinder = null;
this.online = false;
this.listening = false;
this.emit('fork:exit');
this.crashed = code > 0;
if (this.crashed) {
this.emit('crashed');
this.log.bad(`${this.title} crashed`, 'with status code', code);
if (!this.watch) process.exit(code);
} else {
// restart after graceful shutdowns
this.start();
}
}
onChange(path) {
if (!this.watch) return;
this.changes.push(path);
this.start();
}
async shutdown() {
if (this.fork && !dead(this.fork)) {
// kill the fork
this.fork.process.kill();
this.fork.killed = true;
// stop listening to the fork, it's just going to die
this.forkBinder.destroy();
// we don't need to react to process.exit anymore
this.processBinder.destroy();
// wait until the cluster reports this fork has exitted, then resolve
await new Promise(resolve => this.once('fork:exit', resolve));
}
}
parseIncomingMessage(msg) {
if (!_.isArray(msg)) return;
this.onMessage(...msg);
}
onMessage(type, data) {
switch (type) {
case 'WORKER_BROADCAST':
this.emit('broadcast', data);
break;
case 'WORKER_LISTENING':
this.listening = true;
this.emit('listening');
break;
}
}
onOnline() {
this.online = true;
this.emit('fork:online');
this.crashed = false;
}
onDisconnect() {
this.online = false;
this.listening = false;
}
flushChangeBuffer() {
let files = _.unique(this.changes.splice(0));
let prefix = files.length > 1 ? '\n - ' : '';
return files.reduce(function (list, file) {
return `${list || ''}${prefix}"${file}"`;
}, '');
}
async start() {
if (this.fork) {
// once "exit" event is received with 0 status, start() is called again
this.shutdown();
await new Promise(cb => this.once('online', cb));
return;
}
if (this.changes.length) {
this.log.warn(`restarting ${this.title}`, `due to changes in ${this.flushChangeBuffer()}`);
}
else if (this.startCount++) {
this.log.warn(`restarting ${this.title}...`);
}
this.fork = cluster.fork(this.env);
this.forkBinder = new BinderFor(this.fork);
// when the fork sends a message, comes online, or looses it's connection, then react
this.forkBinder.on('message', msg => this.parseIncomingMessage(msg));
this.forkBinder.on('online', () => this.onOnline());
this.forkBinder.on('disconnect', () => this.onDisconnect());
// when the cluster says a fork has exitted, check if it is ours
this.clusterBinder.on('exit', (fork, code) => this.onExit(fork, code));
// when the process exits, make sure we kill our workers
this.processBinder.on('exit', () => this.shutdown());
// wait for the fork to report it is online before resolving
await new Promise(cb => this.once('fork:online', cb));
}
};