@ndustrial/node-distributed-lock
Version:
Enables distributed locking for sequelize applications
112 lines (99 loc) • 3.18 kB
JavaScript
const getLockInterface = require('./lock-interface');
const {
AlreadyObtainedError,
TableLockedError
} = require('./error');
const getQueryInterface = require('./query-interface');
const QueryInterface = require('./query-interface/base');
const detectSchema = (lockTableName) => {
let schema;
if (lockTableName.indexOf('.') > -1) {
([schema] = lockTableName.split('.'));
}
return schema;
};
class Mutex {
constructor(options) {
const { queryInterface, lockTableName, lockTTLSeconds, queryInterfaceName } = options;
this.lockTableName = lockTableName;
this.lockTTLSeconds = lockTTLSeconds;
this.queryInterface = getQueryInterface({ queryInterface, queryInterfaceName });
this.lockInterface = getLockInterface(this.queryInterface.getDialectName());
}
async initializeLockTable() {
try {
const schema = detectSchema(this.lockTableName);
await this.queryInterface.useTransaction(async (transaction) => {
if (typeof schema !== 'undefined') {
await this.queryInterface.query(
this.lockInterface.createSchema(schema),
{ transaction, firstResult: true }
);
}
return this.queryInterface.query(
this.lockInterface.createLockTable(this.lockTableName),
{ transaction, firstResult: true }
);
});
} catch (e) {
switch (this.queryInterface.handleError(e)) {
case QueryInterface.DatabaseErrors.UniqueConstraintError:
// ignore
return;
default:
throw e;
}
}
}
async obtainLock(lockName, nodeId) {
const { node_id: ownerId } = await this.withLock(async (transaction) =>
this.queryInterface.query(this.lockInterface.obtainMutex(this.lockTableName), {
replacements: {
mutex: lockName,
nodeId
},
transaction,
firstResult: true
}));
if (ownerId !== nodeId) {
throw new AlreadyObtainedError(lockName, ownerId);
}
}
async releaseLock(lockName, nodeId) {
await this.withLock(
async (transaction) =>
this.queryInterface.query(this.lockInterface.removeMutex(this.lockTableName), {
replacements: {
mutex: lockName,
nodeId
},
transaction,
firstResult: true
}),
true
);
}
async withLock(callback, wait = false) {
return this.queryInterface.useTransaction(async (transaction) => {
try {
await this.queryInterface.query(
this.lockInterface.lockTable(this.lockTableName, wait),
{ transaction, firstResult: true }
);
await this.queryInterface.query(
this.lockInterface.removeStaleLocks(this.lockTableName, this.lockTTLSeconds),
{ transaction, firstResult: true }
);
return callback(transaction);
} catch (e) {
switch (this.queryInterface.handleError(e)) {
case QueryInterface.DatabaseErrors.TableLockedError:
throw new TableLockedError(this.lockTableName);
default:
throw e;
}
}
});
}
}
module.exports = Mutex;