UNPKG

softkave-js-utils

Version:

JavaScript & Typescript utility functions, types, and classes

146 lines 4.54 kB
import { first, noop } from 'lodash-es'; import { getNewId } from '../id/index.js'; import { getDeferredPromise } from '../promise/getDeferredPromise.js'; import { ListenableResource } from './ListenableResource.js'; const kLockQueueItemState = { waiting: 'w', waitingOnResolve: 'wr', }; export class LockStore { constructor() { this.locks = {}; this.waiters = []; this.execNext = (name) => { const queue = this.getLockQueue(name, true); const next = first(queue); const item = next === null || next === void 0 ? void 0 : next.get(); if (!next || !item) { return; } if (item.state === kLockQueueItemState.waitingOnResolve) { next.listen(this.execLockItem); } this.execLockItem(item); }; this.execLockItem = (item) => { if ((item === null || item === void 0 ? void 0 : item.state) === kLockQueueItemState.waiting) { item.resolveFn(); return true; } return false; }; } run(name, fn) { const item = this.queue(name); return this.__run(name, item, fn); } has(name) { var _a; return !!((_a = this.getLockQueue(name, false)) === null || _a === void 0 ? void 0 : _a.length); } wait(params) { var _a, _b, _c; const { name, timeoutMs } = params; const queueLength = (_b = (_a = this.getLockQueue(name, false)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0; const remaining = (_c = params.remaining) !== null && _c !== void 0 ? _c : queueLength; if (queueLength === 0 || remaining <= 0) { return Promise.resolve(); } const p = getDeferredPromise(); const waiter = { remaining, resolveFn: p.resolve, timeoutId: undefined, }; this.waiters.push(waiter); if (timeoutMs) { waiter.timeoutId = setTimeout(() => { p.reject(new Error('Timeout')); this.waiters = this.waiters.filter(w => w !== waiter); }, timeoutMs); } return p.promise; } dispose() { this.waiters.forEach(waiter => { if (waiter.timeoutId) { clearTimeout(waiter.timeoutId); } }); } async __run(name, item, fn) { await this.acquire(name, item); try { return await fn(); } finally { this.release(name); } } queue(name) { const queue = this.getLockQueue(name, true); const item = new ListenableResource({ state: kLockQueueItemState.waitingOnResolve, resolveFn: noop, }); queue.push(item); return item; } acquire(name, item) { const p = new Promise(resolve => { item.set({ state: kLockQueueItemState.waiting, resolveFn: resolve, }); }); setTimeout(() => this.execNext(name), 0); return p; } release(name) { const queue = this.getLockQueue(name, true); queue.shift(); this.waiters = this.waiters.filter(w => { w.remaining--; const isWaitDone = w.remaining <= 0; if (isWaitDone) { if (w.timeoutId) { clearTimeout(w.timeoutId); } w.resolveFn(); } return !isWaitDone; }); this.execNext(name); } getLockQueue(name, init) { let queue = this.locks[name]; if (!queue && init) { queue = this.locks[name] = []; } return queue; } } export class LockableResource { constructor(locks, resource, name = getNewId()) { this.locks = locks; this.resource = resource; this.name = name; } async run(fn) { await this.locks.run(this.name, async () => { const newData = await fn(this.resource); if (newData) { this.resource = newData; } }); } } export class SingleInstanceRunner { static make(locks, opts) { return async (...args) => { const id = opts.instanceSpecifier(...args); return await locks.run(id, () => opts.fn(...args)); }; } } //# sourceMappingURL=LockStore.js.map