UNPKG

@ayonli/jsext

Version:

A JavaScript extension package for building strong and modern applications.

167 lines (162 loc) 4.58 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var collections_BiMap = require('./collections/BiMap.js'); /** * A mutual exclusion mechanism for concurrent operations and protecting shared * data. * @module */ var _a; if (typeof Symbol.dispose === "undefined") { Object.defineProperty(Symbol, "dispose", { value: Symbol("Symbol.dispose") }); } const _queue = Symbol.for("queue"); const _value = Symbol.for("value"); const _mutex = Symbol.for("mutex"); const _unlocked = Symbol.for("unlocked"); /** * Mutual Exclusion prevents multiple coroutines from accessing the same shared * resource simultaneously. * * **NOTE:** * Currently, the Mutex instance can not be used across multiple threads, but is * considering adding support for `parallel` threads. * * @example * ```ts * import { Mutex } from "@ayonli/jsext/lock"; * import { random } from "@ayonli/jsext/number"; * import { sleep } from "@ayonli/jsext/async"; * * const mutex = new Mutex(1); * * async function concurrentOperation() { * using shared = await mutex.lock(); * const value1 = shared.value; * * await otherAsyncOperations(); * * shared.value += 1 * const value2 = shared.value; * * // Without mutex lock, the shared value may have been modified by other * // calls during `await otherAsyncOperation()`, and the following * // assertion will fail. * console.assert(value1 + 1 === value2); * } * * async function otherAsyncOperations() { * await sleep(100 * random(1, 10)); * } * * await Promise.all([ * concurrentOperation(), * concurrentOperation(), * concurrentOperation(), * concurrentOperation(), * ]); * ``` */ class Mutex { /** * @param value The data associated to the mutex instance. */ constructor(value) { this[_a] = []; this[_value] = value; } /** * Acquires the lock of the mutex, optionally for modifying the shared * resource. */ async lock() { await new Promise(resolve => { if (this[_queue].length) { this[_queue].push(resolve); } else { this[_queue].push(resolve); resolve(); } }); const lock = Object.create(Mutex.Lock.prototype); lock[_mutex] = this; return lock; } } _a = _queue; (function (Mutex) { var _b; class Lock { constructor(mutex) { this[_b] = false; this[_mutex] = mutex; } /** Accesses the data associated to the mutex instance. */ get value() { if (this[_unlocked]) { throw new ReferenceError("trying to access data after unlocked"); } return this[_mutex][_value]; } set value(v) { if (this[_unlocked]) { throw new ReferenceError("trying to access data after unlocked"); } this[_mutex][_value] = v; } /** Releases the current lock of the mutex. */ unlock() { this[_unlocked] = true; const queue = this[_mutex][_queue]; queue.shift(); const next = queue[0]; if (next) { next(); } else if (registry.hasValue(this[_mutex])) { registry.deleteValue(this[_mutex]); } } [(_b = _unlocked, Symbol.dispose)]() { this.unlock(); } } Mutex.Lock = Lock; })(Mutex || (Mutex = {})); const registry = new collections_BiMap.default(); /** * Acquires a mutex lock for the given key in order to perform concurrent * operations and prevent conflicts. * * If the key is currently being locked by other coroutines, this function will * block until the lock becomes available again. * * @example * ```ts * import lock from "@ayonli/jsext/lock"; * * const key = "lock_key"; * * export async function concurrentOperation() { * using ctx = await lock(key); * void ctx; * * // This block will never be run if there are other coroutines holding * // the lock. * // * // Other coroutines trying to lock the same key will also never be run * // before this function completes. * } * ``` */ async function lock(key) { let mutex = registry.get(key); if (!mutex) { registry.set(key, mutex = new Mutex(void 0)); } return await mutex.lock(); } exports.Mutex = Mutex; exports.default = lock; //# sourceMappingURL=lock.js.map