Merge branch 'master' into task-functions-properties
authorJérôme Benoit <jerome.benoit@sap.com>
Mon, 29 Apr 2024 15:53:32 +0000 (17:53 +0200)
committerGitHub <noreply@github.com>
Mon, 29 Apr 2024 15:53:32 +0000 (17:53 +0200)
34 files changed:
CHANGELOG.md
docs/api.md
src/deque.ts
src/index.ts
src/pools/abstract-pool.ts
src/pools/pool.ts
src/pools/selection-strategies/abstract-worker-choice-strategy.ts
src/pools/selection-strategies/selection-strategies-utils.ts [new file with mode: 0644]
src/pools/selection-strategies/worker-choice-strategies-context.ts [new file with mode: 0644]
src/pools/selection-strategies/worker-choice-strategy-context.ts [deleted file]
src/pools/utils.ts
src/pools/worker-node.ts
src/pools/worker.ts
src/priority-queue.ts [new file with mode: 0644]
src/utility-types.ts
src/utils.ts
src/worker/abstract-worker.ts
src/worker/cluster-worker.ts
src/worker/task-functions.ts
src/worker/thread-worker.ts
src/worker/utils.ts
tests/pools/abstract-pool.test.mjs
tests/pools/cluster/dynamic.test.mjs
tests/pools/selection-strategies/selection-strategies-utils.test.mjs [new file with mode: 0644]
tests/pools/selection-strategies/selection-strategies.test.mjs
tests/pools/selection-strategies/worker-choice-strategies-context.test.mjs [new file with mode: 0644]
tests/pools/selection-strategies/worker-choice-strategy-context.test.mjs [deleted file]
tests/pools/thread/dynamic.test.mjs
tests/pools/utils.test.mjs
tests/pools/worker-node.test.mjs
tests/priority-queue.test.mjs [new file with mode: 0644]
tests/worker/abstract-worker.test.mjs
tests/worker/cluster-worker.test.mjs
tests/worker/thread-worker.test.mjs

index 4986a585d356cc734bcfeda23377a66def15a218..fb7ce9f02a424e30ba6ff6b1510e488ccf744a97 100644 (file)
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Changed
+
+- BREAKING CHANGE: `listTaskFunctionNames()` to `listTaskFunctionsProperties()` in pool and worker API returning registered task functions properties.
+
 ## [3.1.30] - 2024-04-22
 
 ### Fixed:
index 5e4eeb227ff12b5d2c894f952ca6435d66cc0418..731104f497964c3e4170657e562475c1d385cae2 100644 (file)
@@ -11,7 +11,7 @@
   - [`pool.hasTaskFunction(name)`](#poolhastaskfunctionname)
   - [`pool.addTaskFunction(name, fn)`](#pooladdtaskfunctionname-fn)
   - [`pool.removeTaskFunction(name)`](#poolremovetaskfunctionname)
-  - [`pool.listTaskFunctionNames()`](#poollisttaskfunctionnames)
+  - [`pool.listTaskFunctionsProperties()`](#poollisttaskfunctionsproperties)
   - [`pool.setDefaultTaskFunction(name)`](#poolsetdefaulttaskfunctionname)
   - [Pool options](#pool-options)
 - [Worker](#worker)
@@ -19,7 +19,7 @@
     - [`YourWorker.hasTaskFunction(name)`](#yourworkerhastaskfunctionname)
     - [`YourWorker.addTaskFunction(name, fn)`](#yourworkeraddtaskfunctionname-fn)
     - [`YourWorker.removeTaskFunction(name)`](#yourworkerremovetaskfunctionname)
-    - [`YourWorker.listTaskFunctionNames()`](#yourworkerlisttaskfunctionnames)
+    - [`YourWorker.listTaskFunctionsProperties()`](#yourworkerlisttaskfunctionsproperties)
     - [`YourWorker.setDefaultTaskFunction(name)`](#yourworkersetdefaulttaskfunctionname)
 
 ## Pool
@@ -72,9 +72,9 @@ This method is available on both pool implementations and returns a boolean prom
 
 This method is available on both pool implementations and returns a boolean promise.
 
-### `pool.listTaskFunctionNames()`
+### `pool.listTaskFunctionsProperties()`
 
-This method is available on both pool implementations and returns an array of the task function names.
+This method is available on both pool implementations and returns an array of the task function properties.
 
 ### `pool.setDefaultTaskFunction(name)`
 
@@ -95,7 +95,7 @@ An object with these properties:
 - `exitHandler` (optional) - A function that will listen for exit event on each worker.  
   Default: `() => {}`
 
-- `workerChoiceStrategy` (optional) - The worker choice strategy to use in this pool:
+- `workerChoiceStrategy` (optional) - The default worker choice strategy to use in this pool:
 
   - `WorkerChoiceStrategies.ROUND_ROBIN`: Submit tasks to worker in a round robin fashion
   - `WorkerChoiceStrategies.LEAST_USED`: Submit tasks to the worker with the minimum number of executed, executing and queued tasks
@@ -186,9 +186,9 @@ This method is available on both worker implementations and returns `{ status: b
 
 This method is available on both worker implementations and returns `{ status: boolean, error?: Error }`.
 
-#### `YourWorker.listTaskFunctionNames()`
+#### `YourWorker.listTaskFunctionsProperties()`
 
-This method is available on both worker implementations and returns an array of the task function names.
+This method is available on both worker implementations and returns an array of the task function properties.
 
 #### `YourWorker.setDefaultTaskFunction(name)`
 
index 52441a3ec25f4f7251575104a0884f9ffeb22d00..5fb26770de369c581073abbd1b3fd752ab4695b5 100644 (file)
@@ -187,6 +187,11 @@ export class Deque<T> {
     }
   }
 
+  /**
+   * Increments the size of the deque.
+   *
+   * @returns The new size of the deque.
+   */
   private incrementSize (): number {
     ++this.size
     if (this.size > this.maxSize) {
index 5b0eb9090e3c8cf583a67ee6c9d392842a23267c..1d778513d8030595decb29b6aae67bbcddf5122d 100644 (file)
@@ -27,7 +27,7 @@ export {
   Measurements,
   WorkerChoiceStrategies
 } from './pools/selection-strategies/selection-strategies-types.js'
-export type { WorkerChoiceStrategyContext } from './pools/selection-strategies/worker-choice-strategy-context.js'
+export type { WorkerChoiceStrategiesContext } from './pools/selection-strategies/worker-choice-strategies-context.js'
 export { DynamicThreadPool } from './pools/thread/dynamic.js'
 export type { ThreadPoolOptions } from './pools/thread/fixed.js'
 export { FixedThreadPool } from './pools/thread/fixed.js'
@@ -50,6 +50,7 @@ export type {
   WorkerUsage
 } from './pools/worker.js'
 export { WorkerTypes } from './pools/worker.js'
+export type { PriorityQueue } from './priority-queue.js'
 export type {
   MessageValue,
   PromiseResponseWrapper,
index 199344067013c0b06169b53287d1848eb42e58cd..5e88a7cd3392e98ae9e4757a4d4a00c174b4d471 100644 (file)
@@ -7,10 +7,12 @@ import type { TransferListItem } from 'node:worker_threads'
 import type {
   MessageValue,
   PromiseResponseWrapper,
-  Task
+  Task,
+  TaskFunctionProperties
 } from '../utility-types.js'
 import {
   average,
+  buildTaskFunctionProperties,
   DEFAULT_TASK_NAME,
   EMPTY_FUNCTION,
   exponentialDelay,
@@ -22,7 +24,10 @@ import {
   round,
   sleep
 } from '../utils.js'
-import type { TaskFunction } from '../worker/task-functions.js'
+import type {
+  TaskFunction,
+  TaskFunctionObject
+} from '../worker/task-functions.js'
 import { KillBehaviors } from '../worker/worker-options.js'
 import {
   type IPool,
@@ -39,7 +44,7 @@ import {
   type WorkerChoiceStrategy,
   type WorkerChoiceStrategyOptions
 } from './selection-strategies/selection-strategies-types.js'
-import { WorkerChoiceStrategyContext } from './selection-strategies/worker-choice-strategy-context.js'
+import { WorkerChoiceStrategiesContext } from './selection-strategies/worker-choice-strategies-context.js'
 import {
   checkFilePath,
   checkValidTasksQueueOptions,
@@ -82,7 +87,7 @@ export abstract class AbstractPool<
   /**
    * The task execution response promise map:
    * - `key`: The message id of each submitted task.
-   * - `value`: An object that contains the worker, the execution response promise resolve and reject callbacks.
+   * - `value`: An object that contains task's worker node key, execution response promise resolve and reject callbacks, async resource.
    *
    * When we receive a message from the worker, we get a map entry with the promise resolve/reject bound to the message id.
    */
@@ -90,9 +95,9 @@ export abstract class AbstractPool<
     new Map<string, PromiseResponseWrapper<Response>>()
 
   /**
-   * Worker choice strategy context referencing a worker choice algorithm implementation.
+   * Worker choice strategies context referencing worker choice algorithms implementation.
    */
-  protected workerChoiceStrategyContext?: WorkerChoiceStrategyContext<
+  protected workerChoiceStrategiesContext?: WorkerChoiceStrategiesContext<
   Worker,
   Data,
   Response
@@ -101,9 +106,12 @@ export abstract class AbstractPool<
   /**
    * The task functions added at runtime map:
    * - `key`: The task function name.
-   * - `value`: The task function itself.
+   * - `value`: The task function object.
    */
-  private readonly taskFunctions: Map<string, TaskFunction<Data, Response>>
+  private readonly taskFunctions: Map<
+  string,
+  TaskFunctionObject<Data, Response>
+  >
 
   /**
    * Whether the pool is started or not.
@@ -161,19 +169,20 @@ export abstract class AbstractPool<
     if (this.opts.enableEvents === true) {
       this.initializeEventEmitter()
     }
-    this.workerChoiceStrategyContext = new WorkerChoiceStrategyContext<
+    this.workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext<
     Worker,
     Data,
     Response
     >(
       this,
-      this.opts.workerChoiceStrategy,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      [this.opts.workerChoiceStrategy!],
       this.opts.workerChoiceStrategyOptions
     )
 
     this.setupHook()
 
-    this.taskFunctions = new Map<string, TaskFunction<Data, Response>>()
+    this.taskFunctions = new Map<string, TaskFunctionObject<Data, Response>>()
 
     this.started = false
     this.starting = false
@@ -288,13 +297,13 @@ export abstract class AbstractPool<
       started: this.started,
       ready: this.ready,
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      strategy: this.opts.workerChoiceStrategy!,
-      strategyRetries: this.workerChoiceStrategyContext?.retriesCount ?? 0,
+      defaultStrategy: this.opts.workerChoiceStrategy!,
+      strategyRetries: this.workerChoiceStrategiesContext?.retriesCount ?? 0,
       minSize: this.minimumNumberOfWorkers,
       maxSize: this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers,
-      ...(this.workerChoiceStrategyContext?.getTaskStatisticsRequirements()
+      ...(this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
         .runTime.aggregate === true &&
-        this.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+        this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
           .waitTime.aggregate && {
         utilization: round(this.utilization)
       }),
@@ -357,7 +366,7 @@ export abstract class AbstractPool<
           accumulator + workerNode.usage.tasks.failed,
         0
       ),
-      ...(this.workerChoiceStrategyContext?.getTaskStatisticsRequirements()
+      ...(this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
         .runTime.aggregate === true && {
         runTime: {
           minimum: round(
@@ -374,7 +383,7 @@ export abstract class AbstractPool<
               )
             )
           ),
-          ...(this.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+          ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
             .runTime.average && {
             average: round(
               average(
@@ -386,7 +395,7 @@ export abstract class AbstractPool<
               )
             )
           }),
-          ...(this.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+          ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
             .runTime.median && {
             median: round(
               median(
@@ -400,7 +409,7 @@ export abstract class AbstractPool<
           })
         }
       }),
-      ...(this.workerChoiceStrategyContext?.getTaskStatisticsRequirements()
+      ...(this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
         .waitTime.aggregate === true && {
         waitTime: {
           minimum: round(
@@ -417,7 +426,7 @@ export abstract class AbstractPool<
               )
             )
           ),
-          ...(this.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+          ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
             .waitTime.average && {
             average: round(
               average(
@@ -429,7 +438,7 @@ export abstract class AbstractPool<
               )
             )
           }),
-          ...(this.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+          ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
             .waitTime.median && {
             median: round(
               median(
@@ -538,31 +547,52 @@ export abstract class AbstractPool<
     workerChoiceStrategy: WorkerChoiceStrategy,
     workerChoiceStrategyOptions?: WorkerChoiceStrategyOptions
   ): void {
+    let requireSync = false
     checkValidWorkerChoiceStrategy(workerChoiceStrategy)
-    this.opts.workerChoiceStrategy = workerChoiceStrategy
-    this.workerChoiceStrategyContext?.setWorkerChoiceStrategy(
-      this.opts.workerChoiceStrategy
-    )
     if (workerChoiceStrategyOptions != null) {
-      this.setWorkerChoiceStrategyOptions(workerChoiceStrategyOptions)
+      requireSync = this.setWorkerChoiceStrategyOptions(
+        workerChoiceStrategyOptions
+      )
     }
-    for (const [workerNodeKey, workerNode] of this.workerNodes.entries()) {
-      workerNode.resetUsage()
-      this.sendStatisticsMessageToWorker(workerNodeKey)
+    if (workerChoiceStrategy !== this.opts.workerChoiceStrategy) {
+      this.opts.workerChoiceStrategy = workerChoiceStrategy
+      this.workerChoiceStrategiesContext?.setDefaultWorkerChoiceStrategy(
+        this.opts.workerChoiceStrategy,
+        this.opts.workerChoiceStrategyOptions
+      )
+      requireSync = true
+    }
+    if (requireSync) {
+      this.workerChoiceStrategiesContext?.syncWorkerChoiceStrategies(
+        this.getWorkerWorkerChoiceStrategies(),
+        this.opts.workerChoiceStrategyOptions
+      )
+      for (const workerNodeKey of this.workerNodes.keys()) {
+        this.sendStatisticsMessageToWorker(workerNodeKey)
+      }
     }
   }
 
   /** @inheritDoc */
   public setWorkerChoiceStrategyOptions (
     workerChoiceStrategyOptions: WorkerChoiceStrategyOptions | undefined
-  ): void {
+  ): boolean {
     this.checkValidWorkerChoiceStrategyOptions(workerChoiceStrategyOptions)
     if (workerChoiceStrategyOptions != null) {
       this.opts.workerChoiceStrategyOptions = workerChoiceStrategyOptions
+      this.workerChoiceStrategiesContext?.setOptions(
+        this.opts.workerChoiceStrategyOptions
+      )
+      this.workerChoiceStrategiesContext?.syncWorkerChoiceStrategies(
+        this.getWorkerWorkerChoiceStrategies(),
+        this.opts.workerChoiceStrategyOptions
+      )
+      for (const workerNodeKey of this.workerNodes.keys()) {
+        this.sendStatisticsMessageToWorker(workerNodeKey)
+      }
+      return true
     }
-    this.workerChoiceStrategyContext?.setOptions(
-      this.opts.workerChoiceStrategyOptions
-    )
+    return false
   }
 
   /** @inheritDoc */
@@ -803,21 +833,15 @@ export abstract class AbstractPool<
 
   /** @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
+    return this.listTaskFunctionsProperties().some(
+      taskFunctionProperties => taskFunctionProperties.name === name
+    )
   }
 
   /** @inheritDoc */
   public async addTaskFunction (
     name: string,
-    fn: TaskFunction<Data, Response>
+    fn: TaskFunction<Data, Response> | TaskFunctionObject<Data, Response>
   ): Promise<boolean> {
     if (typeof name !== 'string') {
       throw new TypeError('name argument must be a string')
@@ -825,15 +849,21 @@ export abstract class AbstractPool<
     if (typeof name === 'string' && name.trim().length === 0) {
       throw new TypeError('name argument must not be an empty string')
     }
-    if (typeof fn !== 'function') {
-      throw new TypeError('fn argument must be a function')
+    if (typeof fn === 'function') {
+      fn = { taskFunction: fn } satisfies TaskFunctionObject<Data, Response>
+    }
+    if (typeof fn.taskFunction !== 'function') {
+      throw new TypeError('taskFunction property must be a function')
     }
     const opResult = await this.sendTaskFunctionOperationToWorkers({
       taskFunctionOperation: 'add',
-      taskFunctionName: name,
-      taskFunction: fn.toString()
+      taskFunctionProperties: buildTaskFunctionProperties(name, fn),
+      taskFunction: fn.taskFunction.toString()
     })
     this.taskFunctions.set(name, fn)
+    this.workerChoiceStrategiesContext?.syncWorkerChoiceStrategies(
+      this.getWorkerWorkerChoiceStrategies()
+    )
     return opResult
   }
 
@@ -846,31 +876,78 @@ export abstract class AbstractPool<
     }
     const opResult = await this.sendTaskFunctionOperationToWorkers({
       taskFunctionOperation: 'remove',
-      taskFunctionName: name
+      taskFunctionProperties: buildTaskFunctionProperties(
+        name,
+        this.taskFunctions.get(name)
+      )
     })
     this.deleteTaskFunctionWorkerUsages(name)
     this.taskFunctions.delete(name)
+    this.workerChoiceStrategiesContext?.syncWorkerChoiceStrategies(
+      this.getWorkerWorkerChoiceStrategies()
+    )
     return opResult
   }
 
   /** @inheritDoc */
-  public listTaskFunctionNames (): string[] {
+  public listTaskFunctionsProperties (): TaskFunctionProperties[] {
     for (const workerNode of this.workerNodes) {
       if (
-        Array.isArray(workerNode.info.taskFunctionNames) &&
-        workerNode.info.taskFunctionNames.length > 0
+        Array.isArray(workerNode.info.taskFunctionsProperties) &&
+        workerNode.info.taskFunctionsProperties.length > 0
       ) {
-        return workerNode.info.taskFunctionNames
+        return workerNode.info.taskFunctionsProperties
       }
     }
     return []
   }
 
+  /**
+   * Gets task function strategy, if any.
+   *
+   * @param name - The task function name.
+   * @returns The task function worker choice strategy if the task function worker choice strategy is defined, `undefined` otherwise.
+   */
+  private readonly getTaskFunctionWorkerWorkerChoiceStrategy = (
+    name?: string
+  ): WorkerChoiceStrategy | undefined => {
+    if (name != null) {
+      return this.listTaskFunctionsProperties().find(
+        (taskFunctionProperties: TaskFunctionProperties) =>
+          taskFunctionProperties.name === name
+      )?.strategy
+    }
+  }
+
+  /**
+   * Gets the worker choice strategies registered in this pool.
+   *
+   * @returns The worker choice strategies.
+   */
+  private readonly getWorkerWorkerChoiceStrategies =
+    (): Set<WorkerChoiceStrategy> => {
+      return new Set([
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        this.opts.workerChoiceStrategy!,
+        ...(this.listTaskFunctionsProperties()
+          .map(
+            (taskFunctionProperties: TaskFunctionProperties) =>
+              taskFunctionProperties.strategy
+          )
+          .filter(
+            (strategy: WorkerChoiceStrategy | undefined) => strategy != null
+          ) as WorkerChoiceStrategy[])
+      ])
+    }
+
   /** @inheritDoc */
   public async setDefaultTaskFunction (name: string): Promise<boolean> {
     return await this.sendTaskFunctionOperationToWorkers({
       taskFunctionOperation: 'default',
-      taskFunctionName: name
+      taskFunctionProperties: buildTaskFunctionProperties(
+        name,
+        this.taskFunctions.get(name)
+      )
     })
   }
 
@@ -921,7 +998,9 @@ export abstract class AbstractPool<
         return
       }
       const timestamp = performance.now()
-      const workerNodeKey = this.chooseWorkerNode()
+      const workerNodeKey = this.chooseWorkerNode(
+        this.getTaskFunctionWorkerWorkerChoiceStrategy(name)
+      )
       const task: Task<Data> = {
         name: name ?? DEFAULT_TASK_NAME,
         // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -1092,7 +1171,7 @@ export abstract class AbstractPool<
       const workerUsage = this.workerNodes[workerNodeKey].usage
       ++workerUsage.tasks.executing
       updateWaitTimeWorkerUsage(
-        this.workerChoiceStrategyContext,
+        this.workerChoiceStrategiesContext,
         workerUsage,
         task
       )
@@ -1110,7 +1189,7 @@ export abstract class AbstractPool<
       ].getTaskFunctionWorkerUsage(task.name!)!
       ++taskFunctionWorkerUsage.tasks.executing
       updateWaitTimeWorkerUsage(
-        this.workerChoiceStrategyContext,
+        this.workerChoiceStrategiesContext,
         taskFunctionWorkerUsage,
         task
       )
@@ -1134,12 +1213,12 @@ export abstract class AbstractPool<
       const workerUsage = this.workerNodes[workerNodeKey].usage
       updateTaskStatisticsWorkerUsage(workerUsage, message)
       updateRunTimeWorkerUsage(
-        this.workerChoiceStrategyContext,
+        this.workerChoiceStrategiesContext,
         workerUsage,
         message
       )
       updateEluWorkerUsage(
-        this.workerChoiceStrategyContext,
+        this.workerChoiceStrategiesContext,
         workerUsage,
         message
       )
@@ -1159,19 +1238,19 @@ export abstract class AbstractPool<
       ].getTaskFunctionWorkerUsage(message.taskPerformance!.name)!
       updateTaskStatisticsWorkerUsage(taskFunctionWorkerUsage, message)
       updateRunTimeWorkerUsage(
-        this.workerChoiceStrategyContext,
+        this.workerChoiceStrategiesContext,
         taskFunctionWorkerUsage,
         message
       )
       updateEluWorkerUsage(
-        this.workerChoiceStrategyContext,
+        this.workerChoiceStrategiesContext,
         taskFunctionWorkerUsage,
         message
       )
       needWorkerChoiceStrategyUpdate = true
     }
     if (needWorkerChoiceStrategyUpdate) {
-      this.workerChoiceStrategyContext?.update(workerNodeKey)
+      this.workerChoiceStrategiesContext?.update(workerNodeKey)
     }
   }
 
@@ -1185,30 +1264,31 @@ export abstract class AbstractPool<
     const workerInfo = this.getWorkerInfo(workerNodeKey)
     return (
       workerInfo != null &&
-      Array.isArray(workerInfo.taskFunctionNames) &&
-      workerInfo.taskFunctionNames.length > 2
+      Array.isArray(workerInfo.taskFunctionsProperties) &&
+      workerInfo.taskFunctionsProperties.length > 2
     )
   }
 
   /**
    * Chooses a worker node for the next task.
    *
-   * The default worker choice strategy uses a round robin algorithm to distribute the tasks.
-   *
+   * @param workerChoiceStrategy - The worker choice strategy.
    * @returns The chosen worker node key
    */
-  private chooseWorkerNode (): number {
+  private chooseWorkerNode (
+    workerChoiceStrategy?: WorkerChoiceStrategy
+  ): number {
     if (this.shallCreateDynamicWorker()) {
       const workerNodeKey = this.createAndSetupDynamicWorkerNode()
       if (
-        this.workerChoiceStrategyContext?.getStrategyPolicy()
-          .dynamicWorkerUsage === true
+        this.workerChoiceStrategiesContext?.getPolicy().dynamicWorkerUsage ===
+        true
       ) {
         return workerNodeKey
       }
     }
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return this.workerChoiceStrategyContext!.execute()
+    return this.workerChoiceStrategiesContext!.execute(workerChoiceStrategy)
   }
 
   /**
@@ -1329,11 +1409,14 @@ export abstract class AbstractPool<
       checkActive: true
     })
     if (this.taskFunctions.size > 0) {
-      for (const [taskFunctionName, taskFunction] of this.taskFunctions) {
+      for (const [taskFunctionName, taskFunctionObject] of this.taskFunctions) {
         this.sendTaskFunctionOperationToWorker(workerNodeKey, {
           taskFunctionOperation: 'add',
-          taskFunctionName,
-          taskFunction: taskFunction.toString()
+          taskFunctionProperties: buildTaskFunctionProperties(
+            taskFunctionName,
+            taskFunctionObject
+          ),
+          taskFunction: taskFunctionObject.taskFunction.toString()
         }).catch((error: unknown) => {
           this.emitter?.emit(PoolEvents.error, error)
         })
@@ -1342,10 +1425,10 @@ export abstract class AbstractPool<
     const workerNode = this.workerNodes[workerNodeKey]
     workerNode.info.dynamic = true
     if (
-      this.workerChoiceStrategyContext?.getStrategyPolicy()
-        .dynamicWorkerReady === true ||
-      this.workerChoiceStrategyContext?.getStrategyPolicy()
-        .dynamicWorkerUsage === true
+      this.workerChoiceStrategiesContext?.getPolicy().dynamicWorkerReady ===
+        true ||
+      this.workerChoiceStrategiesContext?.getPolicy().dynamicWorkerUsage ===
+        true
     ) {
       workerNode.info.ready = true
     }
@@ -1440,11 +1523,11 @@ export abstract class AbstractPool<
     this.sendToWorker(workerNodeKey, {
       statistics: {
         runTime:
-          this.workerChoiceStrategyContext?.getTaskStatisticsRequirements()
+          this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
             .runTime.aggregate ?? false,
         elu:
-          this.workerChoiceStrategyContext?.getTaskStatisticsRequirements().elu
-            .aggregate ?? false
+          this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+            .elu.aggregate ?? false
       }
     })
   }
@@ -1587,10 +1670,10 @@ export abstract class AbstractPool<
     ) {
       workerInfo.stealing = false
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      for (const taskName of workerInfo.taskFunctionNames!) {
+      for (const taskFunctionProperties of workerInfo.taskFunctionsProperties!) {
         this.resetTaskSequentiallyStolenStatisticsTaskFunctionWorkerUsage(
           workerNodeKey,
-          taskName
+          taskFunctionProperties.name
         )
       }
       this.resetTaskSequentiallyStolenStatisticsWorkerUsage(workerNodeKey)
@@ -1726,21 +1809,21 @@ export abstract class AbstractPool<
     message: MessageValue<Response>
   ): void => {
     this.checkMessageWorkerId(message)
-    const { workerId, ready, taskId, taskFunctionNames } = message
-    if (ready != null && taskFunctionNames != null) {
+    const { workerId, ready, taskId, taskFunctionsProperties } = message
+    if (ready != null && taskFunctionsProperties != null) {
       // Worker ready response received from worker
       this.handleWorkerReadyResponse(message)
-    } else if (taskId != null) {
-      // Task execution response received from worker
-      this.handleTaskExecutionResponse(message)
-    } else if (taskFunctionNames != null) {
-      // Task function names message received from worker
+    } else if (taskFunctionsProperties != null) {
+      // Task function properties message received from worker
       const workerInfo = this.getWorkerInfo(
         this.getWorkerNodeKeyByWorkerId(workerId)
       )
       if (workerInfo != null) {
-        workerInfo.taskFunctionNames = taskFunctionNames
+        workerInfo.taskFunctionsProperties = taskFunctionsProperties
       }
+    } else if (taskId != null) {
+      // Task execution response received from worker
+      this.handleTaskExecutionResponse(message)
     }
   }
 
@@ -1752,14 +1835,14 @@ export abstract class AbstractPool<
   }
 
   private handleWorkerReadyResponse (message: MessageValue<Response>): void {
-    const { workerId, ready, taskFunctionNames } = message
+    const { workerId, ready, taskFunctionsProperties } = message
     if (ready == null || !ready) {
       throw new Error(`Worker ${workerId} failed to initialize`)
     }
     const workerNode =
       this.workerNodes[this.getWorkerNodeKeyByWorkerId(workerId)]
     workerNode.info.ready = ready
-    workerNode.info.taskFunctionNames = taskFunctionNames
+    workerNode.info.taskFunctionsProperties = taskFunctionsProperties
     this.checkAndEmitReadyEvent()
   }
 
@@ -1905,7 +1988,7 @@ export abstract class AbstractPool<
     const workerNodeKey = this.workerNodes.indexOf(workerNode)
     if (workerNodeKey !== -1) {
       this.workerNodes.splice(workerNodeKey, 1)
-      this.workerChoiceStrategyContext?.remove(workerNodeKey)
+      this.workerChoiceStrategiesContext?.remove(workerNodeKey)
     }
     this.checkAndEmitEmptyEvent()
   }
index bcf7d9385035b938ac3c0ed9bdb24dda6382e0a1..ccb4b5b94ef858ad00c0eb5a28c490651854489e 100644 (file)
@@ -2,6 +2,7 @@ import type { ClusterSettings } from 'node:cluster'
 import type { EventEmitterAsyncResource } from 'node:events'
 import type { TransferListItem, WorkerOptions } from 'node:worker_threads'
 
+import type { TaskFunctionProperties } from '../utility-types.js'
 import type { TaskFunction } from '../worker/task-functions.js'
 import type {
   WorkerChoiceStrategy,
@@ -76,7 +77,7 @@ export interface PoolInfo {
   readonly worker: WorkerType
   readonly started: boolean
   readonly ready: boolean
-  readonly strategy: WorkerChoiceStrategy
+  readonly defaultStrategy: WorkerChoiceStrategy
   readonly strategyRetries: number
   readonly minSize: number
   readonly maxSize: number
@@ -184,7 +185,7 @@ export interface PoolOptions<Worker extends IWorker> {
    */
   startWorkers?: boolean
   /**
-   * The worker choice strategy to use in this pool.
+   * The default worker choice strategy to use in this pool.
    *
    * @defaultValue WorkerChoiceStrategies.ROUND_ROBIN
    */
@@ -321,11 +322,11 @@ export interface IPool<
    */
   readonly removeTaskFunction: (name: string) => Promise<boolean>
   /**
-   * Lists the names of task function available in this pool.
+   * Lists the properties of task functions available in this pool.
    *
-   * @returns The names of task function available in this pool.
+   * @returns The properties of task functions available in this pool.
    */
-  readonly listTaskFunctionNames: () => string[]
+  readonly listTaskFunctionsProperties: () => TaskFunctionProperties[]
   /**
    * Sets the default task function in this pool.
    *
@@ -334,9 +335,9 @@ export interface IPool<
    */
   readonly setDefaultTaskFunction: (name: string) => Promise<boolean>
   /**
-   * Sets the worker choice strategy in this pool.
+   * Sets the default worker choice strategy in this pool.
    *
-   * @param workerChoiceStrategy - The worker choice strategy.
+   * @param workerChoiceStrategy - The default worker choice strategy.
    * @param workerChoiceStrategyOptions - The worker choice strategy options.
    */
   readonly setWorkerChoiceStrategy: (
@@ -347,10 +348,11 @@ export interface IPool<
    * Sets the worker choice strategy options in this pool.
    *
    * @param workerChoiceStrategyOptions - The worker choice strategy options.
+   * @returns `true` if the worker choice strategy options were set, `false` otherwise.
    */
   readonly setWorkerChoiceStrategyOptions: (
     workerChoiceStrategyOptions: WorkerChoiceStrategyOptions
-  ) => void
+  ) => boolean
   /**
    * Enables/disables the worker node tasks queue in this pool.
    *
index 3618944c2043a07b1e8e709aa877d831b2bfc76b..849b8a4f96feed20bcdb6bf22ba7330c8964517f 100644 (file)
@@ -1,16 +1,16 @@
 import type { IPool } from '../pool.js'
-import {
-  buildWorkerChoiceStrategyOptions,
-  DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
-} from '../utils.js'
+import { DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS } from '../utils.js'
 import type { IWorker } from '../worker.js'
 import type {
   IWorkerChoiceStrategy,
-  MeasurementStatisticsRequirements,
   StrategyPolicy,
   TaskStatisticsRequirements,
   WorkerChoiceStrategyOptions
 } from './selection-strategies-types.js'
+import {
+  buildWorkerChoiceStrategyOptions,
+  toggleMedianMeasurementStatisticsRequirements
+} from './selection-strategies-utils.js'
 
 /**
  * Worker choice strategy abstract base class.
@@ -64,37 +64,23 @@ export abstract class AbstractWorkerChoiceStrategy<
   protected setTaskStatisticsRequirements (
     opts: WorkerChoiceStrategyOptions | undefined
   ): void {
-    this.toggleMedianMeasurementStatisticsRequirements(
+    toggleMedianMeasurementStatisticsRequirements(
       this.taskStatisticsRequirements.runTime,
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       opts!.runTime!.median
     )
-    this.toggleMedianMeasurementStatisticsRequirements(
+    toggleMedianMeasurementStatisticsRequirements(
       this.taskStatisticsRequirements.waitTime,
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       opts!.waitTime!.median
     )
-    this.toggleMedianMeasurementStatisticsRequirements(
+    toggleMedianMeasurementStatisticsRequirements(
       this.taskStatisticsRequirements.elu,
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       opts!.elu!.median
     )
   }
 
-  private toggleMedianMeasurementStatisticsRequirements (
-    measurementStatisticsRequirements: MeasurementStatisticsRequirements,
-    toggleMedian: boolean
-  ): void {
-    if (measurementStatisticsRequirements.average && toggleMedian) {
-      measurementStatisticsRequirements.average = false
-      measurementStatisticsRequirements.median = toggleMedian
-    }
-    if (measurementStatisticsRequirements.median && !toggleMedian) {
-      measurementStatisticsRequirements.average = true
-      measurementStatisticsRequirements.median = toggleMedian
-    }
-  }
-
   protected resetWorkerNodeKeyProperties (): void {
     this.nextWorkerNodeKey = 0
     this.previousWorkerNodeKey = 0
diff --git a/src/pools/selection-strategies/selection-strategies-utils.ts b/src/pools/selection-strategies/selection-strategies-utils.ts
new file mode 100644 (file)
index 0000000..6e63f78
--- /dev/null
@@ -0,0 +1,191 @@
+import { cpus } from 'node:os'
+
+import type { IPool } from '../pool.js'
+import type { IWorker } from '../worker.js'
+import { FairShareWorkerChoiceStrategy } from './fair-share-worker-choice-strategy.js'
+import { InterleavedWeightedRoundRobinWorkerChoiceStrategy } from './interleaved-weighted-round-robin-worker-choice-strategy.js'
+import { LeastBusyWorkerChoiceStrategy } from './least-busy-worker-choice-strategy.js'
+import { LeastEluWorkerChoiceStrategy } from './least-elu-worker-choice-strategy.js'
+import { LeastUsedWorkerChoiceStrategy } from './least-used-worker-choice-strategy.js'
+import { RoundRobinWorkerChoiceStrategy } from './round-robin-worker-choice-strategy.js'
+import {
+  type IWorkerChoiceStrategy,
+  type MeasurementStatisticsRequirements,
+  type StrategyPolicy,
+  type TaskStatisticsRequirements,
+  WorkerChoiceStrategies,
+  type WorkerChoiceStrategy,
+  type WorkerChoiceStrategyOptions
+} from './selection-strategies-types.js'
+import { WeightedRoundRobinWorkerChoiceStrategy } from './weighted-round-robin-worker-choice-strategy.js'
+import type { WorkerChoiceStrategiesContext } from './worker-choice-strategies-context.js'
+
+const estimatedCpuSpeed = (): number => {
+  const runs = 150000000
+  const begin = performance.now()
+  // eslint-disable-next-line no-empty
+  for (let i = runs; i > 0; i--) {}
+  const end = performance.now()
+  const duration = end - begin
+  return Math.trunc(runs / duration / 1000) // in MHz
+}
+
+const getDefaultWorkerWeight = (): number => {
+  const currentCpus = cpus()
+  let estCpuSpeed: number | undefined
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+  if (currentCpus.every(cpu => cpu.speed == null || cpu.speed === 0)) {
+    estCpuSpeed = estimatedCpuSpeed()
+  }
+  let cpusCycleTimeWeight = 0
+  for (const cpu of currentCpus) {
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+    if (cpu.speed == null || cpu.speed === 0) {
+      cpu.speed =
+        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+        currentCpus.find(cpu => cpu.speed != null && cpu.speed !== 0)?.speed ??
+        estCpuSpeed ??
+        2000
+    }
+    // CPU estimated cycle time
+    const numberOfDigits = cpu.speed.toString().length - 1
+    const cpuCycleTime = 1 / (cpu.speed / Math.pow(10, numberOfDigits))
+    cpusCycleTimeWeight += cpuCycleTime * Math.pow(10, numberOfDigits)
+  }
+  return Math.round(cpusCycleTimeWeight / currentCpus.length)
+}
+
+const getDefaultWeights = (
+  poolMaxSize: number,
+  defaultWorkerWeight?: number
+): Record<number, number> => {
+  defaultWorkerWeight = defaultWorkerWeight ?? getDefaultWorkerWeight()
+  const weights: Record<number, number> = {}
+  for (let workerNodeKey = 0; workerNodeKey < poolMaxSize; workerNodeKey++) {
+    weights[workerNodeKey] = defaultWorkerWeight
+  }
+  return weights
+}
+
+export const getWorkerChoiceStrategiesRetries = <
+  Worker extends IWorker,
+  Data,
+  Response
+>(
+    pool: IPool<Worker, Data, Response>,
+    opts?: WorkerChoiceStrategyOptions
+  ): number => {
+  return (
+    pool.info.maxSize +
+    Object.keys(opts?.weights ?? getDefaultWeights(pool.info.maxSize)).length
+  )
+}
+
+export const buildWorkerChoiceStrategyOptions = <
+  Worker extends IWorker,
+  Data,
+  Response
+>(
+    pool: IPool<Worker, Data, Response>,
+    opts?: WorkerChoiceStrategyOptions
+  ): WorkerChoiceStrategyOptions => {
+  opts = structuredClone(opts ?? {})
+  opts.weights = opts.weights ?? getDefaultWeights(pool.info.maxSize)
+  return {
+    ...{
+      runTime: { median: false },
+      waitTime: { median: false },
+      elu: { median: false }
+    },
+    ...opts
+  }
+}
+
+export const toggleMedianMeasurementStatisticsRequirements = (
+  measurementStatisticsRequirements: MeasurementStatisticsRequirements,
+  toggleMedian: boolean
+): void => {
+  if (measurementStatisticsRequirements.average && toggleMedian) {
+    measurementStatisticsRequirements.average = false
+    measurementStatisticsRequirements.median = toggleMedian
+  }
+  if (measurementStatisticsRequirements.median && !toggleMedian) {
+    measurementStatisticsRequirements.average = true
+    measurementStatisticsRequirements.median = toggleMedian
+  }
+}
+
+export const buildWorkerChoiceStrategiesPolicy = (
+  workerChoiceStrategies: Map<WorkerChoiceStrategy, IWorkerChoiceStrategy>
+): StrategyPolicy => {
+  const policies: StrategyPolicy[] = []
+  for (const workerChoiceStrategy of workerChoiceStrategies.values()) {
+    policies.push(workerChoiceStrategy.strategyPolicy)
+  }
+  return {
+    dynamicWorkerUsage: policies.some(p => p.dynamicWorkerUsage),
+    dynamicWorkerReady: policies.some(p => p.dynamicWorkerReady)
+  }
+}
+
+export const buildWorkerChoiceStrategiesTaskStatisticsRequirements = (
+  workerChoiceStrategies: Map<WorkerChoiceStrategy, IWorkerChoiceStrategy>
+): TaskStatisticsRequirements => {
+  const taskStatisticsRequirements: TaskStatisticsRequirements[] = []
+  for (const workerChoiceStrategy of workerChoiceStrategies.values()) {
+    taskStatisticsRequirements.push(
+      workerChoiceStrategy.taskStatisticsRequirements
+    )
+  }
+  return {
+    runTime: {
+      aggregate: taskStatisticsRequirements.some(r => r.runTime.aggregate),
+      average: taskStatisticsRequirements.some(r => r.runTime.average),
+      median: taskStatisticsRequirements.some(r => r.runTime.median)
+    },
+    waitTime: {
+      aggregate: taskStatisticsRequirements.some(r => r.waitTime.aggregate),
+      average: taskStatisticsRequirements.some(r => r.waitTime.average),
+      median: taskStatisticsRequirements.some(r => r.waitTime.median)
+    },
+    elu: {
+      aggregate: taskStatisticsRequirements.some(r => r.elu.aggregate),
+      average: taskStatisticsRequirements.some(r => r.elu.average),
+      median: taskStatisticsRequirements.some(r => r.elu.median)
+    }
+  }
+}
+
+export const getWorkerChoiceStrategy = <Worker extends IWorker, Data, Response>(
+  workerChoiceStrategy: WorkerChoiceStrategy,
+  pool: IPool<Worker, Data, Response>,
+  context: ThisType<WorkerChoiceStrategiesContext<Worker, Data, Response>>,
+  opts?: WorkerChoiceStrategyOptions
+): IWorkerChoiceStrategy => {
+  switch (workerChoiceStrategy) {
+    case WorkerChoiceStrategies.ROUND_ROBIN:
+      return new (RoundRobinWorkerChoiceStrategy.bind(context))(pool, opts)
+    case WorkerChoiceStrategies.LEAST_USED:
+      return new (LeastUsedWorkerChoiceStrategy.bind(context))(pool, opts)
+    case WorkerChoiceStrategies.LEAST_BUSY:
+      return new (LeastBusyWorkerChoiceStrategy.bind(context))(pool, opts)
+    case WorkerChoiceStrategies.LEAST_ELU:
+      return new (LeastEluWorkerChoiceStrategy.bind(context))(pool, opts)
+    case WorkerChoiceStrategies.FAIR_SHARE:
+      return new (FairShareWorkerChoiceStrategy.bind(context))(pool, opts)
+    case WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN:
+      return new (WeightedRoundRobinWorkerChoiceStrategy.bind(context))(
+        pool,
+        opts
+      )
+    case WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN:
+      return new (InterleavedWeightedRoundRobinWorkerChoiceStrategy.bind(
+        context
+      ))(pool, opts)
+    default:
+      throw new Error(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `Worker choice strategy '${workerChoiceStrategy}' is not valid`
+      )
+  }
+}
diff --git a/src/pools/selection-strategies/worker-choice-strategies-context.ts b/src/pools/selection-strategies/worker-choice-strategies-context.ts
new file mode 100644 (file)
index 0000000..d088015
--- /dev/null
@@ -0,0 +1,282 @@
+import type { IPool } from '../pool.js'
+import type { IWorker } from '../worker.js'
+import type {
+  IWorkerChoiceStrategy,
+  StrategyPolicy,
+  TaskStatisticsRequirements,
+  WorkerChoiceStrategy,
+  WorkerChoiceStrategyOptions
+} from './selection-strategies-types.js'
+import { WorkerChoiceStrategies } from './selection-strategies-types.js'
+import {
+  buildWorkerChoiceStrategiesPolicy,
+  buildWorkerChoiceStrategiesTaskStatisticsRequirements,
+  getWorkerChoiceStrategiesRetries,
+  getWorkerChoiceStrategy
+} from './selection-strategies-utils.js'
+
+/**
+ * The worker choice strategies context.
+ *
+ * @typeParam Worker - Type of worker.
+ * @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
+ * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
+ */
+export class WorkerChoiceStrategiesContext<
+  Worker extends IWorker,
+  Data = unknown,
+  Response = unknown
+> {
+  /**
+   * The number of worker choice strategies execution retries.
+   */
+  public retriesCount: number
+
+  /**
+   * The default worker choice strategy in the context.
+   */
+  private defaultWorkerChoiceStrategy: WorkerChoiceStrategy
+
+  /**
+   * The worker choice strategies registered in the context.
+   */
+  private readonly workerChoiceStrategies: Map<
+  WorkerChoiceStrategy,
+  IWorkerChoiceStrategy
+  >
+
+  /**
+   * The active worker choice strategies in the context policy.
+   */
+  private workerChoiceStrategiesPolicy: StrategyPolicy
+
+  /**
+   * The active worker choice strategies in the context task statistics requirements.
+   */
+  private workerChoiceStrategiesTaskStatisticsRequirements: TaskStatisticsRequirements
+
+  /**
+   * The maximum number of worker choice strategies execution retries.
+   */
+  private readonly retries: number
+
+  /**
+   * Worker choice strategies context constructor.
+   *
+   * @param pool - The pool instance.
+   * @param workerChoiceStrategies - The worker choice strategies. @defaultValue [WorkerChoiceStrategies.ROUND_ROBIN]
+   * @param opts - The worker choice strategy options.
+   */
+  public constructor (
+    private readonly pool: IPool<Worker, Data, Response>,
+    workerChoiceStrategies: WorkerChoiceStrategy[] = [
+      WorkerChoiceStrategies.ROUND_ROBIN
+    ],
+    opts?: WorkerChoiceStrategyOptions
+  ) {
+    this.execute = this.execute.bind(this)
+    this.defaultWorkerChoiceStrategy = workerChoiceStrategies[0]
+    this.workerChoiceStrategies = new Map<
+    WorkerChoiceStrategy,
+    IWorkerChoiceStrategy
+    >()
+    for (const workerChoiceStrategy of workerChoiceStrategies) {
+      this.addWorkerChoiceStrategy(workerChoiceStrategy, this.pool, opts)
+    }
+    this.workerChoiceStrategiesPolicy = buildWorkerChoiceStrategiesPolicy(
+      this.workerChoiceStrategies
+    )
+    this.workerChoiceStrategiesTaskStatisticsRequirements =
+      buildWorkerChoiceStrategiesTaskStatisticsRequirements(
+        this.workerChoiceStrategies
+      )
+    this.retriesCount = 0
+    this.retries = getWorkerChoiceStrategiesRetries<Worker, Data, Response>(
+      this.pool,
+      opts
+    )
+  }
+
+  /**
+   * Gets the active worker choice strategies in the context policy.
+   *
+   * @returns The strategies policy.
+   */
+  public getPolicy (): StrategyPolicy {
+    return this.workerChoiceStrategiesPolicy
+  }
+
+  /**
+   * Gets the active worker choice strategies in the context task statistics requirements.
+   *
+   * @returns The strategies task statistics requirements.
+   */
+  public getTaskStatisticsRequirements (): TaskStatisticsRequirements {
+    return this.workerChoiceStrategiesTaskStatisticsRequirements
+  }
+
+  /**
+   * Sets the default worker choice strategy to use in the context.
+   *
+   * @param workerChoiceStrategy - The default worker choice strategy to set.
+   * @param opts - The worker choice strategy options.
+   */
+  public setDefaultWorkerChoiceStrategy (
+    workerChoiceStrategy: WorkerChoiceStrategy,
+    opts?: WorkerChoiceStrategyOptions
+  ): void {
+    if (workerChoiceStrategy !== this.defaultWorkerChoiceStrategy) {
+      this.defaultWorkerChoiceStrategy = workerChoiceStrategy
+      this.addWorkerChoiceStrategy(workerChoiceStrategy, this.pool, opts)
+    }
+  }
+
+  /**
+   * Updates the worker node key in the active worker choice strategies in the context internals.
+   *
+   * @returns `true` if the update is successful, `false` otherwise.
+   */
+  public update (workerNodeKey: number): boolean {
+    const res: boolean[] = []
+    for (const workerChoiceStrategy of this.workerChoiceStrategies.values()) {
+      res.push(workerChoiceStrategy.update(workerNodeKey))
+    }
+    return res.every(r => r)
+  }
+
+  /**
+   * Executes the given worker choice strategy in the context algorithm.
+   *
+   * @param workerChoiceStrategy - The worker choice strategy algorithm to execute. @defaultValue this.defaultWorkerChoiceStrategy
+   * @returns The key of the worker node.
+   * @throws {@link https://nodejs.org/api/errors.html#class-error} If after computed retries the worker node key is null or undefined.
+   */
+  public execute (
+    workerChoiceStrategy: WorkerChoiceStrategy = this
+      .defaultWorkerChoiceStrategy
+  ): number {
+    return this.executeStrategy(
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.workerChoiceStrategies.get(workerChoiceStrategy)!
+    )
+  }
+
+  /**
+   * Executes the given worker choice strategy.
+   *
+   * @param workerChoiceStrategy - The worker choice strategy.
+   * @returns The key of the worker node.
+   * @throws {@link https://nodejs.org/api/errors.html#class-error} If after computed retries the worker node key is null or undefined.
+   */
+  private executeStrategy (workerChoiceStrategy: IWorkerChoiceStrategy): number {
+    let workerNodeKey: number | undefined
+    let chooseCount = 0
+    let retriesCount = 0
+    do {
+      workerNodeKey = workerChoiceStrategy.choose()
+      if (workerNodeKey == null && chooseCount > 0) {
+        ++retriesCount
+        ++this.retriesCount
+      }
+      ++chooseCount
+    } while (workerNodeKey == null && retriesCount < this.retries)
+    if (workerNodeKey == null) {
+      throw new Error(
+        `Worker node key chosen is null or undefined after ${retriesCount} retries`
+      )
+    }
+    return workerNodeKey
+  }
+
+  /**
+   * Removes the worker node key from the active worker choice strategies in the context.
+   *
+   * @param workerNodeKey - The worker node key.
+   * @returns `true` if the removal is successful, `false` otherwise.
+   */
+  public remove (workerNodeKey: number): boolean {
+    const res: boolean[] = []
+    for (const workerChoiceStrategy of this.workerChoiceStrategies.values()) {
+      res.push(workerChoiceStrategy.remove(workerNodeKey))
+    }
+    return res.every(r => r)
+  }
+
+  /**
+   * Sets the active worker choice strategies in the context options.
+   *
+   * @param opts - The worker choice strategy options.
+   */
+  public setOptions (opts: WorkerChoiceStrategyOptions | undefined): void {
+    for (const workerChoiceStrategy of this.workerChoiceStrategies.values()) {
+      workerChoiceStrategy.setOptions(opts)
+    }
+  }
+
+  /**
+   * Synchronizes the active worker choice strategies in the context with the given worker choice strategies.
+   *
+   * @param workerChoiceStrategies - The worker choice strategies to synchronize.
+   * @param opts - The worker choice strategy options.
+   */
+  public syncWorkerChoiceStrategies (
+    workerChoiceStrategies: Set<WorkerChoiceStrategy>,
+    opts?: WorkerChoiceStrategyOptions
+  ): void {
+    for (const workerChoiceStrategy of this.workerChoiceStrategies.keys()) {
+      if (!workerChoiceStrategies.has(workerChoiceStrategy)) {
+        this.removeWorkerChoiceStrategy(workerChoiceStrategy)
+      }
+    }
+    for (const workerChoiceStrategy of workerChoiceStrategies) {
+      if (!this.workerChoiceStrategies.has(workerChoiceStrategy)) {
+        this.addWorkerChoiceStrategy(workerChoiceStrategy, this.pool, opts)
+      }
+    }
+    this.workerChoiceStrategiesPolicy = buildWorkerChoiceStrategiesPolicy(
+      this.workerChoiceStrategies
+    )
+    this.workerChoiceStrategiesTaskStatisticsRequirements =
+      buildWorkerChoiceStrategiesTaskStatisticsRequirements(
+        this.workerChoiceStrategies
+      )
+  }
+
+  /**
+   * Adds a worker choice strategy to the context.
+   *
+   * @param workerChoiceStrategy - The worker choice strategy to add.
+   * @param opts - The worker choice strategy options.
+   * @returns The worker choice strategies.
+   */
+  private addWorkerChoiceStrategy (
+    workerChoiceStrategy: WorkerChoiceStrategy,
+    pool: IPool<Worker, Data, Response>,
+    opts?: WorkerChoiceStrategyOptions
+  ): Map<WorkerChoiceStrategy, IWorkerChoiceStrategy> {
+    if (!this.workerChoiceStrategies.has(workerChoiceStrategy)) {
+      return this.workerChoiceStrategies.set(
+        workerChoiceStrategy,
+        getWorkerChoiceStrategy<Worker, Data, Response>(
+          workerChoiceStrategy,
+          pool,
+          this,
+          opts
+        )
+      )
+    }
+    return this.workerChoiceStrategies
+  }
+
+  /**
+   * Removes a worker choice strategy from the context.
+   *
+   * @param workerChoiceStrategy - The worker choice strategy to remove.
+   * @returns `true` if the worker choice strategy is removed, `false` otherwise.
+   */
+  private removeWorkerChoiceStrategy (
+    workerChoiceStrategy: WorkerChoiceStrategy
+  ): boolean {
+    return this.workerChoiceStrategies.delete(workerChoiceStrategy)
+  }
+}
diff --git a/src/pools/selection-strategies/worker-choice-strategy-context.ts b/src/pools/selection-strategies/worker-choice-strategy-context.ts
deleted file mode 100644 (file)
index f9ddb3a..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-import type { IPool } from '../pool.js'
-import { getWorkerChoiceStrategyRetries } from '../utils.js'
-import type { IWorker } from '../worker.js'
-import { FairShareWorkerChoiceStrategy } from './fair-share-worker-choice-strategy.js'
-import { InterleavedWeightedRoundRobinWorkerChoiceStrategy } from './interleaved-weighted-round-robin-worker-choice-strategy.js'
-import { LeastBusyWorkerChoiceStrategy } from './least-busy-worker-choice-strategy.js'
-import { LeastEluWorkerChoiceStrategy } from './least-elu-worker-choice-strategy.js'
-import { LeastUsedWorkerChoiceStrategy } from './least-used-worker-choice-strategy.js'
-import { RoundRobinWorkerChoiceStrategy } from './round-robin-worker-choice-strategy.js'
-import type {
-  IWorkerChoiceStrategy,
-  StrategyPolicy,
-  TaskStatisticsRequirements,
-  WorkerChoiceStrategy,
-  WorkerChoiceStrategyOptions
-} from './selection-strategies-types.js'
-import { WorkerChoiceStrategies } from './selection-strategies-types.js'
-import { WeightedRoundRobinWorkerChoiceStrategy } from './weighted-round-robin-worker-choice-strategy.js'
-
-/**
- * The worker choice strategy context.
- *
- * @typeParam Worker - Type of worker.
- * @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
- * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
- */
-export class WorkerChoiceStrategyContext<
-  Worker extends IWorker,
-  Data = unknown,
-  Response = unknown
-> {
-  /**
-   * The number of worker choice strategy execution retries.
-   */
-  public retriesCount: number
-
-  /**
-   * The worker choice strategy instances registered in the context.
-   */
-  private readonly workerChoiceStrategies: Map<
-  WorkerChoiceStrategy,
-  IWorkerChoiceStrategy
-  >
-
-  /**
-   * The maximum number of worker choice strategy execution retries.
-   */
-  private readonly retries: number
-
-  /**
-   * Worker choice strategy context constructor.
-   *
-   * @param pool - The pool instance.
-   * @param workerChoiceStrategy - The worker choice strategy.
-   * @param opts - The worker choice strategy options.
-   */
-  public constructor (
-    pool: IPool<Worker, Data, Response>,
-    private workerChoiceStrategy: WorkerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN,
-    opts?: WorkerChoiceStrategyOptions
-  ) {
-    this.execute = this.execute.bind(this)
-    this.workerChoiceStrategies = new Map<
-    WorkerChoiceStrategy,
-    IWorkerChoiceStrategy
-    >([
-      [
-        WorkerChoiceStrategies.ROUND_ROBIN,
-        new (RoundRobinWorkerChoiceStrategy.bind(this))<Worker, Data, Response>(
-          pool,
-          opts
-        )
-      ],
-      [
-        WorkerChoiceStrategies.LEAST_USED,
-        new (LeastUsedWorkerChoiceStrategy.bind(this))<Worker, Data, Response>(
-          pool,
-          opts
-        )
-      ],
-      [
-        WorkerChoiceStrategies.LEAST_BUSY,
-        new (LeastBusyWorkerChoiceStrategy.bind(this))<Worker, Data, Response>(
-          pool,
-          opts
-        )
-      ],
-      [
-        WorkerChoiceStrategies.LEAST_ELU,
-        new (LeastEluWorkerChoiceStrategy.bind(this))<Worker, Data, Response>(
-          pool,
-          opts
-        )
-      ],
-      [
-        WorkerChoiceStrategies.FAIR_SHARE,
-        new (FairShareWorkerChoiceStrategy.bind(this))<Worker, Data, Response>(
-          pool,
-          opts
-        )
-      ],
-      [
-        WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN,
-        new (WeightedRoundRobinWorkerChoiceStrategy.bind(this))<
-        Worker,
-        Data,
-        Response
-        >(pool, opts)
-      ],
-      [
-        WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN,
-        new (InterleavedWeightedRoundRobinWorkerChoiceStrategy.bind(this))<
-        Worker,
-        Data,
-        Response
-        >(pool, opts)
-      ]
-    ])
-    this.retriesCount = 0
-    this.retries = getWorkerChoiceStrategyRetries(pool, opts)
-  }
-
-  /**
-   * Gets the strategy policy in the context.
-   *
-   * @returns The strategy policy.
-   */
-  public getStrategyPolicy (): StrategyPolicy {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return this.workerChoiceStrategies.get(this.workerChoiceStrategy)!
-      .strategyPolicy
-  }
-
-  /**
-   * Gets the worker choice strategy in the context task statistics requirements.
-   *
-   * @returns The task statistics requirements.
-   */
-  public getTaskStatisticsRequirements (): TaskStatisticsRequirements {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return this.workerChoiceStrategies.get(this.workerChoiceStrategy)!
-      .taskStatisticsRequirements
-  }
-
-  /**
-   * Sets the worker choice strategy to use in the context.
-   *
-   * @param workerChoiceStrategy - The worker choice strategy to set.
-   */
-  public setWorkerChoiceStrategy (
-    workerChoiceStrategy: WorkerChoiceStrategy
-  ): void {
-    if (this.workerChoiceStrategy !== workerChoiceStrategy) {
-      this.workerChoiceStrategy = workerChoiceStrategy
-    }
-    this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()
-  }
-
-  /**
-   * Updates the worker node key in the worker choice strategy in the context internals.
-   *
-   * @returns `true` if the update is successful, `false` otherwise.
-   */
-  public update (workerNodeKey: number): boolean {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return this.workerChoiceStrategies
-      .get(this.workerChoiceStrategy)!
-      .update(workerNodeKey)
-  }
-
-  /**
-   * Executes the worker choice strategy in the context algorithm.
-   *
-   * @returns The key of the worker node.
-   * @throws {@link https://nodejs.org/api/errors.html#class-error} If after computed retries the worker node key is null or undefined.
-   */
-  public execute (): number {
-    return this.executeStrategy(
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.workerChoiceStrategies.get(this.workerChoiceStrategy)!
-    )
-  }
-
-  /**
-   * Executes the given worker choice strategy.
-   *
-   * @param workerChoiceStrategy - The worker choice strategy.
-   * @returns The key of the worker node.
-   * @throws {@link https://nodejs.org/api/errors.html#class-error} If after computed retries the worker node key is null or undefined.
-   */
-  private executeStrategy (workerChoiceStrategy: IWorkerChoiceStrategy): number {
-    let workerNodeKey: number | undefined
-    let chooseCount = 0
-    let retriesCount = 0
-    do {
-      workerNodeKey = workerChoiceStrategy.choose()
-      if (workerNodeKey == null && chooseCount > 0) {
-        ++retriesCount
-        ++this.retriesCount
-      }
-      ++chooseCount
-    } while (workerNodeKey == null && retriesCount < this.retries)
-    if (workerNodeKey == null) {
-      throw new Error(
-        `Worker node key chosen is null or undefined after ${retriesCount} retries`
-      )
-    }
-    return workerNodeKey
-  }
-
-  /**
-   * Removes the worker node key from the worker choice strategy in the context.
-   *
-   * @param workerNodeKey - The worker node key.
-   * @returns `true` if the removal is successful, `false` otherwise.
-   */
-  public remove (workerNodeKey: number): boolean {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return this.workerChoiceStrategies
-      .get(this.workerChoiceStrategy)!
-      .remove(workerNodeKey)
-  }
-
-  /**
-   * Sets the worker choice strategies in the context options.
-   *
-   * @param opts - The worker choice strategy options.
-   */
-  public setOptions (opts: WorkerChoiceStrategyOptions | undefined): void {
-    for (const workerChoiceStrategy of this.workerChoiceStrategies.values()) {
-      workerChoiceStrategy.setOptions(opts)
-    }
-  }
-}
index 724c4ab960c691e8f4a5d8661f9c442ff14d5b3d..3fa17312d1fe24f54fb121eeda4d9dee6124e0c9 100644 (file)
@@ -1,6 +1,5 @@
 import cluster, { Worker as ClusterWorker } from 'node:cluster'
 import { existsSync } from 'node:fs'
-import { cpus } from 'node:os'
 import { env } from 'node:process'
 import {
   SHARE_ENV,
@@ -10,14 +9,13 @@ import {
 
 import type { MessageValue, Task } from '../utility-types.js'
 import { average, isPlainObject, max, median, min } from '../utils.js'
-import type { IPool, TasksQueueOptions } from './pool.js'
+import type { TasksQueueOptions } from './pool.js'
 import {
   type MeasurementStatisticsRequirements,
   WorkerChoiceStrategies,
-  type WorkerChoiceStrategy,
-  type WorkerChoiceStrategyOptions
+  type WorkerChoiceStrategy
 } from './selection-strategies/selection-strategies-types.js'
-import type { WorkerChoiceStrategyContext } from './selection-strategies/worker-choice-strategy-context.js'
+import type { WorkerChoiceStrategiesContext } from './selection-strategies/worker-choice-strategies-context.js'
 import {
   type IWorker,
   type IWorkerNode,
@@ -50,91 +48,6 @@ export const getDefaultTasksQueueOptions = (
   }
 }
 
-export const getWorkerChoiceStrategyRetries = <
-  Worker extends IWorker,
-  Data,
-  Response
->(
-    pool: IPool<Worker, Data, Response>,
-    opts?: WorkerChoiceStrategyOptions
-  ): number => {
-  return (
-    pool.info.maxSize +
-    Object.keys(opts?.weights ?? getDefaultWeights(pool.info.maxSize)).length
-  )
-}
-
-export const buildWorkerChoiceStrategyOptions = <
-  Worker extends IWorker,
-  Data,
-  Response
->(
-    pool: IPool<Worker, Data, Response>,
-    opts?: WorkerChoiceStrategyOptions
-  ): WorkerChoiceStrategyOptions => {
-  opts = clone(opts ?? {})
-  opts.weights = opts.weights ?? getDefaultWeights(pool.info.maxSize)
-  return {
-    ...{
-      runTime: { median: false },
-      waitTime: { median: false },
-      elu: { median: false }
-    },
-    ...opts
-  }
-}
-
-const clone = <T>(object: T): T => {
-  return structuredClone<T>(object)
-}
-
-const getDefaultWeights = (
-  poolMaxSize: number,
-  defaultWorkerWeight?: number
-): Record<number, number> => {
-  defaultWorkerWeight = defaultWorkerWeight ?? getDefaultWorkerWeight()
-  const weights: Record<number, number> = {}
-  for (let workerNodeKey = 0; workerNodeKey < poolMaxSize; workerNodeKey++) {
-    weights[workerNodeKey] = defaultWorkerWeight
-  }
-  return weights
-}
-
-const estimatedCpuSpeed = (): number => {
-  const runs = 150000000
-  const begin = performance.now()
-  // eslint-disable-next-line no-empty
-  for (let i = runs; i > 0; i--) {}
-  const end = performance.now()
-  const duration = end - begin
-  return Math.trunc(runs / duration / 1000) // in MHz
-}
-
-const getDefaultWorkerWeight = (): number => {
-  const currentCpus = cpus()
-  let estCpuSpeed: number | undefined
-  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-  if (currentCpus.every(cpu => cpu.speed == null || cpu.speed === 0)) {
-    estCpuSpeed = estimatedCpuSpeed()
-  }
-  let cpusCycleTimeWeight = 0
-  for (const cpu of currentCpus) {
-    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-    if (cpu.speed == null || cpu.speed === 0) {
-      cpu.speed =
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-        currentCpus.find(cpu => cpu.speed != null && cpu.speed !== 0)?.speed ??
-        estCpuSpeed ??
-        2000
-    }
-    // CPU estimated cycle time
-    const numberOfDigits = cpu.speed.toString().length - 1
-    const cpuCycleTime = 1 / (cpu.speed / Math.pow(10, numberOfDigits))
-    cpusCycleTimeWeight += cpuCycleTime * Math.pow(10, numberOfDigits)
-  }
-  return Math.round(cpusCycleTimeWeight / currentCpus.length)
-}
-
 export const checkFilePath = (filePath: string | undefined): void => {
   if (filePath == null) {
     throw new TypeError('The worker file path must be specified')
@@ -174,6 +87,19 @@ export const checkDynamicPoolSize = (
   }
 }
 
+export const checkValidPriority = (priority: number | undefined): void => {
+  if (priority != null && !Number.isSafeInteger(priority)) {
+    throw new TypeError(`Invalid property 'priority': '${priority}'`)
+  }
+  if (
+    priority != null &&
+    Number.isSafeInteger(priority) &&
+    (priority < -20 || priority > 19)
+  ) {
+    throw new RangeError("Property 'priority' must be between -20 and 19")
+  }
+}
+
 export const checkValidWorkerChoiceStrategy = (
   workerChoiceStrategy: WorkerChoiceStrategy | undefined
 ): void => {
@@ -317,7 +243,7 @@ export const updateWaitTimeWorkerUsage = <
   Response = unknown
 >(
     workerChoiceStrategyContext:
-    | WorkerChoiceStrategyContext<Worker, Data, Response>
+    | WorkerChoiceStrategiesContext<Worker, Data, Response>
     | undefined,
     workerUsage: WorkerUsage,
     task: Task<Data>
@@ -356,7 +282,7 @@ export const updateRunTimeWorkerUsage = <
   Response = unknown
 >(
     workerChoiceStrategyContext:
-    | WorkerChoiceStrategyContext<Worker, Data, Response>
+    | WorkerChoiceStrategiesContext<Worker, Data, Response>
     | undefined,
     workerUsage: WorkerUsage,
     message: MessageValue<Response>
@@ -377,7 +303,7 @@ export const updateEluWorkerUsage = <
   Response = unknown
 >(
     workerChoiceStrategyContext:
-    | WorkerChoiceStrategyContext<Worker, Data, Response>
+    | WorkerChoiceStrategiesContext<Worker, Data, Response>
     | undefined,
     workerUsage: WorkerUsage,
     message: MessageValue<Response>
index 1a4df6cd4afd479607a267e0d5ada4744a312e9e..e7fbe0e83cc9d74fae0ccfd8338870fb8e0c2907 100644 (file)
@@ -3,6 +3,7 @@ import { MessageChannel } from 'node:worker_threads'
 
 import { CircularArray } from '../circular-array.js'
 import { Deque } from '../deque.js'
+import { PriorityQueue } from '../priority-queue.js'
 import type { Task } from '../utility-types.js'
 import { DEFAULT_TASK_NAME } from '../utils.js'
 import {
@@ -45,6 +46,7 @@ export class WorkerNode<Worker extends IWorker, Data = unknown>
   /** @inheritdoc */
   public tasksQueueBackPressureSize: number
   private readonly tasksQueue: Deque<Task<Data>>
+  private readonly tasksQueue2 = new PriorityQueue<Task<Data>>()
   private onBackPressureStarted: boolean
   private readonly taskFunctionsUsage: Map<string, WorkerUsage>
 
@@ -121,12 +123,6 @@ export class WorkerNode<Worker extends IWorker, Data = unknown>
     return this.tasksQueue.size >= this.tasksQueueBackPressureSize
   }
 
-  /** @inheritdoc */
-  public resetUsage (): void {
-    this.usage = this.initWorkerUsage()
-    this.taskFunctionsUsage.clear()
-  }
-
   /** @inheritdoc */
   public async terminate (): Promise<void> {
     const waitWorkerExit = new Promise<void>(resolve => {
@@ -169,21 +165,21 @@ export class WorkerNode<Worker extends IWorker, Data = unknown>
 
   /** @inheritdoc */
   public getTaskFunctionWorkerUsage (name: string): WorkerUsage | undefined {
-    if (!Array.isArray(this.info.taskFunctionNames)) {
+    if (!Array.isArray(this.info.taskFunctionsProperties)) {
       throw new Error(
-        `Cannot get task function worker usage for task function name '${name}' when task function names list is not yet defined`
+        `Cannot get task function worker usage for task function name '${name}' when task function properties list is not yet defined`
       )
     }
     if (
-      Array.isArray(this.info.taskFunctionNames) &&
-      this.info.taskFunctionNames.length < 3
+      Array.isArray(this.info.taskFunctionsProperties) &&
+      this.info.taskFunctionsProperties.length < 3
     ) {
       throw new Error(
-        `Cannot get task function worker usage for task function name '${name}' when task function names list has less than 3 elements`
+        `Cannot get task function worker usage for task function name '${name}' when task function properties list has less than 3 elements`
       )
     }
     if (name === DEFAULT_TASK_NAME) {
-      name = this.info.taskFunctionNames[1]
+      name = this.info.taskFunctionsProperties[1].name
     }
     if (!this.taskFunctionsUsage.has(name)) {
       this.taskFunctionsUsage.set(name, this.initTaskFunctionWorkerUsage(name))
@@ -262,7 +258,7 @@ export class WorkerNode<Worker extends IWorker, Data = unknown>
         if (
           (task.name === DEFAULT_TASK_NAME &&
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            name === this.info.taskFunctionNames![1]) ||
+            name === this.info.taskFunctionsProperties![1].name) ||
           (task.name !== DEFAULT_TASK_NAME && name === task.name)
         ) {
           ++taskFunctionQueueSize
index 12a2e321b11c3724e739d0e7d9fd4fe708eb9a12..ffe92e0cbaac75b7b041e5cf1c632f826450aed1 100644 (file)
@@ -2,7 +2,7 @@ import type { EventEmitter } from 'node:events'
 import type { MessageChannel, WorkerOptions } from 'node:worker_threads'
 
 import type { CircularArray } from '../circular-array.js'
-import type { Task } from '../utility-types.js'
+import type { Task, TaskFunctionProperties } from '../utility-types.js'
 
 /**
  * Callback invoked when the worker has started successfully.
@@ -173,9 +173,9 @@ export interface WorkerInfo {
    */
   stealing: boolean
   /**
-   * Task function names.
+   * Task functions properties.
    */
-  taskFunctionNames?: string[]
+  taskFunctionsProperties?: TaskFunctionProperties[]
 }
 
 /**
@@ -346,10 +346,6 @@ export interface IWorkerNode<Worker extends IWorker, Data = unknown>
    * @returns `true` if the worker node has back pressure, `false` otherwise.
    */
   readonly hasBackPressure: () => boolean
-  /**
-   * Resets usage statistics.
-   */
-  readonly resetUsage: () => void
   /**
    * Terminates the worker node.
    */
diff --git a/src/priority-queue.ts b/src/priority-queue.ts
new file mode 100644 (file)
index 0000000..cf4fb15
--- /dev/null
@@ -0,0 +1,116 @@
+/**
+ * @internal
+ */
+interface PriorityQueueNode<T> {
+  data: T
+  priority: number
+}
+
+/**
+ * k-priority queue.
+ *
+ * @typeParam T - Type of priority queue data.
+ * @internal
+ */
+export class PriorityQueue<T> {
+  private nodeArray!: Array<PriorityQueueNode<T>>
+  /** Prioritized bucket size. */
+  private readonly k: number
+  /** The size of the priority queue. */
+  public size!: number
+  /** The maximum size of the priority queue. */
+  public maxSize!: number
+
+  /**
+   * Constructs a k-priority queue.
+   *
+   * @param k - Prioritized bucket size.
+   */
+  public constructor (k = Infinity) {
+    if (k !== Infinity && !Number.isSafeInteger(k)) {
+      throw new TypeError('k must be an integer')
+    }
+    if (k < 1) {
+      throw new RangeError('k must be greater than or equal to 1')
+    }
+    this.k = k
+    this.clear()
+  }
+
+  /**
+   * Enqueue data into the priority queue.
+   *
+   * @param data - Data to enqueue.
+   * @param priority - Priority of the data. Lower values have higher priority.
+   * @returns The new size of the priority queue.
+   */
+  public enqueue (data: T, priority?: number): number {
+    priority = priority ?? 0
+    const startIndex =
+      this.k === Infinity || this.nodeArray.length / this.k < 1
+        ? 0
+        : Math.trunc(this.nodeArray.length / this.k) * this.k
+    let inserted = false
+    for (let index = startIndex; index < this.nodeArray.length; index++) {
+      if (this.nodeArray[index].priority > priority) {
+        this.nodeArray.splice(index, 0, { data, priority })
+        inserted = true
+        break
+      }
+    }
+    if (!inserted) {
+      this.nodeArray.push({ data, priority })
+    }
+    return this.incrementSize()
+  }
+
+  /**
+   * Dequeue data from the priority queue.
+   *
+   * @returns The dequeued data or `undefined` if the priority queue is empty.
+   */
+  public dequeue (): T | undefined {
+    if (this.size > 0) {
+      --this.size
+    }
+    return this.nodeArray.shift()?.data
+  }
+
+  /**
+   * Peeks at the first data.
+   * @returns The first data or `undefined` if the priority queue is empty.
+   */
+  public peekFirst (): T | undefined {
+    return this.nodeArray[0]?.data
+  }
+
+  /**
+   * Peeks at the last data.
+   * @returns The last data or `undefined` if the priority queue is empty.
+   */
+  public peekLast (): T | undefined {
+    return this.nodeArray[this.nodeArray.length - 1]?.data
+  }
+
+  /**
+   * Clears the priority queue.
+   */
+  public clear (): void {
+    this.nodeArray = []
+    this.size = 0
+    this.maxSize = 0
+  }
+
+  /**
+   * Increments the size of the deque.
+   *
+   * @returns The new size of the deque.
+   */
+  private incrementSize (): number {
+    ++this.size
+    if (this.size > this.maxSize) {
+      this.maxSize = this.size
+    }
+    return this.size
+  }
+}
index b3cea1a1516004b18b757333b71853c3faa35f8b..10d9183ca6f262cd25d475737d0e86613a55d247 100644 (file)
@@ -2,6 +2,7 @@ import type { AsyncResource } from 'node:async_hooks'
 import type { EventLoopUtilization } from 'node:perf_hooks'
 import type { MessagePort, TransferListItem } from 'node:worker_threads'
 
+import type { WorkerChoiceStrategy } from './pools/selection-strategies/selection-strategies-types.js'
 import type { KillBehavior } from './worker/worker-options.js'
 
 /**
@@ -64,6 +65,26 @@ export interface WorkerStatistics {
   readonly elu: boolean
 }
 
+/**
+ * Task function properties.
+ *
+ * @internal
+ */
+export interface TaskFunctionProperties {
+  /**
+   * Task function name.
+   */
+  name: string
+  /**
+   * Task function priority. Lower values have higher priority.
+   */
+  priority?: number
+  /**
+   * Task function worker choice strategy.
+   */
+  strategy?: WorkerChoiceStrategy
+}
+
 /**
  * Message object that is passed as a task between main worker and worker.
  *
@@ -130,17 +151,17 @@ export interface MessageValue<Data = unknown, ErrorData = unknown>
    */
   readonly taskFunctionOperationStatus?: boolean
   /**
-   * Task function serialized to string.
+   * Task function properties.
    */
-  readonly taskFunction?: string
+  readonly taskFunctionProperties?: TaskFunctionProperties
   /**
-   * Task function name.
+   * Task function serialized to string.
    */
-  readonly taskFunctionName?: string
+  readonly taskFunction?: string
   /**
-   * Task function names.
+   * Task functions properties.
    */
-  readonly taskFunctionNames?: string[]
+  readonly taskFunctionsProperties?: TaskFunctionProperties[]
   /**
    * Whether the worker computes the given statistics or not.
    */
index c568a29a049eda2de26cf9bd99d63e613294bc3f..dd839bc491b622aa871b876424474d24abdfb44e 100644 (file)
@@ -1,6 +1,8 @@
 import { getRandomValues } from 'node:crypto'
 import * as os from 'node:os'
 
+import type { TaskFunctionProperties } from './utility-types.js'
+import type { TaskFunctionObject } from './worker/task-functions.js'
 import type { KillBehavior } from './worker/worker-options.js'
 
 /**
@@ -205,7 +207,7 @@ export const max = (...args: number[]): number =>
  * @internal
  */
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
-export const once = <A extends any[], R, C>(
+export const once = <A extends any[], R, C extends ThisType<any>>(
   fn: (...args: A) => R,
   context: C
 ): ((...args: A) => R) => {
@@ -220,3 +222,18 @@ export const once = <A extends any[], R, C>(
     return result
   }
 }
+
+export const buildTaskFunctionProperties = <Data, Response>(
+  name: string,
+  taskFunctionObject: TaskFunctionObject<Data, Response> | undefined
+): TaskFunctionProperties => {
+  return {
+    name,
+    ...(taskFunctionObject?.priority != null && {
+      priority: taskFunctionObject.priority
+    }),
+    ...(taskFunctionObject?.strategy != null && {
+      strategy: taskFunctionObject.strategy
+    })
+  }
+}
index 12307f47c07ba019ca2da01f9547b633cfc64e03..2a5f1121521f15c193df29022aa755ed2aabee8f 100644 (file)
@@ -5,10 +5,12 @@ import type { MessagePort } from 'node:worker_threads'
 import type {
   MessageValue,
   Task,
+  TaskFunctionProperties,
   TaskPerformance,
   WorkerStatistics
 } from '../utility-types.js'
 import {
+  buildTaskFunctionProperties,
   DEFAULT_TASK_NAME,
   EMPTY_FUNCTION,
   isAsyncFunction,
@@ -17,13 +19,14 @@ import {
 import type {
   TaskAsyncFunction,
   TaskFunction,
+  TaskFunctionObject,
   TaskFunctionOperationResult,
   TaskFunctions,
   TaskSyncFunction
 } from './task-functions.js'
 import {
   checkTaskFunctionName,
-  checkValidTaskFunctionEntry,
+  checkValidTaskFunctionObjectEntry,
   checkValidWorkerOptions
 } from './utils.js'
 import { KillBehaviors, type WorkerOptions } from './worker-options.js'
@@ -62,9 +65,9 @@ export abstract class AbstractWorker<
    */
   protected abstract id: number
   /**
-   * Task function(s) processed by the worker when the pool's `execution` function is invoked.
+   * Task function object(s) processed by the worker when the pool's `execution` function is invoked.
    */
-  protected taskFunctions!: Map<string, TaskFunction<Data, Response>>
+  protected taskFunctions!: Map<string, TaskFunctionObject<Data, Response>>
   /**
    * Timestamp of the last task processed by this worker.
    */
@@ -122,27 +125,33 @@ export abstract class AbstractWorker<
     if (taskFunctions == null) {
       throw new Error('taskFunctions parameter is mandatory')
     }
-    this.taskFunctions = new Map<string, TaskFunction<Data, Response>>()
+    this.taskFunctions = new Map<string, TaskFunctionObject<Data, Response>>()
     if (typeof taskFunctions === 'function') {
-      const boundFn = taskFunctions.bind(this)
-      this.taskFunctions.set(DEFAULT_TASK_NAME, boundFn)
+      const fnObj = { taskFunction: taskFunctions.bind(this) }
+      this.taskFunctions.set(DEFAULT_TASK_NAME, fnObj)
       this.taskFunctions.set(
         typeof taskFunctions.name === 'string' &&
           taskFunctions.name.trim().length > 0
           ? taskFunctions.name
           : 'fn1',
-        boundFn
+        fnObj
       )
     } else if (isPlainObject(taskFunctions)) {
       let firstEntry = true
-      for (const [name, fn] of Object.entries(taskFunctions)) {
-        checkValidTaskFunctionEntry<Data, Response>(name, fn)
-        const boundFn = fn.bind(this)
+      for (let [name, fnObj] of Object.entries(taskFunctions)) {
+        if (typeof fnObj === 'function') {
+          fnObj = { taskFunction: fnObj } satisfies TaskFunctionObject<
+          Data,
+          Response
+          >
+        }
+        checkValidTaskFunctionObjectEntry<Data, Response>(name, fnObj)
+        fnObj.taskFunction = fnObj.taskFunction.bind(this)
         if (firstEntry) {
-          this.taskFunctions.set(DEFAULT_TASK_NAME, boundFn)
+          this.taskFunctions.set(DEFAULT_TASK_NAME, fnObj)
           firstEntry = false
         }
-        this.taskFunctions.set(name, boundFn)
+        this.taskFunctions.set(name, fnObj)
       }
       if (firstEntry) {
         throw new Error('taskFunctions parameter object is empty')
@@ -179,7 +188,7 @@ export abstract class AbstractWorker<
    */
   public addTaskFunction (
     name: string,
-    fn: TaskFunction<Data, Response>
+    fn: TaskFunction<Data, Response> | TaskFunctionObject<Data, Response>
   ): TaskFunctionOperationResult {
     try {
       checkTaskFunctionName(name)
@@ -188,18 +197,19 @@ export abstract class AbstractWorker<
           'Cannot add a task function with the default reserved name'
         )
       }
-      if (typeof fn !== 'function') {
-        throw new TypeError('fn parameter is not a function')
+      if (typeof fn === 'function') {
+        fn = { taskFunction: fn } satisfies TaskFunctionObject<Data, Response>
       }
-      const boundFn = fn.bind(this)
+      checkValidTaskFunctionObjectEntry<Data, Response>(name, fn)
+      fn.taskFunction = fn.taskFunction.bind(this)
       if (
         this.taskFunctions.get(name) ===
         this.taskFunctions.get(DEFAULT_TASK_NAME)
       ) {
-        this.taskFunctions.set(DEFAULT_TASK_NAME, boundFn)
+        this.taskFunctions.set(DEFAULT_TASK_NAME, fn)
       }
-      this.taskFunctions.set(name, boundFn)
-      this.sendTaskFunctionNamesToMainWorker()
+      this.taskFunctions.set(name, fn)
+      this.sendTaskFunctionsPropertiesToMainWorker()
       return { status: true }
     } catch (error) {
       return { status: false, error: error as Error }
@@ -229,7 +239,7 @@ export abstract class AbstractWorker<
         )
       }
       const deleteStatus = this.taskFunctions.delete(name)
-      this.sendTaskFunctionNamesToMainWorker()
+      this.sendTaskFunctionsPropertiesToMainWorker()
       return { status: deleteStatus }
     } catch (error) {
       return { status: false, error: error as Error }
@@ -237,28 +247,38 @@ export abstract class AbstractWorker<
   }
 
   /**
-   * Lists the names of the worker's task functions.
+   * Lists the properties of the worker's task functions.
    *
-   * @returns The names of the worker's task functions.
+   * @returns The properties of the worker's task functions.
    */
-  public listTaskFunctionNames (): string[] {
-    const names = [...this.taskFunctions.keys()]
+  public listTaskFunctionsProperties (): TaskFunctionProperties[] {
     let defaultTaskFunctionName = DEFAULT_TASK_NAME
-    for (const [name, fn] of this.taskFunctions) {
+    for (const [name, fnObj] of this.taskFunctions) {
       if (
         name !== DEFAULT_TASK_NAME &&
-        fn === this.taskFunctions.get(DEFAULT_TASK_NAME)
+        fnObj === this.taskFunctions.get(DEFAULT_TASK_NAME)
       ) {
         defaultTaskFunctionName = name
         break
       }
     }
+    const taskFunctionsProperties: TaskFunctionProperties[] = []
+    for (const [name, fnObj] of this.taskFunctions) {
+      if (name === DEFAULT_TASK_NAME || name === defaultTaskFunctionName) {
+        continue
+      }
+      taskFunctionsProperties.push(buildTaskFunctionProperties(name, fnObj))
+    }
     return [
-      names[names.indexOf(DEFAULT_TASK_NAME)],
-      defaultTaskFunctionName,
-      ...names.filter(
-        name => name !== DEFAULT_TASK_NAME && name !== defaultTaskFunctionName
-      )
+      buildTaskFunctionProperties(
+        DEFAULT_TASK_NAME,
+        this.taskFunctions.get(DEFAULT_TASK_NAME)
+      ),
+      buildTaskFunctionProperties(
+        defaultTaskFunctionName,
+        this.taskFunctions.get(defaultTaskFunctionName)
+      ),
+      ...taskFunctionsProperties
     ]
   }
 
@@ -283,7 +303,7 @@ export abstract class AbstractWorker<
       }
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       this.taskFunctions.set(DEFAULT_TASK_NAME, this.taskFunctions.get(name)!)
-      this.sendTaskFunctionNamesToMainWorker()
+      this.sendTaskFunctionsPropertiesToMainWorker()
       return { status: true }
     } catch (error) {
       return { status: false, error: error as Error }
@@ -325,29 +345,34 @@ export abstract class AbstractWorker<
   protected handleTaskFunctionOperationMessage (
     message: MessageValue<Data>
   ): void {
-    const { taskFunctionOperation, taskFunctionName, taskFunction } = message
-    if (taskFunctionName == null) {
+    const { taskFunctionOperation, taskFunctionProperties, taskFunction } =
+      message
+    if (taskFunctionProperties == null) {
       throw new Error(
-        'Cannot handle task function operation message without a task function name'
+        'Cannot handle task function operation message without task function properties'
       )
     }
     let response: TaskFunctionOperationResult
     switch (taskFunctionOperation) {
       case 'add':
-        response = this.addTaskFunction(
-          taskFunctionName,
+        response = this.addTaskFunction(taskFunctionProperties.name, {
           // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
-          new Function(`return ${taskFunction}`)() as TaskFunction<
-          Data,
-          Response
-          >
-        )
+          taskFunction: new Function(
+            `return ${taskFunction}`
+          )() as TaskFunction<Data, Response>,
+          ...(taskFunctionProperties.priority != null && {
+            priority: taskFunctionProperties.priority
+          }),
+          ...(taskFunctionProperties.strategy != null && {
+            strategy: taskFunctionProperties.strategy
+          })
+        })
         break
       case 'remove':
-        response = this.removeTaskFunction(taskFunctionName)
+        response = this.removeTaskFunction(taskFunctionProperties.name)
         break
       case 'default':
-        response = this.setDefaultTaskFunction(taskFunctionName)
+        response = this.setDefaultTaskFunction(taskFunctionProperties.name)
         break
       default:
         response = { status: false, error: new Error('Unknown task operation') }
@@ -356,11 +381,11 @@ export abstract class AbstractWorker<
     this.sendToMainWorker({
       taskFunctionOperation,
       taskFunctionOperationStatus: response.status,
-      taskFunctionName,
+      taskFunctionProperties,
       ...(!response.status &&
         response.error != null && {
         workerError: {
-          name: taskFunctionName,
+          name: taskFunctionProperties.name,
           message: this.handleError(response.error as Error | string)
         }
       })
@@ -466,11 +491,11 @@ export abstract class AbstractWorker<
   ): void
 
   /**
-   * Sends task function names to the main worker.
+   * Sends task functions properties to the main worker.
    */
-  protected sendTaskFunctionNamesToMainWorker (): void {
+  protected sendTaskFunctionsPropertiesToMainWorker (): void {
     this.sendToMainWorker({
-      taskFunctionNames: this.listTaskFunctionNames()
+      taskFunctionsProperties: this.listTaskFunctionsProperties()
     })
   }
 
@@ -504,7 +529,7 @@ export abstract class AbstractWorker<
       })
       return
     }
-    const fn = this.taskFunctions.get(taskFunctionName)
+    const fn = this.taskFunctions.get(taskFunctionName)?.taskFunction
     if (isAsyncFunction(fn)) {
       this.runAsync(fn as TaskAsyncFunction<Data, Response>, task)
     } else {
index 325b7a410b6cb7686ebee51f1d37b06043622fad..847f2174d1657b05824d12461b8bba054287ae54 100644 (file)
@@ -43,12 +43,12 @@ export class ClusterWorker<
         this.getMainWorker().on('message', this.messageListener.bind(this))
         this.sendToMainWorker({
           ready: true,
-          taskFunctionNames: this.listTaskFunctionNames()
+          taskFunctionsProperties: this.listTaskFunctionsProperties()
         })
       } catch {
         this.sendToMainWorker({
           ready: false,
-          taskFunctionNames: this.listTaskFunctionNames()
+          taskFunctionsProperties: this.listTaskFunctionsProperties()
         })
       }
     }
index 5518043eb022857209401a32ecd0a0a9d18bcf30..8999b69decbe215d279ce09cce457e6b5bca633f 100644 (file)
@@ -1,3 +1,5 @@
+import type { WorkerChoiceStrategy } from '../pools/selection-strategies/selection-strategies-types.js'
+
 /**
  * Task synchronous function that can be executed.
  *
@@ -36,18 +38,38 @@ export type TaskFunction<Data = unknown, Response = unknown> =
   | TaskSyncFunction<Data, Response>
   | TaskAsyncFunction<Data, Response>
 
+/**
+ * Task function object.
+ *
+ * @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
+ * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
+ */
+export interface TaskFunctionObject<Data = unknown, Response = unknown> {
+  /**
+   * Task function.
+   */
+  taskFunction: TaskFunction<Data, Response>
+  /**
+   * Task function priority. Lower values have higher priority.
+   */
+  priority?: number
+  /**
+   * Task function worker choice strategy.
+   */
+  strategy?: WorkerChoiceStrategy
+}
+
 /**
  * Tasks functions that can be executed.
- * This object can contain synchronous or asynchronous functions.
- * The key is the name of the function.
- * The value is the function itself.
+ * The key is the name of the task function or task function object.
+ * The value is the function or task function object.
  *
  * @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
  * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
  */
 export type TaskFunctions<Data = unknown, Response = unknown> = Record<
 string,
-TaskFunction<Data, Response>
+TaskFunction<Data, Response> | TaskFunctionObject<Data, Response>
 >
 
 /**
index 7115d71568a6fc0664ba6f74b17085b35bc50655..bf8647c53b68f972c1dbe2cf806689d7889d2d2f 100644 (file)
@@ -58,12 +58,12 @@ export class ThreadWorker<
         this.port.on('message', this.messageListener.bind(this))
         this.sendToMainWorker({
           ready: true,
-          taskFunctionNames: this.listTaskFunctionNames()
+          taskFunctionsProperties: this.listTaskFunctionsProperties()
         })
       } catch {
         this.sendToMainWorker({
           ready: false,
-          taskFunctionNames: this.listTaskFunctionNames()
+          taskFunctionsProperties: this.listTaskFunctionsProperties()
         })
       }
     }
index d8c4c3e905318b3c485991155c65cfdc03f269a7..d0883893581b0a14c24cb415bef585541b71c446 100644 (file)
@@ -1,5 +1,9 @@
+import {
+  checkValidPriority,
+  checkValidWorkerChoiceStrategy
+} from '../pools/utils.js'
 import { isPlainObject } from '../utils.js'
-import type { TaskFunction } from './task-functions.js'
+import type { TaskFunctionObject } from './task-functions.js'
 import { KillBehaviors, type WorkerOptions } from './worker-options.js'
 
 export const checkValidWorkerOptions = (
@@ -32,10 +36,13 @@ export const checkValidWorkerOptions = (
   }
 }
 
-export const checkValidTaskFunctionEntry = <Data = unknown, Response = unknown>(
-  name: string,
-  fn: TaskFunction<Data, Response>
-): void => {
+export const checkValidTaskFunctionObjectEntry = <
+  Data = unknown,
+  Response = unknown
+>(
+    name: string,
+    fnObj: TaskFunctionObject<Data, Response>
+  ): void => {
   if (typeof name !== 'string') {
     throw new TypeError('A taskFunctions parameter object key is not a string')
   }
@@ -44,11 +51,14 @@ export const checkValidTaskFunctionEntry = <Data = unknown, Response = unknown>(
       'A taskFunctions parameter object key is an empty string'
     )
   }
-  if (typeof fn !== 'function') {
+  if (typeof fnObj.taskFunction !== 'function') {
     throw new TypeError(
-      'A taskFunctions parameter object value is not a function'
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      `taskFunction object 'taskFunction' property '${fnObj.taskFunction}' is not a function`
     )
   }
+  checkValidPriority(fnObj.priority)
+  checkValidWorkerChoiceStrategy(fnObj.strategy)
 }
 
 export const checkTaskFunctionName = (name: string): void => {
index 7054c46a3c02acc4a8fbc0efd1e601fcf6103bf8..e79795188b272fe1842f539673f3f23841dff90f 100644 (file)
@@ -232,7 +232,7 @@ describe('Abstract pool test suite', () => {
       enableTasksQueue: false,
       workerChoiceStrategy: WorkerChoiceStrategies.ROUND_ROBIN
     })
-    for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext
+    for (const [, workerChoiceStrategy] of pool.workerChoiceStrategiesContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
         runTime: { median: false },
@@ -288,7 +288,7 @@ describe('Abstract pool test suite', () => {
       errorHandler: testHandler,
       exitHandler: testHandler
     })
-    for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext
+    for (const [, workerChoiceStrategy] of pool.workerChoiceStrategiesContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
         runTime: { median: true },
@@ -447,7 +447,7 @@ describe('Abstract pool test suite', () => {
       { workerChoiceStrategy: WorkerChoiceStrategies.FAIR_SHARE }
     )
     expect(pool.opts.workerChoiceStrategyOptions).toBeUndefined()
-    for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext
+    for (const [, workerChoiceStrategy] of pool.workerChoiceStrategiesContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
         runTime: { median: false },
@@ -460,7 +460,7 @@ describe('Abstract pool test suite', () => {
       })
     }
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: true,
@@ -486,7 +486,7 @@ describe('Abstract pool test suite', () => {
       runTime: { median: true },
       elu: { median: true }
     })
-    for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext
+    for (const [, workerChoiceStrategy] of pool.workerChoiceStrategiesContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
         runTime: { median: true },
@@ -499,7 +499,7 @@ describe('Abstract pool test suite', () => {
       })
     }
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: true,
@@ -525,7 +525,7 @@ describe('Abstract pool test suite', () => {
       runTime: { median: false },
       elu: { median: false }
     })
-    for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext
+    for (const [, workerChoiceStrategy] of pool.workerChoiceStrategiesContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
         runTime: { median: false },
@@ -538,7 +538,7 @@ describe('Abstract pool test suite', () => {
       })
     }
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: true,
@@ -706,7 +706,7 @@ describe('Abstract pool test suite', () => {
       worker: WorkerTypes.thread,
       started: true,
       ready: true,
-      strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
       strategyRetries: 0,
       minSize: numberOfWorkers,
       maxSize: numberOfWorkers,
@@ -729,7 +729,7 @@ describe('Abstract pool test suite', () => {
       worker: WorkerTypes.cluster,
       started: true,
       ready: true,
-      strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
       strategyRetries: 0,
       minSize: Math.floor(numberOfWorkers / 2),
       maxSize: numberOfWorkers,
@@ -975,7 +975,7 @@ describe('Abstract pool test suite', () => {
     await pool.destroy()
   })
 
-  it('Verify that pool worker tasks usage are reset at worker choice strategy change', async () => {
+  it("Verify that pool worker tasks usage aren't reset at worker choice strategy change", async () => {
     const pool = new DynamicThreadPool(
       Math.floor(numberOfWorkers / 2),
       numberOfWorkers,
@@ -1026,7 +1026,7 @@ describe('Abstract pool test suite', () => {
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
         tasks: {
-          executed: 0,
+          executed: expect.any(Number),
           executing: 0,
           queued: 0,
           maxQueued: 0,
@@ -1049,6 +1049,10 @@ describe('Abstract pool test suite', () => {
           }
         }
       })
+      expect(workerNode.usage.tasks.executed).toBeGreaterThan(0)
+      expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
+        numberOfWorkers * maxMultiplier
+      )
       expect(workerNode.usage.runTime.history.length).toBe(0)
       expect(workerNode.usage.waitTime.history.length).toBe(0)
       expect(workerNode.usage.elu.idle.history.length).toBe(0)
@@ -1079,7 +1083,7 @@ describe('Abstract pool test suite', () => {
       worker: WorkerTypes.cluster,
       started: true,
       ready: true,
-      strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
       strategyRetries: expect.any(Number),
       minSize: expect.any(Number),
       maxSize: expect.any(Number),
@@ -1120,7 +1124,7 @@ describe('Abstract pool test suite', () => {
       worker: WorkerTypes.thread,
       started: true,
       ready: true,
-      strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
       strategyRetries: expect.any(Number),
       minSize: expect.any(Number),
       maxSize: expect.any(Number),
@@ -1160,7 +1164,7 @@ describe('Abstract pool test suite', () => {
       worker: WorkerTypes.thread,
       started: true,
       ready: true,
-      strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
       strategyRetries: expect.any(Number),
       minSize: expect.any(Number),
       maxSize: expect.any(Number),
@@ -1203,7 +1207,7 @@ describe('Abstract pool test suite', () => {
       worker: WorkerTypes.thread,
       started: true,
       ready: true,
-      strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
       strategyRetries: expect.any(Number),
       minSize: expect.any(Number),
       maxSize: expect.any(Number),
@@ -1363,29 +1367,42 @@ describe('Abstract pool test suite', () => {
       new TypeError('name argument must not be an empty string')
     )
     await expect(dynamicThreadPool.addTaskFunction('test', 0)).rejects.toThrow(
-      new TypeError('fn argument must be a function')
+      new TypeError('taskFunction property must be a function')
     )
     await expect(dynamicThreadPool.addTaskFunction('test', '')).rejects.toThrow(
-      new TypeError('fn argument must be a function')
+      new TypeError('taskFunction property must be a function')
     )
-    expect(dynamicThreadPool.listTaskFunctionNames()).toStrictEqual([
-      DEFAULT_TASK_NAME,
-      'test'
+    expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'test' }
     ])
+    expect([
+      ...dynamicThreadPool.workerChoiceStrategiesContext.workerChoiceStrategies.keys()
+    ]).toStrictEqual([WorkerChoiceStrategies.ROUND_ROBIN])
     const echoTaskFunction = data => {
       return data
     }
     await expect(
-      dynamicThreadPool.addTaskFunction('echo', echoTaskFunction)
+      dynamicThreadPool.addTaskFunction('echo', {
+        taskFunction: echoTaskFunction,
+        strategy: WorkerChoiceStrategies.LEAST_ELU
+      })
     ).resolves.toBe(true)
     expect(dynamicThreadPool.taskFunctions.size).toBe(1)
-    expect(dynamicThreadPool.taskFunctions.get('echo')).toStrictEqual(
-      echoTaskFunction
-    )
-    expect(dynamicThreadPool.listTaskFunctionNames()).toStrictEqual([
-      DEFAULT_TASK_NAME,
-      'test',
-      'echo'
+    expect(dynamicThreadPool.taskFunctions.get('echo')).toStrictEqual({
+      taskFunction: echoTaskFunction,
+      strategy: WorkerChoiceStrategies.LEAST_ELU
+    })
+    expect([
+      ...dynamicThreadPool.workerChoiceStrategiesContext.workerChoiceStrategies.keys()
+    ]).toStrictEqual([
+      WorkerChoiceStrategies.ROUND_ROBIN,
+      WorkerChoiceStrategies.LEAST_ELU
+    ])
+    expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'test' },
+      { name: 'echo', strategy: WorkerChoiceStrategies.LEAST_ELU }
     ])
     const taskFunctionData = { test: 'test' }
     const echoResult = await dynamicThreadPool.execute(taskFunctionData, 'echo')
@@ -1408,9 +1425,15 @@ describe('Abstract pool test suite', () => {
         },
         elu: {
           idle: {
+            aggregate: 0,
+            maximum: 0,
+            minimum: 0,
             history: new CircularArray()
           },
           active: {
+            aggregate: 0,
+            maximum: 0,
+            minimum: 0,
             history: new CircularArray()
           }
         }
@@ -1426,9 +1449,9 @@ describe('Abstract pool test suite', () => {
       './tests/worker-files/thread/testWorker.mjs'
     )
     await waitPoolEvents(dynamicThreadPool, PoolEvents.ready, 1)
-    expect(dynamicThreadPool.listTaskFunctionNames()).toStrictEqual([
-      DEFAULT_TASK_NAME,
-      'test'
+    expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'test' }
     ])
     await expect(dynamicThreadPool.removeTaskFunction('test')).rejects.toThrow(
       new Error('Cannot remove a task function not handled on the pool side')
@@ -1436,40 +1459,53 @@ describe('Abstract pool test suite', () => {
     const echoTaskFunction = data => {
       return data
     }
-    await dynamicThreadPool.addTaskFunction('echo', echoTaskFunction)
+    await dynamicThreadPool.addTaskFunction('echo', {
+      taskFunction: echoTaskFunction,
+      strategy: WorkerChoiceStrategies.LEAST_ELU
+    })
     expect(dynamicThreadPool.taskFunctions.size).toBe(1)
-    expect(dynamicThreadPool.taskFunctions.get('echo')).toStrictEqual(
-      echoTaskFunction
-    )
-    expect(dynamicThreadPool.listTaskFunctionNames()).toStrictEqual([
-      DEFAULT_TASK_NAME,
-      'test',
-      'echo'
+    expect(dynamicThreadPool.taskFunctions.get('echo')).toStrictEqual({
+      taskFunction: echoTaskFunction,
+      strategy: WorkerChoiceStrategies.LEAST_ELU
+    })
+    expect([
+      ...dynamicThreadPool.workerChoiceStrategiesContext.workerChoiceStrategies.keys()
+    ]).toStrictEqual([
+      WorkerChoiceStrategies.ROUND_ROBIN,
+      WorkerChoiceStrategies.LEAST_ELU
+    ])
+    expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'test' },
+      { name: 'echo', strategy: WorkerChoiceStrategies.LEAST_ELU }
     ])
     await expect(dynamicThreadPool.removeTaskFunction('echo')).resolves.toBe(
       true
     )
     expect(dynamicThreadPool.taskFunctions.size).toBe(0)
     expect(dynamicThreadPool.taskFunctions.get('echo')).toBeUndefined()
-    expect(dynamicThreadPool.listTaskFunctionNames()).toStrictEqual([
-      DEFAULT_TASK_NAME,
-      'test'
+    expect([
+      ...dynamicThreadPool.workerChoiceStrategiesContext.workerChoiceStrategies.keys()
+    ]).toStrictEqual([WorkerChoiceStrategies.ROUND_ROBIN])
+    expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'test' }
     ])
     await dynamicThreadPool.destroy()
   })
 
-  it('Verify that listTaskFunctionNames() is working', async () => {
+  it('Verify that listTaskFunctionsProperties() is working', async () => {
     const dynamicThreadPool = new DynamicThreadPool(
       Math.floor(numberOfWorkers / 2),
       numberOfWorkers,
       './tests/worker-files/thread/testMultipleTaskFunctionsWorker.mjs'
     )
     await waitPoolEvents(dynamicThreadPool, PoolEvents.ready, 1)
-    expect(dynamicThreadPool.listTaskFunctionNames()).toStrictEqual([
-      DEFAULT_TASK_NAME,
-      'jsonIntegerSerialization',
-      'factorial',
-      'fibonacci'
+    expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'jsonIntegerSerialization' },
+      { name: 'factorial' },
+      { name: 'fibonacci' }
     ])
     await dynamicThreadPool.destroy()
     const fixedClusterPool = new FixedClusterPool(
@@ -1477,11 +1513,11 @@ describe('Abstract pool test suite', () => {
       './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.cjs'
     )
     await waitPoolEvents(fixedClusterPool, PoolEvents.ready, 1)
-    expect(fixedClusterPool.listTaskFunctionNames()).toStrictEqual([
-      DEFAULT_TASK_NAME,
-      'jsonIntegerSerialization',
-      'factorial',
-      'fibonacci'
+    expect(fixedClusterPool.listTaskFunctionsProperties()).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'jsonIntegerSerialization' },
+      { name: 'factorial' },
+      { name: 'fibonacci' }
     ])
     await fixedClusterPool.destroy()
   })
@@ -1513,29 +1549,29 @@ describe('Abstract pool test suite', () => {
         `Task function operation 'default' failed on worker ${workerId} with error: 'Error: Cannot set the default task function to a non-existing task function'`
       )
     )
-    expect(dynamicThreadPool.listTaskFunctionNames()).toStrictEqual([
-      DEFAULT_TASK_NAME,
-      'jsonIntegerSerialization',
-      'factorial',
-      'fibonacci'
+    expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'jsonIntegerSerialization' },
+      { name: 'factorial' },
+      { name: 'fibonacci' }
     ])
     await expect(
       dynamicThreadPool.setDefaultTaskFunction('factorial')
     ).resolves.toBe(true)
-    expect(dynamicThreadPool.listTaskFunctionNames()).toStrictEqual([
-      DEFAULT_TASK_NAME,
-      'factorial',
-      'jsonIntegerSerialization',
-      'fibonacci'
+    expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'factorial' },
+      { name: 'jsonIntegerSerialization' },
+      { name: 'fibonacci' }
     ])
     await expect(
       dynamicThreadPool.setDefaultTaskFunction('fibonacci')
     ).resolves.toBe(true)
-    expect(dynamicThreadPool.listTaskFunctionNames()).toStrictEqual([
-      DEFAULT_TASK_NAME,
-      'fibonacci',
-      'jsonIntegerSerialization',
-      'factorial'
+    expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'fibonacci' },
+      { name: 'jsonIntegerSerialization' },
+      { name: 'factorial' }
     ])
     await dynamicThreadPool.destroy()
   })
@@ -1558,15 +1594,17 @@ describe('Abstract pool test suite', () => {
     expect(pool.info.executingTasks).toBe(0)
     expect(pool.info.executedTasks).toBe(4)
     for (const workerNode of pool.workerNodes) {
-      expect(workerNode.info.taskFunctionNames).toStrictEqual([
-        DEFAULT_TASK_NAME,
-        'jsonIntegerSerialization',
-        'factorial',
-        'fibonacci'
+      expect(workerNode.info.taskFunctionsProperties).toStrictEqual([
+        { name: DEFAULT_TASK_NAME },
+        { name: 'jsonIntegerSerialization' },
+        { name: 'factorial' },
+        { name: 'fibonacci' }
       ])
       expect(workerNode.taskFunctionsUsage.size).toBe(3)
-      for (const name of pool.listTaskFunctionNames()) {
-        expect(workerNode.getTaskFunctionWorkerUsage(name)).toStrictEqual({
+      for (const taskFunctionProperties of pool.listTaskFunctionsProperties()) {
+        expect(
+          workerNode.getTaskFunctionWorkerUsage(taskFunctionProperties.name)
+        ).toStrictEqual({
           tasks: {
             executed: expect.any(Number),
             executing: 0,
@@ -1591,14 +1629,15 @@ describe('Abstract pool test suite', () => {
           }
         })
         expect(
-          workerNode.getTaskFunctionWorkerUsage(name).tasks.executed
+          workerNode.getTaskFunctionWorkerUsage(taskFunctionProperties.name)
+            .tasks.executed
         ).toBeGreaterThan(0)
       }
       expect(
         workerNode.getTaskFunctionWorkerUsage(DEFAULT_TASK_NAME)
       ).toStrictEqual(
         workerNode.getTaskFunctionWorkerUsage(
-          workerNode.info.taskFunctionNames[1]
+          workerNode.info.taskFunctionsProperties[1].name
         )
       )
     }
@@ -1628,13 +1667,17 @@ describe('Abstract pool test suite', () => {
     await expect(
       pool.sendTaskFunctionOperationToWorker(workerNodeKey, {
         taskFunctionOperation: 'add',
-        taskFunctionName: 'empty',
+        taskFunctionProperties: { name: 'empty' },
         taskFunction: (() => {}).toString()
       })
     ).resolves.toBe(true)
     expect(
-      pool.workerNodes[workerNodeKey].info.taskFunctionNames
-    ).toStrictEqual([DEFAULT_TASK_NAME, 'test', 'empty'])
+      pool.workerNodes[workerNodeKey].info.taskFunctionsProperties
+    ).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'test' },
+      { name: 'empty' }
+    ])
     await pool.destroy()
   })
 
@@ -1647,15 +1690,15 @@ describe('Abstract pool test suite', () => {
     await expect(
       pool.sendTaskFunctionOperationToWorkers({
         taskFunctionOperation: 'add',
-        taskFunctionName: 'empty',
+        taskFunctionProperties: { name: 'empty' },
         taskFunction: (() => {}).toString()
       })
     ).resolves.toBe(true)
     for (const workerNode of pool.workerNodes) {
-      expect(workerNode.info.taskFunctionNames).toStrictEqual([
-        DEFAULT_TASK_NAME,
-        'test',
-        'empty'
+      expect(workerNode.info.taskFunctionsProperties).toStrictEqual([
+        { name: DEFAULT_TASK_NAME },
+        { name: 'test' },
+        { name: 'empty' }
       ])
     }
     await pool.destroy()
index a60d95fa82accd3085c2dc352d6e89f60c6db304..166498f9a0b0c4c7a9f4a997f3d296518e3ca457 100644 (file)
@@ -119,8 +119,9 @@ describe('Dynamic cluster pool test suite', () => {
     await waitWorkerEvents(longRunningPool, 'exit', max - min)
     expect(longRunningPool.workerNodes.length).toBe(min)
     expect(
-      longRunningPool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        longRunningPool.workerChoiceStrategyContext.workerChoiceStrategy
+      longRunningPool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        longRunningPool.workerChoiceStrategiesContext
+          .defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toBeLessThan(longRunningPool.workerNodes.length)
     // We need to clean up the resources after our test
diff --git a/tests/pools/selection-strategies/selection-strategies-utils.test.mjs b/tests/pools/selection-strategies/selection-strategies-utils.test.mjs
new file mode 100644 (file)
index 0000000..dc6fc71
--- /dev/null
@@ -0,0 +1,64 @@
+import { expect } from 'expect'
+
+import { FixedClusterPool, FixedThreadPool } from '../../../lib/index.cjs'
+import {
+  buildWorkerChoiceStrategyOptions,
+  getWorkerChoiceStrategiesRetries
+} from '../../../lib/pools/selection-strategies/selection-strategies-utils.cjs'
+
+describe('Selection strategies utils test suite', () => {
+  it('Verify buildWorkerChoiceStrategyOptions() behavior', async () => {
+    const numberOfWorkers = 4
+    const pool = new FixedClusterPool(
+      numberOfWorkers,
+      './tests/worker-files/cluster/testWorker.cjs'
+    )
+    expect(buildWorkerChoiceStrategyOptions(pool)).toStrictEqual({
+      runTime: { median: false },
+      waitTime: { median: false },
+      elu: { median: false },
+      weights: expect.objectContaining({
+        0: expect.any(Number),
+        [pool.info.maxSize - 1]: expect.any(Number)
+      })
+    })
+    const workerChoiceStrategyOptions = {
+      runTime: { median: true },
+      waitTime: { median: true },
+      elu: { median: true },
+      weights: {
+        0: 100,
+        1: 100
+      }
+    }
+    expect(
+      buildWorkerChoiceStrategyOptions(pool, workerChoiceStrategyOptions)
+    ).toStrictEqual(workerChoiceStrategyOptions)
+    await pool.destroy()
+  })
+
+  it('Verify getWorkerChoiceStrategyRetries() behavior', async () => {
+    const numberOfThreads = 4
+    const pool = new FixedThreadPool(
+      numberOfThreads,
+      './tests/worker-files/thread/testWorker.mjs'
+    )
+    expect(getWorkerChoiceStrategiesRetries(pool)).toBe(pool.info.maxSize * 2)
+    const workerChoiceStrategyOptions = {
+      runTime: { median: true },
+      waitTime: { median: true },
+      elu: { median: true },
+      weights: {
+        0: 100,
+        1: 100
+      }
+    }
+    expect(
+      getWorkerChoiceStrategiesRetries(pool, workerChoiceStrategyOptions)
+    ).toBe(
+      pool.info.maxSize +
+        Object.keys(workerChoiceStrategyOptions.weights).length
+    )
+    await pool.destroy()
+  })
+})
index 5241168cd880e101d2ed793831583398a209488d..0e7f19fbdf0abe23685f31059328caa975ced2a3 100644 (file)
@@ -1,3 +1,5 @@
+import { randomInt } from 'node:crypto'
+
 import { expect } from 'expect'
 
 import { CircularArray } from '../../../lib/circular-array.cjs'
@@ -36,6 +38,9 @@ describe('Selection strategies test suite', () => {
     expect(pool.opts.workerChoiceStrategy).toBe(
       WorkerChoiceStrategies.ROUND_ROBIN
     )
+    expect(pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy).toBe(
+      WorkerChoiceStrategies.ROUND_ROBIN
+    )
     // We need to clean up the resources after our test
     await pool.destroy()
   })
@@ -48,9 +53,9 @@ describe('Selection strategies test suite', () => {
         { workerChoiceStrategy }
       )
       expect(pool.opts.workerChoiceStrategy).toBe(workerChoiceStrategy)
-      expect(pool.workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-        workerChoiceStrategy
-      )
+      expect(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      ).toBe(workerChoiceStrategy)
       await pool.destroy()
     }
   })
@@ -64,9 +69,9 @@ describe('Selection strategies test suite', () => {
       )
       pool.setWorkerChoiceStrategy(workerChoiceStrategy)
       expect(pool.opts.workerChoiceStrategy).toBe(workerChoiceStrategy)
-      expect(pool.workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-        workerChoiceStrategy
-      )
+      expect(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      ).toBe(workerChoiceStrategy)
       await pool.destroy()
     }
     for (const workerChoiceStrategy of Object.values(WorkerChoiceStrategies)) {
@@ -77,26 +82,27 @@ describe('Selection strategies test suite', () => {
       )
       pool.setWorkerChoiceStrategy(workerChoiceStrategy)
       expect(pool.opts.workerChoiceStrategy).toBe(workerChoiceStrategy)
-      expect(pool.workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-        workerChoiceStrategy
-      )
+      expect(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      ).toBe(workerChoiceStrategy)
       await pool.destroy()
     }
   })
 
   it('Verify available strategies default internals at pool creation', async () => {
-    const pool = new FixedThreadPool(
-      max,
-      './tests/worker-files/thread/testWorker.mjs'
-    )
     for (const workerChoiceStrategy of Object.values(WorkerChoiceStrategies)) {
+      const pool = new FixedThreadPool(
+        max,
+        './tests/worker-files/thread/testWorker.mjs',
+        { workerChoiceStrategy }
+      )
       expect(
-        pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
           workerChoiceStrategy
         ).nextWorkerNodeKey
       ).toBe(0)
       expect(
-        pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
           workerChoiceStrategy
         ).previousWorkerNodeKey
       ).toBe(0)
@@ -104,7 +110,7 @@ describe('Selection strategies test suite', () => {
         workerChoiceStrategy === WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
       ) {
         expect(
-          pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
+          pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
             workerChoiceStrategy
           ).workerNodeVirtualTaskRunTime
         ).toBe(0)
@@ -113,35 +119,35 @@ describe('Selection strategies test suite', () => {
         WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN
       ) {
         expect(
-          pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
+          pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
             workerChoiceStrategy
           ).workerNodeVirtualTaskRunTime
         ).toBe(0)
         expect(
-          pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
+          pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
             workerChoiceStrategy
           ).roundId
         ).toBe(0)
         expect(
-          pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
+          pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
             workerChoiceStrategy
           ).workerNodeId
         ).toBe(0)
         expect(
-          pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
+          pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
             workerChoiceStrategy
           ).roundWeights.length
         ).toBe(1)
         expect(
           Number.isSafeInteger(
-            pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
+            pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
               workerChoiceStrategy
             ).roundWeights[0]
           )
         ).toBe(true)
       }
+      await pool.destroy()
     }
-    await pool.destroy()
   })
 
   it('Verify ROUND_ROBIN strategy default policy', async () => {
@@ -151,7 +157,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -162,7 +168,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -178,7 +184,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: false,
@@ -204,7 +210,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: false,
@@ -268,13 +274,13 @@ describe('Selection strategies test suite', () => {
       })
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toBe(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toBe(pool.workerNodes.length - 1)
     // We need to clean up the resources after our test
@@ -328,13 +334,13 @@ describe('Selection strategies test suite', () => {
       )
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toBe(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toBe(pool.workerNodes.length - 1)
     // We need to clean up the resources after our test
@@ -367,62 +373,54 @@ describe('Selection strategies test suite', () => {
     await pool.destroy()
   })
 
-  it('Verify ROUND_ROBIN strategy internals are resets after setting it', async () => {
+  it("Verify ROUND_ROBIN strategy internals aren't reset after setting it", async () => {
     const workerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
     let pool = new FixedThreadPool(
       max,
       './tests/worker-files/thread/testWorker.mjs',
-      { workerChoiceStrategy: WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN }
+      { workerChoiceStrategy }
     )
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
-      ).nextWorkerNodeKey
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
-      ).previousWorkerNodeKey
-    ).toBeDefined()
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+    ).nextWorkerNodeKey = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+    ).previousWorkerNodeKey = randomInt(1, max - 1)
     pool.setWorkerChoiceStrategy(workerChoiceStrategy)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     await pool.destroy()
     pool = new DynamicThreadPool(
       min,
       max,
       './tests/worker-files/thread/testWorker.mjs',
-      { workerChoiceStrategy: WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN }
+      { workerChoiceStrategy }
     )
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
-      ).nextWorkerNodeKey
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
-      ).previousWorkerNodeKey
-    ).toBeDefined()
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+    ).nextWorkerNodeKey = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+    ).previousWorkerNodeKey = randomInt(1, max - 1)
     pool.setWorkerChoiceStrategy(workerChoiceStrategy)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     // We need to clean up the resources after our test
     await pool.destroy()
   })
@@ -434,7 +432,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -445,7 +443,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -461,7 +459,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: false,
@@ -487,7 +485,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: false,
@@ -554,13 +552,13 @@ describe('Selection strategies test suite', () => {
       )
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toEqual(expect.any(Number))
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(expect.any(Number))
     // We need to clean up the resources after our test
@@ -613,13 +611,13 @@ describe('Selection strategies test suite', () => {
       )
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toEqual(expect.any(Number))
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(expect.any(Number))
     // We need to clean up the resources after our test
@@ -633,7 +631,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -644,7 +642,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -660,7 +658,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: true,
@@ -686,7 +684,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: true,
@@ -763,13 +761,13 @@ describe('Selection strategies test suite', () => {
       }
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toEqual(expect.any(Number))
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(expect.any(Number))
     // We need to clean up the resources after our test
@@ -832,13 +830,13 @@ describe('Selection strategies test suite', () => {
       }
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toEqual(expect.any(Number))
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(expect.any(Number))
     // We need to clean up the resources after our test
@@ -852,7 +850,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -863,7 +861,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -879,7 +877,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: false,
@@ -905,7 +903,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: false,
@@ -988,13 +986,13 @@ describe('Selection strategies test suite', () => {
       }
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toEqual(expect.any(Number))
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(expect.any(Number))
     // We need to clean up the resources after our test
@@ -1063,13 +1061,13 @@ describe('Selection strategies test suite', () => {
       }
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toEqual(expect.any(Number))
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(expect.any(Number))
     // We need to clean up the resources after our test
@@ -1083,7 +1081,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -1094,7 +1092,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -1110,7 +1108,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: true,
@@ -1136,7 +1134,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: true,
@@ -1230,13 +1228,13 @@ describe('Selection strategies test suite', () => {
       expect(workerNode.strategyData.virtualTaskEndTimestamp).toBeGreaterThan(0)
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toEqual(expect.any(Number))
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(expect.any(Number))
     // We need to clean up the resources after our test
@@ -1316,13 +1314,13 @@ describe('Selection strategies test suite', () => {
       expect(workerNode.strategyData.virtualTaskEndTimestamp).toBeGreaterThan(0)
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toEqual(expect.any(Number))
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(expect.any(Number))
     // We need to clean up the resources after our test
@@ -1407,20 +1405,20 @@ describe('Selection strategies test suite', () => {
       expect(workerNode.strategyData.virtualTaskEndTimestamp).toBeGreaterThan(0)
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toEqual(expect.any(Number))
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(expect.any(Number))
     // We need to clean up the resources after our test
     await pool.destroy()
   })
 
-  it('Verify FAIR_SHARE strategy internals are resets after setting it', async () => {
+  it("Verify FAIR_SHARE strategy internals aren't reset after setting it", async () => {
     const workerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE
     let pool = new FixedThreadPool(
       max,
@@ -1433,7 +1431,7 @@ describe('Selection strategies test suite', () => {
     }
     pool.setWorkerChoiceStrategy(workerChoiceStrategy)
     for (const workerNode of pool.workerNodes) {
-      expect(workerNode.strategyData.virtualTaskEndTimestamp).toBeUndefined()
+      expect(workerNode.strategyData.virtualTaskEndTimestamp).toBeGreaterThan(0)
     }
     await pool.destroy()
     pool = new DynamicThreadPool(
@@ -1448,7 +1446,7 @@ describe('Selection strategies test suite', () => {
     }
     pool.setWorkerChoiceStrategy(workerChoiceStrategy)
     for (const workerNode of pool.workerNodes) {
-      expect(workerNode.strategyData.virtualTaskEndTimestamp).toBeUndefined()
+      expect(workerNode.strategyData.virtualTaskEndTimestamp).toBeGreaterThan(0)
     }
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -1461,7 +1459,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -1472,7 +1470,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -1488,7 +1486,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: true,
@@ -1514,7 +1512,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: true,
@@ -1591,18 +1589,18 @@ describe('Selection strategies test suite', () => {
       }
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toBe(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).workerNodeVirtualTaskRunTime
     ).toBeGreaterThanOrEqual(0)
     // We need to clean up the resources after our test
@@ -1665,18 +1663,18 @@ describe('Selection strategies test suite', () => {
       }
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toEqual(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).workerNodeVirtualTaskRunTime
     ).toBeGreaterThanOrEqual(0)
     // We need to clean up the resources after our test
@@ -1744,98 +1742,88 @@ describe('Selection strategies test suite', () => {
       }
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toEqual(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).workerNodeVirtualTaskRunTime
     ).toBeGreaterThanOrEqual(0)
     // We need to clean up the resources after our test
     await pool.destroy()
   })
 
-  it('Verify WEIGHTED_ROUND_ROBIN strategy internals are resets after setting it', async () => {
+  it("Verify WEIGHTED_ROUND_ROBIN strategy internals aren't reset after setting it", async () => {
     const workerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
     let pool = new FixedThreadPool(
       max,
-      './tests/worker-files/thread/testWorker.mjs'
+      './tests/worker-files/thread/testWorker.mjs',
+      { workerChoiceStrategy }
     )
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).nextWorkerNodeKey
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).previousWorkerNodeKey
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).workerNodeVirtualTaskRunTime
-    ).toBeDefined()
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).nextWorkerNodeKey = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).previousWorkerNodeKey = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).workerNodeVirtualTaskRunTime = randomInt(100, 1000)
     pool.setWorkerChoiceStrategy(workerChoiceStrategy)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).workerNodeVirtualTaskRunTime
-    ).toBe(0)
+    ).toBeGreaterThan(99)
     await pool.destroy()
     pool = new DynamicThreadPool(
       min,
       max,
-      './tests/worker-files/thread/testWorker.mjs'
+      './tests/worker-files/thread/testWorker.mjs',
+      { workerChoiceStrategy }
     )
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).nextWorkerNodeKey
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).previousWorkerNodeKey
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).workerNodeVirtualTaskRunTime
-    ).toBeDefined()
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).nextWorkerNodeKey = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).previousWorkerNodeKey = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).workerNodeVirtualTaskRunTime = randomInt(100, 1000)
     pool.setWorkerChoiceStrategy(workerChoiceStrategy)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).workerNodeVirtualTaskRunTime
-    ).toBe(0)
+    ).toBeGreaterThan(99)
     // We need to clean up the resources after our test
     await pool.destroy()
   })
@@ -1848,7 +1836,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -1859,7 +1847,7 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy }
     )
-    expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({
+    expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
       dynamicWorkerUsage: false,
       dynamicWorkerReady: true
     })
@@ -1876,7 +1864,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: true,
@@ -1902,7 +1890,7 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(
-      pool.workerChoiceStrategyContext.getTaskStatisticsRequirements()
+      pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
       runTime: {
         aggregate: true,
@@ -1972,34 +1960,34 @@ describe('Selection strategies test suite', () => {
       )
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).roundId
     ).toBe(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).workerNodeId
     ).toBe(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toBe(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).roundWeights.length
     ).toBe(1)
     expect(
       Number.isSafeInteger(
-        pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-          pool.workerChoiceStrategyContext.workerChoiceStrategy
+        pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+          pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
         ).roundWeights[0]
       )
     ).toBe(true)
@@ -2056,34 +2044,34 @@ describe('Selection strategies test suite', () => {
       )
     }
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).roundId
     ).toBe(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).workerNodeId
     ).toBe(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toBe(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
     ).toEqual(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).roundWeights.length
     ).toBe(1)
     expect(
       Number.isSafeInteger(
-        pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-          pool.workerChoiceStrategyContext.workerChoiceStrategy
+        pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+          pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
         ).roundWeights[0]
       )
     ).toBe(true)
@@ -2091,68 +2079,59 @@ describe('Selection strategies test suite', () => {
     await pool.destroy()
   })
 
-  it('Verify INTERLEAVED_WEIGHTED_ROUND_ROBIN strategy internals are resets after setting it', async () => {
+  it("Verify INTERLEAVED_WEIGHTED_ROUND_ROBIN strategy internals aren't resets after setting it", async () => {
     const workerChoiceStrategy =
       WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN
     let pool = new FixedThreadPool(
       max,
-      './tests/worker-files/thread/testWorker.mjs'
+      './tests/worker-files/thread/testWorker.mjs',
+      { workerChoiceStrategy }
     )
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).roundId
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).workerNodeId
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).nextWorkerNodeKey
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).previousWorkerNodeKey
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).roundWeights
-    ).toBeDefined()
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).roundId = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).workerNodeId = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).nextWorkerNodeKey = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).previousWorkerNodeKey = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).roundWeights = [randomInt(1, max - 1), randomInt(1, max - 1)]
     pool.setWorkerChoiceStrategy(workerChoiceStrategy)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).roundId
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).workerNodeId
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).roundWeights.length
-    ).toBe(1)
+    ).toBeGreaterThan(1)
     expect(
       Number.isSafeInteger(
-        pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-          pool.workerChoiceStrategyContext.workerChoiceStrategy
+        pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+          pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
         ).roundWeights[0]
       )
     ).toBe(true)
@@ -2160,63 +2139,54 @@ describe('Selection strategies test suite', () => {
     pool = new DynamicThreadPool(
       min,
       max,
-      './tests/worker-files/thread/testWorker.mjs'
+      './tests/worker-files/thread/testWorker.mjs',
+      { workerChoiceStrategy }
     )
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).roundId
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).workerNodeId
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).nextWorkerNodeKey
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).previousWorkerNodeKey
-    ).toBeDefined()
-    expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      ).roundWeights
-    ).toBeDefined()
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).roundId = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).workerNodeId = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).nextWorkerNodeKey = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).previousWorkerNodeKey = randomInt(1, max - 1)
+    pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+      workerChoiceStrategy
+    ).roundWeights = [randomInt(1, max - 1), randomInt(1, max - 1)]
     pool.setWorkerChoiceStrategy(workerChoiceStrategy)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).roundId
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).workerNodeId
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).previousWorkerNodeKey
-    ).toBe(0)
+    ).toBeGreaterThan(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
+      pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
       ).roundWeights.length
-    ).toBe(1)
+    ).toBeGreaterThan(1)
     expect(
       Number.isSafeInteger(
-        pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-          pool.workerChoiceStrategyContext.workerChoiceStrategy
+        pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+          pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
         ).roundWeights[0]
       )
     ).toBe(true)
diff --git a/tests/pools/selection-strategies/worker-choice-strategies-context.test.mjs b/tests/pools/selection-strategies/worker-choice-strategies-context.test.mjs
new file mode 100644 (file)
index 0000000..8fcd90b
--- /dev/null
@@ -0,0 +1,478 @@
+import { expect } from 'expect'
+import { createStubInstance, restore, stub } from 'sinon'
+
+import {
+  DynamicThreadPool,
+  FixedThreadPool,
+  WorkerChoiceStrategies
+} from '../../../lib/index.cjs'
+import { FairShareWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/fair-share-worker-choice-strategy.cjs'
+import { InterleavedWeightedRoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/interleaved-weighted-round-robin-worker-choice-strategy.cjs'
+import { LeastBusyWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/least-busy-worker-choice-strategy.cjs'
+import { LeastEluWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/least-elu-worker-choice-strategy.cjs'
+import { LeastUsedWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/least-used-worker-choice-strategy.cjs'
+import { RoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/round-robin-worker-choice-strategy.cjs'
+import { WeightedRoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.cjs'
+import { WorkerChoiceStrategiesContext } from '../../../lib/pools/selection-strategies/worker-choice-strategies-context.cjs'
+
+describe('Worker choice strategies context test suite', () => {
+  const min = 1
+  const max = 3
+  let fixedPool, dynamicPool
+
+  before(() => {
+    fixedPool = new FixedThreadPool(
+      max,
+      './tests/worker-files/thread/testWorker.mjs'
+    )
+    dynamicPool = new DynamicThreadPool(
+      min,
+      max,
+      './tests/worker-files/thread/testWorker.mjs'
+    )
+  })
+
+  afterEach(() => {
+    restore()
+  })
+
+  after(async () => {
+    await fixedPool.destroy()
+    await dynamicPool.destroy()
+  })
+
+  it('Verify that constructor() initializes the context with the default choice strategy', () => {
+    let workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool
+    )
+    expect(workerChoiceStrategiesContext.workerChoiceStrategies.size).toBe(1)
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
+    workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      dynamicPool
+    )
+    expect(workerChoiceStrategiesContext.workerChoiceStrategies.size).toBe(1)
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
+  })
+
+  it('Verify that constructor() initializes the context with retries attribute properly set', () => {
+    let workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool
+    )
+    expect(workerChoiceStrategiesContext.retries).toBe(
+      fixedPool.info.maxSize * 2
+    )
+    workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      dynamicPool
+    )
+    expect(workerChoiceStrategiesContext.retries).toBe(
+      dynamicPool.info.maxSize * 2
+    )
+  })
+
+  it('Verify that execute() throws error if null or undefined is returned after retries', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool
+    )
+    expect(workerChoiceStrategiesContext.defaultWorkerChoiceStrategy).toBe(
+      WorkerChoiceStrategies.ROUND_ROBIN
+    )
+    const workerChoiceStrategyUndefinedStub = createStubInstance(
+      RoundRobinWorkerChoiceStrategy,
+      {
+        choose: stub().returns(undefined)
+      }
+    )
+    workerChoiceStrategiesContext.workerChoiceStrategies.set(
+      workerChoiceStrategiesContext.defaultWorkerChoiceStrategy,
+      workerChoiceStrategyUndefinedStub
+    )
+    expect(() => workerChoiceStrategiesContext.execute()).toThrow(
+      new Error(
+        `Worker node key chosen is null or undefined after ${workerChoiceStrategiesContext.retries} retries`
+      )
+    )
+    const workerChoiceStrategyNullStub = createStubInstance(
+      RoundRobinWorkerChoiceStrategy,
+      {
+        choose: stub().returns(null)
+      }
+    )
+    workerChoiceStrategiesContext.workerChoiceStrategies.set(
+      workerChoiceStrategiesContext.defaultWorkerChoiceStrategy,
+      workerChoiceStrategyNullStub
+    )
+    expect(() => workerChoiceStrategiesContext.execute()).toThrow(
+      new Error(
+        `Worker node key chosen is null or undefined after ${workerChoiceStrategiesContext.retries} retries`
+      )
+    )
+  })
+
+  it('Verify that execute() retry until a worker node is chosen', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool
+    )
+    const workerChoiceStrategyStub = createStubInstance(
+      RoundRobinWorkerChoiceStrategy,
+      {
+        choose: stub()
+          .onCall(0)
+          .returns(undefined)
+          .onCall(1)
+          .returns(undefined)
+          .onCall(2)
+          .returns(undefined)
+          .onCall(3)
+          .returns(undefined)
+          .onCall(4)
+          .returns(undefined)
+          .returns(1)
+      }
+    )
+    expect(workerChoiceStrategiesContext.defaultWorkerChoiceStrategy).toBe(
+      WorkerChoiceStrategies.ROUND_ROBIN
+    )
+    workerChoiceStrategiesContext.workerChoiceStrategies.set(
+      workerChoiceStrategiesContext.defaultWorkerChoiceStrategy,
+      workerChoiceStrategyStub
+    )
+    const chosenWorkerKey = workerChoiceStrategiesContext.execute()
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      ).choose.callCount
+    ).toBe(6)
+    expect(chosenWorkerKey).toBe(1)
+  })
+
+  it('Verify that execute() return the worker node key chosen by the strategy with fixed pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool
+    )
+    const workerChoiceStrategyStub = createStubInstance(
+      RoundRobinWorkerChoiceStrategy,
+      {
+        choose: stub().returns(0)
+      }
+    )
+    expect(workerChoiceStrategiesContext.defaultWorkerChoiceStrategy).toBe(
+      WorkerChoiceStrategies.ROUND_ROBIN
+    )
+    workerChoiceStrategiesContext.workerChoiceStrategies.set(
+      workerChoiceStrategiesContext.defaultWorkerChoiceStrategy,
+      workerChoiceStrategyStub
+    )
+    const chosenWorkerKey = workerChoiceStrategiesContext.execute()
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      ).choose.calledOnce
+    ).toBe(true)
+    expect(chosenWorkerKey).toBe(0)
+  })
+
+  it('Verify that execute() return the worker node key chosen by the strategy with dynamic pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      dynamicPool
+    )
+    const workerChoiceStrategyStub = createStubInstance(
+      RoundRobinWorkerChoiceStrategy,
+      {
+        choose: stub().returns(0)
+      }
+    )
+    expect(workerChoiceStrategiesContext.defaultWorkerChoiceStrategy).toBe(
+      WorkerChoiceStrategies.ROUND_ROBIN
+    )
+    workerChoiceStrategiesContext.workerChoiceStrategies.set(
+      workerChoiceStrategiesContext.defaultWorkerChoiceStrategy,
+      workerChoiceStrategyStub
+    )
+    const chosenWorkerKey = workerChoiceStrategiesContext.execute()
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      ).choose.calledOnce
+    ).toBe(true)
+    expect(chosenWorkerKey).toBe(0)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with ROUND_ROBIN and fixed pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.ROUND_ROBIN
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with ROUND_ROBIN and dynamic pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      dynamicPool
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.ROUND_ROBIN
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with LEAST_USED and fixed pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool
+    )
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.LEAST_USED
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(LeastUsedWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with LEAST_USED and dynamic pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      dynamicPool
+    )
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.LEAST_USED
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(LeastUsedWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with LEAST_BUSY and fixed pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool
+    )
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.LEAST_BUSY
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(LeastBusyWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with LEAST_BUSY and dynamic pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      dynamicPool
+    )
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.LEAST_BUSY
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(LeastBusyWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with LEAST_ELU and fixed pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool
+    )
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.LEAST_ELU
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(LeastEluWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with LEAST_ELU and dynamic pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      dynamicPool
+    )
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.LEAST_ELU
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(LeastEluWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with FAIR_SHARE and fixed pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool
+    )
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.FAIR_SHARE
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(FairShareWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with FAIR_SHARE and dynamic pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      dynamicPool
+    )
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.FAIR_SHARE
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(FairShareWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with WEIGHTED_ROUND_ROBIN and fixed pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool
+    )
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(WeightedRoundRobinWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with WEIGHTED_ROUND_ROBIN and dynamic pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      dynamicPool
+    )
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(WeightedRoundRobinWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with INTERLEAVED_WEIGHTED_ROUND_ROBIN and fixed pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool
+    )
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(InterleavedWeightedRoundRobinWorkerChoiceStrategy)
+  })
+
+  it('Verify that setDefaultWorkerChoiceStrategy() works with INTERLEAVED_WEIGHTED_ROUND_ROBIN and dynamic pool', () => {
+    const workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      dynamicPool
+    )
+    workerChoiceStrategiesContext.setDefaultWorkerChoiceStrategy(
+      WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN
+    )
+    expect(
+      workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
+      )
+    ).toBeInstanceOf(InterleavedWeightedRoundRobinWorkerChoiceStrategy)
+  })
+
+  it('Verify that worker choice strategy options enable median runtime pool statistics', () => {
+    const wwrWorkerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
+    let workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool,
+      [wwrWorkerChoiceStrategy],
+      {
+        runTime: { median: true }
+      }
+    )
+    expect(
+      workerChoiceStrategiesContext.getTaskStatisticsRequirements().runTime
+        .average
+    ).toBe(false)
+    expect(
+      workerChoiceStrategiesContext.getTaskStatisticsRequirements().runTime
+        .median
+    ).toBe(true)
+    workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      dynamicPool,
+      [wwrWorkerChoiceStrategy],
+      {
+        runTime: { median: true }
+      }
+    )
+    expect(
+      workerChoiceStrategiesContext.getTaskStatisticsRequirements().runTime
+        .average
+    ).toBe(false)
+    expect(
+      workerChoiceStrategiesContext.getTaskStatisticsRequirements().runTime
+        .median
+    ).toBe(true)
+    const fsWorkerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE
+    workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      fixedPool,
+      [fsWorkerChoiceStrategy],
+      {
+        runTime: { median: true }
+      }
+    )
+    expect(
+      workerChoiceStrategiesContext.getTaskStatisticsRequirements().runTime
+        .average
+    ).toBe(false)
+    expect(
+      workerChoiceStrategiesContext.getTaskStatisticsRequirements().runTime
+        .median
+    ).toBe(true)
+    workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext(
+      dynamicPool,
+      [fsWorkerChoiceStrategy],
+      {
+        runTime: { median: true }
+      }
+    )
+    expect(
+      workerChoiceStrategiesContext.getTaskStatisticsRequirements().runTime
+        .average
+    ).toBe(false)
+    expect(
+      workerChoiceStrategiesContext.getTaskStatisticsRequirements().runTime
+        .median
+    ).toBe(true)
+  })
+})
diff --git a/tests/pools/selection-strategies/worker-choice-strategy-context.test.mjs b/tests/pools/selection-strategies/worker-choice-strategy-context.test.mjs
deleted file mode 100644 (file)
index c591235..0000000
+++ /dev/null
@@ -1,494 +0,0 @@
-import { expect } from 'expect'
-import { createStubInstance, restore, stub } from 'sinon'
-
-import {
-  DynamicThreadPool,
-  FixedThreadPool,
-  WorkerChoiceStrategies
-} from '../../../lib/index.cjs'
-import { FairShareWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/fair-share-worker-choice-strategy.cjs'
-import { InterleavedWeightedRoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/interleaved-weighted-round-robin-worker-choice-strategy.cjs'
-import { LeastBusyWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/least-busy-worker-choice-strategy.cjs'
-import { LeastEluWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/least-elu-worker-choice-strategy.cjs'
-import { LeastUsedWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/least-used-worker-choice-strategy.cjs'
-import { RoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/round-robin-worker-choice-strategy.cjs'
-import { WeightedRoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.cjs'
-import { WorkerChoiceStrategyContext } from '../../../lib/pools/selection-strategies/worker-choice-strategy-context.cjs'
-
-describe('Worker choice strategy context test suite', () => {
-  const min = 1
-  const max = 3
-  let fixedPool, dynamicPool
-
-  before(() => {
-    fixedPool = new FixedThreadPool(
-      max,
-      './tests/worker-files/thread/testWorker.mjs'
-    )
-    dynamicPool = new DynamicThreadPool(
-      min,
-      max,
-      './tests/worker-files/thread/testWorker.mjs'
-    )
-  })
-
-  afterEach(() => {
-    restore()
-  })
-
-  after(async () => {
-    await fixedPool.destroy()
-    await dynamicPool.destroy()
-  })
-
-  it('Verify that constructor() initializes the context with all the available worker choice strategies', () => {
-    let workerChoiceStrategyContext = new WorkerChoiceStrategyContext(fixedPool)
-    expect(workerChoiceStrategyContext.workerChoiceStrategies.size).toBe(
-      Object.keys(WorkerChoiceStrategies).length
-    )
-    workerChoiceStrategyContext = new WorkerChoiceStrategyContext(dynamicPool)
-    expect(workerChoiceStrategyContext.workerChoiceStrategies.size).toBe(
-      Object.keys(WorkerChoiceStrategies).length
-    )
-  })
-
-  it('Verify that constructor() initializes the context with retries attribute properly set', () => {
-    let workerChoiceStrategyContext = new WorkerChoiceStrategyContext(fixedPool)
-    expect(workerChoiceStrategyContext.retries).toBe(fixedPool.info.maxSize * 2)
-    workerChoiceStrategyContext = new WorkerChoiceStrategyContext(dynamicPool)
-    expect(workerChoiceStrategyContext.retries).toBe(
-      dynamicPool.info.maxSize * 2
-    )
-  })
-
-  it('Verify that execute() throws error if null or undefined is returned after retries', () => {
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      fixedPool
-    )
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      WorkerChoiceStrategies.ROUND_ROBIN
-    )
-    const workerChoiceStrategyUndefinedStub = createStubInstance(
-      RoundRobinWorkerChoiceStrategy,
-      {
-        choose: stub().returns(undefined)
-      }
-    )
-    workerChoiceStrategyContext.workerChoiceStrategies.set(
-      workerChoiceStrategyContext.workerChoiceStrategy,
-      workerChoiceStrategyUndefinedStub
-    )
-    expect(() => workerChoiceStrategyContext.execute()).toThrow(
-      new Error(
-        `Worker node key chosen is null or undefined after ${workerChoiceStrategyContext.retries} retries`
-      )
-    )
-    const workerChoiceStrategyNullStub = createStubInstance(
-      RoundRobinWorkerChoiceStrategy,
-      {
-        choose: stub().returns(null)
-      }
-    )
-    workerChoiceStrategyContext.workerChoiceStrategies.set(
-      workerChoiceStrategyContext.workerChoiceStrategy,
-      workerChoiceStrategyNullStub
-    )
-    expect(() => workerChoiceStrategyContext.execute()).toThrow(
-      new Error(
-        `Worker node key chosen is null or undefined after ${workerChoiceStrategyContext.retries} retries`
-      )
-    )
-  })
-
-  it('Verify that execute() retry until a worker node is chosen', () => {
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      fixedPool
-    )
-    const workerChoiceStrategyStub = createStubInstance(
-      RoundRobinWorkerChoiceStrategy,
-      {
-        choose: stub()
-          .onCall(0)
-          .returns(undefined)
-          .onCall(1)
-          .returns(undefined)
-          .onCall(2)
-          .returns(undefined)
-          .onCall(3)
-          .returns(undefined)
-          .onCall(4)
-          .returns(undefined)
-          .returns(1)
-      }
-    )
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      WorkerChoiceStrategies.ROUND_ROBIN
-    )
-    workerChoiceStrategyContext.workerChoiceStrategies.set(
-      workerChoiceStrategyContext.workerChoiceStrategy,
-      workerChoiceStrategyStub
-    )
-    const chosenWorkerKey = workerChoiceStrategyContext.execute()
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategyContext.workerChoiceStrategy
-      ).choose.callCount
-    ).toBe(6)
-    expect(chosenWorkerKey).toBe(1)
-  })
-
-  it('Verify that execute() return the worker node key chosen by the strategy with fixed pool', () => {
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      fixedPool
-    )
-    const workerChoiceStrategyStub = createStubInstance(
-      RoundRobinWorkerChoiceStrategy,
-      {
-        choose: stub().returns(0)
-      }
-    )
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      WorkerChoiceStrategies.ROUND_ROBIN
-    )
-    workerChoiceStrategyContext.workerChoiceStrategies.set(
-      workerChoiceStrategyContext.workerChoiceStrategy,
-      workerChoiceStrategyStub
-    )
-    const chosenWorkerKey = workerChoiceStrategyContext.execute()
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategyContext.workerChoiceStrategy
-      ).choose.calledOnce
-    ).toBe(true)
-    expect(chosenWorkerKey).toBe(0)
-  })
-
-  it('Verify that execute() return the worker node key chosen by the strategy with dynamic pool', () => {
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      dynamicPool
-    )
-    const workerChoiceStrategyStub = createStubInstance(
-      RoundRobinWorkerChoiceStrategy,
-      {
-        choose: stub().returns(0)
-      }
-    )
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      WorkerChoiceStrategies.ROUND_ROBIN
-    )
-    workerChoiceStrategyContext.workerChoiceStrategies.set(
-      workerChoiceStrategyContext.workerChoiceStrategy,
-      workerChoiceStrategyStub
-    )
-    const chosenWorkerKey = workerChoiceStrategyContext.execute()
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategyContext.workerChoiceStrategy
-      ).choose.calledOnce
-    ).toBe(true)
-    expect(chosenWorkerKey).toBe(0)
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with ROUND_ROBIN and fixed pool', () => {
-    const workerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      fixedPool
-    )
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with ROUND_ROBIN and dynamic pool', () => {
-    const workerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      dynamicPool
-    )
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with LEAST_USED and fixed pool', () => {
-    const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_USED
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      fixedPool
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(LeastUsedWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with LEAST_USED and dynamic pool', () => {
-    const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_USED
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      dynamicPool
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(LeastUsedWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with LEAST_BUSY and fixed pool', () => {
-    const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_BUSY
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      fixedPool
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(LeastBusyWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with LEAST_BUSY and dynamic pool', () => {
-    const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_BUSY
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      dynamicPool
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(LeastBusyWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with LEAST_ELU and fixed pool', () => {
-    const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_ELU
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      fixedPool
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(LeastEluWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with LEAST_ELU and dynamic pool', () => {
-    const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_ELU
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      dynamicPool
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(LeastEluWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with FAIR_SHARE and fixed pool', () => {
-    const workerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      fixedPool
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(FairShareWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with FAIR_SHARE and dynamic pool', () => {
-    const workerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      dynamicPool
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(FairShareWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with WEIGHTED_ROUND_ROBIN and fixed pool', () => {
-    const workerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      fixedPool
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(WeightedRoundRobinWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with WEIGHTED_ROUND_ROBIN and dynamic pool', () => {
-    const workerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      dynamicPool
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(WeightedRoundRobinWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with INTERLEAVED_WEIGHTED_ROUND_ROBIN and fixed pool', () => {
-    const workerChoiceStrategy =
-      WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      fixedPool
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(InterleavedWeightedRoundRobinWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that setWorkerChoiceStrategy() works with INTERLEAVED_WEIGHTED_ROUND_ROBIN and dynamic pool', () => {
-    const workerChoiceStrategy =
-      WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN
-    const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      dynamicPool
-    )
-    workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategies.get(
-        workerChoiceStrategy
-      )
-    ).toBeInstanceOf(InterleavedWeightedRoundRobinWorkerChoiceStrategy)
-    expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
-      workerChoiceStrategy
-    )
-  })
-
-  it('Verify that worker choice strategy options enable median runtime pool statistics', () => {
-    const wwrWorkerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
-    let workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      fixedPool,
-      wwrWorkerChoiceStrategy,
-      {
-        runTime: { median: true }
-      }
-    )
-    expect(
-      workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime
-        .average
-    ).toBe(false)
-    expect(
-      workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median
-    ).toBe(true)
-    workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      dynamicPool,
-      wwrWorkerChoiceStrategy,
-      {
-        runTime: { median: true }
-      }
-    )
-    expect(
-      workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime
-        .average
-    ).toBe(false)
-    expect(
-      workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median
-    ).toBe(true)
-    const fsWorkerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE
-    workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      fixedPool,
-      fsWorkerChoiceStrategy,
-      {
-        runTime: { median: true }
-      }
-    )
-    expect(
-      workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime
-        .average
-    ).toBe(false)
-    expect(
-      workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median
-    ).toBe(true)
-    workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
-      dynamicPool,
-      fsWorkerChoiceStrategy,
-      {
-        runTime: { median: true }
-      }
-    )
-    expect(
-      workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime
-        .average
-    ).toBe(false)
-    expect(
-      workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median
-    ).toBe(true)
-  })
-})
index 6cefa0aad2486726fa7e25e9093aa200c7bc7f97..22d89223f337c0d7a714811fa3b37c09ecd6b861 100644 (file)
@@ -119,8 +119,9 @@ describe('Dynamic thread pool test suite', () => {
     await waitWorkerEvents(longRunningPool, 'exit', max - min)
     expect(longRunningPool.workerNodes.length).toBe(min)
     expect(
-      longRunningPool.workerChoiceStrategyContext.workerChoiceStrategies.get(
-        longRunningPool.workerChoiceStrategyContext.workerChoiceStrategy
+      longRunningPool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
+        longRunningPool.workerChoiceStrategiesContext
+          .defaultWorkerChoiceStrategy
       ).nextWorkerNodeKey
     ).toBeLessThan(longRunningPool.workerNodes.length)
     // We need to clean up the resources after our test
index c6cc06d31487f2b492850203fb64ceda79d2ae87..cfa378e16aa37d81b1a4cbb51bfbe7c2a57babc7 100644 (file)
@@ -7,17 +7,11 @@ import {
   CircularArray,
   DEFAULT_CIRCULAR_ARRAY_SIZE
 } from '../../lib/circular-array.cjs'
+import { WorkerTypes } from '../../lib/index.cjs'
 import {
-  FixedClusterPool,
-  FixedThreadPool,
-  WorkerTypes
-} from '../../lib/index.cjs'
-import {
-  buildWorkerChoiceStrategyOptions,
   createWorker,
   DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
   getDefaultTasksQueueOptions,
-  getWorkerChoiceStrategyRetries,
   getWorkerId,
   getWorkerType,
   updateMeasurementStatistics
@@ -43,61 +37,6 @@ describe('Pool utils test suite', () => {
     })
   })
 
-  it('Verify getWorkerChoiceStrategyRetries() behavior', async () => {
-    const numberOfThreads = 4
-    const pool = new FixedThreadPool(
-      numberOfThreads,
-      './tests/worker-files/thread/testWorker.mjs'
-    )
-    expect(getWorkerChoiceStrategyRetries(pool)).toBe(pool.info.maxSize * 2)
-    const workerChoiceStrategyOptions = {
-      runTime: { median: true },
-      waitTime: { median: true },
-      elu: { median: true },
-      weights: {
-        0: 100,
-        1: 100
-      }
-    }
-    expect(
-      getWorkerChoiceStrategyRetries(pool, workerChoiceStrategyOptions)
-    ).toBe(
-      pool.info.maxSize +
-        Object.keys(workerChoiceStrategyOptions.weights).length
-    )
-    await pool.destroy()
-  })
-
-  it('Verify buildWorkerChoiceStrategyOptions() behavior', async () => {
-    const numberOfWorkers = 4
-    const pool = new FixedClusterPool(
-      numberOfWorkers,
-      './tests/worker-files/cluster/testWorker.cjs'
-    )
-    expect(buildWorkerChoiceStrategyOptions(pool)).toStrictEqual({
-      runTime: { median: false },
-      waitTime: { median: false },
-      elu: { median: false },
-      weights: expect.objectContaining({
-        0: expect.any(Number),
-        [pool.info.maxSize - 1]: expect.any(Number)
-      })
-    })
-    const workerChoiceStrategyOptions = {
-      runTime: { median: true },
-      waitTime: { median: true },
-      elu: { median: true },
-      weights: {
-        0: 100,
-        1: 100
-      }
-    }
-    expect(
-      buildWorkerChoiceStrategyOptions(pool, workerChoiceStrategyOptions)
-    ).toStrictEqual(workerChoiceStrategyOptions)
-    await pool.destroy()
-  })
-
   it('Verify updateMeasurementStatistics() behavior', () => {
     const measurementStatistics = {
       history: new CircularArray()
index 5633530f35d6fd0d06c1cae72af18405daabb997..6aa889baa9bc4b1edb48d0dbcbd6d79592398f4b 100644 (file)
@@ -214,18 +214,25 @@ describe('Worker node test suite', () => {
       threadWorkerNode.getTaskFunctionWorkerUsage('invalidTaskFunction')
     ).toThrow(
       new TypeError(
-        "Cannot get task function worker usage for task function name 'invalidTaskFunction' when task function names list is not yet defined"
+        "Cannot get task function worker usage for task function name 'invalidTaskFunction' when task function properties list is not yet defined"
       )
     )
-    threadWorkerNode.info.taskFunctionNames = [DEFAULT_TASK_NAME, 'fn1']
+    threadWorkerNode.info.taskFunctionsProperties = [
+      { name: DEFAULT_TASK_NAME },
+      { name: 'fn1' }
+    ]
     expect(() =>
       threadWorkerNode.getTaskFunctionWorkerUsage('invalidTaskFunction')
     ).toThrow(
       new TypeError(
-        "Cannot get task function worker usage for task function name 'invalidTaskFunction' when task function names list has less than 3 elements"
+        "Cannot get task function worker usage for task function name 'invalidTaskFunction' when task function properties list has less than 3 elements"
       )
     )
-    threadWorkerNode.info.taskFunctionNames = [DEFAULT_TASK_NAME, 'fn1', 'fn2']
+    threadWorkerNode.info.taskFunctionsProperties = [
+      { name: DEFAULT_TASK_NAME },
+      { name: 'fn1' },
+      { name: 'fn2' }
+    ]
     expect(
       threadWorkerNode.getTaskFunctionWorkerUsage(DEFAULT_TASK_NAME)
     ).toStrictEqual({
@@ -304,10 +311,10 @@ describe('Worker node test suite', () => {
   })
 
   it('Worker node deleteTaskFunctionWorkerUsage()', () => {
-    expect(threadWorkerNode.info.taskFunctionNames).toStrictEqual([
-      DEFAULT_TASK_NAME,
-      'fn1',
-      'fn2'
+    expect(threadWorkerNode.info.taskFunctionsProperties).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'fn1' },
+      { name: 'fn2' }
     ])
     expect(threadWorkerNode.taskFunctionsUsage.size).toBe(2)
     expect(
diff --git a/tests/priority-queue.test.mjs b/tests/priority-queue.test.mjs
new file mode 100644 (file)
index 0000000..6b6c5f4
--- /dev/null
@@ -0,0 +1,197 @@
+import { expect } from 'expect'
+
+// eslint-disable-next-line n/no-missing-import, import/no-unresolved
+import { PriorityQueue } from '../lib/priority-queue.cjs'
+
+describe.skip('Priority queue test suite', () => {
+  it('Verify constructor() behavior', () => {
+    expect(() => new PriorityQueue('')).toThrow(
+      new TypeError('k must be an integer')
+    )
+    expect(() => new PriorityQueue(-1)).toThrow(
+      new RangeError('k must be greater than or equal to 1')
+    )
+    expect(() => new PriorityQueue(0)).toThrow(
+      new RangeError('k must be greater than or equal to 1')
+    )
+    let priorityQueue = new PriorityQueue()
+    expect(priorityQueue.k).toBe(Infinity)
+    expect(priorityQueue.size).toBe(0)
+    expect(priorityQueue.maxSize).toBe(0)
+    expect(priorityQueue.nodeArray).toStrictEqual([])
+    priorityQueue = new PriorityQueue(2)
+    expect(priorityQueue.k).toBe(2)
+    expect(priorityQueue.size).toBe(0)
+    expect(priorityQueue.maxSize).toBe(0)
+    expect(priorityQueue.nodeArray).toStrictEqual([])
+  })
+
+  it('Verify default k enqueue() behavior', () => {
+    const priorityQueue = new PriorityQueue()
+    let rtSize = priorityQueue.enqueue(1)
+    expect(priorityQueue.size).toBe(1)
+    expect(priorityQueue.maxSize).toBe(1)
+    expect(rtSize).toBe(priorityQueue.size)
+    expect(priorityQueue.nodeArray).toStrictEqual([{ data: 1, priority: 0 }])
+    rtSize = priorityQueue.enqueue(2)
+    expect(priorityQueue.size).toBe(2)
+    expect(priorityQueue.maxSize).toBe(2)
+    expect(rtSize).toBe(priorityQueue.size)
+    expect(priorityQueue.nodeArray).toStrictEqual([
+      { data: 1, priority: 0 },
+      { data: 2, priority: 0 }
+    ])
+    rtSize = priorityQueue.enqueue(3)
+    expect(priorityQueue.size).toBe(3)
+    expect(priorityQueue.maxSize).toBe(3)
+    expect(rtSize).toBe(priorityQueue.size)
+    expect(priorityQueue.nodeArray).toStrictEqual([
+      { data: 1, priority: 0 },
+      { data: 2, priority: 0 },
+      { data: 3, priority: 0 }
+    ])
+    rtSize = priorityQueue.enqueue(3, -1)
+    expect(priorityQueue.size).toBe(4)
+    expect(priorityQueue.maxSize).toBe(4)
+    expect(rtSize).toBe(priorityQueue.size)
+    expect(priorityQueue.nodeArray).toStrictEqual([
+      { data: 3, priority: -1 },
+      { data: 1, priority: 0 },
+      { data: 2, priority: 0 },
+      { data: 3, priority: 0 }
+    ])
+    rtSize = priorityQueue.enqueue(1, 1)
+    expect(priorityQueue.size).toBe(5)
+    expect(priorityQueue.maxSize).toBe(5)
+    expect(rtSize).toBe(priorityQueue.size)
+    expect(priorityQueue.nodeArray).toStrictEqual([
+      { data: 3, priority: -1 },
+      { data: 1, priority: 0 },
+      { data: 2, priority: 0 },
+      { data: 3, priority: 0 },
+      { data: 1, priority: 1 }
+    ])
+  })
+
+  it('Verify k=2 enqueue() behavior', () => {
+    const priorityQueue = new PriorityQueue(2)
+    let rtSize = priorityQueue.enqueue(1)
+    expect(priorityQueue.size).toBe(1)
+    expect(priorityQueue.maxSize).toBe(1)
+    expect(rtSize).toBe(priorityQueue.size)
+    expect(priorityQueue.nodeArray).toStrictEqual([{ data: 1, priority: 0 }])
+    rtSize = priorityQueue.enqueue(2)
+    expect(priorityQueue.size).toBe(2)
+    expect(priorityQueue.maxSize).toBe(2)
+    expect(rtSize).toBe(priorityQueue.size)
+    expect(priorityQueue.nodeArray).toStrictEqual([
+      { data: 1, priority: 0 },
+      { data: 2, priority: 0 }
+    ])
+    rtSize = priorityQueue.enqueue(3)
+    expect(priorityQueue.size).toBe(3)
+    expect(priorityQueue.maxSize).toBe(3)
+    expect(rtSize).toBe(priorityQueue.size)
+    expect(priorityQueue.nodeArray).toStrictEqual([
+      { data: 1, priority: 0 },
+      { data: 2, priority: 0 },
+      { data: 3, priority: 0 }
+    ])
+    rtSize = priorityQueue.enqueue(3, -1)
+    expect(priorityQueue.size).toBe(4)
+    expect(priorityQueue.maxSize).toBe(4)
+    expect(rtSize).toBe(priorityQueue.size)
+    expect(priorityQueue.nodeArray).toStrictEqual([
+      { data: 1, priority: 0 },
+      { data: 2, priority: 0 },
+      { data: 3, priority: -1 },
+      { data: 3, priority: 0 }
+    ])
+    rtSize = priorityQueue.enqueue(1, 1)
+    expect(priorityQueue.size).toBe(5)
+    expect(priorityQueue.maxSize).toBe(5)
+    expect(rtSize).toBe(priorityQueue.size)
+    expect(priorityQueue.nodeArray).toStrictEqual([
+      { data: 1, priority: 0 },
+      { data: 2, priority: 0 },
+      { data: 3, priority: -1 },
+      { data: 3, priority: 0 },
+      { data: 1, priority: 1 }
+    ])
+    rtSize = priorityQueue.enqueue(2, -2)
+    expect(priorityQueue.size).toBe(6)
+    expect(priorityQueue.maxSize).toBe(6)
+    expect(rtSize).toBe(priorityQueue.size)
+    expect(priorityQueue.nodeArray).toStrictEqual([
+      { data: 1, priority: 0 },
+      { data: 2, priority: 0 },
+      { data: 3, priority: -1 },
+      { data: 3, priority: 0 },
+      { data: 2, priority: -2 },
+      { data: 1, priority: 1 }
+    ])
+  })
+
+  it('Verify dequeue() behavior', () => {
+    const priorityQueue = new PriorityQueue()
+    priorityQueue.enqueue(1)
+    priorityQueue.enqueue(2, -1)
+    priorityQueue.enqueue(3)
+    let rtItem = priorityQueue.dequeue()
+    expect(priorityQueue.size).toBe(2)
+    expect(priorityQueue.maxSize).toBe(3)
+    expect(rtItem).toBe(2)
+    expect(priorityQueue.nodeArray).toStrictEqual([
+      { data: 1, priority: 0 },
+      { data: 3, priority: 0 }
+    ])
+    rtItem = priorityQueue.dequeue()
+    expect(priorityQueue.size).toBe(1)
+    expect(priorityQueue.maxSize).toBe(3)
+    expect(rtItem).toBe(1)
+    expect(priorityQueue.nodeArray).toStrictEqual([{ data: 3, priority: 0 }])
+    rtItem = priorityQueue.dequeue()
+    expect(priorityQueue.size).toBe(0)
+    expect(priorityQueue.maxSize).toBe(3)
+    expect(rtItem).toBe(3)
+    expect(priorityQueue.nodeArray).toStrictEqual([])
+  })
+
+  it('Verify peekFirst() behavior', () => {
+    const priorityQueue = new PriorityQueue()
+    priorityQueue.enqueue(1)
+    priorityQueue.enqueue(2)
+    priorityQueue.enqueue(3)
+    expect(priorityQueue.size).toBe(3)
+    expect(priorityQueue.peekFirst()).toBe(1)
+    expect(priorityQueue.size).toBe(3)
+  })
+
+  it('Verify peekLast() behavior', () => {
+    const priorityQueue = new PriorityQueue()
+    priorityQueue.enqueue(1)
+    priorityQueue.enqueue(2)
+    priorityQueue.enqueue(3)
+    expect(priorityQueue.size).toBe(3)
+    expect(priorityQueue.peekLast()).toBe(3)
+    expect(priorityQueue.size).toBe(3)
+  })
+
+  it('Verify clear() behavior', () => {
+    const priorityQueue = new PriorityQueue()
+    priorityQueue.enqueue(1)
+    priorityQueue.enqueue(2)
+    priorityQueue.enqueue(3)
+    expect(priorityQueue.size).toBe(3)
+    expect(priorityQueue.maxSize).toBe(3)
+    expect(priorityQueue.nodeArray).toStrictEqual([
+      { data: 1, priority: 0 },
+      { data: 2, priority: 0 },
+      { data: 3, priority: 0 }
+    ])
+    priorityQueue.clear()
+    expect(priorityQueue.size).toBe(0)
+    expect(priorityQueue.maxSize).toBe(0)
+    expect(priorityQueue.nodeArray).toStrictEqual([])
+  })
+})
index 781f7790a1bfcbd3b9bc63657484b1fc7f667fce..b2aa996dfaa8764f6fd27c693d708807e9d790fc 100644 (file)
@@ -1,7 +1,12 @@
 import { expect } from 'expect'
 import { restore, stub } from 'sinon'
 
-import { ClusterWorker, KillBehaviors, ThreadWorker } from '../../lib/index.cjs'
+import {
+  ClusterWorker,
+  KillBehaviors,
+  ThreadWorker,
+  WorkerChoiceStrategies
+} from '../../lib/index.cjs'
 import { DEFAULT_TASK_NAME, EMPTY_FUNCTION } from '../../lib/utils.cjs'
 
 describe('Abstract worker test suite', () => {
@@ -131,9 +136,13 @@ describe('Abstract worker test suite', () => {
   })
 
   it('Verify that taskFunctions parameter with unique function is taken', () => {
-    const worker = new ThreadWorker(() => {})
-    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
+    const worker = new ThreadWorker(EMPTY_FUNCTION)
+    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn1')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
     expect(worker.taskFunctions.size).toBe(2)
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
       worker.taskFunctions.get('fn1')
@@ -149,7 +158,36 @@ describe('Abstract worker test suite', () => {
       new TypeError('A taskFunctions parameter object key is an empty string')
     )
     expect(() => new ThreadWorker({ fn1, fn2 })).toThrow(
-      new TypeError('A taskFunctions parameter object value is not a function')
+      new TypeError(
+        "taskFunction object 'taskFunction' property 'undefined' is not a function"
+      )
+    )
+    expect(() => new ThreadWorker({ fn1: { fn1 } })).toThrow(
+      new TypeError(
+        "taskFunction object 'taskFunction' property 'undefined' is not a function"
+      )
+    )
+    expect(() => new ThreadWorker({ fn2: { taskFunction: fn2 } })).toThrow(
+      new TypeError(
+        "taskFunction object 'taskFunction' property '' is not a function"
+      )
+    )
+    expect(
+      () => new ThreadWorker({ fn1: { taskFunction: fn1, priority: '' } })
+    ).toThrow(new TypeError("Invalid property 'priority': ''"))
+    expect(
+      () => new ThreadWorker({ fn1: { taskFunction: fn1, priority: -21 } })
+    ).toThrow(new TypeError("Property 'priority' must be between -20 and 19"))
+    expect(
+      () => new ThreadWorker({ fn1: { taskFunction: fn1, priority: 20 } })
+    ).toThrow(new RangeError("Property 'priority' must be between -20 and 19"))
+    expect(
+      () =>
+        new ThreadWorker({
+          fn1: { taskFunction: fn1, strategy: 'invalidStrategy' }
+        })
+    ).toThrow(
+      new RangeError("Invalid worker choice strategy 'invalidStrategy'")
     )
   })
 
@@ -161,9 +199,42 @@ describe('Abstract worker test suite', () => {
       return 2
     }
     const worker = new ClusterWorker({ fn1, fn2 })
-    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
+    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn1')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn2')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.size).toBe(3)
+    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
+      worker.taskFunctions.get('fn1')
+    )
+  })
+
+  it('Verify that taskFunctions parameter with multiple task functions object is taken', () => {
+    const fn1Obj = {
+      taskFunction: () => {
+        return 1
+      },
+      priority: 5
+    }
+    const fn2Obj = {
+      taskFunction: () => {
+        return 2
+      },
+      priority: 6,
+      strategy: WorkerChoiceStrategies.LESS_BUSY
+    }
+    const worker = new ThreadWorker({
+      fn1: fn1Obj,
+      fn2: fn2Obj
+    })
+    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(fn1Obj)
+    expect(worker.taskFunctions.get('fn1')).toStrictEqual(fn1Obj)
+    expect(worker.taskFunctions.get('fn2')).toStrictEqual(fn2Obj)
     expect(worker.taskFunctions.size).toBe(3)
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
       worker.taskFunctions.get('fn1')
@@ -231,10 +302,16 @@ describe('Abstract worker test suite', () => {
     })
     expect(worker.addTaskFunction('fn3', '')).toStrictEqual({
       status: false,
-      error: new TypeError('fn parameter is not a function')
+      error: new TypeError(
+        "taskFunction object 'taskFunction' property 'undefined' is not a function"
+      )
+    })
+    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn1')).toStrictEqual({
+      taskFunction: expect.any(Function)
     })
-    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
     expect(worker.taskFunctions.size).toBe(2)
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
       worker.taskFunctions.get('fn1')
@@ -246,24 +323,36 @@ describe('Abstract worker test suite', () => {
       )
     })
     worker.addTaskFunction('fn2', fn2)
-    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
+    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn1')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn2')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
     expect(worker.taskFunctions.size).toBe(3)
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
       worker.taskFunctions.get('fn1')
     )
     worker.addTaskFunction('fn1', fn1Replacement)
-    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
+    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn1')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn2')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
     expect(worker.taskFunctions.size).toBe(3)
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
       worker.taskFunctions.get('fn1')
     )
   })
 
-  it('Verify that listTaskFunctionNames() is working', () => {
+  it('Verify that listTaskFunctionsProperties() is working', () => {
     const fn1 = () => {
       return 1
     }
@@ -271,10 +360,10 @@ describe('Abstract worker test suite', () => {
       return 2
     }
     const worker = new ClusterWorker({ fn1, fn2 })
-    expect(worker.listTaskFunctionNames()).toStrictEqual([
-      DEFAULT_TASK_NAME,
-      'fn1',
-      'fn2'
+    expect(worker.listTaskFunctionsProperties()).toStrictEqual([
+      { name: DEFAULT_TASK_NAME },
+      { name: 'fn1' },
+      { name: 'fn2' }
     ])
   })
 
@@ -294,9 +383,15 @@ describe('Abstract worker test suite', () => {
       status: false,
       error: new TypeError('name parameter is an empty string')
     })
-    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
+    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn1')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn2')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
     expect(worker.taskFunctions.size).toBe(3)
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
       worker.taskFunctions.get('fn1')
index 228a969a574473c1ab78fa5e6c502baa8a1b9871..70d63639dab40d0a61622f73a8e5da18809c7ec7 100644 (file)
@@ -52,9 +52,15 @@ describe('Cluster worker test suite', () => {
       status: false,
       error: new TypeError('name parameter is an empty string')
     })
-    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
+    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn1')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn2')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
     expect(worker.taskFunctions.size).toBe(3)
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
       worker.taskFunctions.get('fn1')
@@ -72,8 +78,12 @@ describe('Cluster worker test suite', () => {
       )
     })
     worker.removeTaskFunction('fn2')
-    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
+    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn1')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
     expect(worker.taskFunctions.get('fn2')).toBeUndefined()
     expect(worker.taskFunctions.size).toBe(2)
     expect(worker.getMainWorker.calledTwice).toBe(true)
index 6d694ec6464c412d1d3860d246438689abb44e47..3151b90a4a04d367e262f9f3cee8bf8569bc25c4 100644 (file)
@@ -53,9 +53,15 @@ describe('Thread worker test suite', () => {
       status: false,
       error: new TypeError('name parameter is an empty string')
     })
-    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
+    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn1')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn2')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
     expect(worker.taskFunctions.size).toBe(3)
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
       worker.taskFunctions.get('fn1')
@@ -73,8 +79,12 @@ describe('Thread worker test suite', () => {
       )
     })
     worker.removeTaskFunction('fn2')
-    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
-    expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
+    expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
+    expect(worker.taskFunctions.get('fn1')).toStrictEqual({
+      taskFunction: expect.any(Function)
+    })
     expect(worker.taskFunctions.get('fn2')).toBeUndefined()
     expect(worker.taskFunctions.size).toBe(2)
     expect(worker.port.postMessage.calledOnce).toBe(true)