X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fpools%2Fabstract-pool.ts;h=ad657b17cffd21b373473444a802fba34c6cefb8;hb=f751010550f07200428ca708afb2ec1d242cf7a7;hp=ba05d92959a83f362fd721b4ca3615e66c86d196;hpb=f65efa796405c785a1ab5e646e30fc27418a6151;p=poolifier.git diff --git a/src/pools/abstract-pool.ts b/src/pools/abstract-pool.ts index ba05d929..ad657b17 100644 --- a/src/pools/abstract-pool.ts +++ b/src/pools/abstract-pool.ts @@ -4,10 +4,12 @@ import type { MessageValue, PromiseResponseWrapper } from '../utility-types' import { DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS, EMPTY_FUNCTION, + isKillBehavior, isPlainObject, - median + median, + round } from '../utils' -import { KillBehaviors, isKillBehavior } from '../worker/worker-options' +import { KillBehaviors } from '../worker/worker-options' import { CircularArray } from '../circular-array' import { Queue } from '../queue' import { @@ -36,6 +38,7 @@ import { type WorkerChoiceStrategyOptions } from './selection-strategies/selection-strategies-types' import { WorkerChoiceStrategyContext } from './selection-strategies/worker-choice-strategy-context' +import { version } from './version' /** * Base class that implements some shared logic for all poolifier pools. @@ -77,6 +80,11 @@ export abstract class AbstractPool< Response > + /** + * The start timestamp of the pool. + */ + private readonly startTimestamp + /** * Constructs a new poolifier pool. * @@ -116,9 +124,11 @@ export abstract class AbstractPool< this.setupHook() - for (let i = 1; i <= this.numberOfWorkers; i++) { + while (this.workerNodes.length < this.numberOfWorkers) { this.createAndSetupWorker() } + + this.startTimestamp = performance.now() } private checkFilePath (filePath: string): void { @@ -237,21 +247,18 @@ export abstract class AbstractPool< } } - private get starting (): boolean { - return this.workerNodes.some(workerNode => !workerNode.info.started) - } - - private get started (): boolean { - return this.workerNodes.some(workerNode => workerNode.info.started) - } - /** @inheritDoc */ public get info (): PoolInfo { return { + version, type: this.type, worker: this.worker, minSize: this.minSize, maxSize: this.maxSize, + ...(this.workerChoiceStrategyContext.getTaskStatisticsRequirements() + .runTime.aggregate && + this.workerChoiceStrategyContext.getTaskStatisticsRequirements() + .waitTime.aggregate && { utilization: round(this.utilization) }), workerNodes: this.workerNodes.length, idleWorkerNodes: this.workerNodes.reduce( (accumulator, workerNode) => @@ -293,6 +300,27 @@ export abstract class AbstractPool< } } + /** + * Gets the approximate pool utilization. + * + * @returns The pool utilization. + */ + private get utilization (): number { + const poolRunTimeCapacity = + (performance.now() - this.startTimestamp) * this.maxSize + const totalTasksRunTime = this.workerNodes.reduce( + (accumulator, workerNode) => + accumulator + workerNode.usage.runTime.aggregate, + 0 + ) + const totalTasksWaitTime = this.workerNodes.reduce( + (accumulator, workerNode) => + accumulator + workerNode.usage.waitTime.aggregate, + 0 + ) + return (totalTasksRunTime + totalTasksWaitTime) / poolRunTimeCapacity + } + /** * Pool type. * @@ -471,7 +499,13 @@ export abstract class AbstractPool< this.workerNodes.map(async (workerNode, workerNodeKey) => { this.flushTasksQueue(workerNodeKey) // FIXME: wait for tasks to be finished + const workerExitPromise = new Promise(resolve => { + workerNode.worker.on('exit', () => { + resolve() + }) + }) await this.destroyWorker(workerNode.worker) + await workerExitPromise }) ) } @@ -551,7 +585,16 @@ export abstract class AbstractPool< this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime .aggregate ) { - workerUsage.runTime.aggregate += message.taskPerformance?.runTime ?? 0 + const taskRunTime = message.taskPerformance?.runTime ?? 0 + workerUsage.runTime.aggregate += taskRunTime + workerUsage.runTime.minimum = Math.min( + taskRunTime, + workerUsage.runTime.minimum + ) + workerUsage.runTime.maximum = Math.max( + taskRunTime, + workerUsage.runTime.maximum + ) if ( this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime .average && @@ -582,7 +625,15 @@ export abstract class AbstractPool< this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime .aggregate ) { - workerUsage.waitTime.aggregate += taskWaitTime ?? 0 + workerUsage.waitTime.aggregate += taskWaitTime + workerUsage.waitTime.minimum = Math.min( + taskWaitTime, + workerUsage.waitTime.minimum + ) + workerUsage.waitTime.maximum = Math.max( + taskWaitTime, + workerUsage.waitTime.maximum + ) if ( this.workerChoiceStrategyContext.getTaskStatisticsRequirements() .waitTime.average && @@ -611,17 +662,33 @@ export abstract class AbstractPool< this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu .aggregate ) { - if (workerUsage.elu != null && message.taskPerformance?.elu != null) { + if (message.taskPerformance?.elu != null) { workerUsage.elu.idle.aggregate += message.taskPerformance.elu.idle workerUsage.elu.active.aggregate += message.taskPerformance.elu.active - workerUsage.elu.utilization = - (workerUsage.elu.utilization + - message.taskPerformance.elu.utilization) / - 2 - } else if (message.taskPerformance?.elu != null) { - workerUsage.elu.idle.aggregate = message.taskPerformance.elu.idle - workerUsage.elu.active.aggregate = message.taskPerformance.elu.active - workerUsage.elu.utilization = message.taskPerformance.elu.utilization + if (workerUsage.elu.utilization != null) { + workerUsage.elu.utilization = + (workerUsage.elu.utilization + + message.taskPerformance.elu.utilization) / + 2 + } else { + workerUsage.elu.utilization = message.taskPerformance.elu.utilization + } + workerUsage.elu.idle.minimum = Math.min( + message.taskPerformance.elu.idle, + workerUsage.elu.idle.minimum + ) + workerUsage.elu.idle.maximum = Math.max( + message.taskPerformance.elu.idle, + workerUsage.elu.idle.maximum + ) + workerUsage.elu.active.minimum = Math.min( + message.taskPerformance.elu.active, + workerUsage.elu.active.minimum + ) + workerUsage.elu.active.maximum = Math.max( + message.taskPerformance.elu.active, + workerUsage.elu.active.maximum + ) } if ( this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu @@ -732,7 +799,34 @@ export abstract class AbstractPool< if (this.emitter != null) { this.emitter.emit(PoolEvents.error, error) } - if (this.opts.restartWorkerOnError === true && !this.starting) { + if (this.opts.enableTasksQueue === true) { + const workerNodeKey = this.getWorkerNodeKey(worker) + while (this.tasksQueueSize(workerNodeKey) > 0) { + let targetWorkerNodeKey: number = workerNodeKey + let minQueuedTasks = Infinity + for (const [workerNodeId, workerNode] of this.workerNodes.entries()) { + if ( + workerNodeId !== workerNodeKey && + workerNode.usage.tasks.queued === 0 + ) { + targetWorkerNodeKey = workerNodeId + break + } + if ( + workerNodeId !== workerNodeKey && + workerNode.usage.tasks.queued < minQueuedTasks + ) { + minQueuedTasks = workerNode.usage.tasks.queued + targetWorkerNodeKey = workerNodeId + } + } + this.enqueueTask( + targetWorkerNodeKey, + this.dequeueTask(workerNodeKey) as Task + ) + } + } + if (this.opts.restartWorkerOnError === true) { this.createAndSetupWorker() } }) @@ -785,40 +879,53 @@ export abstract class AbstractPool< return message => { if (message.workerId != null && message.started != null) { // Worker started message received - const worker = this.getWorkerById(message.workerId) - if (worker != null) { - this.workerNodes[this.getWorkerNodeKey(worker)].info.started = - message.started - } else { - throw new Error('Worker started message received from unknown worker') - } + this.handleWorkerStartedMessage(message) } else if (message.id != null) { // Task execution response received - const promiseResponse = this.promiseResponseMap.get(message.id) - if (promiseResponse != null) { - if (message.taskError != null) { - if (this.emitter != null) { - this.emitter.emit(PoolEvents.taskError, message.taskError) - } - promiseResponse.reject(message.taskError.message) - } else { - promiseResponse.resolve(message.data as Response) - } - this.afterTaskExecutionHook(promiseResponse.worker, message) - this.promiseResponseMap.delete(message.id) - const workerNodeKey = this.getWorkerNodeKey(promiseResponse.worker) - if ( - this.opts.enableTasksQueue === true && - this.tasksQueueSize(workerNodeKey) > 0 - ) { - this.executeTask( - workerNodeKey, - this.dequeueTask(workerNodeKey) as Task - ) - } - this.workerChoiceStrategyContext.update(workerNodeKey) + this.handleTaskExecutionResponse(message) + } + } + } + + private handleWorkerStartedMessage (message: MessageValue): void { + // Worker started message received + const worker = this.getWorkerById(message.workerId as number) + if (worker != null) { + this.workerNodes[this.getWorkerNodeKey(worker)].info.started = + message.started as boolean + } else { + throw new Error( + `Worker started message received from unknown worker '${ + message.workerId as number + }'` + ) + } + } + + private handleTaskExecutionResponse (message: MessageValue): void { + const promiseResponse = this.promiseResponseMap.get(message.id as string) + if (promiseResponse != null) { + if (message.taskError != null) { + if (this.emitter != null) { + this.emitter.emit(PoolEvents.taskError, message.taskError) } + promiseResponse.reject(message.taskError.message) + } else { + promiseResponse.resolve(message.data as Response) } + this.afterTaskExecutionHook(promiseResponse.worker, message) + this.promiseResponseMap.delete(message.id as string) + const workerNodeKey = this.getWorkerNodeKey(promiseResponse.worker) + if ( + this.opts.enableTasksQueue === true && + this.tasksQueueSize(workerNodeKey) > 0 + ) { + this.executeTask( + workerNodeKey, + this.dequeueTask(workerNodeKey) as Task + ) + } + this.workerChoiceStrategyContext.update(workerNodeKey) } } @@ -855,7 +962,7 @@ export abstract class AbstractPool< private pushWorkerNode (worker: Worker): number { this.workerNodes.push({ worker, - info: { id: this.getWorkerId(worker), started: false }, + info: { id: this.getWorkerId(worker), started: true }, usage: this.getWorkerUsage(), tasksQueue: new Queue>() }) @@ -940,13 +1047,11 @@ export abstract class AbstractPool< } private flushTasksQueue (workerNodeKey: number): void { - if (this.tasksQueueSize(workerNodeKey) > 0) { - for (let i = 0; i < this.tasksQueueSize(workerNodeKey); i++) { - this.executeTask( - workerNodeKey, - this.dequeueTask(workerNodeKey) as Task - ) - } + while (this.tasksQueueSize(workerNodeKey) > 0) { + this.executeTask( + workerNodeKey, + this.dequeueTask(workerNodeKey) as Task + ) } this.workerNodes[workerNodeKey].tasksQueue.clear() } @@ -990,12 +1095,16 @@ export abstract class AbstractPool< }, runTime: { aggregate: 0, + maximum: 0, + minimum: 0, average: 0, median: 0, history: new CircularArray() }, waitTime: { aggregate: 0, + maximum: 0, + minimum: 0, average: 0, median: 0, history: new CircularArray() @@ -1003,17 +1112,20 @@ export abstract class AbstractPool< elu: { idle: { aggregate: 0, + maximum: 0, + minimum: 0, average: 0, median: 0, history: new CircularArray() }, active: { aggregate: 0, + maximum: 0, + minimum: 0, average: 0, median: 0, history: new CircularArray() - }, - utilization: 0 + } } } }