UNPKG

peter

Version:
89 lines (76 loc) 2.61 kB
/** * @file src/utils/concurrency-lock-pool.js * @author Ryan Rossiter, ryan@kingsds.network * @date July 2020 * * This class module is used to manage a pool of concurrency locks. * It provides an acquire method that will allow [size] locks to be * acquired before blocking until one of the already-acquired locks * are released. */ module.exports = class ConcurrencyLockPool { /** * @constructor * @param {number} size */ constructor(size) { if (!Number.isFinite(size) || !(size >= 0)) throw new TypeError(`The size of ConcurrencyLockPool specified is not a finite positive number. It's '${size}' (a ${typeof size}).`); this.idCounter = 0; this.size = size; /** @type {Symbol[]} */ this.locks = []; /** @type {null | Promise} */ this.acquireBlocker = null; /** @type {null | (() => void)} */ this.releaseAcquireBlocker = null; /** @type {null | Promise} */ this.emptyBlocker = null; /** @type {null | (() => void)} */ this.releaseEmptyBlocker = null; } /** * Acquires a lock from the pool. * Must be awaited on as this could block until a lock is released. * Returns a function for releasing the acquired lock. * * @returns {Promise<() => void>} */ async acquire() { await this.acquireBlocker; // block until a lock is available const lockSymbol = Symbol(`Concurrency Lock ${this.idCounter++}`); if (this.locks.push(lockSymbol) === this.size) { // this lock filled the lock pool, create a new acquireBlocker promise this.acquireBlocker = new Promise((resolve) => (this.releaseAcquireBlocker = resolve)); } if (this.emptyBlocker === null) { this.emptyBlocker = new Promise((resolve) => (this.releaseEmptyBlocker = resolve)); } return this.release.bind(this, lockSymbol); } /** * @param {Symbol} lockSymbol */ release(lockSymbol) { const lockIndex = this.locks.indexOf(lockSymbol); if (lockIndex === -1) { throw new Error(`Tried to release lock that doesn't exist: ${lockSymbol}`); } this.locks.splice(lockIndex, 1); if (this.releaseAcquireBlocker) { this.releaseAcquireBlocker(); this.releaseAcquireBlocker = null; } if (this.releaseEmptyBlocker && this.locks.length === 0) { this.releaseEmptyBlocker(); this.releaseEmptyBlocker = null; } } /** * The returned promise will only resolve once the pool has been emptied (all locks released) * @returns {Promise<void>} */ async waitUntilEmpty() { await this.emptyBlocker; } }