pot-js
Version:
Process management module
329 lines (260 loc) • 9.38 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _crossSpawn = require('cross-spawn');
var _crossSpawn2 = _interopRequireDefault(_crossSpawn);
var _events = require('events');
var _fkill = require('fkill');
var _fkill2 = _interopRequireDefault(_fkill);
var _cluster = require('cluster');
var _cluster2 = _interopRequireDefault(_cluster);
var _lodash = require('lodash');
var _isWin = require('../utils/isWin');
var _isWin2 = _interopRequireDefault(_isWin);
var _path = require('path');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } /**
* this module is inspired by [respawn](https://github.com/mafintosh/respawn)
*/
const defaultSleep = function defaultSleep(sleep) {
sleep = Array.isArray(sleep) ? sleep : [sleep || 1000];
return function (restarts) {
return sleep[restarts - 1] || sleep[sleep.length - 1];
};
};
// if `cluster` is not set, and the base name of `execPath` includes `node`
// `cluster` will be `true`
const ensureClusterMode = function ensureClusterMode(_ref) {
let cluster = _ref.cluster,
execPath = _ref.execPath;
return (0, _lodash.isBoolean)(cluster) ? cluster : /\bnode\b/.test((0, _path.basename)(execPath));
};
const kill = (() => {
var _ref2 = _asyncToGenerator(function* (pid) {
return (0, _fkill2.default)(pid, { force: _isWin2.default }).catch(_lodash.noop);
});
function kill(_x) {
return _ref2.apply(this, arguments);
}
return kill;
})();
const EventTypes = {
SPAWN: 'spawn',
START: 'start',
RESTART: 'restart',
SLEEP: 'sleep',
CRASH: 'crash',
EXIT: 'exit',
STOP: 'stop',
STDOUT: 'stdout',
STDERR: 'stderr',
WARN: 'warn'
};
class WorkerMonitor extends _events.EventEmitter {
constructor(opts) {
super();
this.id = 0;
this.status = 'stopped';
this.execPath = opts.execPath;
this.execArgv = opts.execArgv;
this.cluster = ensureClusterMode(opts);
this.name = opts.name;
this.cwd = opts.cwd || '.';
this.env = opts.env || {};
this.data = _extends({}, opts.data);
this.uid = opts.uid;
this.gid = opts.gid;
this.pid = 0;
this.ppid = opts.ppid;
this.restarts = -1;
this.crashes = 0;
this.stdio = opts.stdio;
this.stdout = opts.stdout;
this.stderr = opts.stderr;
this.silent = opts.silent;
this.windowsVerbatimArguments = opts.windowsVerbatimArguments;
this.windowsHide = opts.windowsHide !== false;
this.crashed = false;
this.sleep = typeof opts.sleep === 'function' ? opts.sleep : defaultSleep(opts.sleep);
this.maxRestarts = opts.maxRestarts === 0 ? 0 : opts.maxRestarts || -1;
this.kill = opts.kill === false ? false : opts.kill || 30000;
this.child = null;
this.started = null;
this.timeout = null;
}
stop() {
var _this = this;
return _asyncToGenerator(function* () {
if (_this.status === 'stopped' || _this.status === 'stopping') {
return;
}
_this.status = 'stopping';
clearTimeout(_this.timeout);
if (!_this.child) return _this._stopped();
let wait;
const child = _this.child;
const sigkill = (() => {
var _ref3 = _asyncToGenerator(function* () {
yield kill(child.pid);
_this.emit('force-kill');
});
return function sigkill() {
return _ref3.apply(this, arguments);
};
})();
const onexit = function () {
clearTimeout(wait);
};
if (_this.kill !== false) {
wait = setTimeout(sigkill, _this.kill);
_this.child.on('exit', onexit);
}
yield Promise.all([new Promise(function (resolve) {
if (_this.child) _this.child.once('exit', resolve);else resolve();
}), kill(_this.child.pid)]);
})();
}
start() {
var _arguments = arguments,
_this2 = this;
return _asyncToGenerator(function* () {
let options = _arguments.length > 0 && _arguments[0] !== undefined ? _arguments[0] : {};
let restart = options.restart;
if (_this2.status === 'running') {
if (!restart) return false;
yield _this2.stop();
}
let clock = 60000;
const env = Object.assign({}, process.env, _this2.env);
const loop = function () {
_this2.restarts++;
let worker;
let child;
const commomOptions = {
cwd: _this2.cwd,
uid: _this2.uid,
gid: _this2.gid,
stdio: _this2.stdio,
silent: _this2.silent,
windowsVerbatimArguments: _this2.windowsVerbatimArguments,
windowsHide: _this2.windowsHide
};
if (_this2.cluster) {
_cluster2.default.setupMaster(_extends({}, commomOptions, {
execPath: _this2.execPath,
execArgv: _this2.execArgv
}));
worker = _cluster2.default.fork(env);
child = worker.process;
} else {
child = (0, _crossSpawn2.default)(_this2.execPath, _this2.execArgv, _extends({}, commomOptions, {
env
}));
}
_this2.started = new Date();
_this2.status = 'running';
_this2.child = child;
_this2.pid = child.pid;
_this2.data.pid = _this2.pid;
_this2.data.ppid = _this2.ppid;
_this2.emit(EventTypes.SPAWN, child);
child.setMaxListeners(0);
if (child.stdout) {
child.stdout.on('data', function (data) {
_this2.emit(EventTypes.STDOUT, data);
});
if (_this2.stdout) {
child.stdout.pipe(_this2.stdout);
}
}
if (child.stderr) {
child.stderr.on('data', function (data) {
_this2.emit(EventTypes.STDERR, data);
});
if (_this2.stderr) {
child.stderr.pipe(_this2.stderr);
}
}
child.on('message', function (message) {
_this2.emit('message', message);
});
const clear = function () {
if (_this2.child !== child) return false;
_this2.child = null;
_this2.pid = 0;
return true;
};
child.on('error', function (err) {
_this2.emit(EventTypes.WARN, err); // too opionated? maybe just forward err
if (!clear()) return;
if (_this2.status === 'stopping') return _this2._stopped();
_this2.crashes++;
_this2._crash();
});
child.on('exit', function (code, signal) {
_this2.emit(EventTypes.EXIT, code, signal);
if (!clear()) return;
if (_this2.status === 'stopping') return _this2._stopped();
clock -= Date.now() - (_this2.started ? _this2.started.getTime() : 0);
if (clock <= 0) {
clock = 60000;
}
_this2.crashes++;
if (_this2.restarts > _this2.maxRestarts && _this2.maxRestarts !== -1) {
return _this2._crash();
}
_this2.status = 'sleeping';
_this2.emit(EventTypes.SLEEP);
const restartTimeout = _this2.sleep(_this2.restarts);
_this2.timeout = setTimeout(loop, restartTimeout);
});
const emitReady = function () {
_this2.emit(_this2.restarts ? EventTypes.RESTART : EventTypes.START);
};
worker ? worker.on('online', emitReady) : emitReady();
};
clearTimeout(_this2.timeout);
loop();
return _this2.status === 'running';
})();
}
restart() {
var _this3 = this;
return _asyncToGenerator(function* () {
return _this3.start({ restart: true });
})();
}
toJSON() {
return _extends({}, this.data, {
pid: this.pid,
ppid: this.ppid,
monitor: {
instanceId: this.id,
name: this.name,
status: this.status,
started: this.started,
pid: this.ppid,
crashes: this.crashes,
command: this.command,
cwd: this.cwd,
env: this.env
}
});
}
_crash() {
if (this.status !== 'running') return;
this.status = 'crashed';
this.emit(EventTypes.CRASH);
if (this.status === 'crashed') this._stopped();
}
_stopped() {
if (this.status === 'stopped') return;
if (this.status !== 'crashed') this.status = 'stopped';
this.started = null;
this.emit(EventTypes.STOP);
}
}
exports.default = WorkerMonitor;
WorkerMonitor.EventTypes = EventTypes;