composable-locks
Version:
Composable concurrency locks for Javascript.
1 lines • 12.3 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../src/mutex.ts","../src/keyed.ts","../src/reentrant.ts","../src/readwrite.ts","../src/utils.ts"],"sourcesContent":["import type { ILock, Releaser } from \"./interfaces\";\n\n// inspired from https://github.com/mgtitimoli/await-mutex, with some tweaks for typescript\nexport class Mutex implements ILock<[]> {\n protected _locking = Promise.resolve();\n\n acquire(): Promise<Releaser> {\n let unlockNext: Releaser;\n const willLock = new Promise<void>((resolve) => {\n unlockNext = () => resolve();\n });\n\n const willUnlock = this._locking.then(() => unlockNext);\n this._locking = this._locking.then(() => willLock);\n\n return willUnlock;\n }\n}\n","import type { ILock, Releaser } from \"./interfaces\";\n\nexport type Resolver<K> = (key: K) => K;\n\ntype LockRecord<T> = {\n count: number;\n lock: T;\n};\n\n/**\n * A keyed lock, for mapping strings to a lock type\n */\nexport class KeyedMutex<\n TArgs extends unknown[],\n TKey extends string | number | symbol\n> implements ILock<[TKey, ...TArgs]>\n{\n protected locks: Record<TKey, LockRecord<ILock<TArgs>>> = {} as Record<\n TKey,\n LockRecord<ILock<TArgs>>\n >;\n protected resolver: Resolver<TKey>;\n\n /**\n * A keyed lock, for mapping strings to a lock type\n * @param newLock A function to create a new lock interface\n * @param resolver A function to transform a key into a normalized form.\n * Useful for resolving paths.\n */\n constructor(\n protected newLock: () => ILock<TArgs>,\n resolver?: Resolver<TKey>\n ) {\n this.resolver = resolver ?? ((key) => key);\n }\n\n private getOrCreateLock(key: TKey): LockRecord<ILock<TArgs>> {\n const record = this.locks[key];\n if (record) return record;\n\n const newRecord: LockRecord<ILock<TArgs>> = {\n count: 0,\n lock: this.newLock(),\n };\n this.locks[key] = newRecord;\n return newRecord;\n }\n\n public async acquire(key: TKey, ...args: TArgs): Promise<Releaser> {\n const resolved = this.resolver(key);\n const record = this.getOrCreateLock(resolved);\n\n record.count++;\n\n const release = await record.lock.acquire(...args);\n\n // ensure idempotence\n let released = false;\n\n return () => {\n release();\n\n if (released) return;\n record.count--;\n if (record.count === 0) {\n delete this.locks[resolved];\n }\n\n released = true;\n };\n }\n}\n","import type { ILock, Releaser } from \"./interfaces\";\n\ntype Queued = {\n id: symbol;\n reentrants: number;\n releaser: Promise<Releaser>;\n};\n\n/**\n * A re-entrant Mutex.\n *\n * Usage:\n * ```\n * const lock = new ReentrantMutex()\n * const domain = Symbol()\n *\n * const release1 = await lock.acquire(domain)\n * const release2 = await lock.acquire(domain)\n * release1()\n * release2()\n * ```\n */\nexport class ReentrantMutex<A extends unknown[]>\n implements ILock<[symbol, ...A]>\n{\n protected latest: Queued | null = null;\n protected lockMap: Record<symbol, Queued> = {};\n protected lock: ILock<A>;\n\n constructor(newLock: () => ILock<A>, private readonly greedy = true) {\n this.lock = newLock();\n }\n\n /**\n * Acquire the lock\n * @param id The domain identifier.\n * @returns A function to release the lock. A domain *must* call all releasers before exiting.\n */\n public async acquire(id: symbol, ...args: A) {\n const queued = this.getQueued(id, ...args);\n\n const releaser = await queued.releaser;\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n this.release(queued, releaser);\n };\n }\n\n /**\n * Get a queued domain object\n * @param id The domain to get the queued information for\n * @param args Passed to the underlying mutex\n * @returns The queued information, and a boolean that is true if the domain existed.\n */\n private getQueued(id: symbol, ...args: A): Queued {\n if (this.greedy) {\n return this.getQueuedGreedy(id, ...args);\n } else {\n return this.getQueuedUngreedy(id, ...args);\n }\n }\n\n private getQueuedGreedy(id: symbol, ...args: A): Queued {\n const existing = this.lockMap[id];\n if (existing) {\n return existing;\n } else {\n const queued = this.createQueued(id, ...args);\n this.lockMap[id] = queued;\n return queued;\n }\n }\n\n private getQueuedUngreedy(id: symbol, ...args: A): Queued {\n if (!this.latest || this.latest.id !== id) {\n const queued = this.createQueued(id, ...args);\n this.latest = queued;\n return queued;\n } else {\n const queued = this.latest;\n queued.reentrants++;\n return queued;\n }\n }\n\n private createQueued(id: symbol, ...args: A): Queued {\n return {\n id,\n reentrants: 1,\n releaser: this.lock.acquire(...args),\n };\n }\n\n private cleanup(queued: Queued) {\n if (this.greedy) {\n delete this.lockMap[queued.id];\n } else if (this.latest === queued) {\n this.latest = null;\n }\n }\n\n private release(queued: Queued, releaser: Releaser) {\n queued.reentrants--;\n if (queued.reentrants === 0) {\n this.cleanup(queued);\n releaser();\n }\n }\n}\n","import type { ILock, Releaser } from \"./interfaces\";\nimport { ReentrantMutex } from \"./reentrant\";\n\nexport type RWLockType = \"read\" | \"write\";\n\nexport class RWMutex<A extends unknown[]> implements ILock<[RWLockType, ...A]> {\n protected readerDomain = Symbol();\n protected base: ReentrantMutex<A>;\n\n constructor(newLock: () => ILock<A>, preferRead = false) {\n this.base = new ReentrantMutex(newLock, preferRead);\n }\n\n public acquire(type: RWLockType, ...args: A): Promise<Releaser> {\n switch (type) {\n case \"read\":\n return this.base.acquire(this.readerDomain, ...args);\n case \"write\":\n return this.base.acquire(Symbol(), ...args);\n }\n }\n}\n","import type { Releaser } from \"./interfaces\";\n\n/**\n * Execute an async function with permissions\n * @param permssions An array of promises that will resolve to release functions to release permissions\n * @param f The function to execute with permissions\n * @returns The return value of f\n */\nexport const withPermissions = async <T>(\n permssions: Promise<Releaser>[],\n f: () => T | Promise<T>\n): Promise<T> => {\n const releasers = await Promise.all(permssions);\n try {\n return await f();\n } finally {\n releasers.forEach((release) => release());\n }\n};\n"],"names":["Mutex","_locking","Promise","resolve","acquire","unlockNext","willLock","willUnlock","then","KeyedMutex","constructor","newLock","resolver","locks","key","getOrCreateLock","record","newRecord","count","lock","args","resolved","release","released","ReentrantMutex","greedy","latest","lockMap","id","queued","getQueued","releaser","getQueuedGreedy","getQueuedUngreedy","existing","createQueued","reentrants","cleanup","RWMutex","preferRead","readerDomain","Symbol","base","type","withPermissions","permssions","f","releasers","all","forEach"],"mappings":"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"}