first commit
This commit is contained in:
74
batch/leader/locker.js
Normal file
74
batch/leader/locker.js
Normal file
@@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
var RedisDistlockLocker = require('./provider/redis-distlock');
|
||||
var debug = require('../util/debug')('leader-locker');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
var LOCK = {
|
||||
TTL: 5000
|
||||
};
|
||||
|
||||
function Locker(locker, ttl) {
|
||||
EventEmitter.call(this);
|
||||
this.locker = locker;
|
||||
this.ttl = (Number.isFinite(ttl) && ttl > 0) ? ttl : LOCK.TTL;
|
||||
this.renewInterval = this.ttl / 5;
|
||||
this.intervalIds = {};
|
||||
}
|
||||
util.inherits(Locker, EventEmitter);
|
||||
|
||||
module.exports = Locker;
|
||||
|
||||
Locker.prototype.lock = function(resource, callback) {
|
||||
var self = this;
|
||||
debug('Locker.lock(%s, %d)', resource, this.ttl);
|
||||
this.locker.lock(resource, this.ttl, function (err, lock) {
|
||||
if (!err) {
|
||||
self.startRenewal(resource);
|
||||
}
|
||||
return callback(err, lock);
|
||||
});
|
||||
};
|
||||
|
||||
Locker.prototype.unlock = function(resource, callback) {
|
||||
var self = this;
|
||||
debug('Locker.unlock(%s)', resource);
|
||||
this.locker.unlock(resource, function(err) {
|
||||
self.stopRenewal(resource);
|
||||
return callback(err);
|
||||
});
|
||||
};
|
||||
|
||||
Locker.prototype.startRenewal = function(resource) {
|
||||
var self = this;
|
||||
if (!this.intervalIds.hasOwnProperty(resource)) {
|
||||
this.intervalIds[resource] = setInterval(function() {
|
||||
debug('Trying to extend lock resource=%s', resource);
|
||||
self.locker.lock(resource, self.ttl, function(err, _lock) {
|
||||
if (err) {
|
||||
self.emit('error', err, resource);
|
||||
return self.stopRenewal(resource);
|
||||
}
|
||||
if (_lock) {
|
||||
debug('Extended lock resource=%s', resource);
|
||||
}
|
||||
});
|
||||
}, this.renewInterval);
|
||||
}
|
||||
};
|
||||
|
||||
Locker.prototype.stopRenewal = function(resource) {
|
||||
if (this.intervalIds.hasOwnProperty(resource)) {
|
||||
clearInterval(this.intervalIds[resource]);
|
||||
delete this.intervalIds[resource];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.create = function createLocker(type, config) {
|
||||
if (type !== 'redis-distlock') {
|
||||
throw new Error('Invalid type Locker type. Valid types are: "redis-distlock"');
|
||||
}
|
||||
var locker = new RedisDistlockLocker(config.pool);
|
||||
return new Locker(locker, config.ttl);
|
||||
};
|
||||
111
batch/leader/provider/redis-distlock.js
Normal file
111
batch/leader/provider/redis-distlock.js
Normal file
@@ -0,0 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
var REDIS_DISTLOCK = {
|
||||
DB: 5,
|
||||
PREFIX: 'batch:locks:'
|
||||
};
|
||||
|
||||
var Redlock = require('redlock');
|
||||
var debug = require('../../util/debug')('leader:redis-distlock');
|
||||
|
||||
function RedisDistlockLocker(redisPool) {
|
||||
this.pool = redisPool;
|
||||
this.redlock = new Redlock([{}], {
|
||||
// see http://redis.io/topics/distlock
|
||||
driftFactor: 0.01, // time in ms
|
||||
// the max number of times Redlock will attempt to lock a resource before failing
|
||||
retryCount: 3,
|
||||
// the time in ms between attempts
|
||||
retryDelay: 100
|
||||
});
|
||||
this._locks = {};
|
||||
}
|
||||
|
||||
module.exports = RedisDistlockLocker;
|
||||
module.exports.type = 'redis-distlock';
|
||||
|
||||
function resourceId(resource) {
|
||||
return REDIS_DISTLOCK.PREFIX + resource;
|
||||
}
|
||||
|
||||
RedisDistlockLocker.prototype.lock = function(resource, ttl, callback) {
|
||||
var self = this;
|
||||
debug('RedisDistlockLocker.lock(%s, %d)', resource, ttl);
|
||||
var lockId = resourceId(resource);
|
||||
|
||||
var lock = this._getLock(lockId);
|
||||
function acquireCallback(err, _lock) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self._setLock(lockId, _lock);
|
||||
return callback(null, _lock);
|
||||
}
|
||||
if (lock) {
|
||||
return this._tryExtend(lock, ttl, function(err, _lock) {
|
||||
if (err) {
|
||||
return self._tryAcquire(lockId, ttl, acquireCallback);
|
||||
}
|
||||
|
||||
return callback(null, _lock);
|
||||
});
|
||||
} else {
|
||||
return this._tryAcquire(lockId, ttl, acquireCallback);
|
||||
}
|
||||
};
|
||||
|
||||
RedisDistlockLocker.prototype.unlock = function(resource, callback) {
|
||||
var self = this;
|
||||
var lock = this._getLock(resourceId(resource));
|
||||
if (lock) {
|
||||
this.pool.acquire(REDIS_DISTLOCK.DB, function (err, client) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self.redlock.servers = [client];
|
||||
return self.redlock.unlock(lock, function(err) {
|
||||
self.pool.release(REDIS_DISTLOCK.DB, client);
|
||||
return callback(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
RedisDistlockLocker.prototype._getLock = function(resource) {
|
||||
if (this._locks.hasOwnProperty(resource)) {
|
||||
return this._locks[resource];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
RedisDistlockLocker.prototype._setLock = function(resource, lock) {
|
||||
this._locks[resource] = lock;
|
||||
};
|
||||
|
||||
RedisDistlockLocker.prototype._tryExtend = function(lock, ttl, callback) {
|
||||
var self = this;
|
||||
this.pool.acquire(REDIS_DISTLOCK.DB, function (err, client) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self.redlock.servers = [client];
|
||||
return lock.extend(ttl, function(err, _lock) {
|
||||
self.pool.release(REDIS_DISTLOCK.DB, client);
|
||||
return callback(err, _lock);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
RedisDistlockLocker.prototype._tryAcquire = function(resource, ttl, callback) {
|
||||
var self = this;
|
||||
this.pool.acquire(REDIS_DISTLOCK.DB, function (err, client) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self.redlock.servers = [client];
|
||||
return self.redlock.lock(resource, ttl, function(err, _lock) {
|
||||
self.pool.release(REDIS_DISTLOCK.DB, client);
|
||||
return callback(err, _lock);
|
||||
});
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user