UNPKG

fivebeans

Version:

beanstalkd client & worker daemon for node.

304 lines (264 loc) 6.55 kB
var _ = require('lodash'), Beanstalk = require('./client'), events = require('events'), util = require('util') ; /* Events emitted: error: payload is error; execution is halted close: no payload warning: payload is object with error information; execution continues info: payload is object with action info started: no payload stopped: no payload job.reserved: job has been reserved; payload is job id job.handled: payload is object with job info job.deleted: payload is jobid job.buried: payload is jobid */ var FiveBeansWorker = function(options) { events.EventEmitter.call(this); this.id = options.id; this.host = options.host; this.port = options.port; this.handlers = options.handlers; this.ignoreDefault = options.ignoreDefault; this.stopped = false; this.timeout = options.timeout || 10; this.client = null; }; util.inherits(FiveBeansWorker, events.EventEmitter); FiveBeansWorker.prototype.start = function(tubes) { var self = this; this.stopped = false; this.on('next', this.doNext.bind(this)); function finishedStarting() { self.emit('started'); self.emit('next'); } this.client = new Beanstalk(this.host, this.port); this.client.on('connect', function() { self.emitInfo('connected to beanstalkd at ' + self.host + ':' + self.port); self.watch(tubes, function() { if (tubes && tubes.length && self.ignoreDefault) { self.ignore(['default'], function() { finishedStarting(); }); } else { finishedStarting(); } }); }); this.client.on('error', function(err) { self.emitWarning({message: 'beanstalkd connection error', error: err}); self.emit('error', err); }); this.client.on('close', function() { self.emitInfo('beanstalkd connection closed'); self.emit('close'); }); this.client.connect(); }; FiveBeansWorker.prototype.watch = function(tubes, callback) { var self = this; var tube; if (tubes && (tube = tubes[0])) { self.emitInfo('watching tube ' + tube); self.client.watch(tube, function(err) { if (err) self.emitWarning({ message: 'error watching tube', tube: tube, error: err }); self.watch(tubes.slice(1), callback); }); } else callback(); }; FiveBeansWorker.prototype.ignore = function(tubes, callback) { var self = this; var tube; if (tubes && (tube = tubes[0])) { self.emitInfo('ignoring tube ' + tube); self.client.ignore(tube, function(err) { if (err) self.emitWarning({ message: 'error ignoring tube', tube: tube, error: err }); self.ignore(tubes.slice(1), callback); }); } else callback(); }; FiveBeansWorker.prototype.stop = function() { this.emitInfo('stopping...'); this.stopped = true; }; FiveBeansWorker.prototype.doNext = function() { var self = this; if (self.stopped) { self.client.end(); self.emitInfo('stopped'); self.emit('stopped'); return; } self.client.reserve_with_timeout(self.timeout, function(err, jobID, payload) { if (err) { if ('TIMED_OUT' !== err) self.emitWarning({ message: 'error reserving job', error: err }); self.emit('next'); } else { self.emit('job.reserved', jobID); var job = null; try { job = JSON.parse(payload.toString()); } catch (e) { self.emitWarning({ message: 'parsing job JSON', id: jobID, error: e }); } if (!job || !_.isObject(job)) self.buryAndMoveOn(jobID); else if (job instanceof Array) self.runJob(jobID, job[1]); else self.runJob(jobID, job); } }); }; FiveBeansWorker.prototype.runJob = function(jobID, job) { var self = this; var handler = this.lookupHandler(job.type); if (job.type === undefined) { self.emitWarning({ message: 'no job type', id: jobID, job: job }); self.deleteAndMoveOn(jobID); } else if (!handler) { self.emitWarning({ message: 'no handler found', id: jobID, type: job.type }); self.buryAndMoveOn(jobID); } else { self.callHandler(handler, jobID, job.payload); } }; FiveBeansWorker.prototype.lookupHandler = function(type) { return this.handlers[type]; }; // issue #25 FiveBeansWorker.prototype.callHandler = function callHandler(handler, jobID, jobdata) { if (handler.work.length === 3) { var patchedHandler = { work: function(payload, callback) { return handler.work(jobID, payload, callback); } }; FiveBeansWorker.prototype.doWork.call(this, patchedHandler, jobID, jobdata); } else { // pass it right on through FiveBeansWorker.prototype.doWork.apply(this, arguments); } }; FiveBeansWorker.prototype.doWork = function doWork(handler, jobID, jobdata) { var self = this; var start = new Date().getTime(); this.currentJob = jobID; this.currentHandler = handler; try { handler.work(jobdata, function(action, delay) { var elapsed = new Date().getTime() - start; self.emit('job.handled', { id: jobID, type: handler.type, elapsed: elapsed, action: action }); switch (action) { case 'success': self.deleteAndMoveOn(jobID); break; case 'release': self.releaseAndMoveOn(jobID, delay); break; case 'bury': self.buryAndMoveOn(jobID); break; default: self.buryAndMoveOn(jobID); break; } }); } catch (e) { self.emitWarning({ message: 'exception in job handler', id: jobID, handler: handler.type, error: e }); self.buryAndMoveOn(jobID); } }; FiveBeansWorker.prototype.buryAndMoveOn = function(jobID) { var self = this; self.client.bury(jobID, Beanstalk.LOWEST_PRIORITY, function(err) { if (err) self.emitWarning({ message: 'error burying', id: jobID, error: err }); self.emit('job.buried', jobID); self.emit('next'); }); }; FiveBeansWorker.prototype.releaseAndMoveOn = function(jobID, delay) { var self = this; if (delay === undefined) delay = 30; self.client.release(jobID, Beanstalk.LOWEST_PRIORITY, delay, function(err) { if (err) self.emitWarning({ message: 'error releasing', id: jobID, error: err }); self.emit('job.released', jobID); self.emit('next'); }); }; FiveBeansWorker.prototype.deleteAndMoveOn = function(jobID) { var self = this; self.client.destroy(jobID, function(err) { if (err) self.emitWarning({ message: 'error deleting', id: jobID, error: err }); self.emit('job.deleted', jobID); self.emit('next'); }); }; FiveBeansWorker.prototype.emitInfo = function(message) { this.emit('info', { clientid: this.id, message: message, }); }; FiveBeansWorker.prototype.emitWarning = function(data) { data.clientid = this.id; this.emit('warning', data); }; module.exports = FiveBeansWorker;