manticore
Version:
Mythical multi-process worker pool
189 lines (188 loc) • 6.44 kB
JavaScript
'use strict';
var __extends = this.__extends || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
var events = require('events');
var child_process = require('child_process');
var typeOf = require('type-detect');
var through2 = require('through2');
var JSONStream = require('JSONStream');
var lib = require('./lib');
var jobI = 0;
var Job = (function () {
function Job(task, params, callback) {
this.attempts = 1;
this.send = false;
this.id = 'job.' + jobI++;
this.task = task;
this.params = params;
this.callback = callback;
}
Job.prototype.toString = function () {
return this.id + '-' + this.task;
};
return Job;
})();
exports.Job = Job;
var Worker = (function (_super) {
__extends(Worker, _super);
function Worker(options) {
var _this = this;
_super.call(this);
this.ready = false;
this.jobs = Object.create(null);
this._activeCount = 0;
this.options = options;
this.idleTimer = new lib.BumpTimeout(this.options.idleTimeout, function () {
if (_this.activeCount === 0) {
_this.status('idle timeout');
_this.kill();
} else {
_this.idleTimer.next();
}
});
var args = [
this.options.worker
];
var opts = {
cwd: process.cwd(),
stdio: ['ignore', process.stdout, process.stderr, 'pipe', 'pipe', 'ipc']
};
this.child = child_process.spawn(process.execPath, args, opts);
this.id = 'worker.' + this.child.pid;
this.write = JSONStream.stringify(false);
this.write.pipe(through2()).pipe(this.child.stdio[lib.WORK_TO_CLIENT]);
this.read = this.child.stdio[lib.CLIENT_TO_WORK].pipe(JSONStream.parse(true));
this.read.on('data', function (msg) {
if (msg.type === lib.TASK_RESULT) {
if (msg.id in _this.jobs) {
var job = _this.jobs[msg.id];
_this._activeCount--;
delete _this.jobs[msg.id];
// upfix Error
if (msg.error) {
msg.error.toString = function () {
return msg.message;
};
}
_this.status('completed', job, Math.round(msg.duration) + 'ms');
job.callback(msg.error, msg.result);
_this.emit(lib.TASK_RESULT, job);
_this.idleTimer.next();
}
}
});
this.child.stdio[lib.WORK_TO_CLIENT].on('close', function () {
_this.status('client closed WORK_TO_CLIENT stream');
_this.kill();
});
this.child.stdio[lib.CLIENT_TO_WORK].on('close', function () {
_this.status('client closed CLIENT_TO_WORK stream');
_this.kill();
});
this.child.send({ a: 1 }, null);
var onMessage = function (msg) {
if (msg.type === lib.WORKER_READY) {
_this.status('ready');
_this.ready = true;
_this.flushWaiting();
} else {
_this.status('unknown message', typeOf(msg), JSON.stringify(msg, null, 3));
}
};
var onError = function (error) {
_this.status('job error', error);
_this.kill();
};
var onClose = function (code) {
_this.status('job close', code);
_this.kill();
};
this.child.on('message', onMessage);
this.child.on('error', onError);
this.child.on('close', onClose);
this.kill = function () {
_this.status('kill');
_this.write.removeAllListeners();
_this.read.removeAllListeners();
if (_this.child) {
_this.child.stdio[lib.WORK_TO_CLIENT].removeAllListeners();
_this.child.stdio[lib.CLIENT_TO_WORK].removeAllListeners();
_this.child.removeAllListeners();
_this.child.kill('SIGKILL');
_this.child = null;
}
_this.ready = false;
_this.emit(lib.WORKER_DOWN);
for (var id in _this.jobs) {
var job = _this.jobs[id];
_this._activeCount--;
delete _this.jobs[id];
_this.emit(lib.TASK_ABORT, job);
}
_this.idleTimer.clear();
_this.removeAllListeners();
};
}
Worker.prototype.run = function (job) {
if (!this.child) {
this.emit(lib.TASK_ABORT, job);
this.kill();
return;
}
this.jobs[job.id] = job;
this._activeCount++;
this.idleTimer.next();
if (this.ready) {
this.send(job);
}
};
Worker.prototype.send = function (job) {
if (job.send) {
return;
}
this.status('started', job);
var msg = {
type: lib.TASK_RUN,
task: job.task,
id: job.id,
params: job.params
};
job.send = true;
// this.child.send(msg, null);
this.write.write(msg);
};
Worker.prototype.flushWaiting = function () {
this.idleTimer.next();
for (var id in this.jobs) {
var job = this.jobs[id];
if (!job.send) {
this.send(job);
}
}
};
Worker.prototype.status = function () {
var message = [];
for (var _i = 0; _i < (arguments.length - 0); _i++) {
message[_i] = arguments[_i + 0];
}
if (this.options.emit) {
this.emit(lib.STATUS, this + '; ' + message.join('; '));
}
};
Object.defineProperty(Worker.prototype, "activeCount", {
get: function () {
return this._activeCount;
},
enumerable: true,
configurable: true
});
Worker.prototype.toString = function () {
return this.id + ' <' + (this.child ? (this.activeCount + '/' + this.options.paralel) : 'killed') + '>';
};
return Worker;
})(events.EventEmitter);
exports.Worker = Worker;