@matrixai/async-locks
Advanced tools
Comparing version 2.3.1 to 3.0.0
@@ -27,4 +27,8 @@ "use strict"; | ||
} | ||
let released = false; | ||
return [ | ||
async () => { | ||
if (released) | ||
return; | ||
released = true; | ||
--this._count; | ||
@@ -31,0 +35,0 @@ release(); |
import type { ResourceAcquire } from '@matrixai/resources'; | ||
import type { Lockable, ToString, LockRequest } from './types'; | ||
declare class LockBox<L extends Lockable> implements Lockable { | ||
import type { ToString, Lockable, MultiLockRequest, MultiLockAcquire, MultiLockAcquired } from './types'; | ||
declare class LockBox<L extends Lockable = Lockable> implements Lockable { | ||
protected _locks: Map<string, L>; | ||
lock(...requests: Array<LockRequest<L>>): ResourceAcquire<LockBox<L>>; | ||
lock(...requests: Array<MultiLockRequest<L>>): ResourceAcquire<LockBox<L>>; | ||
lockMulti(...requests: Array<MultiLockRequest<L>>): Array<MultiLockAcquire<L>>; | ||
get locks(): ReadonlyMap<string, L>; | ||
@@ -11,10 +12,18 @@ get count(): number; | ||
withF<T>(...params: [ | ||
...requests: Array<LockRequest<L>>, | ||
...requests: Array<MultiLockRequest<L>>, | ||
f: (lockBox: LockBox<L>) => Promise<T> | ||
]): Promise<T>; | ||
withMultiF<T>(...params: [ | ||
...requests: Array<MultiLockRequest<L>>, | ||
f: (multiLocks: Array<MultiLockAcquired<L>>) => Promise<T> | ||
]): Promise<T>; | ||
withG<T, TReturn, TNext>(...params: [ | ||
...requests: Array<LockRequest<L>>, | ||
...requests: Array<MultiLockRequest<L>>, | ||
g: (lockBox: LockBox<L>) => AsyncGenerator<T, TReturn, TNext> | ||
]): AsyncGenerator<T, TReturn, TNext>; | ||
withMultiG<T, TReturn, TNext>(...params: [ | ||
...requests: Array<MultiLockRequest<L>>, | ||
g: (multiLocks: Array<MultiLockAcquired<L>>) => AsyncGenerator<T, TReturn, TNext> | ||
]): AsyncGenerator<T, TReturn, TNext>; | ||
} | ||
export default LockBox; |
@@ -26,34 +26,39 @@ "use strict"; | ||
const locks = []; | ||
for (const [key, LockConstructor, ...lockingParams] of requests_) { | ||
let lock = this._locks.get(key); | ||
if (lock == null) { | ||
lock = new LockConstructor(); | ||
this._locks.set(key, lock); | ||
} | ||
else { | ||
// It is possible to swap the lock class, but only after the lock key is released | ||
if (!(lock instanceof LockConstructor)) { | ||
throw new errors_1.ErrorAsyncLocksLockBoxConflict(`Lock ${key} is already locked with class ${lock.constructor.name}, which conflicts with class ${LockConstructor.name}`); | ||
try { | ||
for (const [key, LockConstructor, ...lockingParams] of requests_) { | ||
let lock = this._locks.get(key); | ||
if (lock == null) { | ||
lock = new LockConstructor(); | ||
this._locks.set(key, lock); | ||
} | ||
} | ||
const lockAcquire = lock.lock(...lockingParams); | ||
let lockRelease; | ||
try { | ||
[lockRelease] = await lockAcquire(); | ||
} | ||
catch (e) { | ||
// Release all intermediate locks in reverse order | ||
locks.reverse(); | ||
for (const [key, lockRelease, lock] of locks) { | ||
await lockRelease(); | ||
if (!lock.isLocked()) { | ||
this._locks.delete(key); | ||
else { | ||
// It is possible to swap the lock class, but only after the lock key is released | ||
if (!(lock instanceof LockConstructor)) { | ||
throw new errors_1.ErrorAsyncLocksLockBoxConflict(`Lock ${key} is already locked with class ${lock.constructor.name}, which conflicts with class ${LockConstructor.name}`); | ||
} | ||
} | ||
throw e; | ||
const lockAcquire = lock.lock(...lockingParams); | ||
const [lockRelease] = await lockAcquire(); | ||
locks.push([key, lockRelease, lock]); | ||
} | ||
locks.push([key, lockRelease, lock]); | ||
} | ||
catch (e) { | ||
// Release all intermediate locks in reverse order | ||
locks.reverse(); | ||
for (const [key, lockRelease, lock] of locks) { | ||
await lockRelease(); | ||
// If it is still locked, then it is held by a different context | ||
// only delete if no contexts are locking the lock | ||
if (!lock.isLocked()) { | ||
this._locks.delete(key); | ||
} | ||
} | ||
throw e; | ||
} | ||
let released = false; | ||
return [ | ||
async () => { | ||
if (released) | ||
return; | ||
released = true; | ||
// Release all locks in reverse order | ||
@@ -63,2 +68,4 @@ locks.reverse(); | ||
await lockRelease(); | ||
// If it is still locked, then it is held by a different context | ||
// only delete if no contexts are locking the lock | ||
if (!lock.isLocked()) { | ||
@@ -73,2 +80,66 @@ this._locks.delete(key); | ||
} | ||
lockMulti(...requests) { | ||
// Convert to strings | ||
// This creates a copy of the requests | ||
let requests_ = requests.map(([key, ...rest]) => typeof key === 'string' | ||
? [key, key, ...rest] | ||
: [key.toString(), key, ...rest]); | ||
// Sort to ensure lock hierarchy | ||
requests_.sort(([key1], [key2]) => { | ||
// Deterministic string comparison according to 16-bit code units | ||
if (key1 < key2) | ||
return -1; | ||
if (key1 > key2) | ||
return 1; | ||
return 0; | ||
}); | ||
// Avoid duplicate locking | ||
requests_ = requests_.filter(([key], i, arr) => i === 0 || key !== arr[i - 1][0]); | ||
const lockAcquires = []; | ||
for (const [key, keyOrig, LockConstructor, ...lockingParams] of requests_) { | ||
const lockAcquire = async () => { | ||
let lock = this._locks.get(key); | ||
let lockRelease; | ||
try { | ||
if (lock == null) { | ||
lock = new LockConstructor(); | ||
this._locks.set(key, lock); | ||
} | ||
else { | ||
// It is possible to swap the lock class, but only after the lock key is released | ||
if (!(lock instanceof LockConstructor)) { | ||
throw new errors_1.ErrorAsyncLocksLockBoxConflict(`Lock ${key} is already locked with class ${lock.constructor.name}, which conflicts with class ${LockConstructor.name}`); | ||
} | ||
} | ||
const lockAcquire = lock.lock(...lockingParams); | ||
[lockRelease] = await lockAcquire(); | ||
} | ||
catch (e) { | ||
// If it is still locked, then it is held by a different context | ||
// only delete if no contexts are locking the lock | ||
if (!lock.isLocked()) { | ||
this._locks.delete(key); | ||
} | ||
throw e; | ||
} | ||
let released = false; | ||
return [ | ||
async () => { | ||
if (released) | ||
return; | ||
released = true; | ||
await lockRelease(); | ||
// If it is still locked, then it is held by a different context | ||
// only delete if no contexts are locking the lock | ||
if (!lock.isLocked()) { | ||
this._locks.delete(key); | ||
} | ||
}, | ||
lock, | ||
]; | ||
}; | ||
lockAcquires.push([keyOrig, lockAcquire, ...lockingParams]); | ||
} | ||
return lockAcquires; | ||
} | ||
get locks() { | ||
@@ -118,2 +189,8 @@ return this._locks; | ||
} | ||
async withMultiF(...params) { | ||
const f = params.pop(); | ||
const lockAcquires = this.lockMulti(...params); | ||
const lockAcquires_ = lockAcquires.map(([key, lockAcquire, ...lockingParams]) => (...r) => lockAcquire(...r).then(([lockRelease, lock]) => [lockRelease, [key, lock, ...lockingParams]])); | ||
return (0, resources_1.withF)(lockAcquires_, f); | ||
} | ||
withG(...params) { | ||
@@ -123,4 +200,10 @@ const g = params.pop(); | ||
} | ||
withMultiG(...params) { | ||
const g = params.pop(); | ||
const lockAcquires = this.lockMulti(...params); | ||
const lockAcquires_ = lockAcquires.map(([key, lockAcquire, ...lockingParams]) => (...r) => lockAcquire(...r).then(([lockRelease, lock]) => [lockRelease, [key, lock, ...lockingParams]])); | ||
return (0, resources_1.withG)(lockAcquires_, g); | ||
} | ||
} | ||
exports.default = LockBox; | ||
//# sourceMappingURL=LockBox.js.map |
@@ -15,3 +15,3 @@ import type { MutexInterface } from 'async-mutex'; | ||
protected _writerCount: number; | ||
lock(type: 'read' | 'write', timeout?: number): ResourceAcquire<RWLockReader>; | ||
lock(type?: 'read' | 'write', timeout?: number): ResourceAcquire<RWLockReader>; | ||
read(timeout?: number): ResourceAcquire<RWLockReader>; | ||
@@ -18,0 +18,0 @@ write(timeout?: number): ResourceAcquire<RWLockReader>; |
@@ -18,3 +18,3 @@ "use strict"; | ||
} | ||
lock(type, timeout) { | ||
lock(type = 'write', timeout) { | ||
switch (type) { | ||
@@ -67,4 +67,8 @@ case 'read': | ||
} | ||
let released = false; | ||
return [ | ||
async () => { | ||
if (released) | ||
return; | ||
released = true; | ||
readersRelease = await this.readersLock.acquire(); | ||
@@ -99,4 +103,8 @@ const readerCount = --this._readerCount; | ||
} | ||
let released = false; | ||
return [ | ||
async () => { | ||
if (released) | ||
return; | ||
released = true; | ||
release(); | ||
@@ -103,0 +111,0 @@ --this._writerCount; |
@@ -15,3 +15,3 @@ import type { MutexInterface } from 'async-mutex'; | ||
protected _writerCount: number; | ||
lock(type: 'read' | 'write', timeout?: number): ResourceAcquire<RWLockWriter>; | ||
lock(type?: 'read' | 'write', timeout?: number): ResourceAcquire<RWLockWriter>; | ||
read(timeout?: number): ResourceAcquire<RWLockWriter>; | ||
@@ -18,0 +18,0 @@ write(timeout?: number): ResourceAcquire<RWLockWriter>; |
@@ -19,3 +19,3 @@ "use strict"; | ||
} | ||
lock(type, timeout) { | ||
lock(type = 'write', timeout) { | ||
switch (type) { | ||
@@ -71,4 +71,8 @@ case 'read': | ||
} | ||
let released = false; | ||
return [ | ||
async () => { | ||
if (released) | ||
return; | ||
released = true; | ||
const readerCount = --this._readerCount; | ||
@@ -117,4 +121,8 @@ // The last reader unlocks | ||
} | ||
let released = false; | ||
return [ | ||
async () => { | ||
if (released) | ||
return; | ||
released = true; | ||
this.readersRelease(); | ||
@@ -121,0 +129,0 @@ writersRelease(); |
@@ -22,3 +22,3 @@ import type { ResourceAcquire } from '@matrixai/resources'; | ||
} | ||
declare type LockRequest<L extends Lockable = Lockable> = [ | ||
declare type MultiLockRequest<L extends Lockable = Lockable> = [ | ||
key: ToString, | ||
@@ -28,2 +28,12 @@ lockConstructor: new () => L, | ||
]; | ||
export type { POJO, ToString, Lockable, LockRequest }; | ||
declare type MultiLockAcquire<L extends Lockable = Lockable> = [ | ||
key: ToString, | ||
lockAcquire: ResourceAcquire<L>, | ||
...lockingParams: Parameters<L['lock']> | ||
]; | ||
declare type MultiLockAcquired<L extends Lockable = Lockable> = [ | ||
key: ToString, | ||
lock: L, | ||
...lockingParams: Parameters<L['lock']> | ||
]; | ||
export type { POJO, ToString, Lockable, MultiLockRequest, MultiLockAcquire, MultiLockAcquired, }; |
{ | ||
"name": "@matrixai/async-locks", | ||
"version": "2.3.1", | ||
"version": "3.0.0", | ||
"author": "Roger Qiu", | ||
@@ -5,0 +5,0 @@ "description": "Asynchronous locking utilities", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
72509
961