From 36adaf06028acd6337cf32183974fbe5a62c8444 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Thu, 9 Nov 2023 21:00:15 +0100 Subject: [PATCH] fix: ensure charging stations are stopped if UI server is disabled at shutdown 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 | 85 +++++++++++-------- .../ui-server/AbstractUIServer.ts | 7 ++ src/utils/ErrorUtils.ts | 2 + 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index 465d5bf2..2c7f56fe 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events'; import { dirname, extname, join } from 'node:path'; -import { exit } from 'node:process'; +import process, { exit } from 'node:process'; import { fileURLToPath } from 'node:url'; import chalk from 'chalk'; @@ -56,7 +56,7 @@ export class Bootstrap extends EventEmitter { public numberOfChargingStations!: number; public numberOfChargingStationTemplates!: number; private workerImplementation: WorkerAbstract | null; - private readonly uiServer!: AbstractUIServer | null; + private readonly uiServer: AbstractUIServer | null; private readonly storage!: Storage; private numberOfStartedChargingStations!: number; private readonly version: string = version; @@ -69,7 +69,7 @@ export class Bootstrap extends EventEmitter { private constructor() { super(); for (const signal of ['SIGINT', 'SIGQUIT', 'SIGTERM']) { - process.on(signal, this.gracefulShutdown); + process.on(signal, this.gracefulShutdown.bind(this)); } // Enable unconditionally for now handleUnhandledRejection(); @@ -84,11 +84,9 @@ export class Bootstrap extends EventEmitter { dirname(fileURLToPath(import.meta.url)), `ChargingStationWorker${extname(fileURLToPath(import.meta.url))}`, ); - const uiServerConfiguration = Configuration.getConfigurationSection( - ConfigurationSection.uiServer, + this.uiServer = UIServerFactory.getUIServerImplementation( + Configuration.getConfigurationSection(ConfigurationSection.uiServer), ); - uiServerConfiguration.enabled === true && - (this.uiServer = UIServerFactory.getUIServerImplementation(uiServerConfiguration)); const performanceStorageConfiguration = Configuration.getConfigurationSection( ConfigurationSection.performanceStorage, @@ -120,7 +118,8 @@ export class Bootstrap extends EventEmitter { this.initializeWorkerImplementation(workerConfiguration); await this.workerImplementation?.start(); await this.storage?.open(); - this.uiServer?.start(); + Configuration.getConfigurationSection(ConfigurationSection.uiServer) + .enabled === true && this.uiServer?.start(); // Start ChargingStation object instance in worker thread for (const stationTemplateUrl of Configuration.getStationTemplateUrls()!) { try { @@ -173,34 +172,19 @@ export class Bootstrap extends EventEmitter { } } - public async stop(waitChargingStationsStopped = true): Promise { + public async stop(stopChargingStations = true): Promise { if (this.started === true) { if (this.stopping === false) { this.stopping = true; - await this.uiServer?.sendInternalRequest( - this.uiServer.buildProtocolRequest( - generateUUID(), - ProcedureName.STOP_CHARGING_STATION, - Constants.EMPTY_FROZEN_OBJECT, - ), - ); - if (waitChargingStationsStopped === true) { - await Promise.race([ - waitChargingStationEvents( - this, - ChargingStationWorkerMessageEvents.stopped, - this.numberOfChargingStations, + if (stopChargingStations === true) { + await this.uiServer?.sendInternalRequest( + this.uiServer.buildProtocolRequest( + generateUUID(), + ProcedureName.STOP_CHARGING_STATION, + Constants.EMPTY_FROZEN_OBJECT, ), - new Promise((resolve) => { - setTimeout(() => { - const message = `Timeout ${formatDurationMilliSeconds( - Constants.STOP_SIMULATOR_TIMEOUT, - )} reached at stopping charging stations simulator`; - console.warn(chalk.yellow(message)); - resolve(message); - }, Constants.STOP_SIMULATOR_TIMEOUT); - }), - ]); + ); + await this.waitChargingStationsStopped(); } await this.workerImplementation?.stop(); this.workerImplementation = null; @@ -218,11 +202,30 @@ export class Bootstrap extends EventEmitter { } } - public async restart(waitChargingStationsStopped?: boolean): Promise { - await this.stop(waitChargingStationsStopped); + public async restart(stopChargingStations?: boolean): Promise { + await this.stop(stopChargingStations); await this.start(); } + private async waitChargingStationsStopped(): Promise { + await Promise.race([ + waitChargingStationEvents( + this, + ChargingStationWorkerMessageEvents.stopped, + this.numberOfChargingStations, + ), + new Promise((resolve) => { + setTimeout(() => { + const message = `Timeout ${formatDurationMilliSeconds( + Constants.STOP_SIMULATOR_TIMEOUT, + )} reached at stopping charging stations simulator`; + console.warn(chalk.yellow(message)); + resolve(message); + }, Constants.STOP_SIMULATOR_TIMEOUT); + }), + ]); + } + private initializeWorkerImplementation(workerConfiguration: WorkerConfiguration): void { let elementsPerWorker: number | undefined; if (workerConfiguration?.elementsPerWorker === 'auto') { @@ -383,17 +386,25 @@ export class Bootstrap extends EventEmitter { }); } - private gracefulShutdown = (): void => { + private gracefulShutdown(): void { this.stop() .then(() => { console.info(`${chalk.green('Graceful shutdown')}`); - exit(exitCodes.succeeded); + // stop() asks for charging stations to stop by default + this.waitChargingStationsStopped() + .then(() => { + exit(exitCodes.succeeded); + }) + .catch((error) => { + console.error(chalk.red('Error while waiting for charging stations to stop: '), error); + exit(exitCodes.gracefulShutdownError); + }); }) .catch((error) => { console.error(chalk.red('Error while shutdowning charging stations simulator: '), error); exit(exitCodes.gracefulShutdownError); }); - }; + } private logPrefix = (): string => { return logPrefix(' Bootstrap |'); diff --git a/src/charging-station/ui-server/AbstractUIServer.ts b/src/charging-station/ui-server/AbstractUIServer.ts index 133389d6..d27ea616 100644 --- a/src/charging-station/ui-server/AbstractUIServer.ts +++ b/src/charging-station/ui-server/AbstractUIServer.ts @@ -56,6 +56,7 @@ export abstract class AbstractUIServer { } public stop(): void { + this.stopHttpServer(); this.chargingStations.clear(); } @@ -93,6 +94,12 @@ export abstract class AbstractUIServer { next(); } + private stopHttpServer(): void { + if (this.httpServer.listening === true) { + this.httpServer.close(); + } + } + private isBasicAuthEnabled(): boolean { return ( this.uiServerConfiguration.authentication?.enabled === true && diff --git a/src/utils/ErrorUtils.ts b/src/utils/ErrorUtils.ts index 87234879..537aa308 100644 --- a/src/utils/ErrorUtils.ts +++ b/src/utils/ErrorUtils.ts @@ -1,3 +1,5 @@ +import process from 'node:process'; + import chalk from 'chalk'; import { logger } from './Logger'; -- 2.34.1