build: switch to NodeNext module resolution
[e-mobility-charging-stations-simulator.git] / src / utils / AsyncLock.ts
1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
2
3 import Queue from 'mnemonist/queue.js';
4
5 import { Constants } from './Constants.js';
6
7 export enum AsyncLockType {
8 configuration = 'configuration',
9 performance = 'performance',
10 }
11
12 type ResolveType = (value: void | PromiseLike<void>) => void;
13
14 export class AsyncLock {
15 private static readonly asyncLocks = new Map<AsyncLockType, AsyncLock>();
16 private acquired: boolean;
17 private readonly resolveQueue: Queue<ResolveType>;
18
19 private constructor() {
20 this.acquired = false;
21 this.resolveQueue = new Queue<ResolveType>();
22 }
23
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> {
33 const asyncLock = AsyncLock.getAsyncLock(type);
34 if (!asyncLock.acquired) {
35 asyncLock.acquired = true;
36 return;
37 }
38 return new Promise<void>((resolve) => {
39 asyncLock.resolveQueue.enqueue(resolve);
40 });
41 }
42
43 private static async release(type: AsyncLockType): Promise<void> {
44 const asyncLock = AsyncLock.getAsyncLock(type);
45 if (asyncLock.resolveQueue.size === 0 && asyncLock.acquired) {
46 asyncLock.acquired = false;
47 return;
48 }
49 const queuedResolve = asyncLock.resolveQueue.dequeue()!;
50 return new Promise<void>((resolve) => {
51 queuedResolve();
52 resolve();
53 });
54 }
55
56 private static getAsyncLock(type: AsyncLockType): AsyncLock {
57 if (!AsyncLock.asyncLocks.has(type)) {
58 AsyncLock.asyncLocks.set(type, new AsyncLock());
59 }
60 return AsyncLock.asyncLocks.get(type)!;
61 }
62 }