Commit | Line | Data |
---|---|---|
b9b617a2 JB |
1 | // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. |
2 | ||
95e03ad1 | 3 | import Queue from 'mnemonist/queue.js' |
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 | |
95e03ad1 | 21 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment |
66a7748d | 22 | this.resolveQueue = new Queue<ResolveType>() |
1227a6f1 JB |
23 | } |
24 | ||
0ebf7c2e | 25 | public static async runExclusive<T>(type: AsyncLockType, fn: () => T | Promise<T>): Promise<T> { |
66a7748d | 26 | return await AsyncLock.acquire(type) |
0ebf7c2e JB |
27 | .then(fn) |
28 | .finally(() => { | |
66a7748d JB |
29 | AsyncLock.release(type).catch(Constants.EMPTY_FUNCTION) |
30 | }) | |
0ebf7c2e JB |
31 | } |
32 | ||
66a7748d JB |
33 | private static async acquire (type: AsyncLockType): Promise<void> { |
34 | const asyncLock = AsyncLock.getAsyncLock(type) | |
dd485b56 | 35 | if (!asyncLock.acquired) { |
66a7748d JB |
36 | asyncLock.acquired = true |
37 | return | |
1227a6f1 | 38 | } |
66a7748d | 39 | await new Promise<void>((resolve) => { |
95e03ad1 | 40 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access |
66a7748d JB |
41 | asyncLock.resolveQueue.enqueue(resolve) |
42 | }) | |
1227a6f1 JB |
43 | } |
44 | ||
66a7748d JB |
45 | private static async release (type: AsyncLockType): Promise<void> { |
46 | const asyncLock = AsyncLock.getAsyncLock(type) | |
95e03ad1 | 47 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access |
4f9327bf | 48 | if (asyncLock.resolveQueue.size === 0 && asyncLock.acquired) { |
66a7748d JB |
49 | asyncLock.acquired = false |
50 | return | |
1227a6f1 | 51 | } |
95e03ad1 | 52 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access |
66a7748d JB |
53 | const queuedResolve = asyncLock.resolveQueue.dequeue()! |
54 | await new Promise<void>((resolve) => { | |
95e03ad1 | 55 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call |
66a7748d JB |
56 | queuedResolve() |
57 | resolve() | |
58 | }) | |
1227a6f1 | 59 | } |
dd485b56 | 60 | |
66a7748d | 61 | private static getAsyncLock (type: AsyncLockType): AsyncLock { |
dd485b56 | 62 | if (!AsyncLock.asyncLocks.has(type)) { |
66a7748d | 63 | AsyncLock.asyncLocks.set(type, new AsyncLock()) |
dd485b56 | 64 | } |
66a7748d JB |
65 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
66 | return AsyncLock.asyncLocks.get(type)! | |
dd485b56 | 67 | } |
1227a6f1 | 68 | } |