UNPKG

manticore

Version:

Mythical multi-process worker pool

208 lines (207 loc) 6.03 kB
'use strict'; 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; });