Initial support for main configuration file changes handling.
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 27 Jan 2021 12:44:46 +0000 (13:44 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 27 Jan 2021 12:44:46 +0000 (13:44 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/Bootstrap.ts [new file with mode: 0644]
src/start.ts
src/utils/Configuration.ts
src/worker/WorkerDynamicPool.ts
src/worker/WorkerFactory.ts
src/worker/WorkerSet.ts
src/worker/WorkerStaticPool.ts
src/worker/Wrk.ts

diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts
new file mode 100644 (file)
index 0000000..50e861d
--- /dev/null
@@ -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<void> {
+    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<void> {
+    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<void> {
+    await this.stop();
+    await this.start();
+  }
+
+  private getWorkerImplementation(): Wrk {
+    if (!this.workerImplementation) {
+      this.workerImplementation = WorkerFactory.getWorkerImpl<StationWorkerData>(this.workerScript, Configuration.getWorkerProcess(), {
+        poolMaxSize: Configuration.getWorkerPoolMaxSize(),
+        poolMinSize: Configuration.getWorkerPoolMinSize(),
+        elementsPerWorker: Configuration.getChargingStationsPerWorker()
+      });
+    }
+    return this.workerImplementation;
+  }
+}
index 265b1fb0a17548bd02c99bb221d1d9041a46f86e..56bafee7b86bebb53a6a97414183c544c8d72f58 100644 (file)
@@ -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<StationWorkerData>('./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);
   }
index e27ec1276539a2d7b32decec68ff9f3e6a87e39e..d71af225afec4539344c45da6a3af47604a51219 100644 (file)
@@ -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;
   }
index 4410aa3a6a7f5e58e81767770aa8902d613c43c4..aa3542b26386f72e75c39eb589656e3f9009e1ca 100644 (file)
@@ -34,6 +34,15 @@ export default class WorkerDynamicPool<T> extends Wrk {
   // eslint-disable-next-line @typescript-eslint/no-empty-function
   public async start(): Promise<void> { }
 
+  /**
+   *
+   * @return {Promise<void>}
+   * @public
+   */
+  public async stop(): Promise<void> {
+    return this.pool.destroy();
+  }
+
   /**
    *
    * @return {Promise<void>}
index d71e4227841d5072778c2a7a8a71d256e12ae1e3..fcfe4f80677820266f8438aab242907d281de1ff 100644 (file)
@@ -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<T>(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;
     }
index 0fd54fb219bf1f0c99a2013e6b4e21a0c4e82d68..c550149b71b5945e50058b8a6f27c1d72bf88275 100644 (file)
@@ -7,7 +7,7 @@ import Wrk from './Wrk';
 
 export default class WorkerSet<T> extends Wrk {
   public maxElementsPerWorker: number;
-  private workers: Set<WorkerSetElement>;
+  private workerSet: Set<WorkerSetElement>;
 
   /**
    * Create a new `WorkerSet`.
@@ -17,12 +17,12 @@ export default class WorkerSet<T> extends Wrk {
    */
   constructor(workerScript: string, maxElementsPerWorker = 1) {
     super(workerScript);
-    this.workers = new Set<WorkerSetElement>();
+    this.workerSet = new Set<WorkerSetElement>();
     this.maxElementsPerWorker = maxElementsPerWorker;
   }
 
   get size(): number {
-    return this.workers.size;
+    return this.workerSet.size;
   }
 
   /**
@@ -31,7 +31,7 @@ export default class WorkerSet<T> extends Wrk {
    * @public
    */
   public async addElement(elementData: T): Promise<void> {
-    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<T> extends Wrk {
     await Utils.sleep(Constants.START_WORKER_DELAY);
   }
 
+  /**
+   *
+   * @return {Promise<void>}
+   * @public
+   */
+  public async stop(): Promise<void> {
+    for (const workerSetElement of this.workerSet) {
+      await workerSetElement.worker.terminate();
+    }
+    this.workerSet.clear();
+  }
+
   /**
    *
    * @return {Promise}
@@ -69,13 +81,13 @@ export default class WorkerSet<T> 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<T> 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;
       }
index c6625d8b609ac55d171e5b7959498242289d90d0..1de7cba23304a7f3812dd906a9c64162a3f39dc9 100644 (file)
@@ -34,6 +34,15 @@ export default class WorkerStaticPool<T> extends Wrk {
   // eslint-disable-next-line @typescript-eslint/no-empty-function
   public async start(): Promise<void> { }
 
+  /**
+   *
+   * @return {Promise<void>}
+   * @public
+   */
+  public async stop(): Promise<void> {
+    return this.pool.destroy();
+  }
+
   /**
    *
    * @return {Promise<void>}
index 02589a593d7a7ecbf5bb416caa1168d0f67b2eec..d29494a8f33282c1370234b247014622b4f2c279 100644 (file)
@@ -15,5 +15,6 @@ export default abstract class Wrk {
   }
 
   public abstract start(): Promise<void>;
+  public abstract stop(): Promise<void>;
   public abstract addElement(elementData: WorkerData): Promise<void>;
 }