X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fworker%2FWorkerSet.ts;h=1e3000cc2f719722fe6ddded93cae1ca4acc3e45;hb=1d7a504ba27c38e3f8617c80cf53aed85da580af;hp=ea5d654cdd001f599e6af1b670e61e494852430c;hpb=5199f9fdf202eb534948f165a0994e1993675aa8;p=e-mobility-charging-stations-simulator.git diff --git a/src/worker/WorkerSet.ts b/src/worker/WorkerSet.ts index ea5d654c..1e3000cc 100644 --- a/src/worker/WorkerSet.ts +++ b/src/worker/WorkerSet.ts @@ -1,5 +1,6 @@ -// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. +// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved. +import { randomUUID } from 'node:crypto' import { EventEmitterAsyncResource } from 'node:events' import { SHARE_ENV, Worker } from 'node:worker_threads' @@ -16,9 +17,20 @@ import { } from './WorkerTypes.js' import { randomizeDelay, sleep } from './WorkerUtils.js' -export class WorkerSet extends WorkerAbstract { +interface ResponseWrapper { + resolve: (value: R | PromiseLike) => void + reject: (reason?: unknown) => void + workerSetElement: WorkerSetElement +} + +export class WorkerSet extends WorkerAbstract { public readonly emitter: EventEmitterAsyncResource | undefined private readonly workerSet: Set + private readonly promiseResponseMap: Map< + `${string}-${string}-${string}-${string}`, + ResponseWrapper + > + private started: boolean private workerStartup: boolean @@ -40,6 +52,10 @@ export class WorkerSet extends WorkerAbstract { throw new RangeError('Elements per worker must be greater than zero') } this.workerSet = new Set() + this.promiseResponseMap = new Map< + `${string}-${string}-${string}-${string}`, + ResponseWrapper + >() if (this.workerOptions.poolOptions?.enableEvents === true) { this.emitter = new EventEmitterAsyncResource({ name: 'workerset' }) } @@ -52,6 +68,7 @@ export class WorkerSet extends WorkerAbstract { version: workerSetVersion, type: 'set', worker: 'thread', + started: this.started, size: this.size, elementsExecuting: [...this.workerSet].reduce( (accumulator, workerSetElement) => accumulator + workerSetElement.numberOfWorkerElements, @@ -86,37 +103,43 @@ export class WorkerSet extends WorkerAbstract { public async stop (): Promise { for (const workerSetElement of this.workerSet) { const worker = workerSetElement.worker - const waitWorkerExit = new Promise((resolve) => { + const waitWorkerExit = new Promise(resolve => { worker.once('exit', () => { resolve() }) }) + worker.unref() await worker.terminate() await waitWorkerExit - this.emitter?.emit(WorkerSetEvents.stopped, this.info) - this.emitter?.emitDestroy() - this.emitter?.removeAllListeners() - this.started = false } + this.emitter?.emit(WorkerSetEvents.stopped, this.info) + this.started = false + this.emitter?.emitDestroy() } /** @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() - workerSetElement.worker.postMessage({ - event: WorkerMessageEvents.startWorkerElement, - data: elementData + const sendMessageToWorker = new Promise((resolve, reject) => { + const message = { + uuid: randomUUID(), + event: WorkerMessageEvents.addWorkerElement, + data: elementData + } satisfies WorkerMessage + workerSetElement.worker.postMessage(message) + this.promiseResponseMap.set(message.uuid, { resolve, reject, workerSetElement }) }) - ++workerSetElement.numberOfWorkerElements + const response = await sendMessageToWorker // Add element sequentially to optimize memory at startup // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if (this.workerOptions.elementStartDelay! > 0) { + if (this.workerOptions.elementAddDelay! > 0) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await sleep(randomizeDelay(this.workerOptions.elementStartDelay!)) + await sleep(randomizeDelay(this.workerOptions.elementAddDelay!)) } + return response } /** @@ -129,15 +152,24 @@ export class WorkerSet extends WorkerAbstract { ...this.workerOptions.poolOptions?.workerOptions }) worker.on('message', this.workerOptions.poolOptions?.messageHandler ?? EMPTY_FUNCTION) - worker.on('message', (message: WorkerMessage) => { - if (message.event === WorkerMessageEvents.startedWorkerElement) { - this.emitter?.emit(WorkerSetEvents.elementStarted, this.info) - } else if (message.event === WorkerMessageEvents.startWorkerElementError) { - this.emitter?.emit(WorkerSetEvents.elementError, message.data) + worker.on('message', (message: WorkerMessage) => { + const { uuid, event, data } = message + if (this.promiseResponseMap.has(uuid)) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { resolve, reject, workerSetElement } = this.promiseResponseMap.get(uuid)! + if (event === WorkerMessageEvents.addedWorkerElement) { + this.emitter?.emit(WorkerSetEvents.elementAdded, this.info) + workerSetElement.numberOfWorkerElements++ + resolve(data) + } else if (event === WorkerMessageEvents.workerElementError) { + this.emitter?.emit(WorkerSetEvents.elementError, data) + reject(data) + } + this.promiseResponseMap.delete(message.uuid) } }) worker.on('error', this.workerOptions.poolOptions?.errorHandler ?? EMPTY_FUNCTION) - worker.on('error', (error) => { + worker.on('error', error => { this.emitter?.emit(WorkerSetEvents.error, error) if ( this.workerOptions.poolOptions?.restartWorkerOnError === true && @@ -146,6 +178,8 @@ export class WorkerSet extends WorkerAbstract { ) { this.addWorkerSetElement() } + worker.unref() + worker.terminate().catch((error: unknown) => this.emitter?.emit(WorkerSetEvents.error, error)) }) worker.on('online', this.workerOptions.poolOptions?.onlineHandler ?? EMPTY_FUNCTION) worker.on('exit', this.workerOptions.poolOptions?.exitHandler ?? EMPTY_FUNCTION)