updateMeasurementStatistics
} from '../utils'
import { KillBehaviors } from '../worker/worker-options'
+import type { TaskFunction } from '../worker/task-functions'
import {
type IPool,
PoolEmitter,
*/
protected readonly max?: number
+ /**
+ * The task functions added at runtime map:
+ * - `key`: The task function name.
+ * - `value`: The task function itself.
+ */
+ private readonly taskFunctions: Map<string, TaskFunction<Data, Response>>
+
/**
* Whether the pool is starting or not.
*/
this.setupHook()
+ this.taskFunctions = new Map<string, TaskFunction<Data, Response>>()
+
this.starting = true
this.startPool()
this.starting = false
minimum: round(
min(
...this.workerNodes.map(
- (workerNode) => workerNode.usage.runTime?.minimum ?? Infinity
+ workerNode => workerNode.usage.runTime?.minimum ?? Infinity
)
)
),
maximum: round(
max(
...this.workerNodes.map(
- (workerNode) => workerNode.usage.runTime?.maximum ?? -Infinity
+ workerNode => workerNode.usage.runTime?.maximum ?? -Infinity
)
)
),
minimum: round(
min(
...this.workerNodes.map(
- (workerNode) => workerNode.usage.waitTime?.minimum ?? Infinity
+ workerNode => workerNode.usage.waitTime?.minimum ?? Infinity
)
)
),
maximum: round(
max(
...this.workerNodes.map(
- (workerNode) => workerNode.usage.waitTime?.maximum ?? -Infinity
+ workerNode => workerNode.usage.waitTime?.maximum ?? -Infinity
)
)
),
*/
private getWorkerNodeKeyByWorker (worker: Worker): number {
return this.workerNodes.findIndex(
- (workerNode) => workerNode.worker === worker
+ workerNode => workerNode.worker === worker
)
}
* @param workerId - The worker id.
* @returns The worker node key if the worker id is found in the pool worker nodes, `-1` otherwise.
*/
- private getWorkerNodeKeyByWorkerId (workerId: number): number {
+ private getWorkerNodeKeyByWorkerId (workerId: number | undefined): number {
return this.workerNodes.findIndex(
- (workerNode) => workerNode.info.id === workerId
+ workerNode => workerNode.info.id === workerId
)
}
if (this.opts.enableTasksQueue === true) {
return (
this.workerNodes.findIndex(
- (workerNode) =>
+ workerNode =>
workerNode.info.ready &&
workerNode.usage.tasks.executing <
(this.opts.tasksQueueOptions?.concurrency as number)
} else {
return (
this.workerNodes.findIndex(
- (workerNode) =>
+ workerNode =>
workerNode.info.ready && workerNode.usage.tasks.executing === 0
) === -1
)
}
}
+ private async sendTaskFunctionOperationToWorker (
+ workerNodeKey: number,
+ message: MessageValue<Data>
+ ): Promise<boolean> {
+ const workerId = this.getWorkerInfo(workerNodeKey).id as number
+ return await new Promise<boolean>((resolve, reject) => {
+ this.registerWorkerMessageListener(workerNodeKey, message => {
+ if (
+ message.workerId === workerId &&
+ message.taskFunctionOperationStatus === true
+ ) {
+ resolve(true)
+ } else if (
+ message.workerId === workerId &&
+ message.taskFunctionOperationStatus === false
+ ) {
+ reject(
+ new Error(
+ `Task function operation ${
+ message.taskFunctionOperation as string
+ } failed on worker ${message.workerId}`
+ )
+ )
+ }
+ })
+ this.sendToWorker(workerNodeKey, message)
+ })
+ }
+
+ private async sendTaskFunctionOperationToWorkers (
+ message: Omit<MessageValue<Data>, 'workerId'>
+ ): Promise<boolean> {
+ return await new Promise<boolean>((resolve, reject) => {
+ const responsesReceived = new Array<MessageValue<Data | Response>>()
+ for (const [workerNodeKey] of this.workerNodes.entries()) {
+ this.registerWorkerMessageListener(workerNodeKey, message => {
+ if (message.taskFunctionOperationStatus != null) {
+ responsesReceived.push(message)
+ if (
+ responsesReceived.length === this.workerNodes.length &&
+ responsesReceived.every(
+ message => message.taskFunctionOperationStatus === true
+ )
+ ) {
+ resolve(true)
+ } else if (
+ responsesReceived.length === this.workerNodes.length &&
+ responsesReceived.some(
+ message => message.taskFunctionOperationStatus === false
+ )
+ ) {
+ reject(
+ new Error(
+ `Task function operation ${
+ message.taskFunctionOperation as string
+ } failed on worker ${message.workerId as number}`
+ )
+ )
+ }
+ }
+ })
+ this.sendToWorker(workerNodeKey, message)
+ }
+ })
+ }
+
+ /** @inheritDoc */
+ public hasTaskFunction (name: string): boolean {
+ for (const workerNode of this.workerNodes) {
+ if (
+ Array.isArray(workerNode.info.taskFunctionNames) &&
+ workerNode.info.taskFunctionNames.includes(name)
+ ) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /** @inheritDoc */
+ public async addTaskFunction (
+ name: string,
+ taskFunction: TaskFunction<Data, Response>
+ ): Promise<boolean> {
+ this.taskFunctions.set(name, taskFunction)
+ return await this.sendTaskFunctionOperationToWorkers({
+ taskFunctionOperation: 'add',
+ taskFunctionName: name,
+ taskFunction: taskFunction.toString()
+ })
+ }
+
+ /** @inheritDoc */
+ public async removeTaskFunction (name: string): Promise<boolean> {
+ this.taskFunctions.delete(name)
+ return await this.sendTaskFunctionOperationToWorkers({
+ taskFunctionOperation: 'remove',
+ taskFunctionName: name
+ })
+ }
+
/** @inheritDoc */
- public listTaskFunctions (): string[] {
+ public listTaskFunctionNames (): string[] {
for (const workerNode of this.workerNodes) {
if (
- Array.isArray(workerNode.info.taskFunctions) &&
- workerNode.info.taskFunctions.length > 0
+ Array.isArray(workerNode.info.taskFunctionNames) &&
+ workerNode.info.taskFunctionNames.length > 0
) {
- return workerNode.info.taskFunctions
+ return workerNode.info.taskFunctionNames
}
}
return []
}
+ /** @inheritDoc */
+ public async setDefaultTaskFunction (name: string): Promise<boolean> {
+ return await this.sendTaskFunctionOperationToWorkers({
+ taskFunctionOperation: 'default',
+ taskFunctionName: name
+ })
+ }
+
private shallExecuteTask (workerNodeKey: number): boolean {
return (
this.tasksQueueSize(workerNodeKey) === 0 &&
}
const timestamp = performance.now()
const workerNodeKey = this.chooseWorkerNode()
- const workerInfo = this.getWorkerInfo(workerNodeKey)
const task: Task<Data> = {
name: name ?? DEFAULT_TASK_NAME,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
data: data ?? ({} as Data),
transferList,
timestamp,
- workerId: workerInfo.id as number,
taskId: randomUUID()
}
this.promiseResponseMap.set(task.taskId as string, {
}
protected async sendKillMessageToWorker (
- workerNodeKey: number,
- workerId: number
+ workerNodeKey: number
): Promise<void> {
await new Promise<void>((resolve, reject) => {
- this.registerWorkerMessageListener(workerNodeKey, (message) => {
+ this.registerWorkerMessageListener(workerNodeKey, message => {
if (message.kill === 'success') {
resolve()
} else if (message.kill === 'failure') {
- reject(new Error(`Worker ${workerId} kill message handling failed`))
+ reject(
+ new Error(
+ `Worker ${
+ message.workerId as number
+ } kill message handling failed`
+ )
+ )
}
})
- this.sendToWorker(workerNodeKey, { kill: true, workerId })
+ this.sendToWorker(workerNodeKey, { kill: true })
})
}
const workerInfo = this.getWorkerInfo(workerNodeKey)
return (
workerInfo != null &&
- Array.isArray(workerInfo.taskFunctions) &&
- workerInfo.taskFunctions.length > 2
+ Array.isArray(workerInfo.taskFunctionNames) &&
+ workerInfo.taskFunctionNames.length > 2
)
}
) {
--workerTaskStatistics.executing
}
- if (message.taskError == null) {
+ if (message.workerError == null) {
++workerTaskStatistics.executed
} else {
++workerTaskStatistics.failed
workerUsage: WorkerUsage,
message: MessageValue<Response>
): void {
- if (message.taskError != null) {
+ if (message.workerError != null) {
return
}
updateMeasurementStatistics(
workerUsage: WorkerUsage,
message: MessageValue<Response>
): void {
- if (message.taskError != null) {
+ if (message.workerError != null) {
return
}
const eluTaskStatisticsRequirements: MeasurementStatisticsRequirements =
worker.on('online', this.opts.onlineHandler ?? EMPTY_FUNCTION)
worker.on('message', this.opts.messageHandler ?? EMPTY_FUNCTION)
worker.on('error', this.opts.errorHandler ?? EMPTY_FUNCTION)
- worker.on('error', (error) => {
+ worker.on('error', error => {
const workerNodeKey = this.getWorkerNodeKeyByWorker(worker)
const workerInfo = this.getWorkerInfo(workerNodeKey)
workerInfo.ready = false
this.emitter?.emit(PoolEvents.error, error)
if (
this.opts.restartWorkerOnError === true &&
- !this.starting &&
- this.started
+ this.started &&
+ !this.starting
) {
if (workerInfo.dynamic) {
this.createAndSetupDynamicWorkerNode()
*/
protected createAndSetupDynamicWorkerNode (): number {
const workerNodeKey = this.createAndSetupWorkerNode()
- this.registerWorkerMessageListener(workerNodeKey, (message) => {
+ this.registerWorkerMessageListener(workerNodeKey, message => {
const localWorkerNodeKey = this.getWorkerNodeKeyByWorkerId(
message.workerId
)
workerUsage.tasks.executing === 0 &&
this.tasksQueueSize(localWorkerNodeKey) === 0)))
) {
- this.destroyWorkerNode(localWorkerNodeKey).catch((error) => {
+ this.destroyWorkerNode(localWorkerNodeKey).catch(error => {
this.emitter?.emit(PoolEvents.error, error)
})
}
})
const workerInfo = this.getWorkerInfo(workerNodeKey)
this.sendToWorker(workerNodeKey, {
- checkActive: true,
- workerId: workerInfo.id as number
+ checkActive: true
})
+ if (this.taskFunctions.size > 0) {
+ for (const [taskFunctionName, taskFunction] of this.taskFunctions) {
+ this.sendTaskFunctionOperationToWorker(workerNodeKey, {
+ taskFunctionOperation: 'add',
+ taskFunctionName,
+ taskFunction: taskFunction.toString()
+ }).catch(error => {
+ this.emitter?.emit(PoolEvents.error, error)
+ })
+ }
+ }
workerInfo.dynamic = true
if (
this.workerChoiceStrategyContext.getStrategyPolicy().dynamicWorkerReady ||
.runTime.aggregate,
elu: this.workerChoiceStrategyContext.getTaskStatisticsRequirements()
.elu.aggregate
- },
- workerId: this.getWorkerInfo(workerNodeKey).id as number
+ }
})
}
while (this.tasksQueueSize(workerNodeKey) > 0) {
const destinationWorkerNodeKey = this.workerNodes.reduce(
(minWorkerNodeKey, workerNode, workerNodeKey, workerNodes) => {
- if (!workerNode.info.ready) {
- return minWorkerNodeKey
- }
- return workerNode.usage.tasks.queued <
- workerNodes[minWorkerNodeKey].usage.tasks.queued
+ return workerNode.info.ready &&
+ workerNode.usage.tasks.queued <
+ workerNodes[minWorkerNodeKey].usage.tasks.queued
? workerNodeKey
: minWorkerNodeKey
},
0
)
- if (destinationWorkerNodeKey != null) {
- const destinationWorkerNode = this.workerNodes[destinationWorkerNodeKey]
- const task = {
- ...(this.dequeueTask(workerNodeKey) as Task<Data>),
- workerId: destinationWorkerNode.info.id as number
- }
- if (this.shallExecuteTask(destinationWorkerNodeKey)) {
- this.executeTask(destinationWorkerNodeKey, task)
- } else {
- this.enqueueTask(destinationWorkerNodeKey, task)
- }
+ const task = this.dequeueTask(workerNodeKey) as Task<Data>
+ if (this.shallExecuteTask(destinationWorkerNodeKey)) {
+ this.executeTask(destinationWorkerNodeKey, task)
+ } else {
+ this.enqueueTask(destinationWorkerNodeKey, task)
}
}
}
private taskStealingOnEmptyQueue (workerId: number): void {
const destinationWorkerNodeKey = this.getWorkerNodeKeyByWorkerId(workerId)
- const destinationWorkerNode = this.workerNodes[destinationWorkerNodeKey]
const workerNodes = this.workerNodes
.slice()
.sort(
workerNodeB.usage.tasks.queued - workerNodeA.usage.tasks.queued
)
const sourceWorkerNode = workerNodes.find(
- (workerNode) =>
+ workerNode =>
workerNode.info.ready &&
workerNode.info.id !== workerId &&
workerNode.usage.tasks.queued > 0
)
if (sourceWorkerNode != null) {
- const task = {
- ...(sourceWorkerNode.popTask() as Task<Data>),
- workerId: destinationWorkerNode.info.id as number
- }
+ const task = sourceWorkerNode.popTask() as Task<Data>
if (this.shallExecuteTask(destinationWorkerNodeKey)) {
this.executeTask(destinationWorkerNodeKey, task)
} else {
workerNode.usage.tasks.queued <
(this.opts.tasksQueueOptions?.size as number) - sizeOffset
) {
- const task = {
- ...(sourceWorkerNode.popTask() as Task<Data>),
- workerId: workerNode.info.id as number
- }
+ const task = sourceWorkerNode.popTask() as Task<Data>
if (this.shallExecuteTask(workerNodeKey)) {
this.executeTask(workerNodeKey, task)
} else {
* @returns The listener function to execute when a message is received from a worker.
*/
protected workerListener (): (message: MessageValue<Response>) => void {
- return (message) => {
+ return message => {
this.checkMessageWorkerId(message)
- if (message.ready != null && message.taskFunctions != null) {
+ if (message.ready != null && message.taskFunctionNames != null) {
// Worker ready response received from worker
this.handleWorkerReadyResponse(message)
} else if (message.taskId != null) {
// Task execution response received from worker
this.handleTaskExecutionResponse(message)
- } else if (message.taskFunctions != null) {
- // Task functions message received from worker
+ } else if (message.taskFunctionNames != null) {
+ // Task function names message received from worker
this.getWorkerInfo(
this.getWorkerNodeKeyByWorkerId(message.workerId)
- ).taskFunctions = message.taskFunctions
+ ).taskFunctionNames = message.taskFunctionNames
}
}
}
private handleWorkerReadyResponse (message: MessageValue<Response>): void {
if (message.ready === false) {
- throw new Error(`Worker ${message.workerId} failed to initialize`)
+ throw new Error(
+ `Worker ${message.workerId as number} failed to initialize`
+ )
}
const workerInfo = this.getWorkerInfo(
this.getWorkerNodeKeyByWorkerId(message.workerId)
)
workerInfo.ready = message.ready as boolean
- workerInfo.taskFunctions = message.taskFunctions
+ workerInfo.taskFunctionNames = message.taskFunctionNames
if (this.emitter != null && this.ready) {
this.emitter.emit(PoolEvents.ready, this.info)
}
}
private handleTaskExecutionResponse (message: MessageValue<Response>): void {
- const { taskId, taskError, data } = message
+ const { taskId, workerError, data } = message
const promiseResponse = this.promiseResponseMap.get(taskId as string)
if (promiseResponse != null) {
- if (taskError != null) {
- this.emitter?.emit(PoolEvents.taskError, taskError)
- promiseResponse.reject(taskError.message)
+ if (workerError != null) {
+ this.emitter?.emit(PoolEvents.taskError, workerError)
+ promiseResponse.reject(workerError.message)
} else {
promiseResponse.resolve(data as Response)
}
return (
this.opts.enableTasksQueue === true &&
this.workerNodes.findIndex(
- (workerNode) => !workerNode.hasBackPressure()
+ workerNode => !workerNode.hasBackPressure()
) === -1
)
}