From ded13d9799aa6fb958da48a6b702d4193e7954f2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Wed, 27 Jan 2021 13:44:46 +0100 Subject: [PATCH] Initial support for main configuration file changes handling. 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 | 91 +++++++++++++++++++++++++++++++ src/start.ts | 51 +---------------- src/utils/Configuration.ts | 16 +++++- src/worker/WorkerDynamicPool.ts | 9 +++ src/worker/WorkerFactory.ts | 4 ++ src/worker/WorkerSet.ts | 26 ++++++--- src/worker/WorkerStaticPool.ts | 9 +++ src/worker/Wrk.ts | 1 + 8 files changed, 150 insertions(+), 57 deletions(-) create mode 100644 src/charging-station/Bootstrap.ts diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts new file mode 100644 index 00000000..50e861dd --- /dev/null +++ b/src/charging-station/Bootstrap.ts @@ -0,0 +1,91 @@ +import Configuration from '../utils/Configuration'; +import { StationWorkerData } from '../types/Worker'; +import Utils from '../utils/Utils'; +import WorkerFactory from '../worker/WorkerFactory'; +import Wrk from '../worker/Wrk'; +import { isMainThread } from 'worker_threads'; + +export default class Bootstrap { + private static instance: Bootstrap; + private isStarted: boolean; + private workerScript: string; + private workerImplementation: Wrk; + + private constructor() { + this.isStarted = false; + this.workerScript = './dist/charging-station/StationWorker.js'; + } + + public static getInstance(): Bootstrap { + if (!Bootstrap.instance) { + Bootstrap.instance = new Bootstrap(); + } + return Bootstrap.instance; + } + + public async start(): Promise { + if (isMainThread && !this.isStarted) { + try { + let numStationsTotal = 0; + await this.getWorkerImplementation().start(); + // Start ChargingStation object in worker thread + if (Configuration.getStationTemplateURLs()) { + for (const stationURL of Configuration.getStationTemplateURLs()) { + try { + const nbStations = stationURL.numberOfStations ? stationURL.numberOfStations : 0; + for (let index = 1; index <= nbStations; index++) { + const workerData: StationWorkerData = { + index, + templateFile: stationURL.file + }; + await this.getWorkerImplementation().addElement(workerData); + numStationsTotal++; + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('Charging station start with template file ' + stationURL.file + ' error ', error); + } + } + } else { + console.log('No stationTemplateURLs defined in configuration, exiting'); + } + if (numStationsTotal === 0) { + console.log('No charging station template enabled in configuration, exiting'); + } else { + console.log(`Charging station simulator started with ${numStationsTotal.toString()} charging station(s) and ${Utils.workerDynamicPoolInUse() ? `${Configuration.getWorkerPoolMinSize().toString()}/` : ''}${this.getWorkerImplementation().size}${Utils.workerPoolInUse() ? `/${Configuration.getWorkerPoolMaxSize().toString()}` : ''} worker(s) concurrently running in '${Configuration.getWorkerProcess()}' mode (${this.getWorkerImplementation().maxElementsPerWorker} charging station(s) per worker)`); + } + this.isStarted = true; + } catch (error) { + // eslint-disable-next-line no-console + console.error('Bootstrap start error ', error); + } + } + } + + public async stop(): Promise { + if (isMainThread && this.isStarted) { + await this.getWorkerImplementation().stop(); + if (this.workerImplementation) { + // Nullify to force worker implementation instance creation + this.workerImplementation = null; + } + } + this.isStarted = false; + } + + public async restart(): Promise { + await this.stop(); + await this.start(); + } + + private getWorkerImplementation(): Wrk { + if (!this.workerImplementation) { + this.workerImplementation = WorkerFactory.getWorkerImpl(this.workerScript, Configuration.getWorkerProcess(), { + poolMaxSize: Configuration.getWorkerPoolMaxSize(), + poolMinSize: Configuration.getWorkerPoolMinSize(), + elementsPerWorker: Configuration.getChargingStationsPerWorker() + }); + } + return this.workerImplementation; + } +} diff --git a/src/start.ts b/src/start.ts index 265b1fb0..56bafee7 100644 --- a/src/start.ts +++ b/src/start.ts @@ -1,53 +1,6 @@ -import Configuration from './utils/Configuration'; -import { StationWorkerData } from './types/Worker'; -import Utils from './utils/Utils'; -import WorkerFactory from './worker/WorkerFactory'; -import Wrk from './worker/Wrk'; +import Bootstrap from './charging-station/Bootstrap'; -class Bootstrap { - static async start() { - try { - let numStationsTotal = 0; - const workerImplementation: Wrk = WorkerFactory.getWorkerImpl('./dist/charging-station/StationWorker.js', Configuration.getWorkerProcess(), { - poolMaxSize: Configuration.getWorkerPoolMaxSize(), - poolMinSize: Configuration.getWorkerPoolMinSize(), - elementsPerWorker: Configuration.getChargingStationsPerWorker() - }); - await workerImplementation.start(); - // Start ChargingStation object in worker thread - if (Configuration.getStationTemplateURLs()) { - for (const stationURL of Configuration.getStationTemplateURLs()) { - try { - const nbStations = stationURL.numberOfStations ? stationURL.numberOfStations : 0; - for (let index = 1; index <= nbStations; index++) { - const workerData: StationWorkerData = { - index, - templateFile: stationURL.file - }; - await workerImplementation.addElement(workerData); - numStationsTotal++; - } - } catch (error) { - // eslint-disable-next-line no-console - console.error('Charging station start with template file ' + stationURL.file + ' error ', error); - } - } - } else { - console.log('No stationTemplateURLs defined in configuration, exiting'); - } - if (numStationsTotal === 0) { - console.log('No charging station template enabled in configuration, exiting'); - } else { - console.log(`Charging station simulator started with ${numStationsTotal.toString()} charging station(s) and ${Utils.workerDynamicPoolInUse() ? `${Configuration.getWorkerPoolMinSize().toString()}/` : ''}${workerImplementation.size}${Utils.workerPoolInUse() ? `/${Configuration.getWorkerPoolMaxSize().toString()}` : ''} worker(s) concurrently running in '${Configuration.getWorkerProcess()}' mode (${workerImplementation.maxElementsPerWorker} charging station(s) per worker)`); - } - } catch (error) { - // eslint-disable-next-line no-console - console.error('Bootstrap start error ', error); - } - } -} - -Bootstrap.start().catch( +Bootstrap.getInstance().start().catch( (error) => { console.error(error); } diff --git a/src/utils/Configuration.ts b/src/utils/Configuration.ts index e27ec127..d71af225 100644 --- a/src/utils/Configuration.ts +++ b/src/utils/Configuration.ts @@ -1,9 +1,12 @@ import ConfigurationData, { StationTemplateURL } from '../types/ConfigurationData'; +import Bootstrap from '../charging-station/Bootstrap'; import { WorkerProcessType } from '../types/Worker'; import fs from 'fs'; export default class Configuration { + private static configurationFilePath = './src/assets/config.json'; + private static configurationFileWatcher: fs.FSWatcher; private static configuration: ConfigurationData; static getStatisticsDisplayInterval(): number { @@ -105,11 +108,22 @@ export default class Configuration { // Read the config file private static getConfig(): ConfigurationData { if (!Configuration.configuration) { - Configuration.configuration = JSON.parse(fs.readFileSync('./src/assets/config.json', 'utf8')) as ConfigurationData; + Configuration.configuration = JSON.parse(fs.readFileSync(Configuration.configurationFilePath, 'utf8')) as ConfigurationData; + if (!Configuration.configurationFileWatcher) { + Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher(); + } } return Configuration.configuration; } + private static getConfigurationFileWatcher(): fs.FSWatcher { + return fs.watch(Configuration.configurationFilePath).on('change', async (e) => { + // Nullify to force configuration file reading + Configuration.configuration = null; + await Bootstrap.getInstance().restart(); + }); + } + private static objectHasOwnProperty(object: any, property: string): boolean { return Object.prototype.hasOwnProperty.call(object, property) as boolean; } diff --git a/src/worker/WorkerDynamicPool.ts b/src/worker/WorkerDynamicPool.ts index 4410aa3a..aa3542b2 100644 --- a/src/worker/WorkerDynamicPool.ts +++ b/src/worker/WorkerDynamicPool.ts @@ -34,6 +34,15 @@ export default class WorkerDynamicPool extends Wrk { // eslint-disable-next-line @typescript-eslint/no-empty-function public async start(): Promise { } + /** + * + * @return {Promise} + * @public + */ + public async stop(): Promise { + return this.pool.destroy(); + } + /** * * @return {Promise} diff --git a/src/worker/WorkerFactory.ts b/src/worker/WorkerFactory.ts index d71e4227..fcfe4f80 100644 --- a/src/worker/WorkerFactory.ts +++ b/src/worker/WorkerFactory.ts @@ -5,9 +5,13 @@ import WorkerDynamicPool from './WorkerDynamicPool'; import WorkerSet from './WorkerSet'; import WorkerStaticPool from './WorkerStaticPool'; import Wrk from './Wrk'; +import { isMainThread } from 'worker_threads'; export default class WorkerFactory { public static getWorkerImpl(workerScript: string, workerProcessType: WorkerProcessType, options?: WorkerOptions): Wrk { + if (!isMainThread) { + throw new Error('Trying to get a worker implementation outside the main thread'); + } if (Utils.isUndefined(options)) { options = {} as WorkerOptions; } diff --git a/src/worker/WorkerSet.ts b/src/worker/WorkerSet.ts index 0fd54fb2..c550149b 100644 --- a/src/worker/WorkerSet.ts +++ b/src/worker/WorkerSet.ts @@ -7,7 +7,7 @@ import Wrk from './Wrk'; export default class WorkerSet extends Wrk { public maxElementsPerWorker: number; - private workers: Set; + private workerSet: Set; /** * Create a new `WorkerSet`. @@ -17,12 +17,12 @@ export default class WorkerSet extends Wrk { */ constructor(workerScript: string, maxElementsPerWorker = 1) { super(workerScript); - this.workers = new Set(); + this.workerSet = new Set(); this.maxElementsPerWorker = maxElementsPerWorker; } get size(): number { - return this.workers.size; + return this.workerSet.size; } /** @@ -31,7 +31,7 @@ export default class WorkerSet extends Wrk { * @public */ public async addElement(elementData: T): Promise { - if (!this.workers) { + if (!this.workerSet) { throw Error('Cannot add a WorkerSet element: workers\' set does not exist'); } if (this.getLastWorkerSetElement().numberOfWorkerElements >= this.maxElementsPerWorker) { @@ -54,6 +54,18 @@ export default class WorkerSet extends Wrk { await Utils.sleep(Constants.START_WORKER_DELAY); } + /** + * + * @return {Promise} + * @public + */ + public async stop(): Promise { + for (const workerSetElement of this.workerSet) { + await workerSetElement.worker.terminate(); + } + this.workerSet.clear(); + } + /** * * @return {Promise} @@ -69,13 +81,13 @@ export default class WorkerSet extends Wrk { } // FIXME: remove matching worker set element }); - this.workers.add({ worker, numberOfWorkerElements: 0 }); + this.workerSet.add({ worker, numberOfWorkerElements: 0 }); } private getLastWorkerSetElement(): WorkerSetElement { let workerSetElement: WorkerSetElement; // eslint-disable-next-line no-empty - for (workerSetElement of this.workers) { } + for (workerSetElement of this.workerSet) { } return workerSetElement; } @@ -85,7 +97,7 @@ export default class WorkerSet extends Wrk { private getWorkerSetElementByWorker(worker: Worker): WorkerSetElement { let workerSetElt: WorkerSetElement; - this.workers.forEach((workerSetElement) => { + this.workerSet.forEach((workerSetElement) => { if (JSON.stringify(workerSetElement.worker) === JSON.stringify(worker)) { workerSetElt = workerSetElement; } diff --git a/src/worker/WorkerStaticPool.ts b/src/worker/WorkerStaticPool.ts index c6625d8b..1de7cba2 100644 --- a/src/worker/WorkerStaticPool.ts +++ b/src/worker/WorkerStaticPool.ts @@ -34,6 +34,15 @@ export default class WorkerStaticPool extends Wrk { // eslint-disable-next-line @typescript-eslint/no-empty-function public async start(): Promise { } + /** + * + * @return {Promise} + * @public + */ + public async stop(): Promise { + return this.pool.destroy(); + } + /** * * @return {Promise} diff --git a/src/worker/Wrk.ts b/src/worker/Wrk.ts index 02589a59..d29494a8 100644 --- a/src/worker/Wrk.ts +++ b/src/worker/Wrk.ts @@ -15,5 +15,6 @@ export default abstract class Wrk { } public abstract start(): Promise; + public abstract stop(): Promise; public abstract addElement(elementData: WorkerData): Promise; } -- 2.34.1