import type {
MessageValue,
PromiseResponseWrapper,
- Task,
- Writable
+ Task
} from '../utility-types'
import {
DEFAULT_TASK_NAME,
)
}
if (
- workerChoiceStrategyOptions.choiceRetries != null &&
- !Number.isSafeInteger(workerChoiceStrategyOptions.choiceRetries)
+ workerChoiceStrategyOptions.retries != null &&
+ !Number.isSafeInteger(workerChoiceStrategyOptions.retries)
) {
throw new TypeError(
- 'Invalid worker choice strategy options: choice retries must be an integer'
+ 'Invalid worker choice strategy options: retries must be an integer'
)
}
if (
- workerChoiceStrategyOptions.choiceRetries != null &&
- workerChoiceStrategyOptions.choiceRetries <= 0
+ workerChoiceStrategyOptions.retries != null &&
+ workerChoiceStrategyOptions.retries < 0
) {
throw new RangeError(
- `Invalid worker choice strategy options: choice retries '${workerChoiceStrategyOptions.choiceRetries}' must be greater than zero`
+ `Invalid worker choice strategy options: retries '${workerChoiceStrategyOptions.retries}' must be greater or equal than zero`
)
}
if (
}
private checkValidTasksQueueOptions (
- tasksQueueOptions: Writable<TasksQueueOptions>
+ tasksQueueOptions: TasksQueueOptions
): void {
if (tasksQueueOptions != null && !isPlainObject(tasksQueueOptions)) {
throw new TypeError('Invalid tasks queue options: must be a plain object')
`Invalid worker node tasks concurrency: ${tasksQueueOptions.concurrency} is a negative integer or zero`
)
}
- if (
- tasksQueueOptions?.queueMaxSize != null &&
- tasksQueueOptions?.size != null
- ) {
+ if (tasksQueueOptions?.queueMaxSize != null) {
throw new Error(
- 'Invalid tasks queue options: cannot specify both queueMaxSize and size'
+ 'Invalid tasks queue options: queueMaxSize is deprecated, please use size instead'
)
}
- if (tasksQueueOptions?.queueMaxSize != null) {
- tasksQueueOptions.size = tasksQueueOptions.queueMaxSize
- }
if (
tasksQueueOptions?.size != null &&
!Number.isSafeInteger(tasksQueueOptions.size)
) {
throw new TypeError(
- 'Invalid worker node tasks queue max size: must be an integer'
+ 'Invalid worker node tasks queue size: must be an integer'
)
}
if (tasksQueueOptions?.size != null && tasksQueueOptions.size <= 0) {
throw new RangeError(
- `Invalid worker node tasks queue max size: ${tasksQueueOptions.size} is a negative integer or zero`
+ `Invalid worker node tasks queue size: ${tasksQueueOptions.size} is a negative integer or zero`
)
}
}
...(this.opts.enableTasksQueue === true && {
backPressure: this.hasBackPressure()
}),
+ ...(this.opts.enableTasksQueue === true && {
+ stolenTasks: this.workerNodes.reduce(
+ (accumulator, workerNode) =>
+ accumulator + workerNode.usage.tasks.stolen,
+ 0
+ )
+ }),
failedTasks: this.workerNodes.reduce(
(accumulator, workerNode) =>
accumulator + workerNode.usage.tasks.failed,
this.checkValidTasksQueueOptions(tasksQueueOptions)
this.opts.tasksQueueOptions =
this.buildTasksQueueOptions(tasksQueueOptions)
- this.setTasksQueueMaxSize(this.opts.tasksQueueOptions.size as number)
+ this.setTasksQueueSize(this.opts.tasksQueueOptions.size as number)
} else if (this.opts.tasksQueueOptions != null) {
delete this.opts.tasksQueueOptions
}
}
- private setTasksQueueMaxSize (size: number): void {
+ private setTasksQueueSize (size: number): void {
for (const workerNode of this.workerNodes) {
workerNode.tasksQueueBackPressureSize = size
}
return []
}
+ private shallExecuteTask (workerNodeKey: number): boolean {
+ return (
+ this.tasksQueueSize(workerNodeKey) === 0 &&
+ this.workerNodes[workerNodeKey].usage.tasks.executing <
+ (this.opts.tasksQueueOptions?.concurrency as number)
+ )
+ }
+
/** @inheritDoc */
public async execute (
data?: Data,
return await new Promise<Response>((resolve, reject) => {
if (!this.started) {
reject(new Error('Cannot execute a task on destroyed pool'))
+ return
}
if (name != null && typeof name !== 'string') {
reject(new TypeError('name argument must be a string'))
+ return
}
if (
name != null &&
name.trim().length === 0
) {
reject(new TypeError('name argument must not be an empty string'))
+ return
}
if (transferList != null && !Array.isArray(transferList)) {
reject(new TypeError('transferList argument must be an array'))
+ return
}
const timestamp = performance.now()
const workerNodeKey = this.chooseWorkerNode()
const workerInfo = this.getWorkerInfo(workerNodeKey) as WorkerInfo
- if (
- name != null &&
- Array.isArray(workerInfo.taskFunctions) &&
- !workerInfo.taskFunctions.includes(name)
- ) {
- reject(
- new Error(`Task function '${name}' is not registered in the pool`)
- )
- }
const task: Task<Data> = {
name: name ?? DEFAULT_TASK_NAME,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
if (
this.opts.enableTasksQueue === false ||
(this.opts.enableTasksQueue === true &&
- this.workerNodes[workerNodeKey].usage.tasks.executing <
- (this.opts.tasksQueueOptions?.concurrency as number))
+ this.shallExecuteTask(workerNodeKey))
) {
this.executeTask(workerNodeKey, task)
} else {
* @virtual
*/
protected setupHook (): void {
- // Intentionally empty
+ /* Intentionally empty */
}
/**
while (this.tasksQueueSize(workerNodeKey) > 0) {
let destinationWorkerNodeKey!: number
let minQueuedTasks = Infinity
- let executeTask = false
for (const [workerNodeId, workerNode] of this.workerNodes.entries()) {
if (workerNode.info.ready && workerNodeId !== workerNodeKey) {
- if (
- workerNode.usage.tasks.executing <
- (this.opts.tasksQueueOptions?.concurrency as number)
- ) {
- executeTask = true
- }
if (workerNode.usage.tasks.queued === 0) {
destinationWorkerNodeKey = workerNodeId
break
}
}
if (destinationWorkerNodeKey != null) {
+ const destinationWorkerNode = this.workerNodes[destinationWorkerNodeKey]
const task = {
...(this.dequeueTask(workerNodeKey) as Task<Data>),
- workerId: (this.getWorkerInfo(destinationWorkerNodeKey) as WorkerInfo)
- .id as number
+ workerId: destinationWorkerNode.info.id as number
}
- if (executeTask) {
+ if (this.shallExecuteTask(destinationWorkerNodeKey)) {
this.executeTask(destinationWorkerNodeKey, task)
} else {
this.enqueueTask(destinationWorkerNodeKey, task)
}
}
+ private updateTaskStolenStatisticsWorkerUsage (
+ workerNodeKey: number,
+ taskName: string
+ ): void {
+ const workerNode = this.workerNodes[workerNodeKey]
+ if (workerNode?.usage != null) {
+ ++workerNode.usage.tasks.stolen
+ }
+ if (
+ this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
+ workerNode.getTaskFunctionWorkerUsage(taskName) != null
+ ) {
+ const taskFunctionWorkerUsage = workerNode.getTaskFunctionWorkerUsage(
+ taskName
+ ) as WorkerUsage
+ ++taskFunctionWorkerUsage.tasks.stolen
+ }
+ }
+
private taskStealingOnEmptyQueue (workerId: number): void {
const destinationWorkerNodeKey = this.getWorkerNodeKeyByWorkerId(workerId)
const destinationWorkerNode = this.workerNodes[destinationWorkerNodeKey]
...(sourceWorkerNode.popTask() as Task<Data>),
workerId: destinationWorkerNode.info.id as number
}
- if (
- destinationWorkerNode.usage.tasks.executing <
- (this.opts.tasksQueueOptions?.concurrency as number)
- ) {
+ if (this.shallExecuteTask(destinationWorkerNodeKey)) {
this.executeTask(destinationWorkerNodeKey, task)
} else {
this.enqueueTask(destinationWorkerNodeKey, task)
}
+ this.updateTaskStolenStatisticsWorkerUsage(
+ destinationWorkerNodeKey,
+ task.name as string
+ )
break
}
}
}
private tasksStealingOnBackPressure (workerId: number): void {
+ const sizeOffset = 1
+ if ((this.opts.tasksQueueOptions?.size as number) <= sizeOffset) {
+ return
+ }
const sourceWorkerNode =
this.workerNodes[this.getWorkerNodeKeyByWorkerId(workerId)]
const workerNodes = this.workerNodes
workerNode.info.ready &&
workerNode.info.id !== workerId &&
workerNode.usage.tasks.queued <
- (this.opts.tasksQueueOptions?.size as number) - 1
+ (this.opts.tasksQueueOptions?.size as number) - sizeOffset
) {
const task = {
...(sourceWorkerNode.popTask() as Task<Data>),
workerId: workerNode.info.id as number
}
- if (
- workerNode.usage.tasks.executing <
- (this.opts.tasksQueueOptions?.concurrency as number)
- ) {
+ if (this.shallExecuteTask(workerNodeKey)) {
this.executeTask(workerNodeKey, task)
} else {
this.enqueueTask(workerNodeKey, task)
}
+ this.updateTaskStolenStatisticsWorkerUsage(
+ workerNodeKey,
+ task.name as string
+ )
}
}
}
private addWorkerNode (worker: Worker): number {
const workerNode = new WorkerNode<Worker, Data>(
worker,
- this.worker,
this.opts.tasksQueueOptions?.size ?? Math.pow(this.maxSize, 2)
)
// Flag the worker node as ready at pool startup.
this.workerNodes.push(workerNode)
const workerNodeKey = this.getWorkerNodeKeyByWorker(worker)
if (workerNodeKey === -1) {
- throw new Error('Worker node added not found')
+ throw new Error('Worker added not found in worker nodes')
}
return workerNodeKey
}