From: Jérôme Benoit Date: Wed, 20 Mar 2024 19:29:37 +0000 (+0100) Subject: feat: ensure charging station add op return its station info X-Git-Tag: v1.3.1~24 X-Git-Url: https://git.piment-noir.org/?p=e-mobility-charging-stations-simulator.git;a=commitdiff_plain;h=3b09e788c6dab8e5a8b3314e8e69ede16ff82fcc feat: ensure charging station add op return its station info Signed-off-by: Jérôme Benoit --- diff --git a/README.md b/README.md index 739f33f3..8f55b2cc 100644 --- a/README.md +++ b/README.md @@ -638,6 +638,8 @@ Set the Websocket header _Sec-Websocket-Protocol_ to `ui0.0.1`. - Response: `PDU`: { `status`: 'success' | 'failure' + `hashIdsSucceeded`: charging station unique identifier strings array (optional), + `hashIdsFailed`: charging station unique identifier strings array (optional) } ###### Delete Charging Stations diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index 34ac90f3..4f72d6df 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -15,6 +15,7 @@ import { BaseError } from '../exception/index.js' import { type Storage, StorageFactory } from '../performance/index.js' import { type ChargingStationData, + type ChargingStationInfo, type ChargingStationOptions, type ChargingStationWorkerData, type ChargingStationWorkerEventError, @@ -59,7 +60,7 @@ enum exitCodes { export class Bootstrap extends EventEmitter { private static instance: Bootstrap | null = null - private workerImplementation?: WorkerAbstract + private workerImplementation?: WorkerAbstract private readonly uiServer: AbstractUIServer private storage?: Storage private readonly templateStatistics: Map @@ -346,7 +347,10 @@ export class Bootstrap extends EventEmitter { : 1 break } - this.workerImplementation = WorkerFactory.getWorkerImplementation( + this.workerImplementation = WorkerFactory.getWorkerImplementation< + ChargingStationWorkerData, + ChargingStationInfo + >( join( dirname(fileURLToPath(import.meta.url)), `ChargingStationWorker${extname(fileURLToPath(import.meta.url))}` @@ -541,13 +545,13 @@ export class Bootstrap extends EventEmitter { index: number, templateFile: string, options?: ChargingStationOptions - ): Promise { + ): Promise { if (!this.started && !this.starting) { throw new BaseError( 'Cannot add charging station while the charging stations simulator is not started' ) } - await this.workerImplementation?.addElement({ + const stationInfo = await this.workerImplementation?.addElement({ index, templateFile: join( dirname(fileURLToPath(import.meta.url)), @@ -561,6 +565,7 @@ export class Bootstrap extends EventEmitter { const templateStatistics = this.templateStatistics.get(buildTemplateName(templateFile))! ++templateStatistics.added templateStatistics.indexes.add(index) + return stationInfo } private gracefulShutdown (): void { diff --git a/src/charging-station/ChargingStationWorker.ts b/src/charging-station/ChargingStationWorker.ts index 35a7e534..042d3d5b 100644 --- a/src/charging-station/ChargingStationWorker.ts +++ b/src/charging-station/ChargingStationWorker.ts @@ -6,23 +6,24 @@ import { ThreadWorker } from 'poolifier' import { BaseError } from '../exception/index.js' import type { - ChargingStationData, + ChargingStationInfo, ChargingStationWorkerData, ChargingStationWorkerEventError, ChargingStationWorkerMessage } from '../types/index.js' -import { buildChargingStationDataPayload, Configuration } from '../utils/index.js' +import { Configuration } from '../utils/index.js' import { type WorkerMessage, WorkerMessageEvents } from '../worker/index.js' import { ChargingStation } from './ChargingStation.js' export let chargingStationWorker: object if (Configuration.workerPoolInUse()) { - chargingStationWorker = new ThreadWorker( - (data?: ChargingStationWorkerData): void => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, no-new - new ChargingStation(data!.index, data!.templateFile, data!.options) - } - ) + chargingStationWorker = new ThreadWorker< + ChargingStationWorkerData, + ChargingStationInfo | undefined + >((data?: ChargingStationWorkerData): ChargingStationInfo | undefined => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, no-new + return new ChargingStation(data!.index, data!.templateFile, data!.options).stationInfo + }) } else { // eslint-disable-next-line @typescript-eslint/no-extraneous-class class ChargingStationWorker { @@ -38,13 +39,14 @@ if (Configuration.workerPoolInUse()) { ) parentPort?.postMessage({ event: WorkerMessageEvents.addedWorkerElement, - data: buildChargingStationDataPayload(chargingStation) - } satisfies ChargingStationWorkerMessage) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + data: chargingStation.stationInfo! + } satisfies ChargingStationWorkerMessage) } catch (error) { parentPort?.postMessage({ event: WorkerMessageEvents.workerElementError, data: { - event: WorkerMessageEvents.addWorkerElement, + event: message.event, name: (error as Error).name, message: (error as Error).message, stack: (error as Error).stack diff --git a/src/charging-station/ui-server/ui-services/AbstractUIService.ts b/src/charging-station/ui-server/ui-services/AbstractUIService.ts index 6aed70a1..6652def2 100644 --- a/src/charging-station/ui-server/ui-services/AbstractUIService.ts +++ b/src/charging-station/ui-server/ui-services/AbstractUIService.ts @@ -2,6 +2,7 @@ import { BaseError, type OCPPError } from '../../../exception/index.js' import { BroadcastChannelProcedureName, type BroadcastChannelRequestPayload, + type ChargingStationInfo, type ChargingStationOptions, ConfigurationSection, type JsonObject, @@ -275,23 +276,30 @@ export abstract class AbstractUIService { errorMessage: `Template '${template}' not found` } satisfies ResponsePayload } + const stationInfos: ChargingStationInfo[] = [] for (let i = 0; i < numberOfStations; i++) { + let stationInfo: ChargingStationInfo | undefined try { - await Bootstrap.getInstance().addChargingStation( + stationInfo = await Bootstrap.getInstance().addChargingStation( Bootstrap.getInstance().getLastIndex(template) + 1, `${template}.json`, options ) + if (stationInfo != null) { + stationInfos.push(stationInfo) + } } catch (error) { return { status: ResponseStatus.FAILURE, + ...(stationInfo?.hashId != null && { hashIdsFailed: [stationInfo.hashId] }), errorMessage: (error as Error).message, errorStack: (error as Error).stack } satisfies ResponsePayload } } return { - status: ResponseStatus.SUCCESS + status: ResponseStatus.SUCCESS, + hashIdsSucceeded: stationInfos.map(stationInfo => stationInfo.hashId) } } diff --git a/src/types/ChargingStationWorker.ts b/src/types/ChargingStationWorker.ts index 861d447e..c6fd0af2 100644 --- a/src/types/ChargingStationWorker.ts +++ b/src/types/ChargingStationWorker.ts @@ -70,6 +70,7 @@ export interface ChargingStationWorkerEventError extends WorkerData { } export type ChargingStationWorkerMessageData = + | ChargingStationInfo | ChargingStationData | Statistics | ChargingStationWorkerEventError diff --git a/src/worker/WorkerAbstract.ts b/src/worker/WorkerAbstract.ts index 907d0bd0..f6713f89 100644 --- a/src/worker/WorkerAbstract.ts +++ b/src/worker/WorkerAbstract.ts @@ -5,7 +5,7 @@ import type { PoolInfo } from 'poolifier' import type { SetInfo, WorkerData, WorkerOptions } from './WorkerTypes.js' -export abstract class WorkerAbstract { +export abstract class WorkerAbstract { protected readonly workerScript: string protected readonly workerOptions: WorkerOptions public abstract readonly info: PoolInfo | SetInfo @@ -49,5 +49,5 @@ export abstract class WorkerAbstract { * * @param elementData - */ - public abstract addElement (elementData: T): Promise + public abstract addElement (elementData: D): Promise } diff --git a/src/worker/WorkerDynamicPool.ts b/src/worker/WorkerDynamicPool.ts index 20432973..5217d7fe 100644 --- a/src/worker/WorkerDynamicPool.ts +++ b/src/worker/WorkerDynamicPool.ts @@ -6,8 +6,11 @@ import { WorkerAbstract } from './WorkerAbstract.js' import type { WorkerData, WorkerOptions } from './WorkerTypes.js' import { randomizeDelay, sleep } from './WorkerUtils.js' -export class WorkerDynamicPool extends WorkerAbstract { - private readonly pool: DynamicThreadPool +export class WorkerDynamicPool extends WorkerAbstract< +D, +R +> { + private readonly pool: DynamicThreadPool /** * Creates a new `WorkerDynamicPool`. @@ -17,7 +20,7 @@ export class WorkerDynamicPool extends WorkerAbstract { */ constructor (workerScript: string, workerOptions: WorkerOptions) { super(workerScript, workerOptions) - this.pool = new DynamicThreadPool( + this.pool = new DynamicThreadPool( this.workerOptions.poolMinSize, this.workerOptions.poolMaxSize, this.workerScript, @@ -52,12 +55,13 @@ export class WorkerDynamicPool extends WorkerAbstract { } /** @inheritDoc */ - public async addElement (elementData: WorkerData): Promise { - await this.pool.execute(elementData) + public async addElement (elementData: D): Promise { + const response = await this.pool.execute(elementData) // Start element sequentially to optimize memory at startup // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.workerOptions.elementAddDelay! > 0 && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion (await sleep(randomizeDelay(this.workerOptions.elementAddDelay!))) + return response } } diff --git a/src/worker/WorkerFactory.ts b/src/worker/WorkerFactory.ts index 67da00d8..73bcd56d 100644 --- a/src/worker/WorkerFactory.ts +++ b/src/worker/WorkerFactory.ts @@ -15,30 +15,25 @@ export class WorkerFactory { // This is intentional } - public static getWorkerImplementation( + public static getWorkerImplementation( workerScript: string, workerProcessType: WorkerProcessType, workerOptions?: WorkerOptions - ): WorkerAbstract | undefined { + ): WorkerAbstract { if (!isMainThread) { throw new Error('Cannot get a worker implementation outside the main thread') } workerOptions = mergeDeepRight(DEFAULT_WORKER_OPTIONS, workerOptions ?? {}) - let workerImplementation: WorkerAbstract switch (workerProcessType) { case WorkerProcessType.workerSet: - workerImplementation = new WorkerSet(workerScript, workerOptions) - break + return new WorkerSet(workerScript, workerOptions) case WorkerProcessType.fixedPool: - workerImplementation = new WorkerFixedPool(workerScript, workerOptions) - break + return new WorkerFixedPool(workerScript, workerOptions) case WorkerProcessType.dynamicPool: - workerImplementation = new WorkerDynamicPool(workerScript, workerOptions) - break + return new WorkerDynamicPool(workerScript, workerOptions) default: // eslint-disable-next-line @typescript-eslint/restrict-template-expressions throw new Error(`Worker implementation type '${workerProcessType}' not found`) } - return workerImplementation } } diff --git a/src/worker/WorkerFixedPool.ts b/src/worker/WorkerFixedPool.ts index 22290666..96060854 100644 --- a/src/worker/WorkerFixedPool.ts +++ b/src/worker/WorkerFixedPool.ts @@ -6,8 +6,11 @@ import { WorkerAbstract } from './WorkerAbstract.js' import type { WorkerData, WorkerOptions } from './WorkerTypes.js' import { randomizeDelay, sleep } from './WorkerUtils.js' -export class WorkerFixedPool extends WorkerAbstract { - private readonly pool: FixedThreadPool +export class WorkerFixedPool extends WorkerAbstract< +D, +R +> { + private readonly pool: FixedThreadPool /** * Creates a new `WorkerFixedPool`. @@ -17,7 +20,7 @@ export class WorkerFixedPool extends WorkerAbstract { */ constructor (workerScript: string, workerOptions: WorkerOptions) { super(workerScript, workerOptions) - this.pool = new FixedThreadPool( + this.pool = new FixedThreadPool( this.workerOptions.poolMaxSize, this.workerScript, this.workerOptions.poolOptions @@ -51,12 +54,13 @@ export class WorkerFixedPool extends WorkerAbstract { } /** @inheritDoc */ - public async addElement (elementData: WorkerData): Promise { - await this.pool.execute(elementData) + public async addElement (elementData: D): Promise { + const response = await this.pool.execute(elementData) // Start element sequentially to optimize memory at startup // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.workerOptions.elementAddDelay! > 0 && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion (await sleep(randomizeDelay(this.workerOptions.elementAddDelay!))) + return response } } diff --git a/src/worker/WorkerSet.ts b/src/worker/WorkerSet.ts index d0f80b6e..ba2b10ad 100644 --- a/src/worker/WorkerSet.ts +++ b/src/worker/WorkerSet.ts @@ -16,7 +16,7 @@ import { } from './WorkerTypes.js' import { randomizeDelay, sleep } from './WorkerUtils.js' -export class WorkerSet extends WorkerAbstract { +export class WorkerSet extends WorkerAbstract { public readonly emitter: EventEmitterAsyncResource | undefined private readonly workerSet: Set private started: boolean @@ -102,22 +102,37 @@ export class WorkerSet extends WorkerAbstract { } /** @inheritDoc */ - public async addElement (elementData: WorkerData): Promise { + public async addElement (elementData: D): Promise { if (!this.started) { throw new Error('Cannot add a WorkerSet element: not started') } const workerSetElement = await this.getWorkerSetElement() + const waitForAddedWorkerElement = new Promise((resolve, reject) => { + const messageHandler = (message: WorkerMessage): void => { + if (message.event === WorkerMessageEvents.addedWorkerElement) { + ++workerSetElement.numberOfWorkerElements + resolve(message.data) + workerSetElement.worker.off('message', messageHandler) + } else if (message.event === WorkerMessageEvents.workerElementError) { + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors + reject(message.data) + workerSetElement.worker.off('message', messageHandler) + } + } + workerSetElement.worker.on('message', messageHandler) + }) workerSetElement.worker.postMessage({ event: WorkerMessageEvents.addWorkerElement, data: elementData }) - ++workerSetElement.numberOfWorkerElements + const response = await waitForAddedWorkerElement // Add element sequentially to optimize memory at startup // eslint-disable-next-line @typescript-eslint/no-non-null-assertion if (this.workerOptions.elementAddDelay! > 0) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await sleep(randomizeDelay(this.workerOptions.elementAddDelay!)) } + return response } /** @@ -130,7 +145,7 @@ export class WorkerSet extends WorkerAbstract { ...this.workerOptions.poolOptions?.workerOptions }) worker.on('message', this.workerOptions.poolOptions?.messageHandler ?? EMPTY_FUNCTION) - worker.on('message', (message: WorkerMessage) => { + worker.on('message', (message: WorkerMessage) => { if (message.event === WorkerMessageEvents.addedWorkerElement) { this.emitter?.emit(WorkerSetEvents.elementAdded, this.info) } else if (message.event === WorkerMessageEvents.workerElementError) {