From f130b8e620e8808516bbf80bc4247c81fae02186 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 26 May 2023 12:55:09 +0200 Subject: [PATCH] fix: wait for charging stations to be stopped at simulator stop MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/charging-station/Bootstrap.ts | 52 ++++++++++++++----- .../ui-server/AbstractUIServer.ts | 14 ++--- .../ui-services/AbstractUIService.ts | 15 +++--- 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index 4a6bd75c..db884ed8 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -1,5 +1,6 @@ // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. +import { EventEmitter } from 'node:events'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { type Worker, isMainThread } from 'node:worker_threads'; @@ -31,7 +32,7 @@ enum exitCodes { noChargingStationTemplates = 2, } -export class Bootstrap { +export class Bootstrap extends EventEmitter { private static instance: Bootstrap | null = null; public numberOfChargingStations!: number; public numberOfChargingStationTemplates!: number; @@ -45,10 +46,9 @@ export class Bootstrap { private readonly workerScript: string; private constructor() { + super(); for (const signal of ['SIGINT', 'SIGQUIT', 'SIGTERM']) { - process.on(signal, () => { - this.gracefulShutdown().catch(Constants.EMPTY_FUNCTION); - }); + process.on(signal, this.gracefulShutdown); } // Enable unconditionally for now ErrorUtils.handleUnhandledRejection(); @@ -130,10 +130,13 @@ export class Bootstrap { public async stop(): Promise { if (isMainThread && this.started === true) { await this.uiServer?.sendBroadcastChannelRequest( - Utils.generateUUID(), - ProcedureName.STOP_CHARGING_STATION, - Constants.EMPTY_FREEZED_OBJECT + this.uiServer.buildProtocolRequest( + Utils.generateUUID(), + ProcedureName.STOP_CHARGING_STATION, + Constants.EMPTY_FREEZED_OBJECT + ) ); + await this.waitForChargingStationsStopped(); await this.workerImplementation?.stop(); this.workerImplementation = null; this.uiServer?.stop(); @@ -183,15 +186,22 @@ export class Bootstrap { switch (msg.id) { case ChargingStationWorkerMessageEvents.started: this.workerEventStarted(msg.data as ChargingStationData); + this.emit(ChargingStationWorkerMessageEvents.started, msg.data as ChargingStationData); break; case ChargingStationWorkerMessageEvents.stopped: this.workerEventStopped(msg.data as ChargingStationData); + this.emit(ChargingStationWorkerMessageEvents.stopped, msg.data as ChargingStationData); break; case ChargingStationWorkerMessageEvents.updated: this.workerEventUpdated(msg.data as ChargingStationData); + this.emit(ChargingStationWorkerMessageEvents.updated, msg.data as ChargingStationData); break; case ChargingStationWorkerMessageEvents.performanceStatistics: this.workerEventPerformanceStatistics(msg.data as Statistics); + this.emit( + ChargingStationWorkerMessageEvents.performanceStatistics, + msg.data as Statistics + ); break; default: throw new BaseError( @@ -282,14 +292,28 @@ export class Bootstrap { }); } - private gracefulShutdown = async (): Promise => { + private gracefulShutdown = (): void => { console.info(`${chalk.green('Graceful shutdown')}`); - try { - await this.stop(); - process.exit(0); - } catch (error) { - process.exit(1); - } + this.stop() + .then(() => { + process.exit(0); + }) + .catch((error) => { + console.error(chalk.red('Error while stopping charging stations simulator:'), error); + process.exit(1); + }); + }; + + private waitForChargingStationsStopped = async (): Promise => { + return new Promise((resolve) => { + let stoppedEvents = 0; + this.on(ChargingStationWorkerMessageEvents.stopped, () => { + ++stoppedEvents; + if (stoppedEvents === this.numberOfChargingStations) { + resolve(); + } + }); + }); }; private logPrefix = (): string => { diff --git a/src/charging-station/ui-server/AbstractUIServer.ts b/src/charging-station/ui-server/AbstractUIServer.ts index 2f6f950b..af5c0129 100644 --- a/src/charging-station/ui-server/AbstractUIServer.ts +++ b/src/charging-station/ui-server/AbstractUIServer.ts @@ -11,7 +11,7 @@ import { type ProcedureName, type ProtocolRequest, type ProtocolResponse, - type ProtocolVersion, + ProtocolVersion, type RequestPayload, type ResponsePayload, type UIServerConfiguration, @@ -46,14 +46,10 @@ export abstract class AbstractUIServer { this.chargingStations.clear(); } - public async sendBroadcastChannelRequest( - id: string, - procedureName: ProcedureName, - requestPayload: RequestPayload - ): Promise { - for (const uiService of this.uiServices.values()) { - await uiService.requestHandler(this.buildProtocolRequest(id, procedureName, requestPayload)); - } + public async sendBroadcastChannelRequest(request: ProtocolRequest): Promise { + const protocolVersion = ProtocolVersion['0.0.1']; + this.registerProtocolVersionUIService(protocolVersion); + return this.uiServices.get(protocolVersion)?.requestHandler(request); } protected startHttpServer(): void { diff --git a/src/charging-station/ui-server/ui-services/AbstractUIService.ts b/src/charging-station/ui-server/ui-services/AbstractUIService.ts index 541966af..392a63ed 100644 --- a/src/charging-station/ui-server/ui-services/AbstractUIService.ts +++ b/src/charging-station/ui-server/ui-services/AbstractUIService.ts @@ -5,6 +5,7 @@ import { ProcedureName, type ProtocolRequest, type ProtocolRequestHandler, + type ProtocolResponse, type ProtocolVersion, type RequestPayload, type ResponsePayload, @@ -65,11 +66,11 @@ export abstract class AbstractUIService { this.broadcastChannelRequests = new Map(); } - public async requestHandler(request: ProtocolRequest): Promise { + public async requestHandler(request: ProtocolRequest): Promise { let messageId: string; let command: ProcedureName; let requestPayload: RequestPayload | undefined; - let responsePayload: ResponsePayload; + let responsePayload: ResponsePayload | undefined; try { [messageId, command, requestPayload] = request; @@ -98,11 +99,11 @@ export abstract class AbstractUIService { errorStack: (error as Error).stack, errorDetails: (error as OCPPError).details, }; - } finally { - // Send response for payload not forwarded to broadcast channel - if (!Utils.isNullOrUndefined(responsePayload)) { - this.sendResponse(messageId, responsePayload); - } + } + // Send response + if (!Utils.isNullOrUndefined(responsePayload)) { + this.sendResponse(messageId, responsePayload); + return this.uiServer.buildProtocolResponse(messageId, responsePayload); } } -- 2.34.1