pg-mutex-lock
Version:
Mutex Lock using postgreSQL
119 lines (118 loc) • 4.27 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const pg_connection_string_1 = require("pg-connection-string");
const crypto_1 = require("crypto");
const events_1 = require("events");
const driver_1 = require("./driver");
var EVENTS;
(function (EVENTS) {
EVENTS["CONNECTED"] = "connected";
EVENTS["CONNECT_FAILED"] = "connect_failed";
})(EVENTS || (EVENTS = {}));
class PGMutexLock {
constructor({ database = {}, timeout = 10 * 1000, retryCount = 3 } = {}) {
this.emitter = new events_1.EventEmitter();
this.isConnected = false;
this.timeout = timeout;
this.retryCount = retryCount;
const dbName = (() => {
if (typeof database === 'string') {
return pg_connection_string_1.parse(database).database;
}
else {
const { database: dbName, connectionString } = database || {};
return dbName ||
(connectionString && pg_connection_string_1.parse(connectionString).database) ||
process.env.PGDATABASE ||
process.env.PGUSER ||
process.env.USER;
}
})();
this.client = driver_1.createClient(database);
this.client.connect()
.then(() => initTable(this.client, dbName))
.then((oid) => {
this.databaseId = oid;
this.isConnected = true;
this.emitter.emit(EVENTS.CONNECTED);
})
.catch((err) => {
console.error(err);
this.emitter.emit(EVENTS.CONNECT_FAILED, err);
});
this.end = this.end.bind(this);
this.acquireLock = this.acquireLock.bind(this);
this.releaseLock = this.releaseLock.bind(this);
}
waitConnection() {
return new Promise((resolve, reject) => {
if (this.isConnected) {
resolve();
return;
}
this.emitter.once(EVENTS.CONNECTED, () => {
resolve();
});
this.emitter.once(EVENTS.CONNECT_FAILED, (err) => {
reject(err);
});
});
}
async acquireLock(key) {
let [classid, objid] = strToKey(key);
await this.waitConnection();
// Check in session lock
for (let i = 0; i < this.retryCount; i++) {
let time = +new Date();
while (+new Date() - time < this.timeout) {
let res = await this.client.query(`
SELECT
CASE count(*) WHEN 0 THEN (SELECT pg_try_advisory_lock($1, $2))
ELSE FALSE
END as pg_try_advisory_lock
FROM
pg_locks
WHERE
pid = (
SELECT
pg_backend_pid()
)
AND locktype = 'advisory'
AND classid = $1 AND objid = $2
AND "database" = $3;
`, [classid, objid, this.databaseId]);
if (res.rows[0].pg_try_advisory_lock == true)
return true;
await sleep(100);
}
}
throw Error("Cannot acquire lock");
}
async releaseLock(key) {
let [classid, objid] = strToKey(key);
await this.waitConnection();
let res = await this.client.query(`
SELECT pg_advisory_unlock($1, $2);
`, [classid, objid]);
return res.rows[0].pg_advisory_unlock;
}
end() {
return this.client.end();
}
}
exports.default = PGMutexLock;
async function initTable(client, databasename) {
let res = await client.query(`
SELECT oid from pg_database WHERE datname=$1;
`, [databasename]);
if (res.rowCount == 0)
throw Error("Table does not exists!!");
return res.rows[0].oid;
}
function strToKey(str) {
let buf = crypto_1.createHash("sha256").update(str).digest();
return [buf.readInt32LE(0), buf.readInt32LE(4)];
}
function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}