+// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
+
+import { Queue } from 'mnemonist'
+
+import { Constants } from './Constants.js'
+
export enum AsyncLockType {
configuration = 'configuration',
- performance = 'performance',
+ performance = 'performance'
}
+type ResolveType = (value: void | PromiseLike<void>) => void
+
export class AsyncLock {
- private static readonly instances = new Map<AsyncLockType, AsyncLock>();
- private acquired: boolean;
- private readonly resolveQueue: ((value: void | PromiseLike<void>) => void)[];
+ private static readonly asyncLocks = new Map<AsyncLockType, AsyncLock>()
+ private acquired: boolean
+ private readonly resolveQueue: Queue<ResolveType>
+
+ private constructor () {
+ this.acquired = false
+ this.resolveQueue = new Queue<ResolveType>()
+ }
- private constructor(private readonly type: AsyncLockType) {
- this.acquired = false;
- this.resolveQueue = [];
+ public static async runExclusive<T>(type: AsyncLockType, fn: () => T | Promise<T>): Promise<T> {
+ return await AsyncLock.acquire(type)
+ .then(fn)
+ .finally(() => {
+ AsyncLock.release(type).catch(Constants.EMPTY_FUNCTION)
+ })
}
- public static getInstance(type: AsyncLockType): AsyncLock {
- if (!AsyncLock.instances.has(type)) {
- AsyncLock.instances.set(type, new AsyncLock(type));
+ private static async acquire (type: AsyncLockType): Promise<void> {
+ const asyncLock = AsyncLock.getAsyncLock(type)
+ if (!asyncLock.acquired) {
+ asyncLock.acquired = true
+ return
}
- return AsyncLock.instances.get(type);
+ await new Promise<void>(resolve => {
+ asyncLock.resolveQueue.enqueue(resolve)
+ })
}
- public async acquire(): Promise<void> {
- if (!this.acquired) {
- this.acquired = true;
- } else {
- return new Promise((resolve) => {
- this.resolveQueue.push(resolve);
- });
+ private static async release (type: AsyncLockType): Promise<void> {
+ const asyncLock = AsyncLock.getAsyncLock(type)
+ if (asyncLock.resolveQueue.size === 0 && asyncLock.acquired) {
+ asyncLock.acquired = false
+ return
}
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const queuedResolve = asyncLock.resolveQueue.dequeue()!
+ await new Promise<void>(resolve => {
+ queuedResolve()
+ resolve()
+ })
}
- public async release(): Promise<void> {
- if (this.resolveQueue.length === 0 && this.acquired) {
- this.acquired = false;
- return;
+ private static getAsyncLock (type: AsyncLockType): AsyncLock {
+ if (!AsyncLock.asyncLocks.has(type)) {
+ AsyncLock.asyncLocks.set(type, new AsyncLock())
}
- const queuedResolve = this.resolveQueue.shift();
- return new Promise((resolve) => {
- queuedResolve();
- resolve();
- });
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ return AsyncLock.asyncLocks.get(type)!
}
}