Commit | Line | Data |
---|---|---|
b9b617a2 JB |
1 | // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. |
2 | ||
f31d1d0c | 3 | import Queue from 'mnemonist/queue.js'; |
4f9327bf | 4 | |
0ebf7c2e JB |
5 | import { Constants } from './Constants'; |
6 | ||
1227a6f1 JB |
7 | export enum AsyncLockType { |
8 | configuration = 'configuration', | |
9 | performance = 'performance', | |
10 | } | |
11 | ||
4f9327bf JB |
12 | type ResolveType = (value: void | PromiseLike<void>) => void; |
13 | ||
1227a6f1 | 14 | export class AsyncLock { |
dd485b56 | 15 | private static readonly asyncLocks = new Map<AsyncLockType, AsyncLock>(); |
7e0bf360 | 16 | private acquired: boolean; |
4f9327bf | 17 | private readonly resolveQueue: Queue<ResolveType>; |
1227a6f1 | 18 | |
42486f23 | 19 | private constructor() { |
1227a6f1 | 20 | this.acquired = false; |
4f9327bf | 21 | this.resolveQueue = new Queue<ResolveType>(); |
1227a6f1 JB |
22 | } |
23 | ||
0ebf7c2e JB |
24 | public static async runExclusive<T>(type: AsyncLockType, fn: () => T | Promise<T>): Promise<T> { |
25 | return AsyncLock.acquire(type) | |
26 | .then(fn) | |
27 | .finally(() => { | |
28 | AsyncLock.release(type).catch(Constants.EMPTY_FUNCTION); | |
29 | }); | |
30 | } | |
31 | ||
32 | private static async acquire(type: AsyncLockType): Promise<void> { | |
dd485b56 JB |
33 | const asyncLock = AsyncLock.getAsyncLock(type); |
34 | if (!asyncLock.acquired) { | |
35 | asyncLock.acquired = true; | |
acf727c7 | 36 | return; |
1227a6f1 | 37 | } |
474d4ffc | 38 | return new Promise<void>((resolve) => { |
4f9327bf | 39 | asyncLock.resolveQueue.enqueue(resolve); |
acf727c7 | 40 | }); |
1227a6f1 JB |
41 | } |
42 | ||
0ebf7c2e | 43 | private static async release(type: AsyncLockType): Promise<void> { |
dd485b56 | 44 | const asyncLock = AsyncLock.getAsyncLock(type); |
4f9327bf | 45 | if (asyncLock.resolveQueue.size === 0 && asyncLock.acquired) { |
dd485b56 | 46 | asyncLock.acquired = false; |
1227a6f1 JB |
47 | return; |
48 | } | |
e1d9a0f4 | 49 | const queuedResolve = asyncLock.resolveQueue.dequeue()!; |
474d4ffc | 50 | return new Promise<void>((resolve) => { |
1227a6f1 JB |
51 | queuedResolve(); |
52 | resolve(); | |
53 | }); | |
54 | } | |
dd485b56 JB |
55 | |
56 | private static getAsyncLock(type: AsyncLockType): AsyncLock { | |
57 | if (!AsyncLock.asyncLocks.has(type)) { | |
42486f23 | 58 | AsyncLock.asyncLocks.set(type, new AsyncLock()); |
dd485b56 | 59 | } |
e1d9a0f4 | 60 | return AsyncLock.asyncLocks.get(type)!; |
dd485b56 | 61 | } |
1227a6f1 | 62 | } |