screwdriver-api
Version:
API server for the Screwdriver.cd service
115 lines (99 loc) • 3.59 kB
JavaScript
;
const Redis = require('ioredis');
const Redlock = require('redlock');
const config = require('config');
const logger = require('screwdriver-logger');
/**
* parse value to Boolean
* @method parseBool
* @param {(Boolean|String)} value
* @return {Boolean}
*/
function parseBool(value) {
if (typeof value === 'boolean') {
return value;
}
// True values refers to https://yaml.org/type/bool.html
return ['on', 'true', 'yes', 'y'].includes(String(value).toLowerCase());
}
class Lock {
/**
* Constructor
*/
constructor() {
if (!parseBool(config.get('redisLock.enabled'))) {
return;
}
const redisLockConfig = config.get('redisLock.options');
const { connectionType } = redisLockConfig;
if (!connectionType || (connectionType !== 'redis' && connectionType !== 'redisCluster')) {
throw new Error(
`'connectionType ${connectionType}' is not supported, use 'redis' or 'redisCluster' for the queue.connectionType setting`
);
}
const redisConfig = redisLockConfig[`${connectionType}Connection`];
const redisOptions = {
password: redisConfig.options && redisConfig.options.password,
tls: redisConfig.options ? parseBool(redisConfig.options.tls) : false
};
try {
if (connectionType === 'redisCluster') {
this.redis = new Redis.Cluster(redisConfig.hosts, {
redisOptions,
slotsRefreshTimeout: parseInt(redisConfig.slotsRefreshTimeout, 10),
clusterRetryStrategy: () => 100
});
} else {
this.redis = new Redis(redisConfig.port, redisConfig.host, redisOptions);
}
this.redlock = new Redlock([this.redis], {
driftFactor: parseFloat(redisLockConfig.driftFactor),
retryCount: parseInt(redisLockConfig.retryCount, 10),
retryDelay: parseFloat(redisLockConfig.retryDelay),
retryJitter: parseFloat(redisLockConfig.retryJitter)
});
this.ttl = parseFloat(redisLockConfig.ttl);
} catch (err) {
logger.error('Failed to initialize redlock', err);
}
}
/**
* Attempt to acquire a lock for resource, will retry acquiring lock depending
* on configuration.
* @method lock
* @param {String} resource the string identifier for the resource to lock
* @param {Number} ttl maximum lock duration in milliseconds
* @returns {Promise}
*/
async lock(resource, ttl = this.ttl) {
if (this.redlock) {
try {
const lock = await this.redlock.lock(resource, ttl);
return lock;
} catch (err) {
logger.error(`Failed to lock ${resource}`, err);
}
}
return null;
}
/**
* Attempt to release a lock for resource.
*
* @method unlock
* @param {Object} lock Lock representing the resource
* @param {String} resource the string identifier for the resource to lock
* @returns {Promise}
*/
async unlock(lock, resource) {
try {
if (lock) {
const wait = await lock.unlock();
return wait;
}
} catch (err) {
logger.error(`Failed to unlock ${resource}`, err);
}
return null;
}
}
module.exports = new Lock();