@deepkit/core
Version:
Deepkit core library
154 lines • 5.16 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Mutex = exports.ProcessLocker = exports.ProcessLock = void 0;
function __assignType(fn, args) {
fn.__type = args;
return fn;
}
/*
* Deepkit Framework
* Copyright (C) 2021 Deepkit UG, Marc J. Schmidt
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the MIT License.
*
* You should have received a copy of the MIT License along with this program.
*/
const array_js_1 = require("./array.js");
const LOCKS = {};
/**
* This lock mechanism works only for one process (worker).
*
* live-mutex: has horrible API and doesn't allow to check if an key is currently locked.
* proper-filelock: No way to do a correct mutex locking with event-driven blocking acquire() method.
* redislock: Very bad performance on high-load (when multiple locks on the same key `wait`, since it loops)
* mongodb lock: even worse performance than redis. Jesus.
*/
class ProcessLock {
constructor(id) {
this.id = id;
this.holding = false;
}
async acquire(ttl = 0, timeout = 0) {
if (this.holding) {
throw new Error('Lock already acquired');
}
return new Promise(__assignType((resolve, reject) => {
const ourTake = () => {
LOCKS[this.id].time = Date.now() / 1000;
this.holding = true;
resolve();
if (ttl) {
this.ttlTimeout = setTimeout(() => {
this.unlock();
}, ttl * 1000);
}
};
if (timeout > 0) {
setTimeout(() => {
if (LOCKS[this.id])
(0, array_js_1.arrayRemoveItem)(LOCKS[this.id].queue, ourTake);
//reject is never handled when resolve is called first
reject('Lock timed out ' + this.id);
}, timeout * 1000);
}
if (LOCKS[this.id]) {
LOCKS[this.id].queue.push(ourTake);
}
else {
LOCKS[this.id] = {
time: Date.now() / 1000,
queue: []
};
this.holding = true;
resolve();
if (ttl) {
this.ttlTimeout = setTimeout(() => {
this.unlock();
}, ttl * 1000);
}
}
}, ['resolve', 'reject', '', 'P"2!"2""/#']));
}
isLocked() {
return this.holding;
}
tryLock(ttl = 0) {
this.holding = false;
if (!LOCKS[this.id]) {
LOCKS[this.id] = {
time: Date.now() / 1000,
queue: []
};
this.holding = true;
if (ttl) {
this.ttlTimeout = setTimeout(() => {
this.unlock();
}, ttl * 1000);
}
}
return this.holding;
}
unlock() {
clearTimeout(this.ttlTimeout);
if (!this.holding) {
return;
}
this.holding = false;
if (LOCKS[this.id].queue.length) {
//there are other locks waiting.
//so we pick the next, and call it
const next = LOCKS[this.id].queue.shift();
next();
}
else {
//nobody is waiting, so we just delete that lock
delete LOCKS[this.id];
}
}
}
exports.ProcessLock = ProcessLock;
ProcessLock.__type = ['ttlTimeout', 'id', 'constructor', 'ttl', () => 0, 'timeout', () => 0, 'acquire', 'isLocked', () => 0, 'tryLock', 'unlock', '"3!<P&2":"0#P\'2$>%\'2&>\'"0(P"0)P\'2$>*"0+P"0,5'];
class ProcessLocker {
/**
*
* @param id
* @param ttl optional defines when the times automatically unlocks.
* @param timeout if after `timeout` seconds the lock isn't acquired, it throws an error.
*/
async acquireLock(id, ttl = 0, timeout = 0) {
const lock = new ProcessLock(id);
await lock.acquire(ttl, timeout);
return lock;
}
async tryLock(id, ttl = 0) {
const lock = new ProcessLock(id);
if (lock.tryLock(ttl)) {
return lock;
}
return;
}
isLocked(id) {
return !!LOCKS[id];
}
}
exports.ProcessLocker = ProcessLocker;
ProcessLocker.__type = ['id', 'ttl', () => 0, 'timeout', () => 0, () => ProcessLock, 'acquireLock', () => 0, () => ProcessLock, 'tryLock', 'isLocked', 'P&2!\'2">#\'2$>%P7&`0\'P&2!\'2">(PP7)-J`0*P&2!)0+5'];
class Mutex {
unlock() {
if (this.resolver)
this.resolver();
this.promise = undefined;
}
async lock() {
while (this.promise) {
await this.promise;
}
this.promise = new Promise(__assignType((resolver) => {
this.resolver = resolver;
}, ['resolver', '', 'P"2!"/"']));
}
}
exports.Mutex = Mutex;
Mutex.__type = ['promise', '', 'resolver', 'unlock', 'lock', '$`3!8<P"/"3#8<P$0$P$`0%5'];
//# sourceMappingURL=process-locker.js.map