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