"moment": "^2.29.4",
"mongodb": "^5.3.0",
"poolifier": "^2.4.11",
- "proper-lockfile": "^4.1.2",
"source-map-support": "^0.5.21",
"tar": "^6.1.13",
"tslib": "^2.5.0",
"@types/mocha": "^10.0.1",
"@types/mochawesome": "^6.2.1",
"@types/node": "^18.16.3",
- "@types/proper-lockfile": "^4.1.2",
"@types/sinon": "^10.0.14",
"@types/tar": "^6.1.4",
"@types/ws": "^8.5.4",
poolifier:
specifier: ^2.4.11
version: 2.4.11
- proper-lockfile:
- specifier: ^4.1.2
- version: 4.1.2
source-map-support:
specifier: ^0.5.21
version: 0.5.21
'@types/node':
specifier: ^18.16.3
version: 18.16.3
- '@types/proper-lockfile':
- specifier: ^4.1.2
- version: 4.1.2
'@types/sinon':
specifier: ^10.0.14
version: 10.0.14
resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==}
dev: true
- /@types/proper-lockfile@4.1.2:
- resolution: {integrity: sha512-kd4LMvcnpYkspDcp7rmXKedn8iJSCoa331zRRamUp5oanKt/CefbEGPQP7G89enz7sKD4bvsr8mHSsC8j5WOvA==}
- dependencies:
- '@types/retry': 0.12.2
- dev: true
-
/@types/responselike@1.0.0:
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
dependencies:
'@types/node': 18.16.3
dev: true
- /@types/retry@0.12.2:
- resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==}
- dev: true
-
/@types/seedrandom@2.4.30:
resolution: {integrity: sha512-AnxLHewubLVzoF/A4qdxBGHCKifw8cY32iro3DQX9TPcetE95zBeVt3jnsvtvAUf1vwzMfwzp4t/L2yqPlnjkQ==}
dev: true
react-is: 16.13.1
dev: true
- /proper-lockfile@4.1.2:
- resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==}
- dependencies:
- graceful-fs: 4.2.11
- retry: 0.12.0
- signal-exit: 3.0.7
- dev: false
-
/proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
dev: true
/retry@0.12.0:
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
engines: {node: '>= 4'}
+ optional: true
/retry@0.13.1:
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
'node:util',
'node:worker_threads',
'poolifier',
- 'proper-lockfile',
'tar',
'winston',
'winston-daily-rotate-file',
} from '../types';
import {
ACElectricUtils,
+ AsyncLock,
+ AsyncLockType,
Configuration,
Constants,
DCElectricUtils,
.update(JSON.stringify(configurationData))
.digest('hex');
if (this.configurationFileHash !== configurationHash) {
- configurationData.configurationHash = configurationHash;
- const measureId = `${FileType.ChargingStationConfiguration} write`;
- const beginId = PerformanceStatistics.beginMeasure(measureId);
- const fileDescriptor = fs.openSync(this.configurationFile, 'w');
- fs.writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8');
- fs.closeSync(fileDescriptor);
- PerformanceStatistics.endMeasure(measureId, beginId);
- this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
- this.sharedLRUCache.setChargingStationConfiguration(configurationData);
- this.configurationFileHash = configurationHash;
+ const asyncLock = AsyncLock.getInstance(AsyncLockType.configuration);
+ asyncLock
+ .acquire()
+ .then(() => {
+ configurationData.configurationHash = configurationHash;
+ const measureId = `${FileType.ChargingStationConfiguration} write`;
+ const beginId = PerformanceStatistics.beginMeasure(measureId);
+ const fileDescriptor = fs.openSync(this.configurationFile, 'w');
+ fs.writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8');
+ fs.closeSync(fileDescriptor);
+ PerformanceStatistics.endMeasure(measureId, beginId);
+ this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
+ this.sharedLRUCache.setChargingStationConfiguration(configurationData);
+ this.configurationFileHash = configurationHash;
+ })
+ .catch((error) => {
+ FileUtils.handleFileException(
+ this.configurationFile,
+ FileType.ChargingStationConfiguration,
+ error as NodeJS.ErrnoException,
+ this.logPrefix()
+ );
+ })
+ .finally(() => {
+ asyncLock.release().catch(Constants.EMPTY_FUNCTION);
+ });
} else {
logger.debug(
`${this.logPrefix()} Not saving unchanged charging station configuration file ${
import fs from 'node:fs';
-import lockfile from 'proper-lockfile';
-
import { FileType, type Statistics } from '../../types';
-import { Constants, FileUtils, Utils } from '../../utils';
+import { AsyncLock, AsyncLockType, Constants, FileUtils, Utils } from '../../utils';
import { Storage } from '../internal';
export class JsonFileStorage extends Storage {
public storePerformanceStatistics(performanceStatistics: Statistics): void {
this.checkPerformanceRecordsFile();
- lockfile
- .lock(this.dbName, { stale: 5000, retries: 3 })
- .then(async (release) => {
- try {
- const fileData = fs.readFileSync(this.dbName, 'utf8');
- const performanceRecords: Statistics[] = fileData
- ? (JSON.parse(fileData) as Statistics[])
- : [];
- performanceRecords.push(performanceStatistics);
- fs.writeFileSync(
- this.dbName,
- Utils.JSONStringifyWithMapSupport(performanceRecords, 2),
- 'utf8'
- );
- } catch (error) {
- FileUtils.handleFileException(
- this.dbName,
- FileType.PerformanceRecords,
- error as NodeJS.ErrnoException,
- this.logPrefix
- );
- }
- await release();
+ const asyncLock = AsyncLock.getInstance(AsyncLockType.performance);
+ asyncLock
+ .acquire()
+ .then(() => {
+ const fileData = fs.readFileSync(this.dbName, 'utf8');
+ const performanceRecords: Statistics[] = fileData
+ ? (JSON.parse(fileData) as Statistics[])
+ : [];
+ performanceRecords.push(performanceStatistics);
+ fs.writeFileSync(
+ this.dbName,
+ Utils.JSONStringifyWithMapSupport(performanceRecords, 2),
+ 'utf8'
+ );
+ })
+ .catch((error) => {
+ FileUtils.handleFileException(
+ this.dbName,
+ FileType.PerformanceRecords,
+ error as NodeJS.ErrnoException,
+ this.logPrefix
+ );
})
- .catch(Constants.EMPTY_FUNCTION);
+ .finally(() => {
+ asyncLock.release().catch(Constants.EMPTY_FUNCTION);
+ });
}
public open(): void {
--- /dev/null
+export enum AsyncLockType {
+ configuration = 'configuration',
+ performance = 'performance',
+}
+
+export class AsyncLock {
+ private static readonly instances = new Map<AsyncLockType, AsyncLock>();
+ private acquired = false;
+ private readonly resolveQueue: ((value: void | PromiseLike<void>) => void)[];
+
+ private constructor(private readonly type: AsyncLockType) {
+ this.acquired = false;
+ this.resolveQueue = [];
+ }
+
+ public static getInstance(type: AsyncLockType): AsyncLock {
+ if (!AsyncLock.instances.has(type)) {
+ AsyncLock.instances.set(type, new AsyncLock(type));
+ }
+ return AsyncLock.instances.get(type);
+ }
+
+ public async acquire(): Promise<void> {
+ if (!this.acquired) {
+ this.acquired = true;
+ } else {
+ return new Promise((resolve) => {
+ this.resolveQueue.push(resolve);
+ });
+ }
+ }
+
+ public async release(): Promise<void> {
+ if (this.resolveQueue.length === 0 && this.acquired) {
+ this.acquired = false;
+ return;
+ }
+ const queuedResolve = this.resolveQueue.shift();
+ return new Promise((resolve) => {
+ queuedResolve();
+ resolve();
+ });
+ }
+}
export {
ACElectricUtils,
+ AsyncLock,
+ AsyncLockType,
CircularArray,
Configuration,
Constants,
+export * from './AsyncLock';
export * from './CircularArray';
export * from './Constants';
export * from './ElectricUtils';