UNPKG

forq

Version:

manage forked processes using async.queue

217 lines (176 loc) 5.08 kB
/* jshint loopfunc: true */ var os = require('os'); var fork = require('child_process').fork; var debug = require('debug')('task'); var _ = require('lodash'); var Errors = require('./errors'); var DEFAULT_CONCURRENCY = 3; var NUM_CPUS = os.cpus().length; var DEFAULT_WORKER_OPTIONS = { stdio: [0, 0, 'pipe' ] }; var DEFAULT_TIMEOUT = 60000; var DEFAULT_POLLING_FREQUENCY = 1000; function __attachEventListeners () { var standard_fork_event_names = [ 'error', 'exit', 'disconnect', 'message' ]; var f = this; var events = this.events; var w = this.work; var eventName; var customEvents = _.each(events, function(v, k) { if ( !_.includes(standard_fork_event_names, k) ) { return v; } }); var standardEvents = _.each(events, function(v, k) { if ( _.includes(standard_fork_event_names, k) ) { return v; } }); function defaultEndHandler () { if (f.connected) { f.send({ event: 'finished', data: {} }); } } for (eventName in standardEvents) { f.on(eventName, standardEvents[eventName]); } for (eventName in customEvents) { f.addListener('message', function (msg){ if (msg.event && msg.event === eventName) { customEvents[eventName].apply(f, [ msg.data ]); } }); } // add default events f.addListener('exit', function(code, err){ var errorObj, d; if (code !== 0) { errorObj = new Errors.ForkError('fork "'+f.id+'" threw an error with code '+code); f.emit('error', errorObj); // emit a general task error f.queue.emit('taskError', errorObj ); // emit a namespaced error for the individual task f.queue.emit('taskError:'+f.id, errorObj); } if (f.connected) { // signal termination via pseudo-event // send error obj if one exists if (errorObj) { d = errorObj.toObject(); } else { errorObj = null; d = {}; } f.send({ event: 'finished', data: d }); } else { // terminate f.terminate(errorObj); } }); f.addListener('disconnect', function(){ // terminate f.terminate(); }); f.addListener('message', function(){ var msg = arguments[0]; if (msg.event && msg.event === 'finished') { debug("finished"); f.terminate(msg.data); // soft error handling } else if (msg.event && msg.event === 'softError') { var er = new Errors.SoftError(msg.data); // emit a normal error event on the fork passing in softError as object f.emit('error', er); f.terminate( er ); } }); return f; } function __assignForkId () { var w = this.work; function hid(){ return Date.now().toString('16').slice(2); } var id = w.id ? w.id : hid(); if (!this.queue.forksHash[id]) { this.id = id; } else { this.id = hid(); } } function __setForkTimer (f) { return setInterval(function(){ var currentTime = Date.now(); if (currentTime - f.startTime > f.killTimeout) { clearInterval(f.timer); f.terminate(new Error('Time out')); if (f.connected && f.kill) { f.kill(); } } }, f.pollFrequency || DEFAULT_POLLING_FREQUENCY ); } function Task (w, q) { var ctx = this; var self = q; var d = q.domain; this.completed = false; this.fn = function fn (done) { function terminate (err) { var e = err ? err : null; clearInterval(this.timer); if (!this.terminated) { this.task.completed = true; debug('terminated worker '+this.id); this.terminated = true; if (this.connected) { this.emit('terminated'); } if (this.cb) { this.cb(e); } } } var fork_args = [w.path, w.args]; if (w.opts) { fork_args.push(w.opts); } else { // use default worker options fork_args.push(DEFAULT_WORKER_OPTIONS); } // create the forked process var f = fork.apply(this, fork_args); // expose task context in fork f.task = ctx; // access fork object from context ctx.fn.fork = f; // store start time of fork f.startTime = Date.now(); // access queue from fork f.queue = self; // attach work params to fork f.work = w; // set killTimeout of fork f.killTimeout = w.killTimeout || DEFAULT_TIMEOUT; f.pollFrequency = w.pollFrequency || DEFAULT_POLLING_FREQUENCY; // store callback f.cb = done; // store events from queue f.events = self.events || {}; // add fork to the domain d.add(f); // set terminate method f.terminate = terminate.bind(f); // set timer for timeouts, etc. f.timer = __setForkTimer(f); // assign event handlers __attachEventListeners.apply(f); // assign fork id __assignForkId.apply(f); // expose id in task context ctx.id = f.id; f.terminated = false; // add to forks hash self.forksHash[f.id] = f; // create empty array to hold errors for this fork self.errors[f.id] = []; // add to forks array self.forks.push(f); }; } module.exports = Task;