node-resque
Version:
an opinionated implementation of resque in node
158 lines (145 loc) • 4.42 kB
JavaScript
// TODO: Locking like ruby does
var EventEmitter = require('events').EventEmitter;
var util = require("util");
var connection = require(__dirname + "/connection.js").connection;
var queue = require(__dirname + "/queue.js").queue;
var scheduler = function(options, jobs, callback){
var self = this;
var defaults = self.defaults();
for(var i in defaults){
if(options[i] === null || options[i] === undefined){
options[i] = defaults[i];
}
}
if(typeof jobs == 'function' && !callback){
callback = jobs;
jobs = {};
}
self.options = options;
self.connection = new connection(options.connection);
self.running = false;
self.connection.connect(function(err){
self.queue = new queue({connection: options.connection}, jobs, function(err){
if(typeof callback === 'function'){ callback(err); }
});
});
};
util.inherits(scheduler, EventEmitter);
scheduler.prototype.defaults = function(){
var self = this;
return {
timeout: 5000,
};
};
scheduler.prototype.start = function() {
var self = this;
if (!self.running) {
self.emit('start');
self.running = true;
self.processing = false;
self.timer = setTimeout((function() {
self.poll();
}), self.options.timeout);
}
};
scheduler.prototype.end = function(callback) {
var self = this;
self.running = false;
if(self.processing === false){
clearTimeout(self.timer);
self.emit('end');
process.nextTick(function(){
if(typeof callback === 'function'){ callback(); }
});
}else if(self.processing === true){
setTimeout(function(){
self.end(callback);
}, (self.options.timeout / 2));
}
};
scheduler.prototype.poll = function(callback) {
var self = this;
self.processing = true;
clearTimeout(self.timer);
self.emit('poll');
self.nextDelayedTimestamp(function(err, timestamp){
if(!err && timestamp){
self.emit('working_timestamp', timestamp);
self.enqueueDelayedItemsForTimestamp(timestamp, function(err){
if(err){ self.emit('error', err); }
self.poll(callback);
});
}else{
if(err){ self.emit('error', err); }
self.processing = false;
if(self.running === true){
self.timer = setTimeout((function() {
self.poll();
}), self.options.timeout);
}
if(typeof callback === 'function'){ callback(); }
}
});
};
scheduler.prototype.nextDelayedTimestamp = function(callback) {
var self = this;
var time = Math.round(new Date().getTime() / 1000);
self.connection.redis.zrangebyscore(self.connection.key('delayed_queue_schedule'), '-inf', time, 'limit', 0, 1, function(err, items) {
if (err || items === null || items.length === 0) {
callback(err);
} else {
callback(null, items[0]);
}
});
};
scheduler.prototype.enqueueDelayedItemsForTimestamp = function(timestamp, callback) {
var self = this;
self.nextItemForTimestamp(timestamp, function(err, job){
if (!err && job ) {
self.transfer(timestamp, job, function(){
self.enqueueDelayedItemsForTimestamp(timestamp, callback);
});
} else {
callback(err);
}
});
};
scheduler.prototype.nextItemForTimestamp = function(timestamp, callback) {
var self = this;
var key = self.connection.key("delayed:" + timestamp);
self.connection.redis.lpop(key, function(err, job){
if(err){
callback(err);
}else{
self.connection.redis.srem(self.connection.key("timestamps:" + job), key, function(err){
self.cleanupTimestamp(timestamp, function(){
if (err) {
callback(err);
} else {
callback(null, JSON.parse(job));
}
});
});
}
});
};
scheduler.prototype.transfer = function(timestamp, job, callback) {
var self = this;
self.queue.enqueue(job.queue, job.class, job.args, function(err){
if(err){ self.emit('error', err); }
self.emit('transferred_job', timestamp, job);
callback();
});
};
scheduler.prototype.cleanupTimestamp = function(timestamp, callback) {
var self = this;
var key = self.connection.key("delayed:" + timestamp);
self.connection.redis.llen(key, function(err, len) {
if (len === 0) {
self.connection.redis.del(key);
self.connection.redis.zrem(self.connection.key('delayed_queue_schedule'), timestamp);
}
callback();
});
};
exports.scheduler = scheduler;