UNPKG

uow

Version:

A unit-of-work queue.

325 lines (243 loc) 7.38 kB
/** * This is the Queue interface. * ============================ * - Queue needs to emit "task::ready" events when a task becomes ready. * - @TODO handle worker disconnection events. * - @TODO send out alive checks? * - @TODO remove the local lock store registry. */ var uuid = require('uuid'); var later = require('later'); var inherits = require('util').inherits; var isArray = require('util').isArray; var EventEmitter = require('events').EventEmitter; var Task = require('./task.js'); function setTaskResponse(response, task) { if(!task.response) { task.response = []; } if(!isArray(task.response)) { task.response = [ task.response ]; } task.response.push(response); return task; } function taskUpdate(self, status, eventName) { return function(workerId, taskId, response) { return self.taskLock(workerId, taskId) .then(function(task) { task.status = status; task = setTaskResponse(response, task); return self.store.updateTask(workerId, task); }) .then(function(task) { self.emit('task::' + eventName, task); return task; }); }; } /** * Constructor for a Queue object. * ------------------------------- * @constructor {Queue} * @param {Store} store */ function Queue(store) { // optional new pattern. if(! (this instanceof Queue) ) { return new Queue(store); } this.registry = {}; this.store = store; var workerNotify = this.workerNotify.bind(this); this.store.on('ready', function(task) { workerNotify(task); }); /** * Notify the Queue that the task has finished processing. * ------------------------------------------------------- * - emit "task::finished" * @param {string} workerId * @param {string} taskId * @param {Object} response - any data to add as the task response. * @return {Promise::task} */ this.taskComplete = taskUpdate(this, Task.STATUS.SUCCESS, 'finished'); /** * Notify the Queue that the task has errored. * ------------------------------------------- * - emit "task::failed" * @param {stirng} workerId * @param {string} taskId * @param {Object} response - any data to add as the task response. * @return {Promise::Task} */ this.taskFail = taskUpdate(this, Task.STATUS.FAILED, 'failed'); /** * Notify the Queue that the task has been cancelled. * -------------------------------------------------- * - emit "task::cancelled" * @param {string} workerId * @param {string} taskId * @param {Object} response - any data to add as the task response. * @return {Promise::Task} */ this.taskCancel = taskUpdate(this, Task.STATUS.CANCELLED, 'cancelled'); } inherits(Queue, EventEmitter); /** * Retrieve a task object by identifier. * ------------------------------------- * @param {string} taskId * @return {Promise::task} * @throws {LockError} */ Queue.prototype.taskGetById = function(taskId) { return this.store.getTaskById(taskId) .then(function(stored_task) { return Task.dePickle(stored_task, this); }.bind(this)); }; /** * Add a task object into the queue. * --------------------------------- * @param {Task} task * @return {Promise::Task} */ Queue.prototype.taskAdd = function(task) { return this.store.createTask(task); }; /** * Attempt to acquire a lock for the given task. * --------------------------------------------- * - emit "task::locked" * - lock is encrypted using the worker key. * - lock relies upon the Queue store to ensure atomicity. * @param {string} workerId * @param {string} taskId * @return {Promise::Task} */ Queue.prototype.taskLock = function (workerId, taskId) { return this.taskGetById(taskId) .then(function(task) { return this.store.lockTask(workerId, task.id); }.bind(this)) .then(function(task) { this.emit('task::locked', task); return task; }.bind(this)); }; /** * Attempt to release the lock on a task. * -------------------------------------- * @param {string} workerId * @param {string} taskId * @return {Promise::Task} * @throws {LockError} */ Queue.prototype.taskUnlock = function(workerId, taskId) { return this.store.unlockTask(workerId, taskId) .then(function(task) { this.emit('task::unlocked', task); return task; }.bind(this)); }; /** * Notify the Queue that the task instance is processed. * ----------------------------------------------------- * - emit "task::finished" * @param {string} workerId * @param {string} taskId * @param {Object} response - any data to add as the task response. * @return {Promise::Task} * @throws {TypeError} */ Queue.prototype.taskYield = function(workerId, taskId, response) { return this.taskLock(workerId, taskId) .then(function(task) { if(!task.schedule) { throw new TypeError('TaskNotScheduled'); } // calculate next schedule. var schedule = later.parse.text(task.schedule); var next = later.schedule(schedule).next(2); task.after = next[1].getTime(); task = setTaskResponse(response, task); return this.store.updateTask(workerId, task); }.bind(this)) .then(function(task) { this.emit('task::finished', task); return task; }.bind(this)); }; /** * Notify the Queue that the task processing has progressed. * --------------------------------------------------------- * - emit "task::progress" * @param {string} workerId * @param {string} taskId * @param {ProgressObject} progress * @return {void} */ Queue.prototype.taskProgress = function(workerId, taskId, progress) { return this.taskLock(workerId, taskId) .then(function (task) { task.progress = progress; return this.store.updateTask(workerId, task); }.bind(this)) .then(function(task) { this.emit('task::progress', task); return task; }.bind(this)); }; /** * Register a task worker * ----------------------- * - emit "worker::registered" * - Returns a key (UUID v4) to each registered worker. * @param {Array.<string>} taskTypes * @param {Worker} worker * @return {string} */ Queue.prototype.workerRegister = function(taskTypes, worker) { var key = uuid.v4(); this.registry[key] = { worker: worker, types: taskTypes }; var taskHandler = (function(handler, typeList) { return function(taskType, taskId) { if(typeList.indexOf(taskType) > -1) { handler(taskType, taskId); } }; }(worker.taskReady.bind(worker), taskTypes)); this.on('task::ready', taskHandler); return key; }; /** * Notify workers that there is a task ready to be processed. * ---------------------------------------------------------- * @param {Task} task * @return {Queue} * @throws {TypeError} */ Queue.prototype.workerNotify = function(task) { if(!task.id) { throw new TypeError('TaskNotPersisted'); } if(!task.name) { throw new TypeError('TaskTypeMissing'); } this.emit('task::ready', task.name, task.id); return this; }; /** * Return a list of current workers with their current status. * ----------------------------------------------------------- * @return {QueueStatus} */ Queue.prototype.workerStatus = function() { throw new Error('NotImplemented'); }; module.exports = Queue;