From 0ebf7c2e12d3087edb301d92baffc4597bb34ebe Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Wed, 9 Aug 2023 13:57:07 +0200 Subject: [PATCH] refactor: introduce an async lock mutex helper to run exclusively a code block MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/charging-station/ChargingStation.ts | 55 +++++++++------------- src/performance/storage/JsonFileStorage.ts | 40 +++++++--------- src/utils/AsyncLock.ts | 14 +++++- 3 files changed, 51 insertions(+), 58 deletions(-) diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 6e5ca1de..f1e0e022 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -1,15 +1,7 @@ // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. import { createHash } from 'node:crypto'; -import { - type FSWatcher, - closeSync, - existsSync, - mkdirSync, - openSync, - readFileSync, - writeFileSync, -} from 'node:fs'; +import { type FSWatcher, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { URL } from 'node:url'; import { parentPort } from 'node:worker_threads'; @@ -1699,30 +1691,27 @@ export class ChargingStation { ) .digest('hex'); if (this.configurationFileHash !== configurationHash) { - AsyncLock.acquire(AsyncLockType.configuration) - .then(() => { - configurationData.configurationHash = configurationHash; - const measureId = `${FileType.ChargingStationConfiguration} write`; - const beginId = PerformanceStatistics.beginMeasure(measureId); - const fileDescriptor = openSync(this.configurationFile, 'w'); - writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8'); - closeSync(fileDescriptor); - PerformanceStatistics.endMeasure(measureId, beginId); - this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash); - this.sharedLRUCache.setChargingStationConfiguration(configurationData); - this.configurationFileHash = configurationHash; - }) - .catch((error) => { - handleFileException( - this.configurationFile, - FileType.ChargingStationConfiguration, - error as NodeJS.ErrnoException, - this.logPrefix(), - ); - }) - .finally(() => { - AsyncLock.release(AsyncLockType.configuration).catch(Constants.EMPTY_FUNCTION); - }); + AsyncLock.runExclusive(AsyncLockType.configuration, () => { + configurationData.configurationHash = configurationHash; + const measureId = `${FileType.ChargingStationConfiguration} write`; + const beginId = PerformanceStatistics.beginMeasure(measureId); + writeFileSync( + this.configurationFile, + JSON.stringify(configurationData, null, 2), + 'utf8', + ); + PerformanceStatistics.endMeasure(measureId, beginId); + this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash); + this.sharedLRUCache.setChargingStationConfiguration(configurationData); + this.configurationFileHash = configurationHash; + }).catch((error) => { + handleFileException( + this.configurationFile, + FileType.ChargingStationConfiguration, + error as NodeJS.ErrnoException, + this.logPrefix(), + ); + }); } else { logger.debug( `${this.logPrefix()} Not saving unchanged charging station configuration file ${ diff --git a/src/performance/storage/JsonFileStorage.ts b/src/performance/storage/JsonFileStorage.ts index 2f6e4987..44366b4d 100644 --- a/src/performance/storage/JsonFileStorage.ts +++ b/src/performance/storage/JsonFileStorage.ts @@ -9,7 +9,6 @@ import { FileType, type Statistics } from '../../types'; import { AsyncLock, AsyncLockType, - Constants, JSONStringifyWithMapSupport, handleFileException, isNullOrUndefined, @@ -30,27 +29,22 @@ export class JsonFileStorage extends Storage { public storePerformanceStatistics(performanceStatistics: Statistics): void { this.checkPerformanceRecordsFile(); - AsyncLock.acquire(AsyncLockType.performance) - .then(() => { - JsonFileStorage.performanceRecords.set(performanceStatistics.id, performanceStatistics); - writeSync( - this.fd!, - JSONStringifyWithMapSupport([...JsonFileStorage.performanceRecords.values()], 2), - 0, - 'utf8', - ); - }) - .catch((error) => { - handleFileException( - this.dbName, - FileType.PerformanceRecords, - error as NodeJS.ErrnoException, - this.logPrefix, - ); - }) - .finally(() => { - AsyncLock.release(AsyncLockType.performance).catch(Constants.EMPTY_FUNCTION); - }); + AsyncLock.runExclusive(AsyncLockType.performance, () => { + JsonFileStorage.performanceRecords.set(performanceStatistics.id, performanceStatistics); + writeSync( + this.fd!, + JSONStringifyWithMapSupport([...JsonFileStorage.performanceRecords.values()], 2), + 0, + 'utf8', + ); + }).catch((error) => { + handleFileException( + this.dbName, + FileType.PerformanceRecords, + error as NodeJS.ErrnoException, + this.logPrefix, + ); + }); } public open(): void { @@ -59,7 +53,7 @@ export class JsonFileStorage extends Storage { if (!existsSync(dirname(this.dbName))) { mkdirSync(dirname(this.dbName), { recursive: true }); } - this.fd = openSync(this.dbName, 'w+'); + this.fd = openSync(this.dbName, 'w'); } } catch (error) { handleFileException( diff --git a/src/utils/AsyncLock.ts b/src/utils/AsyncLock.ts index 9c058456..fe984ceb 100644 --- a/src/utils/AsyncLock.ts +++ b/src/utils/AsyncLock.ts @@ -2,6 +2,8 @@ import Queue from 'mnemonist/queue.js'; +import { Constants } from './Constants'; + export enum AsyncLockType { configuration = 'configuration', performance = 'performance', @@ -19,7 +21,15 @@ export class AsyncLock { this.resolveQueue = new Queue(); } - public static async acquire(type: AsyncLockType): Promise { + public static async runExclusive(type: AsyncLockType, fn: () => T | Promise): Promise { + return AsyncLock.acquire(type) + .then(fn) + .finally(() => { + AsyncLock.release(type).catch(Constants.EMPTY_FUNCTION); + }); + } + + private static async acquire(type: AsyncLockType): Promise { const asyncLock = AsyncLock.getAsyncLock(type); if (!asyncLock.acquired) { asyncLock.acquired = true; @@ -30,7 +40,7 @@ export class AsyncLock { }); } - public static async release(type: AsyncLockType): Promise { + private static async release(type: AsyncLockType): Promise { const asyncLock = AsyncLock.getAsyncLock(type); if (asyncLock.resolveQueue.size === 0 && asyncLock.acquired) { asyncLock.acquired = false; -- 2.34.1