manticore
Version:
Mythical multi-process worker pool
208 lines (207 loc) • 6.03 kB
JavaScript
;
var fs = require('fs');
var assertMod = require('assert');
var typeOf = require('type-detect');
var JSONStream = require('JSONStream');
var lib = require('./lib');
var through2 = require('through2');
var state = {
id: 'worker.' + process.pid,
tasks: Object.create(null),
active: Object.create(null),
closing: false,
isInit: false
};
var read = null;
var write = null;
var objects = null;
function init() {
if (state.isInit || state.closing) {
return;
}
state.isInit = true;
var fdI = 0;
var fdCheck = setInterval(function () {
try {
fs.fstatSync(lib.WORK_TO_CLIENT);
fs.fstatSync(lib.CLIENT_TO_WORK);
} catch (e) {
if (fdI++ > 10) {
bail('cannot locate file descriptors');
}
return;
}
clearInterval(fdCheck);
read = fs.createReadStream(null, { fd: lib.WORK_TO_CLIENT });
read.on('error', function (err) {
bail('client intput stream errored', err);
});
read.on('close', function () {
bail('client intput stream unexpectedly closed');
});
read.on('end', function () {
bail('client intput stream unexpectedly ended');
});
write = JSONStream.stringify(false);
write.pipe(through2()).pipe(fs.createWriteStream(null, { fd: lib.CLIENT_TO_WORK }));
write.on('error', function (err) {
state.closing = true;
bail('object input stream errored', err);
});
objects = read.pipe(JSONStream.parse(true));
objects.on('data', function (msg) {
if (msg.type === lib.TASK_RUN) {
process.nextTick(function () {
runFunc(msg);
});
} else {
console.error('client unknown data');
console.error(msg);
}
});
objects.on('error', function (err) {
state.closing = true;
bail('object input stream errored', err);
});
process.send({ type: lib.WORKER_READY });
}, 10);
process.on('uncaughtException', function (err) {
bail('uncaughtException', err);
});
}
function bail(message) {
var messages = [];
for (var _i = 0; _i < (arguments.length - 1); _i++) {
messages[_i] = arguments[_i + 1];
}
console.error.apply(console, arguments);
abortAll();
if (read) {
read.removeAllListeners();
}
if (write) {
write.removeAllListeners();
}
if (objects) {
objects.removeAllListeners();
}
process.exit(1);
}
function abortAll() {
if (!state.closing) {
for (var id in state.active) {
var info = state.active[id];
info.res.type = lib.TASK_ABORT;
info.send();
}
}
}
function defineTask(name, func) {
lib.assertType(name, 'string', 'name');
lib.assertType(func, 'function', 'func');
assertMod(!(name in state.tasks), 'cannot redefine task ' + name + '');
state.tasks[name] = func;
}
function registerTasks(map) {
if (!state.isInit) {
init();
}
if (typeOf(map) === 'array') {
map.forEach(exports.registerTask);
} else if (typeOf(map) === 'object') {
Object.keys(map).forEach(function (name) {
var func = map[name];
lib.assertType(func, 'function', name);
defineTask(name, func);
});
}
}
exports.registerTasks = registerTasks;
function registerTask(arg, func) {
if (!state.isInit) {
init();
}
if (typeOf(arg) === 'function') {
func = arg;
arg = arg.name;
}
defineTask(arg, func);
}
exports.registerTask = registerTask;
function runFunc(msg) {
var res = {
worker: state.id,
type: lib.TASK_RESULT,
id: msg.id,
error: null,
result: null,
duration: null
};
var start = Date.now();
var result = null;
var hasSent = false;
var info = {
msg: msg,
res: res,
hasSent: function () {
return hasSent;
},
send: function () {
delete state.active[msg.id];
if (!hasSent) {
// errors don't serialise well
if (typeOf(res.error) === 'object') {
var err = {
name: res.error.name,
message: res.error.message,
stack: res.error.stack,
// add some bling
code: res.error.code,
actual: res.error.actual,
expected: res.error.expected
};
res.error = err;
}
res.duration = Date.now() - start;
hasSent = true;
process.nextTick(function () {
write.write(res);
});
}
}
};
state.active[msg.id] = info;
if (!(msg.task in state.tasks)) {
res.type = lib.ERROR;
res.error = new Error('unknown task ' + msg.task);
info.send();
}
try {
result = state.tasks[msg.task](msg.params, function (error, result) {
res.error = error;
res.result = result;
info.send();
});
if (typeOf(result) !== 'undefined' && !hasSent) {
if (typeOf(result.then) === 'function') {
result.then(function (result) {
res.result = result;
info.send();
}, function (err) {
res.error = err;
info.send();
});
} else {
res.result = result;
info.send();
}
}
} catch (e) {
res.error = e;
info.send();
}
}
process.on('uncaughtException', function (e) {
bail(e);
throw e;
});