refactor: introduce an async lock mutex helper to run exclusively a code
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 9 Aug 2023 11:57:07 +0000 (13:57 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 9 Aug 2023 11:57:07 +0000 (13:57 +0200)
block

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/ChargingStation.ts
src/performance/storage/JsonFileStorage.ts
src/utils/AsyncLock.ts

index 6e5ca1dec8f9a8504edf31349132ed310dc42591..f1e0e022ce842c023c962714805c93d271627bbf 100644 (file)
@@ -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 ${
index 2f6e498749f5751f2364cc62716b3cfa8904872f..44366b4da6507aafeea783271328b53ec1632bbe 100644 (file)
@@ -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(
index 9c0584565e3ecb80897b2ad32418a1afdb0cc888..fe984cebdef97d8043d0f29f6fb9ba99ec502f3c 100644 (file)
@@ -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<ResolveType>();
   }
 
-  public static async acquire(type: AsyncLockType): Promise<void> {
+  public static async runExclusive<T>(type: AsyncLockType, fn: () => T | Promise<T>): Promise<T> {
+    return AsyncLock.acquire(type)
+      .then(fn)
+      .finally(() => {
+        AsyncLock.release(type).catch(Constants.EMPTY_FUNCTION);
+      });
+  }
+
+  private static async acquire(type: AsyncLockType): Promise<void> {
     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<void> {
+  private static async release(type: AsyncLockType): Promise<void> {
     const asyncLock = AsyncLock.getAsyncLock(type);
     if (asyncLock.resolveQueue.size === 0 && asyncLock.acquired) {
       asyncLock.acquired = false;