UNPKG

@toolbuilder/semaphore

Version:

Basic semaphore and mutex with both sync and async acquire methods.

137 lines (124 loc) 3.31 kB
'use strict'; /** * @typedef {() => void} Resolver */ /** * Promise based semaphore. */ class Semaphore { /** * Create a semaphore. * * @param {number} [max] - maximum number of locks that can be acquired at any given time */ constructor (max = 1) { /** @private */ this._max = max; /** @private */ this._active = 0; /** * @private * @type {Resolver[]} */ this._resolvers = []; // when locked, each acquire requires a new promise } /** * Returns whether a lock is available. If one is available, * acquireSync will succeed. * @returns {boolean} - true if a lock is available, false otherwise */ available () { return !(this._active >= this._max) } /** * Acquires a lock synchronously. * @returns {boolean} - true if lock was acquired, false otherwise */ acquireSync () { if (this._active >= this._max) return false this._active++; return true } /** * Acquires a lock asynchronously. * @returns {PromiseLike<void>} - promise resolves when a lock has been acquired. */ acquire () { this._active++; if (this._active > this._max) { let resolver; const promise = new Promise(resolve => (resolver = resolve)); this._resolvers.push(resolver); return promise } else { return Promise.resolve() } } /** * Releases a lock so that it is available to be acquired. * Each acquire or acquireSync call must be matched by exactly one release call. * @returns {void} */ release () { this._active--; if (this._resolvers.length > 0) { this._resolvers.shift()(); // let awaiting code run by resolving a promise } } } /** * @function * @param {import('./semaphore.js').Resolver} fn - function to call only once * @returns {() => void} */ const once = fn => { let alreadyCalled = false; return () => { if (!alreadyCalled) { alreadyCalled = true; fn(); } } }; /** * Simple mutex class. * @example * const mutex = new Mutex() * const release = await mutex.acquire() * release() // to release mutex */ class Mutex { constructor () { /** @private */ this._semaphore = new Semaphore(1); } /** * Determine if the lock is available. * @returns {boolean} - true if lock is available, false otherwise */ available () { return this._semaphore.available() } /** * Get a lock if available. * * @returns {() => void} - if the lock is available, returns a * function to release it. Otherwise returns null. The release function can * be called multiple times, it will only release once. * @example * const mutex = new Mutex() * const release = mutex.acquireSync() * if (release) release() */ acquireSync () { const release = once(() => this._semaphore.release()); return (this._semaphore.acquireSync()) ? release : null } /** * Acquire a lock. * @returns {PromiseLike<() => void>} - returns a Promise that resolves to a release function. The release * function can be called multiple times, it will only release once. */ acquire () { const release = once(() => this._semaphore.release()); return this._semaphore.acquire().then(() => release) } } exports.Mutex = Mutex; exports.Semaphore = Semaphore;